Changeset View
Standalone View
test/functional/abc-schnorr-activation.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2015-2016 The Bitcoin Core developers | # Copyright (c) 2015-2016 The Bitcoin Core developers | ||||
# Copyright (c) 2017-2019 The Bitcoin developers | # Copyright (c) 2017-2019 The Bitcoin developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
""" | """ | ||||
This tests the activation of Schnorr transaction signatures: | This tests the treatment of Schnorr transaction signatures: | ||||
- rejection prior to upgrade both in mempool and blocks. | - acceptance both in mempool and blocks. | ||||
- acceptance after upgrade both in mempool and blocks. | - check banning for peers who send txns with 64 byte ECDSA DER sigs. | ||||
- check non-banning for peers who send txns that would be valid on the | |||||
other side of the upgrade. (e.g., if we are still before upgrade and | |||||
peer is post-upgrade) | |||||
- optional: tests of valid 64-byte DER signatures (same length as Schnorr). | |||||
This requires a temporary patch to bitcoind; see fakeDER64 comment below. | |||||
- advance and rewind mempool drop tests. | |||||
Derived from abc-replay-protection.py with improvements borrowed from | Derived from abc-replay-protection.py with improvements borrowed from | ||||
abc-segwit-recovery-activation.py. | abc-segwit-recovery-activation.py. Later reduced down to this feature test. | ||||
""" | """ | ||||
import time | |||||
from test_framework.blocktools import ( | from test_framework.blocktools import ( | ||||
create_block, | create_block, | ||||
create_coinbase, | create_coinbase, | ||||
create_transaction, | create_transaction, | ||||
make_conform_to_ctor, | make_conform_to_ctor, | ||||
) | ) | ||||
from test_framework.comptool import RejectResult, TestInstance, TestManager | from test_framework.comptool import RejectResult, TestInstance, TestManager | ||||
from test_framework.key import CECKey | from test_framework.key import CECKey | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
COIN, | COIN, | ||||
COutPoint, | COutPoint, | ||||
CTransaction, | CTransaction, | ||||
CTxIn, | CTxIn, | ||||
CTxOut, | CTxOut, | ||||
msg_tx, | msg_tx, | ||||
ToHex, | ToHex, | ||||
) | ) | ||||
from test_framework.mininode import ( | from test_framework.mininode import ( | ||||
mininode_lock, | |||||
network_thread_start, | network_thread_start, | ||||
P2PInterface, | P2PInterface, | ||||
) | ) | ||||
from test_framework import schnorr | from test_framework import schnorr | ||||
from test_framework.script import ( | from test_framework.script import ( | ||||
CScript, | CScript, | ||||
OP_1, | OP_1, | ||||
OP_CHECKMULTISIG, | OP_CHECKMULTISIG, | ||||
OP_CHECKSIG, | OP_CHECKSIG, | ||||
OP_TRUE, | OP_TRUE, | ||||
SIGHASH_ALL, | SIGHASH_ALL, | ||||
SIGHASH_FORKID, | SIGHASH_FORKID, | ||||
SignatureHashForkId, | SignatureHashForkId, | ||||
) | ) | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import assert_equal, assert_raises_rpc_error, sync_blocks | from test_framework.util import assert_raises_rpc_error, sync_blocks | ||||
# far into the future | |||||
GREAT_WALL_START_TIME = 2000000000 | |||||
# First blocks (initial coinbases, pre-fork test blocks) happen 1 day before. | # A mandatory (bannable) error occurs when people pass Schnorr signatures into OP_CHECKMULTISIG. | ||||
FIRST_BLOCK_TIME = GREAT_WALL_START_TIME - 86400 | RPC_SCHNORR_MULTISIG_ERROR = '16: mandatory-script-verify-flag-failed (Signature cannot be 65 bytes in CHECKMULTISIG)' | ||||
# If we don't do this, autoreplay protection will activate simultaneous with | # A mandatory (bannable) error occurs when people send invalid Schnorr sigs into OP_CHECKSIG. | ||||
# great_wall and all our sigs will mysteriously fail. | RPC_NULLFAIL_ERROR = '16: mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)' | ||||
REPLAY_PROTECTION_START_TIME = GREAT_WALL_START_TIME * 2 | |||||
# A mandatory (bannable) error occurs when people pass Schnorr signatures | |||||
# into OP_CHECKMULTISIG. The precise error cause changes before/after upgrade | |||||
# (DER / BADLENGTH) so we just match the start of the error. | |||||
RPC_SCHNORR_MULTISIG_ERROR = '16: mandatory-script-verify-flag-failed' | |||||
# These non-mandatory (forgiven) errors occur when your signature isn't valid | |||||
# now, but would be valid on the other side of the upgrade. | |||||
# Error due to passing a Schnorr signature to CHECKSIG before upgrade, but it | |||||
# would have been valid after. | |||||
EARLY_SCHNORR_ERROR = b'upgrade-conditional-script-failure (Non-canonical DER signature)' | |||||
RPC_EARLY_SCHNORR_ERROR = '16: ' + \ | |||||
EARLY_SCHNORR_ERROR.decode('utf8') | |||||
# Error due to passing a 65-byte ECDSA CHECKSIG to mempool after upgrade, but | |||||
# it would have been valid before. | |||||
LATE_DER64_CHECKSIG_ERROR = b'upgrade-conditional-script-failure (Signature must be zero for failed CHECK(MULTI)SIG operation)' | |||||
RPC_LATE_DER64_CHECKSIG_ERROR = '16: ' + \ | |||||
LATE_DER64_CHECKSIG_ERROR.decode('utf8') | |||||
# Error due to passing a 65-byte ECDSA CHECKMULTISIG to mempool after upgrade, | |||||
# but it would have been valid before. | |||||
LATE_DER64_CHECKMULTISIG_ERROR = b'upgrade-conditional-script-failure (Signature cannot be 65 bytes in CHECKMULTISIG)' | |||||
RPC_LATE_DER64_CHECKMULTISIG_ERROR = '16: ' + \ | |||||
LATE_DER64_CHECKMULTISIG_ERROR.decode('utf8') | |||||
# For normal test running: | |||||
fakeDER64 = b'' | |||||
# To properly test activation, we need to make txes with 64 byte DER sigs. | |||||
# (total 65 bytes with the appended hashtype byte, as in CHECKSIG/MULTISIG) | |||||
# The easiest way to do this is to fake them, and then temporarily modify | |||||
# VerifySignature in src/script/interpreter.cpp to always `return true;` | |||||
# for ECDSA sigs, instead of `return pubkey.VerifyECDSA(sighash, vchSig);` | |||||
# Once that patch is done, you can uncomment the following and tests should | |||||
# pass. | |||||
# fakeDER64 = bytes.fromhex('303e021d44444444444444444444444444444444444444444' | |||||
# '44444444444444444021d4444444444444444444444444444' | |||||
# '444444444444444444444444444444') | |||||
assert len(fakeDER64) in [0, 64] | # This 64-byte signature is used to test exclusion & banning according to | ||||
# the above error messages. | |||||
# Tests of real 64 byte ECDSA signatures can be found in script_tests. | |||||
sig64 = b'\0'*64 | |||||
class PreviousSpendableOutput(object): | class PreviousSpendableOutput(object): | ||||
def __init__(self, tx=CTransaction(), n=-1): | def __init__(self, tx=CTransaction(), n=-1): | ||||
self.tx = tx | self.tx = tx | ||||
self.n = n | self.n = n | ||||
class SchnorrActivationTest(BitcoinTestFramework): | class SchnorrTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.block_heights = {} | self.block_heights = {} | ||||
self.tip = None | self.tip = None | ||||
self.blocks = {} | self.blocks = {} | ||||
self.extra_args = [['-whitelist=127.0.0.1', | self.extra_args = [['-whitelist=127.0.0.1'], | ||||
"-greatwallactivationtime={}".format( | []] | ||||
GREAT_WALL_START_TIME), | |||||
"-replayprotectionactivationtime={}".format( | |||||
REPLAY_PROTECTION_START_TIME)], | |||||
["-greatwallactivationtime={}".format( | |||||
GREAT_WALL_START_TIME), | |||||
"-replayprotectionactivationtime={}".format( | |||||
REPLAY_PROTECTION_START_TIME)]] | |||||
def run_test(self): | def run_test(self): | ||||
for node in self.nodes: | |||||
node.setmocktime(GREAT_WALL_START_TIME) | |||||
test = TestManager(self, self.options.tmpdir) | test = TestManager(self, self.options.tmpdir) | ||||
test.add_all_connections([self.nodes[0]]) | test.add_all_connections([self.nodes[0]]) | ||||
# We have made a second node for ban-testing, to which we connect | # We have made a second node for ban-testing, to which we connect | ||||
# the mininode (but not test framework). We make multiple connections | # the mininode (but not test framework). We make multiple connections | ||||
# since each disconnect event consumes a connection (and, after we | # since each disconnect event consumes a connection (and, after we | ||||
# run network_thread_start() we can't make any more connections). | # run network_thread_start() we can't make any more connections). | ||||
for _ in range(3): | for _ in range(3): | ||||
self.nodes[1].add_p2p_connection(P2PInterface()) | self.nodes[1].add_p2p_connection(P2PInterface()) | ||||
network_thread_start() | network_thread_start() | ||||
test.run() | test.run() | ||||
def next_block(self, number, transactions=None, nTime=None): | def next_block(self, number, transactions=None, nTime=None): | ||||
if self.tip == None: | if self.tip == None: | ||||
base_block_hash = self.genesis_hash | base_block_hash = self.genesis_hash | ||||
block_time = FIRST_BLOCK_TIME | block_time = int(time.time()) + 1 | ||||
deadalnix: This should probably be avoided as well. I know it is a common pattern, but this is a bad one. | |||||
jasonbcoxUnsubmitted Not Done Inline ActionsWouldn't it be better to just pick a time in the past and hardcode that here? jasonbcox: Wouldn't it be better to just pick a time in the past and hardcode that here? | |||||
markblundebergAuthorUnsubmitted Done Inline ActionsHmm, I'll give that past-time thing a try. markblundeberg: Hmm, I'll give that past-time thing a try. | |||||
markblundebergAuthorUnsubmitted Done Inline ActionsInteresting, it doesn't work. What happens is that node0 never relays the blocks to node1. The test then fails on sync_blocks(self.nodes), line 262. A brief look in the debug logs shows that node0 never leaves IBD, presumably since it's getting ancient blocks. markblundeberg: Interesting, it doesn't work. What happens is that node0 never relays the blocks to node1. The… | |||||
jasonbcoxUnsubmitted Not Done Inline ActionsYou can node.generate(1) at the very beginning of the test to bring it out of IBD. Alternatively, don't set this line self.setup_clean_chain = True and just extend the test chain from the latest block time. Both of these approaches require some tweaking of values elsewhere in the test though. As Amaury said, this pattern is common, but bad. I think we should move on from this diff and work to solve this problem holistically in the future. jasonbcox: You can `node.generate(1)` at the very beginning of the test to bring it out of IBD. | |||||
markblundebergAuthorUnsubmitted Done Inline ActionsYes, a couple days ago I was experimenting with writing up another test for something else and I tried the setup_clean_chain=False thing with generate(1) at the start. It is not only nicer, but it also is apparently faster! This test doesn't need to be done the way it's done now, especially since it's not activation-related anymore. markblundeberg: Yes, a couple days ago I was experimenting with writing up another test for something else and… | |||||
FabienUnsubmitted Not Done Inline ActionsYes it's definitely faster, because the "unclean" chain first start the nodes and build the blocks, then restart the nodes before running the test. These stop/start are wasting time. Fabien: Yes it's definitely faster, because the "unclean" chain first start the nodes and build the… | |||||
else: | else: | ||||
base_block_hash = self.tip.sha256 | base_block_hash = self.tip.sha256 | ||||
block_time = self.tip.nTime + 1 | block_time = self.tip.nTime + 1 | ||||
if nTime: | if nTime: | ||||
block_time = nTime | block_time = nTime | ||||
# First create the coinbase | # First create the coinbase | ||||
height = self.block_heights[base_block_hash] + 1 | height = self.block_heights[base_block_hash] + 1 | ||||
coinbase = create_coinbase(height) | coinbase = create_coinbase(height) | ||||
▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
return txfund, txspend | return txfund, txspend | ||||
def send_transaction_to_mempool(tx): | def send_transaction_to_mempool(tx): | ||||
tx_id = node.sendrawtransaction(ToHex(tx)) | tx_id = node.sendrawtransaction(ToHex(tx)) | ||||
assert(tx_id in set(node.getrawmempool())) | assert(tx_id in set(node.getrawmempool())) | ||||
return tx_id | return tx_id | ||||
# Check we are not banned when sending a txn that node_ban rejects. | |||||
def check_for_no_ban_on_rejected_tx(tx, reject_code, reject_reason): | |||||
# Grab the first connection | |||||
p2p = node_ban.p2p | |||||
assert(p2p.state == 'connected') | |||||
# The P2PConnection stores a public counter for each message type | |||||
# and the last receive message of each type. We use this counter to | |||||
# identify that we received a new reject message. | |||||
with mininode_lock: | |||||
rejects_count = p2p.message_count['reject'] | |||||
# Send the transaction directly. We use a ping for synchronization: | |||||
# if we have been banned, the pong message won't be received, a | |||||
# timeout occurs and the test fails. | |||||
p2p.send_message(msg_tx(tx)) | |||||
p2p.sync_with_ping() | |||||
# Check we haven't been disconnected | |||||
assert(p2p.state == 'connected') | |||||
# Check the reject message matches what we expected | |||||
with mininode_lock: | |||||
assert(p2p.message_count['reject'] == | |||||
rejects_count + 1) | |||||
reject_msg = p2p.last_message['reject'] | |||||
assert(reject_msg.code == reject_code and | |||||
reject_msg.reason == reject_reason and | |||||
reject_msg.data == tx.sha256) | |||||
# Check we are disconnected when sending a txn that node_ban rejects. | # Check we are disconnected when sending a txn that node_ban rejects. | ||||
# (Can't actually get banned, since bitcoind won't ban local peers.) | # (Can't actually get banned, since bitcoind won't ban local peers.) | ||||
def check_for_ban_on_rejected_tx(tx): | def check_for_ban_on_rejected_tx(tx): | ||||
# Take a connection | # Take a connection | ||||
p2p = node_ban.p2ps.pop() | p2p = node_ban.p2ps.pop() | ||||
assert(p2p.state == 'connected') | assert(p2p.state == 'connected') | ||||
# make sure we can ping | # make sure we can ping | ||||
p2p.sync_with_ping() | p2p.sync_with_ping() | ||||
# send the naughty transaction | # send the naughty transaction | ||||
p2p.send_message(msg_tx(tx)) | p2p.send_message(msg_tx(tx)) | ||||
# if not "banned", this will timeout and raise exception. | # if not "banned", this will timeout and raise exception. | ||||
p2p.wait_for_disconnect() | p2p.wait_for_disconnect() | ||||
# Setup fundings | # Setup fundings | ||||
fundings = [] | fundings = [] | ||||
fund, schnorrchecksigtx = create_fund_and_spend_tx(out[0]) | fund, schnorrchecksigtx = create_fund_and_spend_tx(out[0]) | ||||
fundings.append(fund) | fundings.append(fund) | ||||
fund, schnorrmultisigtx = create_fund_and_spend_tx(out[1], multi=True) | fund, schnorrmultisigtx = create_fund_and_spend_tx(out[1], multi=True) | ||||
fundings.append(fund) | fundings.append(fund) | ||||
fund, ecdsachecksigtx = create_fund_and_spend_tx(out[2], sig='ecdsa') | fund, ecdsachecksigtx = create_fund_and_spend_tx(out[2], sig='ecdsa') | ||||
fundings.append(fund) | fundings.append(fund) | ||||
if fakeDER64: | |||||
fund, DER64checksigtx = create_fund_and_spend_tx( | fund, sig64checksigtx = create_fund_and_spend_tx( | ||||
out[5], sig=fakeDER64) | out[5], sig=sig64) | ||||
fundings.append(fund) | fundings.append(fund) | ||||
fund, DER64multisigtx = create_fund_and_spend_tx( | fund, sig64multisigtx = create_fund_and_spend_tx( | ||||
out[6], multi=True, sig=fakeDER64) | out[6], multi=True, sig=sig64) | ||||
fundings.append(fund) | fundings.append(fund) | ||||
for fund in fundings: | for fund in fundings: | ||||
send_transaction_to_mempool(fund) | send_transaction_to_mempool(fund) | ||||
block(1, transactions=fundings) | block(1, transactions=fundings) | ||||
yield accepted() | yield accepted() | ||||
# we're now set up for the various spends; make sure the other node | # we're now set up for the various spends; make sure the other node | ||||
# is set up, too. | # is set up, too. | ||||
sync_blocks(self.nodes) | sync_blocks(self.nodes) | ||||
# We are before the upgrade, no Schnorrs get in the mempool. | # Typical ECDSA and Schnorr CHECKSIG are valid | ||||
assert_raises_rpc_error(-26, RPC_EARLY_SCHNORR_ERROR, | schnorr_tx_id = send_transaction_to_mempool(schnorrchecksigtx) | ||||
node.sendrawtransaction, ToHex(schnorrchecksigtx)) | ecdsa_tx_id = send_transaction_to_mempool(ecdsachecksigtx) | ||||
assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, | # It can also be mined | ||||
node.sendrawtransaction, ToHex(schnorrmultisigtx)) | block(2, transactions=[schnorrchecksigtx, ecdsachecksigtx]) | ||||
# And blocks containing them are rejected as well. | |||||
block(2, transactions=[schnorrchecksigtx]) | |||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | |||||
# Rewind bad block | |||||
tip(1) | |||||
block(3, transactions=[schnorrmultisigtx]) | |||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | |||||
# Rewind bad block | |||||
tip(1) | |||||
# So far we were creating blocks well in advance of activation. | |||||
# Now, start creating blocks that will move mediantime up to near | |||||
# activation. | |||||
bfork = block(5555, nTime=GREAT_WALL_START_TIME - 1) | |||||
yield accepted() | yield accepted() | ||||
assert schnorr_tx_id not in set(node.getrawmempool()) | |||||
assert ecdsa_tx_id not in set(node.getrawmempool()) | |||||
sync_blocks(self.nodes) | # Schnorr in multisig is rejected with mandatory error. | ||||
# Create 5 more blocks with timestamps from GREAT_WALL_START_TIME+0 to +4 | |||||
for i in range(5): | |||||
block(5200 + i) | |||||
test.blocks_and_transactions.append([self.tip, True]) | |||||
yield test | |||||
# Check we are just before the activation time. | |||||
assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | |||||
GREAT_WALL_START_TIME - 1) | |||||
# We are just before the upgrade, still no Schnorrs get in the mempool, | |||||
assert_raises_rpc_error(-26, RPC_EARLY_SCHNORR_ERROR, | |||||
node.sendrawtransaction, ToHex(schnorrchecksigtx)) | |||||
assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, | assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, | ||||
node.sendrawtransaction, ToHex(schnorrmultisigtx)) | node.sendrawtransaction, ToHex(schnorrmultisigtx)) | ||||
# ... nor in blocks. | # And it is banworthy. | ||||
block(10, transactions=[schnorrchecksigtx]) | check_for_ban_on_rejected_tx(schnorrmultisigtx) | ||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | # And it can't be mined | ||||
# Rewind bad block | block(13, transactions=[schnorrmultisigtx]) | ||||
tip(5204) | |||||
block(11, transactions=[schnorrmultisigtx]) | |||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | yield rejected(RejectResult(16, b'blk-bad-inputs')) | ||||
# Rewind bad block | # Rewind bad block | ||||
tip(5204) | tip(2) | ||||
# Ensure that sending future-valid schnorr txns is *non-bannable*. | |||||
check_for_no_ban_on_rejected_tx( | |||||
schnorrchecksigtx, 16, EARLY_SCHNORR_ERROR) | |||||
# Ensure that sending schnorrs in multisig *is* bannable. | |||||
check_for_ban_on_rejected_tx(schnorrmultisigtx) | |||||
if fakeDER64: | |||||
# Throw a couple of "valid" 65-byte ECDSA signatures into the | |||||
# mempool just prior to the activation. | |||||
faked_checksig_tx_id = send_transaction_to_mempool(DER64checksigtx) | |||||
faked_multisig_tx_id = send_transaction_to_mempool(DER64multisigtx) | |||||
# Put a proper ECDSA transaction into the mempool but it won't | |||||
# be mined... | |||||
ecdsa_tx_id = send_transaction_to_mempool(ecdsachecksigtx) | |||||
# Activate the Schnorr! | |||||
forkblock = block(5556) | |||||
yield accepted() | |||||
# We have exactly hit the activation time. | # If we try to submit a bad 64-byte sig, we fail with mandatory errors. | ||||
assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | |||||
GREAT_WALL_START_TIME) | |||||
# Make sure ECDSA is still in -- we don't want to lose uninvolved txns | |||||
# when the upgrade happens. | |||||
assert ecdsa_tx_id in set(node.getrawmempool()) | |||||
if fakeDER64: | |||||
# The 64-byte DER sigs must be ejected. | |||||
assert faked_checksig_tx_id not in set(node.getrawmempool()) | |||||
assert faked_multisig_tx_id not in set(node.getrawmempool()) | |||||
# If we try to re-add them, they fail with non-banning errors. | |||||
# In CHECKSIG it's invalid Schnorr and hence NULLFAIL. | # In CHECKSIG it's invalid Schnorr and hence NULLFAIL. | ||||
assert_raises_rpc_error(-26, RPC_LATE_DER64_CHECKSIG_ERROR, | assert_raises_rpc_error(-26, RPC_NULLFAIL_ERROR, | ||||
node.sendrawtransaction, ToHex(DER64checksigtx)) | node.sendrawtransaction, ToHex(sig64checksigtx)) | ||||
# In CHECKMULTISIG it's invalid length and hence BAD_LENGTH. | # In CHECKMULTISIG it's invalid length and hence BAD_LENGTH. | ||||
assert_raises_rpc_error(-26, RPC_LATE_DER64_CHECKMULTISIG_ERROR, | assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, | ||||
node.sendrawtransaction, ToHex(DER64multisigtx)) | node.sendrawtransaction, ToHex(sig64multisigtx)) | ||||
# Getting sent these transactions is banworthy. | |||||
check_for_ban_on_rejected_tx(sig64checksigtx) | |||||
check_for_ban_on_rejected_tx(sig64multisigtx) | |||||
# And they can't be mined either... | # And they can't be mined either... | ||||
block(14, transactions=[DER64checksigtx]) | block(14, transactions=[sig64checksigtx]) | ||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | |||||
# Rewind bad block | |||||
tip(5556) | |||||
block(15, transactions=[DER64multisigtx]) | |||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | yield rejected(RejectResult(16, b'blk-bad-inputs')) | ||||
# Rewind bad block | # Rewind bad block | ||||
tip(5556) | tip(2) | ||||
block(15, transactions=[sig64multisigtx]) | |||||
# Ensure that sending past-valid DER64 txns is *non-bannable*. | |||||
check_for_no_ban_on_rejected_tx( | |||||
DER64checksigtx, 16, LATE_DER64_CHECKSIG_ERROR) | |||||
check_for_no_ban_on_rejected_tx( | |||||
DER64multisigtx, 16, LATE_DER64_CHECKMULTISIG_ERROR) | |||||
# The multisig throws a different error now | |||||
assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, | |||||
node.sendrawtransaction, ToHex(schnorrmultisigtx)) | |||||
# And it still can't be mined | |||||
block(16, transactions=[schnorrmultisigtx]) | |||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | yield rejected(RejectResult(16, b'blk-bad-inputs')) | ||||
# Rewind bad block | # Rewind bad block | ||||
tip(5556) | tip(2) | ||||
# Sending schnorrs in multisig is STILL bannable. | |||||
check_for_ban_on_rejected_tx(schnorrmultisigtx) | |||||
# The Schnorr CHECKSIG is now valid | |||||
schnorr_tx_id = send_transaction_to_mempool(schnorrchecksigtx) | |||||
# It can also be mined | |||||
postforkblock = block( | |||||
21, transactions=[schnorrchecksigtx, ecdsachecksigtx]) | |||||
yield accepted() | |||||
# (we mined the ecdsa tx too) | |||||
assert schnorr_tx_id not in set(node.getrawmempool()) | |||||
assert ecdsa_tx_id not in set(node.getrawmempool()) | |||||
# Ok, now we check if a rewind works properly across the activation. | |||||
# First, rewind the normal post-fork block. | |||||
node.invalidateblock(postforkblock.hash) | |||||
# txes popped back into mempool | |||||
assert schnorr_tx_id in set(node.getrawmempool()) | |||||
assert ecdsa_tx_id in set(node.getrawmempool()) | |||||
# Deactivating upgrade. | |||||
node.invalidateblock(forkblock.hash) | |||||
# This should kick out the Schnorr sig, but not the valid ECDSA sig. | |||||
assert schnorr_tx_id not in set(node.getrawmempool()) | |||||
assert ecdsa_tx_id in set(node.getrawmempool()) | |||||
# Check that we also do it properly on deeper rewind. | |||||
node.reconsiderblock(forkblock.hash) | |||||
node.reconsiderblock(postforkblock.hash) | |||||
node.invalidateblock(forkblock.hash) | |||||
assert schnorr_tx_id not in set(node.getrawmempool()) | |||||
assert ecdsa_tx_id in set(node.getrawmempool()) | |||||
# Try an actual reorg (deactivates then activates upgrade in one step) | |||||
node.reconsiderblock(forkblock.hash) | |||||
node.reconsiderblock(postforkblock.hash) | |||||
tip(5204) | |||||
test = TestInstance(sync_every_block=False) | |||||
for i in range(3): | |||||
block(5900 + i) | |||||
test.blocks_and_transactions.append([self.tip, True]) | |||||
# Perform the reorg | |||||
yield test | |||||
# reorg finishes after the fork | |||||
assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | |||||
GREAT_WALL_START_TIME+2) | |||||
# Schnorr didn't get lost! | |||||
assert schnorr_tx_id in set(node.getrawmempool()) | |||||
assert ecdsa_tx_id in set(node.getrawmempool()) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
SchnorrActivationTest().main() | SchnorrTest().main() |
This should probably be avoided as well. I know it is a common pattern, but this is a bad one.