mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-15 12:51:00 +02:00
finalize functional test for blocksigning
This commit is contained in:
parent
d1c17499f3
commit
7f2302b365
2 changed files with 65 additions and 153 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue