Skip to content

Commit 5c0f0b9

Browse files
Fuzzbawlsfurszy
authored andcommitted
WIP: port the test for "fake stake" issue test
Currently this is non-functional as the created coinstake transaction cannot be signed properly (passing the transaction to the coin daemon via `signrawtransaction` returns an error)
1 parent b074cd0 commit 5c0f0b9

File tree

4 files changed

+761
-0
lines changed

4 files changed

+761
-0
lines changed
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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.qtum import *
8+
from test_framework.blocktools import *
9+
from test_framework.key import *
10+
11+
import io
12+
import time
13+
import subprocess
14+
15+
16+
# TestNode: A peer we use to send messages to bitcoind, and store responses.
17+
class TestNode(P2PInterface):
18+
def __init__(self):
19+
super().__init__()
20+
self.last_sendcmpct = []
21+
self.block_announced = False
22+
# Store the hashes of blocks we've seen announced.
23+
# This is for synchronizing the p2p message traffic,
24+
# so we can eg wait until a particular block is announced.
25+
self.announced_blockhashes = set()
26+
27+
def on_sendcmpct(self, message):
28+
self.last_sendcmpct.append(message)
29+
30+
def on_cmpctblock(self, message):
31+
self.block_announced = True
32+
self.last_message["cmpctblock"].header_and_shortids.header.calc_sha256()
33+
self.announced_blockhashes.add(self.last_message["cmpctblock"].header_and_shortids.header.sha256)
34+
35+
def on_headers(self, message):
36+
self.block_announced = True
37+
for x in self.last_message["headers"].headers:
38+
x.calc_sha256()
39+
self.announced_blockhashes.add(x.sha256)
40+
41+
def on_inv(self, message):
42+
for x in self.last_message["inv"].inv:
43+
if x.type == 2:
44+
self.block_announced = True
45+
self.announced_blockhashes.add(x.hash)
46+
47+
# Requires caller to hold mininode_lock
48+
def received_block_announcement(self):
49+
return self.block_announced
50+
51+
def clear_block_announcement(self):
52+
with mininode_lock:
53+
self.block_announced = False
54+
self.last_message.pop("inv", None)
55+
self.last_message.pop("headers", None)
56+
self.last_message.pop("cmpctblock", None)
57+
58+
def get_headers(self, locator, hashstop):
59+
msg = msg_getheaders()
60+
msg.locator.vHave = locator
61+
msg.hashstop = hashstop
62+
self.connection.send_message(msg)
63+
64+
def send_header_for_blocks(self, new_blocks):
65+
headers_message = msg_headers()
66+
headers_message.headers = [CBlockHeader(b) for b in new_blocks]
67+
self.send_message(headers_message)
68+
69+
def request_headers_and_sync(self, locator, hashstop=0):
70+
self.clear_block_announcement()
71+
self.get_headers(locator, hashstop)
72+
wait_until(self.received_block_announcement, timeout=30, lock=mininode_lock)
73+
self.clear_block_announcement()
74+
75+
# Block until a block announcement for a particular block hash is received.
76+
def wait_for_block_announcement(self, block_hash, timeout=30):
77+
def received_hash():
78+
return (block_hash in self.announced_blockhashes)
79+
wait_until(received_hash, timeout=timeout, lock=mininode_lock)
80+
81+
def send_await_disconnect(self, message, timeout=30):
82+
"""Sends a message to the node and wait for disconnect.
83+
84+
This is used when we want to send a message into the node that we expect
85+
will get us disconnected, eg an invalid block."""
86+
self.send_message(message)
87+
wait_until(lambda: not self.connected, timeout=timeout, lock=mininode_lock)
88+
89+
90+
def create_transaction(prevtx, n, sig, value, nTime, scriptPubKey=CScript()):
91+
tx = CTransaction()
92+
assert(n < len(prevtx.vout))
93+
tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), sig, 0xffffffff))
94+
tx.vout.append(CTxOut(value, scriptPubKey))
95+
tx.nTime = nTime
96+
tx.calc_sha256()
97+
return tx
98+
99+
100+
def dir_size(path):
101+
size = subprocess.check_output(['du','-sh', path]).split()[0].decode('utf-8')
102+
return size
103+
104+
105+
NUM_TRANSACTIONS_IN_BLOCK = 1 #6500
106+
107+
108+
class StratiXDOSTest(BitcoinTestFramework):
109+
110+
def create_main_block(self, hashPrevBlock, block_height):
111+
current_time = int(time.time())
112+
nTime = current_time
113+
114+
coinbase = create_coinbase(block_height+1)
115+
coinbase.vout[0].nValue = 0
116+
coinbase.vout[0].scriptPubKey = b""
117+
coinbase.nTime = nTime
118+
coinbase.rehash()
119+
120+
block = create_block(int(hashPrevBlock, 16), coinbase, nTime)
121+
122+
# create a new private key used for block signing.
123+
# solve for the block here
124+
parent_block_stake_modifier = int(self.node.getblock(hashPrevBlock)['modifier'], 16)
125+
if not block.solve_stake(parent_block_stake_modifier, self.staking_prevouts):
126+
raise Exception("Not able to solve for any prev_outpoint")
127+
128+
block.vtx.append(self.sign_single_tx(block.prevoutStake, block.nTime))
129+
del self.staking_prevouts[block.prevoutStake]
130+
131+
# create spam for the block. random transactions
132+
#for j in range(NUM_TRANSACTIONS_IN_BLOCK):
133+
# tx = create_transaction(block.vtx[0], 0, b"1729", j, nTime, scriptPubKey=CScript([self.block_sig_key.get_pubkey(), OP_CHECKSIG]))
134+
# block.vtx.append(tx)
135+
block.hashMerkleRoot = block.calc_merkle_root()
136+
137+
block.rehash()
138+
block.sign_block(self.block_sig_key)
139+
return block
140+
141+
def collect_prevstakeouts(self):
142+
self.staking_prevouts = {}
143+
self.bad_vout_staking_prevouts = []
144+
self.bad_txid_staking_prevouts = []
145+
self.unconfirmed_staking_prevouts = []
146+
tx_block_time = int(time.time())
147+
COINBASE_MATURITY = 100
148+
for unspent in self.node.listunspent():
149+
if unspent['confirmations'] > COINBASE_MATURITY:
150+
self.staking_prevouts[(COutPoint(int(unspent['txid'], 16), unspent['vout']))] = (int(unspent['amount'])*COIN, tx_block_time)
151+
152+
if unspent['confirmations'] < COINBASE_MATURITY:
153+
self.unconfirmed_staking_prevouts.append((COutPoint(int(unspent['txid'], 16), unspent['vout']), int(unspent['amount'])*COIN, tx_block_time))
154+
155+
def sign_single_tx(self, prev_outpoint, nTime):
156+
157+
self.block_sig_key = CECKey()
158+
self.block_sig_key.set_secretbytes(hash256(struct.pack('<I', 0xffff)))
159+
pubkey = self.block_sig_key.get_pubkey()
160+
scriptPubKey = CScript([pubkey, OP_CHECKSIG])
161+
outNValue = 3
162+
163+
stake_tx_unsigned = CTransaction()
164+
stake_tx_unsigned.nTime = nTime
165+
stake_tx_unsigned.vin.append(CTxIn(prev_outpoint))
166+
stake_tx_unsigned.vout.append(CTxOut())
167+
stake_tx_unsigned.vout.append(CTxOut(int(outNValue*COIN), scriptPubKey))
168+
#stake_tx_unsigned.vout.append(CTxOut(int(outNValue*COIN), scriptPubKey))
169+
print(bytes_to_hex_str(stake_tx_unsigned.serialize()))
170+
print("\n\n")
171+
172+
stake_tx_signed_raw_hex = self.node.signrawtransaction(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex']
173+
print(stake_tx_signed_raw_hex)
174+
# print(stake_tx_signed_raw_hex)
175+
# f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))
176+
# print(f)
177+
stake_tx_signed = CTransaction()
178+
# print("init",stake_tx_signed)
179+
stake_tx_signed.deserialize(BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)))
180+
return stake_tx_signed
181+
182+
# Spend all current unspent outputs. Note that we will use these for staking transactions
183+
def spend_tx(self):
184+
for unspent in self.node.listunspent():
185+
try:
186+
inputs = [{"txid":unspent["txid"], "vout":unspent["vout"]}]
187+
outputs = {}
188+
for i in range(0, 10):
189+
outputs[self.node.getnewaddress()] = 25
190+
tx2 = self.node.createrawtransaction(inputs, outputs)
191+
tx2_signed = self.node.signrawtransaction(tx2)["hex"]
192+
self.node.sendrawtransaction(tx2_signed)
193+
except:
194+
pass
195+
196+
def set_test_params(self):
197+
self.setup_clean_chain = True
198+
self.num_nodes = 1
199+
self.extra_args = [['-staking=1', '-debug=net']]*self.num_nodes
200+
201+
def setup_network(self):
202+
# Can't rely on syncing all the nodes when staking=1
203+
self.setup_nodes()
204+
205+
def run_test(self):
206+
# Setup the p2p connections and start up the network thread.
207+
self.nodes[0].add_p2p_connection(TestNode())
208+
209+
network_thread_start() # Start up network handling in another thread
210+
211+
self.node = self.nodes[0]
212+
213+
# Let the test nodes get in sync
214+
for i in range(self.num_nodes):
215+
self.nodes[i].p2p.wait_for_verack()
216+
217+
FORK_DEPTH = 2 # Depth at which we are creating a fork. We are mining
218+
219+
# 1) Starting mining blocks
220+
self.log.info("Mining 200 blocks..")
221+
self.node.generate(200)
222+
223+
# 2) Collect the possible prevouts and spend them
224+
self.log.info("Collecting all unspent coins which we generated from mining..")
225+
self.collect_prevstakeouts()
226+
time.sleep(2)
227+
228+
self.log.info("Spending all the coins which we generated from mining..")
229+
self.spend_tx()
230+
231+
# 3) Start mining again so that spent prevouts get confirmted in a block.
232+
self.log.info("Mining 150 more blocks to confirm the transactions..")
233+
self.node.generate(150)
234+
235+
self.log.info("Sleeping 5 sec. Now mining PoS blocks based on already spent transactions..")
236+
time.sleep(5)
237+
238+
self.log.info("Sending blocks with already spent PoS coinstake transactions..")
239+
240+
block_count = self.node.getblockcount()
241+
242+
pastBlockHash = self.node.getblockhash(block_count - FORK_DEPTH - 1)
243+
244+
self.log.info("Initial size of data dir: %s" % dir_size(self.node.datadir))
245+
MAX_BLOCKS = 30
246+
for i in range(0, MAX_BLOCKS):
247+
if i % 5 == 0:
248+
self.log.info("Sent %s blocks out of %s" % (str(i), str(MAX_BLOCKS)))
249+
height = block_count - FORK_DEPTH - 1
250+
block = self.create_main_block(pastBlockHash, height)
251+
# print(bytes(block.serialize()).hex())
252+
msg = msg_block(block)
253+
self.nodes[0].p2p.send_and_ping(msg)
254+
# In each iteration, send a `block` message with the maximum number of entries
255+
self.log.info("Sent %s blocks out of %s" % (str(i), str(MAX_BLOCKS)))
256+
time.sleep(2)
257+
self.log.info("Final size of data dir: %s" % dir_size(self.node.datadir))
258+
259+
260+
if __name__ == '__main__':
261+
StratiXDOSTest().main()

test/functional/test_framework/messages.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,25 @@ def rehash(self):
453453
self.calc_sha256()
454454
return self.sha256
455455

456+
def solve_stake(self, stakeModifier, prevouts):
457+
target = uint256_from_compact(self.nBits)
458+
loop = True
459+
while loop:
460+
for prevout in prevouts:
461+
nvalue, txBlockTime = prevouts[prevout]
462+
data = b""
463+
data += ser_uint256(stakeModifier)
464+
data += struct.pack("<I", txBlockTime)
465+
data += prevout.serialize()
466+
data += struct.pack("<I", self.nTime)
467+
posHash = uint256_from_str(hash256(data))
468+
if posHash <= target:
469+
self.prevoutStake = prevout
470+
loop = False
471+
break
472+
self.nTime += 1
473+
return True
474+
456475
def __repr__(self):
457476
return "CBlockHeader(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x)" \
458477
% (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot,
@@ -525,6 +544,18 @@ def solve(self):
525544
self.nNonce += 1
526545
self.rehash()
527546

547+
def sign_block(self, key, low_s=True):
548+
data = b""
549+
data += struct.pack("<i", self.nVersion)
550+
data += ser_uint256(self.hashPrevBlock)
551+
data += ser_uint256(self.hashMerkleRoot)
552+
data += struct.pack("<I", self.nTime)
553+
data += struct.pack("<I", self.nBits)
554+
data += struct.pack("<I", self.nNonce)
555+
data += ser_uint256(self.nAccumulatorCheckpoint)
556+
sha256NoSig = hash256(data)
557+
self.vchBlockSig = key.sign(sha256NoSig, low_s=low_s)
558+
528559
def __repr__(self):
529560
return "CBlock(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x vtx=%s)" \
530561
% (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot,

0 commit comments

Comments
 (0)