def create_var_transaction(spendfrom, custom_script, size_bytes, fee_sats):
    # Fund and sign a transaction to a given output, padding it to exactly
    # size = size_bytes and providing the given fee.
    # spendfrom should be a CTransaction with first output to OP_TRUE.

    customout = CTxOut(0, custom_script)
    # set output amount to required dust
    customout.nValue = (len(customout.serialize()) + 148) * 3

    ctx = CTransaction()
    ctx.vin.append(CTxIn(COutPoint(spendfrom.sha256, 0), b''))
    ctx.vout.append(
        CTxOut(spendfrom.vout[0].nValue - customout.nValue - fee_sats,
               bytes([OP_TRUE])))
    ctx.vout.append(customout)
    # two padding outputs
    ctx.vout.append(CTxOut(0, b''))
    ctx.vout.append(CTxOut(0, b''))

    size_without_padding = len(ctx.serialize())
    padding_needed = size_bytes - size_without_padding
    assert padding_needed >= 0, (size_bytes, size_without_padding)

    # Now insert padding. We need to be careful due to the flexible
    # var_int size.
    if padding_needed > 0xFFFF + 4:
        # We can fit all padding in one script.
        # 4 extra bytes will be used for var_int
        ctx.vout[2].scriptPubKey = bytes([OP_RETURN]) * (padding_needed - 4)
        padding_needed = 0
    elif padding_needed >= 0xFD + 2:
        # We can pad most (probably all) using a script that is between
        # 253 and 65535 in length. Probably fit everything in one script.
        # 2 extra bytes will be used for var_int
        scriptsize = min(padding_needed - 2, 0xFFFF)
        ctx.vout[2].scriptPubKey = bytes([OP_RETURN]) * scriptsize
        padding_needed -= scriptsize + 2
    elif padding_needed >= 0xFD:
        # 0xFD or 0xFD+1. We can't pad with just one script, so just
        # take off some.
        ctx.vout[2].scriptPubKey = bytes([OP_RETURN]) * 50
        padding_needed -= 50

    # extra padding on another output is needed if original padding_needed
    # was <= 0xfd+1, or was 0xffff+3 or 0xffff+4 .
    assert padding_needed < 0xfd
    ctx.vout[3].scriptPubKey = bytes([OP_RETURN]) * padding_needed

    ctx.calc_sha256()
    assert_equal(len(ctx.serialize()), size_bytes)
    return ctx
def create_transaction(spendfrom, custom_script, amount=None):
    # Fund and sign a transaction to a given output.
    # spendfrom should be a CTransaction with first output to OP_TRUE.

    # custom output will go on position 1, after position 0 which will be
    # OP_TRUE (so it can be reused).
    customout = CTxOut(0, bytes(custom_script))
    # set output amount to required dust if not given
    customout.nValue = amount or (len(customout.serialize()) + 148) * 3

    ctx = CTransaction()
    ctx.vin.append(CTxIn(COutPoint(spendfrom.sha256, 0), b''))
    ctx.vout.append(CTxOut(0, bytes([OP_TRUE])))
    ctx.vout.append(customout)
    pad_tx(ctx)

    fee = len(ctx.serialize())
    ctx.vout[0].nValue = spendfrom.vout[0].nValue - customout.nValue - fee
    ctx.rehash()

    return ctx
Пример #3
0
    def run_test(self):
        node = self.nodes[0]

        self.log.info('Start with empty mempool, and 200 blocks')
        self.mempool_size = 0
        assert_equal(node.getblockcount(), 200)
        assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
        coins = node.listunspent()

        self.log.info('Should not accept garbage to testmempoolaccept')
        assert_raises_rpc_error(
            -3, 'Expected type array, got string',
            lambda: node.testmempoolaccept(rawtxs='ff00baar'))
        assert_raises_rpc_error(
            -8, 'Array must contain exactly one raw transaction for now',
            lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22']))
        assert_raises_rpc_error(
            -22, 'TX decode failed',
            lambda: node.testmempoolaccept(rawtxs=['ff00baar']))

        self.log.info('A transaction already in the blockchain')
        coin = coins.pop()  # Pick a random coin(base) to spend
        raw_tx_in_block = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': coin['txid'],
                    'vout': coin['vout']
                }],
                outputs=[{
                    node.getnewaddress(): 0.3
                }, {
                    node.getnewaddress(): 49
                }],
            ))['hex']
        txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block,
                                                allowhighfees=True)
        node.generate(1)
        self.mempool_size = 0
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_in_block,
                'allowed': False,
                'reject-reason': '18: txn-already-known'
            }],
            rawtxs=[raw_tx_in_block],
        )

        self.log.info('A transaction not in the mempool')
        fee = 0.00000700
        raw_tx_0 = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    "txid": txid_in_block,
                    "vout": 0,
                    "sequence": BIP125_SEQUENCE_NUMBER
                }],  # RBF is used later
                outputs=[{
                    node.getnewaddress(): 0.3 - fee
                }],
            ))['hex']
        tx = CTransaction()
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        txid_0 = tx.rehash()
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': True
            }],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A final transaction not in the mempool')
        coin = coins.pop()  # Pick a random coin(base) to spend
        raw_tx_final = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': coin['txid'],
                    'vout': coin['vout'],
                    "sequence": 0xffffffff
                }],  # SEQUENCE_FINAL
                outputs=[{
                    node.getnewaddress(): 0.025
                }],
                locktime=node.getblockcount() + 2000,  # Can be anything
            ))['hex']
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final)))
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': True
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
            allowhighfees=True,
        )
        node.sendrawtransaction(hexstring=raw_tx_final, allowhighfees=True)
        self.mempool_size += 1

        self.log.info('A transaction in the mempool')
        node.sendrawtransaction(hexstring=raw_tx_0)
        self.mempool_size += 1
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': False,
                'reject-reason': '18: txn-already-in-mempool'
            }],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A transaction that replaces a mempool transaction')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vout[0].nValue -= int(fee * COIN)  # Double the fee
        tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1  # Now, opt out of RBF
        raw_tx_0 = node.signrawtransactionwithwallet(
            bytes_to_hex_str(tx.serialize()))['hex']
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        txid_0 = tx.rehash()
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': True
            }],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A transaction that conflicts with an unconfirmed tx')
        # Send the transaction that replaces the mempool transaction and opts out of replaceability
        node.sendrawtransaction(hexstring=bytes_to_hex_str(tx.serialize()),
                                allowhighfees=True)
        # take original raw_tx_0
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vout[0].nValue -= int(4 * fee * COIN)  # Set more fee
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '18: txn-mempool-conflict'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
            allowhighfees=True,
        )

        self.log.info('A transaction with missing inputs, that never existed')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14)
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info(
            'A transaction with missing inputs, that existed once in the past')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vin[
            0].prevout.n = 1  # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend
        raw_tx_1 = node.signrawtransactionwithwallet(
            bytes_to_hex_str(tx.serialize()))['hex']
        txid_1 = node.sendrawtransaction(hexstring=raw_tx_1,
                                         allowhighfees=True)
        # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them
        raw_tx_spend_both = node.signrawtransactionwithwallet(
            node.createrawtransaction(inputs=[
                {
                    'txid': txid_0,
                    'vout': 0
                },
                {
                    'txid': txid_1,
                    'vout': 0
                },
            ],
                                      outputs=[{
                                          node.getnewaddress(): 0.1
                                      }]))['hex']
        txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both,
                                                  allowhighfees=True)
        node.generate(1)
        self.mempool_size = 0
        # Now see if we can add the coins back to the utxo set by sending the exact txs again
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[raw_tx_0],
        )
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_1,
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[raw_tx_1],
        )

        self.log.info('Create a signed "reference" tx for later use')
        raw_tx_reference = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': txid_spend_both,
                    'vout': 0
                }],
                outputs=[{
                    node.getnewaddress(): 0.05
                }],
            ))['hex']
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        # Reference tx should be valid on itself
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': True
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A transaction with no outputs')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout = []
        # Skip re-signing the transaction for context independent checks from now on
        # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex'])))
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '16: bad-txns-vout-empty'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A really large transaction')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin = [tx.vin[0]] * math.ceil(
            MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize()))
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '16: bad-txns-oversize'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A transaction with negative output value')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].nValue *= -1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '16: bad-txns-vout-negative'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A transaction with too large output value')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].nValue = 21000000 * COIN + 1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '16: bad-txns-vout-toolarge'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A transaction with too large sum of output values')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout = [tx.vout[0]] * 2
        tx.vout[0].nValue = 21000000 * COIN
        self.check_mempool_result(
            result_expected=[{
                'txid':
                tx.rehash(),
                'allowed':
                False,
                'reject-reason':
                '16: bad-txns-txouttotal-toolarge'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A transaction with duplicate inputs')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin = [tx.vin[0]] * 2
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '16: bad-txns-inputs-duplicate'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A coinbase transaction')
        # Pick the input of the first tx we signed, so it has to be a coinbase tx
        raw_tx_coinbase_spent = node.getrawtransaction(
            txid=node.decoderawtransaction(
                hexstring=raw_tx_in_block)['vin'][0]['txid'])
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent)))
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '16: coinbase'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('Some nonstandard transactions')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.nVersion = 3  # A version currently non-standard
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: version'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].scriptPubKey = CScript([OP_0])  # Some non-standard script
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: scriptpubkey'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin[0].scriptSig = CScript([OP_HASH160
                                       ])  # Some not-pushonly scriptSig
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: scriptsig-not-pushonly'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        output_p2sh_burn = CTxOut(nValue=540,
                                  scriptPubKey=CScript(
                                      [OP_HASH160,
                                       hash160(b'burn'), OP_EQUAL]))
        num_scripts = 100000 // len(output_p2sh_burn.serialize(
        ))  # Use enough outputs to make the tx too large for our policy
        tx.vout = [output_p2sh_burn] * num_scripts
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: tx-size'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0] = output_p2sh_burn
        tx.vout[
            0].nValue -= 1  # Make output smaller, such that it is dust for our policy
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: dust'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
        tx.vout = [tx.vout[0]] * 2
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: multi-op-return'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A timelocked transaction')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin[
            0].nSequence -= 1  # Should be non-max, so locktime is not ignored
        tx.nLockTime = node.getblockcount() + 1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: non-final'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
        )

        self.log.info('A transaction that is locked by BIP68 sequence logic')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin[
            0].nSequence = 2  # We could include it in the second block mined from now, but not the very next one
        # Can skip re-signing the tx because of early rejection
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': '64: non-BIP68-final'
            }],
            rawtxs=[bytes_to_hex_str(tx.serialize())],
            allowhighfees=True,
        )
Пример #4
0
    def run_test(self):
        node = self.nodes[0]

        self.log.info('Start with empty mempool, and 200 blocks')
        self.mempool_size = 0
        assert_equal(node.getblockcount(), 200)
        assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
        coins = node.listunspent()

        self.log.info('Should not accept garbage to testmempoolaccept')
        assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar'))
        assert_raises_rpc_error(-8, 'Array must contain exactly one raw transaction for now', lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22']))
        assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar']))

        self.log.info('A transaction already in the blockchain')
        coin = coins.pop()  # Pick a random coin(base) to spend
        raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction(
            inputs=[{'txid': coin['txid'], 'vout': coin['vout']}],
            outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}],
        ))['hex']
        txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0)
        node.generate(1)
        self.mempool_size = 0
        self.check_mempool_result(
            result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': '18: txn-already-known'}],
            rawtxs=[raw_tx_in_block],
        )

        self.log.info('A transaction not in the mempool')
        fee = 0.00000700
        raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction(
            inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}],  # RBF is used later
            outputs=[{node.getnewaddress(): 0.3 - fee}],
        ))['hex']
        tx = CTransaction()
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        txid_0 = tx.rehash()
        self.check_mempool_result(
            result_expected=[{'txid': txid_0, 'allowed': True}],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A final transaction not in the mempool')
        coin = coins.pop()  # Pick a random coin(base) to spend
        raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction(
            inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}],  # SEQUENCE_FINAL
            outputs=[{node.getnewaddress(): 0.025}],
            locktime=node.getblockcount() + 2000,  # Can be anything
        ))['hex']
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final)))
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': True}],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )
        node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0)
        self.mempool_size += 1

        self.log.info('A transaction in the mempool')
        node.sendrawtransaction(hexstring=raw_tx_0)
        self.mempool_size += 1
        self.check_mempool_result(
            result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': '18: txn-already-in-mempool'}],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A transaction that replaces a mempool transaction')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vout[0].nValue -= int(fee * COIN)  # Double the fee
        tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1  # Now, opt out of RBF
        raw_tx_0 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex']
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        txid_0 = tx.rehash()
        self.check_mempool_result(
            result_expected=[{'txid': txid_0, 'allowed': True}],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A transaction that conflicts with an unconfirmed tx')
        # Send the transaction that replaces the mempool transaction and opts out of replaceability
        node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
        # take original raw_tx_0
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vout[0].nValue -= int(4 * fee * COIN)  # Set more fee
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict'}],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )

        self.log.info('A transaction with missing inputs, that never existed')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14)
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with missing inputs, that existed once in the past')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
        tx.vin[0].prevout.n = 1  # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend
        raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex']
        txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0)
        # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them
        raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction(
            inputs=[
                {'txid': txid_0, 'vout': 0},
                {'txid': txid_1, 'vout': 0},
            ],
            outputs=[{node.getnewaddress(): 0.1}]
        ))['hex']
        txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0)
        node.generate(1)
        self.mempool_size = 0
        # Now see if we can add the coins back to the utxo set by sending the exact txs again
        self.check_mempool_result(
            result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs'}],
            rawtxs=[raw_tx_0],
        )
        self.check_mempool_result(
            result_expected=[{'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs'}],
            rawtxs=[raw_tx_1],
        )

        self.log.info('Create a signed "reference" tx for later use')
        raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction(
            inputs=[{'txid': txid_spend_both, 'vout': 0}],
            outputs=[{node.getnewaddress(): 0.05}],
        ))['hex']
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        # Reference tx should be valid on itself
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': True}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with no outputs')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout = []
        # Skip re-signing the transaction for context independent checks from now on
        # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(tx.serialize().hex())['hex'])))
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-empty'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A really large transaction')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize()))
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-oversize'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with negative output value')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].nValue *= -1
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-negative'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with too large output value')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].nValue = 21000000 * COIN + 1
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-toolarge'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with too large sum of output values')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout = [tx.vout[0]] * 2
        tx.vout[0].nValue = 21000000 * COIN
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-txouttotal-toolarge'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with duplicate inputs')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin = [tx.vin[0]] * 2
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-inputs-duplicate'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A coinbase transaction')
        # Pick the input of the first tx we signed, so it has to be a coinbase tx
        raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid'])
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent)))
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: coinbase'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('Some nonstandard transactions')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.nVersion = 3  # A version currently non-standard
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: version'}],
            rawtxs=[tx.serialize().hex()],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].scriptPubKey = CScript([OP_0])  # Some non-standard script
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptpubkey'}],
            rawtxs=[tx.serialize().hex()],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin[0].scriptSig = CScript([OP_HASH160])  # Some not-pushonly scriptSig
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptsig-not-pushonly'}],
            rawtxs=[tx.serialize().hex()],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL]))
        num_scripts = 100000 // len(output_p2sh_burn.serialize())  # Use enough outputs to make the tx too large for our policy
        tx.vout = [output_p2sh_burn] * num_scripts
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: tx-size'}],
            rawtxs=[tx.serialize().hex()],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0] = output_p2sh_burn
        tx.vout[0].nValue -= 1  # Make output smaller, such that it is dust for our policy
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: dust'}],
            rawtxs=[tx.serialize().hex()],
        )
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
        tx.vout = [tx.vout[0]] * 2
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: multi-op-return'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A timelocked transaction')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin[0].nSequence -= 1  # Should be non-max, so locktime is not ignored
        tx.nLockTime = node.getblockcount() + 1
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-final'}],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction that is locked by BIP68 sequence logic')
        tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
        tx.vin[0].nSequence = 2  # We could include it in the second block mined from now, but not the very next one
        # Can skip re-signing the tx because of early rejection
        self.check_mempool_result(
            result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final'}],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )
Пример #5
0
    def run_test(self):
        node = self.nodes[0]

        self.log.info('Start with empty mempool, and 200 blocks')
        self.mempool_size = 0
        assert_equal(node.getblockcount(), 200)
        assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
        coins = node.listunspent()

        self.log.info('Should not accept garbage to testmempoolaccept')
        assert_raises_rpc_error(
            -3, 'Expected type array, got string',
            lambda: node.testmempoolaccept(rawtxs='ff00baar'))
        assert_raises_rpc_error(
            -8, 'Array must contain between 1 and 25 transactions.',
            lambda: node.testmempoolaccept(rawtxs=['ff22'] * 26))
        assert_raises_rpc_error(
            -8, 'Array must contain between 1 and 25 transactions.',
            lambda: node.testmempoolaccept(rawtxs=[]))
        assert_raises_rpc_error(
            -22, 'TX decode failed',
            lambda: node.testmempoolaccept(rawtxs=['ff00baar']))

        self.log.info('A transaction already in the blockchain')
        coin = coins.pop()  # Pick a random coin(base) to spend
        raw_tx_in_block = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': coin['txid'],
                    'vout': coin['vout']
                }],
                outputs=[{
                    node.getnewaddress(): 0.3
                }, {
                    node.getnewaddress(): 49
                }],
            ))['hex']
        txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block,
                                                maxfeerate=0)
        self.generate(node, 1)
        self.mempool_size = 0
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_in_block,
                'allowed': False,
                'reject-reason': 'txn-already-known'
            }],
            rawtxs=[raw_tx_in_block],
        )

        self.log.info('A transaction not in the mempool')
        fee = Decimal('0.000007')
        raw_tx_0 = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    "txid": txid_in_block,
                    "vout": 0,
                    "sequence": BIP125_SEQUENCE_NUMBER
                }],  # RBF is used later
                outputs=[{
                    node.getnewaddress(): Decimal('0.3') - fee
                }],
            ))['hex']
        tx = tx_from_hex(raw_tx_0)
        txid_0 = tx.rehash()
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': True,
                'vsize': tx.get_vsize(),
                'fees': {
                    'base': fee
                }
            }],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A final transaction not in the mempool')
        coin = coins.pop()  # Pick a random coin(base) to spend
        output_amount = Decimal('0.025')
        raw_tx_final = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': coin['txid'],
                    'vout': coin['vout'],
                    "sequence": 0xffffffff
                }],  # SEQUENCE_FINAL
                outputs=[{
                    node.getnewaddress(): output_amount
                }],
                locktime=node.getblockcount() + 2000,  # Can be anything
            ))['hex']
        tx = tx_from_hex(raw_tx_final)
        fee_expected = coin['amount'] - output_amount
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': True,
                'vsize': tx.get_vsize(),
                'fees': {
                    'base': fee_expected
                }
            }],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )
        node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0)
        self.mempool_size += 1

        self.log.info('A transaction in the mempool')
        node.sendrawtransaction(hexstring=raw_tx_0)
        self.mempool_size += 1
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': False,
                'reject-reason': 'txn-already-in-mempool'
            }],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A transaction that replaces a mempool transaction')
        tx = tx_from_hex(raw_tx_0)
        tx.vout[0].nValue -= int(fee * COIN)  # Double the fee
        tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1  # Now, opt out of RBF
        raw_tx_0 = node.signrawtransactionwithwallet(
            tx.serialize().hex())['hex']
        tx = tx_from_hex(raw_tx_0)
        txid_0 = tx.rehash()
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': True,
                'vsize': tx.get_vsize(),
                'fees': {
                    'base': (2 * fee)
                }
            }],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A transaction that conflicts with an unconfirmed tx')
        # Send the transaction that replaces the mempool transaction and opts out of replaceability
        node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
        # take original raw_tx_0
        tx = tx_from_hex(raw_tx_0)
        tx.vout[0].nValue -= int(4 * fee * COIN)  # Set more fee
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'txn-mempool-conflict'
            }],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )

        self.log.info('A transaction with missing inputs, that never existed')
        tx = tx_from_hex(raw_tx_0)
        tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14)
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info(
            'A transaction with missing inputs, that existed once in the past')
        tx = tx_from_hex(raw_tx_0)
        tx.vin[
            0].prevout.n = 1  # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend
        raw_tx_1 = node.signrawtransactionwithwallet(
            tx.serialize().hex())['hex']
        txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0)
        # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them
        raw_tx_spend_both = node.signrawtransactionwithwallet(
            node.createrawtransaction(inputs=[
                {
                    'txid': txid_0,
                    'vout': 0
                },
                {
                    'txid': txid_1,
                    'vout': 0
                },
            ],
                                      outputs=[{
                                          node.getnewaddress(): 0.1
                                      }]))['hex']
        txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both,
                                                  maxfeerate=0)
        self.generate(node, 1)
        self.mempool_size = 0
        # Now see if we can add the coins back to the utxo set by sending the exact txs again
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[raw_tx_0],
        )
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_1,
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[raw_tx_1],
        )

        self.log.info('Create a signed "reference" tx for later use')
        raw_tx_reference = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': txid_spend_both,
                    'vout': 0
                }],
                outputs=[{
                    node.getnewaddress(): 0.05
                }],
            ))['hex']
        tx = tx_from_hex(raw_tx_reference)
        # Reference tx should be valid on itself
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': True,
                'vsize': tx.get_vsize(),
                'fees': {
                    'base': Decimal('0.1') - Decimal('0.05')
                }
            }],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )

        self.log.info('A transaction with no outputs')
        tx = tx_from_hex(raw_tx_reference)
        tx.vout = []
        # Skip re-signing the transaction for context independent checks from now on
        # tx = tx_from_hex(node.signrawtransactionwithwallet(tx.serialize().hex())['hex'])
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-vout-empty'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A really large transaction')
        tx = tx_from_hex(raw_tx_reference)
        tx.vin = [tx.vin[0]] * math.ceil(
            MAX_BLOCK_WEIGHT // 4 / len(tx.vin[0].serialize()))
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-oversize'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with negative output value')
        tx = tx_from_hex(raw_tx_reference)
        tx.vout[0].nValue *= -1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-vout-negative'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        # The following two validations prevent overflow of the output amounts (see CVE-2010-5139).
        self.log.info('A transaction with too large output value')
        tx = tx_from_hex(raw_tx_reference)
        tx.vout[0].nValue = MAX_MONEY + 1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-vout-toolarge'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with too large sum of output values')
        tx = tx_from_hex(raw_tx_reference)
        tx.vout = [tx.vout[0]] * 2
        tx.vout[0].nValue = MAX_MONEY
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-txouttotal-toolarge'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction with duplicate inputs')
        tx = tx_from_hex(raw_tx_reference)
        tx.vin = [tx.vin[0]] * 2
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-inputs-duplicate'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A non-coinbase transaction with coinbase-like outpoint')
        tx = tx_from_hex(raw_tx_reference)
        tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff)))
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-prevout-null'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A coinbase transaction')
        # Pick the input of the first tx we signed, so it has to be a coinbase tx
        raw_tx_coinbase_spent = node.getrawtransaction(
            txid=node.decoderawtransaction(
                hexstring=raw_tx_in_block)['vin'][0]['txid'])
        tx = tx_from_hex(raw_tx_coinbase_spent)
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'coinbase'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('Some nonstandard transactions')
        tx = tx_from_hex(raw_tx_reference)
        tx.nVersion = 3  # A version currently non-standard
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'version'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = tx_from_hex(raw_tx_reference)
        tx.vout[0].scriptPubKey = CScript([OP_0])  # Some non-standard script
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'scriptpubkey'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = tx_from_hex(raw_tx_reference)
        key = ECKey()
        key.generate()
        pubkey = key.get_pubkey().get_bytes()
        tx.vout[0].scriptPubKey = keys_to_multisig_script(
            [pubkey] * 3, k=2)  # Some bare multisig script (2-of-3)
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bare-multisig'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = tx_from_hex(raw_tx_reference)
        tx.vin[0].scriptSig = CScript([OP_HASH160
                                       ])  # Some not-pushonly scriptSig
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'scriptsig-not-pushonly'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = tx_from_hex(raw_tx_reference)
        tx.vin[0].scriptSig = CScript(
            [b'a' * 1648])  # Some too large scriptSig (>1650 bytes)
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'scriptsig-size'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = tx_from_hex(raw_tx_reference)
        output_p2sh_burn = CTxOut(nValue=540,
                                  scriptPubKey=script_to_p2sh_script(b'burn'))
        num_scripts = 100000 // len(output_p2sh_burn.serialize(
        ))  # Use enough outputs to make the tx too large for our policy
        tx.vout = [output_p2sh_burn] * num_scripts
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'tx-size'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = tx_from_hex(raw_tx_reference)
        tx.vout[0] = output_p2sh_burn
        tx.vout[
            0].nValue -= 1  # Make output smaller, such that it is dust for our policy
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'dust'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = tx_from_hex(raw_tx_reference)
        tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
        tx.vout = [tx.vout[0]] * 2
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'multi-op-return'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A timelocked transaction')
        tx = tx_from_hex(raw_tx_reference)
        tx.vin[
            0].nSequence -= 1  # Should be non-max, so locktime is not ignored
        tx.nLockTime = node.getblockcount() + 1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'non-final'
            }],
            rawtxs=[tx.serialize().hex()],
        )

        self.log.info('A transaction that is locked by BIP68 sequence logic')
        tx = tx_from_hex(raw_tx_reference)
        tx.vin[
            0].nSequence = 2  # We could include it in the second block mined from now, but not the very next one
        # Can skip re-signing the tx because of early rejection
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'non-BIP68-final'
            }],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )
Пример #6
0
    def run_test(self):
        node = self.nodes[0]

        self.log.info('Start with empty mempool, and 200 blocks')
        self.mempool_size = 0
        assert_equal(node.getblockcount(), 200)
        assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
        coins = node.listunspent()

        self.log.info('Should not accept garbage to testmempoolaccept')
        assert_raises_rpc_error(
            -3, 'Expected type array, got string',
            lambda: node.testmempoolaccept(rawtxs='ff00baar'))
        assert_raises_rpc_error(
            -8, 'Array must contain exactly one raw transaction for now',
            lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22']))
        assert_raises_rpc_error(
            -22, 'TX decode failed',
            lambda: node.testmempoolaccept(rawtxs=['ff00baar']))

        self.log.info('A transaction already in the blockchain')
        # Pick a random coin(base) to spend
        coin = coins.pop()
        raw_tx_in_block = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': coin['txid'],
                    'vout': coin['vout']
                }],
                outputs=[{
                    node.getnewaddress(): 300000
                }, {
                    node.getnewaddress(): 49000000
                }],
            ))['hex']
        txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block,
                                                maxfeerate=0)
        node.generate(1)
        self.mempool_size = 0
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_in_block,
                'allowed': False,
                'reject-reason': 'txn-already-known'
            }],
            rawtxs=[raw_tx_in_block],
        )

        self.log.info('A transaction not in the mempool')
        fee = 7.00
        raw_tx_0 = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    "txid": txid_in_block,
                    "vout": 0,
                    "sequence": 0xfffffffd
                }],
                outputs=[{
                    node.getnewaddress(): 300000 - fee
                }],
            ))['hex']
        tx = FromHex(CTransaction(), raw_tx_0)
        txid_0 = tx.rehash()
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': True
            }],
            rawtxs=[raw_tx_0],
        )

        self.log.info('A final transaction not in the mempool')
        # Pick a random coin(base) to spend
        coin = coins.pop()
        raw_tx_final = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': coin['txid'],
                    'vout': coin['vout'],
                    "sequence": 0xffffffff
                }],  # SEQUENCE_FINAL
                outputs=[{
                    node.getnewaddress(): 25000
                }],
                locktime=node.getblockcount() + 2000,  # Can be anything
            ))['hex']
        tx = FromHex(CTransaction(), raw_tx_final)
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': True
            }],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )
        node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0)
        self.mempool_size += 1

        self.log.info('A transaction in the mempool')
        node.sendrawtransaction(hexstring=raw_tx_0)
        self.mempool_size += 1
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': False,
                'reject-reason': 'txn-already-in-mempool'
            }],
            rawtxs=[raw_tx_0],
        )

        # Removed RBF test
        # self.log.info('A transaction that replaces a mempool transaction')
        # ...

        self.log.info('A transaction that conflicts with an unconfirmed tx')
        # Send the transaction that conflicts with the mempool transaction
        node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
        # take original raw_tx_0
        tx = FromHex(CTransaction(), raw_tx_0)
        tx.vout[0].nValue -= int(4 * fee * XEC)  # Set more fee
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'txn-mempool-conflict'
            }],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )

        self.log.info('A transaction with missing inputs, that never existed')
        tx = FromHex(CTransaction(), raw_tx_0)
        tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14)
        # skip re-signing the tx
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info(
            'A transaction with missing inputs, that existed once in the past')
        tx = FromHex(CTransaction(), raw_tx_0)
        # Set vout to 1, to spend the other outpoint (49 coins) of the
        # in-chain-tx we want to double spend
        tx.vin[0].prevout.n = 1
        raw_tx_1 = node.signrawtransactionwithwallet(ToHex(tx))['hex']
        txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0)
        # Now spend both to "clearly hide" the outputs, ie. remove the coins
        # from the utxo set by spending them
        raw_tx_spend_both = node.signrawtransactionwithwallet(
            node.createrawtransaction(inputs=[
                {
                    'txid': txid_0,
                    'vout': 0
                },
                {
                    'txid': txid_1,
                    'vout': 0
                },
            ],
                                      outputs=[{
                                          node.getnewaddress(): 100000
                                      }]))['hex']
        txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both,
                                                  maxfeerate=0)
        node.generate(1)
        self.mempool_size = 0
        # Now see if we can add the coins back to the utxo set by sending the
        # exact txs again
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_0,
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[raw_tx_0],
        )
        self.check_mempool_result(
            result_expected=[{
                'txid': txid_1,
                'allowed': False,
                'reject-reason': 'missing-inputs'
            }],
            rawtxs=[raw_tx_1],
        )

        self.log.info('Create a signed "reference" tx for later use')
        raw_tx_reference = node.signrawtransactionwithwallet(
            node.createrawtransaction(
                inputs=[{
                    'txid': txid_spend_both,
                    'vout': 0
                }],
                outputs=[{
                    node.getnewaddress(): 50000
                }],
            ))['hex']
        tx = FromHex(CTransaction(), raw_tx_reference)
        # Reference tx should be valid on itself
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': True
            }],
            rawtxs=[ToHex(tx)],
            maxfeerate=0,
        )

        self.log.info('A transaction with no outputs')
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vout = []
        # Skip re-signing the transaction for context independent checks from now on
        # FromHex(tx, node.signrawtransactionwithwallet(ToHex(tx))['hex'])
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-vout-empty'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('A really large transaction')
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vin = [tx.vin[0]
                  ] * (1 + MAX_BLOCK_BASE_SIZE // len(tx.vin[0].serialize()))
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-oversize'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('A transaction with negative output value')
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vout[0].nValue *= -1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-vout-negative'
            }],
            rawtxs=[ToHex(tx)],
        )

        # The following two validations prevent overflow of the output amounts
        # (see CVE-2010-5139).
        self.log.info('A transaction with too large output value')
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vout[0].nValue = MAX_MONEY + 1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-vout-toolarge'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('A transaction with too large sum of output values')
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vout = [tx.vout[0]] * 2
        tx.vout[0].nValue = MAX_MONEY
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-txouttotal-toolarge'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('A transaction with duplicate inputs')
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vin = [tx.vin[0]] * 2
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-inputs-duplicate'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('A coinbase transaction')
        # Pick the input of the first tx we signed, so it has to be a coinbase
        # tx
        raw_tx_coinbase_spent = node.getrawtransaction(
            txid=node.decoderawtransaction(
                hexstring=raw_tx_in_block)['vin'][0]['txid'])
        tx = FromHex(CTransaction(), raw_tx_coinbase_spent)
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-tx-coinbase'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('Some nonstandard transactions')
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.nVersion = 3  # A version currently non-standard
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'version'
            }],
            rawtxs=[ToHex(tx)],
        )
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vout[0].scriptPubKey = CScript([OP_0])  # Some non-standard script
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'scriptpubkey'
            }],
            rawtxs=[ToHex(tx)],
        )
        tx = FromHex(CTransaction(), raw_tx_reference)
        key = ECKey()
        key.generate()
        pubkey = key.get_pubkey().get_bytes()
        # Some bare multisig script (2-of-3)
        tx.vout[0].scriptPubKey = CScript(
            [OP_2, pubkey, pubkey, pubkey, OP_3, OP_CHECKMULTISIG])
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bare-multisig'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = FromHex(CTransaction(), raw_tx_reference)
        # Some not-pushonly scriptSig
        tx.vin[0].scriptSig = CScript([OP_HASH160])
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'scriptsig-not-pushonly'
            }],
            rawtxs=[ToHex(tx)],
        )
        tx = FromHex(CTransaction(), raw_tx_reference)
        # Some too large scriptSig (>1650 bytes)
        tx.vin[0].scriptSig = CScript([b'a' * 1648])
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'scriptsig-size'
            }],
            rawtxs=[tx.serialize().hex()],
        )
        tx = FromHex(CTransaction(), raw_tx_reference)
        output_p2sh_burn = CTxOut(nValue=540,
                                  scriptPubKey=CScript(
                                      [OP_HASH160,
                                       hash160(b'burn'), OP_EQUAL]))
        # Use enough outputs to make the tx too large for our policy
        num_scripts = 100000 // len(output_p2sh_burn.serialize())
        tx.vout = [output_p2sh_burn] * num_scripts
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'tx-size'
            }],
            rawtxs=[ToHex(tx)],
        )
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vout[0] = output_p2sh_burn
        # Make output smaller, such that it is dust for our policy
        tx.vout[0].nValue -= 1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'dust'
            }],
            rawtxs=[ToHex(tx)],
        )
        tx = FromHex(CTransaction(), raw_tx_reference)
        tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
        tx.vout = [tx.vout[0]] * 2
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'multi-op-return'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('A timelocked transaction')
        tx = FromHex(CTransaction(), raw_tx_reference)
        # Should be non-max, so locktime is not ignored
        tx.vin[0].nSequence -= 1
        tx.nLockTime = node.getblockcount() + 1
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'bad-txns-nonfinal'
            }],
            rawtxs=[ToHex(tx)],
        )

        self.log.info('A transaction that is locked by BIP68 sequence logic')
        tx = FromHex(CTransaction(), raw_tx_reference)
        # We could include it in the second block mined from now, but not the
        # very next one
        tx.vin[0].nSequence = 2
        # Can skip re-signing the tx because of early rejection
        self.check_mempool_result(
            result_expected=[{
                'txid': tx.rehash(),
                'allowed': False,
                'reject-reason': 'non-BIP68-final'
            }],
            rawtxs=[tx.serialize().hex()],
            maxfeerate=0,
        )
Пример #7
0
 )
 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
 tx.vout[0].scriptPubKey = CScript([OP_0])  # Some non-standard script
 self.check_mempool_result(
     result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptpubkey'}],
     rawtxs=[tx.serialize().hex()],
 )
 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
 tx.vin[0].scriptSig = CScript([OP_HASH160])  # Some not-pushonly scriptSig
 self.check_mempool_result(
     result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptsig-not-pushonly'}],
     rawtxs=[tx.serialize().hex()],
 )
 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
 output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL]))
 num_scripts = 100000 // len(output_p2sh_burn.serialize())  # Use enough outputs to make the tx too large for our policy
 tx.vout = [output_p2sh_burn] * num_scripts
 self.check_mempool_result(
     result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: tx-size'}],
     rawtxs=[tx.serialize().hex()],
 )
 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
 tx.vout[0] = output_p2sh_burn
 tx.vout[0].nValue -= 1  # Make output smaller, such that it is dust for our policy
 self.check_mempool_result(
     result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: dust'}],
     rawtxs=[tx.serialize().hex()],
 )
 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference)))
 tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
 tx.vout = [tx.vout[0]] * 2