Merge #6298: test: extend and refactor DIP0020 activation test

c91ba8ac14 fix: no crashes allowed (UdjinM6)
a4cd1d6423 fix: explicitly test no tx in mempool after invalidateblock (UdjinM6)
04b5db9417 test: extend and refactor DIP0020 activation test (UdjinM6)

Pull request description:

  ## Issue being fixed or feature implemented
  ~23590 backport in #6256 results in node crashes (for dashd compiled with `--enable-debug`) when we try to spend coins locked via disabled opcodes in `feature_dip0020_activation.py`. Also,~ there is only rpc part and no tests for relaying such txes via p2p.

  ## What was done?

  ## How Has This Been Tested?
  run test on develop and on top of #6256 + #6299 with and without `--enable-debug`

  ## Breaking Changes

  ## Checklist:
  - [ ] I have performed a self-review of my own code
  - [ ] I have commented my code, particularly in hard-to-understand areas
  - [ ] I have added or updated relevant unit/integration/functional/e2e tests
  - [ ] I have made corresponding changes to the documentation
  - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  PastaPastaPasta:
    utACK c91ba8ac14
  knst:
    utACK c91ba8ac14

Tree-SHA512: 2ba16d6a6bb58cb98c01234ed60a8eecd4ff214d3d8386a4b8ed10f4776e0862d7794747791d82345d6031678a308df39c2dbdd361a902ee1e56cf7f05a73c1a
This commit is contained in:
pasta 2024-10-02 23:07:53 -05:00
commit 42c613ca4d
No known key found for this signature in database
GPG Key ID: E2F3D7916E722D38

View File

@ -2,7 +2,9 @@
# Copyright (c) 2015-2023 The Dash Core developers # Copyright (c) 2015-2023 The Dash Core 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.
from test_framework.blocktools import create_block, create_coinbase
from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut
from test_framework.p2p import P2PDataStore
from test_framework.script import CScript, OP_CAT, OP_DROP, OP_TRUE from test_framework.script import CScript, OP_CAT, OP_DROP, OP_TRUE
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_raises_rpc_error, softfork_active, satoshi_round from test_framework.util import assert_raises_rpc_error, softfork_active, satoshi_round
@ -28,51 +30,84 @@ class DIP0020ActivationTest(BitcoinTestFramework):
def skip_test_if_missing_module(self): def skip_test_if_missing_module(self):
self.skip_if_no_wallet() self.skip_if_no_wallet()
def create_test_block(self, txs, tip_hash, tip_height, tip_time):
block = create_block(int(tip_hash, 16), create_coinbase(tip_height + 1), tip_time + 1)
block.nVersion = 4
block.vtx.extend(txs)
block.hashMerkleRoot = block.calc_merkle_root()
block.rehash()
block.solve()
return block
def run_test(self): def run_test(self):
self.node = self.nodes[0] node = self.nodes[0]
self.relayfee = satoshi_round(self.nodes[0].getnetworkinfo()["relayfee"]) relayfee = satoshi_round(node.getnetworkinfo()["relayfee"])
# We should have some coins already # We should have some coins already
utxos = self.node.listunspent() utxos = node.listunspent()
assert len(utxos) > 0 assert len(utxos) > 0
# Lock some coins using disabled opcodes # Lock some coins using disabled opcodes
utxo = utxos[len(utxos) - 1] utxo = utxos[len(utxos) - 1]
value = int(satoshi_round(utxo["amount"] - self.relayfee) * COIN) value = int(satoshi_round(utxo["amount"] - relayfee) * COIN)
tx = CTransaction() tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]))) tx.vin.append(CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"])))
tx.vout.append(CTxOut(value, CScript([b'1', b'2', OP_CAT]))) tx.vout.append(CTxOut(value, CScript([b'1', b'2', OP_CAT])))
tx_signed_hex = self.node.signrawtransactionwithwallet(tx.serialize().hex())["hex"] tx_signed_hex = node.signrawtransactionwithwallet(tx.serialize().hex())["hex"]
txid = self.node.sendrawtransaction(tx_signed_hex) txid = node.sendrawtransaction(tx_signed_hex)
# This tx should be completely valid, should be included in mempool and mined in the next block # This tx should be completely valid, should be included in mempool and mined in the next block
assert txid in set(self.node.getrawmempool()) assert txid in set(node.getrawmempool())
self.node.generate(1) node.generate(1)
assert txid not in set(self.node.getrawmempool()) assert txid not in set(node.getrawmempool())
# Create spending tx # Create spending tx
value = int(value - self.relayfee * COIN) value = int(value - relayfee * COIN)
tx0 = CTransaction() tx0 = CTransaction()
tx0.vin.append(CTxIn(COutPoint(int(txid, 16), 0))) tx0.vin.append(CTxIn(COutPoint(int(txid, 16), 0)))
tx0.vout.append(CTxOut(value, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) tx0.vout.append(CTxOut(value, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])))
tx0.rehash() tx0.rehash()
tx0_hex = tx0.serialize().hex() tx0_hex = tx0.serialize().hex()
tx0id = node.decoderawtransaction(tx0_hex)["txid"]
# This tx isn't valid yet # flush state to disk before potential crashes below
assert not softfork_active(self.nodes[0], 'dip0020') self.nodes[0].gettxoutsetinfo()
assert_raises_rpc_error(-26, DISABLED_OPCODE_ERROR, self.node.sendrawtransaction, tx0_hex)
# Generate enough blocks to activate DIP0020 opcodes self.log.info("Transactions spending coins with new opcodes aren't accepted before DIP0020 activation")
self.node.generate(98) assert not softfork_active(node, 'dip0020')
assert softfork_active(self.nodes[0], 'dip0020') assert_raises_rpc_error(-26, DISABLED_OPCODE_ERROR, node.sendrawtransaction, tx0_hex)
helper_peer = node.add_p2p_connection(P2PDataStore())
helper_peer.send_txs_and_test([tx0], node, success=False, reject_reason=DISABLED_OPCODE_ERROR)
tip = node.getblock(node.getbestblockhash(), 1)
test_block = self.create_test_block([tx0], tip["hash"], tip["height"], tip["time"])
helper_peer.send_blocks_and_test([test_block], node, success=False, reject_reason='block-validation-failed', expect_disconnect=True)
# Still need 1 more block for mempool to accept new opcodes self.log.info("Generate enough blocks to activate DIP0020 opcodes")
assert_raises_rpc_error(-26, DISABLED_OPCODE_ERROR, self.node.sendrawtransaction, tx0_hex) node.generate(97)
self.node.generate(1) assert not softfork_active(node, 'dip0020')
node.generate(1)
assert softfork_active(node, 'dip0020')
# Should be spendable now # flush state to disk before potential crashes below
tx0id = self.node.sendrawtransaction(tx0_hex) self.nodes[0].gettxoutsetinfo()
assert tx0id in set(self.node.getrawmempool())
# Still need 1 more block for mempool to accept txes spending new opcodes
self.log.info("Transactions spending coins with new opcodes aren't accepted at DIP0020 activation block")
assert_raises_rpc_error(-26, DISABLED_OPCODE_ERROR, node.sendrawtransaction, tx0_hex)
helper_peer = node.add_p2p_connection(P2PDataStore())
helper_peer.send_txs_and_test([tx0], node, success=False, reject_reason=DISABLED_OPCODE_ERROR)
# A block containing new opcodes is accepted however
tip = node.getblock(node.getbestblockhash(), 1)
test_block = self.create_test_block([tx0], tip["hash"], tip["height"], tip["time"])
helper_peer.send_blocks_and_test([test_block], node, success=True)
# txes spending new opcodes still won't be accepted into mempool if we roll back to the previous tip
node.invalidateblock(node.getbestblockhash())
assert tx0id not in set(node.getrawmempool())
node.generate(1)
self.log.info("Transactions spending coins with new opcodes are accepted one block after DIP0020 activation block")
node.sendrawtransaction(tx0_hex)
assert tx0id in set(node.getrawmempool())
if __name__ == '__main__': if __name__ == '__main__':