diff --git a/src/confidential_validation.cpp b/src/confidential_validation.cpp index 62190a92fd..2054336c83 100644 --- a/src/confidential_validation.cpp +++ b/src/confidential_validation.cpp @@ -1,4 +1,5 @@ +#include #include #include #include @@ -102,14 +103,13 @@ static bool VerifyIssuanceAmount(secp256k1_pedersen_commitment& value_commit, se // Build value commitment if (value.IsExplicit()) { - if (!MoneyRange(value.GetAmount()) || value.GetAmount() == 0) { + if ((asset == Params().GetConsensus().pegged_asset && !MoneyRange(value.GetAmount())) || value.GetAmount() <= 0) { return false; } if (!rangeproof.empty()) { return false; } - ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &value_commit, explicit_blinds, value.GetAmount(), &asset_gen); // The explicit_blinds are all 0, and the amount is not 0. So secp256k1_pedersen_commit does not fail. assert(ret == 1); diff --git a/src/init.cpp b/src/init.cpp index 27addf275a..714b51786c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -621,6 +621,7 @@ void SetupServerArgs(ArgsManager& argsman) std::vector elements_hidden_args = {"-con_fpowallowmindifficultyblocks", "-con_fpownoretargeting", "-con_nsubsidyhalvinginterval", "-con_bip16exception", "-con_bip34height", "-con_bip65height", "-con_bip66height", "-con_npowtargettimespan", "-con_npowtargetspacing", "-con_nrulechangeactivationthreshold", "-con_nminerconfirmationwindow", "-con_powlimit", "-con_bip34hash", "-con_nminimumchainwork", "-con_defaultassumevalid", "-npruneafterheight", "-fdefaultconsistencychecks", "-fmineblocksondemand", "-fallback_fee_enabled", "-pchmessagestart"}; + argsman.AddArg("-acceptunlimitedissuances", strprintf("Relay and mine unblinded issuance transactions (default: %u)", DEFAULT_ACCEPT_UNLIMITED_ISSUANCES), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); argsman.AddArg("-initialfreecoins", strprintf("The amount of OP_TRUE coins created in the genesis block. Primarily for testing. (default: %d)", 0), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-validatepegin", "Validate peg-in claims. An RPC connection will be attempted to the trusted mainchain daemon using the `mainchain*` settings below. All functionaries must run this enabled. (default: 1 if chain has federated peg)", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); argsman.AddArg("-mainchainrpchost=", "The address which the daemon will try to connect to the trusted mainchain daemon to validate peg-ins, if enabled. (default: 127.0.0.1)", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); @@ -1076,6 +1077,7 @@ bool AppInitParameterInteraction(const ArgsManager& args) fIsBareMultisigStd = args.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG); fAcceptDatacarrier = args.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER); nMaxDatacarrierBytes = args.GetIntArg("-datacarriersize", nMaxDatacarrierBytes); + fAcceptUnlimitedIssuances = args.GetBoolArg("-acceptunlimitedissuances", DEFAULT_ACCEPT_UNLIMITED_ISSUANCES); // Option to startup with mocktime set (used for regression testing): SetMockTime(args.GetIntArg("-mocktime", 0)); // SetMockTime(0) is a no-op diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 99435154d2..27c246ec04 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -308,6 +308,22 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) return true; } +bool IsIssuanceInMoneyRange(const CTransaction& tx) +{ + for (size_t i = 0; i < tx.vin.size(); ++i) { + const CAssetIssuance& issuance = tx.vin[i].assetIssuance; + if (issuance.IsNull()) { + continue; + } + if (issuance.nAmount.IsExplicit()) { + if (!MoneyRange(issuance.nAmount.GetAmount())) { + return false; + } + } + } + return true; +} + int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop) { return (std::max(nWeight, nSigOpCost * bytes_per_sigop) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR; diff --git a/src/policy/policy.h b/src/policy/policy.h index 944d65d0c5..38a3ed3065 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -92,6 +92,9 @@ static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_S // ELEMENTS: keep a copy of the upstream default dust relay fee rate static const unsigned int DUST_RELAY_TX_FEE_BITCOIN = 3000; +// ELEMENTS: allow unblinded issuances/reissuances greater than MAX_MONEY +static const bool DEFAULT_ACCEPT_UNLIMITED_ISSUANCES = true; + CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee); @@ -124,6 +127,11 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) */ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); +/* ELEMENTS +* Check if blinded issuance/reissuance is greater than MAX_MONEY +*/ +bool IsIssuanceInMoneyRange(const CTransaction& tx); + /** Compute the virtual transaction size (weight reinterpreted as bytes). */ int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop); int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost, unsigned int bytes_per_sigop); diff --git a/src/validation.cpp b/src/validation.cpp index d62f942720..f91534da1c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -151,6 +151,7 @@ bool g_parallel_script_checks{false}; bool fRequireStandard = true; bool fCheckBlockIndex = false; bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED; +bool fAcceptUnlimitedIssuances = true; int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; uint256 hashAssumeValid; @@ -722,6 +723,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } } + // Reject if unblinded issuance/reissuance is not in MoneyRange and acceptunlimitedissuances false + if (!fAcceptUnlimitedIssuances && !IsIssuanceInMoneyRange(tx)) + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "issuance-out-of-range"); + // Do not work on transactions that are too small. // A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes. // Transactions smaller than this are not relayed to mitigate CVE-2017-12842 by not relaying diff --git a/src/validation.h b/src/validation.h index 78fa8bea6a..f66ea6555b 100644 --- a/src/validation.h +++ b/src/validation.h @@ -130,6 +130,7 @@ extern bool g_parallel_script_checks; extern bool fRequireStandard; extern bool fCheckBlockIndex; extern bool fCheckpointsEnabled; +extern bool fAcceptUnlimitedIssuances; /** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ extern CFeeRate minRelayTxFee; /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ diff --git a/test/functional/wallet_elements_21million.py b/test/functional/wallet_elements_21million.py index ea6b34dd6f..0a4fed3191 100755 --- a/test/functional/wallet_elements_21million.py +++ b/test/functional/wallet_elements_21million.py @@ -7,13 +7,21 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, ) 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 + args = [ + "-blindedaddresses=1" + ] + self.extra_args = [ + args, + args, + args + ["-acceptunlimitedissuances=0"], # node 2 blocks unblinded issuances out of moneyrange + ] def setup_network(self, split=False): self.setup_nodes() @@ -42,6 +50,17 @@ def run_test(self): self.generate(self.nodes[0], 1) assert_equal(self.nodes[0].getbalance()[asset], 200_000_000) + self.log.info("Issue more than 21 million of a unblinded non-policy asset") + issuance = self.nodes[0].issueasset(300_000_000, 100, False) + unblinded_asset = issuance['asset'] + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[0].getbalance()[unblinded_asset], 300_000_000) + + self.log.info("Reissue more than 21 million of a unblinded non-policy asset") + self.nodes[0].reissueasset(unblinded_asset, 200_000_000) + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[0].getbalance()[unblinded_asset], 500_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) @@ -90,5 +109,23 @@ def run_test(self): self.nodes[2].loadwallet(self.default_wallet_name) assert_equal(self.nodes[2].getbalance()[asset], 200_000_000) + # send some policy asset to node 2 for fees + addr = self.nodes[2].getnewaddress() + self.nodes[0].sendtoaddress(address=addr, amount=1) + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[2].getbalance()['bitcoin'], 1) + + self.log.info("Issue more than 21 million of a non-policy asset on node 2 - rejected from mempool") + issuance = self.nodes[2].issueasset(300_000_000, 100, False) + issuance_tx = self.nodes[2].gettransaction(issuance["txid"]) + assert_raises_rpc_error(-26, "issuance-out-of-range", self.nodes[2].sendrawtransaction, issuance_tx['hex']) + # transaction should be accepted on node 0 + self.nodes[0].sendrawtransaction(issuance_tx["hex"]) + asset = issuance['asset'] + assert(issuance['txid'] in self.nodes[0].getrawmempool()) + assert(issuance['txid'] not in self.nodes[2].getrawmempool()) + self.generate(self.nodes[0], 1) + assert_equal(self.nodes[2].getbalance()[asset], 300_000_000) + if __name__ == '__main__': WalletTest().main()