aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml4
-rw-r--r--src/qt/forms/optionsdialog.ui20
-rw-r--r--src/qt/guiutil.cpp14
-rw-r--r--src/qt/optionsdialog.cpp2
-rw-r--r--src/qt/optionsmodel.cpp15
-rw-r--r--src/qt/optionsmodel.h1
-rw-r--r--src/qt/rpcconsole.cpp14
-rw-r--r--src/qt/transactionview.cpp12
-rwxr-xr-xtest/functional/feature_rbf.py18
-rwxr-xr-xtest/functional/feature_segwit.py4
-rwxr-xr-xtest/functional/p2p_filter.py34
-rwxr-xr-xtest/functional/test_framework/script_util.py57
-rw-r--r--test/functional/test_framework/wallet.py28
13 files changed, 162 insertions, 61 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index b1d27540ed..48747ac138 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -150,8 +150,8 @@ task:
# See: https://docs.microsoft.com/en-us/biztalk/technical-guides/settings-that-can-be-modified-to-improve-network-performance
- netsh int ipv4 set dynamicport tcp start=1025 num=64511
- netsh int ipv6 set dynamicport tcp start=1025 num=64511
- # TODO enable '--extended'.
- - python test\functional\test_runner.py --nocleanup --ci --quiet --combinedlogslen=4000 --jobs=4 --timeout-factor=8 --failfast
+ # Exclude feature_dbcrash for now due to timeout
+ - python test\functional\test_runner.py --nocleanup --ci --quiet --combinedlogslen=4000 --jobs=4 --timeout-factor=8 --failfast --extended --exclude feature_dbcrash
task:
name: 'ARM [unit tests, no functional tests] [bullseye]'
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui
index 59d220636d..1c22124616 100644
--- a/src/qt/forms/optionsdialog.ui
+++ b/src/qt/forms/optionsdialog.ui
@@ -33,7 +33,7 @@
<string>Automatically start %1 after logging in to the system.</string>
</property>
<property name="text">
- <string>&amp;Start %1 on system login</string>
+ <string>Start %1 on system &amp;login</string>
</property>
</widget>
</item>
@@ -179,7 +179,7 @@
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
- <height>20</height>
+ <height>40</height>
</size>
</property>
</spacer>
@@ -187,6 +187,16 @@
</layout>
</item>
<item>
+ <widget class="QCheckBox" name="enableServer">
+ <property name="toolTip">
+ <string extracomment="Tooltip text for Options window setting that enables the RPC server.">This allows you or a third party tool to communicate with the node through command-line and JSON-RPC commands.</string>
+ </property>
+ <property name="text">
+ <string extracomment="An Options window setting to enable the RPC server.">Enable RPC &amp;server</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer_Main">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -729,10 +739,10 @@
<item>
<widget class="QLabel" name="thirdPartyTxUrlsLabel">
<property name="toolTip">
- <string>Third party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |.</string>
+ <string>Third-party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |.</string>
</property>
<property name="text">
- <string>&amp;Third party transaction URLs</string>
+ <string>&amp;Third-party transaction URLs</string>
</property>
<property name="buddy">
<cstring>thirdPartyTxUrls</cstring>
@@ -742,7 +752,7 @@
<item>
<widget class="QLineEdit" name="thirdPartyTxUrls">
<property name="toolTip">
- <string>Third party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |.</string>
+ <string>Third-party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |.</string>
</property>
<property name="placeholderText">
<string notr="true">https://example.com/tx/%s</string>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index e98e50ba14..91f2591a31 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -673,14 +673,26 @@ QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction
{
QString prefix;
if (prepend_direction) {
- prefix = (conn_type == ConnectionType::INBOUND) ? QObject::tr("Inbound") : QObject::tr("Outbound") + " ";
+ prefix = (conn_type == ConnectionType::INBOUND) ?
+ /*: An inbound connection from a peer. An inbound connection
+ is a connection initiated by a peer. */
+ QObject::tr("Inbound") :
+ /*: An outbound connection to a peer. An outbound connection
+ is a connection initiated by us. */
+ QObject::tr("Outbound") + " ";
}
switch (conn_type) {
case ConnectionType::INBOUND: return prefix;
+ //: Peer connection type that relays all network information.
case ConnectionType::OUTBOUND_FULL_RELAY: return prefix + QObject::tr("Full Relay");
+ /*: Peer connection type that relays network information about
+ blocks and not transactions or addresses. */
case ConnectionType::BLOCK_RELAY: return prefix + QObject::tr("Block Relay");
+ //: Peer connection type established manually through one of several methods.
case ConnectionType::MANUAL: return prefix + QObject::tr("Manual");
+ //: Short-lived peer connection type that tests the aliveness of known addresses.
case ConnectionType::FEELER: return prefix + QObject::tr("Feeler");
+ //: Short-lived peer connection type that solicits known addresses from a peer.
case ConnectionType::ADDR_FETCH: return prefix + QObject::tr("Address Fetch");
} // no default case, so the compiler can warn about missing cases
assert(false);
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 92644ef24b..ac50e6518f 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -210,6 +210,7 @@ void OptionsDialog::setModel(OptionsModel *_model)
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
/* Network */
connect(ui->allowIncoming, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
+ connect(ui->enableServer, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
connect(ui->connectSocks, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
connect(ui->connectSocksTor, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
/* Display */
@@ -246,6 +247,7 @@ void OptionsDialog::setMapper()
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);
mapper->addMapping(ui->mapPortNatpmp, OptionsModel::MapPortNatpmp);
mapper->addMapping(ui->allowIncoming, OptionsModel::Listen);
+ mapper->addMapping(ui->enableServer, OptionsModel::Server);
mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse);
mapper->addMapping(ui->proxyIp, OptionsModel::ProxyIP);
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index d87fc1f84a..9e2f38f7ec 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -149,6 +149,13 @@ void OptionsModel::Init(bool resetSettings)
if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool()))
addOverriddenOption("-listen");
+ if (!settings.contains("server")) {
+ settings.setValue("server", false);
+ }
+ if (!gArgs.SoftSetBoolArg("-server", settings.value("server").toBool())) {
+ addOverriddenOption("-server");
+ }
+
if (!settings.contains("fUseProxy"))
settings.setValue("fUseProxy", false);
if (!settings.contains("addrProxy"))
@@ -363,6 +370,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
return settings.value("nThreadsScriptVerif");
case Listen:
return settings.value("fListen");
+ case Server:
+ return settings.value("server");
default:
return QVariant();
}
@@ -528,6 +537,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
setRestartRequired(true);
}
break;
+ case Server:
+ if (settings.value("server") != value) {
+ settings.setValue("server", value);
+ setRestartRequired(true);
+ }
+ break;
default:
break;
}
diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h
index 203ee27ad8..65544acfbd 100644
--- a/src/qt/optionsmodel.h
+++ b/src/qt/optionsmodel.h
@@ -69,6 +69,7 @@ public:
ExternalSignerPath, // QString
SpendZeroConfChange, // bool
Listen, // bool
+ Server, // bool
OptionIDRowCount,
};
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 4554f11a41..1c8ed22ada 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -495,14 +495,28 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
constexpr QChar nonbreaking_hyphen(8209);
const std::vector<QString> CONNECTION_TYPE_DOC{
+ //: Explanatory text for an inbound peer connection.
tr("Inbound: initiated by peer"),
+ /*: Explanatory text for an outbound peer connection that
+ relays all network information. This is the default behavior for
+ outbound connections. */
tr("Outbound Full Relay: default"),
+ /*: Explanatory text for an outbound peer connection that relays
+ network information about blocks and not transactions or addresses. */
tr("Outbound Block Relay: does not relay transactions or addresses"),
+ /*: Explanatory text for an outbound peer connection that was
+ established manually through one of several methods. The numbered
+ arguments are stand-ins for the methods available to establish
+ manual connections. */
tr("Outbound Manual: added using RPC %1 or %2/%3 configuration options")
.arg("addnode")
.arg(QString(nonbreaking_hyphen) + "addnode")
.arg(QString(nonbreaking_hyphen) + "connect"),
+ /*: Explanatory text for a short-lived outbound peer connection that
+ is used to test the aliveness of known addresses. */
tr("Outbound Feeler: short-lived, for testing addresses"),
+ /*: Explanatory text for a short-lived outbound peer connection that is used
+ to request addresses from a peer. */
tr("Outbound Address Fetch: short-lived, for soliciting addresses")};
const QString list{"<ul><li>" + Join(CONNECTION_TYPE_DOC, QString("</li><li>")) + "</li></ul>"};
ui->peerConnectionTypeLabel->setToolTip(ui->peerConnectionTypeLabel->toolTip().arg(list));
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 2f16e6edb4..1973c9de9a 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -222,17 +222,21 @@ void TransactionView::setModel(WalletModel *_model)
{
// Add third party transaction URLs to context menu
QStringList listUrls = GUIUtil::SplitSkipEmptyParts(_model->getOptionsModel()->getThirdPartyTxUrls(), "|");
+ bool actions_created = false;
for (int i = 0; i < listUrls.size(); ++i)
{
QString url = listUrls[i].trimmed();
QString host = QUrl(url, QUrl::StrictMode).host();
if (!host.isEmpty())
{
- QAction *thirdPartyTxUrlAction = new QAction(host, this); // use host as menu item label
- if (i == 0)
+ if (!actions_created) {
contextMenu->addSeparator();
- contextMenu->addAction(thirdPartyTxUrlAction);
- connect(thirdPartyTxUrlAction, &QAction::triggered, [this, url] { openThirdPartyTxUrl(url); });
+ actions_created = true;
+ }
+ /*: Transactions table context menu action to show the
+ selected transaction in a third-party block explorer.
+ %1 is a stand-in argument for the URL of the explorer. */
+ contextMenu->addAction(tr("Show in %1").arg(host), [this, url] { openThirdPartyTxUrl(url); });
}
}
}
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index a6e9cd2ed1..d759a5aab5 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -19,7 +19,6 @@ from test_framework.script import CScript, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
- assert_greater_than,
assert_raises_rpc_error,
)
from test_framework.script_util import (
@@ -96,23 +95,10 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
"""Create a txout with a given amount and scriptPubKey
- Assumes that MiniWallet has enough funds to cover the amount and the fixed fee
- (from it's internal utxos, the one with the largest value is taken).
-
confirmed - txouts created will be confirmed in the blockchain;
unconfirmed otherwise.
"""
- # MiniWallet only supports sweeping utxos to its own internal scriptPubKey, so in
- # order to create an output with arbitrary amount/scriptPubKey, we have to add it
- # manually after calling the create_self_transfer method. The MiniWallet output's
- # nValue has to be adapted accordingly (amount and fee deduction). To keep things
- # simple, we use a fixed fee of 1000 Satoshis here.
- fee = 1000
- tx = self.wallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx']
- assert_greater_than(tx.vout[0].nValue, amount + fee)
- tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet
- tx.vout.append(CTxOut(amount, scriptPubKey)) # desired output -> to be returned
- txid = self.wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex())
+ txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey, amount=amount)
# If requested, ensure txouts are confirmed.
if confirmed:
@@ -125,7 +111,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert new_size < mempool_size
mempool_size = new_size
- return COutPoint(int(txid, 16), 1)
+ return COutPoint(int(txid, 16), n)
def test_simple_doublespend(self):
"""Simple doublespend"""
diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py
index 4054a9a903..25d1cb2bf1 100755
--- a/test/functional/feature_segwit.py
+++ b/test/functional/feature_segwit.py
@@ -236,12 +236,14 @@ class SegWitTest(BitcoinTestFramework):
self.log.info("Verify sigops are counted in GBT with BIP141 rules after the fork")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
+ raw_tx = self.nodes[0].getrawtransaction(txid, True)
tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert_greater_than_or_equal(tmpl['sizelimit'], 3999577) # actual maximum size is lower due to minimum mandatory non-witness data
assert_equal(tmpl['weightlimit'], 4000000)
assert_equal(tmpl['sigoplimit'], 80000)
assert_equal(tmpl['transactions'][0]['txid'], txid)
- assert_equal(tmpl['transactions'][0]['sigops'], 8)
+ expected_sigops = 9 if 'txinwitness' in raw_tx["vin"][0] else 8
+ assert_equal(tmpl['transactions'][0]['sigops'], expected_sigops)
assert '!segwit' in tmpl['rules']
self.generate(self.nodes[0], 1) # Mine a block to clear the gbt cache
diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py
index a040665fba..0d8c298bea 100755
--- a/test/functional/p2p_filter.py
+++ b/test/functional/p2p_filter.py
@@ -8,6 +8,7 @@ Test BIP 37
from test_framework.messages import (
CInv,
+ COIN,
MAX_BLOOM_FILTER_SIZE,
MAX_BLOOM_HASH_FUNCS,
MSG_BLOCK,
@@ -28,11 +29,15 @@ from test_framework.p2p import (
)
from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.wallet import (
+ MiniWallet,
+ random_p2wpkh,
+)
class P2PBloomFilter(P2PInterface):
# This is a P2SH watch-only wallet
- watch_script_pubkey = 'a914ffffffffffffffffffffffffffffffffffffffff87'
+ watch_script_pubkey = bytes.fromhex('a914ffffffffffffffffffffffffffffffffffffffff87')
# The initial filter (n=10, fp=0.000001) with just the above scriptPubKey added
watch_filter_init = msg_filterload(
data=
@@ -93,8 +98,9 @@ class FilterTest(BitcoinTestFramework):
'-whitelist=noban@127.0.0.1', # immediate tx relay
]]
- def skip_test_if_missing_module(self):
- self.skip_if_no_wallet()
+ def generatetoscriptpubkey(self, scriptpubkey):
+ """Helper to generate a single block to the given scriptPubKey."""
+ return self.generatetodescriptor(self.nodes[0], 1, f'raw({scriptpubkey.hex()})')[0]
def test_size_limits(self, filter_peer):
self.log.info('Check that too large filter is rejected')
@@ -130,8 +136,7 @@ class FilterTest(BitcoinTestFramework):
filter_peer = P2PBloomFilter()
self.log.debug("Create a tx relevant to the peer before connecting")
- filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
- txid = self.nodes[0].sendtoaddress(filter_address, 90)
+ txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)
self.log.debug("Send a mempool msg after connecting and check that the tx is received")
self.nodes[0].add_p2p_connection(filter_peer)
@@ -142,8 +147,7 @@ class FilterTest(BitcoinTestFramework):
def test_frelay_false(self, filter_peer):
self.log.info("Check that a node with fRelay set to false does not receive invs until the filter is set")
filter_peer.tx_received = False
- filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
- self.nodes[0].sendtoaddress(filter_address, 90)
+ self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)
# Sync to make sure the reason filter_peer doesn't receive the tx is not p2p delays
filter_peer.sync_with_ping()
assert not filter_peer.tx_received
@@ -156,45 +160,44 @@ class FilterTest(BitcoinTestFramework):
filter_peer.send_and_ping(filter_peer.watch_filter_init)
# If fRelay is not already True, sending filterload sets it to True
assert self.nodes[0].getpeerinfo()[0]['relaytxes']
- filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block')
- block_hash = self.generatetoaddress(self.nodes[0], 1, filter_address)[0]
+ block_hash = self.generatetoscriptpubkey(filter_peer.watch_script_pubkey)
txid = self.nodes[0].getblock(block_hash)['tx'][0]
filter_peer.wait_for_merkleblock(block_hash)
filter_peer.wait_for_tx(txid)
self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block')
filter_peer.tx_received = False
- block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0]
+ block_hash = self.generatetoscriptpubkey(random_p2wpkh())
filter_peer.wait_for_merkleblock(block_hash)
assert not filter_peer.tx_received
self.log.info('Check that we not receive a tx if the filter does not match a mempool tx')
filter_peer.merkleblock_received = False
filter_peer.tx_received = False
- self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 90)
+ self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN)
filter_peer.sync_send_with_ping()
assert not filter_peer.merkleblock_received
assert not filter_peer.tx_received
self.log.info('Check that we receive a tx if the filter matches a mempool tx')
filter_peer.merkleblock_received = False
- txid = self.nodes[0].sendtoaddress(filter_address, 90)
+ txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)
filter_peer.wait_for_tx(txid)
assert not filter_peer.merkleblock_received
self.log.info('Check that after deleting filter all txs get relayed again')
filter_peer.send_and_ping(msg_filterclear())
for _ in range(5):
- txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 7)
+ txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN)
filter_peer.wait_for_tx(txid)
self.log.info('Check that request for filtered blocks is ignored if no filter is set')
filter_peer.merkleblock_received = False
filter_peer.tx_received = False
with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']):
- block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0]
+ block_hash = self.generatetoscriptpubkey(random_p2wpkh())
filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))])
filter_peer.sync_with_ping()
assert not filter_peer.merkleblock_received
@@ -210,6 +213,9 @@ class FilterTest(BitcoinTestFramework):
self.nodes[0].disconnect_p2ps()
def run_test(self):
+ self.wallet = MiniWallet(self.nodes[0])
+ self.wallet.rescan_utxos()
+
filter_peer = self.nodes[0].add_p2p_connection(P2PBloomFilter())
self.log.info('Test filter size limits')
self.test_size_limits(filter_peer)
diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py
index 5d1d7ea45c..e0dfce4c2f 100755
--- a/test/functional/test_framework/script_util.py
+++ b/test/functional/test_framework/script_util.py
@@ -3,7 +3,17 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Useful Script constants and utils."""
-from test_framework.script import CScript, hash160, sha256, OP_0, OP_DUP, OP_HASH160, OP_CHECKSIG, OP_EQUAL, OP_EQUALVERIFY
+from test_framework.script import (
+ CScript,
+ hash160,
+ sha256,
+ OP_0,
+ OP_DUP,
+ OP_HASH160,
+ OP_CHECKSIG,
+ OP_EQUAL,
+ OP_EQUALVERIFY,
+)
# To prevent a "tx-size-small" policy rule error, a transaction has to have a
# non-witness size of at least 82 bytes (MIN_STANDARD_TX_NONWITNESS_SIZE in
@@ -25,28 +35,34 @@ from test_framework.script import CScript, hash160, sha256, OP_0, OP_DUP, OP_HAS
DUMMY_P2WPKH_SCRIPT = CScript([b'a' * 21])
DUMMY_2_P2WPKH_SCRIPT = CScript([b'b' * 21])
-def keyhash_to_p2pkh_script(hash, main = False):
+
+def keyhash_to_p2pkh_script(hash):
assert len(hash) == 20
return CScript([OP_DUP, OP_HASH160, hash, OP_EQUALVERIFY, OP_CHECKSIG])
-def scripthash_to_p2sh_script(hash, main = False):
+
+def scripthash_to_p2sh_script(hash):
assert len(hash) == 20
return CScript([OP_HASH160, hash, OP_EQUAL])
-def key_to_p2pkh_script(key, main = False):
+
+def key_to_p2pkh_script(key):
key = check_key(key)
- return keyhash_to_p2pkh_script(hash160(key), main)
+ return keyhash_to_p2pkh_script(hash160(key))
-def script_to_p2sh_script(script, main = False):
+
+def script_to_p2sh_script(script):
script = check_script(script)
- return scripthash_to_p2sh_script(hash160(script), main)
+ return scripthash_to_p2sh_script(hash160(script))
+
-def key_to_p2sh_p2wpkh_script(key, main = False):
+def key_to_p2sh_p2wpkh_script(key):
key = check_key(key)
p2shscript = CScript([OP_0, hash160(key)])
- return script_to_p2sh_script(p2shscript, main)
+ return script_to_p2sh_script(p2shscript)
+
-def program_to_witness_script(version, program, main = False):
+def program_to_witness_script(version, program):
if isinstance(program, str):
program = bytes.fromhex(program)
assert 0 <= version <= 16
@@ -54,29 +70,34 @@ def program_to_witness_script(version, program, main = False):
assert version > 0 or len(program) in [20, 32]
return CScript([version, program])
-def script_to_p2wsh_script(script, main = False):
+
+def script_to_p2wsh_script(script):
script = check_script(script)
- return program_to_witness_script(0, sha256(script), main)
+ return program_to_witness_script(0, sha256(script))
+
-def key_to_p2wpkh_script(key, main = False):
+def key_to_p2wpkh_script(key):
key = check_key(key)
- return program_to_witness_script(0, hash160(key), main)
+ return program_to_witness_script(0, hash160(key))
+
-def script_to_p2sh_p2wsh_script(script, main = False):
+def script_to_p2sh_p2wsh_script(script):
script = check_script(script)
p2shscript = CScript([OP_0, sha256(script)])
- return script_to_p2sh_script(p2shscript, main)
+ return script_to_p2sh_script(p2shscript)
+
def check_key(key):
if isinstance(key, str):
- key = bytes.fromhex(key) # Assuming this is hex string
+ key = bytes.fromhex(key) # Assuming this is hex string
if isinstance(key, bytes) and (len(key) == 33 or len(key) == 65):
return key
assert False
+
def check_script(script):
if isinstance(script, str):
- script = bytes.fromhex(script) # Assuming this is hex string
+ script = bytes.fromhex(script) # Assuming this is hex string
if isinstance(script, bytes) or isinstance(script, CScript):
return script
assert False
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index cea59c6d69..ef27cb3221 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -28,6 +28,7 @@ from test_framework.script import (
OP_NOP,
SIGHASH_ALL,
)
+from test_framework.script_util import key_to_p2wpkh_script
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
@@ -146,6 +147,25 @@ class MiniWallet:
self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex'])
return tx
+ def send_to(self, *, from_node, scriptPubKey, amount, fee=1000):
+ """
+ Create and send a tx with an output to a given scriptPubKey/amount,
+ plus a change output to our internal address. To keep things simple, a
+ fixed fee given in Satoshi is used.
+
+ Note that this method fails if there is no single internal utxo
+ available that can cover the cost for the amount and the fixed fee
+ (the utxo with the largest value is taken).
+
+ Returns a tuple (txid, n) referring to the created external utxo outpoint.
+ """
+ tx = self.create_self_transfer(from_node=from_node, fee_rate=0, mempool_valid=False)['tx']
+ assert_greater_than_or_equal(tx.vout[0].nValue, amount + fee)
+ tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet
+ tx.vout.append(CTxOut(amount, scriptPubKey)) # arbitrary output -> to be returned
+ txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex())
+ return txid, 1
+
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."""
self._utxos = sorted(self._utxos, key=lambda k: k['value'])
@@ -188,6 +208,14 @@ class MiniWallet:
return txid
+def random_p2wpkh():
+ """Generate a random P2WPKH scriptPubKey. Can be used when a random destination is needed,
+ but no compiled wallet is available (e.g. as replacement to the getnewaddress RPC)."""
+ key = ECKey()
+ key.generate()
+ return key_to_p2wpkh_script(key.get_pubkey().get_bytes())
+
+
def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE):
"""Build a transaction that spends parent_txid.vout[n] and produces one output with
amount = parent_value with a fee deducted.