diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 6b16ba246f..3e30746fc4 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -52,6 +52,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-total_valid_epochs", "Per-chain parameter that sets how long a particular fedpegscript is in effect for.", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); argsman.AddArg("-evbparams=deployment:start:end:period:threshold", "Use given start/end times for specified version bits deployment (regtest or custom only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::ELEMENTS); argsman.AddArg("-con_start_p2wsh_script", "Create p2wsh addresses when starting in dynafed mode (regtest or custom only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::ELEMENTS); + argsman.AddArg("-acceptunlimitedissuances", "Allow unblinded issuance amounts to exceed 21 million units", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); // END ELEMENTS // } diff --git a/src/confidential_validation.cpp b/src/confidential_validation.cpp index c108985db8..5663256b18 100644 --- a/src/confidential_validation.cpp +++ b/src/confidential_validation.cpp @@ -1,4 +1,5 @@ +#include #include #include #include @@ -103,14 +104,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/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 6f8c120324..43c95cce65 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -241,6 +241,7 @@ public: consensus.subsidy_asset = CAsset(); anyonecanspend_aremine = false; enforce_pak = false; + accept_unlimited_issuances = false; multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; @@ -385,6 +386,7 @@ public: consensus.subsidy_asset = CAsset(); anyonecanspend_aremine = false; enforce_pak = false; + accept_unlimited_issuances = false; multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; @@ -547,6 +549,7 @@ public: consensus.subsidy_asset = CAsset(); anyonecanspend_aremine = false; enforce_pak = false; + accept_unlimited_issuances = false; multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; @@ -658,6 +661,7 @@ public: consensus.subsidy_asset = CAsset(); anyonecanspend_aremine = false; enforce_pak = false; + accept_unlimited_issuances = false; multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; @@ -949,6 +953,8 @@ protected: enforce_pak = args.GetBoolArg("-enforce_pak", false); + accept_unlimited_issuances = args.GetBoolArg("-acceptunlimitedissuances", false); + // Allow multiple op_return outputs by relay policy multi_data_permitted = args.GetBoolArg("-multi_data_permitted", enforce_pak); @@ -1204,6 +1210,8 @@ public: enforce_pak = true; + accept_unlimited_issuances = false; + multi_data_permitted = true; create_discount_ct = args.GetBoolArg("-creatediscountct", false); accept_discount_ct = args.GetBoolArg("-acceptdiscountct", true) || create_discount_ct; @@ -1557,6 +1565,8 @@ public: enforce_pak = args.GetBoolArg("-enforce_pak", enforce_pak); + accept_unlimited_issuances = false; + multi_data_permitted = args.GetBoolArg("-multi_data_permitted", multi_data_permitted); create_discount_ct = args.GetBoolArg("-creatediscountct", create_discount_ct); accept_discount_ct = args.GetBoolArg("-acceptdiscountct", accept_discount_ct) || create_discount_ct; diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h index ee06b9f370..fa1294fd0c 100644 --- a/src/kernel/chainparams.h +++ b/src/kernel/chainparams.h @@ -142,6 +142,7 @@ public: const std::string& ParentBech32HRP() const { return parent_bech32_hrp; } const std::string& ParentBlech32HRP() const { return parent_blech32_hrp; } bool GetEnforcePak() const { return enforce_pak; } + bool GetAcceptUnlimitedIssuances() const { return accept_unlimited_issuances; } bool GetMultiDataPermitted() const { return multi_data_permitted; } bool GetAcceptDiscountCT() const { return accept_discount_ct; } bool GetCreateDiscountCT() const { return create_discount_ct; } @@ -209,6 +210,7 @@ protected: std::string parent_bech32_hrp; std::string parent_blech32_hrp; bool enforce_pak; + bool accept_unlimited_issuances; bool multi_data_permitted; bool accept_discount_ct; bool create_discount_ct; diff --git a/src/key_io.cpp b/src/key_io.cpp index 257ab2e5a2..62fad3df67 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -164,7 +164,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par bool is_bech32_hrp = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP()); bool is_blech32_hrp = (ToLower(str.substr(0, params.Blech32HRP().size())) == params.Blech32HRP()); - if (!is_bech32 && !is_blech32 && !is_bech32_hrp && !is_blech32_hrp && DecodeBase58Check(str, data, 55)) { + if (!is_bech32 && !is_blech32 && !is_bech32_hrp && !is_blech32_hrp && DecodeBase58Check(str, data, 55)) { // base58-encoded Bitcoin addresses. // Public-key-hash-addresses have version 0 (or 111 testnet). // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 88c5c1e0b2..b9448bb835 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -321,6 +321,20 @@ 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() && !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 08460e366a..ba12398aa0 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -129,6 +129,9 @@ static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS{LOCKTIME_VERIFY_SEQ // 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); @@ -161,6 +164,11 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) */ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); +/* ELEMENTS +* Check if unblinded issuance/reissuance is in MoneyRange +*/ +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 d07157fd52..fe5af9def2 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -750,6 +750,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } } + // Check unblinded issuance is in MoneyRange if configured + if (!chainparams.GetAcceptUnlimitedIssuances() && !IsIssuanceInMoneyRange(tx)) { + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "issuance-out-of-range", "Issuance is greater than 21 million and acceptunlimitedissuances is not enabled."); + } + // Transactions smaller than 65 non-witness bytes are not relayed to mitigate CVE-2017-12842. if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE) return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "tx-size-small"); @@ -2378,12 +2383,12 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, num_blocks_total++; - // Check that all non-zero coinbase outputs pay to the required destination + // Check that all non-zero policyAsset coinbase outputs pay to the required destination const CScript& mandatory_coinbase_destination = params.GetConsensus().mandatory_coinbase_destination; if (mandatory_coinbase_destination != CScript()) { for (auto& txout : block.vtx[0]->vout) { bool mustPay = !txout.nValue.IsExplicit() || txout.nValue.GetAmount() != 0; - if (mustPay && txout.scriptPubKey != mandatory_coinbase_destination) { + if (mustPay && txout.nAsset.GetAsset() == policyAsset && txout.scriptPubKey != mandatory_coinbase_destination) { LogPrintf("ERROR: ConnectBlock(): Coinbase outputs didn't match required scriptPubKey\n"); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-coinbase-txos"); } diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py index 5f2978d1da..0b4f0a5d17 100755 --- a/test/functional/rpc_invalid_address_message.py +++ b/test/functional/rpc_invalid_address_message.py @@ -24,7 +24,7 @@ BECH32_INVALID_PREFIX = 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3z BECH32_TOO_LONG = 'ert1q049edschfnwystcqnsvyfpj23mpsg3jcedq9xv049edschfnwystcqnsvyfpj23mpsg3jcedq9xv049edschfnwystcqnsvyfpj23m' BECH32_ONE_ERROR = 'ert1qtmp7aayg7p24uslctssvjm06q5phz4yr7gdkdv' BECH32_ONE_ERROR_CAPITALS = 'ERT1QTMP7AAYG7P24USLCTSSVJM06Q5PHZ4YR7GDKDV' -BECH32_TWO_ERRORS = 'ert1qtmp74syg7p24uslctsavjm06q5phz4yr7gdkdv' # should be bcrt1qax9suht3qv95sw33wavx8crpxduefdrsvgsklx +BECH32_TWO_ERRORS = 'ert1qtmp74syg7p24uslctsavjm06q5phz4yr7gdkdv' BECH32_NO_SEPARATOR = 'ertq049ldschfnwystcqnsvyfpj23mpsg3jcedq9xv' BECH32_INVALID_CHAR = 'ert1q04oldschfnwystcqnsvyfpj23mpsg3jcedq9xv' BECH32_MULTISIG_TWO_ERRORS = 'ert1qmzm84udpua6axdstxlpwafca7g7na5w2yu8c7vqhe0rhjkcrfcfqwymvhe' @@ -32,7 +32,7 @@ BECH32_WRONG_VERSION = 'ert1ptmp74ayg7p24uslctssvjm06q5phz4yr7gdkdv' BASE58_VALID = '2dcjQH4DQC3pMcSQkMkSQyPPEr7rZ6Ga4GR' BASE58_INVALID_PREFIX = '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem' -BASE58_INVALID_CHECKSUM = '2NEPDEyVRWtPzPL38jaQZkWvbK8E6ywvB6d' +BASE58_INVALID_CHECKSUM = 'mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJJfn' BASE58_INVALID_LENGTH = '2dcjQH4DQC3pMcSQkMkSQyPPEr7rZ6Ga4GR7rZ6Ga4GR' INVALID_ADDRESS = 'asfah14i8fajz0123f' @@ -73,12 +73,12 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework): def test_validateaddress(self): # Invalid Bech32 - self.check_invalid(BECH32_INVALID_SIZE, "Invalid Bech32 address program size (41 bytes)") - self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid or unsupported prefix for Segwit (Bech32) address (expected ert, got bc).') # ELEMENTS + self.check_invalid(BECH32_INVALID_SIZE, 'Invalid Bech32 address program size (41 bytes)') + self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid or unsupported prefix for Segwit (Bech32) address (expected ert, got bc).') self.check_invalid(BECH32_INVALID_BECH32, 'Version 1+ witness address must use Bech32m checksum') self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum') self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version') - self.check_invalid(BECH32_INVALID_V0_SIZE, "Invalid Bech32 v0 address program size (21 bytes), per BIP141") + self.check_invalid(BECH32_INVALID_V0_SIZE, 'Invalid Bech32 v0 address program size (21 bytes), per BIP141') self.check_invalid(BECH32_TOO_LONG, 'Bech32 string too long', list(range(90, 107))) self.check_invalid(BECH32_ONE_ERROR, 'Invalid Bech32 checksum', [9]) self.check_invalid(BECH32_TWO_ERRORS, 'Invalid Bech32 checksum', [10, 23]) diff --git a/test/functional/wallet_elements_21million.py b/test/functional/wallet_elements_21million.py index 61e4598497..912ef78bc7 100755 --- a/test/functional/wallet_elements_21million.py +++ b/test/functional/wallet_elements_21million.py @@ -7,13 +7,21 @@ from test_framework.blocktools import COINBASE_MATURITY 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 + ["-acceptunlimitedissuances=1"], + args + ["-acceptunlimitedissuances=1"], + args, # node 2 blocks unblinded issuances out of moneyrange + ] def add_options(self, parser): self.add_wallet_options(parser) @@ -38,6 +46,17 @@ class WalletTest(BitcoinTestFramework): 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) @@ -86,5 +105,26 @@ class WalletTest(BitcoinTestFramework): 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) + asset = issuance['asset'] + issuance_tx = self.nodes[2].gettransaction(issuance["txid"]) + assert_raises_rpc_error(-26, "issuance-out-of-range", self.nodes[2].sendrawtransaction, issuance_tx['hex']) + self.generate(self.nodes[0], 1) + assert(asset not in self.nodes[2].getbalance()) + # transaction should be accepted on node 0 + self.nodes[0].sendrawtransaction(issuance_tx["hex"]) + assert(issuance['txid'] in self.nodes[0].getrawmempool()) + assert(issuance['txid'] not in self.nodes[2].getrawmempool()) + self.generate(self.nodes[0], 1) + assert(asset not in self.nodes[0].getbalance()) + assert_equal(self.nodes[2].getbalance()[asset], 300_000_000) + if __name__ == '__main__': WalletTest().main()