예제 #1
0
    def send_self_transfer(self,
                           *,
                           fee_rate=Decimal("0.003"),
                           from_node,
                           utxo_to_spend=None):
        """Create and send a tx with the specified fee_rate. Fee may be exact or at most one electron higher than needed."""
        self._utxos = sorted(self._utxos, key=lambda k: k['value'])
        utxo_to_spend = utxo_to_spend or self._utxos.pop(
        )  # Pick the largest utxo (if none provided) and hope it covers the fee
        vsize = Decimal(96)
        send_value = electron_round(utxo_to_spend['value'] - fee_rate *
                                    (vsize / 1000))
        fee = utxo_to_spend['value'] - send_value
        assert send_value > 0

        tx = CTransaction()
        tx.vin = [
            CTxIn(
                COutPoint(int(utxo_to_spend['txid'], 16),
                          utxo_to_spend['vout']))
        ]
        tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)]
        tx.wit.vtxinwit = [CTxInWitness()]
        tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
        tx_hex = tx.serialize().hex()

        txid = from_node.sendrawtransaction(tx_hex)
        self._utxos.append({'txid': txid, 'vout': 0, 'value': send_value})
        tx_info = from_node.getmempoolentry(txid)
        assert_equal(tx_info['vsize'], vsize)
        assert_equal(tx_info['fee'], fee)
        return {'txid': txid, 'wtxid': tx_info['wtxid'], 'hex': tx_hex}
예제 #2
0
def split_inputs(from_node, txins, txouts, initial_split=False):
    """Generate a lot of inputs so we can generate a ton of transactions.

    This function takes an input from txins, and creates and sends a transaction
    which splits the value into 2 outputs which are appended to txouts.
    Previously this was designed to be small inputs so they wouldn't have
    a high coin age when the notion of priority still existed."""

    prevtxout = txins.pop()
    tx = CTransaction()
    tx.vin.append(
        CTxIn(COutPoint(int(prevtxout["txid"], 16), prevtxout["vout"]), b""))

    half_change = electron_round(prevtxout["amount"] / 2)
    rem_change = prevtxout["amount"] - half_change - Decimal("0.00001000")
    tx.vout.append(CTxOut(int(half_change * COIN), P2SH_1))
    tx.vout.append(CTxOut(int(rem_change * COIN), P2SH_2))

    # If this is the initial split we actually need to sign the transaction
    # Otherwise we just need to insert the proper ScriptSig
    if (initial_split):
        completetx = from_node.signrawtransactionwithwallet(ToHex(tx))["hex"]
    else:
        tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]]
        completetx = ToHex(tx)
    txid = from_node.sendrawtransaction(hexstring=completetx, maxfeerate=0)
    txouts.append({"txid": txid, "vout": 0, "amount": half_change})
    txouts.append({"txid": txid, "vout": 1, "amount": rem_change})
예제 #3
0
def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
    """Create a txout with a given amount and scriptPubKey

    Mines coins as needed.

    confirmed - txouts created will be confirmed in the blockchain;
                unconfirmed otherwise.
    """
    fee = 1*COIN
    while node.getbalance() < electron_round((amount + fee)/COIN):
        node.generate(100)

    new_addr = node.getnewaddress()
    txid = node.sendtoaddress(new_addr, electron_round((amount+fee)/COIN))
    tx1 = node.getrawtransaction(txid, 1)
    txid = int(txid, 16)
    i = None

    for i, txout in enumerate(tx1['vout']):
        if txout['scriptPubKey']['addresses'] == [new_addr]:
            break
    assert i is not None

    tx2 = CTransaction()
    tx2.vin = [CTxIn(COutPoint(txid, i))]
    tx2.vout = [CTxOut(amount, scriptPubKey)]
    tx2.rehash()

    signed_tx = node.signrawtransactionwithwallet(txToHex(tx2))

    txid = node.sendrawtransaction(signed_tx['hex'], 0)

    # If requested, ensure txouts are confirmed.
    if confirmed:
        mempool_size = len(node.getrawmempool())
        while mempool_size > 0:
            node.generate(1)
            new_size = len(node.getrawmempool())
            # Error out if we have something stuck in the mempool, as this
            # would likely be a bug.
            assert new_size < mempool_size
            mempool_size = new_size

    return COutPoint(int(txid, 16), 0)
예제 #4
0
 def chain_transaction(self, node, parent_txid, vout, value, fee,
                       num_outputs):
     send_value = electron_round((value - fee) / num_outputs)
     inputs = [{'txid': parent_txid, 'vout': vout}]
     outputs = {}
     for _ in range(num_outputs):
         outputs[node.getnewaddress()] = send_value
     rawtx = node.createrawtransaction(inputs, outputs)
     signedtx = node.signrawtransactionwithwallet(rawtx)
     txid = node.sendrawtransaction(signedtx['hex'])
     fulltx = node.getrawtransaction(txid, 1)
     assert len(
         fulltx['vout']
     ) == num_outputs  # make sure we didn't generate a change output
     return (txid, send_value)
예제 #5
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 ECC

        utxos = self.nodes[0].listunspent(0, 0)
        assert len(utxos) > 0

        utxo = utxos[0]

        tx1 = CTransaction()
        value = int(electron_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"]),
                  nSequence=sequence_value)
        ]
        tx1.vout = [CTxOut(value, DUMMY_P2WPKH_SCRIPT)]

        tx1_signed = self.nodes[0].signrawtransactionwithwallet(
            ToHex(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), nSequence=sequence_value)]
        tx2.vout = [
            CTxOut(int(value - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)
        ]
        tx2.rehash()

        assert_raises_rpc_error(-26, NOT_FINAL_ERROR,
                                self.nodes[0].sendrawtransaction, ToHex(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(ToHex(tx2))
예제 #6
0
def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee,
                           fee_increment):
    """Create and send a transaction with a random fee.

    The transaction pays to a trivial P2SH script, and assumes that its inputs
    are of the same form.
    The function takes a list of confirmed outputs and unconfirmed outputs
    and attempts to use the confirmed list first for its inputs.
    It adds the newly created outputs to the unconfirmed list.
    Returns (raw transaction, fee)."""

    # It's best to exponentially distribute our random fees
    # because the buckets are exponentially spaced.
    # Exponentially distributed from 1-128 * fee_increment
    rand_fee = float(fee_increment) * (1.1892**random.randint(0, 28))
    # Total fee ranges from min_fee to min_fee + 127*fee_increment
    fee = min_fee - fee_increment + electron_round(rand_fee)
    tx = CTransaction()
    total_in = Decimal("0.00000000")
    while total_in <= (amount + fee) and len(conflist) > 0:
        t = conflist.pop(0)
        total_in += t["amount"]
        tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b""))
    if total_in <= amount + fee:
        while total_in <= (amount + fee) and len(unconflist) > 0:
            t = unconflist.pop(0)
            total_in += t["amount"]
            tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b""))
        if total_in <= amount + fee:
            raise RuntimeError("Insufficient funds: need %d, have %d" %
                               (amount + fee, total_in))
    tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH_1))
    tx.vout.append(CTxOut(int(amount * COIN), P2SH_2))
    # These transactions don't need to be signed, but we still have to insert
    # the ScriptSig that will satisfy the ScriptPubKey.
    for inp in tx.vin:
        inp.scriptSig = SCRIPT_SIG[inp.prevout.n]
    txid = from_node.sendrawtransaction(hexstring=ToHex(tx), maxfeerate=0)
    unconflist.append({
        "txid": txid,
        "vout": 0,
        "amount": total_in - amount - fee
    })
    unconflist.append({"txid": txid, "vout": 1, "amount": amount})

    return (ToHex(tx), fee)
예제 #7
0
    def run_test(self):
        # Mine some blocks and have them mature.
        peer_inv_store = self.nodes[0].add_p2p_connection(
            P2PTxInvStore())  # keep track of invs
        self.nodes[0].generate(101)
        utxo = self.nodes[0].listunspent(10)
        txid = utxo[0]['txid']
        vout = utxo[0]['vout']
        value = utxo[0]['amount']

        fee = Decimal("0.0001")
        # MAX_ANCESTORS transactions off a confirmed tx should be fine
        chain = []
        witness_chain = []
        for _ in range(MAX_ANCESTORS):
            (txid,
             sent_value) = self.chain_transaction(self.nodes[0], txid, 0,
                                                  value, fee, 1)
            value = sent_value
            chain.append(txid)
            # We need the wtxids to check P2P announcements
            fulltx = self.nodes[0].getrawtransaction(txid)
            witnesstx = self.nodes[0].decoderawtransaction(fulltx, True)
            witness_chain.append(witnesstx['hash'])

        # Wait until mempool transactions have passed initial broadcast (sent inv and received getdata)
        # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between
        peer_inv_store.wait_for_broadcast(witness_chain)

        # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor
        # count and fees should look correct
        mempool = self.nodes[0].getrawmempool(True)
        assert_equal(len(mempool), MAX_ANCESTORS)
        descendant_count = 1
        descendant_fees = 0
        descendant_vsize = 0

        ancestor_vsize = sum([mempool[tx]['vsize'] for tx in mempool])
        ancestor_count = MAX_ANCESTORS
        ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool])

        descendants = []
        ancestors = list(chain)
        for x in reversed(chain):
            # Check that getmempoolentry is consistent with getrawmempool
            entry = self.nodes[0].getmempoolentry(x)
            assert_equal(entry, mempool[x])

            # Check that the descendant calculations are correct
            assert_equal(mempool[x]['descendantcount'], descendant_count)
            descendant_fees += mempool[x]['fee']
            assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
            assert_equal(mempool[x]['fees']['base'], mempool[x]['fee'])
            assert_equal(mempool[x]['fees']['modified'],
                         mempool[x]['modifiedfee'])
            assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN)
            assert_equal(mempool[x]['fees']['descendant'], descendant_fees)
            descendant_vsize += mempool[x]['vsize']
            assert_equal(mempool[x]['descendantsize'], descendant_vsize)
            descendant_count += 1

            # Check that ancestor calculations are correct
            assert_equal(mempool[x]['ancestorcount'], ancestor_count)
            assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN)
            assert_equal(mempool[x]['ancestorsize'], ancestor_vsize)
            ancestor_vsize -= mempool[x]['vsize']
            ancestor_fees -= mempool[x]['fee']
            ancestor_count -= 1

            # Check that parent/child list is correct
            assert_equal(mempool[x]['spentby'], descendants[-1:])
            assert_equal(mempool[x]['depends'], ancestors[-2:-1])

            # Check that getmempooldescendants is correct
            assert_equal(sorted(descendants),
                         sorted(self.nodes[0].getmempooldescendants(x)))

            # Check getmempooldescendants verbose output is correct
            for descendant, dinfo in self.nodes[0].getmempooldescendants(
                    x, True).items():
                assert_equal(dinfo['depends'],
                             [chain[chain.index(descendant) - 1]])
                if dinfo['descendantcount'] > 1:
                    assert_equal(dinfo['spentby'],
                                 [chain[chain.index(descendant) + 1]])
                else:
                    assert_equal(dinfo['spentby'], [])
            descendants.append(x)

            # Check that getmempoolancestors is correct
            ancestors.remove(x)
            assert_equal(sorted(ancestors),
                         sorted(self.nodes[0].getmempoolancestors(x)))

            # Check that getmempoolancestors verbose output is correct
            for ancestor, ainfo in self.nodes[0].getmempoolancestors(
                    x, True).items():
                assert_equal(ainfo['spentby'],
                             [chain[chain.index(ancestor) + 1]])
                if ainfo['ancestorcount'] > 1:
                    assert_equal(ainfo['depends'],
                                 [chain[chain.index(ancestor) - 1]])
                else:
                    assert_equal(ainfo['depends'], [])

        # Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true
        v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True)
        assert_equal(len(v_ancestors), len(chain) - 1)
        for x in v_ancestors.keys():
            assert_equal(mempool[x], v_ancestors[x])
        assert chain[-1] not in v_ancestors.keys()

        v_descendants = self.nodes[0].getmempooldescendants(chain[0], True)
        assert_equal(len(v_descendants), len(chain) - 1)
        for x in v_descendants.keys():
            assert_equal(mempool[x], v_descendants[x])
        assert chain[0] not in v_descendants.keys()

        # Check that ancestor modified fees includes fee deltas from
        # prioritisetransaction
        self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000)
        mempool = self.nodes[0].getrawmempool(True)
        ancestor_fees = 0
        for x in chain:
            ancestor_fees += mempool[x]['fee']
            assert_equal(mempool[x]['fees']['ancestor'],
                         ancestor_fees + Decimal('0.00001'))
            assert_equal(mempool[x]['ancestorfees'],
                         ancestor_fees * COIN + 1000)

        # Undo the prioritisetransaction for later tests
        self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000)

        # Check that descendant modified fees includes fee deltas from
        # prioritisetransaction
        self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000)
        mempool = self.nodes[0].getrawmempool(True)

        descendant_fees = 0
        for x in reversed(chain):
            descendant_fees += mempool[x]['fee']
            assert_equal(mempool[x]['fees']['descendant'],
                         descendant_fees + Decimal('0.00001'))
            assert_equal(mempool[x]['descendantfees'],
                         descendant_fees * COIN + 1000)

        # Adding one more transaction on to the chain should fail.
        assert_raises_rpc_error(-26, "too-long-mempool-chain",
                                self.chain_transaction, self.nodes[0], txid,
                                vout, value, fee, 1)

        # Check that prioritising a tx before it's added to the mempool works
        # First clear the mempool by mining a block.
        self.nodes[0].generate(1)
        self.sync_blocks()
        assert_equal(len(self.nodes[0].getrawmempool()), 0)
        # Prioritise a transaction that has been mined, then add it back to the
        # mempool by using invalidateblock.
        self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000)
        self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
        # Keep node1's tip synced with node0
        self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())

        # Now check that the transaction is in the mempool, with the right modified fee
        mempool = self.nodes[0].getrawmempool(True)

        descendant_fees = 0
        for x in reversed(chain):
            descendant_fees += mempool[x]['fee']
            if (x == chain[-1]):
                assert_equal(mempool[x]['modifiedfee'],
                             mempool[x]['fee'] + electron_round(0.00002))
                assert_equal(mempool[x]['fees']['modified'],
                             mempool[x]['fee'] + electron_round(0.00002))
            assert_equal(mempool[x]['descendantfees'],
                         descendant_fees * COIN + 2000)
            assert_equal(mempool[x]['fees']['descendant'],
                         descendant_fees + electron_round(0.00002))

        # Check that node1's mempool is as expected (-> custom ancestor limit)
        mempool0 = self.nodes[0].getrawmempool(False)
        mempool1 = self.nodes[1].getrawmempool(False)
        assert_equal(len(mempool1), MAX_ANCESTORS_CUSTOM)
        assert set(mempool1).issubset(set(mempool0))
        for tx in chain[:MAX_ANCESTORS_CUSTOM]:
            assert tx in mempool1
        # TODO: more detailed check of node1's mempool (fees etc.)
        # check transaction unbroadcast info (should be false if in both mempools)
        mempool = self.nodes[0].getrawmempool(True)
        for tx in mempool:
            assert_equal(mempool[tx]['unbroadcast'], False)

        # TODO: test ancestor size limits

        # Now test descendant chain limits
        txid = utxo[1]['txid']
        value = utxo[1]['amount']
        vout = utxo[1]['vout']

        transaction_package = []
        tx_children = []
        # First create one parent tx with 10 children
        (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout,
                                                    value, fee, 10)
        parent_transaction = txid
        for i in range(10):
            transaction_package.append({
                'txid': txid,
                'vout': i,
                'amount': sent_value
            })

        # Sign and send up to MAX_DESCENDANT transactions chained off the parent tx
        chain = [
        ]  # save sent txs for the purpose of checking node1's mempool later (see below)
        for _ in range(MAX_DESCENDANTS - 1):
            utxo = transaction_package.pop(0)
            (txid,
             sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'],
                                                  utxo['vout'], utxo['amount'],
                                                  fee, 10)
            chain.append(txid)
            if utxo['txid'] is parent_transaction:
                tx_children.append(txid)
            for j in range(10):
                transaction_package.append({
                    'txid': txid,
                    'vout': j,
                    'amount': sent_value
                })

        mempool = self.nodes[0].getrawmempool(True)
        assert_equal(mempool[parent_transaction]['descendantcount'],
                     MAX_DESCENDANTS)
        assert_equal(sorted(mempool[parent_transaction]['spentby']),
                     sorted(tx_children))

        for child in tx_children:
            assert_equal(mempool[child]['depends'], [parent_transaction])

        # Sending one more chained transaction will fail
        utxo = transaction_package.pop(0)
        assert_raises_rpc_error(-26, "too-long-mempool-chain",
                                self.chain_transaction, self.nodes[0],
                                utxo['txid'], utxo['vout'], utxo['amount'],
                                fee, 10)

        # Check that node1's mempool is as expected, containing:
        # - txs from previous ancestor test (-> custom ancestor limit)
        # - parent tx for descendant test
        # - txs chained off parent tx (-> custom descendant limit)
        self.wait_until(lambda: len(self.nodes[1].getrawmempool(False)) ==
                        MAX_ANCESTORS_CUSTOM + 1 + MAX_DESCENDANTS_CUSTOM,
                        timeout=10)
        mempool0 = self.nodes[0].getrawmempool(False)
        mempool1 = self.nodes[1].getrawmempool(False)
        assert set(mempool1).issubset(set(mempool0))
        assert parent_transaction in mempool1
        for tx in chain[:MAX_DESCENDANTS_CUSTOM]:
            assert tx in mempool1
        for tx in chain[MAX_DESCENDANTS_CUSTOM:]:
            assert tx not in mempool1
        # TODO: more detailed check of node1's mempool (fees etc.)

        # TODO: test descendant size limits

        # Test reorg handling
        # First, the basics:
        self.nodes[0].generate(1)
        self.sync_blocks()
        self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash())
        self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash())

        # Now test the case where node1 has a transaction T in its mempool that
        # depends on transactions A and B which are in a mined block, and the
        # block containing A and B is disconnected, AND B is not accepted back
        # into node1's mempool because its ancestor count is too high.

        # Create 8 transactions, like so:
        # Tx0 -> Tx1 (vout0)
        #   \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7
        #
        # Mine them in the next block, then generate a new tx8 that spends
        # Tx1 and Tx7, and add to node1's mempool, then disconnect the
        # last block.

        # Create tx0 with 2 outputs
        utxo = self.nodes[0].listunspent()
        txid = utxo[0]['txid']
        value = utxo[0]['amount']
        vout = utxo[0]['vout']

        send_value = electron_round((value - fee) / 2)
        inputs = [{'txid': txid, 'vout': vout}]
        outputs = {}
        for _ in range(2):
            outputs[self.nodes[0].getnewaddress()] = send_value
        rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
        signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx)
        txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
        tx0_id = txid
        value = send_value

        # Create tx1
        tx1_id, _ = self.chain_transaction(self.nodes[0], tx0_id, 0, value,
                                           fee, 1)

        # Create tx2-7
        vout = 1
        txid = tx0_id
        for _ in range(6):
            (txid,
             sent_value) = self.chain_transaction(self.nodes[0], txid, vout,
                                                  value, fee, 1)
            vout = 0
            value = sent_value

        # Mine these in a block
        self.nodes[0].generate(1)
        self.sync_all()

        # Now generate tx8, with a big fee
        inputs = [{'txid': tx1_id, 'vout': 0}, {'txid': txid, 'vout': 0}]
        outputs = {self.nodes[0].getnewaddress(): send_value + value - 4 * fee}
        rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
        signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx)
        txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
        self.sync_mempools()

        # Now try to disconnect the tip on each node...
        self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
        self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
        self.sync_blocks()