From 7f2302b3655d0bb63fa63bf9541c2a409dc12619 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Thu, 15 Nov 2018 14:28:56 -0500 Subject: [PATCH] finalize functional test for blocksigning --- test/functional/feature_blocksign.py | 83 +++++++++--- test/functional/feature_signed_blockchain.py | 135 ------------------- 2 files changed, 65 insertions(+), 153 deletions(-) delete mode 100755 test/functional/feature_signed_blockchain.py diff --git a/test/functional/feature_blocksign.py b/test/functional/feature_blocksign.py index 5ff34a0ccc..b31e464359 100755 --- a/test/functional/feature_blocksign.py +++ b/test/functional/feature_blocksign.py @@ -6,11 +6,12 @@ import os import random from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import (assert_raises_rpc_error, assert_equal, connect_nodes_bi) +from test_framework.authproxy import JSONRPCException from test_framework import ( address, key, test_framework, - util, ) # Generate wallet import format from private key. @@ -32,6 +33,22 @@ def make_signblockscript(num_nodes, required_signers, keys): return script class BlockSignTest(BitcoinTestFramework): + """ + Test signed-blockchain-related RPC calls and functionality: + + - getnewblockhex + - signblock + - combineblocksigs + - submitblock + - testproposedblock + - getcompactsketch + - consumecompactsketch + - consumegetblocktxn + - finalizecompactblock + + As well as syncing blocks over p2p + + """ # Dynamically generate N keys to be used for block signing. def init_keys(self, num_keys): self.keys = [] @@ -48,23 +65,26 @@ class BlockSignTest(BitcoinTestFramework): self.wifs.append(wif(pk_bytes)) def set_test_params(self): - self.num_nodes = 4 + self.num_nodes = 5 + self.num_keys = 4 self.required_signers = 3 self.setup_clean_chain = True - self.init_keys(self.num_nodes) - signblockscript = make_signblockscript(self.num_nodes, self.required_signers, self.keys) + self.init_keys(self.num_nodes-1) # Last node cannot sign and is connected to all via p2p + signblockscript = make_signblockscript(self.num_keys, self.required_signers, self.keys) self.extra_args = [[ "-signblockscript={}".format(signblockscript), - "-con_max_block_sig_size={}".format(self.num_nodes*74), + "-con_max_block_sig_size={}".format(self.required_signers*74), "-con_blocksubsidy=5000000000", ]] * self.num_nodes def setup_network(self): self.setup_nodes() + # Connect non-signing node to a single signing one (to not pass blocks between signers) + connect_nodes_bi(self.nodes, 0, self.num_nodes-1) def check_height(self, expected_height): for n in self.nodes: - util.assert_equal(n.getblockcount(), expected_height) + assert_equal(n.getblockcount(), expected_height) def mine_block(self, make_transactions): # mine block in round robin sense: depending on the block number, a node @@ -84,7 +104,7 @@ class BlockSignTest(BitcoinTestFramework): block = miner.getnewblockhex() # other nodes get fed compact blocks - for i in range(self.required_signers): + for i in range(self.num_nodes): if i == mineridx: continue sketch = miner.getcompactsketch(block) @@ -98,30 +118,41 @@ class BlockSignTest(BitcoinTestFramework): # Block should be complete, sans signatures self.nodes[i].testproposedblock(final_block) + # non-signing node can not sign + assert_raises_rpc_error(-25, "Could not sign the block.", self.nodes[-1].signblock, block) - # collect required_signers signatures + # collect num_keys signatures from signers, reduce to required_signers sigs during combine sigs = [] - for i in range(self.required_signers): + for i in range(self.num_keys): result = miner.combineblocksigs(block, sigs) - util.assert_equal(result["complete"], False) - miner.submitblock(result["hex"]) - self.check_height(blockcount) sigs = sigs + self.nodes[i].signblock(block) + assert_equal(result["complete"], i >= self.required_signers) + # submitting should have no effect pre-threshhold + if i < self.required_signers: + miner.submitblock(result["hex"]) + self.check_height(blockcount) result = miner.combineblocksigs(block, sigs) - util.assert_equal(result["complete"], True) + assert_equal(result["complete"], True) - # All must submit... we're not connected! - for node in self.nodes: - node.submitblock(result["hex"]) + # All signing nodes must submit... we're not connected! + self.nodes[0].submitblock(result["hex"]) + early_proposal = self.nodes[0].getnewblockhex() # testproposedblock should reject + # Submit blocks to all other signing nodes next, as well as too-early block proposal + for i in range(1, self.num_keys): + assert_raises_rpc_error(-25, "proposal was not based on our best chain", self.nodes[i].testproposedblock, early_proposal) + self.nodes[i].submitblock(result["hex"]) + + # All nodes should be synced in blocks and transactions(mempool should be empty) + self.sync_all() def mine_blocks(self, num_blocks, transactions): for i in range(num_blocks): self.mine_block(transactions) def run_test(self): - # Have every node import its block signing private key. - for i in range(self.num_nodes): + # Have every node except last import its block signing private key. + for i in range(self.num_keys): self.nodes[i].importprivkey(self.wifs[i]) self.check_height(0) @@ -134,7 +165,23 @@ class BlockSignTest(BitcoinTestFramework): print("Mining and signing non-empty blocks") self.mine_blocks(10, True) + # Height check also makes sure non-signing, p2p connected node gets block self.check_height(111) + # signblock rpc field stuff + tip = self.nodes[0].getblockhash(self.nodes[0].getblockcount()) + header = self.nodes[0].getblockheader(tip) + block = self.nodes[0].getblock(tip) + info = self.nodes[0].getblockchaininfo() + + assert('signblock_witness_asm' in header) + assert('signblock_witness_hex' in header) + assert('signblock_witness_asm' in block) + assert('signblock_witness_hex' in block) + + signblockscript = make_signblockscript(self.num_keys, self.required_signers, self.keys) + assert_equal(info['signblock_asm'], self.nodes[0].decodescript(signblockscript)['asm']) + assert_equal(info['signblock_hex'], signblockscript) + if __name__ == '__main__': BlockSignTest().main() diff --git a/test/functional/feature_signed_blockchain.py b/test/functional/feature_signed_blockchain.py deleted file mode 100755 index 4960ad4e22..0000000000 --- a/test/functional/feature_signed_blockchain.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2014-2016 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -# -# Test RPC calls related to blockchain state. Tests correspond to code in -# rpc/blockchain.cpp. -# - -from decimal import Decimal -from test_framework.test_framework import BitcoinTestFramework -from test_framework.authproxy import JSONRPCException -from test_framework.util import ( - assert_equal, - assert_raises_jsonrpc, -) - -# Keys for signing 2-of-3 -pubkeys = ["039560e48d4336e40db447fc136ce24ae1dfdefa5701e4d4e57aa1a1a9f47f3faa", "025a66517c1d85adcd909f9f675bf656708edc4da1f614693e347be6baf0fef4ae", "02ca238faeb3b01d26ae8a39869220dbd84cc8516398afa8958fe613fe4fdf1c04"] - -sign_script_pubkey = "5221"+pubkeys[0]+"21"+pubkeys[1]+"21"+pubkeys[2]+"53ae" - -class SignedBlockchainTest(BitcoinTestFramework): - """ - Test signed-blockchain-related RPC calls: - - - getnewblockhex - - signblock - - combineblocksigs - - submitblock - - """ - - def __init__(self): - super().__init__() - self.setup_clean_chain = True - self.num_nodes = 3 - - def setup_network(self, split=False): - # Normal multisig scriptPubKey: 1 <33 byte pubkey> <33 byte pubkey> ... 2 OP_CMS - sign_script = "-signblockscript="+sign_script_pubkey - self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [[sign_script],[sign_script], [sign_script]]) - # nodes are disconnected for this test - self.is_network_split = True - - def run_test(self): - keys = ["cRANrxPMceu8jKAA76xzpA9PtTEhBknyZyaaQZ3Z5FnFTGkCAqmT", "cTJRjDBWo1JXdie31B5wv5eXNrPGCAjQKn48umubhmNLjsnj951V", "cVjTXVgKE8PhvCgDowRJVQW68q7j5kSbDGfAT5CUwt9D8dn2cAwf"] - assert_equal(self.nodes[0].getblockcount(), 0) - assert_equal(self.nodes[1].getblockcount(), 0) - - block_hex = self.nodes[0].getnewblockhex() - - # Block needs signatures, but valid and extends chaintip otherwise - assert_equal(self.nodes[0].testproposedblock(block_hex), None) - assert_equal(self.nodes[1].testproposedblock(block_hex), None) - - assert_equal(self.nodes[0].submitblock(block_hex), "block-proof-invalid") - assert_equal(self.nodes[1].submitblock(block_hex), "block-proof-invalid") - - assert_equal(self.nodes[0].getblockcount(), 0) - assert_equal(self.nodes[1].getblockcount(), 0) - - # combineblocksigs only returns true when signatures are appended and enough - # are included to pass validation - assert_equal(self.nodes[0].combineblocksigs(block_hex, [])["complete"], False) - assert_equal(self.nodes[1].combineblocksigs(block_hex, [])["complete"], False) - - # Now we can try to sign, without key - assert_equal(self.nodes[0].signblock(block_hex), "00") - assert_equal(self.nodes[1].signblock(block_hex), "00") - - # Import keys - self.nodes[0].importprivkey(keys[0]) - self.nodes[1].importprivkey(keys[1]) - self.nodes[2].importprivkey(keys[2]) - - sig0 = self.nodes[0].signblock(block_hex) - sig1 = self.nodes[1].signblock(block_hex) - - combined0 = self.nodes[0].combineblocksigs(block_hex, [sig0]) - assert(not combined0["complete"]) - combined1 = self.nodes[0].combineblocksigs(combined0["hex"], [sig1]) - assert(combined1["complete"]) - - # Still haven't moved forward - assert_equal(self.nodes[0].getblockcount(), 0) - assert_equal(self.nodes[1].getblockcount(), 0) - - self.nodes[0].submitblock(combined1["hex"]) - # Move his chain along for later - self.nodes[2].submitblock(combined1["hex"]) - - assert_equal(self.nodes[0].getblockcount(), 1) - assert_equal(self.nodes[1].getblockcount(), 0) - assert_equal(self.nodes[2].getblockcount(), 1) - - block_too_far = self.nodes[0].getnewblockhex() - - assert_raises_jsonrpc,(JSONRPCException, -25, "proposal was not based on our best chain", self.nodes[1].testproposedblock, block_too_far) - - # Finally, submit block - self.nodes[1].submitblock(combined1["hex"]) - assert_equal(self.nodes[1].getblockcount(), 1) - - # Now proposal is fine, aside from sigs - assert_equal(self.nodes[1].testproposedblock(block_too_far), None) - sig0 = self.nodes[0].signblock(block_too_far) - sig1 = self.nodes[1].signblock(block_too_far) - sig2 = self.nodes[2].signblock(block_too_far) - - combined0 = self.nodes[0].combineblocksigs(block_too_far, [sig0]) - assert(not combined0["complete"]) - # combining signature from third node this time - combined1 = self.nodes[0].combineblocksigs(combined0["hex"], [sig2]) - assert(combined1["complete"]) - - # Test getblock(header) signed block fields - tip = self.nodes[0].getblockhash(self.nodes[0].getblockcount()) - header = self.nodes[0].getblockheader(tip) - block = self.nodes[0].getblock(tip) - info = self.nodes[0].getblockchaininfo() - - assert('signblock_witness_asm' in header) - assert('signblock_witness_hex' in header) - assert('signblock_witness_asm' in block) - assert('signblock_witness_hex' in block) - - assert_equal(info['signblock_asm'], "") - assert_equal(info['signblock_hex'], sign_script_pubkey) - - # TODO stuff with too many signatures or junk data manually - -if __name__ == '__main__': - SignedBlockchainTest().main()