Skip to content

Commit 7791d31

Browse files
committed
Merge #1499: ELIP 202: Implementation of optional sidechain peg-in subsidy and minimum peg-in amount
a18a88b grammar: prefer peg-in to pegin in messages (Byron Hambly) 90b2b3b test: add pegin subsidy functional test (Byron Hambly) 186bb25 validation: check for peg-in subsidy and minimum (Byron Hambly) 51c89c6 subsidy: implementation for claimpegin, createrawpegin, and RPCs (Byron Hambly) 94cde59 subsidy: add chainparams and init (Byron Hambly) f3b63f4 DecomposePeginWitness: fix deserialization flags for MerkleBlock proof (Byron Hambly) Pull request description: Implementation for [ELIP 202](https://github.com/ElementsProject/ELIPs/blob/main/elip-0202.mediawiki) ACKs for top commit: jsarenik: Running ACK a18a88b tomt1664: Tested ACK a18a88b Tree-SHA512: 984fe2aa32e6814e14c9535e9fea7e03d3b6cf7a35621ccfb2a081a6ebab01906c5e883c5b505ced53f254401d30b6e975cb05e88efc8d3fbedc79abd0be72a2
2 parents 6bb916a + a18a88b commit 7791d31

13 files changed

Lines changed: 1387 additions & 27 deletions

File tree

src/chainparams.cpp

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
#include <chainparamsseeds.h>
99
#include <consensus/merkle.h>
10+
#include <crypto/sha256.h>
1011
#include <deploymentinfo.h>
1112
#include <hash.h> // for signet block challenge hash
1213
#include <issuance.h>
1314
#include <primitives/transaction.h>
15+
#include <util/moneystr.h>
1416
#include <util/system.h>
15-
#include <crypto/sha256.h>
1617

1718
#include <assert.h>
1819

@@ -232,6 +233,8 @@ class CMainParams : public CChainParams {
232233
multi_data_permitted = false;
233234
accept_discount_ct = false;
234235
create_discount_ct = false;
236+
pegin_subsidy = PeginSubsidy();
237+
pegin_minimum = PeginMinimum();
235238
consensus.has_parent_chain = false;
236239
g_signed_blocks = false;
237240
g_con_elementsmode = false;
@@ -379,6 +382,8 @@ class CTestNetParams : public CChainParams {
379382
multi_data_permitted = false;
380383
accept_discount_ct = false;
381384
create_discount_ct = false;
385+
pegin_subsidy = PeginSubsidy();
386+
pegin_minimum = PeginMinimum();
382387
consensus.has_parent_chain = false;
383388
g_signed_blocks = false;
384389
g_con_elementsmode = false;
@@ -544,6 +549,8 @@ class SigNetParams : public CChainParams {
544549
multi_data_permitted = false;
545550
accept_discount_ct = false;
546551
create_discount_ct = false;
552+
pegin_subsidy = PeginSubsidy();
553+
pegin_minimum = PeginMinimum();
547554
consensus.has_parent_chain = false;
548555
g_signed_blocks = false; // lol
549556
g_con_elementsmode = false;
@@ -648,6 +655,8 @@ class CRegTestParams : public CChainParams {
648655
multi_data_permitted = false;
649656
accept_discount_ct = false;
650657
create_discount_ct = false;
658+
pegin_subsidy = PeginSubsidy();
659+
pegin_minimum = PeginMinimum();
651660
consensus.has_parent_chain = false;
652661
g_signed_blocks = false;
653662
g_con_elementsmode = false;
@@ -792,6 +801,39 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args)
792801
}
793802
}
794803

804+
// ELEMENTS
805+
PeginSubsidy ParsePeginSubsidy(const ArgsManager& args) {
806+
PeginSubsidy pegin_subsidy;
807+
808+
pegin_subsidy.height = args.GetIntArg("-peginsubsidyheight", std::numeric_limits<int>::max());
809+
if (pegin_subsidy.height < 0) {
810+
throw std::runtime_error(strprintf("Invalid block height (%d) for -peginsubsidyheight. Must be positive.", pegin_subsidy.height));
811+
}
812+
if (std::optional<CAmount> amount = ParseMoney(args.GetArg("-peginsubsidythreshold", "0"))) {
813+
pegin_subsidy.threshold = amount.value();
814+
} else {
815+
throw std::runtime_error("Invalid -peginsubsidythreshold");
816+
}
817+
818+
return pegin_subsidy;
819+
};
820+
821+
PeginMinimum ParsePeginMinimum(const ArgsManager& args) {
822+
PeginMinimum pegin_minimum;
823+
824+
pegin_minimum.height = args.GetIntArg("-peginminheight", std::numeric_limits<int>::max());
825+
if (pegin_minimum.height < 0) {
826+
throw std::runtime_error(strprintf("Invalid block height (%d) for -peginminheight. Must be positive.", pegin_minimum.height));
827+
}
828+
if (std::optional<CAmount> amount = ParseMoney(args.GetArg("-peginminamount", "0"))) {
829+
pegin_minimum.amount = amount.value();
830+
} else {
831+
throw std::runtime_error("Invalid -peginminamount");
832+
}
833+
834+
return pegin_minimum;
835+
};
836+
795837
/**
796838
* Custom params for testing.
797839
*/
@@ -932,6 +974,11 @@ class CCustomParams : public CRegTestParams {
932974
consensus.start_p2wsh_script = args.GetIntArg("-con_start_p2wsh_script", consensus.start_p2wsh_script);
933975
create_discount_ct = args.GetBoolArg("-creatediscountct", create_discount_ct);
934976
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", accept_discount_ct) || create_discount_ct;
977+
pegin_subsidy = ParsePeginSubsidy(args);
978+
pegin_minimum = ParsePeginMinimum(args);
979+
if (pegin_subsidy.threshold < pegin_minimum.amount) {
980+
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)));
981+
}
935982

936983
// Calculate pegged Bitcoin asset
937984
std::vector<unsigned char> commit = CommitToArguments(consensus, strNetworkID);
@@ -1178,6 +1225,11 @@ class CLiquidV1Params : public CChainParams {
11781225
multi_data_permitted = true;
11791226
create_discount_ct = args.GetBoolArg("-creatediscountct", false);
11801227
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", true) || create_discount_ct;
1228+
pegin_subsidy = ParsePeginSubsidy(args);
1229+
pegin_minimum = ParsePeginMinimum(args);
1230+
if (pegin_subsidy.threshold < pegin_minimum.amount) {
1231+
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)));
1232+
}
11811233

11821234
parentGenesisBlockHash = uint256S("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
11831235
const bool parent_genesis_is_null = parentGenesisBlockHash == uint256();
@@ -1538,6 +1590,11 @@ class CLiquidV1TestParams : public CLiquidV1Params {
15381590
multi_data_permitted = args.GetBoolArg("-multi_data_permitted", multi_data_permitted);
15391591
create_discount_ct = args.GetBoolArg("-creatediscountct", create_discount_ct);
15401592
accept_discount_ct = args.GetBoolArg("-acceptdiscountct", accept_discount_ct) || create_discount_ct;
1593+
pegin_subsidy = ParsePeginSubsidy(args);
1594+
pegin_minimum = ParsePeginMinimum(args);
1595+
if (pegin_subsidy.threshold < pegin_minimum.amount) {
1596+
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)));
1597+
}
15411598

15421599
if (args.IsArgSet("-parentgenesisblockhash")) {
15431600
parentGenesisBlockHash = uint256S(args.GetArg("-parentgenesisblockhash", ""));

src/chainparams.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,27 @@ struct CCheckpointData {
2828
}
2929
};
3030

31+
// ELEMENTS
32+
struct PeginSubsidy {
33+
int height{std::numeric_limits<int>::max()};
34+
CAmount threshold{0};
35+
36+
PeginSubsidy() {};
37+
bool IsDefined() {
38+
return threshold > 0 || height < std::numeric_limits<int>::max();
39+
};
40+
};
41+
42+
struct PeginMinimum {
43+
int height{std::numeric_limits<int>::max()};
44+
CAmount amount{0};
45+
46+
PeginMinimum() {};
47+
bool IsDefined() {
48+
return amount > 0 || height < std::numeric_limits<int>::max();
49+
};
50+
};
51+
3152
struct AssumeutxoHash : public BaseHash<uint256> {
3253
explicit AssumeutxoHash(const uint256& hash) : BaseHash(hash) {}
3354
};
@@ -138,6 +159,8 @@ class CChainParams
138159
bool GetMultiDataPermitted() const { return multi_data_permitted; }
139160
bool GetAcceptDiscountCT() const { return accept_discount_ct; }
140161
bool GetCreateDiscountCT() const { return create_discount_ct; }
162+
PeginSubsidy GetPeginSubsidy() const { return pegin_subsidy; }
163+
PeginMinimum GetPeginMinimum() const { return pegin_minimum; }
141164

142165
protected:
143166
CChainParams() {}
@@ -173,6 +196,8 @@ class CChainParams
173196
bool multi_data_permitted;
174197
bool accept_discount_ct;
175198
bool create_discount_ct;
199+
PeginSubsidy pegin_subsidy;
200+
PeginMinimum pegin_minimum;
176201
};
177202

178203
/**

src/dynafed.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,34 @@ DynaFedParamEntry ComputeNextBlockCurrentParameters(const CBlockIndex* pindexPre
123123
}
124124
}
125125

126+
bool ParseFedPegQuorum(const CScript& fedpegscript, int& t, int& n) {
127+
CScript::const_iterator it = fedpegscript.begin();
128+
std::vector<unsigned char> vch;
129+
opcodetype opcode;
130+
131+
// parse the required threshold number
132+
if (!fedpegscript.GetOp(it, opcode, vch)) return false;
133+
t = CScript::DecodeOP_N(opcode);
134+
if (t < 1 || t > MAX_PUBKEYS_PER_MULTISIG) return false;
135+
136+
// support a fedpegscript like OP_TRUE if we're at the end of the script
137+
if (it == fedpegscript.end()) return true;
138+
139+
// count the pubkeys
140+
int pubkeys = 0;
141+
while (fedpegscript.GetOp(it, opcode, vch)) {
142+
if (opcode != 0x21) break;
143+
if (vch.size() != 33) return false;
144+
pubkeys++;
145+
}
146+
147+
// parse the total number of pubkeys
148+
n = CScript::DecodeOP_N(opcode);
149+
if (n < 1 || n > MAX_PUBKEYS_PER_MULTISIG || n < t) return false;
150+
if (pubkeys != n) return false;
151+
152+
// the next opcode must be OP_CHECKMULTISIG
153+
if (!fedpegscript.GetOp(it, opcode, vch)) return false;
154+
155+
return opcode == OP_CHECKMULTISIG;
156+
}

src/dynafed.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,10 @@ DynaFedParamEntry ComputeNextBlockFullCurrentParameters(const CBlockIndex* pinde
1515
* publish signblockscript-related fields */
1616
DynaFedParamEntry ComputeNextBlockCurrentParameters(const CBlockIndex* pindexPrev, const Consensus::Params& consensus);
1717

18+
/* Get the threshold (t) and maybe the total pubkeys (n) of the first OP_CHECKMULTISIG in the fedpegscript.
19+
* Assumes the fedpegscript starts with the threshold, otherwise returns false.
20+
* Uses CScript::DecodeOP_N, so only supports up to a threshold of 16, otherwise asserts.
21+
* Supports a fedpegscript like OP_TRUE by returning early. */
22+
bool ParseFedPegQuorum(const CScript& fedpegscript, int& t, int& n);
1823

1924
#endif // BITCOIN_DYNAFED_H

src/init.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ void SetupServerArgs(ArgsManager& argsman)
629629
argsman.AddArg("-mainchainrpcpassword=<pwd>", "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);
630630
argsman.AddArg("-mainchainrpccookiefile=<file>", "The bitcoind cookie auth path which the daemon will use to connect to the trusted mainchain daemon to validate peg-ins. (default: `<datadir>/regtest/.cookie`)", ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
631631
argsman.AddArg("-mainchainrpctimeout=<n>", strprintf("Timeout in seconds during mainchain RPC requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
632-
argsman.AddArg("-peginconfirmationdepth=<n>", strprintf("Pegin claims must be this deep to be considered valid. (default: %d)", DEFAULT_PEGIN_CONFIRMATION_DEPTH), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
632+
argsman.AddArg("-peginconfirmationdepth=<n>", strprintf("Peg-in claims must be this deep to be considered valid. (default: %d)", DEFAULT_PEGIN_CONFIRMATION_DEPTH), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS);
633633
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);
634634
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);
635635
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)
642642
argsman.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
643643
argsman.AddArg("-acceptdiscountct", "Accept discounted fees for Confidential Transactions (default: 1 in liquidtestnet and liquidv1, 0 otherwise)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
644644
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);
645+
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);
646+
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);
647+
argsman.AddArg("-peginminheight", "The block height at which a minimum peg-in value is enforced (default: not active).", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
648+
argsman.AddArg("-peginminamount", "The minimum value for a peg-in transaction after peginminheight (default: unset).", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
645649

646650
#if defined(USE_SYSCALL_SANDBOX)
647651
argsman.AddArg("-sandbox=<mode>", "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)
19641968
if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
19651969
uiInterface.InitMessage(_("Awaiting mainchain RPC warmup").translated);
19661970
if (!MainchainRPCCheck()) {
1967-
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";
1971+
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";
19681972
// We fail immediately if this node has RPC server enabled
19691973
if (gArgs.GetBoolArg("-server", false)) {
19701974
InitError(Untranslated(err_msg));
@@ -1975,6 +1979,23 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
19751979
gArgs.SoftSetArg("-validatepegin", "0");
19761980
}
19771981
}
1982+
// if we are validating peg-in subsidy or minimum then we require bitcoind >= v25
1983+
if (Params().GetPeginSubsidy().IsDefined() || Params().GetPeginMinimum().IsDefined()) {
1984+
UniValue params(UniValue::VARR);
1985+
UniValue reply = CallMainChainRPC("getnetworkinfo", params);
1986+
if (reply["error"].isStr()) {
1987+
InitError(Untranslated(reply["error"].get_str()));
1988+
return false;
1989+
} else {
1990+
const int version = reply["result"]["version"].get_int();
1991+
const std::string& subversion = reply["result"]["subversion"].get_str();
1992+
if (version < 250000 && subversion.find("Satoshi") != std::string::npos) {
1993+
const std::string err = strprintf("ERROR: parent bitcoind must be version 25 or newer for peg-in subsidy/minimum validation. Found version: %s", version);
1994+
InitError(Untranslated(err));
1995+
return false;
1996+
}
1997+
}
1998+
}
19781999
}
19792000

19802001
// Call ActivateBestChain every 30 seconds. This is almost always a

src/pegins.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ bool DecomposePeginWitness(const CScriptWitness& witness, CAmount& value, CAsset
577577
tx = elem_tx;
578578
}
579579

580-
CDataStream ss_proof(stack[5], SER_NETWORK, PROTOCOL_VERSION);
580+
CDataStream ss_proof(stack[5], SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
581581
if (Params().GetConsensus().ParentChainHasPow()) {
582582
Sidechain::Bitcoin::CMerkleBlock tx_proof;
583583
ss_proof >> tx_proof;

src/rpc/blockchain.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <txdb.h>
4141
#include <txmempool.h>
4242
#include <undo.h>
43+
#include <util/moneystr.h>
4344
#include <util/strencodings.h>
4445
#include <util/string.h>
4546
#include <util/translation.h>
@@ -3134,6 +3135,25 @@ static RPCHelpMan getsidechaininfo()
31343135
obj.pushKV("parent_chain_signblockscript_hex", HexStr(consensus.parent_chain_signblockscript));
31353136
obj.pushKV("parent_pegged_asset", consensus.parent_pegged_asset.GetHex());
31363137
}
3138+
3139+
PeginMinimum pegin_minimum = Params().GetPeginMinimum();
3140+
if (pegin_minimum.amount > 0) {
3141+
obj.pushKV("pegin_min_amount", FormatMoney(pegin_minimum.amount));
3142+
}
3143+
if (pegin_minimum.height < std::numeric_limits<int>::max()) {
3144+
obj.pushKV("pegin_min_height", pegin_minimum.height);
3145+
obj.pushKV("pegin_min_active", chainman.ActiveTip()->nHeight >= pegin_minimum.height);
3146+
}
3147+
3148+
PeginSubsidy pegin_subsidy = Params().GetPeginSubsidy();
3149+
if (pegin_subsidy.threshold > 0) {
3150+
obj.pushKV("pegin_subsidy_threshold", FormatMoney(pegin_subsidy.threshold));
3151+
}
3152+
if (pegin_subsidy.height < std::numeric_limits<int>::max()) {
3153+
obj.pushKV("pegin_subsidy_height", pegin_subsidy.height);
3154+
obj.pushKV("pegin_subsidy_active", chainman.ActiveTip()->nHeight >= pegin_subsidy.height);
3155+
}
3156+
31373157
return obj;
31383158
},
31393159
};

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
239239
{ "calculateasset", 3, "blind_reissuance" },
240240
{ "updatepsbtpegin", 1, "input" },
241241
{ "updatepsbtpegin", 2, "value" },
242+
{ "claimpegin", 3, "fee_rate" },
243+
{ "createrawpegin", 3, "fee_rate" },
242244

243245
};
244246
// clang-format on

0 commit comments

Comments
 (0)