Migrate all 92 Python files in qa/ from Python 2 to Python 3: - Update shebangs from python2 to python3 - Replace print statements with print() calls - Fix imports (http.client, urllib.parse, queue, io, functools) - Convert xrange→range, 0L→0, long→int, cmp→key functions - Fix except/as syntax, bytes/str handling, integer division - Use relative imports in test_framework/ package Add qa/run-tests.sh: multi-stage test runner (1183 lines) - Stage 1: Build/binary verification - Stage 2: Security hardening (PIE, NX, RELRO, canary, FORTIFY) - Stage 3-4: Boost/Google test binaries - Stage 5: Library tests (secp256k1, univalue) - Stage 6: RandomX validation (live chain mining + block checks) - Stage 7: RPC integration (skipped: regtest mining incompatible) - Stage 8: Source code invariant checks - Stage 8b: DragonX-specific source checks (11 tests) - Flags: --chain=dragonx, --live-dragonx, --save-release, --quick - Timestamped reports with release archiving to qa/release-reports/ Add qa/clean-test-reports.sh for housekeeping (--keep=N, --dry-run) Fix test build infrastructure: - src/Makefile.gtest.include: fix zcash_gtest→hush_gtest refs, remove 17 stale source files, add LIBZCASH/LIBHUSH/LIBRANDOMX - src/Makefile.test.include: remove stale sources, deduplicate LDADD/CXXFLAGS, add missing libraries - src/gtest/test_checkblock.cpp: add height param to ContextualCheckBlock - src/test/test_bitcoin.cpp: remove JoinSplitTestingSetup (dead code) Add qa/test-reports/ to .gitignore for transient output.
108 lines
4.6 KiB
Python
Executable File
108 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2016-2024 The Hush developers
|
|
# Copyright (c) 2014 The Bitcoin Core developers
|
|
# Distributed under the GPLv3 software license, see the accompanying
|
|
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
#
|
|
# Test proper accounting with malleable transactions
|
|
#
|
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import assert_equal, connect_nodes, \
|
|
sync_blocks, gather_inputs
|
|
|
|
|
|
class TxnMallTest(BitcoinTestFramework):
|
|
|
|
def add_options(self, parser):
|
|
parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true",
|
|
help="Test double-spend of 1-confirmed transaction")
|
|
|
|
def setup_network(self):
|
|
# Start with split network:
|
|
return super(TxnMallTest, self).setup_network(True)
|
|
|
|
def run_test(self):
|
|
mining_reward = 10
|
|
starting_balance = mining_reward * 25
|
|
|
|
for i in range(4):
|
|
assert_equal(self.nodes[i].getbalance(), starting_balance)
|
|
self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress!
|
|
|
|
# Coins are sent to node1_address
|
|
node1_address = self.nodes[1].getnewaddress("")
|
|
|
|
# First: use raw transaction API to send (starting_balance - (mining_reward - 2)) BTC to node1_address,
|
|
# but don't broadcast:
|
|
(total_in, inputs) = gather_inputs(self.nodes[0], (starting_balance - (mining_reward - 2)))
|
|
change_address = self.nodes[0].getnewaddress("")
|
|
outputs = {}
|
|
outputs[change_address] = (mining_reward - 2)
|
|
outputs[node1_address] = (starting_balance - (mining_reward - 2))
|
|
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
|
|
doublespend = self.nodes[0].signrawtransaction(rawtx)
|
|
assert_equal(doublespend["complete"], True)
|
|
|
|
# Create two transaction from node[0] to node[1]; the
|
|
# second must spend change from the first because the first
|
|
# spends all mature inputs:
|
|
txid1 = self.nodes[0].sendfrom("", node1_address, (starting_balance - (mining_reward - 2)), 0)
|
|
txid2 = self.nodes[0].sendfrom("", node1_address, 5, 0)
|
|
|
|
# Have node0 mine a block:
|
|
if (self.options.mine_block):
|
|
self.nodes[0].generate(1)
|
|
sync_blocks(self.nodes[0:2])
|
|
|
|
tx1 = self.nodes[0].gettransaction(txid1)
|
|
tx2 = self.nodes[0].gettransaction(txid2)
|
|
|
|
# Node0's balance should be starting balance, plus mining_reward for another
|
|
# matured block, minus (starting_balance - (mining_reward - 2)), minus 5, and minus transaction fees:
|
|
expected = starting_balance
|
|
if self.options.mine_block: expected += mining_reward
|
|
expected += tx1["amount"] + tx1["fee"]
|
|
expected += tx2["amount"] + tx2["fee"]
|
|
assert_equal(self.nodes[0].getbalance(), expected)
|
|
|
|
if self.options.mine_block:
|
|
assert_equal(tx1["confirmations"], 1)
|
|
assert_equal(tx2["confirmations"], 1)
|
|
# Node1's total balance should be its starting balance plus both transaction amounts:
|
|
assert_equal(self.nodes[1].getbalance(""), starting_balance - (tx1["amount"]+tx2["amount"]))
|
|
else:
|
|
assert_equal(tx1["confirmations"], 0)
|
|
assert_equal(tx2["confirmations"], 0)
|
|
|
|
# Now give doublespend to miner:
|
|
self.nodes[2].sendrawtransaction(doublespend["hex"])
|
|
# ... mine a block...
|
|
self.nodes[2].generate(1)
|
|
|
|
# Reconnect the split network, and sync chain:
|
|
connect_nodes(self.nodes[1], 2)
|
|
self.nodes[2].generate(1) # Mine another block to make sure we sync
|
|
sync_blocks(self.nodes)
|
|
|
|
# Re-fetch transaction info:
|
|
tx1 = self.nodes[0].gettransaction(txid1)
|
|
tx2 = self.nodes[0].gettransaction(txid2)
|
|
|
|
# Both transactions should be conflicted
|
|
assert_equal(tx1["confirmations"], -1)
|
|
assert_equal(tx2["confirmations"], -1)
|
|
|
|
# Node0's total balance should be starting balance, plus (mining_reward * 2) for
|
|
# two more matured blocks, minus (starting_balance - (mining_reward - 2)) for the double-spend:
|
|
expected = starting_balance + (mining_reward * 2) - (starting_balance - (mining_reward - 2))
|
|
assert_equal(self.nodes[0].getbalance(), expected)
|
|
assert_equal(self.nodes[0].getbalance("*"), expected)
|
|
|
|
# Node1's total balance should be its starting balance plus the amount of the mutated send:
|
|
assert_equal(self.nodes[1].getbalance(""), starting_balance + (starting_balance - (mining_reward - 2)))
|
|
|
|
if __name__ == '__main__':
|
|
TxnMallTest().main()
|