mirror of https://github.com/bitcoin/bitcoin.git
Merge bitcoin/bitcoin#33356: [30.0] rc2 backports
c9f751090c
cmake: Install `bitcoin` manpage (Hennadii Stepanov)2327b2b0db
net: Do not apply whitelist permission to onion inbounds (Martin Zumsande)26208b3a0c
test: Add submitblock test in interface_ipc (TheCharlatan)3ae592537d
test: Prevent disk space warning during node_init_tests (Ryan Ofsky)5dbb1bae38
ci: Enable CI_LIMIT_STACK_SIZE=1 in i686_no_ipc task (MarcoFalke)c7faf72ac6
test: Fix CLI_MAX_ARG_SIZE issues (MarcoFalke)0a2afbeb77
cmake: Fix regression in `secp256k1.cmake` (Hennadii Stepanov)75026cddea
wallet: Add m_cached_from_me to cache "from me" status (Ava Chow)bbb4e118f3
test: Add a test for anchor outputs in the wallet (Ava Chow)b85dc7ed3a
wallet: Throw an error in sendall if the tx size cannot be calculated (Ava Chow)d2be9a22d8
wallet: Determine IsFromMe by checking for TXOs of inputs (Ava Chow)ad6c13e041
test: Test wallet 'from me' status change (Ava Chow)35038b03c9
trace: Workaround GCC bug compiling with old systemtap (Luke Dashjr)f7eded1dca
ci: always use tag for LLVM checkout (fanquake)6b19ede1a5
gui: Avoid pathological QT text/markdown behavior... (David Gumberg) Pull request description: Backports: * #33243 * #33268 * #33310 * #33364 * #33379 * #33380 * #33391 * #33407 * https://github.com/bitcoin-core/gui/pull/886 ACKs for top commit: darosior: utACKc9f751090c
hebasto: ACKc9f751090c
, I applied all backports locally without conflicts and obtained a zero diff with this PR branch. Tree-SHA512: 257cc5bd0423fbf2aff62c72957faea3de8731353d809b11e18d0e5cad174c7023dca9dedd0c73e07497eb804b7c48355a055b4461db260e2f0a5712d2514ff6
This commit is contained in:
commit
b7a722724d
|
@ -13,6 +13,7 @@ export CI_IMAGE_PLATFORM="linux/amd64"
|
|||
export PACKAGES="llvm clang g++-multilib"
|
||||
export DEP_OPTS="DEBUG=1 NO_IPC=1"
|
||||
export GOAL="install"
|
||||
export CI_LIMIT_STACK_SIZE=1
|
||||
export TEST_RUNNER_EXTRA="--v2transport --usecli"
|
||||
export BITCOIN_CONFIG="\
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
|
|
|
@ -56,10 +56,9 @@ if [ -n "$PIP_PACKAGES" ]; then
|
|||
fi
|
||||
|
||||
if [[ -n "${USE_INSTRUMENTED_LIBCPP}" ]]; then
|
||||
${CI_RETRY_EXE} git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-21.1.1" /llvm-project
|
||||
|
||||
if [ -n "${APT_LLVM_V}" ]; then
|
||||
${CI_RETRY_EXE} git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-$( clang --version | sed --silent 's@.*clang version \([0-9.]*\).*@\1@p' )" /llvm-project
|
||||
else
|
||||
${CI_RETRY_EXE} git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-21.1.0" /llvm-project
|
||||
|
||||
cmake -G Ninja -B /clang_build/ \
|
||||
-DLLVM_ENABLE_PROJECTS="clang" \
|
||||
|
|
|
@ -36,6 +36,10 @@ if(USDT_INCLUDE_DIR)
|
|||
include(CheckCXXSourceCompiles)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${USDT_INCLUDE_DIR})
|
||||
check_cxx_source_compiles("
|
||||
#if defined(__arm__)
|
||||
# define STAP_SDT_ARG_CONSTRAINT g
|
||||
#endif
|
||||
|
||||
// Setting SDT_USE_VARIADIC lets systemtap (sys/sdt.h) know that we want to use
|
||||
// the optional variadic macros to define tracepoints.
|
||||
#define SDT_USE_VARIADIC 1
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://opensource.org/license/mit/.
|
||||
|
||||
enable_language(C)
|
||||
|
||||
function(add_secp256k1 subdir)
|
||||
message("")
|
||||
message("Configuring secp256k1 subtree...")
|
||||
|
@ -30,7 +32,6 @@ function(add_secp256k1 subdir)
|
|||
string(STRIP "${SECP256K1_APPEND_LDFLAGS} ${APPEND_LDFLAGS}" SECP256K1_APPEND_LDFLAGS)
|
||||
set(SECP256K1_APPEND_LDFLAGS ${SECP256K1_APPEND_LDFLAGS} CACHE STRING "" FORCE)
|
||||
# We want to build libsecp256k1 with the most tested RelWithDebInfo configuration.
|
||||
enable_language(C)
|
||||
foreach(config IN LISTS CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES)
|
||||
if(config STREQUAL "")
|
||||
continue()
|
||||
|
|
|
@ -293,7 +293,7 @@ if(BUILD_BITCOIN_BIN)
|
|||
add_windows_resources(bitcoin bitcoin-res.rc)
|
||||
add_windows_application_manifest(bitcoin)
|
||||
target_link_libraries(bitcoin core_interface bitcoin_util)
|
||||
install_binary_component(bitcoin)
|
||||
install_binary_component(bitcoin HAS_MANPAGE)
|
||||
endif()
|
||||
|
||||
# Bitcoin Core bitcoind.
|
||||
|
|
11
src/net.cpp
11
src/net.cpp
|
@ -574,9 +574,9 @@ void CNode::CloseSocketDisconnect()
|
|||
m_i2p_sam_session.reset();
|
||||
}
|
||||
|
||||
void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr, const std::vector<NetWhitelistPermissions>& ranges) const {
|
||||
void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, std::optional<CNetAddr> addr, const std::vector<NetWhitelistPermissions>& ranges) const {
|
||||
for (const auto& subnet : ranges) {
|
||||
if (subnet.m_subnet.Match(addr)) {
|
||||
if (addr.has_value() && subnet.m_subnet.Match(addr.value())) {
|
||||
NetPermissions::AddFlag(flags, subnet.m_flags);
|
||||
}
|
||||
}
|
||||
|
@ -1768,7 +1768,11 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
|
|||
{
|
||||
int nInbound = 0;
|
||||
|
||||
AddWhitelistPermissionFlags(permission_flags, addr, vWhitelistedRangeIncoming);
|
||||
const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end();
|
||||
|
||||
// Tor inbound connections do not reveal the peer's actual network address.
|
||||
// Therefore do not apply address-based whitelist permissions to them.
|
||||
AddWhitelistPermissionFlags(permission_flags, inbound_onion ? std::optional<CNetAddr>{} : addr, vWhitelistedRangeIncoming);
|
||||
|
||||
{
|
||||
LOCK(m_nodes_mutex);
|
||||
|
@ -1823,7 +1827,6 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
|
|||
NodeId id = GetNewNodeId();
|
||||
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
|
||||
|
||||
const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end();
|
||||
// The V2Transport transparently falls back to V1 behavior when an incoming V1 connection is
|
||||
// detected, so use it whenever we signal NODE_P2P_V2.
|
||||
ServiceFlags local_services = GetLocalServices();
|
||||
|
|
|
@ -1377,7 +1377,7 @@ private:
|
|||
|
||||
bool AttemptToEvictConnection();
|
||||
CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type, bool use_v2transport) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex);
|
||||
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr, const std::vector<NetWhitelistPermissions>& ranges) const;
|
||||
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, std::optional<CNetAddr> addr, const std::vector<NetWhitelistPermissions>& ranges) const;
|
||||
|
||||
void DeleteNode(CNode* pnode);
|
||||
|
||||
|
|
|
@ -573,7 +573,7 @@
|
|||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="messagesWidget">
|
||||
<widget class="PlainCopyTextEdit" name="messagesWidget">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
|
@ -1868,6 +1868,10 @@
|
|||
<slot>clear()</slot>
|
||||
</slots>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PlainCopyTextEdit</class>
|
||||
<extends>QTextEdit</extends>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../bitcoin.qrc"/>
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
#include <QByteArray>
|
||||
#include <QCompleter>
|
||||
#include <QMimeData>
|
||||
#include <QTextDocumentFragment>
|
||||
#include <QTextEdit>
|
||||
#include <QThread>
|
||||
#include <QWidget>
|
||||
|
||||
|
@ -191,4 +194,20 @@ private Q_SLOTS:
|
|||
void updateAlerts(const QString& warnings);
|
||||
};
|
||||
|
||||
/**
|
||||
* A version of QTextEdit that only populates plaintext mime data from a
|
||||
* selection, this avoids some bad behavior in QT's HTML->Markdown conversion.
|
||||
*/
|
||||
class PlainCopyTextEdit : public QTextEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QTextEdit::QTextEdit;
|
||||
protected:
|
||||
QMimeData* createMimeDataFromSelection() const override {
|
||||
auto md = new QMimeData();
|
||||
md->setText(textCursor().selection().toPlainText());
|
||||
return md;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // BITCOIN_QT_RPCCONSOLE_H
|
||||
|
|
|
@ -11,7 +11,12 @@
|
|||
|
||||
using node::NodeContext;
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(node_init_tests, BasicTestingSetup)
|
||||
//! Like BasicTestingSetup, but using regtest network instead of mainnet.
|
||||
struct InitTestSetup : BasicTestingSetup {
|
||||
InitTestSetup() : BasicTestingSetup{ChainType::REGTEST} {}
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(node_init_tests, InitTestSetup)
|
||||
|
||||
//! Custom implementation of interfaces::Init for testing.
|
||||
class TestInit : public interfaces::Init
|
||||
|
|
|
@ -9,6 +9,13 @@
|
|||
|
||||
#ifdef ENABLE_TRACING
|
||||
|
||||
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103395
|
||||
// systemtap 4.6 on 32-bit ARM triggers internal compiler error
|
||||
// (this workaround is included in systemtap 4.7+)
|
||||
#if defined(__arm__)
|
||||
# define STAP_SDT_ARG_CONSTRAINT g
|
||||
#endif
|
||||
|
||||
// Setting SDT_USE_VARIADIC lets systemtap (sys/sdt.h) know that we want to use
|
||||
// the optional variadic macros to define tracepoints.
|
||||
#define SDT_USE_VARIADIC 1
|
||||
|
|
|
@ -195,7 +195,10 @@ void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
|
|||
|
||||
bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx)
|
||||
{
|
||||
return (CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/false) > 0);
|
||||
if (!wtx.m_cached_from_me.has_value()) {
|
||||
wtx.m_cached_from_me = wallet.IsFromMe(*wtx.tx);
|
||||
}
|
||||
return wtx.m_cached_from_me.value();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
|
|
|
@ -1521,7 +1521,6 @@ RPCHelpMan sendall()
|
|||
CoinFilterParams coins_params;
|
||||
coins_params.min_amount = 0;
|
||||
for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, coins_params).All()) {
|
||||
CHECK_NONFATAL(output.input_bytes > 0);
|
||||
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1544,6 +1543,9 @@ RPCHelpMan sendall()
|
|||
|
||||
// estimate final size of tx
|
||||
const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())};
|
||||
if (tx_size.vsize == -1) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors");
|
||||
}
|
||||
const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
|
||||
const std::optional<CAmount> total_bump_fees{pwallet->chain().calculateCombinedBumpFee(outpoints_spent, fee_rate)};
|
||||
CAmount effective_value = total_input_value - fee_from_size - total_bump_fees.value_or(0);
|
||||
|
|
|
@ -232,6 +232,8 @@ public:
|
|||
* CWallet::ComputeTimeSmart().
|
||||
*/
|
||||
unsigned int nTimeSmart;
|
||||
// Cached value for whether the transaction spends any inputs known to the wallet
|
||||
mutable std::optional<bool> m_cached_from_me{std::nullopt};
|
||||
int64_t nOrderPos; //!< position in ordered transaction list
|
||||
std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered;
|
||||
|
||||
|
@ -339,6 +341,7 @@ public:
|
|||
m_amounts[CREDIT].Reset();
|
||||
fChangeCached = false;
|
||||
m_is_cache_empty = true;
|
||||
m_cached_from_me = std::nullopt;
|
||||
}
|
||||
|
||||
/** True if only scriptSigs are different */
|
||||
|
|
|
@ -1634,7 +1634,11 @@ bool CWallet::IsMine(const COutPoint& outpoint) const
|
|||
|
||||
bool CWallet::IsFromMe(const CTransaction& tx) const
|
||||
{
|
||||
return (GetDebit(tx) > 0);
|
||||
LOCK(cs_wallet);
|
||||
for (const CTxIn& txin : tx.vin) {
|
||||
if (GetTXO(txin.prevout)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CAmount CWallet::GetDebit(const CTransaction& tx) const
|
||||
|
|
|
@ -7,7 +7,7 @@ import asyncio
|
|||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from test_framework.messages import (CBlock, CTransaction, ser_uint256)
|
||||
from test_framework.messages import (CBlock, CTransaction, ser_uint256, COIN)
|
||||
from test_framework.test_framework import (BitcoinTestFramework, assert_equal)
|
||||
from test_framework.wallet import MiniWallet
|
||||
|
||||
|
@ -73,6 +73,12 @@ class IPCInterfaceTest(BitcoinTestFramework):
|
|||
block.deserialize(block_data)
|
||||
return block
|
||||
|
||||
async def parse_and_deserialize_coinbase_tx(self, block_template, ctx):
|
||||
coinbase_data = BytesIO((await block_template.result.getCoinbaseTx(ctx)).result)
|
||||
tx = CTransaction()
|
||||
tx.deserialize(coinbase_data)
|
||||
return tx
|
||||
|
||||
def run_echo_test(self):
|
||||
self.log.info("Running echo test")
|
||||
async def async_routine():
|
||||
|
@ -170,6 +176,41 @@ class IPCInterfaceTest(BitcoinTestFramework):
|
|||
self.log.debug("Wait for another, but time out, since the fee threshold is set now")
|
||||
template7 = await template6.result.waitNext(ctx, waitoptions)
|
||||
assert_equal(template7.to_dict(), {})
|
||||
|
||||
current_block_height = self.nodes[0].getchaintips()[0]["height"]
|
||||
check_opts = self.capnp_modules['mining'].BlockCheckOptions()
|
||||
template = await mining.result.createNewBlock(opts)
|
||||
block = await self.parse_and_deserialize_block(template, ctx)
|
||||
coinbase = await self.parse_and_deserialize_coinbase_tx(template, ctx)
|
||||
balance = miniwallet.get_balance()
|
||||
coinbase.vout[0].scriptPubKey = miniwallet.get_output_script()
|
||||
coinbase.vout[0].nValue = COIN
|
||||
block.vtx[0] = coinbase
|
||||
block.hashMerkleRoot = block.calc_merkle_root()
|
||||
original_version = block.nVersion
|
||||
self.log.debug("Submit a block with a bad version")
|
||||
block.nVersion = 0
|
||||
block.solve()
|
||||
res = await mining.result.checkBlock(block.serialize(), check_opts)
|
||||
assert_equal(res.result, False)
|
||||
assert_equal(res.reason, "bad-version(0x00000000)")
|
||||
res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())
|
||||
assert_equal(res.result, False)
|
||||
self.log.debug("Submit a valid block")
|
||||
block.nVersion = original_version
|
||||
block.solve()
|
||||
res = await mining.result.checkBlock(block.serialize(), check_opts)
|
||||
assert_equal(res.result, True)
|
||||
res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())
|
||||
assert_equal(res.result, True)
|
||||
assert_equal(self.nodes[0].getchaintips()[0]["height"], current_block_height + 1)
|
||||
miniwallet.rescan_utxos()
|
||||
assert_equal(miniwallet.get_balance(), balance + 1)
|
||||
self.log.debug("Check block should fail now, since it is a duplicate")
|
||||
res = await mining.result.checkBlock(block.serialize(), check_opts)
|
||||
assert_equal(res.result, False)
|
||||
assert_equal(res.reason, "inconclusive-not-best-prevblk")
|
||||
|
||||
self.log.debug("Destroy template objects")
|
||||
template.result.destroy(ctx)
|
||||
template2.result.destroy(ctx)
|
||||
|
|
|
@ -30,6 +30,13 @@ class RpcMiscTest(BitcoinTestFramework):
|
|||
lambda: node.echo(arg9='trigger_internal_bug'),
|
||||
)
|
||||
|
||||
self.log.info("test max arg size")
|
||||
ARG_SZ_COMMON = 131071 # Common limit, used previously in the test framework, serves as a regression test
|
||||
ARG_SZ_LARGE = 8 * 1024 * 1024 # A large size, which should be rare to hit in practice
|
||||
for arg_sz in [0, 1, 100, ARG_SZ_COMMON, ARG_SZ_LARGE]:
|
||||
arg_string = 'a' * arg_sz
|
||||
assert_equal([arg_string, arg_string], node.echo(arg_string, arg_string))
|
||||
|
||||
self.log.info("test getmemoryinfo")
|
||||
memory = node.getmemoryinfo()['locked']
|
||||
assert_greater_than(memory['used'], 0)
|
||||
|
|
|
@ -66,6 +66,7 @@ DUMMY_MIN_OP_RETURN_SCRIPT = CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 1)))
|
|||
assert len(DUMMY_MIN_OP_RETURN_SCRIPT) == MIN_PADDING
|
||||
|
||||
PAY_TO_ANCHOR = CScript([OP_1, bytes.fromhex("4e73")])
|
||||
ANCHOR_ADDRESS = "bcrt1pfeesnyr2tx"
|
||||
|
||||
def key_to_p2pk_script(key):
|
||||
key = check_key(key)
|
||||
|
|
|
@ -48,7 +48,12 @@ BITCOIND_PROC_WAIT_TIMEOUT = 60
|
|||
# The size of the blocks xor key
|
||||
# from InitBlocksdirXorKey::xor_key.size()
|
||||
NUM_XOR_BYTES = 8
|
||||
CLI_MAX_ARG_SIZE = 131071 # many systems have a 128kb limit per arg (MAX_ARG_STRLEN)
|
||||
# Many systems have a 128kB limit for a command size. Depending on the
|
||||
# platform, this limit may be larger or smaller. Moreover, when using the
|
||||
# 'bitcoin' command, it may internally insert more args, which must be
|
||||
# accounted for. There is no need to pick the largest possible value here
|
||||
# anyway and it should be fine to set it to 1kB in tests.
|
||||
TEST_CLI_MAX_ARG_SIZE = 1024
|
||||
|
||||
# The null blocks key (all 0s)
|
||||
NULL_BLK_XOR_KEY = bytes([0] * NUM_XOR_BYTES)
|
||||
|
@ -951,10 +956,14 @@ class TestNodeCLI():
|
|||
if clicommand is not None:
|
||||
p_args += [clicommand]
|
||||
p_args += pos_args + named_args
|
||||
max_arg_size = max(len(arg) for arg in p_args)
|
||||
|
||||
# TEST_CLI_MAX_ARG_SIZE is set low enough that checking the string
|
||||
# length is enough and encoding to bytes is not needed before
|
||||
# calculating the sum.
|
||||
sum_arg_size = sum(len(arg) for arg in p_args)
|
||||
stdin_data = self.input
|
||||
if max_arg_size > CLI_MAX_ARG_SIZE:
|
||||
self.log.debug(f"Cli: Command size {max_arg_size} too large, using stdin")
|
||||
if sum_arg_size >= TEST_CLI_MAX_ARG_SIZE:
|
||||
self.log.debug(f"Cli: Command size {sum_arg_size} too large, using stdin")
|
||||
rpc_args = "\n".join([arg for arg in p_args[base_arg_pos:]])
|
||||
if stdin_data is not None:
|
||||
stdin_data += "\n" + rpc_args
|
||||
|
|
|
@ -151,6 +151,7 @@ BASE_SCRIPTS = [
|
|||
'rpc_orphans.py',
|
||||
'wallet_listreceivedby.py',
|
||||
'wallet_abandonconflict.py',
|
||||
'wallet_anchor.py',
|
||||
'feature_reindex.py',
|
||||
'feature_reindex_readonly.py',
|
||||
'wallet_labels.py',
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2025-present The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import time
|
||||
|
||||
from test_framework.blocktools import MAX_FUTURE_BLOCK_TIME
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.messages import (
|
||||
COutPoint,
|
||||
CTxIn,
|
||||
CTxInWitness,
|
||||
CTxOut,
|
||||
)
|
||||
from test_framework.script_util import (
|
||||
ANCHOR_ADDRESS,
|
||||
PAY_TO_ANCHOR,
|
||||
)
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
from test_framework.wallet import MiniWallet
|
||||
|
||||
class WalletAnchorTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def test_0_value_anchor_listunspent(self):
|
||||
self.log.info("Test that 0-value anchor outputs are detected as UTXOs")
|
||||
|
||||
# Create an anchor output, and spend it
|
||||
sender = MiniWallet(self.nodes[0])
|
||||
anchor_tx = sender.create_self_transfer(fee_rate=0, version=3)["tx"]
|
||||
anchor_tx.vout.append(CTxOut(0, PAY_TO_ANCHOR))
|
||||
anchor_spend = sender.create_self_transfer(version=3)["tx"]
|
||||
anchor_spend.vin.append(CTxIn(COutPoint(anchor_tx.txid_int, 1), b""))
|
||||
anchor_spend.wit.vtxinwit.append(CTxInWitness())
|
||||
submit_res = self.nodes[0].submitpackage([anchor_tx.serialize().hex(), anchor_spend.serialize().hex()])
|
||||
assert_equal(submit_res["package_msg"], "success")
|
||||
anchor_txid = anchor_tx.txid_hex
|
||||
anchor_spend_txid = anchor_spend.txid_hex
|
||||
|
||||
# Mine each tx in separate blocks
|
||||
self.generateblock(self.nodes[0], sender.get_address(), [anchor_tx.serialize().hex()])
|
||||
anchor_tx_height = self.nodes[0].getblockcount()
|
||||
self.generateblock(self.nodes[0], sender.get_address(), [anchor_spend.serialize().hex()])
|
||||
|
||||
# Mock time forward and generate some blocks to avoid rescanning of latest blocks
|
||||
self.nodes[0].setmocktime(int(time.time()) + MAX_FUTURE_BLOCK_TIME + 1)
|
||||
self.generate(self.nodes[0], 10)
|
||||
|
||||
self.nodes[0].createwallet(wallet_name="anchor", disable_private_keys=True)
|
||||
wallet = self.nodes[0].get_wallet_rpc("anchor")
|
||||
import_res = wallet.importdescriptors([{"desc": descsum_create(f"addr({ANCHOR_ADDRESS})"), "timestamp": "now"}])
|
||||
assert_equal(import_res[0]["success"], True)
|
||||
|
||||
# The wallet should have no UTXOs, and not know of the anchor tx or its spend
|
||||
assert_equal(wallet.listunspent(), [])
|
||||
assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", wallet.gettransaction, anchor_txid)
|
||||
assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", wallet.gettransaction, anchor_spend_txid)
|
||||
|
||||
# Rescanning the block containing the anchor so that listunspent will list the output
|
||||
wallet.rescanblockchain(0, anchor_tx_height)
|
||||
utxos = wallet.listunspent()
|
||||
assert_equal(len(utxos), 1)
|
||||
assert_equal(utxos[0]["txid"], anchor_txid)
|
||||
assert_equal(utxos[0]["address"], ANCHOR_ADDRESS)
|
||||
assert_equal(utxos[0]["amount"], 0)
|
||||
wallet.gettransaction(anchor_txid)
|
||||
assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", wallet.gettransaction, anchor_spend_txid)
|
||||
|
||||
# Rescan the rest of the blockchain to see the anchor was spent
|
||||
wallet.rescanblockchain()
|
||||
assert_equal(wallet.listunspent(), [])
|
||||
wallet.gettransaction(anchor_spend_txid)
|
||||
|
||||
def test_cannot_sign_anchors(self):
|
||||
self.log.info("Test that the wallet cannot spend anchor outputs")
|
||||
for disable_privkeys in [False, True]:
|
||||
self.nodes[0].createwallet(wallet_name=f"anchor_spend_{disable_privkeys}", disable_private_keys=disable_privkeys)
|
||||
wallet = self.nodes[0].get_wallet_rpc(f"anchor_spend_{disable_privkeys}")
|
||||
import_res = wallet.importdescriptors([
|
||||
{"desc": descsum_create(f"addr({ANCHOR_ADDRESS})"), "timestamp": "now"},
|
||||
{"desc": descsum_create(f"raw({PAY_TO_ANCHOR.hex()})"), "timestamp": "now"}
|
||||
])
|
||||
assert_equal(import_res[0]["success"], disable_privkeys)
|
||||
assert_equal(import_res[1]["success"], disable_privkeys)
|
||||
|
||||
anchor_txid = self.default_wallet.sendtoaddress(ANCHOR_ADDRESS, 1)
|
||||
self.generate(self.nodes[0], 1)
|
||||
|
||||
wallet = self.nodes[0].get_wallet_rpc("anchor_spend_True")
|
||||
utxos = wallet.listunspent()
|
||||
assert_equal(len(utxos), 1)
|
||||
assert_equal(utxos[0]["txid"], anchor_txid)
|
||||
assert_equal(utxos[0]["address"], ANCHOR_ADDRESS)
|
||||
assert_equal(utxos[0]["amount"], 1)
|
||||
|
||||
assert_raises_rpc_error(-4, "Missing solving data for estimating transaction size", wallet.send, [{self.default_wallet.getnewaddress(): 0.9999}])
|
||||
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", wallet.sendtoaddress, self.default_wallet.getnewaddress(), 0.9999)
|
||||
assert_raises_rpc_error(-4, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors", wallet.sendall, recipients=[self.default_wallet.getnewaddress()], inputs=utxos)
|
||||
assert_raises_rpc_error(-4, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors", wallet.sendall, recipients=[self.default_wallet.getnewaddress()])
|
||||
|
||||
def run_test(self):
|
||||
self.default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
|
||||
self.test_0_value_anchor_listunspent()
|
||||
self.test_cannot_sign_anchors()
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletAnchorTest(__file__).main()
|
|
@ -5,9 +5,12 @@
|
|||
"""Test the listtransactions API."""
|
||||
|
||||
from decimal import Decimal
|
||||
import time
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from test_framework.blocktools import MAX_FUTURE_BLOCK_TIME
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.messages import (
|
||||
COIN,
|
||||
tx_from_hex,
|
||||
|
@ -18,7 +21,9 @@ from test_framework.util import (
|
|||
assert_array_result,
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
find_vout_for_address,
|
||||
)
|
||||
from test_framework.wallet_util import get_generate_key
|
||||
|
||||
|
||||
class ListTransactionsTest(BitcoinTestFramework):
|
||||
|
@ -97,6 +102,7 @@ class ListTransactionsTest(BitcoinTestFramework):
|
|||
self.run_coinjoin_test()
|
||||
self.run_invalid_parameters_test()
|
||||
self.test_op_return()
|
||||
self.test_from_me_status_change()
|
||||
|
||||
def run_rbf_opt_in_test(self):
|
||||
"""Test the opt-in-rbf flag for sent and received transactions."""
|
||||
|
@ -311,6 +317,49 @@ class ListTransactionsTest(BitcoinTestFramework):
|
|||
|
||||
assert 'address' not in op_ret_tx
|
||||
|
||||
def test_from_me_status_change(self):
|
||||
self.log.info("Test gettransaction after changing a transaction's 'from me' status")
|
||||
self.nodes[0].createwallet("fromme")
|
||||
default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
|
||||
wallet = self.nodes[0].get_wallet_rpc("fromme")
|
||||
|
||||
# The 'fee' field of gettransaction is only added when the transaction is 'from me'
|
||||
# Run twice, once for a transaction in the mempool, again when it confirms
|
||||
for confirm in [False, True]:
|
||||
key = get_generate_key()
|
||||
descriptor = descsum_create(f"wpkh({key.privkey})")
|
||||
default_wallet.importdescriptors([{"desc": descriptor, "timestamp": "now"}])
|
||||
|
||||
send_res = default_wallet.send(outputs=[{key.p2wpkh_addr: 1}, {wallet.getnewaddress(): 1}])
|
||||
assert_equal(send_res["complete"], True)
|
||||
vout = find_vout_for_address(self.nodes[0], send_res["txid"], key.p2wpkh_addr)
|
||||
utxos = [{"txid": send_res["txid"], "vout": vout}]
|
||||
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
|
||||
|
||||
# Send to the test wallet, ensuring that one input is for the descriptor we will import,
|
||||
# and that there are other inputs belonging to only the sending wallet
|
||||
send_res = default_wallet.send(outputs=[{wallet.getnewaddress(): 1.5}], inputs=utxos, add_inputs=True)
|
||||
assert_equal(send_res["complete"], True)
|
||||
txid = send_res["txid"]
|
||||
self.nodes[0].syncwithvalidationinterfacequeue()
|
||||
tx_info = wallet.gettransaction(txid)
|
||||
assert "fee" not in tx_info
|
||||
assert_equal(any(detail["category"] == "send" for detail in tx_info["details"]), False)
|
||||
|
||||
if confirm:
|
||||
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
|
||||
# Mock time forward and generate blocks so that the import does not rescan the transaction
|
||||
self.nodes[0].setmocktime(int(time.time()) + MAX_FUTURE_BLOCK_TIME + 1)
|
||||
self.generate(self.nodes[0], 10, sync_fun=self.no_op)
|
||||
|
||||
import_res = wallet.importdescriptors([{"desc": descriptor, "timestamp": "now"}])
|
||||
assert_equal(import_res[0]["success"], True)
|
||||
# TODO: We should check that the fee matches, but since the transaction spends inputs
|
||||
# not known to the wallet, it is incorrectly calculating the fee.
|
||||
# assert_equal(wallet.gettransaction(txid)["fee"], fee)
|
||||
tx_info = wallet.gettransaction(txid)
|
||||
assert "fee" in tx_info
|
||||
assert_equal(any(detail["category"] == "send" for detail in tx_info["details"]), True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
ListTransactionsTest(__file__).main()
|
||||
|
|
Loading…
Reference in New Issue