Skip to content

Commit ae2a8a4

Browse files
committed
Merge e9a9144 into merged_master (Elements PR ElementsProject#1385)
2 parents ca0a68b + e9a9144 commit ae2a8a4

File tree

12 files changed

+134
-26
lines changed

12 files changed

+134
-26
lines changed

src/asset.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,19 @@ bool operator==(const CAmountMap& a, const CAmountMap& b);
9090
bool operator!=(const CAmountMap& a, const CAmountMap& b);
9191
bool operator!(const CAmountMap& a); // Check if all values are 0
9292

93-
inline bool MoneyRange(const CAmountMap& mapValue) {
93+
inline bool MoneyRange(const CAmountMap& mapValue, const CAsset& pegged_asset) {
9494
for(CAmountMap::const_iterator it = mapValue.begin(); it != mapValue.end(); it++) {
95-
if (it->second < 0 || it->second > MAX_MONEY) {
95+
if (it->second < 0 || ((pegged_asset.IsNull() || it->first == pegged_asset) && it->second > MAX_MONEY)) {
9696
return false;
9797
}
9898
}
9999
return true;
100100
}
101101

102+
inline bool MoneyRange(const CAmountMap& mapValue) {
103+
return MoneyRange(mapValue, CAsset());
104+
}
105+
102106
CAmount valueFor(const CAmountMap& mapValue, const CAsset& asset);
103107

104108
std::ostream& operator<<(std::ostream& out, const CAmountMap& map);

src/blind.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <blind.h>
6+
#include <chainparams.h>
67

78
#include <hash.h>
89
#include <primitives/transaction.h>
@@ -157,11 +158,6 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue&
157158
return false;
158159
}
159160

160-
// Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check)
161-
if (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) {
162-
return false;
163-
}
164-
165161
// Convenience pointers to starting point of each recovered 32 byte message
166162
unsigned char *asset_type = msg;
167163
unsigned char *asset_blinder = msg+32;
@@ -172,6 +168,13 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue&
172168
return false;
173169
}
174170

171+
CAsset asset{std::vector<unsigned char>{asset_type, asset_type + 32}};
172+
173+
// Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check)
174+
if ((!committedScript.IsUnspendable() && amount == 0) || (asset == Params().GetConsensus().pegged_asset && (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)))) {
175+
return false;
176+
}
177+
175178
// Serialize both generators then compare
176179
unsigned char observed_generator[33];
177180
unsigned char derived_generator[33];
@@ -182,7 +185,7 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue&
182185
}
183186

184187
amount_out = (CAmount)amount;
185-
asset_out = CAsset(std::vector<unsigned char>(asset_type, asset_type+32));
188+
asset_out = asset;
186189
asset_blinding_factor_out = uint256(std::vector<unsigned char>(asset_blinder, asset_blinder+32));
187190
return true;
188191
}

src/rpc/rawtransaction.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,7 +2799,7 @@ static RPCHelpMan rawissueasset()
27992799
CAmount asset_amount = 0;
28002800
const UniValue& asset_amount_uni = issuance_o["asset_amount"];
28012801
if (asset_amount_uni.isNum()) {
2802-
asset_amount = AmountFromValue(asset_amount_uni);
2802+
asset_amount = AmountFromValue(asset_amount_uni, false);
28032803
if (asset_amount <= 0) {
28042804
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive");
28052805
}
@@ -2816,7 +2816,7 @@ static RPCHelpMan rawissueasset()
28162816
CAmount token_amount = 0;
28172817
const UniValue& token_amount_uni = issuance_o["token_amount"];
28182818
if (token_amount_uni.isNum()) {
2819-
token_amount = AmountFromValue(token_amount_uni);
2819+
token_amount = AmountFromValue(token_amount_uni, false);
28202820
if (token_amount <= 0) {
28212821
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, token_amount must be positive");
28222822
}
@@ -2927,7 +2927,7 @@ static RPCHelpMan rawreissueasset()
29272927
CAmount asset_amount = 0;
29282928
const UniValue& asset_amount_uni = issuance_o["asset_amount"];
29292929
if (asset_amount_uni.isNum()) {
2930-
asset_amount = AmountFromValue(asset_amount_uni);
2930+
asset_amount = AmountFromValue(asset_amount_uni, false);
29312931
if (asset_amount <= 0) {
29322932
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive");
29332933
}

src/rpc/util.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,14 @@ void RPCTypeCheckObj(const UniValue& o,
9090
}
9191
}
9292

93-
CAmount AmountFromValue(const UniValue& value, int decimals)
93+
CAmount AmountFromValue(const UniValue& value, bool check_range, int decimals)
9494
{
9595
if (!value.isNum() && !value.isStr())
9696
throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string");
9797
CAmount amount;
9898
if (!ParseFixedPoint(value.getValStr(), decimals, &amount))
9999
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount");
100-
if (!MoneyRange(amount))
100+
if (amount < 0 || (check_range && !MoneyRange(amount)))
101101
throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range");
102102
return amount;
103103
}

src/rpc/util.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey);
9898
* @param[in] decimals Number of significant digits (default: 8).
9999
* @returns a CAmount if the various checks pass.
100100
*/
101-
CAmount AmountFromValue(const UniValue& value, int decimals = 8);
101+
CAmount AmountFromValue(const UniValue& value, bool check_range = true, int decimals = 8);
102102

103103
using RPCArgList = std::vector<std::pair<std::string, UniValue>>;
104104
std::string HelpExampleCli(const std::string& methodname, const std::string& args);

src/wallet/receive.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,17 @@ CAmountMap TxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const ismine
5555
{
5656
LOCK(wallet.cs_wallet);
5757

58+
CAsset pegged_asset{Params().GetConsensus().pegged_asset};
5859
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
5960
if (wallet.IsMine(wtx.tx->vout[i]) & filter) {
61+
CAsset asset{wtx.GetOutputAsset(wallet, i)};
6062
CAmount credit = std::max<CAmount>(0, wtx.GetOutputValueOut(wallet, i));
61-
if (!MoneyRange(credit))
63+
if (asset == pegged_asset && !MoneyRange(credit)) {
6264
throw std::runtime_error(std::string(__func__) + ": value out of range");
65+
}
6366

64-
nCredit[wtx.GetOutputAsset(wallet, i)] += credit;
65-
if (!MoneyRange(nCredit))
67+
nCredit[asset] += credit;
68+
if (!MoneyRange(nCredit, pegged_asset))
6669
throw std::runtime_error(std::string(__func__) + ": value out of range");
6770
}
6871
}
@@ -204,16 +207,18 @@ CAmountMap CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wt
204207
bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
205208
CAmountMap nCredit;
206209
uint256 hashTx = wtx.GetHash();
210+
CAsset pegged_asset{Params().GetConsensus().pegged_asset};
207211
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
208212
const CTxOut& txout = wtx.tx->vout[i];
209213
if (!wallet.IsSpent(COutPoint(hashTx, i)) && (allow_used_addresses || !wallet.IsSpentKey(txout.scriptPubKey))) {
210214
if (wallet.IsMine(wtx.tx->vout[i]) & filter) {
215+
CAsset asset = wtx.GetOutputAsset(wallet, i);
211216
CAmount credit = std::max<CAmount>(0, wtx.GetOutputValueOut(wallet, i));
212-
if (!MoneyRange(credit))
217+
if (asset == pegged_asset && !MoneyRange(credit))
213218
throw std::runtime_error(std::string(__func__) + ": value out of range");
214219

215220
nCredit[wtx.GetOutputAsset(wallet, i)] += std::max<CAmount>(0, wtx.GetOutputValueOut(wallet, i));
216-
if (!MoneyRange(nCredit))
221+
if (!MoneyRange(nCredit, pegged_asset))
217222
throw std::runtime_error(std::string(__func__) + ": value out of range");
218223
}
219224
}

src/wallet/rpc/elements.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,8 +1412,8 @@ RPCHelpMan issueasset()
14121412
throw JSONRPCError(RPC_TYPE_ERROR, "Issuance can only be done on elements-style chains. Note: `-regtest` is Bitcoin's regtest mode, instead try `-chain=<custom chain name>`");
14131413
}
14141414

1415-
CAmount nAmount = AmountFromValue(request.params[0]);
1416-
CAmount nTokens = AmountFromValue(request.params[1]);
1415+
CAmount nAmount = AmountFromValue(request.params[0], false);
1416+
CAmount nTokens = AmountFromValue(request.params[1], false);
14171417
if (nAmount == 0 && nTokens == 0) {
14181418
throw JSONRPCError(RPC_TYPE_ERROR, "Issuance must have one non-zero component");
14191419
}
@@ -1517,7 +1517,7 @@ RPCHelpMan reissueasset()
15171517
std::string assetstr = request.params[0].get_str();
15181518
CAsset asset = GetAssetFromString(assetstr);
15191519

1520-
CAmount nAmount = AmountFromValue(request.params[1]);
1520+
CAmount nAmount = AmountFromValue(request.params[1], false);
15211521
if (nAmount <= 0) {
15221522
throw JSONRPCError(RPC_TYPE_ERROR, "Reissuance must create a non-zero amount.");
15231523
}

src/wallet/rpc/spend.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& add
5353
destinations.insert(dest);
5454

5555
CScript script_pub_key = GetScriptForDestination(dest);
56-
CAmount amount = AmountFromValue(address_amounts[i++]);
56+
CAmount amount = AmountFromValue(address_amounts[i++], asset == Params().GetConsensus().pegged_asset);
5757

5858
bool subtract_fee = false;
5959
for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) {
@@ -211,7 +211,7 @@ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const Un
211211
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
212212
}
213213
// Fee rates in sat/vB cannot represent more than 3 significant digits.
214-
cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /*decimals=*/3)};
214+
cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /*check_range=*/true, /*decimals=*/3)};
215215
if (override_min_fee) cc.fOverrideFeeRate = true;
216216
// Default RBF to true for explicit fee_rate, if unset.
217217
if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true;

src/wallet/spend.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,9 @@ CoinsResult AvailableCoins(const CWallet& wallet,
330330
if (asset_filter && asset != *asset_filter) {
331331
continue;
332332
}
333-
if (outValue < nMinimumAmount || outValue > nMaximumAmount)
333+
if (outValue < nMinimumAmount || (asset == Params().GetConsensus().pegged_asset && outValue > nMaximumAmount)) {
334334
continue;
335+
}
335336

336337
if (coinControl && coinControl->HasSelected() && !coinControl->m_allow_other_inputs && !coinControl->IsSelected(outpoint))
337338
continue;

src/wallet/wallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1532,7 +1532,7 @@ CAmountMap CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter)
15321532
for (const CTxIn& txin : tx.vin)
15331533
{
15341534
nDebit += GetDebit(txin, filter);
1535-
if (!MoneyRange(nDebit))
1535+
if (!MoneyRange(nDebit, Params().GetConsensus().pegged_asset))
15361536
throw std::runtime_error(std::string(__func__) + ": value out of range");
15371537
}
15381538
return nDebit;

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
'rpc_getnewblockhex.py',
115115
'wallet_elements_regression_1172.py --legacy-wallet',
116116
'wallet_elements_regression_1259.py --legacy-wallet',
117+
'wallet_elements_21million.py',
117118
# Longest test should go first, to favor running tests in parallel
118119
'wallet_hd.py --legacy-wallet',
119120
'wallet_hd.py --descriptors',
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
6+
from test_framework.blocktools import COINBASE_MATURITY
7+
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import (
9+
assert_equal,
10+
)
11+
12+
class WalletTest(BitcoinTestFramework):
13+
def set_test_params(self):
14+
self.setup_clean_chain = True
15+
self.num_nodes = 3
16+
self.extra_args = [['-blindedaddresses=1']] * self.num_nodes
17+
18+
def setup_network(self, split=False):
19+
self.setup_nodes()
20+
self.connect_nodes(0, 1)
21+
self.connect_nodes(1, 2)
22+
self.connect_nodes(0, 2)
23+
self.sync_all()
24+
25+
def skip_test_if_missing_module(self):
26+
self.skip_if_no_wallet()
27+
28+
def run_test(self):
29+
self.generate(self.nodes[0], COINBASE_MATURITY + 1)
30+
31+
assert_equal(self.nodes[0].getbalance(), {'bitcoin': 50})
32+
assert_equal(self.nodes[1].getbalance(), {'bitcoin': 0})
33+
34+
self.log.info("Issue more than 21 million of a non-policy asset")
35+
issuance = self.nodes[0].issueasset(100_000_000, 100)
36+
asset = issuance['asset']
37+
self.generate(self.nodes[0], 1)
38+
assert_equal(self.nodes[0].getbalance()[asset], 100_000_000)
39+
40+
self.log.info("Reissue more than 21 million of a non-policy asset")
41+
self.nodes[0].reissueasset(asset, 100_000_000)
42+
self.generate(self.nodes[0], 1)
43+
assert_equal(self.nodes[0].getbalance()[asset], 200_000_000)
44+
45+
# send more than 21 million of that asset
46+
addr = self.nodes[1].getnewaddress()
47+
self.nodes[0].sendtoaddress(address=addr, amount=22_000_000, assetlabel=asset)
48+
self.generate(self.nodes[0], 1)
49+
assert_equal(self.nodes[0].getbalance()[asset], 178_000_000)
50+
assert_equal(self.nodes[1].getbalance()[asset], 22_000_000)
51+
52+
# unload/load wallet
53+
self.nodes[1].unloadwallet("")
54+
self.nodes[1].loadwallet("")
55+
assert_equal(self.nodes[1].getbalance()[asset], 22_000_000)
56+
57+
# send more than 45 million of that asset
58+
addr = self.nodes[2].getnewaddress()
59+
self.nodes[0].sendtoaddress(address=addr, amount=46_000_000, assetlabel=asset)
60+
self.generate(self.nodes[0], 1)
61+
assert_equal(self.nodes[0].getbalance()[asset], 132_000_000)
62+
assert_equal(self.nodes[2].getbalance()[asset], 46_000_000)
63+
64+
# unload/load wallet
65+
self.nodes[2].unloadwallet("")
66+
self.nodes[2].loadwallet("")
67+
assert_equal(self.nodes[2].getbalance()[asset], 46_000_000)
68+
69+
# send some policy asset to node 1 for fees
70+
addr = self.nodes[1].getnewaddress()
71+
self.nodes[0].sendtoaddress(address=addr, amount=1)
72+
self.generate(self.nodes[0], 1)
73+
assert_equal(self.nodes[1].getbalance()['bitcoin'], 1)
74+
assert_equal(self.nodes[1].getbalance()[asset], 22_000_000)
75+
76+
# send the remainders
77+
addr = self.nodes[2].getnewaddress()
78+
self.nodes[0].sendtoaddress(address=addr, amount=132_000_000, assetlabel=asset)
79+
addr = self.nodes[2].getnewaddress()
80+
self.nodes[1].sendtoaddress(address=addr, amount=22_000_000, assetlabel=asset)
81+
self.sync_mempools()
82+
self.generate(self.nodes[0], 1)
83+
84+
assert asset not in self.nodes[0].getbalance()
85+
assert asset not in self.nodes[1].getbalance()
86+
assert_equal(self.nodes[2].getbalance()[asset], 200_000_000)
87+
88+
# unload/load wallet
89+
self.nodes[2].unloadwallet("")
90+
self.nodes[2].loadwallet("")
91+
assert_equal(self.nodes[2].getbalance()[asset], 200_000_000)
92+
93+
if __name__ == '__main__':
94+
WalletTest().main()

0 commit comments

Comments
 (0)