pool/stratum/coind_template.cpp
Tanguy Pruvot cbe55a3a84 Squashed commit for segwit support:
commit c59abe5d203fabdabcca81ff5f9c6ff133cfae3b
Author: Tanguy Pruvot <tanguy.pruvot@gmail.com>
Date:   Tue Nov 28 11:13:52 2017 +0100

    segwit: show a segwit icon on blocks history

    + remove some inline styles...

commit b4a8639370e6837ebc5a2047e7c334e9f931abfc
Author: Tanguy Pruvot <tanguy.pruvot@gmail.com>
Date:   Tue Nov 28 09:55:40 2017 +0100

    segwit: cleanup + masternode case

    tested ok with BSD (block 400996), and with real BTX segwit txs (block 90958)

    also ok on VTC and GRS

commit 926dbd11757ebff7f7d4930266de9b9061c8ab16
Author: Tanguy Pruvot <tanguy.pruvot@gmail.com>
Date:   Sat Nov 25 18:41:01 2017 +0100

    sql: add segwit fields, and ui config

    and fill block segwit field if it contains segwit txs,
    an icon is added in the dashboard "last blocks" for these blocks

commit 0b13bf55e9dd1d2229d188f0f8382b27642b20da
Author: Tanguy Pruvot <tanguy.pruvot@gmail.com>
Date:   Sat Nov 25 13:47:20 2017 +0100

    segwit: include commitment in coinbase + .conf toggle

    tested ok on BTX, GRS and VTC with normal txs, but the commitment merkle hash maybe need some more love...

    so, to prevent useless bigger blocks, only generate segwit commitment if a segwit tx is present in mempool

    to check with real segwit txs... not seen any yet..

commit b508bc87943d9e426cda994c2f53c16c11e8d4c3
Author: Tanguy Pruvot <tanguy.pruvot@gmail.com>
Date:   Thu Mar 2 11:18:34 2017 +0100

    segwit: prepare the witness data, but disabled

    need more test, may affect the coinbase merkle and the miners...

commit 19bd3a83b9ddddd8b5ed4b7a1bdf8cf8c233e346
Author: Tanguy Pruvot <tanguy.pruvot@gmail.com>
Date:   Thu Mar 2 10:30:29 2017 +0100

    stratum: handle and auto toggle segwit if supported
2017-11-28 11:55:20 +01:00

584 lines
17 KiB
C++

#include "stratum.h"
void coind_getauxblock(YAAMP_COIND *coind)
{
if(!coind->isaux) return;
json_value *json = rpc_call(&coind->rpc, "getauxblock", "[]");
if(!json)
{
coind_error(coind, "coind_getauxblock");
return;
}
json_value *json_result = json_get_object(json, "result");
if(!json_result)
{
coind_error(coind, "coind_getauxblock");
return;
}
// coind->aux.height = coind->height+1;
coind->aux.chainid = json_get_int(json_result, "chainid");
const char *p = json_get_string(json_result, "target");
if(p) strcpy(coind->aux.target, p);
p = json_get_string(json_result, "hash");
if(p) strcpy(coind->aux.hash, p);
// if(strcmp(coind->symbol, "UNO") == 0)
// {
// string_be1(coind->aux.target);
// string_be1(coind->aux.hash);
// }
json_value_free(json);
}
YAAMP_JOB_TEMPLATE *coind_create_template_memorypool(YAAMP_COIND *coind)
{
json_value *json = rpc_call(&coind->rpc, "getmemorypool");
if(!json || json->type == json_null)
{
coind_error(coind, "getmemorypool");
return NULL;
}
json_value *json_result = json_get_object(json, "result");
if(!json_result || json_result->type == json_null)
{
coind_error(coind, "getmemorypool");
json_value_free(json);
return NULL;
}
YAAMP_JOB_TEMPLATE *templ = new YAAMP_JOB_TEMPLATE;
memset(templ, 0, sizeof(YAAMP_JOB_TEMPLATE));
templ->created = time(NULL);
templ->value = json_get_int(json_result, "coinbasevalue");
// templ->height = json_get_int(json_result, "height");
sprintf(templ->version, "%08x", (unsigned int)json_get_int(json_result, "version"));
sprintf(templ->ntime, "%08x", (unsigned int)json_get_int(json_result, "time"));
strcpy(templ->nbits, json_get_string(json_result, "bits"));
strcpy(templ->prevhash_hex, json_get_string(json_result, "previousblockhash"));
json_value_free(json);
json = rpc_call(&coind->rpc, "getinfo", "[]");
if(!json || json->type == json_null)
{
coind_error(coind, "coind_getinfo");
return NULL;
}
json_result = json_get_object(json, "result");
if(!json_result || json_result->type == json_null)
{
coind_error(coind, "coind_getinfo");
json_value_free(json);
return NULL;
}
templ->height = json_get_int(json_result, "blocks")+1;
json_value_free(json);
coind_getauxblock(coind);
coind->usememorypool = true;
return templ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
static int decred_parse_header(YAAMP_JOB_TEMPLATE *templ, const char *header_hex, bool getwork)
{
struct __attribute__((__packed__)) {
uint32_t version;
char prevblock[32];
char merkleroot[32];
char stakeroot[32];
uint16_t votebits;
char finalstate[6];
uint16_t voters;
uint8_t freshstake;
uint8_t revoc;
uint32_t poolsize;
uint32_t nbits;
uint64_t sbits;
uint32_t height;
uint32_t size;
uint32_t ntime;
uint32_t nonce;
unsigned char extra[32];
uint32_t stakever;
uint32_t hashtag[3];
} header;
//debuglog("HEADER: %s\n", header_hex);
binlify((unsigned char*) &header, header_hex);
templ->height = header.height;
// reversed to tell its not a normal stratum coinbase
sprintf(templ->version, "%08x", getwork ? bswap32(header.version) : header.version);
sprintf(templ->ntime, "%08x", header.ntime);
sprintf(templ->nbits, "%08x", header.nbits);
templ->prevhash_hex[64] = '\0';
uint32_t* prev32 = (uint32_t*) header.prevblock;
for(int i=0; i < 8; i++)
sprintf(&templ->prevhash_hex[i*8], "%08x", getwork ? prev32[7-i] : bswap32(prev32[7-i]));
ser_string_be2(templ->prevhash_hex, templ->prevhash_be, 8);
// store all other stuff
memcpy(templ->header, &header, sizeof(header));
return 0;
}
// decred getwork over stratum
static YAAMP_JOB_TEMPLATE *decred_create_worktemplate(YAAMP_COIND *coind)
{
char rpc_error[1024] = { 0 };
#define GETWORK_RETRY_MAX 3
int retry_cnt = GETWORK_RETRY_MAX;
retry:
json_value *gw = rpc_call(&coind->rpc, "getwork", "[]");
if(!gw || json_is_null(gw)) {
usleep(500*YAAMP_MS); // too much connections ? no data received
if (--retry_cnt > 0) {
if (coind->rpc.curl)
rpc_curl_get_lasterr(rpc_error, 1023);
debuglog("%s getwork retry %d\n", coind->symbol, GETWORK_RETRY_MAX-retry_cnt);
goto retry;
}
debuglog("%s error getwork %s\n", coind->symbol, rpc_error);
return NULL;
}
json_value *gwr = json_get_object(gw, "result");
if(!gwr) {
debuglog("%s no getwork json result!\n", coind->symbol);
return NULL;
}
else if (json_is_null(gwr)) {
json_value *jr = json_get_object(gw, "error");
if (!jr || json_is_null(jr)) return NULL;
const char *err = json_get_string(jr, "message");
if (err && !strcmp(err, "internal error")) {
usleep(500*YAAMP_MS); // not enough voters (testnet)
if (--retry_cnt > 0) {
goto retry;
}
debuglog("%s getwork failed after %d tries: %s\n",
coind->symbol, GETWORK_RETRY_MAX, err);
}
return NULL;
}
const char *header_hex = json_get_string(gwr, "data");
if (!header_hex || !strlen(header_hex)) {
debuglog("%s no getwork data!\n", coind->symbol);
return NULL;
}
YAAMP_JOB_TEMPLATE *templ = new YAAMP_JOB_TEMPLATE;
memset(templ, 0, sizeof(YAAMP_JOB_TEMPLATE));
templ->created = time(NULL);
decred_parse_header(templ, header_hex, true);
json_value_free(gw);
// bypass coinbase and merkle for now... send without nonce/extradata
const unsigned char *hdr = (unsigned char *) &templ->header[36];
hexlify(templ->coinb1, hdr, 192 - 80);
const unsigned char *sfx = (unsigned char *) &templ->header[176];
hexlify(templ->coinb2, sfx, 180 - 176); // stake version
vector<string> txhashes;
txhashes.push_back("");
templ->txmerkles[0] = 0;
templ->txcount = txhashes.size();
templ->txsteps = merkle_steps(txhashes);
txhashes.clear();
return templ;
}
// for future decred real stratum
static void decred_fix_template(YAAMP_COIND *coind, YAAMP_JOB_TEMPLATE *templ, json_value *json)
{
const char *header_hex = json_get_string(json, "header");
if (!header_hex || !strlen(header_hex)) {
stratumlog("decred error, no block header in json!\n");
return;
}
// todo ?
// "mintime": 1455511962,
// "maxtime": 1455522081,
decred_parse_header(templ, header_hex, false);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
YAAMP_JOB_TEMPLATE *coind_create_template(YAAMP_COIND *coind)
{
if(coind->usememorypool)
return coind_create_template_memorypool(coind);
char params[512] = "[{}]";
if(!strcmp(coind->symbol, "PPC")) strcpy(params, "[]");
else if(g_stratum_segwit) strcpy(params, "[{\"rules\":[\"segwit\"]}]");
json_value *json = rpc_call(&coind->rpc, "getblocktemplate", params);
if(!json || json_is_null(json))
{
// coind_error() reset auto_ready, and DCR gbt can fail
if (strcmp(coind->rpcencoding, "DCR") == 0)
debuglog("decred getblocktemplate failed\n");
else
coind_error(coind, "getblocktemplate");
return NULL;
}
json_value *json_result = json_get_object(json, "result");
if(!json_result || json_is_null(json_result))
{
coind_error(coind, "getblocktemplate result");
json_value_free(json);
return NULL;
}
// segwit rule
json_value *json_rules = json_get_array(json_result, "rules");
if(json_rules && !strlen(coind->witness_magic) && json_rules->u.array.length) {
for (int i=0; i<json_rules->u.array.length; i++) {
json_value *val = json_rules->u.array.values[i];
if(!strcmp(val->u.string.ptr, "segwit")) {
const char *commitment = json_get_string(json_result, "default_witness_commitment");
strcpy(coind->witness_magic, "aa21a9ed");
if (commitment && strlen(commitment) > 12) {
strncpy(coind->witness_magic, &commitment[4], 8);
coind->witness_magic[8] = '\0';
}
coind->usesegwit |= g_stratum_segwit;
if (coind->usesegwit)
debuglog("%s segwit enabled, magic %s\n", coind->symbol, coind->witness_magic);
break;
}
}
}
json_value *json_tx = json_get_array(json_result, "transactions");
if(!json_tx)
{
coind_error(coind, "getblocktemplate transactions");
json_value_free(json);
return NULL;
}
json_value *json_coinbaseaux = json_get_object(json_result, "coinbaseaux");
if(!json_coinbaseaux && coind->isaux)
{
coind_error(coind, "getblocktemplate coinbaseaux");
json_value_free(json);
return NULL;
}
YAAMP_JOB_TEMPLATE *templ = new YAAMP_JOB_TEMPLATE;
memset(templ, 0, sizeof(YAAMP_JOB_TEMPLATE));
templ->created = time(NULL);
templ->value = json_get_int(json_result, "coinbasevalue");
templ->height = json_get_int(json_result, "height");
sprintf(templ->version, "%08x", (unsigned int)json_get_int(json_result, "version"));
sprintf(templ->ntime, "%08x", (unsigned int)json_get_int(json_result, "curtime"));
const char *bits = json_get_string(json_result, "bits");
strcpy(templ->nbits, bits ? bits : "");
const char *prev = json_get_string(json_result, "previousblockhash");
strcpy(templ->prevhash_hex, prev ? prev : "");
const char *flags = json_get_string(json_coinbaseaux, "flags");
strcpy(templ->flags, flags ? flags : "");
// LBC Claim Tree (with wallet gbt patch)
const char *claim = json_get_string(json_result, "claimtrie");
if (claim) {
strcpy(templ->claim_hex, claim);
// debuglog("claimtrie: %s\n", templ->claim_hex);
}
else if (strcmp(coind->symbol, "LBC") == 0) {
json_value *json_claim = rpc_call(&coind->rpc, "getclaimtrie");
if (!json_claim || json_claim->type != json_object)
return NULL;
json_value *json_cls = json_get_array(json_claim, "result");
if (!json_cls || !json_is_array(json_cls))
return NULL;
// get first claim "", seems the root
// if empty need 0000000000000000000000000000000000000000000000000000000000000001
json_value *json_obj = json_cls->u.array.values[0];
if (!json_obj || json_claim->type != json_object)
return NULL;
claim = json_get_string(json_obj, "hash");
if (claim) {
strcpy(templ->claim_hex, claim);
debuglog("claim_hex: %s\n", templ->claim_hex);
}
}
if (strcmp(coind->rpcencoding, "DCR") == 0) {
decred_fix_template(coind, templ, json_result);
}
if (!templ->height || !templ->nbits || !strlen(templ->prevhash_hex)) {
stratumlog("%s warning, gbt incorrect : version=%s height=%d value=%d bits=%s time=%s prev=%s\n",
coind->symbol, templ->version, templ->height, templ->value, templ->nbits, templ->ntime, templ->prevhash_hex);
}
// temporary hack, until wallet is fixed...
if (!strcmp(coind->symbol, "MBL")) { // MBL: chainid in version
unsigned int nVersion = (unsigned int)json_get_int(json_result, "version");
if (nVersion & 0xFFFF0000UL == 0) {
nVersion |= (0x16UL << 16);
debuglog("%s version %s >> %08x\n", coind->symbol, templ->version, nVersion);
}
sprintf(templ->version, "%08x", nVersion);
}
// debuglog("%s ntime %s\n", coind->symbol, templ->ntime);
// uint64_t target = decode_compact(json_get_string(json_result, "bits"));
// coind->difficulty = target_to_diff(target);
// string_lower(templ->ntime);
// string_lower(templ->nbits);
// char target[1024];
// strcpy(target, json_get_string(json_result, "target"));
// uint64_t coin_target = decode_compact(templ->nbits);
// debuglog("nbits %s\n", templ->nbits);
// debuglog("target %s\n", target);
// debuglog("0000%016llx\n", coin_target);
if(coind->isaux)
{
json_value_free(json);
coind_getauxblock(coind);
return templ;
}
//////////////////////////////////////////////////////////////////////////////////////////
vector<string> txhashes;
vector<string> txids;
txhashes.push_back("");
txids.push_back("");
templ->has_segwit_txs = false;
// to force/test
// templ->has_segwit_txs = coind->usesegwit = (coind->usesegwit || g_stratum_segwit);
for(int i = 0; i < json_tx->u.array.length; i++)
{
const char *p = json_get_string(json_tx->u.array.values[i], "hash");
char hash_be[256] = { 0 };
string_be(p, hash_be);
txhashes.push_back(hash_be);
const char *txid = json_get_string(json_tx->u.array.values[i], "txid");
if(txid && strlen(txid)) {
char txid_be[256] = { 0 };
string_be(txid, txid_be);
txids.push_back(txid_be);
if (strcmp(hash_be, txid_be)) {
//debuglog("%s segwit tx found, height %d\n", coind->symbol, templ->height);
templ->has_segwit_txs = true; // if not, its useless to generate a segwit block, bigger
}
} else {
templ->has_segwit_txs = false; // force disable if not supported (no txid fields)
}
const char *d = json_get_string(json_tx->u.array.values[i], "data");
templ->txdata.push_back(d);
}
templ->txmerkles[0] = '\0';
if(templ->has_segwit_txs) {
templ->txcount = txids.size();
templ->txsteps = merkle_steps(txids);
} else {
templ->txcount = txhashes.size();
templ->txsteps = merkle_steps(txhashes);
}
if(templ->has_segwit_txs) {
// * We compute the witness hash (which is the hash including witnesses) of all the block's transactions, except the
// coinbase (where 0x0000....0000 is used instead).
// * The coinbase scriptWitness is a stack of a single 32-byte vector, containing a witness nonce (unconstrained).
// * We build a merkle tree with all those witness hashes as leaves (similar to the hashMerkleRoot in the block header).
// * There must be at least one output whose scriptPubKey is a single 36-byte push, the first 4 bytes (magic) of which are
// {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness nonce). In case there are
/*
char bin[YAAMP_HASHLEN_BIN*2];
char witness[128] = { 0 };
vector<string> mt_verify = merkle_steps(txhashes);
string witness_mt = merkle_with_first(mt_verify, "0000000000000000000000000000000000000000000000000000000000000000");
mt_verify.clear();
witness_mt = witness_mt + "0000000000000000000000000000000000000000000000000000000000000000";
binlify((unsigned char *)bin, witness_mt.c_str());
sha256_double_hash_hex(bin, witness, YAAMP_HASHLEN_BIN*2);
int clen = (int) (strlen(coind->witness_magic) + strlen(witness)); // 4 + 32 = 36 = 0x24
sprintf(coind->commitment, "6a%02x%s%s", clen/2, coind->witness_magic, witness);
*/
// default commitment is already computed correctly
const char *commitment = json_get_string(json_result, "default_witness_commitment");
if (commitment) {
sprintf(coind->commitment, "%s", commitment);
} else {
templ->has_segwit_txs = false;
}
}
txhashes.clear();
txids.clear();
vector<string>::const_iterator i;
for(i = templ->txsteps.begin(); i != templ->txsteps.end(); ++i)
sprintf(templ->txmerkles + strlen(templ->txmerkles), "\"%s\",", (*i).c_str());
if(templ->txmerkles[0])
templ->txmerkles[strlen(templ->txmerkles)-1] = 0;
// debuglog("merkle transactions %d [%s]\n", templ->txcount, templ->txmerkles);
ser_string_be2(templ->prevhash_hex, templ->prevhash_be, 8);
if(!strcmp(coind->symbol, "LBC"))
ser_string_be2(templ->claim_hex, templ->claim_be, 8);
if(!coind->pos)
coind_aux_build_auxs(templ);
coinbase_create(coind, templ, json_result);
json_value_free(json);
return templ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool coind_create_job(YAAMP_COIND *coind, bool force)
{
// debuglog("create job %s\n", coind->symbol);
bool b = rpc_connected(&coind->rpc);
if(!b) return false;
CommonLock(&coind->mutex);
YAAMP_JOB_TEMPLATE *templ;
// DCR gbt block header is not compatible with getwork submit, so...
if (coind->usegetwork && strcmp(coind->rpcencoding, "DCR") == 0)
templ = decred_create_worktemplate(coind);
else
templ = coind_create_template(coind);
if(!templ)
{
CommonUnlock(&coind->mutex);
// debuglog("%s: create job template failed!\n", coind->symbol);
return false;
}
YAAMP_JOB *job_last = coind->job;
if( !force && job_last && job_last->templ && job_last->templ->created + 45 > time(NULL) &&
templ->height == job_last->templ->height &&
templ->txcount == job_last->templ->txcount &&
strcmp(templ->coinb2, job_last->templ->coinb2) == 0)
{
// debuglog("coind_create_job %s %d same template %x \n", coind->name, coind->height, coind->job->id);
if (templ->txcount) {
templ->txsteps.clear();
templ->txdata.clear();
}
delete templ;
CommonUnlock(&coind->mutex);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////
int height = coind->height;
coind->height = templ->height-1;
if(height > coind->height)
{
stratumlog("%s went from %d to %d\n", coind->name, height, coind->height);
// coind->auto_ready = false;
}
if(height < coind->height && !coind->newblock)
{
if(coind->auto_ready && coind->notreportingcounter++ > 5)
stratumlog("%s %d not reporting\n", coind->name, coind->height);
}
uint64_t coin_target = decode_compact(templ->nbits);
if (templ->nbits && !coin_target) coin_target = 0xFFFF000000000000ULL; // under decode_compact min diff
coind->difficulty = target_to_diff(coin_target);
// stratumlog("%s %d diff %g %llx %s\n", coind->name, height, coind->difficulty, coin_target, templ->nbits);
coind->newblock = false;
////////////////////////////////////////////////////////////////////////////////////////
object_delete(coind->job);
coind->job = new YAAMP_JOB;
memset(coind->job, 0, sizeof(YAAMP_JOB));
sprintf(coind->job->name, "%s", coind->symbol);
coind->job->id = job_get_jobid();
coind->job->templ = templ;
coind->job->profit = coind_profitability(coind);
coind->job->maxspeed = coind_nethash(coind) *
(g_current_algo->profit? min(1.0, coind_profitability(coind)/g_current_algo->profit): 1);
coind->job->coind = coind;
coind->job->remote = NULL;
g_list_job.AddTail(coind->job);
CommonUnlock(&coind->mutex);
// debuglog("coind_create_job %s %d new job %x\n", coind->name, coind->height, coind->job->id);
return true;
}