def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [ CTxIn(COutPoint(orig_tx.x16r, 0), n_sequence=sequence_value) ] tx.vout = [ CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), CScript([b'a'])) ] tx.rehash() if orig_tx.hash in node.getrawmempool(): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, to_hex(tx)) else: # sendrawtransaction should succeed if the tx is not in the mempool node.sendrawtransaction(to_hex(tx)) return tx
def test_version2_relay(self): inputs = [] outputs = {self.nodes[1].getnewaddress(): 1.0} rawtx = self.nodes[1].createrawtransaction(inputs, outputs) rawtxfund = self.nodes[1].fundrawtransaction(rawtx)['hex'] tx = from_hex(CTransaction(), rawtxfund) tx.nVersion = 2 tx_signed = self.nodes[1].signrawtransaction(to_hex(tx))["hex"] self.nodes[1].sendrawtransaction(tx_signed)
def test_sequence_lock_confirmed_inputs(self): # Create lots of confirmed utxos, and use them to generate lots of random # transactions. max_outputs = 50 addresses = [] while len(addresses) < max_outputs: addresses.append(self.nodes[0].getnewaddress()) while len(self.nodes[0].listunspent()) < 200: random.shuffle(addresses) num_outputs = random.randint(1, max_outputs) outputs = {} for i in range(num_outputs): outputs[addresses[i]] = random.randint(1, 20) * 0.01 self.nodes[0].sendmany("", outputs) self.nodes[0].generate(1) utxos = self.nodes[0].listunspent() # Try creating a lot of random transactions. # Each time, choose a random number of inputs, and randomly set # some of those inputs to be sequence locked (and randomly choose # between height/time locking). Small random chance of making the locks # all pass. for i in range(400): # Randomly choose up to 10 inputs num_inputs = random.randint(1, 10) random.shuffle(utxos) # Track whether any sequence locks used should fail should_pass = True # Track whether this transaction was built with sequence locks using_sequence_locks = False tx = CTransaction() tx.nVersion = 2 value = 0 for j in range(num_inputs): sequence_value = 0xfffffffe # this disables sequence locks # 50% chance we enable sequence locks if random.randint(0, 1): using_sequence_locks = True # 10% of the time, make the input sequence value pass input_will_pass = (random.randint(1, 10) == 1) sequence_value = utxos[j]["confirmations"] if not input_will_pass: sequence_value += 1 should_pass = False # Figure out what the median-time-past was for the confirmed input # Note that if an input has N confirmations, we're going back N blocks # from the tip so that we're looking up MTP of the block # PRIOR to the one the input appears in, as per the BIP68 spec. orig_time = self.get_median_time_past( utxos[j]["confirmations"]) cur_time = self.get_median_time_past(0) # MTP of the tip # can only timelock this input if it's not too old -- otherwise use height can_time_lock = True if ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY ) >= SEQUENCE_LOCKTIME_MASK: can_time_lock = False # if time-lockable, then 50% chance we make this a time lock if random.randint(0, 1) and can_time_lock: # Find first time-lock value that fails, or latest one that succeeds time_delta = sequence_value << SEQUENCE_LOCKTIME_GRANULARITY if input_will_pass and time_delta > cur_time - orig_time: sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) elif not input_will_pass and time_delta <= cur_time - orig_time: sequence_value = ( (cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) + 1 sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx.vin.append( CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), n_sequence=sequence_value)) value += utxos[j]["amount"] * COIN # Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output tx_size = len(to_hex(tx)) // 2 + 120 * num_inputs + 50 tx.vout.append( CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), CScript([b'a']))) rawtx = self.nodes[0].signrawtransaction(to_hex(tx))["hex"] if using_sequence_locks and not should_pass: # This transaction should be rejected assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, rawtx) else: # This raw transaction should be accepted self.nodes[0].sendrawtransaction(rawtx) utxos = self.nodes[0].listunspent()
def test_disable_flag(self): # Create some unconfirmed inputs new_addr = self.nodes[0].getnewaddress() self.nodes[0].sendtoaddress(new_addr, 2) # send 2 RVN utxos = self.nodes[0].listunspent(0, 0) assert (len(utxos) > 0) utxo = utxos[0] tx1 = CTransaction() value = int(satoshi_round(utxo["amount"] - self.relayfee) * COIN) # Check that the disable flag disables relative locktime. # If sequence locks were used, this would require 1 block for the # input to mature. sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 tx1.vin = [ CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), n_sequence=sequence_value) ] tx1.vout = [CTxOut(value, CScript([b'a']))] tx1_signed = self.nodes[0].signrawtransaction(to_hex(tx1))["hex"] tx1_id = self.nodes[0].sendrawtransaction(tx1_signed) tx1_id = int(tx1_id, 16) # This transaction will enable sequence-locks, so this transaction should # fail tx2 = CTransaction() tx2.nVersion = 2 sequence_value = sequence_value & 0x7fffffff tx2.vin = [CTxIn(COutPoint(tx1_id, 0), n_sequence=sequence_value)] tx2.vout = [CTxOut(int(value - self.relayfee * COIN), CScript([b'a']))] tx2.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, to_hex(tx2)) # Setting the version back down to 1 should disable the sequence lock, # so this should be accepted. tx2.nVersion = 1 self.nodes[0].sendrawtransaction(to_hex(tx2))
def test_bip68_not_consensus(self): assert (get_bip9_status(self.nodes[0], 'csv')['status'] != 'active') txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = from_hex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Make an anyone-can-spend transaction tx2 = CTransaction() tx2.nVersion = 1 tx2.vin = [CTxIn(COutPoint(tx1.x16r, 0), n_sequence=0)] tx2.vout = [ CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), CScript([b'a'])) ] # sign tx2 tx2_raw = self.nodes[0].signrawtransaction(to_hex(tx2))["hex"] tx2 = from_hex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(to_hex(tx2)) # Now make an invalid spend of tx2 according to BIP68 sequence_value = 100 # 100 block relative locktime tx3 = CTransaction() tx3.nVersion = 2 tx3.vin = [CTxIn(COutPoint(tx2.x16r, 0), n_sequence=sequence_value)] tx3.vout = [ CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), CScript([b'a'])) ] tx3.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, to_hex(tx3)) # make a block that violates bip68; ensure that the tip updates tip = int(self.nodes[0].getbestblockhash(), 16) block = create_block( tip, create_coinbase(self.nodes[0].getblockcount() + 1)) block.nVersion = 3 block.vtx.extend([tx1, tx2, tx3]) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() self.nodes[0].submitblock(to_hex(block)) assert_equal(self.nodes[0].getbestblockhash(), block.hash)
def test_sequence_lock_unconfirmed_inputs(self): # Store height so we can easily reset the chain at the end of the test cur_height = self.nodes[0].getblockcount() # Create a mempool tx. txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = from_hex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Anyone-can-spend mempool tx. # Sequence lock of 0 should pass. tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.x16r, 0), n_sequence=0)] tx2.vout = [ CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), CScript([b'a'])) ] tx2_raw = self.nodes[0].signrawtransaction(to_hex(tx2))["hex"] tx2 = from_hex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(tx2_raw) # Create a spend of the 0th output of orig_tx with a sequence lock # of 1, and test what happens when submitting. # orig_tx.vout[0] must be an anyone-can-spend output def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [ CTxIn(COutPoint(orig_tx.x16r, 0), n_sequence=sequence_value) ] tx.vout = [ CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), CScript([b'a'])) ] tx.rehash() if orig_tx.hash in node.getrawmempool(): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, to_hex(tx)) else: # sendrawtransaction should succeed if the tx is not in the mempool node.sendrawtransaction(to_hex(tx)) return tx test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Now mine some blocks, but make sure tx2 doesn't get mined. # Use prioritisetransaction to lower the effective feerate to 0 self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(-self.relayfee * COIN)) cur_time = int(time.time()) for _ in range(10): self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) cur_time += 600 assert (tx2.hash in self.nodes[0].getrawmempool()) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Mine tx2, and then try again self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(self.relayfee * COIN)) # Advance the time on the node so that we can test timelocks self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) assert (tx2.hash not in self.nodes[0].getrawmempool()) # Now that tx2 is not in the mempool, a sequence locked spend should # succeed tx3 = test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) assert (tx3.hash in self.nodes[0].getrawmempool()) self.nodes[0].generate(1) assert (tx3.hash not in self.nodes[0].getrawmempool()) # One more test, this time using height locks tx4 = test_nonzero_locks(tx3, self.nodes[0], self.relayfee, use_height_lock=True) assert (tx4.hash in self.nodes[0].getrawmempool()) # Now try combining confirmed and unconfirmed inputs tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True) assert (tx5.hash not in self.nodes[0].getrawmempool()) utxos = self.nodes[0].listunspent() tx5.vin.append( CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), n_sequence=1)) tx5.vout[0].nValue += int(utxos[0]["amount"] * COIN) raw_tx5 = self.nodes[0].signrawtransaction(to_hex(tx5))["hex"] assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5) # Test mempool-BIP68 consistency after reorg # # State of the transactions in the last blocks: # ... -> [ tx2 ] -> [ tx3 ] # tip-1 tip # And currently tx4 is in the mempool. # # If we invalidate the tip, tx3 should get added to the mempool, causing # tx4 to be removed (fails sequence-lock). self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) assert (tx4.hash not in self.nodes[0].getrawmempool()) assert (tx3.hash in self.nodes[0].getrawmempool()) # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in # diagram above). # This would cause tx2 to be added back to the mempool, which in turn causes # tx3 to be removed. tip = int( self.nodes[0].getblockhash(self.nodes[0].getblockcount() - 1), 16) height = self.nodes[0].getblockcount() for _ in range(2): block = create_block(tip, create_coinbase(height), cur_time) block.nVersion = 3 block.rehash() block.solve() tip = block.x16r height += 1 self.nodes[0].submitblock(to_hex(block)) cur_time += 1 mempool = self.nodes[0].getrawmempool() assert (tx3.hash not in mempool) assert (tx2.hash in mempool) # Reset the chain and get rid of the mocktimed-blocks self.nodes[0].setmocktime(0) self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height + 1)) self.nodes[0].generate(10)
def test_double_votes(self): def corrupt_script(script, n_byte): script = bytearray(script) script[n_byte] = 1 if script[n_byte] == 0 else 0 return bytes(script) # initial topology where arrows denote the direction of connections # finalizer2 ← fork1 → finalizer1 # ↓ ︎ # fork2 fork1 = self.nodes[0] fork2 = self.nodes[1] fork1.importmasterkey(regtest_mnemonics[0]['mnemonics']) fork2.importmasterkey(regtest_mnemonics[1]['mnemonics']) finalizer1 = self.nodes[2] finalizer2 = self.nodes[3] connect_nodes(fork1, fork2.index) connect_nodes(fork1, finalizer1.index) connect_nodes(fork1, finalizer2.index) # leave IBD generate_block(fork1) sync_blocks([fork1, fork2, finalizer1, finalizer2]) # clone finalizer finalizer2.importmasterkey(regtest_mnemonics[2]['mnemonics']) finalizer1.importmasterkey(regtest_mnemonics[2]['mnemonics']) disconnect_nodes(fork1, finalizer2.index) addr = finalizer1.getnewaddress('', 'legacy') txid1 = finalizer1.deposit(addr, 1500) wait_until(lambda: txid1 in fork1.getrawmempool()) finalizer2.setlabel(addr, '') txid2 = finalizer2.deposit(addr, 1500) assert_equal(txid1, txid2) connect_nodes(fork1, finalizer2.index) generate_block(fork1) sync_blocks([fork1, fork2, finalizer1, finalizer2]) disconnect_nodes(fork1, finalizer1.index) disconnect_nodes(fork1, finalizer2.index) # pass instant finalization # F F F F J # e0 - e1 - e2 - e3 - e4 - e5 - e[26] fork1, fork2 generate_block(fork1, count=3 + 5 + 5 + 5 + 5 + 1) assert_equal(fork1.getblockcount(), 26) assert_finalizationstate( fork1, { 'currentEpoch': 6, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 1 }) # change topology where forks are not connected # finalizer1 → fork1 # # finalizer2 → fork2 sync_blocks([fork1, fork2]) disconnect_nodes(fork1, fork2.index) # test that same vote included on different forks # doesn't create a slash transaction # v1 # - e6[27, 28, 29, 30] fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] # \ v1 # - e6[27, 28, 29, 30] fork2 self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=fork1) v1 = fork1.getrawtransaction(fork1.getrawmempool()[0]) generate_block(fork1, count=4) assert_equal(fork1.getblockcount(), 30) assert_finalizationstate( fork1, { 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork2) generate_block(fork2) assert_raises_rpc_error(-27, 'transaction already in block chain', fork2.sendrawtransaction, v1) assert_equal(len(fork2.getrawmempool()), 0) generate_block(fork2, count=3) assert_equal(fork2.getblockcount(), 30) assert_finalizationstate( fork1, { 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info('same vote on two forks was accepted') # test that double-vote with invalid vote signature is ignored # and doesn't cause slashing # v1 v2a # - e6 - e7[31, 32] fork1 # F F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] # \ v1 v2a # - e6 - e7[31, 32] fork2 generate_block(fork1) self.wait_for_vote_and_disconnect(finalizer=finalizer1, node=fork1) v2a = fork1.getrawtransaction(fork1.getrawmempool()[0]) generate_block(fork1) assert_equal(fork1.getblockcount(), 32) assert_finalizationstate( fork1, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) generate_block(fork2) tx_v2a = FromHex(CTransaction(), v2a) # corrupt the 1st byte of the validators pubkey in the commit script # see schema in CScript::CommitScript tx_v2a.vout[0].scriptPubKey = corrupt_script( script=tx_v2a.vout[0].scriptPubKey, n_byte=2) assert_raises_rpc_error(-26, 'bad-vote-signature', fork2.sendrawtransaction, ToHex(tx_v2a)) assert_equal(len(fork2.getrawmempool()), 0) self.wait_for_vote_and_disconnect(finalizer=finalizer2, node=fork2) time.sleep( 10 ) # slash transactions are processed every 10 sec. UNIT-E TODO: remove once optimized assert_equal(len(fork2.getrawmempool()), 1) v2b = fork2.getrawtransaction(fork2.getrawmempool()[0]) tx_v2b = FromHex(CTransaction(), v2b) assert_equal(tx_v2b.get_type(), TxType.VOTE) generate_block(fork2) assert_equal(len(fork2.getrawmempool()), 0) assert_equal(fork2.getblockcount(), 32) assert_finalizationstate( fork1, { 'currentEpoch': 7, 'lastJustifiedEpoch': 6, 'lastFinalizedEpoch': 5, 'validators': 1 }) self.log.info('double-vote with invalid signature is ignored') # test that valid double-vote but corrupt withdraw address # creates slash tx it is included in the next block # v1 v2a # - e6 - e7[31, 32] fork1 # F F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 - e6[26] # \ v1 v2a s1 # - e6 - e7[31, 32, 33] fork2 # corrupt the 1st byte of the address in the scriptpubkey # but keep the correct vote signature see schema in CScript::CommitScript tx_v2a = FromHex(CTransaction(), v2a) # Remove the signature tx_v2a.vin[0].scriptSig = list(CScript(tx_v2a.vin[0].scriptSig))[1] tx_v2a.vout[0].scriptPubKey = corrupt_script( script=tx_v2a.vout[0].scriptPubKey, n_byte=42) tx_v2a = sign_transaction(finalizer2, tx_v2a) assert_raises_rpc_error(-26, 'bad-vote-invalid', fork2.sendrawtransaction, ToHex(tx_v2a)) wait_until(lambda: len(fork2.getrawmempool()) == 1, timeout=20) s1_hash = fork2.getrawmempool()[0] s1 = FromHex(CTransaction(), fork2.getrawtransaction(s1_hash)) assert_equal(s1.get_type(), TxType.SLASH) b33 = generate_block(fork2)[0] block = FromHex(CBlock(), fork2.getblock(b33, 0)) assert_equal(len(block.vtx), 2) block.vtx[1].rehash() assert_equal(block.vtx[1].hash, s1_hash) self.log.info('slash tx for double-vote was successfully created')