From 8fa0139da7663983970466fab39c92cf3f004d5b Mon Sep 17 00:00:00 2001 From: Paolo Tagliaferri Date: Mon, 16 Aug 2021 12:55:03 +0200 Subject: [PATCH 1/3] Fixed an issue with the management of certificates in GetBlockTemplate The GetBlockTemplate function didn't take into account certificates added to the mempool, but only transactions. For this reason, if the mempool got updated with just new certificates, the GetBlockTemplate function returned an "empty" block. --- qa/rpc-tests/sc_cert_getblocktemplate.py | 193 +++++++++++++++++++++++ src/txmempool.cpp | 2 +- 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100755 qa/rpc-tests/sc_cert_getblocktemplate.py diff --git a/qa/rpc-tests/sc_cert_getblocktemplate.py b/qa/rpc-tests/sc_cert_getblocktemplate.py new file mode 100755 index 0000000000..559257c9f2 --- /dev/null +++ b/qa/rpc-tests/sc_cert_getblocktemplate.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014 The Bitcoin Core developers +# Copyright (c) 2018 The Zencash developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +from test_framework.test_framework import BitcoinTestFramework +from test_framework.authproxy import JSONRPCException +from test_framework.util import assert_equal, initialize_chain_clean, \ + start_nodes, sync_blocks, sync_mempools, connect_nodes_bi, mark_logs,\ + get_epoch_data, assert_false, assert_true, swap_bytes +from test_framework.test_framework import MINIMAL_SC_HEIGHT, MINER_REWARD_POST_H200 +from test_framework.mc_test.mc_test import CertTestUtils, generate_random_field_element_hex +import os +import pprint +import time +from decimal import Decimal + +DEBUG_MODE = 1 +NUMB_OF_NODES = 3 +EPOCH_LENGTH = 17 +FT_SC_FEE = Decimal('0') +MBTR_SC_FEE = Decimal('0') +CERT_FEE = Decimal('0.00015') + + +class sc_cert_base(BitcoinTestFramework): + + alert_filename = None + + def setup_chain(self, split=False): + print("Initializing test directory " + self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, NUMB_OF_NODES) + self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt") + with open(self.alert_filename, 'w'): + pass # Just open then close to create zero-length file + + def setup_network(self, split=False): + self.nodes = [] + + self.nodes = start_nodes(NUMB_OF_NODES, self.options.tmpdir, extra_args= + [['-debug=py', '-debug=sc', '-debug=mempool', '-debug=net', '-debug=cert', '-debug=zendoo_mc_cryptolib', '-scproofqueuesize=0', '-logtimemicros=1']] * NUMB_OF_NODES) + + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 1, 2) + sync_blocks(self.nodes[1:NUMB_OF_NODES]) + sync_mempools(self.nodes[1:NUMB_OF_NODES]) + self.is_network_split = split + self.sync_all() + + def run_test(self): + + ''' + The test checks that the "GetBlockTemplate" command correctly detects a new certificate in the mempool, + in the same way as it happens for normal transactions. + ''' + + # amounts + creation_amount = Decimal("10") + bwt_amount = Decimal("1.0") + + mark_logs("Node 0 generates {} block".format(MINIMAL_SC_HEIGHT), self.nodes, DEBUG_MODE) + self.nodes[0].generate(MINIMAL_SC_HEIGHT) + self.sync_all() + + #generate wCertVk and constant + mcTest = CertTestUtils(self.options.tmpdir, self.options.srcdir) + vk = mcTest.generate_params("sc1") + constant = generate_random_field_element_hex() + + ret = self.nodes[0].sc_create(EPOCH_LENGTH, "dada", creation_amount, vk, "", constant) + scid = ret['scid'] + scid_swapped = str(swap_bytes(scid)) + mark_logs("Node 0 created a SC", self.nodes, DEBUG_MODE) + + nblocks = EPOCH_LENGTH + mark_logs("Node 0 generating {} more blocks to confirm the sidechain and reach the end of withdrawal epoch".format(nblocks), self.nodes, DEBUG_MODE) + self.nodes[0].generate(nblocks) + self.sync_all() + + epoch_number, epoch_cum_tree_hash = get_epoch_data(scid, self.nodes[0], EPOCH_LENGTH) + pkh_node1 = self.nodes[1].getnewaddress("", True) + + #Create proof for WCert + quality = 10 + proof = mcTest.create_test_proof( + "sc1", scid_swapped, epoch_number, quality, MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash, constant, [pkh_node1], [bwt_amount]) + + amount_cert_1 = [{"pubkeyhash": pkh_node1, "amount": bwt_amount}] + + cur_h = self.nodes[0].getblockcount() + ret = self.nodes[0].getscinfo(scid, True, False)['items'][0] + ceas_h = ret['ceasing height'] + ceas_limit_delta = ceas_h - cur_h - 1 + + mark_logs("Node0 generating {} blocks reaching the third to last block before the SC ceasing".format(ceas_limit_delta), self.nodes, DEBUG_MODE) + self.nodes[0].generate(ceas_limit_delta - 2) + self.sync_all() + + mark_logs("\nCall GetBlockTemplate on each node to create a cached (empty) version", self.nodes, DEBUG_MODE) + for i in range(0, NUMB_OF_NODES): + self.nodes[i].getblocktemplate() + + mark_logs("Node 0 sends a normal mainchain transaction to mempool and checks that it's not immediately included into the block template", self.nodes, DEBUG_MODE) + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 0.1) + self.sync_all() + + for i in range(0, NUMB_OF_NODES): + assert(len(self.nodes[i].getblocktemplate()['certificates']) == 0) + assert(len(self.nodes[i].getblocktemplate()['transactions']) == 0) + + GET_BLOCK_TEMPLATE_DELAY = 5 # Seconds + mark_logs("Wait {} seconds and check that the transaction is now included into the block template".format(GET_BLOCK_TEMPLATE_DELAY), self.nodes, DEBUG_MODE) + time.sleep(GET_BLOCK_TEMPLATE_DELAY) + for i in range(0, NUMB_OF_NODES): + assert(len(self.nodes[i].getblocktemplate()['certificates']) == 0) + assert(len(self.nodes[i].getblocktemplate()['transactions']) == 1) + + mark_logs("Node 0 mines one block to clean the mempool", self.nodes, DEBUG_MODE) + self.nodes[0].generate(1) + self.sync_all() + + mark_logs("\nCall GetBlockTemplate on each node to create a new cached version", self.nodes, DEBUG_MODE) + for i in range(0, NUMB_OF_NODES): + self.nodes[i].getblocktemplate() + + mark_logs("Node 0 sends a certificate", self.nodes, DEBUG_MODE) + try: + cert_epoch_0 = self.nodes[0].send_certificate(scid, epoch_number, quality, + epoch_cum_tree_hash, proof, amount_cert_1, FT_SC_FEE, MBTR_SC_FEE, CERT_FEE) + assert(len(cert_epoch_0) > 0) + except JSONRPCException, e: + errorString = e.error['message'] + mark_logs("Send certificate failed with reason {}".format(errorString), self.nodes, DEBUG_MODE) + assert(False) + + self.sync_all() + + mark_logs("Check that the certificate is not immediately included into the block template", self.nodes, DEBUG_MODE) + + for i in range(0, NUMB_OF_NODES): + assert(len(self.nodes[i].getblocktemplate()['certificates']) == 0) + assert(len(self.nodes[i].getblocktemplate()['transactions']) == 0) + + GET_BLOCK_TEMPLATE_DELAY = 5 # Seconds + mark_logs("Wait {} seconds and check that the certificate is now included into the block template".format(GET_BLOCK_TEMPLATE_DELAY), self.nodes, DEBUG_MODE) + time.sleep(GET_BLOCK_TEMPLATE_DELAY) + for i in range(0, NUMB_OF_NODES): + assert(len(self.nodes[i].getblocktemplate()['certificates']) == 1) + assert(len(self.nodes[i].getblocktemplate()['transactions']) == 0) + + mark_logs("Node 0 mines one block to clean the mempool", self.nodes, DEBUG_MODE) + self.nodes[0].generate(1) + self.sync_all() + + mark_logs("\nCall GetBlockTemplate on each node to create a new cached version", self.nodes, DEBUG_MODE) + for i in range(0, NUMB_OF_NODES): + self.nodes[i].getblocktemplate() + + mark_logs("Node 0 sends a normal transaction and a certificate", self.nodes, DEBUG_MODE) + + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 0.1) + + quality = quality + 1 + proof = mcTest.create_test_proof( + "sc1", scid_swapped, epoch_number, quality, MBTR_SC_FEE, FT_SC_FEE, epoch_cum_tree_hash, constant, [pkh_node1], [bwt_amount]) + + try: + cert_epoch_0 = self.nodes[0].send_certificate(scid, epoch_number, quality, + epoch_cum_tree_hash, proof, amount_cert_1, FT_SC_FEE, MBTR_SC_FEE, CERT_FEE) + assert(len(cert_epoch_0) > 0) + except JSONRPCException, e: + errorString = e.error['message'] + mark_logs("Send certificate failed with reason {}".format(errorString), self.nodes, DEBUG_MODE) + assert(False) + + self.sync_all() + + mark_logs("Check that the certificate is not immediately included into the block template", self.nodes, DEBUG_MODE) + + for i in range(0, NUMB_OF_NODES): + assert(len(self.nodes[i].getblocktemplate()['certificates']) == 0) + assert(len(self.nodes[i].getblocktemplate()['transactions']) == 0) + + GET_BLOCK_TEMPLATE_DELAY = 5 # Seconds + mark_logs("Wait {} seconds and check that the certificate is now included into the block template".format(GET_BLOCK_TEMPLATE_DELAY), self.nodes, DEBUG_MODE) + time.sleep(GET_BLOCK_TEMPLATE_DELAY) + for i in range(0, NUMB_OF_NODES): + assert(len(self.nodes[i].getblocktemplate()['certificates']) == 1) + assert(len(self.nodes[i].getblocktemplate()['transactions']) == 1) + + +if __name__ == '__main__': + sc_cert_base().main() diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 9e17a8ea54..3436f6d534 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -145,7 +145,7 @@ void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins) unsigned int CTxMemPool::GetTransactionsUpdated() const { LOCK(cs); - return nTransactionsUpdated; + return nTransactionsUpdated + nCertificatesUpdated; } void CTxMemPool::AddTransactionsUpdated(unsigned int n) From 9c6c5ceb8c26a2e6cf508779c5c0359386b55df2 Mon Sep 17 00:00:00 2001 From: Paolo Tagliaferri Date: Tue, 17 Aug 2021 11:00:29 +0200 Subject: [PATCH 2/3] Added debug log when using GetBlockTemplate RPC call --- src/rpc/server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index a8f99180d6..ecd3ff9d93 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -537,8 +537,8 @@ void JSONRequest::parse(const UniValue& valRequest) if (!valMethod.isStr()) throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); strMethod = valMethod.get_str(); - if (strMethod != "getblocktemplate") - LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod)); + + LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod)); // Parse params UniValue valParams = find_value(request, "params"); From aba7e8432a2e785711ebdcda45b738e988381566 Mon Sep 17 00:00:00 2001 From: Paolo Tagliaferri Date: Wed, 18 Aug 2021 15:54:07 +0200 Subject: [PATCH 3/3] Added "sc_cert_getblocktemplate.py" to regtest script --- qa/pull-tester/rpc-tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 709ad46465..59920be46d 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -116,6 +116,7 @@ testScripts=( 'cbh_rpcheck.py' 'tlsprotocols.py' 'getblockmerkleroots.py' + 'sc_cert_getblocktemplate.py' ); testScriptsExt=( 'getblocktemplate_longpoll.py'