Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2422,6 +2422,7 @@ static RPCHelpMan scantxoutset()
{RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"},
{RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"},
{RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"},
{RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"},
{RPCResult::Type::NUM, "height", "Height of the unspent transaction output"},
}},
}},
Expand Down Expand Up @@ -2525,6 +2526,7 @@ static RPCHelpMan scantxoutset()
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", ValueFromAmount(txo.nValue));
unspent.pushKV("coinbase", coin.IsCoinBase());
unspent.pushKV("height", (int32_t)coin.nHeight);

unspents.push_back(unspent);
Expand Down
15 changes: 10 additions & 5 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,14 +727,19 @@ static RPCHelpMan getblocktemplate()
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");

const CConnman& connman = EnsureConnman(node);
CHECK_NONFATAL(node.sporkman);
if (connman.GetNodeCount(ConnectionDirection::Both) == 0)
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!");

if (active_chainstate.IsInitialBlockDownload())
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
if (!Params().IsTestChain()) {
if (connman.GetNodeCount(ConnectionDirection::Both) == 0) {
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!");
}

if (active_chainstate.IsInitialBlockDownload()) {
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
}
}

// next bock is a superblock and we need governance info to correctly construct it
CHECK_NONFATAL(node.sporkman);
if (AreSuperblocksEnabled(*node.sporkman)
&& !node.mn_sync->IsSynced()
&& CSuperblock::IsValidBlockHeight(active_chain.Height() + 1))
Expand Down
104 changes: 44 additions & 60 deletions test/functional/feature_bip68_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
softfork_active,
)
from test_framework.script_util import DUMMY_P2SH_SCRIPT
from test_framework.wallet import MiniWallet

SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31)
SEQUENCE_LOCKTIME_TYPE_FLAG = (1<<22) # this means use time (0 means height)
Expand All @@ -47,14 +48,12 @@ def set_test_params(self):
],
]

def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def run_test(self):
self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"]
self.wallet = MiniWallet(self.nodes[0])

# Generate some coins
self.generate(self.nodes[0], 110)
self.generate(self.wallet, 110)

self.log.info("Running test disable flag")
self.test_disable_flag()
Expand All @@ -81,16 +80,10 @@ def run_test(self):
# the first sequence bit is set.
def test_disable_flag(self):
# Create some unconfirmed inputs
new_addr = self.nodes[0].getnewaddress()
self.nodes[0].sendtoaddress(new_addr, 2) # send 2 BTC

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

utxo = utxos[0]
utxo = self.wallet.send_self_transfer(from_node=self.nodes[0])["new_utxo"]

tx1 = CTransaction()
value = int((utxo["amount"] - self.relayfee) * COIN)
value = int((utxo["value"] - self.relayfee) * COIN)

# Check that the disable flag disables relative locktime.
# If sequence locks were used, this would require 1 block for the
Expand All @@ -99,8 +92,8 @@ def test_disable_flag(self):
tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)]
tx1.vout = [CTxOut(value, DUMMY_P2SH_SCRIPT)]

tx1_signed = self.nodes[0].signrawtransactionwithwallet(tx1.serialize().hex())["hex"]
tx1_id = self.nodes[0].sendrawtransaction(tx1_signed)
self.wallet.sign_tx(tx=tx1)
tx1_id = self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx1.serialize().hex())
tx1_id = int(tx1_id, 16)

# This transaction will enable sequence-locks, so this transaction should
Expand All @@ -112,13 +105,13 @@ def test_disable_flag(self):
tx2.vout = [CTxOut(int(value - self.relayfee * COIN), DUMMY_P2SH_SCRIPT)]
tx2.rehash()

assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx2.serialize().hex())
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx2.serialize().hex())

# Setting the version back down to 1 should disable the sequence lock,
# so this should be accepted.
tx2.nVersion = 1

self.nodes[0].sendrawtransaction(tx2.serialize().hex())
self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2.serialize().hex())

# Calculate the median time past of a prior block ("confirmations" before
# the current tip).
Expand All @@ -131,20 +124,13 @@ def test_sequence_lock_confirmed_inputs(self):
# Create lots of confirmed utxos, and use them to generate lots of random
# transactions.
max_outputs = 50
addresses = []
while len(addresses) < max_outputs:
addresses.append(self.nodes[0].getnewaddress())
while len(self.nodes[0].listunspent()) < 200:
while len(self.wallet.get_utxos(include_immature_coinbase=False, mark_as_spent=False)) < 200:
import random
random.shuffle(addresses)
num_outputs = random.randint(1, max_outputs)
outputs = {}
for i in range(num_outputs):
outputs[addresses[i]] = random.randint(1, 20)*0.01
self.nodes[0].sendmany("", outputs)
self.generate(self.nodes[0], 1)
self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=num_outputs)
self.generate(self.wallet, 1)

utxos = self.nodes[0].listunspent()
utxos = self.wallet.get_utxos(include_immature_coinbase=False)

# Try creating a lot of random transactions.
# Each time, choose a random number of inputs, and randomly set
Expand Down Expand Up @@ -201,19 +187,20 @@ def test_sequence_lock_confirmed_inputs(self):
sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)+1
sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG
tx.vin.append(CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), nSequence=sequence_value))
value += utxos[j]["amount"]*COIN
value += utxos[j]["value"]*COIN
# Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output
tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50
tx.vout.append(CTxOut(int(value-self.relayfee*tx_size*COIN/1000), DUMMY_P2SH_SCRIPT))
rawtx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())["hex"]
self.wallet.sign_tx(tx=tx)

if (using_sequence_locks and not should_pass):
# This transaction should be rejected
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, rawtx)
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx.serialize().hex())
else:
# This raw transaction should be accepted
self.nodes[0].sendrawtransaction(rawtx)
utxos = self.nodes[0].listunspent()
self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx.serialize().hex())
self.wallet.rescan_utxos()
utxos = self.wallet.get_utxos(include_immature_coinbase=False)

# Test that sequence locks on unconfirmed inputs must have nSequence
# height or time of 0 to be accepted.
Expand All @@ -224,8 +211,8 @@ def test_sequence_lock_unconfirmed_inputs(self):
cur_height = self.nodes[0].getblockcount()

# Create a mempool tx.
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2)
tx1 = tx_from_hex(self.nodes[0].getrawtransaction(txid))
self.wallet.rescan_utxos()
tx1 = self.wallet.send_self_transfer(from_node=self.nodes[0])["tx"]
tx1.rehash()

# Anyone-can-spend mempool tx.
Expand All @@ -234,11 +221,11 @@ def test_sequence_lock_unconfirmed_inputs(self):
tx2.nVersion = 2
tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2SH_SCRIPT)]
tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"]
tx2 = tx_from_hex(tx2_raw)
self.wallet.sign_tx(tx=tx2)
tx2_raw = tx2.serialize().hex()
tx2.rehash()

self.nodes[0].sendrawtransaction(tx2_raw)
self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2_raw)

# Create a spend of the 0th output of orig_tx with a sequence lock
# of 1, and test what happens when submitting.
Expand All @@ -256,10 +243,10 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):

if (orig_tx.hash in node.getrawmempool()):
# sendrawtransaction should fail if the tx is in the mempool
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, tx.serialize().hex())
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=node, tx_hex=tx.serialize().hex())
else:
# sendrawtransaction should succeed if the tx is not in the mempool
node.sendrawtransaction(tx.serialize().hex())
self.wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex())

return tx

Expand All @@ -272,7 +259,7 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
cur_time = self.mocktime
for _ in range(10):
self.nodes[0].setmocktime(cur_time + 600)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
self.generate(self.wallet, 1, sync_fun=self.no_op)
cur_time += 600

assert tx2.hash in self.nodes[0].getrawmempool()
Expand Down Expand Up @@ -306,12 +293,12 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True)
assert tx5.hash not in self.nodes[0].getrawmempool()

utxos = self.nodes[0].listunspent()
tx5.vin.append(CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1))
tx5.vout[0].nValue += int(utxos[0]["amount"]*COIN)
raw_tx5 = self.nodes[0].signrawtransactionwithwallet(tx5.serialize().hex())["hex"]
utxo = self.wallet.get_utxo()
tx5.vin.append(CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=1))
tx5.vout[0].nValue += int(utxo["value"]*COIN)
self.wallet.sign_tx(tx=tx5)

assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5)
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx5.serialize().hex())

# Test mempool-BIP68 consistency after reorg
#
Expand Down Expand Up @@ -347,17 +334,16 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
# Reset the chain and get rid of the mocktimed-blocks
self.nodes[0].setmocktime(self.mocktime)
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height+1))
self.generate(self.nodes[0], 10, sync_fun=self.no_op)
self.generate(self.wallet, 10, sync_fun=self.no_op)

# Make sure that BIP68 isn't being used to validate blocks prior to
# activation height. If more blocks are mined prior to this test
# being run, then it's possible the test has activated the soft fork, and
# this test should be moved to run earlier, or deleted.
def test_bip68_not_consensus(self):
assert not softfork_active(self.nodes[0], 'csv')
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2)

tx1 = tx_from_hex(self.nodes[0].getrawtransaction(txid))
tx1 = self.wallet.send_self_transfer(from_node=self.nodes[0])["tx"]
tx1.rehash()

# Make an anyone-can-spend transaction
Expand All @@ -367,11 +353,12 @@ def test_bip68_not_consensus(self):
tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2SH_SCRIPT)]

# sign tx2
tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"]
self.wallet.sign_tx(tx=tx2)
tx2_raw = tx2.serialize().hex()
tx2 = tx_from_hex(tx2_raw)
tx2.rehash()

self.nodes[0].sendrawtransaction(tx2.serialize().hex())
self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2_raw)

# Now make an invalid spend of tx2 according to BIP68
sequence_value = 100 # 100 block relative locktime
Expand All @@ -382,7 +369,7 @@ def test_bip68_not_consensus(self):
tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), DUMMY_P2SH_SCRIPT)]
tx3.rehash()

assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx3.serialize().hex())
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx3.serialize().hex())

# make a block that violates bip68; ensure that the tip updates
block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS))
Expand All @@ -399,22 +386,19 @@ def activateCSV(self):
min_activation_height = 432
height = self.nodes[0].getblockcount()
assert_greater_than(min_activation_height - height, 2)
self.generate(self.nodes[0], min_activation_height - height - 2, sync_fun=self.no_op)
self.generate(self.wallet, min_activation_height - height - 2, sync_fun=self.no_op)
assert not softfork_active(self.nodes[0], 'csv')
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
self.generate(self.wallet, 1, sync_fun=self.no_op)
assert softfork_active(self.nodes[0], 'csv')
self.sync_blocks()

# Use self.nodes[1] to test that version 2 transactions are standard.
def test_version2_relay(self):
inputs = [ ]
outputs = { self.nodes[1].getnewaddress() : 1.0 }
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
rawtxfund = self.nodes[1].fundrawtransaction(rawtx)['hex']
tx = tx_from_hex(rawtxfund)
mini_wallet = MiniWallet(self.nodes[1])
mini_wallet.rescan_utxos()
tx = mini_wallet.create_self_transfer()["tx"]
tx.nVersion = 2
tx_signed = self.nodes[1].signrawtransactionwithwallet(tx.serialize().hex())["hex"]
self.nodes[1].sendrawtransaction(tx_signed)
mini_wallet.sendrawtransaction(from_node=self.nodes[1], tx_hex=tx.serialize().hex())

if __name__ == '__main__':
BIP68Test().main()
Loading
Loading