Skip to content

Commit bfbd746

Browse files
authored
Merge pull request #1177 from psgreco/elements-22_0_1
Elements 22 0 1
2 parents e0c911a + 4aa849e commit bfbd746

File tree

4 files changed

+171
-17
lines changed

4 files changed

+171
-17
lines changed

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
AC_PREREQ([2.69])
22
define(_CLIENT_VERSION_MAJOR, 22)
33
define(_CLIENT_VERSION_MINOR, 0)
4-
define(_CLIENT_VERSION_BUILD, 0)
4+
define(_CLIENT_VERSION_BUILD, 1)
55
define(_CLIENT_VERSION_RC, 0)
66
define(_CLIENT_VERSION_IS_RELEASE, true)
77
define(_COPYRIGHT_YEAR, 2022)

src/wallet/spend.cpp

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -718,25 +718,29 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin
718718
}
719719

720720
// Reset all non-global blinding details.
721-
void resetBlindDetails(BlindDetails* det) {
721+
static void resetBlindDetails(BlindDetails* det, bool preserve_output_data = false) {
722722
det->i_amount_blinds.clear();
723723
det->i_asset_blinds.clear();
724724
det->i_assets.clear();
725725
det->i_amounts.clear();
726726

727727
det->o_amounts.clear();
728-
det->o_pubkeys.clear();
728+
if (!preserve_output_data) {
729+
det->o_pubkeys.clear();
730+
}
729731
det->o_amount_blinds.clear();
730732
det->o_assets.clear();
731733
det->o_asset_blinds.clear();
732734

733-
det->num_to_blind = 0;
734-
det->change_to_blind = 0;
735-
det->only_recipient_blind_index = -1;
736-
det->only_change_pos = -1;
735+
if (!preserve_output_data) {
736+
det->num_to_blind = 0;
737+
det->change_to_blind = 0;
738+
det->only_recipient_blind_index = -1;
739+
det->only_change_pos = -1;
740+
}
737741
}
738742

739-
bool fillBlindDetails(BlindDetails* det, CWallet* wallet, CMutableTransaction& txNew, std::vector<CInputCoin>& selected_coins, bilingual_str& error) {
743+
static bool fillBlindDetails(BlindDetails* det, CWallet* wallet, CMutableTransaction& txNew, std::vector<CInputCoin>& selected_coins, bilingual_str& error) {
740744
int num_inputs_blinded = 0;
741745

742746
// Fill in input blinding details
@@ -1076,6 +1080,28 @@ bool CWallet::CreateTransactionInternal(
10761080
// the blinding logic.
10771081
coin_selection_params.tx_noinputs_size += 70 + 66 +(MAX_RANGEPROOF_SIZE + DEFAULT_SURJECTIONPROOF_SIZE + WITNESS_SCALE_FACTOR - 1)/WITNESS_SCALE_FACTOR;
10781082
}
1083+
// If we are going to issue an asset, add the issuance data to the noinputs_size so that
1084+
// we allocate enough coins for them.
1085+
if (issuance_details) {
1086+
size_t issue_count = 0;
1087+
for (unsigned int i = 0; i < txNew.vout.size(); i++) {
1088+
if (txNew.vout[i].nAsset.IsExplicit() && txNew.vout[i].nAsset.GetAsset() == CAsset(uint256S("1"))) {
1089+
issue_count++;
1090+
} else if (txNew.vout[i].nAsset.IsExplicit() && txNew.vout[i].nAsset.GetAsset() == CAsset(uint256S("2"))) {
1091+
issue_count++;
1092+
}
1093+
}
1094+
if (issue_count > 0) {
1095+
// Allocate space for blinding nonce, entropy, and whichever of nAmount/nInflationKeys is null
1096+
coin_selection_params.tx_noinputs_size += 2 * 32 + 2 * (2 - issue_count);
1097+
}
1098+
// Allocate non-null nAmount/nInflationKeys and rangeproofs
1099+
if (issuance_details->blind_issuance) {
1100+
coin_selection_params.tx_noinputs_size += issue_count * (33 * WITNESS_SCALE_FACTOR + MAX_RANGEPROOF_SIZE + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
1101+
} else {
1102+
coin_selection_params.tx_noinputs_size += issue_count * 9;
1103+
}
1104+
}
10791105

10801106
// Include the fees for things that aren't inputs, excluding the change output
10811107
const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
@@ -1393,15 +1419,15 @@ bool CWallet::CreateTransactionInternal(
13931419
blind_details->num_to_blind--;
13941420
blind_details->change_to_blind--;
13951421

1396-
// FIXME: I promise this makes sense and fixes an actual problem
1397-
// with the wallet that users could encounter. But no human could
1398-
// follow the logic as to what this does or why it is safe. After
1399-
// the 22.0 rebase we need to double-back and replace the blinding
1400-
// logic to eliminate a bunch of edge cases and make this logic
1401-
// incomprehensible. But in the interest of minimizing diff during
1402-
// the rebase I am going to do this for now.
1403-
if (blind_details->num_to_blind == 1) {
1404-
resetBlindDetails(blind_details);
1422+
// FIXME: If we drop the change *and* this means we have only one
1423+
// blinded output *and* we have no blinded inputs, then this puts
1424+
// us in a situation where BlindTransaction will fail. This is
1425+
// prevented in fillBlindDetails, which adds an OP_RETURN output
1426+
// to handle this case. So do this ludicrous hack to accomplish
1427+
// this. This whole lump of un-followable-logic needs to be replaced
1428+
// by a complete rewriting of the wallet blinding logic.
1429+
if (blind_details->num_to_blind < 2) {
1430+
resetBlindDetails(blind_details, true /* don't wipe output data */);
14051431
if (!fillBlindDetails(blind_details, this, txNew, selected_coins, error)) {
14061432
return false;
14071433
}
@@ -1535,6 +1561,7 @@ bool CWallet::CreateTransactionInternal(
15351561
int ret = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, txNew);
15361562
assert(ret != -1);
15371563
if (ret != blind_details->num_to_blind) {
1564+
WalletLogPrintf("ERROR: tried to blind %d outputs but only blinded %d\n", (int) blind_details->num_to_blind, (int) ret);
15381565
error = _("Unable to blind the transaction properly. This should not happen.");
15391566
return false;
15401567
}

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
'feature_initial_reissuance_token.py',
110110
'feature_progress.py',
111111
'rpc_getnewblockhex.py',
112+
'wallet_elements_regression_1172.py',
112113
# Longest test should go first, to favor running tests in parallel
113114
'wallet_hd.py --legacy-wallet',
114115
'wallet_hd.py --descriptors',
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2017-2020 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test blinding logic when change is dropped and we have only one other blinded input
6+
7+
Constructs a transaction with a sufficiently small change output that it
8+
gets dropped, in which there is only one other blinded input. In the case
9+
that we have no blinded inputs, we would need to add an OP_RETURN output
10+
to the transaction, neccessitating special logic.
11+
12+
Check that this special logic still results in a correct transaction that
13+
sends the money to the desired recipient (and that the recipient is able
14+
to receive/spend the money).
15+
"""
16+
17+
from decimal import Decimal
18+
19+
from test_framework.blocktools import COINBASE_MATURITY
20+
from test_framework.test_framework import BitcoinTestFramework
21+
from test_framework.util import (
22+
assert_equal,
23+
satoshi_round,
24+
)
25+
26+
class WalletCtTest(BitcoinTestFramework):
27+
def set_test_params(self):
28+
self.setup_clean_chain = True
29+
self.num_nodes = 3
30+
self.extra_args = [[
31+
"-blindedaddresses=1",
32+
"-initialfreecoins=2100000000000000",
33+
"-con_blocksubsidy=0",
34+
"-con_connect_genesis_outputs=1",
35+
"-txindex=1",
36+
]] * self.num_nodes
37+
self.extra_args[0].append("-anyonecanspendaremine=1") # first node gets the coins
38+
39+
def skip_test_if_missing_module(self):
40+
self.skip_if_no_wallet()
41+
42+
def test_send(self, amt, from_idx, to_idx, confidential):
43+
# Try to send those coins to yet another wallet, sending a large enough amount
44+
# that the change output is dropped.
45+
address = self.nodes[to_idx].getnewaddress()
46+
if not confidential:
47+
address = self.nodes[to_idx].getaddressinfo(address)['unconfidential']
48+
txid = self.nodes[from_idx].sendtoaddress(address, amt)
49+
self.log.info(f"Sent {amt} LBTC to node {to_idx} in {txid}")
50+
self.nodes[from_idx].generate(2)
51+
self.sync_all()
52+
53+
for i in range(self.num_nodes):
54+
self.log.info(f"Finished with node {i} balance: {self.nodes[i].getbalance()}")
55+
assert_equal(self.nodes[from_idx].getbalance(), { "bitcoin": Decimal(0) })
56+
assert_equal(self.nodes[to_idx].getbalance(), { "bitcoin": amt })
57+
58+
def run_test(self):
59+
# Mine 101 blocks to get the initial coins out of IBD
60+
self.nodes[0].generate(COINBASE_MATURITY + 1)
61+
self.nodes[0].syncwithvalidationinterfacequeue()
62+
self.sync_all()
63+
64+
for i in range(self.num_nodes):
65+
self.log.info(f"Starting with node {i} balance: {self.nodes[i].getbalance()}")
66+
67+
# Send 1 coin to a new wallet
68+
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
69+
self.log.info(f"Sent one coin to node 1 in {txid}")
70+
self.nodes[0].generate(2)
71+
self.sync_all()
72+
73+
# Try to send those coins to yet another wallet, sending a large enough amount
74+
# that the change output is dropped.
75+
amt = satoshi_round(Decimal(0.9995))
76+
self.test_send(amt, 1, 2, True)
77+
78+
# Repeat, sending to a non-confidential output
79+
amt = satoshi_round(Decimal(amt - Decimal(0.00035)))
80+
self.test_send(amt, 2, 1, False)
81+
82+
# Again, sending from non-confidential to non-confidential
83+
amt = satoshi_round(Decimal(amt - Decimal(0.00033)))
84+
self.test_send(amt, 1, 2, False)
85+
86+
# Finally sending from non-confidential to confidential
87+
amt = satoshi_round(Decimal(amt - Decimal(0.0005)))
88+
self.test_send(amt, 2, 1, True)
89+
90+
# Then send the coins again to make sure they're spendable
91+
amt = satoshi_round(Decimal(amt - Decimal(0.0005)))
92+
self.test_send(amt, 1, 2, True)
93+
94+
addresses = [ self.nodes[1].getnewaddress() for i in range(15) ] \
95+
+ [ self.nodes[2].getnewaddress() for i in range(15) ]
96+
txid = self.nodes[2].sendmany(amounts={address: satoshi_round(Decimal(0.00025)) for address in addresses})
97+
self.log.info(f"Sent many small UTXOs to nodes 1 and 2 in {txid}")
98+
self.nodes[2].generate(2)
99+
self.sync_all()
100+
101+
self.log.info(f"Issuing some assets from node 1")
102+
# Try issuing assets
103+
amt = satoshi_round(Decimal(1))
104+
res1 = self.nodes[1].issueasset(amt, amt, True)
105+
res2 = self.nodes[1].issueasset(amt, amt, False)
106+
107+
assets = [ res1["asset"], res1["token"], res2["asset"], res2["token"] ]
108+
addresses = [ self.nodes[2].getnewaddress() for i in range(len(assets)) ]
109+
txid = self.nodes[1].sendmany(
110+
amounts={address: amt for address in addresses},
111+
output_assets={addresses[i]: assets[i] for i in range(len(assets))},
112+
)
113+
self.log.info(f"Sent them to node 2 in {txid}")
114+
self.nodes[1].generate(2)
115+
self.sync_all()
116+
# Send them back
117+
addresses = [ self.nodes[1].getnewaddress() for i in range(len(assets)) ]
118+
txid = self.nodes[2].sendmany(
119+
amounts={address: amt for address in addresses},
120+
output_assets={addresses[i]: assets[i] for i in range(len(assets))},
121+
)
122+
self.log.info(f"Sent them back to node 1 in {txid}")
123+
124+
if __name__ == '__main__':
125+
WalletCtTest().main()
126+

0 commit comments

Comments
 (0)