示例#1
0
        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
示例#2
0
 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)
示例#3
0
    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()
示例#4
0
    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))
示例#5
0
    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)
示例#6
0
    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)
示例#7
0
    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')