static int blockstore_set_chain_links(struct blockstore *bs, struct blockentry *be) { struct blockentry *prev; char hashStr[80]; uint256 hash; bool s; hash256_calc(&be->header, sizeof be->header, &hash); uint256_snprintf_reverse(hashStr, sizeof hashStr, &hash); if (be->height >= 0) { struct blockentry *li; /* * We've reached the junction with the current/old best chain. All the * entries from now on need to be made orphans. */ Log(LGPFX" Reached block %s\n", hashStr); li = be->next; while (li) { hash256_calc(&li->header, sizeof li->header, &hash); uint256_snprintf_reverse(hashStr, sizeof hashStr, &hash); Log(LGPFX" moving #%d %s from blk -> orphan\n", li->height, hashStr); s = hashtable_remove(bs->hash_blk, &hash, sizeof hash); ASSERT(s); li->height = -1; s = hashtable_insert(bs->hash_orphans, &hash, sizeof hash, li); ASSERT(s); li = li->next; } return be->height; } ASSERT(be->height == -1); // orphan prev = blockstore_lookup(bs, &be->header.prevBlock); ASSERT(prev); be->height = 1 + blockstore_set_chain_links(bs, prev); Log(LGPFX" moving #%d %s from orphan -> blk\n", be->height, hashStr); prev->next = be; be->prev = prev; s = hashtable_remove(bs->hash_orphans, &hash, sizeof hash); ASSERT(s); s = hashtable_insert(bs->hash_blk, &hash, sizeof hash, be); ASSERT(s); return be->height; }
static bool blockstore_validate_chkpt(const uint256 *hash, uint32 height) { struct block_cpt_entry *array; size_t n; int i; if (btc->testnet) { array = block_cpt_testnet; n = ARRAYSIZE(block_cpt_testnet); } else { array = block_cpt_main; n = ARRAYSIZE(block_cpt_main); } for (i = 0; i < n; i++) { if (height == array[i].height) { if (!uint256_issame(hash, &array[i].hash)) { char str[128]; uint256_snprintf_reverse(str, sizeof str, hash); Warning(LGPFX" chkpt validation failed. height=%u %s\n", height, str); return 0; } return 1; } } return 1; }
static void txdb_print_coins(const struct txdb *txdb, bool onlyUnspent) { struct txo_entry *txo_array = NULL; int i; int n; n = hashtable_getnumentries(txdb->hash_txo); if (n == 0) { Log(LGPFX" %s: no coins found\n", __FUNCTION__); return; } hashtable_linearize(txdb->hash_txo, sizeof *txo_array, (void *)&txo_array); ASSERT(txo_array); Log(LGPFX" %s: %d coins available:\n", __FUNCTION__, n); for (i = 0; i < n; i++) { struct txo_entry *txo_ent = txo_array + i; char hashStr[80]; if (onlyUnspent && txo_ent->spent) { continue; } uint256_snprintf_reverse(hashStr, sizeof hashStr, &txo_ent->txHash); Log(LGPFX" txo%03d: %s sp=%u id=%3u v=%llu\n", i, txo_ent->btc_addr, txo_ent->spent, txo_ent->outIdx, txo_ent->value); } free(txo_array); }
int blockstore_get_block_height(struct blockstore *bs, const uint256 *hash) { struct blockentry *be; int height; bool s; if (uint256_iszero(hash)) { return 0; } mutex_lock(bs->lock); s = hashtable_lookup(bs->hash_blk, hash, sizeof *hash, (void *)&be); if (s == 0) { char hashStr[80]; uint256_snprintf_reverse(hashStr, sizeof hashStr, hash); Panic(LGPFX " block %s not found.\n", hashStr); } height = be->height; mutex_unlock(bs->lock); return height; }
time_t blockstore_get_block_timestamp(const struct blockstore *bs, const uint256 *hash) { struct blockentry *be; time_t ts = 0; mutex_lock(bs->lock); if (uint256_iszero(hash)) { goto done; } be = blockstore_lookup(bs, hash); if (be == NULL) { char hashStr[80]; uint256_snprintf_reverse(hashStr, sizeof hashStr, hash); Panic(LGPFX " block %s not found.\n", hashStr); } ts = be->header.timestamp; done: mutex_unlock(bs->lock); return ts; }
void bitc_ios_dashboard_update(void) { btc_block_header hdr; char hashStr[80]; char *ts = NULL; uint256 hash; int height; bool s; if (btc->blockStore == NULL) { return; } height = blockstore_get_height(btc->blockStore); if (height == 0) { return; } s = blockstore_get_block_at_height(btc->blockStore, height, &hash, &hdr); ASSERT(mutex_islocked(btcui->lock)); uint256_snprintf_reverse(hashStr, sizeof hashStr, &hash); ts = print_time_local(hdr.timestamp, "%c"); DashboardUpdate(height, hashStr, btcui->num_peers_active, btcui->num_peers_alive, btcui->num_addrs, ts ? ts : ""); free(ts); }
static void txdb_select_coins(struct txdb *txdb, const struct btc_tx_desc *desc, btc_msg_tx *tx, uint64 *change) { struct txo_entry *txo_array; uint64 value; int txo_num; int i; i = 0; value = 0; tx->in_count = 0; txo_array = txdb_get_coins_sorted(txdb); txo_num = hashtable_getnumentries(txdb->hash_txo); /* * txo_array is sorted in chronological order, so we'll be consuming old * coins first. */ Log(LGPFX" select_coins: total_value=%llu fee=%llu\n", desc->total_value, desc->fee); while (value < (desc->total_value + desc->fee) && i < txo_num) { struct txo_entry *txo_ent = &txo_array[i++]; char hashStr[80]; if (txo_ent->spent == 1 || txo_ent->spendable == 0) { continue; } if (uint256_iszero(&txo_ent->blkHash)) { NOT_TESTED(); continue; } uint256_snprintf_reverse(hashStr, sizeof hashStr, &txo_ent->txHash); Log(LGPFX" using txo for %s id=%3u of %s\n", txo_ent->btc_addr, txo_ent->outIdx, hashStr); value += txo_ent->value; memcpy(&tx->tx_in[tx->in_count].prevTxHash, &txo_ent->txHash, sizeof txo_ent->txHash); tx->tx_in[tx->in_count].prevTxOutIdx = txo_ent->outIdx; tx->tx_in[tx->in_count].sequence = UINT_MAX; tx->in_count++; } ASSERT(value >= desc->total_value); *change = value - desc->total_value - desc->fee; Log(LGPFX" change=%llu\n", *change); free(txo_array); }
static void blockstore_add_entry(struct blockstore *bs, struct blockentry *be, const uint256 *hash) { bool s; if (bs->best_chain == NULL) { s = hashtable_insert(bs->hash_blk, hash, sizeof *hash, be); ASSERT(s); ASSERT(uint256_issame(hash, &bs->genesis_hash)); ASSERT(be->prev == NULL); ASSERT(bs->height); bs->best_chain = be; bs->genesis = be; bs->height = 0; be->height = 0; memcpy(&bs->best_hash, hash, sizeof *hash); } else if (uint256_issame(&be->header.prevBlock, &bs->best_hash)) { s = hashtable_insert(bs->hash_blk, hash, sizeof *hash, be); ASSERT(s); bs->height++; be->height = bs->height; bs->best_chain->next = be; be->prev = bs->best_chain; bs->best_chain = be; memcpy(&bs->best_hash, hash, sizeof *hash); } else { char hashStr[80]; uint32 count; be->height = -1; count = hashtable_getnumentries(bs->hash_orphans); uint256_snprintf_reverse(hashStr, sizeof hashStr, hash); Log(LGPFX" block %s orphaned. %u orphan%s total.\n", hashStr, count, count > 1 ? "s" : ""); s = hashtable_insert(bs->hash_orphans, hash, sizeof *hash, be); ASSERT(s); blockstore_set_best_chain(bs, be, hash); } }
static void txdb_export_tx_cb(const void *key, size_t keyLen, void *cbData, void *keyData) { struct bitcui_tx **txiPtr = (struct bitcui_tx **)cbData; struct tx_entry *txe = (struct tx_entry *)keyData; struct bitcui_tx *txi = *txiPtr; char hashStr[80]; if (txe->relevant == 0) { return; } /* * We need to weed out transactions that made it in an orphan block but were * not integrated later on in the main chain. */ txi->src = NULL; txi->dst = NULL; txi->desc = NULL; ASSERT(keyLen == sizeof(uint256)); memcpy(&txi->txHash, key, keyLen); txi->value = txdb_get_tx_credit(&txe->tx); txi->value -= txdb_get_tx_debit(&txe->tx); txi->blockHeight = -1; if (!uint256_iszero(&txe->blkHash)) { txi->blockHeight = blockstore_get_block_height(btc->blockStore, &txe->blkHash); } uint256_snprintf_reverse(hashStr, sizeof hashStr, (uint256*)key); txi->desc = config_getstring(btc->txLabelsCfg, NULL, "tx.%s.label", hashStr) ; /* * This is a workaround for a bug caused by truncated hashStr. */ if (txi->desc == NULL) { hashStr[63] = '\0'; txi->desc = config_getstring(btc->txLabelsCfg, NULL, "tx.%s.label", hashStr) ; } txi->timestamp = txe->timestamp; *txiPtr += 1; }
static int txdb_save_tx(struct txdb *txdb, const uint256 *blkHash, const uint256 *txHash, mtime_t timestamp, const uint8 *buf, size_t len) { struct tx_ser_data txdata; struct buff *bufd; struct buff *bufk; char hashStr[80]; char *err; err = NULL; memset(&txdata, 0, sizeof txdata); if (blkHash) { memcpy(&txdata.blkHash, blkHash, sizeof *blkHash); } txdata.buf = (uint8 *)buf; txdata.len = len; txdata.timestamp = timestamp; uint256_snprintf_reverse(hashStr, sizeof hashStr, txHash); bufk = txdb_serialize_tx_key(txdb->tx_seq, hashStr); bufd = txdb_serialize_tx_data(&txdata); leveldb_put(txdb->db, txdb->wr_opts, buff_base(bufk), buff_curlen(bufk), buff_base(bufd), buff_curlen(bufd), &err); buff_free(bufk); buff_free(bufd); if (err) { Warning(LGPFX" failed to save tx %s: %s\n", hashStr, err); free(err); } return err != NULL; }
void blockstore_get_hash_from_birth(const struct blockstore *bs, time_t birth, uint256 *hash) { struct blockentry *e; for (e = bs->best_chain; e != bs->genesis; e = e->prev) { if (e->header.timestamp < birth) { char hashStr[80]; uint64 ts = birth; hash256_calc(&e->header, sizeof e->header, hash); uint256_snprintf_reverse(hashStr, sizeof hashStr, hash); Log(LGPFX" birth %llu --> block %s.\n", ts, hashStr); return; } } memcpy(hash, &bs->genesis_hash, sizeof *hash); }
time_t blockstore_get_block_timestamp(const struct blockstore *bs, const uint256 *hash) { struct blockentry *be; if (uint256_iszero(hash)) { return 0; } be = blockstore_lookup(bs, hash); if (be == NULL) { char hashStr[80]; uint256_snprintf_reverse(hashStr, sizeof hashStr, hash); Warning(LGPFX" block %s not found.\n", hashStr); ASSERT(0); return 0; } return be->header.timestamp; }
static void txdb_remove_from_hashtable(struct txdb *txdb, const uint256 *txHash) { struct tx_entry *txe; char hashStr[80]; bool s; txe = txdb_get_tx_entry(txdb, txHash); if (txe == NULL || txe->relevant == 1) { NOT_TESTED(); return; } txdb_free_tx_entry(txe); uint256_snprintf_reverse(hashStr, sizeof hashStr, txHash); s = hashtable_remove(txdb->hash_tx, txHash, sizeof *txHash); Warning(LGPFX" %s removed from hash_tx: %d (count=%u)\n", hashStr, s, hashtable_getnumentries(txdb->hash_tx)); }
int blockstore_get_block_height(struct blockstore *bs, const uint256 *hash) { struct blockentry *be; bool s; if (uint256_iszero(hash)) { return 0; } s = hashtable_lookup(bs->hash_blk, hash, sizeof *hash, (void*)&be); if (s == 0) { char hashStr[80]; uint256_snprintf_reverse(hashStr, sizeof hashStr, hash); Warning(LGPFX" block %s not found.\n", hashStr); //ASSERT(0); return 0; } return be->height; }
static int blockset_open_file(struct blockstore *blockStore, struct blockset *bs) { uint64 offset; mtime_t ts; int res; res = file_open(bs->filename, 0 /* R/O */, 0 /* !unbuf */, &bs->desc); if (res) { return res; } bs->filesize = file_getsize(bs->desc); if (bs->filesize < 0) { return errno; } if (bs->filesize > 0) { char *s = print_size(bs->filesize); char *name = file_getname(bs->filename); Log(LGPFX" reading file %s -- %s -- %llu headers.\n", name, s, bs->filesize / sizeof(btc_block_header)); free(name); free(s); } ts = time_get(); offset = 0; while (offset < bs->filesize) { btc_block_header buf[10000]; size_t numRead; size_t numBytes; int numHeaders; int i; numBytes = MIN(bs->filesize - offset, sizeof buf); res = file_pread(bs->desc, offset, buf, numBytes, &numRead); if (res != 0) { break; } if (btc->stop != 0) { res = 1; NOT_TESTED(); break; } numHeaders = numRead / sizeof(btc_block_header); for (i = 0; i < numHeaders; i++) { struct blockentry *be; uint256 hash; be = blockstore_alloc_entry(buf + i); be->written = 1; hash256_calc(buf + i, sizeof buf[0], &hash); if (!blockstore_validate_chkpt(&hash, blockStore->height + 1)) { return 1; } blockstore_add_entry(blockStore, be, &hash); if (i == numHeaders - 1) { bitcui_set_status("loading headers .. %llu%%", (offset + numBytes) * 100 / bs->filesize); } if (i == numHeaders - 1 || (numBytes < sizeof buf && i > numHeaders - 256)) { bitcui_set_last_block_info(&hash, blockStore->height, be->header.timestamp); } } offset += numRead; } ts = time_get() - ts; char hashStr[80]; char *latStr; uint256_snprintf_reverse(hashStr, sizeof hashStr, &blockStore->best_hash); Log(LGPFX" loaded blocks up to %s\n", hashStr); latStr = print_latency(ts); Log(LGPFX" this took %s\n", latStr); free(latStr); return res; }
void txdb_confirm_one_tx(struct txdb *txdb, const uint256 *blkHash, const uint256 *txHash) { leveldb_iterator_t *iter; struct tx_entry *txe; char bkHashStr[80]; char txHashStr[80]; ASSERT(!uint256_iszero(blkHash)); ASSERT(!uint256_iszero(txHash)); txe = txdb_get_tx_entry(txdb, txHash); if (txe == NULL) { return; } if (txe->relevant == 0) { txdb_remove_from_hashtable(txdb, txHash); NOT_TESTED(); return; } if (!uint256_iszero(&txe->blkHash)) { /* * It's possible for the ASSERT below to fire if a tx is confirmed in * a block that is later orphaned. The tx should then be relayed again * until it finds its way in a new block. */ ASSERT(uint256_issame(&txe->blkHash, blkHash)); return; } peergroup_stop_broadcast_tx(btc->peerGroup, txHash); memcpy(&txe->blkHash, blkHash, sizeof *blkHash); uint256_snprintf_reverse(bkHashStr, sizeof bkHashStr, blkHash); uint256_snprintf_reverse(txHashStr, sizeof txHashStr, txHash); Warning(LGPFX" %s confirmed in %s\n", txHashStr, bkHashStr); NOT_TESTED(); iter = leveldb_create_iterator(txdb->db, txdb->rd_opts); leveldb_iter_seek_to_first(iter); while (leveldb_iter_valid(iter)) { struct tx_ser_key *txkey; struct tx_ser_data *txdata; struct buff *buf; const char *key; const char *val; size_t klen; size_t vlen; char *err = NULL; key = leveldb_iter_key(iter, &klen); txkey = txdb_deserialize_tx_key(key, klen); if (txkey == NULL || uint256_issame(txHash, &txkey->txHash) == 0) { free(txkey); leveldb_iter_next(iter); continue; } NOT_TESTED(); val = leveldb_iter_value(iter, &vlen); txdata = txdb_deserialize_tx_data(val, vlen); ASSERT(uint256_iszero(&txdata->blkHash)); ASSERT(txdata->timestamp != 0); memcpy(&txdata->blkHash, blkHash, sizeof *blkHash); buf = txdb_serialize_tx_data(txdata); leveldb_put(txdb->db, txdb->wr_opts, key, klen, buff_base(buf), buff_curlen(buf), &err); buff_free(buf); if (err) { Warning(LGPFX" failed to write tx entry: %s\n", err); } txdb_export_tx_info(txdb); free(txkey); free(txdata->buf); free(txdata); break; } leveldb_iter_destroy(iter); }
static int txdb_remember_tx(struct txdb *txdb, bool alreadySaved, mtime_t ts, const uint8 *buf, size_t len, const uint256 *txHash, const uint256 *blkHash, bool *relevant) { struct tx_entry *txe; char hashStr[80]; int res; ASSERT(txHash); ASSERT(relevant); *relevant = 0; /* * We always store the tx in the memory pool, that way we know quickly * whether we've already seen it. */ res = txdb_add_to_hashtable(txdb, buf, len, txHash, blkHash, ts, &txe); if (res) { NOT_TESTED(); return res; } uint256_snprintf_reverse(hashStr, sizeof hashStr, txHash); txdb_process_tx_entry(txdb, txHash, blkHash, &txe->tx, &txe->relevant); if (txe->relevant == 0) { Warning(LGPFX" tx %s not relevant (%u)\n", hashStr, hashtable_getnumentries(txdb->hash_tx)); return 0; } if (alreadySaved) { return 0; } /* * OK -- this transaction is relevant to our wallet. */ *relevant = 1; Warning(LGPFX" tx %s ok (%u)\n", hashStr, hashtable_getnumentries(txdb->hash_tx)); res = txdb_save_tx(txdb, blkHash, txHash, ts, buf, len); if (res == 0) { txdb->tx_seq++; } txdb_export_tx_info(txdb); if (bitc_state_ready()) { int64 value = txdb_get_tx_credit(&txe->tx) - txdb_get_tx_debit(&txe->tx); bitcui_set_status("New payment %s: %.8f BTC", value > 0 ? "received" : "made", 1.0 * value / ONE_BTC); } return res; }
int txdb_craft_tx(struct txdb *txdb, const struct btc_tx_desc *tx_desc, btc_msg_tx *tx) { struct buff *buf; char hashStr[80]; uint256 txHash; uint64 change; uint32 numCoins; bool relevant; mtime_t ts; int res; res = 0; relevant = 0; tx->version = 1; txdb_prepare_txout(tx_desc, tx); /* * In order to properly size 'tx->txIn', we need to determine how many coins * we're going to use. Right now, let's just vastly overestimate. */ numCoins = hashtable_getnumentries(txdb->hash_txo); tx->tx_in = safe_calloc(numCoins, sizeof *tx->tx_in); txdb_print_coins(txdb, 1); txdb_select_coins(txdb, tx_desc, tx, &change); /* * Change! XXX: fix me. */ if (change > 0) { const char *btc_change; btc_change = wallet_get_change_addr(btc->wallet); tx->out_count++; txdb_set_txo(tx, tx->out_count - 1, btc_change, change); Warning(LGPFX" change: %llu -- %.8f BTC\n", change, change / ONE_BTC); } txdb_sign_tx_inputs(txdb, tx); /* * Now that the tx is ready, serialize it and check that it's not too big. */ btcmsg_print_tx(tx); buf = buff_alloc(); serialize_tx(buf, tx); if (buff_curlen(buf) > BTC_TX_MAX_SIZE) { Warning(LGPFX" tx too large: %zu\n", buff_curlen(buf)); res = 1; goto exit; } hash256_calc(buff_base(buf), buff_curlen(buf), &txHash); uint256_snprintf_reverse(hashStr, sizeof hashStr, &txHash); Warning(LGPFX" %s (%zu bytes)\n", hashStr, buff_curlen(buf)); Log_Bytes(LGPFX" TX: ", buff_base(buf), buff_curlen(buf)); if (bitc_testing) { Warning("TESTING! Not saving/relaying tx.\n"); goto exit; } ts = time(NULL); res = txdb_remember_tx(txdb, 0 /* save to disk */, ts, buff_base(buf), buff_curlen(buf), &txHash, NULL, &relevant); txdb_save_tx_label(tx_desc, hashStr); txdb_export_tx_info(txdb); res = peergroup_new_tx_broadcast(btc->peerGroup, buf, ts + 2 * 60 * 60, &txHash); if (res) { Warning(LGPFX" failed to transmit tx: %d\n", res); bitcui_set_status("got errors while broadcasting tx"); } exit: buff_free(buf); /* * XXX: We should mark the coins used by this tx as "reserved", so that we * do not attempt to use conflicting coins in subsequent TXs. */ return res; }
static void txdb_process_tx_entry(struct txdb *txdb, const uint256 *txHash, const uint256 *blkHash, const btc_msg_tx *tx, bool *relevant) { struct txo_entry *txo_entry; char hashStr[80]; uint32 i; bool s; ASSERT(txdb); ASSERT(*relevant == 0); uint256_snprintf_reverse(hashStr, sizeof hashStr, txHash); Log(LGPFX" processing %s\n", hashStr); /* * Look at all the tx referred to by the inputs. If any of these match * a txo for the wallet keys, we have a debit. */ for (i = 0; i < tx->in_count; i++) { const btc_msg_tx_in *txi = tx->tx_in + i; /* * Look to see if the txi refers to one of our coins (a known txo). If * so, we need to mark it as spent. */ txo_entry = txdb_lookup_txo(&txi->prevTxHash, txi->prevTxOutIdx); if (txo_entry == NULL) { continue; } ASSERT(txo_entry->spent == 0); txo_entry->spent = 1; *relevant = 1; } /* * Analyze all the txo to see if any credit our addresses. */ for (i = 0; i < tx->out_count; i++) { char key[32 + 4]; // txHash + txo_idx const btc_msg_tx_out *txo = tx->tx_out + i; uint160 pub_key; if (script_parse_pubkey_hash(txo->scriptPubKey, txo->scriptLength, &pub_key) || !wallet_is_pubkey_hash160_mine(btc->wallet, &pub_key)) { continue; } *relevant = 1; memcpy(key, txHash, sizeof(uint256)); memcpy(key + 32, &i, sizeof(uint32)); txo_entry = safe_malloc(sizeof *txo_entry); txo_entry->spent = 0; txo_entry->value = txo->value; txo_entry->btc_addr = b58_pubkey_from_uint160(&pub_key); txo_entry->outIdx = i; txo_entry->spendable = wallet_is_pubkey_spendable(btc->wallet, &pub_key); memcpy(&txo_entry->txHash, txHash, sizeof *txHash); if (blkHash) { memcpy(&txo_entry->blkHash, blkHash, sizeof *blkHash); } else { memset(&txo_entry->blkHash, 0, sizeof txo_entry->blkHash); } s = hashtable_insert(txdb->hash_txo, key, sizeof key, txo_entry); ASSERT(s); } }
static int txdb_load_tx(struct txdb *txdb, const char *key, size_t klen, const char *val, size_t vlen) { struct tx_ser_data *txd; struct tx_ser_key *txk; char hashStr[80]; bool relevant = 0; uint256 txHash; bool confirmed; int res = 0; ASSERT(strncmp(key, "/tx/", 4) == 0); txk = txdb_deserialize_tx_key(key, klen); txd = txdb_deserialize_tx_data(val, vlen); ASSERT(txdb->tx_seq == txk->seq); txdb->tx_seq++; confirmed = !uint256_iszero(&txd->blkHash); hash256_calc(txd->buf, txd->len, &txHash); ASSERT(uint256_issame(&txHash, &txk->txHash)); uint256_snprintf_reverse(hashStr, sizeof hashStr, &txHash); LOG(1, (LGPFX" loading %ctx %s\n", confirmed ? 'c' : 'u', hashStr)); res = txdb_remember_tx(txdb, 1 /* not on disk, just hashtable */, txd->timestamp, txd->buf, txd->len, &txk->txHash, &txd->blkHash, &relevant); /* * If the transaction is still unconfirmed, add to relay set. */ if (!confirmed) { struct buff buf; int numSec; numSec = time(NULL) - txd->timestamp; if (numSec > 0) { int hours = numSec / (60 * 60); int min = (numSec % (60 * 60)) / 60; Log(LGPFX" unconfirmed tx %s was sent %d hours %d min ago.\n", hashStr, hours, min); } buff_init(&buf, txd->buf, txd->len); Log(LGPFX" adding tx %s to relay-set\n", hashStr); peergroup_new_tx_broadcast(btc->peerGroup, &buf, txd->timestamp + 2 * 60 * 60, &txHash); } else { uint256_snprintf_reverse(hashStr, sizeof hashStr, &txd->blkHash); Log(LGPFX" tx in block %s\n", hashStr); } free(txd->buf); free(txd); free(txk); return res; }