aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcontrib/devtools/security-check.py17
-rwxr-xr-xcontrib/devtools/test-security-check.py49
-rw-r--r--depends/packages/boost.mk3
-rw-r--r--doc/REST-interface.md1
-rw-r--r--src/core_write.cpp2
-rw-r--r--src/interfaces/chain.h3
-rw-r--r--src/interfaces/wallet.h1
-rw-r--r--src/net_processing.cpp36
-rw-r--r--src/net_processing.h9
-rw-r--r--src/node/interfaces.cpp5
-rw-r--r--src/qt/guiconstants.h2
-rw-r--r--src/qt/transactiondesc.cpp9
-rw-r--r--src/qt/transactionrecord.cpp15
-rw-r--r--src/qt/transactionrecord.h2
-rw-r--r--src/qt/transactiontablemodel.cpp9
-rw-r--r--src/rpc/blockchain.cpp40
-rw-r--r--src/rpc/client.cpp2
-rw-r--r--src/rpc/rawtransaction.cpp4
-rw-r--r--src/rpc/util.cpp16
-rw-r--r--src/rpc/util.h9
-rw-r--r--src/wallet/coincontrol.h19
-rw-r--r--src/wallet/interfaces.cpp1
-rw-r--r--src/wallet/receive.cpp2
-rw-r--r--src/wallet/rpc/coins.cpp4
-rw-r--r--src/wallet/rpc/spend.cpp79
-rw-r--r--src/wallet/rpc/transactions.cpp4
-rw-r--r--src/wallet/spend.cpp14
-rw-r--r--src/wallet/test/spend_tests.cpp51
-rw-r--r--src/wallet/wallet.cpp51
-rw-r--r--src/wallet/wallet.h2
-rw-r--r--test/functional/data/rpc_decodescript.json11
-rwxr-xr-xtest/functional/feature_maxtipage.py56
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py53
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py16
-rwxr-xr-xtest/functional/rpc_psbt.py89
-rwxr-xr-xtest/functional/rpc_rawtransaction.py39
-rwxr-xr-xtest/functional/test_runner.py2
-rwxr-xr-xtest/functional/wallet_send.py40
-rwxr-xr-xtest/functional/wallet_timelock.py50
-rw-r--r--test/sanitizer_suppressions/ubsan4
-rw-r--r--test/util/data/tt-delin1-out.json2
-rw-r--r--test/util/data/tt-delout1-out.json1
-rw-r--r--test/util/data/tt-locktime317000-out.json2
-rw-r--r--test/util/data/txcreate1.json2
-rw-r--r--test/util/data/txcreate2.json1
-rw-r--r--test/util/data/txcreatedata1.json2
-rw-r--r--test/util/data/txcreatedata2.json2
-rw-r--r--test/util/data/txcreatedata_seq0.json1
-rw-r--r--test/util/data/txcreatedata_seq1.json1
-rw-r--r--test/util/data/txcreatemultisig1.json1
-rw-r--r--test/util/data/txcreatemultisig2.json1
-rw-r--r--test/util/data/txcreatemultisig3.json1
-rw-r--r--test/util/data/txcreatemultisig4.json1
-rw-r--r--test/util/data/txcreatemultisig5.json1
-rw-r--r--test/util/data/txcreateoutpubkey1.json1
-rw-r--r--test/util/data/txcreateoutpubkey2.json1
-rw-r--r--test/util/data/txcreateoutpubkey3.json1
-rw-r--r--test/util/data/txcreatescript1.json1
-rw-r--r--test/util/data/txcreatescript2.json1
-rw-r--r--test/util/data/txcreatescript3.json1
-rw-r--r--test/util/data/txcreatescript4.json1
-rw-r--r--test/util/data/txcreatesignv1.json1
62 files changed, 673 insertions, 175 deletions
diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py
index 137fe377da..9e7059685c 100755
--- a/contrib/devtools/security-check.py
+++ b/contrib/devtools/security-check.py
@@ -111,6 +111,17 @@ def check_ELF_separate_code(binary):
return False
return True
+def check_ELF_control_flow(binary) -> bool:
+ '''
+ Check for control flow instrumentation
+ '''
+ main = binary.get_function_address('main')
+ content = binary.get_content_from_virtual_address(main, 4, lief.Binary.VA_TYPES.AUTO)
+
+ if content == [243, 15, 30, 250]: # endbr64
+ return True
+ return False
+
def check_PE_DYNAMIC_BASE(binary) -> bool:
'''PIE: DllCharacteristics bit 0x40 signifies dynamicbase (ASLR)'''
return lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE in binary.optional_header.dll_characteristics_lists
@@ -172,7 +183,7 @@ def check_NX(binary) -> bool:
'''
return binary.has_nx
-def check_control_flow(binary) -> bool:
+def check_MACHO_control_flow(binary) -> bool:
'''
Check for control flow instrumentation
'''
@@ -205,12 +216,12 @@ BASE_MACHO = [
('NX', check_NX),
('LAZY_BINDINGS', check_MACHO_LAZY_BINDINGS),
('Canary', check_MACHO_Canary),
- ('CONTROL_FLOW', check_control_flow),
+ ('CONTROL_FLOW', check_MACHO_control_flow),
]
CHECKS = {
lief.EXE_FORMATS.ELF: {
- lief.ARCHITECTURES.X86: BASE_ELF,
+ lief.ARCHITECTURES.X86: BASE_ELF + [('CONTROL_FLOW', check_ELF_control_flow)],
lief.ARCHITECTURES.ARM: BASE_ELF,
lief.ARCHITECTURES.ARM64: BASE_ELF,
lief.ARCHITECTURES.PPC: BASE_ELF,
diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py
index 6b748e8743..a5b9eac302 100755
--- a/contrib/devtools/test-security-check.py
+++ b/contrib/devtools/test-security-check.py
@@ -5,6 +5,7 @@
'''
Test script for security-check.py
'''
+import lief #type:ignore
import os
import subprocess
from typing import List
@@ -41,25 +42,49 @@ def call_security_check(cc, source, executable, options):
p = subprocess.run(['./contrib/devtools/security-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True)
return (p.returncode, p.stdout.rstrip())
+def get_arch(cc, source, executable):
+ subprocess.run([*cc, source, '-o', executable], check=True)
+ binary = lief.parse(executable)
+ arch = binary.abstract.header.architecture
+ os.remove(executable)
+ return arch
+
class TestSecurityChecks(unittest.TestCase):
def test_ELF(self):
source = 'test1.c'
executable = 'test1'
cc = determine_wellknown_cmd('CC', 'gcc')
write_testcode(source)
+ arch = get_arch(cc, source, executable)
- self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
- (1, executable+': failed PIE NX RELRO Canary'))
- self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
- (1, executable+': failed PIE RELRO Canary'))
- self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
- (1, executable+': failed PIE RELRO'))
- self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-pie','-fPIE', '-Wl,-z,separate-code']),
- (1, executable+': failed RELRO'))
- self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,noseparate-code']),
- (1, executable+': failed separate_code'))
- self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code']),
- (0, ''))
+ if arch == lief.ARCHITECTURES.X86:
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed PIE NX RELRO Canary CONTROL_FLOW'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed PIE RELRO Canary CONTROL_FLOW'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed PIE RELRO CONTROL_FLOW'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-pie','-fPIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed RELRO CONTROL_FLOW'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,noseparate-code']),
+ (1, executable+': failed separate_code CONTROL_FLOW'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed CONTROL_FLOW'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code', '-fcf-protection=full']),
+ (0, ''))
+ else:
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-zexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed PIE NX RELRO Canary'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fno-stack-protector','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed PIE RELRO Canary'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed PIE RELRO'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-znorelro','-pie','-fPIE', '-Wl,-z,separate-code']),
+ (1, executable+': failed RELRO'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,noseparate-code']),
+ (1, executable+': failed separate_code'))
+ self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code']),
+ (0, ''))
clean_files(source, executable)
diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk
index 5fe2b2bbb8..fe2425ffaf 100644
--- a/depends/packages/boost.mk
+++ b/depends/packages/boost.mk
@@ -26,8 +26,7 @@ $(package)_config_libraries=filesystem,system,test
$(package)_cxxflags+=-std=c++17
$(package)_cxxflags_linux=-fPIC
$(package)_cxxflags_android=-fPIC
-$(package)_cxxflags_x86_64_darwin=-fcf-protection=full
-$(package)_cxxflags_mingw32=-fcf-protection=full
+$(package)_cxxflags_x86_64=-fcf-protection=full
endef
define $(package)_preprocess_cmds
diff --git a/doc/REST-interface.md b/doc/REST-interface.md
index 51a73b89fc..1f0a07a284 100644
--- a/doc/REST-interface.md
+++ b/doc/REST-interface.md
@@ -108,6 +108,7 @@ $ curl localhost:18332/rest/getutxos/checkmempool/b2cdfd7b89def827ff8af7cd9bff76
"value" : 8.8687,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 1c7cebb529b86a04c683dfa87be49de35bcf589e OP_EQUALVERIFY OP_CHECKSIG",
+ "desc" : "addr(mi7as51dvLJsizWnTMurtRmrP8hG2m1XvD)#gj9tznmy"
"hex" : "76a9141c7cebb529b86a04c683dfa87be49de35bcf589e88ac",
"type" : "pubkeyhash",
"address" : "mi7as51dvLJsizWnTMurtRmrP8hG2m1XvD"
diff --git a/src/core_write.cpp b/src/core_write.cpp
index 067f1e4f4e..5ea62cf3ed 100644
--- a/src/core_write.cpp
+++ b/src/core_write.cpp
@@ -8,6 +8,7 @@
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <key_io.h>
+#include <script/descriptor.h>
#include <script/script.h>
#include <script/standard.h>
#include <serialize.h>
@@ -152,6 +153,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include
CTxDestination address;
out.pushKV("asm", ScriptToAsmStr(scriptPubKey));
+ out.pushKV("desc", InferDescriptor(scriptPubKey, DUMMY_SIGNING_PROVIDER)->ToString());
if (include_hex) out.pushKV("hex", HexStr(scriptPubKey));
std::vector<std::vector<unsigned char>> solns;
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index 4f5105a5c1..ddfb4bda95 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -116,9 +116,6 @@ public:
//! or one of its ancestors.
virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0;
- //! Check if transaction will be final given chain height current time.
- virtual bool checkFinalTx(const CTransaction& tx) = 0;
-
//! Return whether node has the block and optionally return block metadata
//! or contents.
virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0;
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index aa33a3c951..f26ac866dc 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -405,7 +405,6 @@ struct WalletTxStatus
int depth_in_main_chain;
unsigned int time_received;
uint32_t lock_time;
- bool is_final;
bool is_trusted;
bool is_abandoned;
bool is_coinbase;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index cc23876af5..bd51b0f785 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -320,7 +320,7 @@ public:
/** Implement PeerManager */
void StartScheduledTasks(CScheduler& scheduler) override;
void CheckForStaleTipAndEvictPeers() override;
- bool FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& index) override;
+ std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override;
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override;
bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; }
void SendPings() override;
@@ -1460,39 +1460,39 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
(GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
}
-bool PeerManagerImpl::FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& index)
+std::optional<std::string> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBlockIndex& block_index)
{
- if (fImporting || fReindex) return false;
+ if (fImporting) return "Importing...";
+ if (fReindex) return "Reindexing...";
LOCK(cs_main);
// Ensure this peer exists and hasn't been disconnected
- CNodeState* state = State(id);
- if (state == nullptr) return false;
+ CNodeState* state = State(peer_id);
+ if (state == nullptr) return "Peer does not exist";
// Ignore pre-segwit peers
- if (!state->fHaveWitness) return false;
+ if (!state->fHaveWitness) return "Pre-SegWit peer";
- // Mark block as in-flight unless it already is
- if (!BlockRequested(id, index)) return false;
+ // Mark block as in-flight unless it already is (for this peer).
+ // If a block was already in-flight for a different peer, its BLOCKTXN
+ // response will be dropped.
+ if (!BlockRequested(peer_id, block_index)) return "Already requested from this peer";
// Construct message to request the block
+ const uint256& hash{block_index.GetBlockHash()};
std::vector<CInv> invs{CInv(MSG_BLOCK | MSG_WITNESS_FLAG, hash)};
// Send block request message to the peer
- bool success = m_connman.ForNode(id, [this, &invs](CNode* node) {
+ bool success = m_connman.ForNode(peer_id, [this, &invs](CNode* node) {
const CNetMsgMaker msgMaker(node->GetCommonVersion());
this->m_connman.PushMessage(node, msgMaker.Make(NetMsgType::GETDATA, invs));
return true;
});
- if (success) {
- LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
- hash.ToString(), id);
- } else {
- RemoveBlockRequest(hash);
- LogPrint(BCLog::NET, "Failed to request block %s from peer=%d\n",
- hash.ToString(), id);
- }
- return success;
+ if (!success) return "Peer not fully connected";
+
+ LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
+ hash.ToString(), peer_id);
+ return std::nullopt;
}
std::unique_ptr<PeerManager> PeerManager::make(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman,
diff --git a/src/net_processing.h b/src/net_processing.h
index 27775cea97..e30f9f516c 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -45,12 +45,11 @@ public:
/**
* Attempt to manually fetch block from a given peer. We must already have the header.
*
- * @param[in] id The peer id
- * @param[in] hash The block hash
- * @param[in] pindex The blockindex
- * @returns Whether a request was successfully made
+ * @param[in] peer_id The peer id
+ * @param[in] block_index The blockindex
+ * @returns std::nullopt if a request was successfully made, otherwise an error message
*/
- virtual bool FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& pindex) = 0;
+ virtual std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) = 0;
/** Begin running background tasks, should only be called once */
virtual void StartScheduledTasks(CScheduler& scheduler) = 0;
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 1a48957f0f..ffad289fa9 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -486,11 +486,6 @@ public:
const CChain& active = Assert(m_node.chainman)->ActiveChain();
return active.GetLocator();
}
- bool checkFinalTx(const CTransaction& tx) override
- {
- LOCK(cs_main);
- return CheckFinalTx(chainman().ActiveChain().Tip(), tx);
- }
std::optional<int> findLocatorFork(const CBlockLocator& locator) override
{
LOCK(cs_main);
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h
index 1adcd5b6b9..fcdf6056c9 100644
--- a/src/qt/guiconstants.h
+++ b/src/qt/guiconstants.h
@@ -33,8 +33,6 @@ static const bool DEFAULT_SPLASHSCREEN = true;
#define COLOR_NEGATIVE QColor(255, 0, 0)
/* Transaction list -- bare address (without label) */
#define COLOR_BAREADDRESS QColor(140, 140, 140)
-/* Transaction list -- TX status decoration - open until date */
-#define COLOR_TX_STATUS_OPENUNTILDATE QColor(64, 64, 255)
/* Transaction list -- TX status decoration - danger, tx needs attention */
#define COLOR_TX_STATUS_DANGER QColor(200, 100, 100)
/* Transaction list -- TX status decoration - default color */
diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp
index 0504639cde..be5851d627 100644
--- a/src/qt/transactiondesc.cpp
+++ b/src/qt/transactiondesc.cpp
@@ -18,7 +18,6 @@
#include <interfaces/wallet.h>
#include <key_io.h>
#include <policy/policy.h>
-#include <script/script.h>
#include <util/system.h>
#include <validation.h>
#include <wallet/ismine.h>
@@ -35,14 +34,6 @@ using wallet::isminetype;
QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks)
{
- if (!status.is_final)
- {
- if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD)
- return tr("Open for %n more block(s)", "", wtx.tx->nLockTime - numBlocks);
- else
- return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.tx->nLockTime));
- }
- else
{
int nDepth = status.depth_in_main_chain;
if (nDepth < 0) {
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp
index 5386569973..26144ba197 100644
--- a/src/qt/transactionrecord.cpp
+++ b/src/qt/transactionrecord.cpp
@@ -179,21 +179,8 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, cons
status.depth = wtx.depth_in_main_chain;
status.m_cur_block_hash = block_hash;
- const bool up_to_date = ((int64_t)QDateTime::currentMSecsSinceEpoch() / 1000 - block_time < MAX_BLOCK_TIME_GAP);
- if (up_to_date && !wtx.is_final) {
- if (wtx.lock_time < LOCKTIME_THRESHOLD) {
- status.status = TransactionStatus::OpenUntilBlock;
- status.open_for = wtx.lock_time - numBlocks;
- }
- else
- {
- status.status = TransactionStatus::OpenUntilDate;
- status.open_for = wtx.lock_time;
- }
- }
// For generated transactions, determine maturity
- else if(type == TransactionRecord::Generated)
- {
+ if (type == TransactionRecord::Generated) {
if (wtx.blocks_to_maturity > 0)
{
status.status = TransactionStatus::Immature;
diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h
index 1c139efabc..dd34656d5f 100644
--- a/src/qt/transactionrecord.h
+++ b/src/qt/transactionrecord.h
@@ -30,8 +30,6 @@ public:
enum Status {
Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/
/// Normal (sent/received) transactions
- OpenUntilDate, /**< Transaction not yet final, waiting for date */
- OpenUntilBlock, /**< Transaction not yet final, waiting for block */
Unconfirmed, /**< Not yet mined into a block **/
Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/
Conflicted, /**< Conflicts with other transaction or mempool **/
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index b42c3f8c24..44b4fee2e7 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -316,12 +316,6 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons
switch(wtx->status.status)
{
- case TransactionStatus::OpenUntilBlock:
- status = tr("Open for %n more block(s)","",wtx->status.open_for);
- break;
- case TransactionStatus::OpenUntilDate:
- status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
- break;
case TransactionStatus::Unconfirmed:
status = tr("Unconfirmed");
break;
@@ -475,9 +469,6 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx)
{
switch(wtx->status.status)
{
- case TransactionStatus::OpenUntilBlock:
- case TransactionStatus::OpenUntilDate:
- return COLOR_TX_STATUS_OPENUNTILDATE;
case TransactionStatus::Unconfirmed:
return QIcon(":/icons/transaction_0");
case TransactionStatus::Abandoned:
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index ccc859619d..128cae6715 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -790,17 +790,15 @@ static RPCHelpMan getblockfrompeer()
{
return RPCHelpMan{
"getblockfrompeer",
- "\nAttempt to fetch block from a given peer.\n"
+ "Attempt to fetch block from a given peer.\n"
"\nWe must have the header for this block, e.g. using submitheader.\n"
- "\nReturns {} if a block-request was successfully scheduled\n",
+ "Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n"
+ "\nReturns an empty JSON object if the request was successfully scheduled.",
{
- {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"},
- {"nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO, "The node ID (see getpeerinfo for node IDs)"},
+ {"block_hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
+ {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
},
- RPCResult{RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "warnings", /*optional=*/true, "any warnings"},
- }},
+ RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
RPCExamples{
HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
@@ -810,31 +808,24 @@ static RPCHelpMan getblockfrompeer()
const NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
PeerManager& peerman = EnsurePeerman(node);
- CConnman& connman = EnsureConnman(node);
-
- uint256 hash(ParseHashV(request.params[0], "hash"));
-
- const NodeId nodeid = static_cast<NodeId>(request.params[1].get_int64());
- // Check that the peer with nodeid exists
- if (!connman.ForNode(nodeid, [](CNode* node) {return true;})) {
- throw JSONRPCError(RPC_MISC_ERROR, strprintf("Peer nodeid %d does not exist", nodeid));
- }
+ const uint256& block_hash{ParseHashV(request.params[0], "block_hash")};
+ const NodeId peer_id{request.params[1].get_int64()};
- const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
+ const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
}
- UniValue result = UniValue::VOBJ;
-
if (index->nStatus & BLOCK_HAVE_DATA) {
- result.pushKV("warnings", "Block already downloaded");
- } else if (!peerman.FetchBlock(nodeid, hash, *index)) {
- throw JSONRPCError(RPC_MISC_ERROR, "Failed to fetch block from peer");
+ throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
}
- return result;
+
+ if (const auto err{peerman.FetchBlock(peer_id, *index)}) {
+ throw JSONRPCError(RPC_MISC_ERROR, err.value());
+ }
+ return UniValue::VOBJ;
},
};
}
@@ -1340,6 +1331,7 @@ static RPCHelpMan gettxout()
{RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "", {
{RPCResult::Type::STR, "asm", ""},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", ""},
{RPCResult::Type::STR, "type", "The type, eg pubkeyhash"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 003ba8bb20..c480a093a4 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -60,7 +60,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getbalance", 1, "minconf" },
{ "getbalance", 2, "include_watchonly" },
{ "getbalance", 3, "avoid_reuse" },
- { "getblockfrompeer", 1, "nodeid" },
+ { "getblockfrompeer", 1, "peer_id" },
{ "getblockhash", 0, "height" },
{ "waitforblockheight", 0, "height" },
{ "waitforblockheight", 1, "timeout" },
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index f227fde0f7..1df1492fe0 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -179,6 +179,7 @@ static RPCHelpMan getrawtransaction()
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
{RPCResult::Type::STR, "asm", "the asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR, "hex", "the hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
@@ -506,6 +507,7 @@ static RPCHelpMan decoderawtransaction()
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
{RPCResult::Type::STR, "asm", "the asm"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", "the hex"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
@@ -561,6 +563,7 @@ static RPCHelpMan decodescript()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "asm", "Script public key"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "type", "The output type (e.g. " + GetAllOutputTypes() + ")"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "p2sh", /*optional=*/true,
@@ -572,6 +575,7 @@ static RPCHelpMan decodescript()
{RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"},
{RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
+ {RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"},
}},
},
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 57e3da0351..5ef7e26ce8 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -831,11 +831,14 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
}
case Type::OBJ_DYN:
case Type::OBJ: {
+ if (m_inner.empty()) {
+ sections.PushSection({indent + maybe_key + "{}", Description("empty JSON object")});
+ return;
+ }
sections.PushSection({indent + maybe_key + "{", Description("json object")});
for (const auto& i : m_inner) {
i.ToSections(sections, OuterType::OBJ, current_indent + 2);
}
- CHECK_NONFATAL(!m_inner.empty());
if (m_type == Type::OBJ_DYN && m_inner.back().m_type != Type::ELISION) {
// If the dictionary keys are dynamic, use three dots for continuation
sections.PushSection({indent_next + "...", ""});
@@ -886,6 +889,17 @@ bool RPCResult::MatchesType(const UniValue& result) const
CHECK_NONFATAL(false);
}
+void RPCResult::CheckInnerDoc() const
+{
+ if (m_type == Type::OBJ) {
+ // May or may not be empty
+ return;
+ }
+ // Everything else must either be empty or not
+ const bool inner_needed{m_type == Type::ARR || m_type == Type::ARR_FIXED || m_type == Type::OBJ_DYN};
+ CHECK_NONFATAL(inner_needed != m_inner.empty());
+}
+
std::string RPCArg::ToStringObj(const bool oneline) const
{
std::string res;
diff --git a/src/rpc/util.h b/src/rpc/util.h
index d43ee33b0f..25ebf78fa1 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -267,8 +267,7 @@ struct RPCResult {
m_cond{std::move(cond)}
{
CHECK_NONFATAL(!m_cond.empty());
- const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN};
- CHECK_NONFATAL(inner_needed != inner.empty());
+ CheckInnerDoc();
}
RPCResult(
@@ -292,8 +291,7 @@ struct RPCResult {
m_description{std::move(description)},
m_cond{}
{
- const bool inner_needed{type == Type::ARR || type == Type::ARR_FIXED || type == Type::OBJ || type == Type::OBJ_DYN};
- CHECK_NONFATAL(inner_needed != inner.empty());
+ CheckInnerDoc();
}
RPCResult(
@@ -311,6 +309,9 @@ struct RPCResult {
std::string ToDescriptionString() const;
/** Check whether the result JSON type matches. */
bool MatchesType(const UniValue& result) const;
+
+private:
+ void CheckInnerDoc() const;
};
struct RPCResults {
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index 5ef2295c88..65a5c83366 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -115,9 +115,28 @@ public:
vOutpoints.assign(setSelected.begin(), setSelected.end());
}
+ void SetInputWeight(const COutPoint& outpoint, int64_t weight)
+ {
+ m_input_weights[outpoint] = weight;
+ }
+
+ bool HasInputWeight(const COutPoint& outpoint) const
+ {
+ return m_input_weights.count(outpoint) > 0;
+ }
+
+ int64_t GetInputWeight(const COutPoint& outpoint) const
+ {
+ auto it = m_input_weights.find(outpoint);
+ assert(it != m_input_weights.end());
+ return it->second;
+ }
+
private:
std::set<COutPoint> setSelected;
std::map<COutPoint, CTxOut> m_external_txouts;
+ //! Map of COutPoints to the maximum weight for that input
+ std::map<COutPoint, int64_t> m_input_weights;
};
} // namespace wallet
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index b1466869b9..9083c304b2 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -90,7 +90,6 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx);
result.time_received = wtx.nTimeReceived;
result.lock_time = wtx.tx->nLockTime;
- result.is_final = wallet.chain().checkFinalTx(*wtx.tx);
result.is_trusted = CachedTxIsTrusted(wallet, wtx);
result.is_abandoned = wtx.isAbandoned();
result.is_coinbase = wtx.IsCoinBase();
diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp
index e598d6f979..1a6f06213c 100644
--- a/src/wallet/receive.cpp
+++ b/src/wallet/receive.cpp
@@ -279,8 +279,6 @@ bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminef
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents)
{
AssertLockHeld(wallet.cs_wallet);
- // Quick answer in most cases
- if (!wallet.chain().checkFinalTx(*wtx.tx)) return false;
int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth >= 1) return true;
if (nDepth < 0) return false;
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index f10de11662..035541babd 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -60,8 +60,8 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
if (depth < min_depth
// Coinbase with less than 1 confirmation is no longer in the main chain
|| (wtx.IsCoinBase() && (depth < 1 || !include_coinbase))
- || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)
- || !wallet.chain().checkFinalTx(*wtx.tx)) {
+ || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
+ {
continue;
}
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index cae3542a5e..433b5a1815 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <consensus/validation.h>
#include <core_io.h>
#include <key_io.h>
#include <policy/policy.h>
@@ -429,6 +430,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
{"replaceable", UniValueType(UniValue::VBOOL)},
{"conf_target", UniValueType(UniValue::VNUM)},
{"estimate_mode", UniValueType(UniValue::VSTR)},
+ {"input_weights", UniValueType(UniValue::VARR)},
},
true, true);
@@ -548,6 +550,37 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
}
}
+ if (options.exists("input_weights")) {
+ for (const UniValue& input : options["input_weights"].get_array().getValues()) {
+ uint256 txid = ParseHashO(input, "txid");
+
+ const UniValue& vout_v = find_value(input, "vout");
+ if (!vout_v.isNum()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key");
+ }
+ int vout = vout_v.get_int();
+ if (vout < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
+ }
+
+ const UniValue& weight_v = find_value(input, "weight");
+ if (!weight_v.isNum()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing weight key");
+ }
+ int64_t weight = weight_v.get_int64();
+ const int64_t min_input_weight = GetTransactionInputWeight(CTxIn());
+ CHECK_NONFATAL(min_input_weight == 165);
+ if (weight < min_input_weight) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, weight cannot be less than 165 (41 bytes (size of outpoint + sequence + empty scriptSig) * 4 (witness scaling factor)) + 1 (empty witness)");
+ }
+ if (weight > MAX_STANDARD_TX_WEIGHT) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, weight cannot be greater than the maximum standard tx weight of %d", MAX_STANDARD_TX_WEIGHT));
+ }
+
+ coinControl.SetInputWeight(COutPoint(txid, vout), weight);
+ }
+ }
+
if (tx.vout.size() == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
@@ -585,6 +618,23 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
}
}
+static void SetOptionsInputWeights(const UniValue& inputs, UniValue& options)
+{
+ if (options.exists("input_weights")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Input weights should be specified in inputs rather than in options.");
+ }
+ if (inputs.size() == 0) {
+ return;
+ }
+ UniValue weights(UniValue::VARR);
+ for (const UniValue& input : inputs.getValues()) {
+ if (input.exists("weight")) {
+ weights.push_back(input);
+ }
+ }
+ options.pushKV("input_weights", weights);
+}
+
RPCHelpMan fundrawtransaction()
{
return RPCHelpMan{"fundrawtransaction",
@@ -626,6 +676,17 @@ RPCHelpMan fundrawtransaction()
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
},
},
+ {"input_weights", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "Inputs and their corresponding weights",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
+ {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output index"},
+ {"weight", RPCArg::Type::NUM, RPCArg::Optional::NO, "The maximum weight for this input, "
+ "including the weight of the outpoint and sequence number. "
+ "Note that serialized signature sizes are not guaranteed to be consistent, "
+ "so the maximum DER signatures size of 73 bytes should be used when considering ECDSA signatures."
+ "Remember to convert serialized sizes to weight units when necessary."},
+ },
+ },
},
FundTxDoc()),
"options"},
@@ -1007,6 +1068,11 @@ RPCHelpMan send()
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
{"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"},
+ {"weight", RPCArg::Type::NUM, RPCArg::DefaultHint{"Calculated from wallet and solving data"}, "The maximum weight for this input, "
+ "including the weight of the outpoint and sequence number. "
+ "Note that signature sizes are not guaranteed to be consistent, "
+ "so the maximum DER signatures size of 73 bytes should be used when considering ECDSA signatures."
+ "Remember to convert serialized sizes to weight units when necessary."},
},
},
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
@@ -1110,6 +1176,7 @@ RPCHelpMan send()
// Automatically select coins, unless at least one is manually selected. Can
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
+ SetOptionsInputWeights(options["inputs"], options);
FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false);
bool add_to_wallet = true;
@@ -1250,6 +1317,11 @@ RPCHelpMan walletcreatefundedpsbt()
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
{"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'locktime' and 'options.replaceable' arguments"}, "The sequence number"},
+ {"weight", RPCArg::Type::NUM, RPCArg::DefaultHint{"Calculated from wallet and solving data"}, "The maximum weight for this input, "
+ "including the weight of the outpoint and sequence number. "
+ "Note that signature sizes are not guaranteed to be consistent, "
+ "so the maximum DER signatures size of 73 bytes should be used when considering ECDSA signatures."
+ "Remember to convert serialized sizes to weight units when necessary."},
},
},
},
@@ -1330,10 +1402,12 @@ RPCHelpMan walletcreatefundedpsbt()
}, true
);
+ UniValue options = request.params[3];
+
CAmount fee;
int change_position;
bool rbf{wallet.m_signal_rbf};
- const UniValue &replaceable_arg = request.params[3]["replaceable"];
+ const UniValue &replaceable_arg = options["replaceable"];
if (!replaceable_arg.isNull()) {
RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL);
rbf = replaceable_arg.isTrue();
@@ -1343,7 +1417,8 @@ RPCHelpMan walletcreatefundedpsbt()
// Automatically select coins, unless at least one is manually selected. Can
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
- FundTransaction(wallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true);
+ SetOptionsInputWeights(request.params[0], options);
+ FundTransaction(wallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ true);
// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index d9034808f4..eef2c13ee1 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -114,8 +114,8 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
// Coinbase with less than 1 confirmation is no longer in the main chain
if ((wtx.IsCoinBase() && (nDepth < 1 || !include_coinbase))
- || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)
- || !wallet.chain().checkFinalTx(*wtx.tx)) {
+ || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
+ {
continue;
}
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index d87bdc8679..a42df8262c 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -105,10 +105,6 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
const uint256& wtxid = entry.first;
const CWalletTx& wtx = entry.second;
- if (!wallet.chain().checkFinalTx(*wtx.tx)) {
- continue;
- }
-
if (wallet.IsTxImmatureCoinBase(wtx))
continue;
@@ -455,15 +451,17 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
}
input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false);
txout = wtx.tx->vout.at(outpoint.n);
- }
- if (input_bytes == -1) {
- // The input is external. We either did not find the tx in mapWallet, or we did but couldn't compute the input size with wallet data
+ } else {
+ // The input is external. We did not find the tx in mapWallet.
if (!coin_control.GetExternalOutput(outpoint, txout)) {
- // Not ours, and we don't have solving data.
return std::nullopt;
}
input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
}
+ // If available, override calculated size with coin control specified size
+ if (coin_control.HasInputWeight(outpoint)) {
+ input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
+ }
CInputCoin coin(outpoint, txout, input_bytes);
if (coin.m_input_bytes == -1) {
diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp
index b2a0697c21..334bd5b8bc 100644
--- a/src/wallet/test/spend_tests.cpp
+++ b/src/wallet/test/spend_tests.cpp
@@ -63,5 +63,56 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
BOOST_CHECK_EQUAL(fee, check_tx(fee + 123));
}
+static void TestFillInputToWeight(int64_t additional_weight, std::vector<int64_t> expected_stack_sizes)
+{
+ static const int64_t EMPTY_INPUT_WEIGHT = GetTransactionInputWeight(CTxIn());
+
+ CTxIn input;
+ int64_t target_weight = EMPTY_INPUT_WEIGHT + additional_weight;
+ BOOST_CHECK(FillInputToWeight(input, target_weight));
+ BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), target_weight);
+ BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), expected_stack_sizes.size());
+ for (unsigned int i = 0; i < expected_stack_sizes.size(); ++i) {
+ BOOST_CHECK_EQUAL(input.scriptWitness.stack[i].size(), expected_stack_sizes[i]);
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(FillInputToWeightTest, BasicTestingSetup)
+{
+ {
+ // Less than or equal minimum of 165 should not add any witness data
+ CTxIn input;
+ BOOST_CHECK(!FillInputToWeight(input, -1));
+ BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
+ BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
+ BOOST_CHECK(!FillInputToWeight(input, 0));
+ BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
+ BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
+ BOOST_CHECK(!FillInputToWeight(input, 164));
+ BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
+ BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
+ BOOST_CHECK(FillInputToWeight(input, 165));
+ BOOST_CHECK_EQUAL(GetTransactionInputWeight(input), 165);
+ BOOST_CHECK_EQUAL(input.scriptWitness.stack.size(), 0);
+ }
+
+ // Make sure we can add at least one weight
+ TestFillInputToWeight(1, {0});
+
+ // 1 byte compact size uint boundary
+ TestFillInputToWeight(252, {251});
+ TestFillInputToWeight(253, {83, 168});
+ TestFillInputToWeight(262, {86, 174});
+ TestFillInputToWeight(263, {260});
+
+ // 3 byte compact size uint boundary
+ TestFillInputToWeight(65535, {65532});
+ TestFillInputToWeight(65536, {21842, 43688});
+ TestFillInputToWeight(65545, {21845, 43694});
+ TestFillInputToWeight(65546, {65541});
+
+ // Note: We don't test the next boundary because of memory allocation constraints.
+}
+
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 3fcb086d2d..337a5139e1 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1507,6 +1507,49 @@ bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut
return true;
}
+bool FillInputToWeight(CTxIn& txin, int64_t target_weight)
+{
+ assert(txin.scriptSig.empty());
+ assert(txin.scriptWitness.IsNull());
+
+ int64_t txin_weight = GetTransactionInputWeight(txin);
+
+ // Do nothing if the weight that should be added is less than the weight that already exists
+ if (target_weight < txin_weight) {
+ return false;
+ }
+ if (target_weight == txin_weight) {
+ return true;
+ }
+
+ // Subtract current txin weight, which should include empty witness stack
+ int64_t add_weight = target_weight - txin_weight;
+ assert(add_weight > 0);
+
+ // We will want to subtract the size of the Compact Size UInt that will also be serialized.
+ // However doing so when the size is near a boundary can result in a problem where it is not
+ // possible to have a stack element size and combination to exactly equal a target.
+ // To avoid this possibility, if the weight to add is less than 10 bytes greater than
+ // a boundary, the size will be split so that 2/3rds will be in one stack element, and
+ // the remaining 1/3rd in another. Using 3rds allows us to avoid additional boundaries.
+ // 10 bytes is used because that accounts for the maximum size. This does not need to be super precise.
+ if ((add_weight >= 253 && add_weight < 263)
+ || (add_weight > std::numeric_limits<uint16_t>::max() && add_weight <= std::numeric_limits<uint16_t>::max() + 10)
+ || (add_weight > std::numeric_limits<uint32_t>::max() && add_weight <= std::numeric_limits<uint32_t>::max() + 10)) {
+ int64_t first_weight = add_weight / 3;
+ add_weight -= first_weight;
+
+ first_weight -= GetSizeOfCompactSize(first_weight);
+ txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), first_weight, 0);
+ }
+
+ add_weight -= GetSizeOfCompactSize(add_weight);
+ txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), add_weight, 0);
+ assert(GetTransactionInputWeight(txin) == target_weight);
+
+ return true;
+}
+
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
{
@@ -1515,6 +1558,14 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
for (const auto& txout : txouts)
{
CTxIn& txin = txNew.vin[nIn];
+ // If weight was provided, fill the input to that weight
+ if (coin_control && coin_control->HasInputWeight(txin.prevout)) {
+ if (!FillInputToWeight(txin, coin_control->GetInputWeight(txin.prevout))) {
+ return false;
+ }
+ nIn++;
+ continue;
+ }
// Use max sig if watch only inputs were used or if this particular input is an external input
// to ensure a sufficient fee is attained for the requested feerate.
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 00a1865a0e..e2c5c69c91 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -939,6 +939,8 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig);
+
+bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
} // namespace wallet
#endif // BITCOIN_WALLET_WALLET_H
diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json
index d1aa9ab00d..8903f5efac 100644
--- a/test/functional/data/rpc_decodescript.json
+++ b/test/functional/data/rpc_decodescript.json
@@ -4,6 +4,7 @@
{
"asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"address": "bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh",
+ "desc": "addr(bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh)#v52jnujz",
"type": "witness_v1_taproot"
}
],
@@ -12,6 +13,7 @@
{
"asm": "1 -28398",
"address": "bcrt1pamhqk96edn",
+ "desc": "addr(bcrt1pamhqk96edn)#vkh8uj5a",
"type": "witness_unknown"
}
],
@@ -20,6 +22,7 @@
{
"asm": "0 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"address": "bcrt1qamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqgdn98t",
+ "desc": "addr(bcrt1qamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqgdn98t)#afaecevx",
"type": "witness_v0_scripthash",
"p2sh": "2MwGk8mw1GBP6U9D5X8gTvgvXpuknmAK3fo"
}
@@ -29,6 +32,7 @@
{
"asm": "OP_HASH160 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee OP_EQUAL",
"address": "2NF2b3KS8xXb9XHvbRMXdZh8s5g92rUZHtp",
+ "desc": "addr(2NF2b3KS8xXb9XHvbRMXdZh8s5g92rUZHtp)#ywfcpmh9",
"type": "scripthash"
}
],
@@ -36,6 +40,7 @@
"6a00",
{
"asm": "OP_RETURN 0",
+ "desc": "raw(6a00)#ncfmkl43",
"type": "nulldata"
}
],
@@ -43,6 +48,7 @@
"6aee",
{
"asm": "OP_RETURN OP_UNKNOWN",
+ "desc": "raw(6aee)#vsyzgqdt",
"type": "nonstandard"
}
],
@@ -50,6 +56,7 @@
"6a02ee",
{
"asm": "OP_RETURN [error]",
+ "desc": "raw(6a02ee)#gvdwnlzl",
"type": "nonstandard"
}
],
@@ -57,10 +64,12 @@
"02eeee",
{
"asm": "-28398",
+ "desc": "raw(02eeee)#5xzck7pr",
"type": "nonstandard",
"p2sh": "2N34iiGoUUkVSPiaaTFpJjB1FR9TXQu3PGM",
"segwit": {
"asm": "0 96c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42",
+ "desc": "addr(bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5)#5akkdska",
"hex": "002096c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42",
"address": "bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5",
"type": "witness_v0_scripthash",
@@ -72,6 +81,7 @@
"ba",
{
"asm": "OP_CHECKSIGADD",
+ "desc": "raw(ba)#yy0eg44l",
"type": "nonstandard"
}
],
@@ -79,6 +89,7 @@
"50",
{
"asm": "OP_RESERVED",
+ "desc": "raw(50)#a7tu03xf",
"type": "nonstandard"
}
]
diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py
new file mode 100755
index 0000000000..87f9d6962d
--- /dev/null
+++ b/test/functional/feature_maxtipage.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test logic for setting nMaxTipAge on command line.
+
+Nodes don't consider themselves out of "initial block download" as long as
+their best known block header time is more than nMaxTipAge in the past.
+"""
+
+import time
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+DEFAULT_MAX_TIP_AGE = 24 * 60 * 60
+
+
+class MaxTipAgeTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ def test_maxtipage(self, maxtipage, set_parameter=True):
+ node_miner = self.nodes[0]
+ node_ibd = self.nodes[1]
+
+ self.restart_node(1, [f'-maxtipage={maxtipage}'] if set_parameter else None)
+ self.connect_nodes(0, 1)
+
+ # tips older than maximum age -> stay in IBD
+ cur_time = int(time.time())
+ node_ibd.setmocktime(cur_time)
+ for delta in [5, 4, 3, 2, 1]:
+ node_miner.setmocktime(cur_time - maxtipage - delta)
+ self.generate(node_miner, 1)
+ assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], True)
+
+ # tip within maximum age -> leave IBD
+ node_miner.setmocktime(cur_time - maxtipage)
+ self.generate(node_miner, 1)
+ assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], False)
+
+ def run_test(self):
+ self.log.info("Test IBD with maximum tip age of 24 hours (default).")
+ self.test_maxtipage(DEFAULT_MAX_TIP_AGE, set_parameter=False)
+
+ for hours in [20, 10, 5, 2, 1]:
+ maxtipage = hours * 60 * 60
+ self.log.info(f"Test IBD with maximum tip age of {hours} hours (-maxtipage={maxtipage}).")
+ self.test_maxtipage(maxtipage)
+
+
+if __name__ == '__main__':
+ MaxTipAgeTest().main()
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index a8e6acea45..759e43194b 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -4,8 +4,10 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the fundrawtransaction RPC."""
+
from decimal import Decimal
from itertools import product
+from math import ceil
from test_framework.descriptors import descsum_create
from test_framework.key import ECKey
@@ -1003,7 +1005,7 @@ class RawTransactionsTest(BitcoinTestFramework):
ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0]
# An external input without solving data should result in an error
- raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): 15})
+ raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): ext_utxo["amount"] / 2})
assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, raw_tx)
# Error conditions
@@ -1011,6 +1013,12 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-5, "'01234567890a0b0c0d0e0f' is not a valid public key", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["01234567890a0b0c0d0e0f"]}})
assert_raises_rpc_error(-5, "'not a script' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"scripts":["not a script"]}})
assert_raises_rpc_error(-8, "Unable to parse descriptor 'not a descriptor'", wallet.fundrawtransaction, raw_tx, {"solving_data": {"descriptors":["not a descriptor"]}})
+ assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"]}]})
+ assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": -1}]})
+ assert_raises_rpc_error(-8, "Invalid parameter, missing weight key", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"]}]})
+ assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 164}]})
+ assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": -1}]})
+ assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be greater than", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 400001}]})
# But funding should work when the solving data is provided
funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}})
@@ -1020,10 +1028,45 @@ class RawTransactionsTest(BitcoinTestFramework):
assert signed_tx['complete']
funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}})
- signed_tx = wallet.signrawtransactionwithwallet(funded_tx['hex'])
- assert not signed_tx['complete']
- signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex'])
- assert signed_tx['complete']
+ signed_tx1 = wallet.signrawtransactionwithwallet(funded_tx['hex'])
+ assert not signed_tx1['complete']
+ signed_tx2 = self.nodes[0].signrawtransactionwithwallet(signed_tx1['hex'])
+ assert signed_tx2['complete']
+
+ unsigned_weight = self.nodes[0].decoderawtransaction(signed_tx1["hex"])["weight"]
+ signed_weight = self.nodes[0].decoderawtransaction(signed_tx2["hex"])["weight"]
+ # Input's weight is difference between weight of signed and unsigned,
+ # and the weight of stuff that didn't change (prevout, sequence, 1 byte of scriptSig)
+ input_weight = signed_weight - unsigned_weight + (41 * 4)
+ low_input_weight = input_weight // 2
+ high_input_weight = input_weight * 2
+
+ # Funding should also work if the input weight is provided
+ funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}]})
+ signed_tx = wallet.signrawtransactionwithwallet(funded_tx["hex"])
+ signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx["hex"])
+ assert_equal(self.nodes[0].testmempoolaccept([signed_tx["hex"]])[0]["allowed"], True)
+ assert_equal(signed_tx["complete"], True)
+ # Reducing the weight should have a lower fee
+ funded_tx2 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": low_input_weight}]})
+ assert_greater_than(funded_tx["fee"], funded_tx2["fee"])
+ # Increasing the weight should have a higher fee
+ funded_tx2 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}]})
+ assert_greater_than(funded_tx2["fee"], funded_tx["fee"])
+ # The provided weight should override the calculated weight when solving data is provided
+ funded_tx3 = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}, "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}]})
+ assert_equal(funded_tx2["fee"], funded_tx3["fee"])
+ # The feerate should be met
+ funded_tx4 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], "fee_rate": 10})
+ input_add_weight = high_input_weight - (41 * 4)
+ tx4_weight = wallet.decoderawtransaction(funded_tx4["hex"])["weight"] + input_add_weight
+ tx4_vsize = int(ceil(tx4_weight / 4))
+ assert_fee_amount(funded_tx4["fee"], tx4_vsize, Decimal(0.0001))
+
+ # Funding with weight at csuint boundaries should not cause problems
+ funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 255}]})
+ funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 65539}]})
+
self.nodes[2].unloadwallet("extfund")
def test_include_unsafe(self):
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
index effcebe854..b65322d920 100755
--- a/test/functional/rpc_getblockfrompeer.py
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -40,12 +40,8 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
self.sync_blocks()
self.log.info("Node 0 should only have the header for node 1's block 3")
- for x in self.nodes[0].getchaintips():
- if x['hash'] == short_tip:
- assert_equal(x['status'], "headers-only")
- break
- else:
- raise AssertionError("short tip not synced")
+ x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips()))
+ assert_equal(x['status'], "headers-only")
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip)
self.log.info("Fetch block from node 1")
@@ -60,17 +56,15 @@ class GetBlockFromPeerTest(BitcoinTestFramework):
assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0)
self.log.info("Non-existent peer generates error")
- assert_raises_rpc_error(-1, f"Peer nodeid {peer_0_peer_1_id + 1} does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1)
+ assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1)
self.log.info("Successful fetch")
result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
self.wait_until(lambda: self.check_for_block(short_tip), timeout=1)
- assert(not "warnings" in result)
+ assert_equal(result, {})
self.log.info("Don't fetch blocks we already have")
- result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
- assert("warnings" in result)
- assert_equal(result["warnings"], "Block already downloaded")
+ assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id)
if __name__ == '__main__':
GetBlockFromPeerTest().main()
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 153a201e95..b037807b53 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -606,11 +606,15 @@ class PSBTTest(BitcoinTestFramework):
assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==')
- # Test that we can fund psbts with external inputs specified
+ self.log.info("Test that we can fund psbts with external inputs specified")
+
eckey = ECKey()
eckey.generate()
privkey = bytes_to_wif(eckey.get_bytes())
+ self.nodes[1].createwallet("extfund")
+ wallet = self.nodes[1].get_wallet_rpc("extfund")
+
# Make a weird but signable script. sh(pkh()) descriptor accomplishes this
desc = descsum_create("sh(pkh({}))".format(privkey))
if self.options.descriptors:
@@ -622,26 +626,97 @@ class PSBTTest(BitcoinTestFramework):
addr_info = self.nodes[0].getaddressinfo(addr)
self.nodes[0].sendtoaddress(addr, 10)
+ self.nodes[0].sendtoaddress(wallet.getnewaddress(), 10)
self.generate(self.nodes[0], 6)
ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0]
# An external input without solving data should result in an error
- assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[1].walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 10 + ext_utxo['amount']}, 0, {'add_inputs': True})
+ assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15})
# But funding should work when the solving data is provided
- psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}})
- signed = self.nodes[1].walletprocesspsbt(psbt['psbt'])
+ psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}})
+ signed = wallet.walletprocesspsbt(psbt['psbt'])
assert not signed['complete']
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
assert signed['complete']
self.nodes[0].finalizepsbt(signed['psbt'])
- psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data":{"descriptors": [desc]}})
- signed = self.nodes[1].walletprocesspsbt(psbt['psbt'])
+ psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data":{"descriptors": [desc]}})
+ signed = wallet.walletprocesspsbt(psbt['psbt'])
assert not signed['complete']
signed = self.nodes[0].walletprocesspsbt(signed['psbt'])
assert signed['complete']
- self.nodes[0].finalizepsbt(signed['psbt'])
+ final = self.nodes[0].finalizepsbt(signed['psbt'], False)
+
+ dec = self.nodes[0].decodepsbt(signed["psbt"])
+ for i, txin in enumerate(dec["tx"]["vin"]):
+ if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]:
+ input_idx = i
+ break
+ psbt_in = dec["inputs"][input_idx]
+ # Calculate the input weight
+ # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness
+ len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0
+ len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0
+ input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness
+ low_input_weight = input_weight // 2
+ high_input_weight = input_weight * 2
+
+ # Input weight error conditions
+ assert_raises_rpc_error(
+ -8,
+ "Input weights should be specified in inputs rather than in options.",
+ wallet.walletcreatefundedpsbt,
+ inputs=[ext_utxo],
+ outputs={self.nodes[0].getnewaddress(): 15},
+ options={"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]}
+ )
+
+ # Funding should also work if the input weight is provided
+ psbt = wallet.walletcreatefundedpsbt(
+ inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}],
+ outputs={self.nodes[0].getnewaddress(): 15},
+ options={"add_inputs": True}
+ )
+ signed = wallet.walletprocesspsbt(psbt["psbt"])
+ signed = self.nodes[0].walletprocesspsbt(signed["psbt"])
+ final = self.nodes[0].finalizepsbt(signed["psbt"])
+ assert self.nodes[0].testmempoolaccept([final["hex"]])[0]["allowed"]
+ # Reducing the weight should have a lower fee
+ psbt2 = wallet.walletcreatefundedpsbt(
+ inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": low_input_weight}],
+ outputs={self.nodes[0].getnewaddress(): 15},
+ options={"add_inputs": True}
+ )
+ assert_greater_than(psbt["fee"], psbt2["fee"])
+ # Increasing the weight should have a higher fee
+ psbt2 = wallet.walletcreatefundedpsbt(
+ inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}],
+ outputs={self.nodes[0].getnewaddress(): 15},
+ options={"add_inputs": True}
+ )
+ assert_greater_than(psbt2["fee"], psbt["fee"])
+ # The provided weight should override the calculated weight when solving data is provided
+ psbt3 = wallet.walletcreatefundedpsbt(
+ inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}],
+ outputs={self.nodes[0].getnewaddress(): 15},
+ options={'add_inputs': True, "solving_data":{"descriptors": [desc]}}
+ )
+ assert_equal(psbt2["fee"], psbt3["fee"])
+
+ # Import the external utxo descriptor so that we can sign for it from the test wallet
+ if self.options.descriptors:
+ res = wallet.importdescriptors([{"desc": desc, "timestamp": "now"}])
+ else:
+ res = wallet.importmulti([{"desc": desc, "timestamp": "now"}])
+ assert res[0]["success"]
+ # The provided weight should override the calculated weight for a wallet input
+ psbt3 = wallet.walletcreatefundedpsbt(
+ inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}],
+ outputs={self.nodes[0].getnewaddress(): 15},
+ options={"add_inputs": True}
+ )
+ assert_equal(psbt2["fee"], psbt3["fee"])
if __name__ == '__main__':
PSBTTest().main()
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 96691b2686..a839af0288 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -99,25 +99,36 @@ class RawTransactionsTest(BitcoinTestFramework):
rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999})
rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx)
txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex'])
- self.generate(self.nodes[0], 1)
+ self.generateblock(self.nodes[0], output=self.nodes[0].getnewaddress(), transactions=[rawTxSigned['hex']])
+ err_msg = (
+ "No such mempool transaction. Use -txindex or provide a block hash to enable"
+ " blockchain transaction queries. Use gettransaction for wallet transactions."
+ )
for n in [0, 3]:
self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex")
- # 1. valid parameters - only supply txid
- assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex'])
- # 2. valid parameters - supply txid and 0 for non-verbose
- assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex'])
+ if n == 0:
+ # With -txindex.
+ # 1. valid parameters - only supply txid
+ assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex'])
- # 3. valid parameters - supply txid and False for non-verbose
- assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex'])
+ # 2. valid parameters - supply txid and 0 for non-verbose
+ assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex'])
- # 4. valid parameters - supply txid and 1 for verbose.
- # We only check the "hex" field of the output so we don't need to update this test every time the output format changes.
- assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex'])
+ # 3. valid parameters - supply txid and False for non-verbose
+ assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex'])
- # 5. valid parameters - supply txid and True for non-verbose
- assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex'])
+ # 4. valid parameters - supply txid and 1 for verbose.
+ # We only check the "hex" field of the output so we don't need to update this test every time the output format changes.
+ assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex'])
+
+ # 5. valid parameters - supply txid and True for non-verbose
+ assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex'])
+ else:
+ # Without -txindex, expect to raise.
+ for verbose in [None, 0, False, 1, True]:
+ assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txId, verbose)
# 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose
for value in ["True", "False"]:
@@ -145,10 +156,6 @@ class RawTransactionsTest(BitcoinTestFramework):
assert 'in_active_chain' not in gottx
else:
self.log.info("Test getrawtransaction without -txindex, without blockhash: expect the call to raise")
- err_msg = (
- "No such mempool transaction. Use -txindex or provide a block hash to enable"
- " blockchain transaction queries. Use gettransaction for wallet transactions."
- )
assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txid=tx, verbose=True)
# We should not get the tx if we provide an unrelated block
assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2)
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index eb2d030f4a..e833128063 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -198,6 +198,7 @@ BASE_SCRIPTS = [
'wallet_keypool.py --legacy-wallet',
'wallet_keypool.py --descriptors',
'wallet_descriptor.py --descriptors',
+ 'feature_maxtipage.py',
'p2p_nobloomfilter_messages.py',
'p2p_filter.py',
'rpc_setban.py',
@@ -308,6 +309,7 @@ BASE_SCRIPTS = [
'feature_coinstatsindex.py --legacy-wallet',
'feature_coinstatsindex.py --descriptors',
'wallet_orphanedreward.py',
+ 'wallet_timelock.py',
'p2p_node_network_limited.py',
'p2p_permissions.py',
'feature_blocksdir.py',
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index d77d554baa..843a9f52b7 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -518,5 +518,45 @@ class WalletSendTest(BitcoinTestFramework):
assert signed["complete"]
self.nodes[0].finalizepsbt(signed["psbt"])
+ dec = self.nodes[0].decodepsbt(signed["psbt"])
+ for i, txin in enumerate(dec["tx"]["vin"]):
+ if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]:
+ input_idx = i
+ break
+ psbt_in = dec["inputs"][input_idx]
+ # Calculate the input weight
+ # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness
+ len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0
+ len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0
+ input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness
+
+ # Input weight error conditions
+ assert_raises_rpc_error(
+ -8,
+ "Input weights should be specified in inputs rather than in options.",
+ ext_wallet.send,
+ outputs={self.nodes[0].getnewaddress(): 15},
+ options={"inputs": [ext_utxo], "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]}
+ )
+
+ # Funding should also work when input weights are provided
+ res = self.test_send(
+ from_wallet=ext_wallet,
+ to_wallet=self.nodes[0],
+ amount=15,
+ inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}],
+ add_inputs=True,
+ psbt=True,
+ include_watching=True,
+ fee_rate=10
+ )
+ signed = ext_wallet.walletprocesspsbt(res["psbt"])
+ signed = ext_fund.walletprocesspsbt(res["psbt"])
+ assert signed["complete"]
+ tx = self.nodes[0].finalizepsbt(signed["psbt"])
+ testres = self.nodes[0].testmempoolaccept([tx["hex"]])[0]
+ assert_equal(testres["allowed"], True)
+ assert_fee_amount(testres["fees"]["base"], testres["vsize"], Decimal(0.0001))
+
if __name__ == '__main__':
WalletSendTest().main()
diff --git a/test/functional/wallet_timelock.py b/test/functional/wallet_timelock.py
new file mode 100755
index 0000000000..cf233a00ef
--- /dev/null
+++ b/test/functional/wallet_timelock.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+class WalletLocktimeTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 1
+
+ def skip_test_if_missing_module(self):
+ self.skip_if_no_wallet()
+
+ def run_test(self):
+ node = self.nodes[0]
+
+ mtp_tip = node.getblockheader(node.getbestblockhash())["mediantime"]
+
+ self.log.info("Get new address with label")
+ label = "timelock⌛🔓"
+ address = node.getnewaddress(label=label)
+
+ self.log.info("Send to new address with locktime")
+ node.send(
+ outputs={address: 5},
+ options={"locktime": mtp_tip - 1},
+ )
+ self.generate(node, 1)
+
+ self.log.info("Check that clock can not change finality of confirmed txs")
+ amount_before_ad = node.getreceivedbyaddress(address)
+ amount_before_lb = node.getreceivedbylabel(label)
+ list_before_ad = node.listreceivedbyaddress(address_filter=address)
+ list_before_lb = node.listreceivedbylabel(include_empty=False)
+ balance_before = node.getbalances()["mine"]["trusted"]
+ coin_before = node.listunspent(maxconf=1)
+ node.setmocktime(mtp_tip - 1)
+ assert_equal(node.getreceivedbyaddress(address), amount_before_ad)
+ assert_equal(node.getreceivedbylabel(label), amount_before_lb)
+ assert_equal(node.listreceivedbyaddress(address_filter=address), list_before_ad)
+ assert_equal(node.listreceivedbylabel(include_empty=False), list_before_lb)
+ assert_equal(node.getbalances()["mine"]["trusted"], balance_before)
+ assert_equal(node.listunspent(maxconf=1), coin_before)
+
+
+if __name__ == "__main__":
+ WalletLocktimeTest().main()
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
index 393278bd6a..c557f2de92 100644
--- a/test/sanitizer_suppressions/ubsan
+++ b/test/sanitizer_suppressions/ubsan
@@ -85,10 +85,6 @@ implicit-signed-integer-truncation:addrman.cpp
implicit-signed-integer-truncation:addrman.h
implicit-signed-integer-truncation:chain.h
implicit-signed-integer-truncation:crypto/
-implicit-signed-integer-truncation:node/miner.cpp
-implicit-signed-integer-truncation:net.cpp
-implicit-signed-integer-truncation:streams.h
-implicit-signed-integer-truncation:torcontrol.cpp
implicit-unsigned-integer-truncation:crypto/
shift-base:arith_uint256.cpp
shift-base:crypto/
diff --git a/test/util/data/tt-delin1-out.json b/test/util/data/tt-delin1-out.json
index c5b9f6df01..6e053fe2b9 100644
--- a/test/util/data/tt-delin1-out.json
+++ b/test/util/data/tt-delin1-out.json
@@ -194,6 +194,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 8fd139bb39ced713f231c58a4d07bf6954d1c201 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o)#xvg87vgr",
"hex": "76a9148fd139bb39ced713f231c58a4d07bf6954d1c20188ac",
"address": "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o",
"type": "pubkeyhash"
@@ -204,6 +205,7 @@
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 6c772e9cf96371bba3da8cb733da70a2fcf20078 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb)#tsyprkms",
"hex": "76a9146c772e9cf96371bba3da8cb733da70a2fcf2007888ac",
"address": "1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb",
"type": "pubkeyhash"
diff --git a/test/util/data/tt-delout1-out.json b/test/util/data/tt-delout1-out.json
index 3863416430..e61b9c79db 100644
--- a/test/util/data/tt-delout1-out.json
+++ b/test/util/data/tt-delout1-out.json
@@ -203,6 +203,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 8fd139bb39ced713f231c58a4d07bf6954d1c201 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o)#xvg87vgr",
"hex": "76a9148fd139bb39ced713f231c58a4d07bf6954d1c20188ac",
"address": "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o",
"type": "pubkeyhash"
diff --git a/test/util/data/tt-locktime317000-out.json b/test/util/data/tt-locktime317000-out.json
index 62e785f7d0..873628e124 100644
--- a/test/util/data/tt-locktime317000-out.json
+++ b/test/util/data/tt-locktime317000-out.json
@@ -203,6 +203,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 8fd139bb39ced713f231c58a4d07bf6954d1c201 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o)#xvg87vgr",
"hex": "76a9148fd139bb39ced713f231c58a4d07bf6954d1c20188ac",
"address": "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o",
"type": "pubkeyhash"
@@ -213,6 +214,7 @@
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 6c772e9cf96371bba3da8cb733da70a2fcf20078 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb)#tsyprkms",
"hex": "76a9146c772e9cf96371bba3da8cb733da70a2fcf2007888ac",
"address": "1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb",
"type": "pubkeyhash"
diff --git a/test/util/data/txcreate1.json b/test/util/data/txcreate1.json
index 96d77ef273..c4a76f22a6 100644
--- a/test/util/data/txcreate1.json
+++ b/test/util/data/txcreate1.json
@@ -41,6 +41,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(13tuJJDR2RgArmgfv6JScSdreahzgc4T6o)#ztmwxg4c",
"hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac",
"address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
"type": "pubkeyhash"
@@ -51,6 +52,7 @@
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 f2d4db28cad6502226ee484ae24505c2885cb12d OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(1P8yWvZW8jVihP1bzHeqfE4aoXNX8AVa46)#vdmdu766",
"hex": "76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac",
"address": "1P8yWvZW8jVihP1bzHeqfE4aoXNX8AVa46",
"type": "pubkeyhash"
diff --git a/test/util/data/txcreate2.json b/test/util/data/txcreate2.json
index ee9b9c3c17..95953cc90e 100644
--- a/test/util/data/txcreate2.json
+++ b/test/util/data/txcreate2.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "",
+ "desc": "raw()#58lrscpx",
"hex": "",
"type": "nonstandard"
}
diff --git a/test/util/data/txcreatedata1.json b/test/util/data/txcreatedata1.json
index 87fc7e9cf7..1454ffdab7 100644
--- a/test/util/data/txcreatedata1.json
+++ b/test/util/data/txcreatedata1.json
@@ -23,6 +23,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(13tuJJDR2RgArmgfv6JScSdreahzgc4T6o)#ztmwxg4c",
"hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac",
"address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
"type": "pubkeyhash"
@@ -33,6 +34,7 @@
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN 54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e",
+ "desc": "raw(6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e)#zf2avljj",
"hex": "6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e",
"type": "nulldata"
}
diff --git a/test/util/data/txcreatedata2.json b/test/util/data/txcreatedata2.json
index d03b1c8244..ca20d2aa45 100644
--- a/test/util/data/txcreatedata2.json
+++ b/test/util/data/txcreatedata2.json
@@ -23,6 +23,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(13tuJJDR2RgArmgfv6JScSdreahzgc4T6o)#ztmwxg4c",
"hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac",
"address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
"type": "pubkeyhash"
@@ -33,6 +34,7 @@
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN 54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e",
+ "desc": "raw(6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e)#zf2avljj",
"hex": "6a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e",
"type": "nulldata"
}
diff --git a/test/util/data/txcreatedata_seq0.json b/test/util/data/txcreatedata_seq0.json
index 8a123f1ba8..9838383c06 100644
--- a/test/util/data/txcreatedata_seq0.json
+++ b/test/util/data/txcreatedata_seq0.json
@@ -23,6 +23,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(13tuJJDR2RgArmgfv6JScSdreahzgc4T6o)#ztmwxg4c",
"hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac",
"address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
"type": "pubkeyhash"
diff --git a/test/util/data/txcreatedata_seq1.json b/test/util/data/txcreatedata_seq1.json
index 006fd7259f..c729f8dcfb 100644
--- a/test/util/data/txcreatedata_seq1.json
+++ b/test/util/data/txcreatedata_seq1.json
@@ -32,6 +32,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(13tuJJDR2RgArmgfv6JScSdreahzgc4T6o)#ztmwxg4c",
"hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac",
"address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
"type": "pubkeyhash"
diff --git a/test/util/data/txcreatemultisig1.json b/test/util/data/txcreatemultisig1.json
index baa290c2b1..9632b20ece 100644
--- a/test/util/data/txcreatemultisig1.json
+++ b/test/util/data/txcreatemultisig1.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "2 02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397 021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d 02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485 3 OP_CHECKMULTISIG",
+ "desc": "multi(2,02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397,021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d,02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485)#8s88p9pl",
"hex": "522102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff39721021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d2102df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb48553ae",
"type": "multisig"
}
diff --git a/test/util/data/txcreatemultisig2.json b/test/util/data/txcreatemultisig2.json
index 6685512587..021cf539a8 100644
--- a/test/util/data/txcreatemultisig2.json
+++ b/test/util/data/txcreatemultisig2.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 1c6fbaf46d64221e80cbae182c33ddf81b9294ac OP_EQUAL",
+ "desc": "addr(34HNh57oBCRKkxNyjTuWAJkTbuGh6jg2Ms)#ngnz8933",
"hex": "a9141c6fbaf46d64221e80cbae182c33ddf81b9294ac87",
"address": "34HNh57oBCRKkxNyjTuWAJkTbuGh6jg2Ms",
"type": "scripthash"
diff --git a/test/util/data/txcreatemultisig3.json b/test/util/data/txcreatemultisig3.json
index be96f4c704..3c20a88a91 100644
--- a/test/util/data/txcreatemultisig3.json
+++ b/test/util/data/txcreatemultisig3.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "0 e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05",
+ "desc": "addr(bc1qu9dgdg330r6r84g5mw7wqshg04exv2uttmw2elfwx74h5tgntuzs44gyfg)#yvt39j9m",
"hex": "0020e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05",
"address": "bc1qu9dgdg330r6r84g5mw7wqshg04exv2uttmw2elfwx74h5tgntuzs44gyfg",
"type": "witness_v0_scripthash"
diff --git a/test/util/data/txcreatemultisig4.json b/test/util/data/txcreatemultisig4.json
index 08831ecdca..7ae18fd90a 100644
--- a/test/util/data/txcreatemultisig4.json
+++ b/test/util/data/txcreatemultisig4.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 6edf12858999f0dae74f9c692e6694ee3621b2ac OP_EQUAL",
+ "desc": "addr(3BoFUz1StqcNcgUTZE5cC1eFhuYFzj3fGH)#466tx6fn",
"hex": "a9146edf12858999f0dae74f9c692e6694ee3621b2ac87",
"address": "3BoFUz1StqcNcgUTZE5cC1eFhuYFzj3fGH",
"type": "scripthash"
diff --git a/test/util/data/txcreatemultisig5.json b/test/util/data/txcreatemultisig5.json
index 93048cf261..98a5c2d8d1 100644
--- a/test/util/data/txcreatemultisig5.json
+++ b/test/util/data/txcreatemultisig5.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 a4051c02398868af83f28f083208fae99a769263 OP_EQUAL",
+ "desc": "addr(3GeGs1eHUxPz5YyuFe9WPpXid2UsUb5Jos)#juhnnegr",
"hex": "a914a4051c02398868af83f28f083208fae99a76926387",
"address": "3GeGs1eHUxPz5YyuFe9WPpXid2UsUb5Jos",
"type": "scripthash"
diff --git a/test/util/data/txcreateoutpubkey1.json b/test/util/data/txcreateoutpubkey1.json
index 42b519bb21..3baf479991 100644
--- a/test/util/data/txcreateoutpubkey1.json
+++ b/test/util/data/txcreateoutpubkey1.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397 OP_CHECKSIG",
+ "desc": "pk(02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397)#rk5v7uqw",
"hex": "2102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397ac",
"type": "pubkey"
}
diff --git a/test/util/data/txcreateoutpubkey2.json b/test/util/data/txcreateoutpubkey2.json
index 52168a889b..78acf1658b 100644
--- a/test/util/data/txcreateoutpubkey2.json
+++ b/test/util/data/txcreateoutpubkey2.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "0 a2516e770582864a6a56ed21a102044e388c62e3",
+ "desc": "addr(bc1q5fgkuac9s2ry56jka5s6zqsyfcugcchry5cwu0)#gm7zhxq2",
"hex": "0014a2516e770582864a6a56ed21a102044e388c62e3",
"address": "bc1q5fgkuac9s2ry56jka5s6zqsyfcugcchry5cwu0",
"type": "witness_v0_keyhash"
diff --git a/test/util/data/txcreateoutpubkey3.json b/test/util/data/txcreateoutpubkey3.json
index fce210f8a3..632ed52ccf 100644
--- a/test/util/data/txcreateoutpubkey3.json
+++ b/test/util/data/txcreateoutpubkey3.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 a5ab14c9804d0d8bf02f1aea4e82780733ad0a83 OP_EQUAL",
+ "desc": "addr(3GnzN8FqgvYGYdhj8NW6UNxxVv3Uj1ApQn)#zsln680u",
"hex": "a914a5ab14c9804d0d8bf02f1aea4e82780733ad0a8387",
"address": "3GnzN8FqgvYGYdhj8NW6UNxxVv3Uj1ApQn",
"type": "scripthash"
diff --git a/test/util/data/txcreatescript1.json b/test/util/data/txcreatescript1.json
index af1c4c35e2..cdee9dbbfa 100644
--- a/test/util/data/txcreatescript1.json
+++ b/test/util/data/txcreatescript1.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DROP",
+ "desc": "raw(75)#ppey0zqj",
"hex": "75",
"type": "nonstandard"
}
diff --git a/test/util/data/txcreatescript2.json b/test/util/data/txcreatescript2.json
index 2cde70fdf7..1fbae62f4b 100644
--- a/test/util/data/txcreatescript2.json
+++ b/test/util/data/txcreatescript2.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 71ed53322d470bb96657deb786b94f97dd46fb15 OP_EQUAL",
+ "desc": "addr(3C5QarEGh9feKbDJ3QbMf2YNjnMoiPDhNp)#5mx9waq3",
"hex": "a91471ed53322d470bb96657deb786b94f97dd46fb1587",
"address": "3C5QarEGh9feKbDJ3QbMf2YNjnMoiPDhNp",
"type": "scripthash"
diff --git a/test/util/data/txcreatescript3.json b/test/util/data/txcreatescript3.json
index 7a282faf4f..502fe91692 100644
--- a/test/util/data/txcreatescript3.json
+++ b/test/util/data/txcreatescript3.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "0 0bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6",
+ "desc": "addr(bc1qp0lfxhnscvsu0j36l36uurgv5tuck4pzuqytkvwqp3kh78cupttqyf705v)#s4fdh9tu",
"hex": "00200bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6",
"address": "bc1qp0lfxhnscvsu0j36l36uurgv5tuck4pzuqytkvwqp3kh78cupttqyf705v",
"type": "witness_v0_scripthash"
diff --git a/test/util/data/txcreatescript4.json b/test/util/data/txcreatescript4.json
index 298b37bb4a..1ed89dfff2 100644
--- a/test/util/data/txcreatescript4.json
+++ b/test/util/data/txcreatescript4.json
@@ -14,6 +14,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_HASH160 6a2c482f4985f57e702f325816c90e3723ca81ae OP_EQUAL",
+ "desc": "addr(3BNQbeFeJJGMAyDxPwWPuqxPMrjsFLjk3f)#fdleltnv",
"hex": "a9146a2c482f4985f57e702f325816c90e3723ca81ae87",
"address": "3BNQbeFeJJGMAyDxPwWPuqxPMrjsFLjk3f",
"type": "scripthash"
diff --git a/test/util/data/txcreatesignv1.json b/test/util/data/txcreatesignv1.json
index ca5e003110..56ef9b195e 100644
--- a/test/util/data/txcreatesignv1.json
+++ b/test/util/data/txcreatesignv1.json
@@ -23,6 +23,7 @@
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 5834479edbbe0539b31ffd3a8f8ebadc2165ed01 OP_EQUALVERIFY OP_CHECKSIG",
+ "desc": "addr(193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7)#nw04wh58",
"hex": "76a9145834479edbbe0539b31ffd3a8f8ebadc2165ed0188ac",
"address": "193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7",
"type": "pubkeyhash"