|
| 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() |
0 commit comments