mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-11 04:12:36 -03:00
Merge bitcoin/bitcoin#17526: Add Single Random Draw as an additional coin selection algorithm
3633b667ff
Use SelectCoinsSRD if it has less waste (Andrew Chow)8bf789b4b4
Add SelectCoinsSRD function (Andrew Chow)2ad3b5d2ad
tests: wallet_basic lock needed unspents (Andrew Chow)b77885f13e
tests: wallet_txn explicilty specify inputs (Andrew Chow)59ba7d2861
tests: rpc_fundrawtx better test for UTXO inclusion with include_unsafe (Andrew Chow)a165bfbe44
tests: rpc_fundrawtx use specific inputs for unavailable change test (Andrew Chow)df765a484d
tests: rpc_fundrawtx lock to UTXO types (Andrew Chow) Pull request description: To ease in the use of SRD as our fallback mechanism, this PR adds it as a secondary fallback algorithm in addition to the knapsack solver. Since #22009, the solution with the least waste will be chosen. This pattern is continued with SRD simply being another solution whose waste is compared. ACKs for top commit: glozow: reACK3633b66
via `git range-diff 981b9d1...3633b66`, thanks for taking the suggestions laanwj: Concept and code review ACK3633b667ff
Tree-SHA512: 895659f553fea2230990136565bdf18b1328de8b0ce47f06b64bb4d69301f6dd68cb38debe5c24fb6de1317b735fc020a987c541f00bbea65229de47e53adf92
This commit is contained in:
commit
d5d0a5c604
7 changed files with 135 additions and 19 deletions
|
@ -5,9 +5,11 @@
|
||||||
#include <wallet/coinselection.h>
|
#include <wallet/coinselection.h>
|
||||||
|
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
|
#include <util/check.h>
|
||||||
#include <util/system.h>
|
#include <util/system.h>
|
||||||
#include <util/moneystr.h>
|
#include <util/moneystr.h>
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
// Descending order comparator
|
// Descending order comparator
|
||||||
|
@ -168,6 +170,30 @@ bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selectio
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<std::set<CInputCoin>, CAmount>> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value)
|
||||||
|
{
|
||||||
|
std::set<CInputCoin> out_set;
|
||||||
|
CAmount value_ret = 0;
|
||||||
|
|
||||||
|
std::vector<size_t> indexes;
|
||||||
|
indexes.resize(utxo_pool.size());
|
||||||
|
std::iota(indexes.begin(), indexes.end(), 0);
|
||||||
|
Shuffle(indexes.begin(), indexes.end(), FastRandomContext());
|
||||||
|
|
||||||
|
CAmount selected_eff_value = 0;
|
||||||
|
for (const size_t i : indexes) {
|
||||||
|
const OutputGroup& group = utxo_pool.at(i);
|
||||||
|
Assume(group.GetSelectionAmount() > 0);
|
||||||
|
selected_eff_value += group.GetSelectionAmount();
|
||||||
|
value_ret += group.m_value;
|
||||||
|
util::insert(out_set, group.m_outputs);
|
||||||
|
if (selected_eff_value >= target_value) {
|
||||||
|
return std::make_pair(out_set, value_ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
|
static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
|
||||||
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
|
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
#include <random.h>
|
#include <random.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
//! target minimum change amount
|
//! target minimum change amount
|
||||||
static constexpr CAmount MIN_CHANGE{COIN / 100};
|
static constexpr CAmount MIN_CHANGE{COIN / 100};
|
||||||
//! final minimum change amount after paying for fees
|
//! final minimum change amount after paying for fees
|
||||||
|
@ -185,6 +187,15 @@ struct OutputGroup
|
||||||
|
|
||||||
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret);
|
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret);
|
||||||
|
|
||||||
|
/** Select coins by Single Random Draw. OutputGroups are selected randomly from the eligible
|
||||||
|
* outputs until the target is satisfied
|
||||||
|
*
|
||||||
|
* @param[in] utxo_pool The positive effective value OutputGroups eligible for selection
|
||||||
|
* @param[in] target_value The target value to select for
|
||||||
|
* @returns If successful, a pair of set of outputs and total selected value, otherwise, std::nullopt
|
||||||
|
*/
|
||||||
|
std::optional<std::pair<std::set<CInputCoin>, CAmount>> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value);
|
||||||
|
|
||||||
// Original coin selection algorithm as a fallback
|
// Original coin selection algorithm as a fallback
|
||||||
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet);
|
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet);
|
||||||
|
|
||||||
|
|
|
@ -387,6 +387,15 @@ bool AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const
|
||||||
results.emplace_back(std::make_tuple(waste, std::move(knapsack_coins), knapsack_value));
|
results.emplace_back(std::make_tuple(waste, std::move(knapsack_coins), knapsack_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We include the minimum final change for SRD as we do want to avoid making really small change.
|
||||||
|
// KnapsackSolver does not need this because it includes MIN_CHANGE internally.
|
||||||
|
const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + MIN_FINAL_CHANGE;
|
||||||
|
auto srd_result = SelectCoinsSRD(positive_groups, srd_target);
|
||||||
|
if (srd_result != std::nullopt) {
|
||||||
|
const auto waste = GetSelectionWaste(srd_result->first, coin_selection_params.m_cost_of_change, srd_target, !coin_selection_params.m_subtract_fee_outputs);
|
||||||
|
results.emplace_back(std::make_tuple(waste, std::move(srd_result->first), srd_result->second));
|
||||||
|
}
|
||||||
|
|
||||||
if (results.size() == 0) {
|
if (results.size() == 0) {
|
||||||
// No solution found
|
// No solution found
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -47,7 +47,40 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
self.connect_nodes(0, 2)
|
self.connect_nodes(0, 2)
|
||||||
self.connect_nodes(0, 3)
|
self.connect_nodes(0, 3)
|
||||||
|
|
||||||
|
def lock_outputs_type(self, wallet, outputtype):
|
||||||
|
"""
|
||||||
|
Only allow UTXOs of the given type
|
||||||
|
"""
|
||||||
|
if outputtype in ["legacy", "p2pkh", "pkh"]:
|
||||||
|
prefixes = ["pkh(", "sh(multi("]
|
||||||
|
elif outputtype in ["p2sh-segwit", "sh_wpkh"]:
|
||||||
|
prefixes = ["sh(wpkh(", "sh(wsh("]
|
||||||
|
elif outputtype in ["bech32", "wpkh"]:
|
||||||
|
prefixes = ["wpkh(", "wsh("]
|
||||||
|
else:
|
||||||
|
assert False, f"Unknown output type {outputtype}"
|
||||||
|
|
||||||
|
to_lock = []
|
||||||
|
for utxo in wallet.listunspent():
|
||||||
|
if "desc" in utxo:
|
||||||
|
for prefix in prefixes:
|
||||||
|
if utxo["desc"].startswith(prefix):
|
||||||
|
to_lock.append({"txid": utxo["txid"], "vout": utxo["vout"]})
|
||||||
|
wallet.lockunspent(False, to_lock)
|
||||||
|
|
||||||
|
def unlock_utxos(self, wallet):
|
||||||
|
"""
|
||||||
|
Unlock all UTXOs except the watchonly one
|
||||||
|
"""
|
||||||
|
to_keep = []
|
||||||
|
if self.watchonly_txid is not None and self.watchonly_vout is not None:
|
||||||
|
to_keep.append({"txid": self.watchonly_txid, "vout": self.watchonly_vout})
|
||||||
|
wallet.lockunspent(True)
|
||||||
|
wallet.lockunspent(False, to_keep)
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
|
self.watchonly_txid = None
|
||||||
|
self.watchonly_vout = None
|
||||||
self.log.info("Connect nodes, set fees, generate blocks, and sync")
|
self.log.info("Connect nodes, set fees, generate blocks, and sync")
|
||||||
self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee']
|
self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee']
|
||||||
# This test is not meant to test fee estimation and we'd like
|
# This test is not meant to test fee estimation and we'd like
|
||||||
|
@ -373,6 +406,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
def test_fee_p2pkh(self):
|
def test_fee_p2pkh(self):
|
||||||
"""Compare fee of a standard pubkeyhash transaction."""
|
"""Compare fee of a standard pubkeyhash transaction."""
|
||||||
self.log.info("Test fundrawtxn p2pkh fee")
|
self.log.info("Test fundrawtxn p2pkh fee")
|
||||||
|
self.lock_outputs_type(self.nodes[0], "p2pkh")
|
||||||
inputs = []
|
inputs = []
|
||||||
outputs = {self.nodes[1].getnewaddress():1.1}
|
outputs = {self.nodes[1].getnewaddress():1.1}
|
||||||
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
|
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
|
||||||
|
@ -386,9 +420,12 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
||||||
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
||||||
|
|
||||||
|
self.unlock_utxos(self.nodes[0])
|
||||||
|
|
||||||
def test_fee_p2pkh_multi_out(self):
|
def test_fee_p2pkh_multi_out(self):
|
||||||
"""Compare fee of a standard pubkeyhash transaction with multiple outputs."""
|
"""Compare fee of a standard pubkeyhash transaction with multiple outputs."""
|
||||||
self.log.info("Test fundrawtxn p2pkh fee with multiple outputs")
|
self.log.info("Test fundrawtxn p2pkh fee with multiple outputs")
|
||||||
|
self.lock_outputs_type(self.nodes[0], "p2pkh")
|
||||||
inputs = []
|
inputs = []
|
||||||
outputs = {
|
outputs = {
|
||||||
self.nodes[1].getnewaddress():1.1,
|
self.nodes[1].getnewaddress():1.1,
|
||||||
|
@ -409,8 +446,11 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
||||||
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
||||||
|
|
||||||
|
self.unlock_utxos(self.nodes[0])
|
||||||
|
|
||||||
def test_fee_p2sh(self):
|
def test_fee_p2sh(self):
|
||||||
"""Compare fee of a 2-of-2 multisig p2sh transaction."""
|
"""Compare fee of a 2-of-2 multisig p2sh transaction."""
|
||||||
|
self.lock_outputs_type(self.nodes[0], "p2pkh")
|
||||||
# Create 2-of-2 addr.
|
# Create 2-of-2 addr.
|
||||||
addr1 = self.nodes[1].getnewaddress()
|
addr1 = self.nodes[1].getnewaddress()
|
||||||
addr2 = self.nodes[1].getnewaddress()
|
addr2 = self.nodes[1].getnewaddress()
|
||||||
|
@ -433,9 +473,12 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
||||||
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
||||||
|
|
||||||
|
self.unlock_utxos(self.nodes[0])
|
||||||
|
|
||||||
def test_fee_4of5(self):
|
def test_fee_4of5(self):
|
||||||
"""Compare fee of a standard pubkeyhash transaction."""
|
"""Compare fee of a standard pubkeyhash transaction."""
|
||||||
self.log.info("Test fundrawtxn fee with 4-of-5 addresses")
|
self.log.info("Test fundrawtxn fee with 4-of-5 addresses")
|
||||||
|
self.lock_outputs_type(self.nodes[0], "p2pkh")
|
||||||
|
|
||||||
# Create 4-of-5 addr.
|
# Create 4-of-5 addr.
|
||||||
addr1 = self.nodes[1].getnewaddress()
|
addr1 = self.nodes[1].getnewaddress()
|
||||||
|
@ -474,6 +517,8 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
|
||||||
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
|
||||||
|
|
||||||
|
self.unlock_utxos(self.nodes[0])
|
||||||
|
|
||||||
def test_spend_2of2(self):
|
def test_spend_2of2(self):
|
||||||
"""Spend a 2-of-2 multisig transaction over fundraw."""
|
"""Spend a 2-of-2 multisig transaction over fundraw."""
|
||||||
self.log.info("Test fundpsbt spending 2-of-2 multisig")
|
self.log.info("Test fundpsbt spending 2-of-2 multisig")
|
||||||
|
@ -542,15 +587,18 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
# Drain the keypool.
|
# Drain the keypool.
|
||||||
self.nodes[1].getnewaddress()
|
self.nodes[1].getnewaddress()
|
||||||
self.nodes[1].getrawchangeaddress()
|
self.nodes[1].getrawchangeaddress()
|
||||||
inputs = []
|
|
||||||
outputs = {self.nodes[0].getnewaddress():1.19999500}
|
# Choose 2 inputs
|
||||||
|
inputs = self.nodes[1].listunspent()[0:2]
|
||||||
|
value = sum(inp["amount"] for inp in inputs) - Decimal("0.00000500") # Pay a 500 sat fee
|
||||||
|
outputs = {self.nodes[0].getnewaddress():value}
|
||||||
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
|
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
|
||||||
# fund a transaction that does not require a new key for the change output
|
# fund a transaction that does not require a new key for the change output
|
||||||
self.nodes[1].fundrawtransaction(rawtx)
|
self.nodes[1].fundrawtransaction(rawtx)
|
||||||
|
|
||||||
# fund a transaction that requires a new key for the change output
|
# fund a transaction that requires a new key for the change output
|
||||||
# creating the key must be impossible because the wallet is locked
|
# creating the key must be impossible because the wallet is locked
|
||||||
outputs = {self.nodes[0].getnewaddress():1.1}
|
outputs = {self.nodes[0].getnewaddress():value - Decimal("0.1")}
|
||||||
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
|
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
|
||||||
assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", self.nodes[1].fundrawtransaction, rawtx)
|
assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", self.nodes[1].fundrawtransaction, rawtx)
|
||||||
|
|
||||||
|
@ -944,31 +992,31 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
# We receive unconfirmed funds from external keys (unsafe outputs).
|
# We receive unconfirmed funds from external keys (unsafe outputs).
|
||||||
addr = wallet.getnewaddress()
|
addr = wallet.getnewaddress()
|
||||||
txid1 = self.nodes[2].sendtoaddress(addr, 6)
|
inputs = []
|
||||||
txid2 = self.nodes[2].sendtoaddress(addr, 4)
|
for i in range(0, 2):
|
||||||
self.sync_all()
|
txid = self.nodes[2].sendtoaddress(addr, 5)
|
||||||
vout1 = find_vout_for_address(wallet, txid1, addr)
|
self.sync_mempools()
|
||||||
vout2 = find_vout_for_address(wallet, txid2, addr)
|
vout = find_vout_for_address(wallet, txid, addr)
|
||||||
|
inputs.append((txid, vout))
|
||||||
|
|
||||||
# Unsafe inputs are ignored by default.
|
# Unsafe inputs are ignored by default.
|
||||||
rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 5}])
|
rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 7.5}])
|
||||||
assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx)
|
assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx)
|
||||||
|
|
||||||
# But we can opt-in to use them for funding.
|
# But we can opt-in to use them for funding.
|
||||||
fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
|
fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
|
||||||
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
|
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
|
||||||
assert any([txin['txid'] == txid1 and txin['vout'] == vout1 for txin in tx_dec['vin']])
|
assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
|
||||||
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
|
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
|
||||||
wallet.sendrawtransaction(signedtx['hex'])
|
assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]
|
||||||
|
|
||||||
# And we can also use them once they're confirmed.
|
# And we can also use them once they're confirmed.
|
||||||
self.generate(self.nodes[0], 1)
|
self.generate(self.nodes[0], 1)
|
||||||
rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 3}])
|
fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": False})
|
||||||
fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
|
|
||||||
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
|
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
|
||||||
assert any([txin['txid'] == txid2 and txin['vout'] == vout2 for txin in tx_dec['vin']])
|
assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
|
||||||
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
|
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
|
||||||
wallet.sendrawtransaction(signedtx['hex'])
|
assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]
|
||||||
|
|
||||||
def test_22670(self):
|
def test_22670(self):
|
||||||
# In issue #22670, it was observed that ApproximateBestSubset may
|
# In issue #22670, it was observed that ApproximateBestSubset may
|
||||||
|
|
|
@ -13,6 +13,7 @@ from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
assert_fee_amount,
|
assert_fee_amount,
|
||||||
assert_raises_rpc_error,
|
assert_raises_rpc_error,
|
||||||
|
find_vout_for_address,
|
||||||
)
|
)
|
||||||
from test_framework.wallet_util import test_address
|
from test_framework.wallet_util import test_address
|
||||||
|
|
||||||
|
@ -463,6 +464,9 @@ class WalletTest(BitcoinTestFramework):
|
||||||
# 1. Send some coins to generate new UTXO
|
# 1. Send some coins to generate new UTXO
|
||||||
address_to_import = self.nodes[2].getnewaddress()
|
address_to_import = self.nodes[2].getnewaddress()
|
||||||
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
|
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
|
||||||
|
self.sync_mempools(self.nodes[0:3])
|
||||||
|
vout = find_vout_for_address(self.nodes[2], txid, address_to_import)
|
||||||
|
self.nodes[2].lockunspent(False, [{"txid": txid, "vout": vout}])
|
||||||
self.generate(self.nodes[0], 1)
|
self.generate(self.nodes[0], 1)
|
||||||
self.sync_all(self.nodes[0:3])
|
self.sync_all(self.nodes[0:3])
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
|
find_vout_for_address
|
||||||
)
|
)
|
||||||
from test_framework.messages import (
|
from test_framework.messages import (
|
||||||
COIN,
|
COIN,
|
||||||
|
@ -33,6 +34,13 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
super().setup_network()
|
super().setup_network()
|
||||||
self.disconnect_nodes(1, 2)
|
self.disconnect_nodes(1, 2)
|
||||||
|
|
||||||
|
def spend_txid(self, txid, vout, outputs):
|
||||||
|
inputs = [{"txid": txid, "vout": vout}]
|
||||||
|
tx = self.nodes[0].createrawtransaction(inputs, outputs)
|
||||||
|
tx = self.nodes[0].fundrawtransaction(tx)
|
||||||
|
tx = self.nodes[0].signrawtransactionwithwallet(tx['hex'])
|
||||||
|
return self.nodes[0].sendrawtransaction(tx['hex'])
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
if self.options.segwit:
|
if self.options.segwit:
|
||||||
output_type = "p2sh-segwit"
|
output_type = "p2sh-segwit"
|
||||||
|
@ -49,6 +57,7 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
node0_address1 = self.nodes[0].getnewaddress(address_type=output_type)
|
node0_address1 = self.nodes[0].getnewaddress(address_type=output_type)
|
||||||
node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219)
|
node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219)
|
||||||
node0_tx1 = self.nodes[0].gettransaction(node0_txid1)
|
node0_tx1 = self.nodes[0].gettransaction(node0_txid1)
|
||||||
|
self.nodes[0].lockunspent(False, [{"txid":node0_txid1, "vout": find_vout_for_address(self.nodes[0], node0_txid1, node0_address1)}])
|
||||||
|
|
||||||
node0_address2 = self.nodes[0].getnewaddress(address_type=output_type)
|
node0_address2 = self.nodes[0].getnewaddress(address_type=output_type)
|
||||||
node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29)
|
node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29)
|
||||||
|
@ -61,8 +70,8 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
node1_address = self.nodes[1].getnewaddress()
|
node1_address = self.nodes[1].getnewaddress()
|
||||||
|
|
||||||
# Send tx1, and another transaction tx2 that won't be cloned
|
# Send tx1, and another transaction tx2 that won't be cloned
|
||||||
txid1 = self.nodes[0].sendtoaddress(node1_address, 40)
|
txid1 = self.spend_txid(node0_txid1, find_vout_for_address(self.nodes[0], node0_txid1, node0_address1), {node1_address: 40})
|
||||||
txid2 = self.nodes[0].sendtoaddress(node1_address, 20)
|
txid2 = self.spend_txid(node0_txid2, find_vout_for_address(self.nodes[0], node0_txid2, node0_address2), {node1_address: 20})
|
||||||
|
|
||||||
# Construct a clone of tx1, to be malleated
|
# Construct a clone of tx1, to be malleated
|
||||||
rawtx1 = self.nodes[0].getrawtransaction(txid1, 1)
|
rawtx1 = self.nodes[0].getrawtransaction(txid1, 1)
|
||||||
|
|
|
@ -9,6 +9,7 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
find_output,
|
find_output,
|
||||||
|
find_vout_for_address
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +30,13 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
super().setup_network()
|
super().setup_network()
|
||||||
self.disconnect_nodes(1, 2)
|
self.disconnect_nodes(1, 2)
|
||||||
|
|
||||||
|
def spend_txid(self, txid, vout, outputs):
|
||||||
|
inputs = [{"txid": txid, "vout": vout}]
|
||||||
|
tx = self.nodes[0].createrawtransaction(inputs, outputs)
|
||||||
|
tx = self.nodes[0].fundrawtransaction(tx)
|
||||||
|
tx = self.nodes[0].signrawtransactionwithwallet(tx['hex'])
|
||||||
|
return self.nodes[0].sendrawtransaction(tx['hex'])
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
# All nodes should start with 1,250 BTC:
|
# All nodes should start with 1,250 BTC:
|
||||||
starting_balance = 1250
|
starting_balance = 1250
|
||||||
|
@ -47,6 +55,7 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
node0_address_foo = self.nodes[0].getnewaddress()
|
node0_address_foo = self.nodes[0].getnewaddress()
|
||||||
fund_foo_txid = self.nodes[0].sendtoaddress(node0_address_foo, 1219)
|
fund_foo_txid = self.nodes[0].sendtoaddress(node0_address_foo, 1219)
|
||||||
fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
|
fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
|
||||||
|
self.nodes[0].lockunspent(False, [{"txid":fund_foo_txid, "vout": find_vout_for_address(self.nodes[0], fund_foo_txid, node0_address_foo)}])
|
||||||
|
|
||||||
node0_address_bar = self.nodes[0].getnewaddress()
|
node0_address_bar = self.nodes[0].getnewaddress()
|
||||||
fund_bar_txid = self.nodes[0].sendtoaddress(node0_address_bar, 29)
|
fund_bar_txid = self.nodes[0].sendtoaddress(node0_address_bar, 29)
|
||||||
|
@ -77,8 +86,8 @@ class TxnMallTest(BitcoinTestFramework):
|
||||||
assert_equal(doublespend["complete"], True)
|
assert_equal(doublespend["complete"], True)
|
||||||
|
|
||||||
# Create two spends using 1 50 BTC coin each
|
# Create two spends using 1 50 BTC coin each
|
||||||
txid1 = self.nodes[0].sendtoaddress(node1_address, 40)
|
txid1 = self.spend_txid(fund_foo_txid, find_vout_for_address(self.nodes[0], fund_foo_txid, node0_address_foo), {node1_address: 40})
|
||||||
txid2 = self.nodes[0].sendtoaddress(node1_address, 20)
|
txid2 = self.spend_txid(fund_bar_txid, find_vout_for_address(self.nodes[0], fund_bar_txid, node0_address_bar), {node1_address: 20})
|
||||||
|
|
||||||
# Have node0 mine a block:
|
# Have node0 mine a block:
|
||||||
if (self.options.mine_block):
|
if (self.options.mine_block):
|
||||||
|
|
Loading…
Reference in a new issue