finalize functional test for blocksigning

This commit is contained in:
Gregory Sanders 2018-11-15 14:28:56 -05:00
parent d1c17499f3
commit 7f2302b365
2 changed files with 65 additions and 153 deletions

View file

@ -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()

View file

@ -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()