Merge #6234: backport: bitcoin#21178, #22089, #22130, #22210, #22216, bitcoin-core/gui#361, partial: bitcoin#14123

be5c84f41d Merge bitcoin/bitcoin#22089: test: MiniWallet: fix fee calculation for P2PK and check tx vsize (MarcoFalke)
247141d32c Merge bitcoin/bitcoin#22210: test: Use MiniWallet in test_no_inherited_signaling RBF test (MarcoFalke)
dad3ae3f33 Merge bitcoin/bitcoin#22130: test: refactor: dedup utility function chain_transaction() (MarcoFalke)
9dff334f47 Merge bitcoin-core/gui#361: Fix gui segfault caused by bitcoin/bitcoin#22216 (Hennadii Stepanov)
1087849955 Merge bitcoin/bitcoin#22216: refactor: Make SetupServerArgs callable without NodeContext (MarcoFalke)
fd94de6888 Merge bitcoin/bitcoin#21178: test: run mempool_reorg.py even with wallet disabled (MarcoFalke)
f1f5723fcf fix: missing changes from bitcoin#14123 (Konstantin Akimov)

Pull request description:

  ## Issue being fixed or feature implemented
  Regular backports from bitcoin v22

  ## What was done?
  See commits for list of backported changes. It also have some missing changes from bitcoin#14123

  ## How Has This Been Tested?
  Run unit/functional tests

  ## Breaking Changes
  N/A

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [ ] I have commented my code, particularly in hard-to-understand areas
  - [ ] I have added or updated relevant unit/integration/functional/e2e tests
  - [ ] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone

ACKs for top commit:
  UdjinM6:
    utACK be5c84f41d
  PastaPastaPasta:
    utACK be5c84f41d

Tree-SHA512: 553ffde63c8409799cf6b3b87bf1ee285fbf58b13c08d04cdac29bc0e4dd75059feaa2f163803084ae85175397512517b68a6e0e0cc602d981f38ac70d96e393
This commit is contained in:
pasta 2024-09-03 09:20:41 -05:00
commit a83f76b0d5
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
13 changed files with 143 additions and 137 deletions

View File

@ -117,8 +117,8 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
util::ThreadSetInternalName("init"); util::ThreadSetInternalName("init");
// If Qt is used, parameters/dash.conf are parsed in qt/bitcoin.cpp's main() // If Qt is used, parameters/dash.conf are parsed in qt/bitcoin.cpp's main()
SetupServerArgs(node);
ArgsManager& args = *Assert(node.args); ArgsManager& args = *Assert(node.args);
SetupServerArgs(args);
std::string error; std::string error;
if (!args.ParseParameters(argc, argv, error)) { if (!args.ParseParameters(argc, argv, error)) {
return InitError(Untranslated(strprintf("Error parsing command line arguments: %s\n", error))); return InitError(Untranslated(strprintf("Error parsing command line arguments: %s\n", error)));

View File

@ -483,12 +483,8 @@ std::string GetSupportedSocketEventsStr()
return strSupportedModes; return strSupportedModes;
} }
void SetupServerArgs(NodeContext& node) void SetupServerArgs(ArgsManager& argsman)
{ {
assert(!node.args);
node.args = &gArgs;
ArgsManager& argsman = *node.args;
SetupHelpOptions(argsman); SetupHelpOptions(argsman);
argsman.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-help-debug", "Print help message with debugging options and exit", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);

View File

@ -68,7 +68,7 @@ void PrepareShutdown(NodeContext& node);
/** /**
* Register all arguments with the ArgsManager * Register all arguments with the ArgsManager
*/ */
void SetupServerArgs(NodeContext& node); void SetupServerArgs(ArgsManager& argsman);
/** Returns licensing information (for -version) */ /** Returns licensing information (for -version) */
std::string LicenseInfo(); std::string LicenseInfo();

View File

@ -6,6 +6,7 @@
#include <interfaces/init.h> #include <interfaces/init.h>
#include <interfaces/ipc.h> #include <interfaces/ipc.h>
#include <node/context.h> #include <node/context.h>
#include <util/system.h>
#include <memory> #include <memory>
@ -20,6 +21,7 @@ public:
: m_node(node), : m_node(node),
m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this)) m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this))
{ {
m_node.args = &gArgs;
m_node.init = this; m_node.init = this;
} }
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); } std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }

View File

@ -4,6 +4,7 @@
#include <interfaces/init.h> #include <interfaces/init.h>
#include <node/context.h> #include <node/context.h>
#include <util/system.h>
#include <memory> #include <memory>
@ -14,6 +15,7 @@ class BitcoindInit : public interfaces::Init
public: public:
BitcoindInit(NodeContext& node) : m_node(node) BitcoindInit(NodeContext& node) : m_node(node)
{ {
m_node.args = &gArgs;
m_node.init = this; m_node.init = this;
} }
NodeContext& m_node; NodeContext& m_node;

View File

@ -581,7 +581,8 @@ int GuiMain(int argc, char* argv[])
/// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these
// Command-line options take precedence: // Command-line options take precedence:
SetupServerArgs(node_context); node_context.args = &gArgs;
SetupServerArgs(gArgs);
SetupUIArgs(gArgs); SetupUIArgs(gArgs);
std::string error; std::string error;
if (!gArgs.ParseParameters(argc, argv, error)) { if (!gArgs.ParseParameters(argc, argv, error)) {

View File

@ -369,9 +369,9 @@ void BitcoinGUI::createActions()
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
// These showNormalIfMinimized are needed because Send Coins and Receive Coins // These showNormalIfMinimized are needed because Send Coins and Receive Coins
// can be triggered from the tray menu, and need to show the GUI to be useful. // can be triggered from the tray menu, and need to show the GUI to be useful.
connect(sendCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); connect(sendCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(coinJoinCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); connect(coinJoinCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(receiveCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); connect(receiveCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(sendCoinsMenuAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); });
connect(coinJoinCoinsMenuAction, &QAction::triggered, [this]{ gotoCoinJoinCoinsPage(); }); connect(coinJoinCoinsMenuAction, &QAction::triggered, [this]{ gotoCoinJoinCoinsPage(); });
connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage);
@ -506,11 +506,11 @@ void BitcoinGUI::createActions()
connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase);
connect(unlockWalletAction, &QAction::triggered, walletFrame, &WalletFrame::unlockWallet); connect(unlockWalletAction, &QAction::triggered, walletFrame, &WalletFrame::unlockWallet);
connect(lockWalletAction, &QAction::triggered, walletFrame, &WalletFrame::lockWallet); connect(lockWalletAction, &QAction::triggered, walletFrame, &WalletFrame::lockWallet);
connect(signMessageAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); }); connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); });
connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); }); connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); });
connect(m_load_psbt_clipboard_action, &QAction::triggered, [this]{ gotoLoadPSBT(true); }); connect(m_load_psbt_clipboard_action, &QAction::triggered, [this]{ gotoLoadPSBT(true); });
connect(verifyMessageAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); });
connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses);
connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses);
@ -730,7 +730,7 @@ void BitcoinGUI::createToolBars()
connect(historyButton, &QToolButton::clicked, this, &BitcoinGUI::gotoHistoryPage); connect(historyButton, &QToolButton::clicked, this, &BitcoinGUI::gotoHistoryPage);
// Give the selected tab button a bolder font. // Give the selected tab button a bolder font.
connect(tabGroup, static_cast<void (QButtonGroup::*)(QAbstractButton *, bool)>(&QButtonGroup::buttonToggled), this, &BitcoinGUI::highlightTabButton); connect(tabGroup, qOverload<QAbstractButton *, bool>(&QButtonGroup::buttonToggled), this, &BitcoinGUI::highlightTabButton);
for (auto button : tabGroup->buttons()) { for (auto button : tabGroup->buttons()) {
GUIUtil::setFont({button}, GUIUtil::FontWeight::Normal, 16); GUIUtil::setFont({button}, GUIUtil::FontWeight::Normal, 16);
@ -799,7 +799,7 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH
// Note: ignore this on Mac - this is not the way tray should work there // Note: ignore this on Mac - this is not the way tray should work there
connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated); connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated);
#else #else
// Note: On Mac, the dock icon is also used to provide menu functionality // Note: on macOS, the Dock icon is also used to provide menu functionality
// similar to one for tray icon // similar to one for tray icon
MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated); connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated);
@ -1019,7 +1019,7 @@ void BitcoinGUI::createIconMenu(QMenu *pmenu)
{ {
// Configuration of the tray icon (or dock icon) icon menu // Configuration of the tray icon (or dock icon) icon menu
#ifndef Q_OS_MAC #ifndef Q_OS_MAC
// Note: On Mac, the dock icon's menu already has show / hide action. // Note: On macOS, the Dock icon is used to provide the tray's functionality.
trayIconMenu->addAction(toggleHideAction); trayIconMenu->addAction(toggleHideAction);
trayIconMenu->addSeparator(); trayIconMenu->addSeparator();
#endif #endif
@ -1045,7 +1045,7 @@ void BitcoinGUI::createIconMenu(QMenu *pmenu)
if (enableWallet) { if (enableWallet) {
pmenu->addAction(showBackupsAction); pmenu->addAction(showBackupsAction);
} }
#ifndef Q_OS_MAC // This is built-in on Mac #ifndef Q_OS_MAC // This is built-in on macOS
pmenu->addSeparator(); pmenu->addSeparator();
pmenu->addAction(quitAction); pmenu->addAction(quitAction);
#endif #endif
@ -1617,7 +1617,7 @@ void BitcoinGUI::message(const QString& title, QString message, unsigned int sty
void BitcoinGUI::changeEvent(QEvent *e) void BitcoinGUI::changeEvent(QEvent *e)
{ {
QMainWindow::changeEvent(e); QMainWindow::changeEvent(e);
#ifndef Q_OS_MAC // Ignored on Mac #ifndef Q_OS_MAC // Ignored on macOS
if(e->type() == QEvent::WindowStateChange) if(e->type() == QEvent::WindowStateChange)
{ {
if(clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray()) if(clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray())
@ -1649,7 +1649,7 @@ void BitcoinGUI::changeEvent(QEvent *e)
void BitcoinGUI::closeEvent(QCloseEvent *event) void BitcoinGUI::closeEvent(QCloseEvent *event)
{ {
#ifndef Q_OS_MAC // Ignored on Mac #ifndef Q_OS_MAC // Ignored on macOS
if(clientModel && clientModel->getOptionsModel()) if(clientModel && clientModel->getOptionsModel())
{ {
if(!clientModel->getOptionsModel()->getMinimizeOnClose()) if(!clientModel->getOptionsModel()->getMinimizeOnClose())

View File

@ -139,6 +139,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve
: m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()}, : m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / g_insecure_rand_ctx_temp_path.rand256().ToString()},
m_args{} m_args{}
{ {
m_node.args = &gArgs;
const std::vector<const char*> arguments = Cat( const std::vector<const char*> arguments = Cat(
{ {
"dummy", "dummy",
@ -157,7 +158,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve
gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root)); gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root));
gArgs.ClearPathCache(); gArgs.ClearPathCache();
{ {
SetupServerArgs(m_node); SetupServerArgs(*m_node.args);
std::string error; std::string error;
const bool success{m_node.args->ParseParameters(arguments.size(), arguments.data(), error)}; const bool success{m_node.args->ParseParameters(arguments.size(), arguments.data(), error)};
assert(success); assert(success);

View File

@ -11,7 +11,11 @@ from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
chain_transaction,
)
MAX_ANCESTORS = 25 MAX_ANCESTORS = 25
MAX_DESCENDANTS = 25 MAX_DESCENDANTS = 25
@ -24,23 +28,6 @@ class MempoolPackagesTest(BitcoinTestFramework):
def skip_test_if_missing_module(self): def skip_test_if_missing_module(self):
self.skip_if_no_wallet() self.skip_if_no_wallet()
# Build a transaction that spends parent_txid:vout
# Return amount sent
def chain_transaction(self, node, parent_txids, vouts, value, fee, num_outputs):
send_value = satoshi_round((value - fee)/num_outputs)
inputs = []
for (txid, vout) in zip(parent_txids, vouts):
inputs.append({'txid' : txid, 'vout' : vout})
outputs = {}
for _ in range(num_outputs):
outputs[node.getnewaddress()] = send_value
rawtx = node.createrawtransaction(inputs, outputs)
signedtx = node.signrawtransactionwithwallet(rawtx)
txid = node.sendrawtransaction(signedtx['hex'])
fulltx = node.getrawtransaction(txid, 1)
assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output
return (txid, send_value)
def run_test(self): def run_test(self):
# Mine some blocks and have them mature. # Mine some blocks and have them mature.
self.nodes[0].generate(COINBASE_MATURITY + 1) self.nodes[0].generate(COINBASE_MATURITY + 1)
@ -53,32 +40,32 @@ class MempoolPackagesTest(BitcoinTestFramework):
# MAX_ANCESTORS transactions off a confirmed tx should be fine # MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = [] chain = []
for _ in range(4): for _ in range(4):
(txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2) (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2)
vout = 0 vout = 0
value = sent_value value = sent_value
chain.append([txid, value]) chain.append([txid, value])
for _ in range(MAX_ANCESTORS - 4): for _ in range(MAX_ANCESTORS - 4):
(txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1)
value = sent_value value = sent_value
chain.append([txid, value]) chain.append([txid, value])
(second_chain, second_chain_value) = self.chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1) (second_chain, second_chain_value) = chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1)
# Check mempool has MAX_ANCESTORS + 1 transactions in it # Check mempool has MAX_ANCESTORS + 1 transactions in it
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1) assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1)
# Adding one more transaction on to the chain should fail. # Adding one more transaction on to the chain should fail.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", chain_transaction, self.nodes[0], [txid], [0], value, fee, 1)
# ...even if it chains on from some point in the middle of the chain. # ...even if it chains on from some point in the middle of the chain.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1)
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1)
# ...even if it chains on to two parent transactions with one in the chain. # ...even if it chains on to two parent transactions with one in the chain.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1)
# ...especially if its > 40k weight # ...especially if its > 40k weight
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350)
# But not if it chains directly off the first transaction # But not if it chains directly off the first transaction
self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1)
# and the second chain should work just fine # and the second chain should work just fine
self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1)
# Finally, check that we added two transactions # Finally, check that we added two transactions
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3) assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3)

View File

@ -13,6 +13,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
assert_equal, assert_equal,
assert_raises_rpc_error, assert_raises_rpc_error,
chain_transaction,
satoshi_round, satoshi_round,
) )
@ -42,21 +43,6 @@ class MempoolPackagesTest(BitcoinTestFramework):
def skip_test_if_missing_module(self): def skip_test_if_missing_module(self):
self.skip_if_no_wallet() self.skip_if_no_wallet()
# Build a transaction that spends parent_txid:vout
# Return amount sent
def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs):
send_value = satoshi_round((value - fee)/num_outputs)
inputs = [ {'txid' : parent_txid, 'vout' : vout} ]
outputs = {}
for _ in range(num_outputs):
outputs[node.getnewaddress()] = send_value
rawtx = node.createrawtransaction(inputs, outputs)
signedtx = node.signrawtransactionwithwallet(rawtx)
txid = node.sendrawtransaction(signedtx['hex'])
fulltx = node.getrawtransaction(txid, 1)
assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output
return (txid, send_value)
def run_test(self): def run_test(self):
# Mine some blocks and have them mature. # Mine some blocks and have them mature.
peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs
@ -70,7 +56,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
# MAX_ANCESTORS transactions off a confirmed tx should be fine # MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = [] chain = []
for _ in range(MAX_ANCESTORS): for _ in range(MAX_ANCESTORS):
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1) (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1)
value = sent_value value = sent_value
chain.append(txid) chain.append(txid)
@ -184,7 +170,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
assert_equal(entry['descendantfees'], descendant_fees * COIN + 1000) assert_equal(entry['descendantfees'], descendant_fees * COIN + 1000)
# Adding one more transaction on to the chain should fail. # Adding one more transaction on to the chain should fail.
assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], txid, vout, value, fee, 1) assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [txid], [vout], value, fee, 1)
# Check that prioritising a tx before it's added to the mempool works # Check that prioritising a tx before it's added to the mempool works
# First clear the mempool by mining a block. # First clear the mempool by mining a block.
@ -232,7 +218,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
transaction_package = [] transaction_package = []
tx_children = [] tx_children = []
# First create one parent tx with 10 children # First create one parent tx with 10 children
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 10) (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 10)
parent_transaction = txid parent_transaction = txid
for i in range(10): for i in range(10):
transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value}) transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value})
@ -241,7 +227,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
chain = [] # save sent txs for the purpose of checking node1's mempool later (see below) chain = [] # save sent txs for the purpose of checking node1's mempool later (see below)
for _ in range(MAX_DESCENDANTS - 1): for _ in range(MAX_DESCENDANTS - 1):
utxo = transaction_package.pop(0) utxo = transaction_package.pop(0)
(txid, sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) (txid, sent_value) = chain_transaction(self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10)
chain.append(txid) chain.append(txid)
if utxo['txid'] is parent_transaction: if utxo['txid'] is parent_transaction:
tx_children.append(txid) tx_children.append(txid)
@ -257,7 +243,7 @@ class MempoolPackagesTest(BitcoinTestFramework):
# Sending one more chained transaction will fail # Sending one more chained transaction will fail
utxo = transaction_package.pop(0) utxo = transaction_package.pop(0)
assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10)
# Check that node1's mempool is as expected, containing: # Check that node1's mempool is as expected, containing:
# - txs from previous ancestor test (-> custom ancestor limit) # - txs from previous ancestor test (-> custom ancestor limit)
@ -315,13 +301,13 @@ class MempoolPackagesTest(BitcoinTestFramework):
value = send_value value = send_value
# Create tx1 # Create tx1
tx1_id, _ = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1) tx1_id, _ = chain_transaction(self.nodes[0], [tx0_id], [0], value, fee, 1)
# Create tx2-7 # Create tx2-7
vout = 1 vout = 1
txid = tx0_id txid = tx0_id
for _ in range(6): for _ in range(6):
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1) (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 1)
vout = 0 vout = 0
value = sent_value value = sent_value

View File

@ -8,13 +8,9 @@ Test re-org scenarios with a mempool that contains transactions
that spend (directly or indirectly) coinbase transactions. that spend (directly or indirectly) coinbase transactions.
""" """
from test_framework.blocktools import (
COINBASE_MATURITY,
create_raw_transaction,
)
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.util import assert_equal, assert_raises_rpc_error
from test_framework.wallet import MiniWallet
class MempoolCoinbaseTest(BitcoinTestFramework): class MempoolCoinbaseTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
@ -26,86 +22,90 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
[] []
] ]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self): def run_test(self):
wallet = MiniWallet(self.nodes[0])
# Start with a 200 block chain # Start with a 200 block chain
assert_equal(self.nodes[0].getblockcount(), 200) assert_equal(self.nodes[0].getblockcount(), 200)
# Mine four blocks. After this, nodes[0] blocks self.log.info("Add 4 coinbase utxos to the miniwallet")
# 101, 102, and 103 are spend-able. # Block 76 contains the first spendable coinbase txs.
new_blocks = self.nodes[1].generate(4) first_block = 76
self.sync_all() wallet.scan_blocks(start=first_block, num=4)
node0_address = self.nodes[0].getnewaddress()
node1_address = self.nodes[1].getnewaddress()
# Three scenarios for re-orging coinbase spends in the memory pool: # Three scenarios for re-orging coinbase spends in the memory pool:
# 1. Direct coinbase spend : spend_101 # 1. Direct coinbase spend : spend_1
# 2. Indirect (coinbase spend in chain, child in mempool) : spend_102 and spend_102_1 # 2. Indirect (coinbase spend in chain, child in mempool) : spend_2 and spend_2_1
# 3. Indirect (coinbase and child both in chain) : spend_103 and spend_103_1 # 3. Indirect (coinbase and child both in chain) : spend_3 and spend_3_1
# Use invalidatblock to make all of the above coinbase spends invalid (immature coinbase), # Use invalidateblock to make all of the above coinbase spends invalid (immature coinbase),
# and make sure the mempool code behaves correctly. # and make sure the mempool code behaves correctly.
b = [self.nodes[0].getblockhash(n) for n in range(COINBASE_MATURITY + 1, COINBASE_MATURITY + 5)] b = [self.nodes[0].getblockhash(n) for n in range(first_block, first_block+4)]
coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b]
spend_101_raw = create_raw_transaction(self.nodes[0], coinbase_txids[1], node1_address, amount=499.99) utxo_1 = wallet.get_utxo(txid=coinbase_txids[1])
spend_102_raw = create_raw_transaction(self.nodes[0], coinbase_txids[2], node0_address, amount=499.99) utxo_2 = wallet.get_utxo(txid=coinbase_txids[2])
spend_103_raw = create_raw_transaction(self.nodes[0], coinbase_txids[3], node0_address, amount=499.99) utxo_3 = wallet.get_utxo(txid=coinbase_txids[3])
self.log.info("Create three transactions spending from coinbase utxos: spend_1, spend_2, spend_3")
spend_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_1)
spend_2 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_2)
spend_3 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_3)
# Create a transaction which is time-locked to two blocks in the future self.log.info("Create another transaction which is time-locked to two blocks in the future")
timelock_tx = self.nodes[0].createrawtransaction( utxo = wallet.get_utxo(txid=coinbase_txids[0])
inputs=[{ timelock_tx = wallet.create_self_transfer(
"txid": coinbase_txids[0], from_node=self.nodes[0],
"vout": 0, utxo_to_spend=utxo,
}], mempool_valid=False,
outputs={node0_address: 499.99}, locktime=self.nodes[0].getblockcount() + 2
locktime=self.nodes[0].getblockcount() + 2, )['hex']
)
timelock_tx = self.nodes[0].signrawtransactionwithwallet(timelock_tx)["hex"] self.log.info("Check that the time-locked transaction is too immature to spend")
# This will raise an exception because the timelock transaction is too immature to spend
assert_raises_rpc_error(-26, "non-final", self.nodes[0].sendrawtransaction, timelock_tx) assert_raises_rpc_error(-26, "non-final", self.nodes[0].sendrawtransaction, timelock_tx)
# Broadcast and mine spend_102 and 103: self.log.info("Broadcast and mine spend_2 and spend_3")
spend_102_id = self.nodes[0].sendrawtransaction(spend_102_raw) wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_2['hex'])
spend_103_id = self.nodes[0].sendrawtransaction(spend_103_raw) wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_3['hex'])
self.log.info("Generate a block")
self.nodes[0].generate(1) self.nodes[0].generate(1)
# Time-locked transaction is still too immature to spend self.log.info("Check that time-locked transaction is still too immature to spend")
assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx) assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx)
# Create 102_1 and 103_1: self.log.info("Create spend_2_1 and spend_3_1")
spend_102_1_raw = create_raw_transaction(self.nodes[0], spend_102_id, node1_address, amount=499.98) spend_2_utxo = wallet.get_utxo(txid=spend_2['txid'])
spend_103_1_raw = create_raw_transaction(self.nodes[0], spend_103_id, node1_address, amount=499.98) spend_2_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_2_utxo)
spend_3_utxo = wallet.get_utxo(txid=spend_3['txid'])
spend_3_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_3_utxo)
# Broadcast and mine 103_1: self.log.info("Broadcast and mine spend_3_1")
spend_103_1_id = self.nodes[0].sendrawtransaction(spend_103_1_raw) spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex'])
self.log.info("Generate a block")
last_block = self.nodes[0].generate(1) last_block = self.nodes[0].generate(1)
# Sync blocks, so that peer 1 gets the block before timelock_tx # Sync blocks, so that peer 1 gets the block before timelock_tx
# Otherwise, peer 1 would put the timelock_tx in m_recent_rejects # Otherwise, peer 1 would put the timelock_tx in m_recent_rejects
self.sync_all() self.sync_all()
# Time-locked transaction can now be spent self.log.info("The time-locked transaction can now be spent")
timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx) timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx)
# ... now put spend_101 and spend_102_1 in memory pools: self.log.info("Add spend_1 and spend_2_1 to the mempool")
spend_101_id = self.nodes[0].sendrawtransaction(spend_101_raw) spend_1_id = self.nodes[0].sendrawtransaction(spend_1['hex'])
spend_102_1_id = self.nodes[0].sendrawtransaction(spend_102_1_raw) spend_2_1_id = self.nodes[0].sendrawtransaction(spend_2_1['hex'])
assert_equal(set(self.nodes[0].getrawmempool()), {spend_101_id, spend_102_1_id, timelock_tx_id}) assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, timelock_tx_id})
self.sync_all() self.sync_all()
self.log.info("invalidate the last block")
for node in self.nodes: for node in self.nodes:
node.invalidateblock(last_block[0]) node.invalidateblock(last_block[0])
# Time-locked transaction is now too immature and has been removed from the mempool self.log.info("The time-locked transaction is now too immature and has been removed from the mempool")
# spend_103_1 has been re-orged out of the chain and is back in the mempool self.log.info("spend_3_1 has been re-orged out of the chain and is back in the mempool")
assert_equal(set(self.nodes[0].getrawmempool()), {spend_101_id, spend_102_1_id, spend_103_1_id}) assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, spend_3_1_id})
# Use invalidateblock to re-org back and make all those coinbase spends self.log.info("Use invalidateblock to re-org back and make all those coinbase spends immature/invalid")
# immature/invalid: b = self.nodes[0].getblockhash(first_block + 100)
for node in self.nodes: for node in self.nodes:
node.invalidateblock(new_blocks[0]) node.invalidateblock(b)
# mempool should be empty. self.log.info("Check that the mempool is empty")
assert_equal(set(self.nodes[0].getrawmempool()), set()) assert_equal(set(self.nodes[0].getrawmempool()), set())
self.sync_all() self.sync_all()

View File

@ -539,6 +539,28 @@ def create_confirmed_utxos(fee, node, count):
return utxos return utxos
def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs):
"""Build and send a transaction that spends the given inputs (specified
by lists of parent_txid:vout each), with the desired total value and fee,
equally divided up to the desired number of outputs.
Returns a tuple with the txid and the amount sent per output.
"""
send_value = satoshi_round((value - fee)/num_outputs)
inputs = []
for (txid, vout) in zip(parent_txids, vouts):
inputs.append({'txid' : txid, 'vout' : vout})
outputs = {}
for _ in range(num_outputs):
outputs[node.getnewaddress()] = send_value
rawtx = node.createrawtransaction(inputs, outputs)
signedtx = node.signrawtransactionwithwallet(rawtx)
txid = node.sendrawtransaction(signedtx['hex'])
fulltx = node.getrawtransaction(txid, 1)
assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output
return (txid, send_value)
# Create large OP_RETURN txouts that can be appended to a transaction # Create large OP_RETURN txouts that can be appended to a transaction
# to make it large (helper for constructing large transactions). # to make it large (helper for constructing large transactions).
def gen_return_txouts(): def gen_return_txouts():

View File

@ -91,12 +91,20 @@ class MiniWallet:
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex(): if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value']}) self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value']})
def sign_tx(self, tx): def sign_tx(self, tx, fixed_length=True):
"""Sign tx that has been created by MiniWallet in P2PK mode""" """Sign tx that has been created by MiniWallet in P2PK mode"""
assert self._priv_key is not None assert self._priv_key is not None
(sighash, err) = SignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) (sighash, err) = SignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL)
assert err is None assert err is None
tx.vin[0].scriptSig = CScript([self._priv_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))]) # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability):
# 65 bytes: high-R val (33 bytes) + low-S val (32 bytes)
# with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes
der_sig = b''
while not len(der_sig) == 71:
der_sig = self._priv_key.sign_ecdsa(sighash)
if not fixed_length:
break
tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))])
def generate(self, num_blocks): def generate(self, num_blocks):
"""Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list""" """Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list"""
@ -127,24 +135,28 @@ class MiniWallet:
else: else:
return self._utxos[index] return self._utxos[index]
def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None): def send_self_transfer(self, **kwargs):
"""Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
tx = self.create_self_transfer(fee_rate=fee_rate, from_node=from_node, utxo_to_spend=utxo_to_spend) tx = self.create_self_transfer(**kwargs)
self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex']) self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex'])
return tx return tx
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True): def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0):
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
self._utxos = sorted(self._utxos, key=lambda k: k['value']) self._utxos = sorted(self._utxos, key=lambda k: k['value'])
utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee
vsize = Decimal(85) if self._priv_key is None:
vsize = Decimal(85) # anyone-can-spend
else:
vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
send_value = satoshi_round(utxo_to_spend['value'] - fee_rate * (vsize / 1000)) send_value = satoshi_round(utxo_to_spend['value'] - fee_rate * (vsize / 1000))
fee = utxo_to_spend['value'] - send_value fee = utxo_to_spend['value'] - send_value
assert send_value > 0 assert send_value > 0
tx = CTransaction() tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']))] tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)] tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)]
tx.nLockTime = locktime
if not self._address: if not self._address:
# raw script # raw script
if self._priv_key is not None: if self._priv_key is not None:
@ -160,10 +172,7 @@ class MiniWallet:
tx_info = from_node.testmempoolaccept([tx_hex])[0] tx_info = from_node.testmempoolaccept([tx_hex])[0]
assert_equal(mempool_valid, tx_info['allowed']) assert_equal(mempool_valid, tx_info['allowed'])
if mempool_valid: if mempool_valid:
# TODO: for P2PK, vsize is not constant due to varying scriptSig length, assert_equal(len(tx_hex) // 2, vsize) # 1 byte = 2 character
# so only check this for anyone-can-spend outputs right now
if self._priv_key is None:
assert_equal(len(tx_hex) // 2, vsize) # 1 byte = 2 character
assert_equal(tx_info['fees']['base'], fee) assert_equal(tx_info['fees']['base'], fee)
return {'txid': tx_info['txid'], 'hex': tx_hex, 'tx': tx} return {'txid': tx_info['txid'], 'hex': tx_hex, 'tx': tx}