Skip to content

Commit 2d0f487

Browse files
author
qtum-neil
authored
Merge pull request bitcoin#365 from qtumproject/djaen/python-qa-refactoring
Added MPoS helpers, more python tests and checking and overflow checking for gas stipend
2 parents 14e9f18 + 50da6b8 commit 2d0f487

File tree

8 files changed

+474
-7
lines changed

8 files changed

+474
-7
lines changed

qa/pull-tester/rpc-tests.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,11 @@
185185
'qtum-null-sender.py',
186186
'qtum-transaction-prioritization.py',
187187
'qtum-dgp-block-size-sync.py',
188-
'qtum-opcall.py'
188+
'qtum-opcall.py',
189+
'qtum-assign-mpos-fees-to-gas-refund.py',
190+
'qtum-gas-limit-overflow.py',
191+
'qtum-immature-coinstake-spend.py',
192+
'qtum-ignore-mpos-participant-reward.py',
189193
]
190194

191195

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python3
2+
3+
from test_framework.test_framework import BitcoinTestFramework
4+
from test_framework.util import *
5+
from test_framework.script import *
6+
from test_framework.mininode import *
7+
from test_framework.address import *
8+
from test_framework.qtum import *
9+
import sys
10+
import random
11+
import time
12+
import io
13+
14+
class QtumAssignMPoSFeesToGasRefundTest(BitcoinTestFramework):
15+
def __init__(self):
16+
super().__init__()
17+
self.setup_clean_chain = True
18+
self.num_nodes = 1
19+
20+
def setup_network(self, split=False):
21+
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
22+
self.is_network_split = False
23+
self.node = self.nodes[0]
24+
25+
26+
def run_test(self):
27+
self.node.setmocktime(int(time.time()) - 1000000)
28+
self.node.generate(200 + COINBASE_MATURITY)
29+
30+
activate_mpos(self.node)
31+
# First setup a dummy contract
32+
bytecode = "60606040523415600e57600080fd5b603280601b6000396000f30060606040520000a165627a7a7230582008e466283dd617547ad26d9f100792d4f3e1ec49377f8f53ce3ba3d135beb5b30029"
33+
address = self.node.createcontract(bytecode)['address']
34+
self.node.generate(1)
35+
36+
staking_prevouts = collect_prevouts(self.node, amount=20000)
37+
tip = self.node.getblock(self.node.getbestblockhash())
38+
t = (tip['time'] + 0x30) & 0xfffffff0
39+
self.node.setmocktime(t)
40+
block, block_sig_key = create_unsigned_mpos_block(self.node, staking_prevouts, nTime=t, block_fees=2102200000)
41+
block.hashUTXORoot = int(tip['hashUTXORoot'], 16)
42+
block.hashStateRoot = int(tip['hashStateRoot'], 16)
43+
44+
unspents = [unspent for unspent in self.node.listunspent()[::-1] if unspent['amount'] == 20000]
45+
unspent = unspents.pop(0)
46+
47+
tx_all_fees = CTransaction()
48+
tx_all_fees.vin = [CTxIn(COutPoint(int(unspent['txid'], 16), unspent['vout']))]
49+
tx_all_fees.vout = [CTxOut(0, scriptPubKey=CScript([OP_DUP, OP_HASH160, bytes.fromhex(p2pkh_to_hex_hash(unspent['address'])), OP_EQUALVERIFY, OP_CHECKSIG]))]
50+
tx_all_fees = rpc_sign_transaction(self.node, tx_all_fees)
51+
tx_all_fees.rehash()
52+
block.vtx.append(tx_all_fees)
53+
54+
# Generate a dummy contract call that will steal the mpos participants' fee rewards.
55+
# Since the fees in the last tx was 20000 00000000 we create a tx with 40000000 gas limit and 100000 gas price
56+
unspent = unspents.pop(0)
57+
tx = CTransaction()
58+
tx.vin = [CTxIn(COutPoint(int(unspent['txid'], 16), unspent['vout']))]
59+
tx.vout = [CTxOut(0, scriptPubKey=CScript([b"\x04", CScriptNum(40000000), CScriptNum(100000), b"\x00", bytes.fromhex(address), OP_CALL]))]
60+
tx = rpc_sign_transaction(self.node, tx)
61+
tx.rehash()
62+
block.vtx.append(tx)
63+
64+
# We must also add the refund output to the coinstake
65+
block.vtx[1].vout.append(CTxOut(3997897800000, scriptPubKey=CScript([OP_DUP, OP_HASH160, bytes.fromhex(p2pkh_to_hex_hash(unspent['address'])), OP_EQUALVERIFY, OP_CHECKSIG])))
66+
block.vtx[1].rehash()
67+
68+
block.vtx[1] = rpc_sign_transaction(self.node, block.vtx[1])
69+
block.hashMerkleRoot = block.calc_merkle_root()
70+
block.sign_block(block_sig_key)
71+
blockcount = self.node.getblockcount()
72+
print(self.node.submitblock(bytes_to_hex_str(block.serialize())))
73+
assert_equal(self.node.getblockcount(), blockcount)
74+
75+
if __name__ == '__main__':
76+
QtumAssignMPoSFeesToGasRefundTest().main()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python3
2+
3+
from test_framework.test_framework import BitcoinTestFramework
4+
from test_framework.util import *
5+
from test_framework.script import *
6+
from test_framework.mininode import *
7+
from test_framework.address import *
8+
from test_framework.qtum import *
9+
import sys
10+
import random
11+
import time
12+
import io
13+
14+
class QtumGasLimitOverflowTest(BitcoinTestFramework):
15+
def __init__(self):
16+
super().__init__()
17+
self.setup_clean_chain = True
18+
self.num_nodes = 1
19+
20+
def setup_network(self, split=False):
21+
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
22+
self.is_network_split = False
23+
self.node = self.nodes[0]
24+
25+
def run_test(self):
26+
self.node.setmocktime(int(time.time()) - 1000000)
27+
self.node.generate(200 + COINBASE_MATURITY)
28+
unspents = [unspent for unspent in self.node.listunspent() if unspent['amount'] == 20000]
29+
unspent = unspents.pop(0)
30+
31+
tx = CTransaction()
32+
tx.vin = [CTxIn(COutPoint(int(unspent['txid'], 16), unspent['vout']))]
33+
tx.vout = [CTxOut(0, scriptPubKey=CScript([b"\x04", CScriptNum(0x10000), CScriptNum(0x100000000000), b"\x00", OP_CREATE])) for i in range(0x10)]
34+
tx = rpc_sign_transaction(self.node, tx)
35+
assert_raises(JSONRPCException, self.node.sendrawtransaction, bytes_to_hex_str(tx.serialize()))
36+
self.node.generate(1)
37+
38+
if __name__ == '__main__':
39+
QtumGasLimitOverflowTest().main()
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
3+
from test_framework.test_framework import BitcoinTestFramework
4+
from test_framework.util import *
5+
from test_framework.script import *
6+
from test_framework.mininode import *
7+
from test_framework.address import *
8+
from test_framework.qtum import *
9+
import sys
10+
import random
11+
import time
12+
13+
class QtumIgnoreMPOSParticipantRewardTest(BitcoinTestFramework):
14+
def __init__(self):
15+
super().__init__()
16+
self.setup_clean_chain = True
17+
self.num_nodes = 1
18+
19+
def setup_network(self, split=False):
20+
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
21+
self.is_network_split = False
22+
self.node = self.nodes[0]
23+
24+
def remove_from_staking_prevouts(self, remove_prevout):
25+
for j in range(len(self.staking_prevouts)):
26+
prevout = self.staking_prevouts[j]
27+
if prevout[0].serialize() == remove_prevout.serialize():
28+
self.staking_prevouts.pop(j)
29+
break
30+
31+
def run_test(self):
32+
self.node.setmocktime(int(time.time()) - 1000000)
33+
self.node.generate(10 + COINBASE_MATURITY)
34+
# These are the privkeys that corresponds to the pubkeys in the pos outputs
35+
# These are used by default by create_pos_block
36+
for i in range(0xff+1):
37+
privkey = byte_to_base58(hash256(struct.pack('<I', i)), 239)
38+
self.node.importprivkey(privkey)
39+
40+
"""
41+
pragma solidity ^0.4.12;
42+
contract Test {
43+
function() payable {
44+
}
45+
}
46+
"""
47+
bytecode = "60606040523415600e57600080fd5b5b603580601c6000396000f30060606040525b5b5b0000a165627a7a723058205093ec5d227f741a4c8511f495e12b897d670259ab2f2b5b241af6af08753f5e0029"
48+
contract_address = self.node.createcontract(bytecode)['address']
49+
50+
activate_mpos(self.node)
51+
52+
self.staking_prevouts = collect_prevouts(self.node)
53+
# Only have staking outputs with nValue == 20000.0
54+
# Since the rest of the code relies on this
55+
i = 0
56+
while i < len(self.staking_prevouts):
57+
if self.staking_prevouts[i][1] != 20000*COIN:
58+
self.staking_prevouts.pop(i)
59+
i += 1
60+
61+
nTime = int(time.time()) & 0xfffffff0
62+
self.node.setmocktime(nTime)
63+
64+
65+
# Find the block.number - 505 coinstake's 2nd output
66+
# This will be an mpos participant
67+
mpos_participant_block = self.node.getblock(self.node.getblockhash(self.node.getblockcount() - 505))
68+
mpos_participant_txid = mpos_participant_block['tx'][1]
69+
mpos_participant_tx = self.node.getrawtransaction(mpos_participant_txid, True)
70+
mpos_participant_pubkey = hex_str_to_bytes(mpos_participant_tx['vout'][1]['scriptPubKey']['asm'].split(' ')[0])
71+
mpos_participant_hpubkey = hash160(mpos_participant_pubkey)
72+
mpos_participant_addr = hex_hash_to_p2pkh(bytes_to_hex_str(mpos_participant_hpubkey))
73+
74+
tx = CTransaction()
75+
tx.vin = [make_vin_from_unspent(self.node, address=mpos_participant_addr)]
76+
tx.vout = [CTxOut(0, scriptPubKey=CScript([b"\x04", CScriptNum(4000000), CScriptNum(100000), b"\x00", hex_str_to_bytes(contract_address), OP_CALL]))]
77+
tx = rpc_sign_transaction(self.node, tx)
78+
self.remove_from_staking_prevouts(tx.vin[0].prevout)
79+
80+
block, block_sig_key = create_unsigned_mpos_block(self.node, self.staking_prevouts, block_fees=int(10000*COIN)-397897500000, nTime=nTime)
81+
block.vtx.append(tx)
82+
block.vtx[1].vout.append(CTxOut(397897500000, scriptPubKey=CScript([OP_DUP, OP_HASH160, mpos_participant_hpubkey, OP_EQUALVERIFY, OP_CHECKSIG])))
83+
84+
# Delete the mpos participant reward and assign it to the staker
85+
block.vtx[1].vout.pop(-5)
86+
block.vtx[1].vout[1].nValue += 260210250000
87+
88+
# Resign the coinstake tx
89+
block.vtx[1] = rpc_sign_transaction(self.node, block.vtx[1])
90+
block.hashMerkleRoot = block.calc_merkle_root()
91+
block.sign_block(block_sig_key)
92+
93+
# Make sure that the block was rejected
94+
blockcount = self.node.getblockcount()
95+
print(self.node.submitblock(bytes_to_hex_str(block.serialize())))
96+
assert_equal(self.node.getblockcount(), blockcount)
97+
98+
if __name__ == '__main__':
99+
QtumIgnoreMPOSParticipantRewardTest().main()
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
3+
from test_framework.test_framework import BitcoinTestFramework
4+
from test_framework.util import *
5+
from test_framework.script import *
6+
from test_framework.mininode import *
7+
from test_framework.address import *
8+
from test_framework.qtum import *
9+
import sys
10+
import random
11+
import time
12+
13+
class QtumPrematureCoinstakeSpendTest(BitcoinTestFramework):
14+
def __init__(self):
15+
super().__init__()
16+
self.setup_clean_chain = True
17+
self.num_nodes = 1
18+
19+
def setup_network(self, split=False):
20+
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir)
21+
self.is_network_split = False
22+
self.node = self.nodes[0]
23+
24+
def remove_from_staking_prevouts(self, remove_prevout):
25+
for j in range(len(self.staking_prevouts)):
26+
prevout = self.staking_prevouts[j]
27+
if prevout[0].serialize() == remove_prevout.serialize():
28+
self.staking_prevouts.pop(j)
29+
break
30+
31+
def assert_spend_of_coinstake_at_height(self, height, should_accept):
32+
spend_block = self.node.getblock(self.node.getblockhash(height))
33+
spend_coinstake_txid = spend_block['tx'][1]
34+
spend_coinstake_txout = self.node.gettxout(spend_coinstake_txid, 1)
35+
36+
tx = CTransaction()
37+
tx.vin = [CTxIn(COutPoint(int(spend_coinstake_txid, 16), 1))]
38+
tx.vout = [CTxOut(int(float(str(spend_coinstake_txout['value']))*COIN - 1000000), scriptPubKey=CScript([OP_TRUE]))]
39+
tx = rpc_sign_transaction(self.node, tx)
40+
41+
if should_accept:
42+
self.node.sendrawtransaction(bytes_to_hex_str(tx.serialize()))
43+
else:
44+
assert_raises(JSONRPCException, self.node.sendrawtransaction, bytes_to_hex_str(tx.serialize()))
45+
46+
tip = self.node.getblock(self.node.getbestblockhash())
47+
48+
next_block_time = (tip['time'] + 0x30) & 0xfffffff0
49+
self.node.setmocktime(next_block_time)
50+
block, sig_key = create_unsigned_mpos_block(self.node, self.staking_prevouts, next_block_time, 1000000)
51+
block.vtx.append(tx)
52+
block.hashMerkleRoot = block.calc_merkle_root()
53+
block.rehash()
54+
block.sign_block(sig_key)
55+
blockcount = self.node.getblockcount()
56+
self.node.submitblock(bytes_to_hex_str(block.serialize()))
57+
assert_equal(self.node.getblockcount(), blockcount + (1 if should_accept else 0))
58+
self.remove_from_staking_prevouts(block.prevoutStake)
59+
60+
61+
def run_test(self):
62+
self.node.setmocktime(int(time.time()) - 1000000)
63+
self.node.generate(10 + COINBASE_MATURITY)
64+
# These are the privkeys that corresponds to the pubkeys in the pos outputs
65+
# These are used by default by create_pos_block
66+
for i in range(0xff+1):
67+
privkey = byte_to_base58(hash256(struct.pack('<I', i)), 239)
68+
self.node.importprivkey(privkey)
69+
70+
activate_mpos(self.node)
71+
self.staking_prevouts = collect_prevouts(self.node)
72+
self.assert_spend_of_coinstake_at_height(height=4502, should_accept=False)
73+
self.assert_spend_of_coinstake_at_height(height=4501, should_accept=True)
74+
# Invalidate the last block and make sure that the previous rejection of the premature coinstake spends fails
75+
self.node.invalidateblock(self.node.getbestblockhash())
76+
self.assert_spend_of_coinstake_at_height(height=4502, should_accept=False)
77+
78+
if __name__ == '__main__':
79+
QtumPrematureCoinstakeSpendTest().main()

0 commit comments

Comments
 (0)