diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a2b4c418af..1eb76a4cb7 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -7,12 +7,13 @@ #include #include +#include #include #include // for signet block challenge hash #include #include +#include #include -#include #include @@ -232,6 +233,8 @@ class CMainParams : public CChainParams { multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; + pegin_subsidy = PeginSubsidy(); + pegin_minimum = PeginMinimum(); consensus.has_parent_chain = false; g_signed_blocks = false; g_con_elementsmode = false; @@ -379,6 +382,8 @@ class CTestNetParams : public CChainParams { multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; + pegin_subsidy = PeginSubsidy(); + pegin_minimum = PeginMinimum(); consensus.has_parent_chain = false; g_signed_blocks = false; g_con_elementsmode = false; @@ -544,6 +549,8 @@ class SigNetParams : public CChainParams { multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; + pegin_subsidy = PeginSubsidy(); + pegin_minimum = PeginMinimum(); consensus.has_parent_chain = false; g_signed_blocks = false; // lol g_con_elementsmode = false; @@ -648,6 +655,8 @@ class CRegTestParams : public CChainParams { multi_data_permitted = false; accept_discount_ct = false; create_discount_ct = false; + pegin_subsidy = PeginSubsidy(); + pegin_minimum = PeginMinimum(); consensus.has_parent_chain = false; g_signed_blocks = false; g_con_elementsmode = false; @@ -792,6 +801,39 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) } } +// ELEMENTS +PeginSubsidy ParsePeginSubsidy(const ArgsManager& args) { + PeginSubsidy pegin_subsidy; + + pegin_subsidy.height = args.GetIntArg("-peginsubsidyheight", std::numeric_limits::max()); + if (pegin_subsidy.height < 0) { + throw std::runtime_error(strprintf("Invalid block height (%d) for -peginsubsidyheight. Must be positive.", pegin_subsidy.height)); + } + if (std::optional amount = ParseMoney(args.GetArg("-peginsubsidythreshold", "0"))) { + pegin_subsidy.threshold = amount.value(); + } else { + throw std::runtime_error("Invalid -peginsubsidythreshold"); + } + + return pegin_subsidy; +}; + +PeginMinimum ParsePeginMinimum(const ArgsManager& args) { + PeginMinimum pegin_minimum; + + pegin_minimum.height = args.GetIntArg("-peginminheight", std::numeric_limits::max()); + if (pegin_minimum.height < 0) { + throw std::runtime_error(strprintf("Invalid block height (%d) for -peginminheight. Must be positive.", pegin_minimum.height)); + } + if (std::optional amount = ParseMoney(args.GetArg("-peginminamount", "0"))) { + pegin_minimum.amount = amount.value(); + } else { + throw std::runtime_error("Invalid -peginminamount"); + } + + return pegin_minimum; +}; + /** * Custom params for testing. */ @@ -932,6 +974,11 @@ class CCustomParams : public CRegTestParams { consensus.start_p2wsh_script = args.GetIntArg("-con_start_p2wsh_script", consensus.start_p2wsh_script); create_discount_ct = args.GetBoolArg("-creatediscountct", create_discount_ct); accept_discount_ct = args.GetBoolArg("-acceptdiscountct", accept_discount_ct) || create_discount_ct; + pegin_subsidy = ParsePeginSubsidy(args); + pegin_minimum = ParsePeginMinimum(args); + if (pegin_subsidy.threshold < pegin_minimum.amount) { + throw std::runtime_error(strprintf("Peg-in subsidy threshold (%s) must be greater than or equal to peg-in minimum amount (%s)", FormatMoney(pegin_subsidy.threshold), FormatMoney(pegin_minimum.amount))); + } // Calculate pegged Bitcoin asset std::vector commit = CommitToArguments(consensus, strNetworkID); @@ -1178,6 +1225,11 @@ class CLiquidV1Params : public CChainParams { multi_data_permitted = true; create_discount_ct = args.GetBoolArg("-creatediscountct", false); accept_discount_ct = args.GetBoolArg("-acceptdiscountct", true) || create_discount_ct; + pegin_subsidy = ParsePeginSubsidy(args); + pegin_minimum = ParsePeginMinimum(args); + if (pegin_subsidy.threshold < pegin_minimum.amount) { + throw std::runtime_error(strprintf("Peg-in subsidy threshold (%s) must be greater than or equal to peg-in minimum amount (%s)", FormatMoney(pegin_subsidy.threshold), FormatMoney(pegin_minimum.amount))); + } parentGenesisBlockHash = uint256S("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); const bool parent_genesis_is_null = parentGenesisBlockHash == uint256(); @@ -1538,6 +1590,11 @@ class CLiquidV1TestParams : public CLiquidV1Params { 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; + pegin_subsidy = ParsePeginSubsidy(args); + pegin_minimum = ParsePeginMinimum(args); + if (pegin_subsidy.threshold < pegin_minimum.amount) { + throw std::runtime_error(strprintf("Peg-in subsidy threshold (%s) must be greater than or equal to peg-in minimum amount (%s)", FormatMoney(pegin_subsidy.threshold), FormatMoney(pegin_minimum.amount))); + } if (args.IsArgSet("-parentgenesisblockhash")) { parentGenesisBlockHash = uint256S(args.GetArg("-parentgenesisblockhash", "")); diff --git a/src/chainparams.h b/src/chainparams.h index 840a22ba5c..3e318577ec 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -28,6 +28,27 @@ struct CCheckpointData { } }; +// ELEMENTS +struct PeginSubsidy { + int height{std::numeric_limits::max()}; + CAmount threshold{0}; + + PeginSubsidy() {}; + bool IsDefined() { + return threshold > 0 || height < std::numeric_limits::max(); + }; +}; + +struct PeginMinimum { + int height{std::numeric_limits::max()}; + CAmount amount{0}; + + PeginMinimum() {}; + bool IsDefined() { + return amount > 0 || height < std::numeric_limits::max(); + }; +}; + struct AssumeutxoHash : public BaseHash { explicit AssumeutxoHash(const uint256& hash) : BaseHash(hash) {} }; @@ -138,6 +159,8 @@ class CChainParams bool GetMultiDataPermitted() const { return multi_data_permitted; } bool GetAcceptDiscountCT() const { return accept_discount_ct; } bool GetCreateDiscountCT() const { return create_discount_ct; } + PeginSubsidy GetPeginSubsidy() const { return pegin_subsidy; } + PeginMinimum GetPeginMinimum() const { return pegin_minimum; } protected: CChainParams() {} @@ -173,6 +196,8 @@ class CChainParams bool multi_data_permitted; bool accept_discount_ct; bool create_discount_ct; + PeginSubsidy pegin_subsidy; + PeginMinimum pegin_minimum; }; /** diff --git a/src/dynafed.cpp b/src/dynafed.cpp index d719795149..cb288a83ed 100644 --- a/src/dynafed.cpp +++ b/src/dynafed.cpp @@ -123,3 +123,34 @@ DynaFedParamEntry ComputeNextBlockCurrentParameters(const CBlockIndex* pindexPre } } +bool ParseFedPegQuorum(const CScript& fedpegscript, int& t, int& n) { + CScript::const_iterator it = fedpegscript.begin(); + std::vector vch; + opcodetype opcode; + + // parse the required threshold number + if (!fedpegscript.GetOp(it, opcode, vch)) return false; + t = CScript::DecodeOP_N(opcode); + if (t < 1 || t > MAX_PUBKEYS_PER_MULTISIG) return false; + + // support a fedpegscript like OP_TRUE if we're at the end of the script + if (it == fedpegscript.end()) return true; + + // count the pubkeys + int pubkeys = 0; + while (fedpegscript.GetOp(it, opcode, vch)) { + if (opcode != 0x21) break; + if (vch.size() != 33) return false; + pubkeys++; + } + + // parse the total number of pubkeys + n = CScript::DecodeOP_N(opcode); + if (n < 1 || n > MAX_PUBKEYS_PER_MULTISIG || n < t) return false; + if (pubkeys != n) return false; + + // the next opcode must be OP_CHECKMULTISIG + if (!fedpegscript.GetOp(it, opcode, vch)) return false; + + return opcode == OP_CHECKMULTISIG; +} diff --git a/src/dynafed.h b/src/dynafed.h index ea651631fb..373fdb2e97 100644 --- a/src/dynafed.h +++ b/src/dynafed.h @@ -15,5 +15,10 @@ DynaFedParamEntry ComputeNextBlockFullCurrentParameters(const CBlockIndex* pinde * publish signblockscript-related fields */ DynaFedParamEntry ComputeNextBlockCurrentParameters(const CBlockIndex* pindexPrev, const Consensus::Params& consensus); +/* Get the threshold (t) and maybe the total pubkeys (n) of the first OP_CHECKMULTISIG in the fedpegscript. + * Assumes the fedpegscript starts with the threshold, otherwise returns false. + * Uses CScript::DecodeOP_N, so only supports up to a threshold of 16, otherwise asserts. + * Supports a fedpegscript like OP_TRUE by returning early. */ +bool ParseFedPegQuorum(const CScript& fedpegscript, int& t, int& n); #endif // BITCOIN_DYNAFED_H diff --git a/src/init.cpp b/src/init.cpp index 27addf275a..d8dd3dca73 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -629,7 +629,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-mainchainrpcpassword=", "The rpc password which the daemon will use to connect to the trusted mainchain daemon to validate peg-ins, if enabled. (default: cookie auth)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::ELEMENTS); argsman.AddArg("-mainchainrpccookiefile=", "The bitcoind cookie auth path which the daemon will use to connect to the trusted mainchain daemon to validate peg-ins. (default: `/regtest/.cookie`)", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); argsman.AddArg("-mainchainrpctimeout=", strprintf("Timeout in seconds during mainchain RPC requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); - argsman.AddArg("-peginconfirmationdepth=", strprintf("Pegin claims must be this deep to be considered valid. (default: %d)", DEFAULT_PEGIN_CONFIRMATION_DEPTH), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); + argsman.AddArg("-peginconfirmationdepth=", strprintf("Peg-in claims must be this deep to be considered valid. (default: %d)", DEFAULT_PEGIN_CONFIRMATION_DEPTH), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); argsman.AddArg("-parentpubkeyprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 pubkey address. (default: %d)", 111), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-parentscriptprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 script address. (default: %d)", 196), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-parent_bech32_hrp", strprintf("The human-readable part of the parent chain's bech32 encoding. (default: %s)", "bc"), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); @@ -642,6 +642,10 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-acceptdiscountct", "Accept discounted fees for Confidential Transactions (default: 1 in liquidtestnet and liquidv1, 0 otherwise)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-creatediscountct", "Create Confidential Transactions with discounted fees (default: 0). Setting this to 1 will also set 'acceptdiscountct' to 1.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-peginsubsidyheight", "The block height at which peg-in transactions must have a burn subsidy (default: not active). The subsidy is an OP_RETURN output, with its value equal to the feerate of the parent transaction multiplied by the vsize of spending the P2WSH output created by the peg-in (feerate * 396 sats for liquidv1). ", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-peginsubsidythreshold", "The output value below which peg-in transactions must have a burn subsidy (default: 0). Peg-ins above this value do not require the subsidy.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-peginminheight", "The block height at which a minimum peg-in value is enforced (default: not active).", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-peginminamount", "The minimum value for a peg-in transaction after peginminheight (default: unset).", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); #if defined(USE_SYSCALL_SANDBOX) argsman.AddArg("-sandbox=", "Use the experimental syscall sandbox in the specified mode (-sandbox=log-and-abort or -sandbox=abort). Allow only expected syscalls to be used by bitcoind. Note that this is an experimental new feature that may cause bitcoind to exit or crash unexpectedly: use with caution. In the \"log-and-abort\" mode the invocation of an unexpected syscall results in a debug handler being invoked which will log the incident and terminate the program (without executing the unexpected syscall). In the \"abort\" mode the invocation of an unexpected syscall results in the entire process being killed immediately by the kernel without executing the unexpected syscall.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -1964,7 +1968,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) { uiInterface.InitMessage(_("Awaiting mainchain RPC warmup").translated); if (!MainchainRPCCheck()) { - const std::string err_msg = "ERROR: elements is set to verify pegins but cannot get a valid response from the mainchain daemon. Please check debug.log for more information.\n\nIf you haven't setup a bitcoind please get the latest stable version from https://bitcoincore.org/en/download/ or if you do not need to validate pegins set in your elements configuration validatepegin=0"; + const std::string err_msg = "ERROR: elements is set to verify peg-ins but cannot get a valid response from the mainchain daemon. Please check debug.log for more information.\n\nIf you haven't setup a bitcoind please get the latest stable version from https://bitcoincore.org/en/download/ or if you do not need to validate peg-ins set in your elements configuration validatepegin=0"; // We fail immediately if this node has RPC server enabled if (gArgs.GetBoolArg("-server", false)) { InitError(Untranslated(err_msg)); @@ -1975,6 +1979,23 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) gArgs.SoftSetArg("-validatepegin", "0"); } } + // if we are validating peg-in subsidy or minimum then we require bitcoind >= v25 + if (Params().GetPeginSubsidy().IsDefined() || Params().GetPeginMinimum().IsDefined()) { + UniValue params(UniValue::VARR); + UniValue reply = CallMainChainRPC("getnetworkinfo", params); + if (reply["error"].isStr()) { + InitError(Untranslated(reply["error"].get_str())); + return false; + } else { + const int version = reply["result"]["version"].get_int(); + const std::string& subversion = reply["result"]["subversion"].get_str(); + if (version < 250000 && subversion.find("Satoshi") != std::string::npos) { + const std::string err = strprintf("ERROR: parent bitcoind must be version 25 or newer for peg-in subsidy/minimum validation. Found version: %s", version); + InitError(Untranslated(err)); + return false; + } + } + } } // Call ActivateBestChain every 30 seconds. This is almost always a diff --git a/src/pegins.cpp b/src/pegins.cpp index 72551d2c3b..8e44936398 100644 --- a/src/pegins.cpp +++ b/src/pegins.cpp @@ -577,7 +577,7 @@ bool DecomposePeginWitness(const CScriptWitness& witness, CAmount& value, CAsset tx = elem_tx; } - CDataStream ss_proof(stack[5], SER_NETWORK, PROTOCOL_VERSION); + CDataStream ss_proof(stack[5], SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); if (Params().GetConsensus().ParentChainHasPow()) { Sidechain::Bitcoin::CMerkleBlock tx_proof; ss_proof >> tx_proof; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index b20765a9e2..602cd5d952 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -3134,6 +3135,25 @@ static RPCHelpMan getsidechaininfo() obj.pushKV("parent_chain_signblockscript_hex", HexStr(consensus.parent_chain_signblockscript)); obj.pushKV("parent_pegged_asset", consensus.parent_pegged_asset.GetHex()); } + + PeginMinimum pegin_minimum = Params().GetPeginMinimum(); + if (pegin_minimum.amount > 0) { + obj.pushKV("pegin_min_amount", FormatMoney(pegin_minimum.amount)); + } + if (pegin_minimum.height < std::numeric_limits::max()) { + obj.pushKV("pegin_min_height", pegin_minimum.height); + obj.pushKV("pegin_min_active", chainman.ActiveTip()->nHeight >= pegin_minimum.height); + } + + PeginSubsidy pegin_subsidy = Params().GetPeginSubsidy(); + if (pegin_subsidy.threshold > 0) { + obj.pushKV("pegin_subsidy_threshold", FormatMoney(pegin_subsidy.threshold)); + } + if (pegin_subsidy.height < std::numeric_limits::max()) { + obj.pushKV("pegin_subsidy_height", pegin_subsidy.height); + obj.pushKV("pegin_subsidy_active", chainman.ActiveTip()->nHeight >= pegin_subsidy.height); + } + return obj; }, }; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 5d8da16722..3b65133921 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -239,6 +239,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "calculateasset", 3, "blind_reissuance" }, { "updatepsbtpegin", 1, "input" }, { "updatepsbtpegin", 2, "value" }, + { "claimpegin", 3, "fee_rate" }, + { "createrawpegin", 3, "fee_rate" }, }; // clang-format on diff --git a/src/test/dynafed_tests.cpp b/src/test/dynafed_tests.cpp index 3a186db51c..f9a8099ae1 100644 --- a/src/test/dynafed_tests.cpp +++ b/src/test/dynafed_tests.cpp @@ -2,12 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include -#include #include +#include #include #include