mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Merge pull request #1385 from delta1/wallet-receive-gt-21million
assets: allow issuance and transaction amounts of more than MAX_MONEY for non-policy assets
This commit is contained in:
commit
e9a914465d
12 changed files with 134 additions and 26 deletions
|
|
@ -90,15 +90,19 @@ bool operator==(const CAmountMap& a, const CAmountMap& b);
|
|||
bool operator!=(const CAmountMap& a, const CAmountMap& b);
|
||||
bool operator!(const CAmountMap& a); // Check if all values are 0
|
||||
|
||||
inline bool MoneyRange(const CAmountMap& mapValue) {
|
||||
inline bool MoneyRange(const CAmountMap& mapValue, const CAsset& pegged_asset) {
|
||||
for(CAmountMap::const_iterator it = mapValue.begin(); it != mapValue.end(); it++) {
|
||||
if (it->second < 0 || it->second > MAX_MONEY) {
|
||||
if (it->second < 0 || ((pegged_asset.IsNull() || it->first == pegged_asset) && it->second > MAX_MONEY)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool MoneyRange(const CAmountMap& mapValue) {
|
||||
return MoneyRange(mapValue, CAsset());
|
||||
}
|
||||
|
||||
CAmount valueFor(const CAmountMap& mapValue, const CAsset& asset);
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const CAmountMap& map);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <blind.h>
|
||||
#include <chainparams.h>
|
||||
|
||||
#include <hash.h>
|
||||
#include <primitives/transaction.h>
|
||||
|
|
@ -157,11 +158,6 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue&
|
|||
return false;
|
||||
}
|
||||
|
||||
// Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check)
|
||||
if (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convenience pointers to starting point of each recovered 32 byte message
|
||||
unsigned char *asset_type = msg;
|
||||
unsigned char *asset_blinder = msg+32;
|
||||
|
|
@ -172,6 +168,13 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue&
|
|||
return false;
|
||||
}
|
||||
|
||||
CAsset asset{std::vector<unsigned char>{asset_type, asset_type + 32}};
|
||||
|
||||
// Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check)
|
||||
if ((!committedScript.IsUnspendable() && amount == 0) || (asset == Params().GetConsensus().pegged_asset && (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Serialize both generators then compare
|
||||
unsigned char observed_generator[33];
|
||||
unsigned char derived_generator[33];
|
||||
|
|
@ -182,7 +185,7 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue&
|
|||
}
|
||||
|
||||
amount_out = (CAmount)amount;
|
||||
asset_out = CAsset(std::vector<unsigned char>(asset_type, asset_type+32));
|
||||
asset_out = asset;
|
||||
asset_blinding_factor_out = uint256(std::vector<unsigned char>(asset_blinder, asset_blinder+32));
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3049,7 +3049,7 @@ static RPCHelpMan rawissueasset()
|
|||
CAmount asset_amount = 0;
|
||||
const UniValue& asset_amount_uni = issuance_o["asset_amount"];
|
||||
if (asset_amount_uni.isNum()) {
|
||||
asset_amount = AmountFromValue(asset_amount_uni);
|
||||
asset_amount = AmountFromValue(asset_amount_uni, false);
|
||||
if (asset_amount <= 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive");
|
||||
}
|
||||
|
|
@ -3066,7 +3066,7 @@ static RPCHelpMan rawissueasset()
|
|||
CAmount token_amount = 0;
|
||||
const UniValue& token_amount_uni = issuance_o["token_amount"];
|
||||
if (token_amount_uni.isNum()) {
|
||||
token_amount = AmountFromValue(token_amount_uni);
|
||||
token_amount = AmountFromValue(token_amount_uni, false);
|
||||
if (token_amount <= 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, token_amount must be positive");
|
||||
}
|
||||
|
|
@ -3177,7 +3177,7 @@ static RPCHelpMan rawreissueasset()
|
|||
CAmount asset_amount = 0;
|
||||
const UniValue& asset_amount_uni = issuance_o["asset_amount"];
|
||||
if (asset_amount_uni.isNum()) {
|
||||
asset_amount = AmountFromValue(asset_amount_uni);
|
||||
asset_amount = AmountFromValue(asset_amount_uni, false);
|
||||
if (asset_amount <= 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,14 +90,14 @@ void RPCTypeCheckObj(const UniValue& o,
|
|||
}
|
||||
}
|
||||
|
||||
CAmount AmountFromValue(const UniValue& value, int decimals)
|
||||
CAmount AmountFromValue(const UniValue& value, bool check_range, int decimals)
|
||||
{
|
||||
if (!value.isNum() && !value.isStr())
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string");
|
||||
CAmount amount;
|
||||
if (!ParseFixedPoint(value.getValStr(), decimals, &amount))
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount");
|
||||
if (!MoneyRange(amount))
|
||||
if (amount < 0 || (check_range && !MoneyRange(amount)))
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Amount out of range");
|
||||
return amount;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey);
|
|||
* @param[in] decimals Number of significant digits (default: 8).
|
||||
* @returns a CAmount if the various checks pass.
|
||||
*/
|
||||
CAmount AmountFromValue(const UniValue& value, int decimals = 8);
|
||||
CAmount AmountFromValue(const UniValue& value, bool check_range = true, int decimals = 8);
|
||||
|
||||
using RPCArgList = std::vector<std::pair<std::string, UniValue>>;
|
||||
std::string HelpExampleCli(const std::string& methodname, const std::string& args);
|
||||
|
|
|
|||
|
|
@ -70,14 +70,17 @@ CAmountMap TxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const ismine
|
|||
{
|
||||
LOCK(wallet.cs_wallet);
|
||||
|
||||
CAsset pegged_asset{Params().GetConsensus().pegged_asset};
|
||||
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) {
|
||||
if (wallet.IsMine(wtx.tx->vout[i]) & filter) {
|
||||
CAsset asset{wtx.GetOutputAsset(wallet, i)};
|
||||
CAmount credit = std::max<CAmount>(0, wtx.GetOutputValueOut(wallet, i));
|
||||
if (!MoneyRange(credit))
|
||||
if (asset == pegged_asset && !MoneyRange(credit)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": value out of range");
|
||||
}
|
||||
|
||||
nCredit[wtx.GetOutputAsset(wallet, i)] += credit;
|
||||
if (!MoneyRange(nCredit))
|
||||
nCredit[asset] += credit;
|
||||
if (!MoneyRange(nCredit, pegged_asset))
|
||||
throw std::runtime_error(std::string(__func__) + ": value out of range");
|
||||
}
|
||||
}
|
||||
|
|
@ -226,16 +229,18 @@ CAmountMap CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wt
|
|||
bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||
CAmountMap nCredit;
|
||||
uint256 hashTx = wtx.GetHash();
|
||||
CAsset pegged_asset{Params().GetConsensus().pegged_asset};
|
||||
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
|
||||
{
|
||||
if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) {
|
||||
if (wallet.IsMine(wtx.tx->vout[i]) & filter) {
|
||||
CAsset asset = wtx.GetOutputAsset(wallet, i);
|
||||
CAmount credit = std::max<CAmount>(0, wtx.GetOutputValueOut(wallet, i));
|
||||
if (!MoneyRange(credit))
|
||||
if (asset == pegged_asset && !MoneyRange(credit))
|
||||
throw std::runtime_error(std::string(__func__) + ": value out of range");
|
||||
|
||||
nCredit[wtx.GetOutputAsset(wallet, i)] += std::max<CAmount>(0, wtx.GetOutputValueOut(wallet, i));
|
||||
if (!MoneyRange(nCredit))
|
||||
if (!MoneyRange(nCredit, pegged_asset))
|
||||
throw std::runtime_error(std::string(__func__) + ": value out of range");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1426,8 +1426,8 @@ RPCHelpMan issueasset()
|
|||
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>`");
|
||||
}
|
||||
|
||||
CAmount nAmount = AmountFromValue(request.params[0]);
|
||||
CAmount nTokens = AmountFromValue(request.params[1]);
|
||||
CAmount nAmount = AmountFromValue(request.params[0], false);
|
||||
CAmount nTokens = AmountFromValue(request.params[1], false);
|
||||
if (nAmount == 0 && nTokens == 0) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Issuance must have one non-zero component");
|
||||
}
|
||||
|
|
@ -1524,7 +1524,7 @@ RPCHelpMan reissueasset()
|
|||
std::string assetstr = request.params[0].get_str();
|
||||
CAsset asset = GetAssetFromString(assetstr);
|
||||
|
||||
CAmount nAmount = AmountFromValue(request.params[1]);
|
||||
CAmount nAmount = AmountFromValue(request.params[1], false);
|
||||
if (nAmount <= 0) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Reissuance must create a non-zero amount.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& add
|
|||
destinations.insert(dest);
|
||||
|
||||
CScript script_pub_key = GetScriptForDestination(dest);
|
||||
CAmount amount = AmountFromValue(address_amounts[i++]);
|
||||
CAmount amount = AmountFromValue(address_amounts[i++], asset == Params().GetConsensus().pegged_asset);
|
||||
|
||||
bool subtract_fee = false;
|
||||
for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) {
|
||||
|
|
@ -124,7 +124,7 @@ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const Un
|
|||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
|
||||
}
|
||||
// Fee rates in sat/vB cannot represent more than 3 significant digits.
|
||||
cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /* decimals */ 3)};
|
||||
cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /*check_range=*/true, /*decimals=*/3)};
|
||||
if (override_min_fee) cc.fOverrideFeeRate = true;
|
||||
// Default RBF to true for explicit fee_rate, if unset.
|
||||
if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true;
|
||||
|
|
|
|||
|
|
@ -265,8 +265,9 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput> &vCoins, const C
|
|||
if (asset_filter && asset != *asset_filter) {
|
||||
continue;
|
||||
}
|
||||
if (outValue < nMinimumAmount || outValue > nMaximumAmount)
|
||||
if (outValue < nMinimumAmount || (asset == Params().GetConsensus().pegged_asset && outValue > nMaximumAmount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -1418,7 +1418,7 @@ CAmountMap CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter)
|
|||
for (const CTxIn& txin : tx.vin)
|
||||
{
|
||||
nDebit += GetDebit(txin, filter);
|
||||
if (!MoneyRange(nDebit))
|
||||
if (!MoneyRange(nDebit, Params().GetConsensus().pegged_asset))
|
||||
throw std::runtime_error(std::string(__func__) + ": value out of range");
|
||||
}
|
||||
return nDebit;
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ BASE_SCRIPTS = [
|
|||
'rpc_getnewblockhex.py',
|
||||
'wallet_elements_regression_1172.py --legacy-wallet',
|
||||
'wallet_elements_regression_1259.py --legacy-wallet',
|
||||
'wallet_elements_21million.py',
|
||||
'feature_trim_headers.py',
|
||||
# Longest test should go first, to favor running tests in parallel
|
||||
'wallet_hd.py --legacy-wallet',
|
||||
|
|
|
|||
94
test/functional/wallet_elements_21million.py
Executable file
94
test/functional/wallet_elements_21million.py
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2017-2020 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from test_framework.blocktools import COINBASE_MATURITY
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
)
|
||||
|
||||
class WalletTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 3
|
||||
self.extra_args = [['-blindedaddresses=1']] * self.num_nodes
|
||||
|
||||
def setup_network(self, split=False):
|
||||
self.setup_nodes()
|
||||
self.connect_nodes(0, 1)
|
||||
self.connect_nodes(1, 2)
|
||||
self.connect_nodes(0, 2)
|
||||
self.sync_all()
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
self.generate(self.nodes[0], COINBASE_MATURITY + 1)
|
||||
|
||||
assert_equal(self.nodes[0].getbalance(), {'bitcoin': 50})
|
||||
assert_equal(self.nodes[1].getbalance(), {'bitcoin': 0})
|
||||
|
||||
self.log.info("Issue more than 21 million of a non-policy asset")
|
||||
issuance = self.nodes[0].issueasset(100_000_000, 100)
|
||||
asset = issuance['asset']
|
||||
self.generate(self.nodes[0], 1)
|
||||
assert_equal(self.nodes[0].getbalance()[asset], 100_000_000)
|
||||
|
||||
self.log.info("Reissue more than 21 million of a non-policy asset")
|
||||
self.nodes[0].reissueasset(asset, 100_000_000)
|
||||
self.generate(self.nodes[0], 1)
|
||||
assert_equal(self.nodes[0].getbalance()[asset], 200_000_000)
|
||||
|
||||
# send more than 21 million of that asset
|
||||
addr = self.nodes[1].getnewaddress()
|
||||
self.nodes[0].sendtoaddress(address=addr, amount=22_000_000, assetlabel=asset)
|
||||
self.generate(self.nodes[0], 1)
|
||||
assert_equal(self.nodes[0].getbalance()[asset], 178_000_000)
|
||||
assert_equal(self.nodes[1].getbalance()[asset], 22_000_000)
|
||||
|
||||
# unload/load wallet
|
||||
self.nodes[1].unloadwallet("")
|
||||
self.nodes[1].loadwallet("")
|
||||
assert_equal(self.nodes[1].getbalance()[asset], 22_000_000)
|
||||
|
||||
# send more than 45 million of that asset
|
||||
addr = self.nodes[2].getnewaddress()
|
||||
self.nodes[0].sendtoaddress(address=addr, amount=46_000_000, assetlabel=asset)
|
||||
self.generate(self.nodes[0], 1)
|
||||
assert_equal(self.nodes[0].getbalance()[asset], 132_000_000)
|
||||
assert_equal(self.nodes[2].getbalance()[asset], 46_000_000)
|
||||
|
||||
# unload/load wallet
|
||||
self.nodes[2].unloadwallet("")
|
||||
self.nodes[2].loadwallet("")
|
||||
assert_equal(self.nodes[2].getbalance()[asset], 46_000_000)
|
||||
|
||||
# send some policy asset to node 1 for fees
|
||||
addr = self.nodes[1].getnewaddress()
|
||||
self.nodes[0].sendtoaddress(address=addr, amount=1)
|
||||
self.generate(self.nodes[0], 1)
|
||||
assert_equal(self.nodes[1].getbalance()['bitcoin'], 1)
|
||||
assert_equal(self.nodes[1].getbalance()[asset], 22_000_000)
|
||||
|
||||
# send the remainders
|
||||
addr = self.nodes[2].getnewaddress()
|
||||
self.nodes[0].sendtoaddress(address=addr, amount=132_000_000, assetlabel=asset)
|
||||
addr = self.nodes[2].getnewaddress()
|
||||
self.nodes[1].sendtoaddress(address=addr, amount=22_000_000, assetlabel=asset)
|
||||
self.sync_mempools()
|
||||
self.generate(self.nodes[0], 1)
|
||||
|
||||
assert asset not in self.nodes[0].getbalance()
|
||||
assert asset not in self.nodes[1].getbalance()
|
||||
assert_equal(self.nodes[2].getbalance()[asset], 200_000_000)
|
||||
|
||||
# unload/load wallet
|
||||
self.nodes[2].unloadwallet("")
|
||||
self.nodes[2].loadwallet("")
|
||||
assert_equal(self.nodes[2].getbalance()[asset], 200_000_000)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletTest().main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue