mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Merge ElementsProject/elements#1499: ELIP 202: Implementation of optional sidechain peg-in subsidy and minimum peg-in amount
a18a88b31bgrammar: prefer peg-in to pegin in messages (Byron Hambly)90b2b3bbb0test: add pegin subsidy functional test (Byron Hambly)186bb25a08validation: check for peg-in subsidy and minimum (Byron Hambly)51c89c65d0subsidy: implementation for claimpegin, createrawpegin, and RPCs (Byron Hambly)94cde59dc3subsidy: add chainparams and init (Byron Hambly)f3b63f4b8cDecomposePeginWitness: 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 ACKa18a88btomt1664: Tested ACKa18a88b31bTree-SHA512: 984fe2aa32e6814e14c9535e9fea7e03d3b6cf7a35621ccfb2a081a6ebab01906c5e883c5b505ced53f254401d30b6e975cb05e88efc8d3fbedc79abd0be72a2
This commit is contained in:
commit
7791d31bcf
13 changed files with 1388 additions and 28 deletions
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
#include <chainparamsseeds.h>
|
||||
#include <consensus/merkle.h>
|
||||
#include <crypto/sha256.h>
|
||||
#include <deploymentinfo.h>
|
||||
#include <hash.h> // for signet block challenge hash
|
||||
#include <issuance.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <util/system.h>
|
||||
#include <crypto/sha256.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
|
@ -232,6 +233,8 @@ public:
|
|||
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 @@ public:
|
|||
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 @@ public:
|
|||
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 @@ public:
|
|||
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<int>::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<CAmount> 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<int>::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<CAmount> 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 @@ protected:
|
|||
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<unsigned char> commit = CommitToArguments(consensus, strNetworkID);
|
||||
|
|
@ -1178,6 +1225,11 @@ public:
|
|||
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 @@ public:
|
|||
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", ""));
|
||||
|
|
|
|||
|
|
@ -28,6 +28,27 @@ struct CCheckpointData {
|
|||
}
|
||||
};
|
||||
|
||||
// ELEMENTS
|
||||
struct PeginSubsidy {
|
||||
int height{std::numeric_limits<int>::max()};
|
||||
CAmount threshold{0};
|
||||
|
||||
PeginSubsidy() {};
|
||||
bool IsDefined() {
|
||||
return threshold > 0 || height < std::numeric_limits<int>::max();
|
||||
};
|
||||
};
|
||||
|
||||
struct PeginMinimum {
|
||||
int height{std::numeric_limits<int>::max()};
|
||||
CAmount amount{0};
|
||||
|
||||
PeginMinimum() {};
|
||||
bool IsDefined() {
|
||||
return amount > 0 || height < std::numeric_limits<int>::max();
|
||||
};
|
||||
};
|
||||
|
||||
struct AssumeutxoHash : public BaseHash<uint256> {
|
||||
explicit AssumeutxoHash(const uint256& hash) : BaseHash(hash) {}
|
||||
};
|
||||
|
|
@ -138,6 +159,8 @@ public:
|
|||
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 @@ protected:
|
|||
bool multi_data_permitted;
|
||||
bool accept_discount_ct;
|
||||
bool create_discount_ct;
|
||||
PeginSubsidy pegin_subsidy;
|
||||
PeginMinimum pegin_minimum;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<unsigned char> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
25
src/init.cpp
25
src/init.cpp
|
|
@ -629,7 +629,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
|||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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=<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)
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include <txdb.h>
|
||||
#include <txmempool.h>
|
||||
#include <undo.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/string.h>
|
||||
#include <util/translation.h>
|
||||
|
|
@ -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<int>::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<int>::max()) {
|
||||
obj.pushKV("pegin_subsidy_height", pegin_subsidy.height);
|
||||
obj.pushKV("pegin_subsidy_active", chainman.ActiveTip()->nHeight >= pegin_subsidy.height);
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <test/util/setup_common.h>
|
||||
#include <string>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <dynafed.h>
|
||||
#include <primitives/block.h>
|
||||
#include <script/script.h>
|
||||
#include <serialize.h>
|
||||
#include <string>
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(dynafed_tests, BasicTestingSetup)
|
||||
|
|
@ -41,6 +42,48 @@ BOOST_AUTO_TEST_CASE(dynafed_params_root)
|
|||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_fedpegscript_multisig)
|
||||
{
|
||||
int t = 0;
|
||||
int n = 0;
|
||||
|
||||
auto simplebytes = ParseHex("512103dff4923d778550cc13ce0d887d737553b4b58f4e8e886507fc39f5e447b2186451ae");
|
||||
|
||||
CScript simple{simplebytes.begin(), simplebytes.end()};
|
||||
|
||||
BOOST_CHECK(ParseFedPegQuorum(simple, t, n));
|
||||
BOOST_CHECK_EQUAL(t, 1);
|
||||
BOOST_CHECK_EQUAL(n, 1);
|
||||
|
||||
auto liquidv1bytes = ParseHex("5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68");
|
||||
|
||||
CScript liquidv1{liquidv1bytes.begin(), liquidv1bytes.end()};
|
||||
|
||||
t = 0;
|
||||
n = 0;
|
||||
BOOST_CHECK(ParseFedPegQuorum(liquidv1, t, n));
|
||||
BOOST_CHECK_EQUAL(t, 11);
|
||||
BOOST_CHECK_EQUAL(n, 15);
|
||||
|
||||
auto optruebytes = ParseHex("51");
|
||||
|
||||
CScript optrue{optruebytes.begin(), optruebytes.end()};
|
||||
|
||||
t = 0;
|
||||
n = 0;
|
||||
BOOST_CHECK(ParseFedPegQuorum(optrue, t, n));
|
||||
BOOST_CHECK_EQUAL(t, 1);
|
||||
BOOST_CHECK_EQUAL(n, 0);
|
||||
|
||||
auto op3bytes = ParseHex("53");
|
||||
|
||||
CScript op3{op3bytes.begin(), op3bytes.end()};
|
||||
|
||||
t = 0;
|
||||
n = 0;
|
||||
BOOST_CHECK(ParseFedPegQuorum(op3, t, n));
|
||||
BOOST_CHECK_EQUAL(t, 3);
|
||||
BOOST_CHECK_EQUAL(n, 0);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -663,6 +663,135 @@ private:
|
|||
return true;
|
||||
}
|
||||
|
||||
// ELEMENTS: check if peg-in transaction pays the burn subsidy, and if minimum peg-in amount is met
|
||||
bool CheckPeginSubsidyAndMinimum(TxValidationState& state, const CTransaction& tx, const std::vector<unsigned int>& pegin_indices)
|
||||
{
|
||||
// pegin_indices was calculated directly from the tx in prechecks, assert this invariant
|
||||
assert(tx.witness.vtxinwit.size() >= pegin_indices.size());
|
||||
|
||||
if (pegin_indices.size() > 0) {
|
||||
// calculate the burned subsidy value from the tx
|
||||
CAmount subsidy = 0;
|
||||
for (const CTxOut& txout : tx.vout) {
|
||||
if (txout.scriptPubKey.IsUnspendable() && txout.nAsset.GetAsset() == Params().GetConsensus().pegged_asset && !txout.IsFee()) {
|
||||
subsidy += txout.nValue.GetAmount();
|
||||
}
|
||||
}
|
||||
// calculate the peg-in value, fee, and vsize from the parent chain if we are validating peg-ins
|
||||
// if validatepegin=0 then the minimum 1 sat/vb subsidy applies
|
||||
CAmount value = 0;
|
||||
CAmount parent_fee = 0;
|
||||
uint32_t parent_vsize = 0;
|
||||
for (size_t i = 0; i < pegin_indices.size(); ++i) {
|
||||
// get the parent txid and blockhash from the peg-in witness data
|
||||
CAmount pvalue;
|
||||
CAsset passet;
|
||||
uint256 pgenesis;
|
||||
CScript pscript;
|
||||
std::variant<std::monostate, Sidechain::Bitcoin::CTransactionRef, CTransactionRef> ptx;
|
||||
std::variant<std::monostate, Sidechain::Bitcoin::CMerkleBlock, CMerkleBlock> pmerkle;
|
||||
|
||||
if (!DecomposePeginWitness(tx.witness.vtxinwit[pegin_indices[i]].m_pegin_witness, pvalue, passet, pgenesis, pscript, ptx, pmerkle)) {
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "pegin-invalid-parent-tx", "failed to decompose the parent peg-in witness");
|
||||
}
|
||||
if (passet != Params().GetConsensus().pegged_asset) {
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "pegin-invalid-asset", "pegin asset is not the pegged asset");
|
||||
}
|
||||
|
||||
value += pvalue;
|
||||
|
||||
uint256 txid;
|
||||
switch (ptx.index()) {
|
||||
case 0:
|
||||
// this should never happen, but fail gracefully by rejecting
|
||||
return false;
|
||||
case 1:
|
||||
txid = std::get<Sidechain::Bitcoin::CTransactionRef>(ptx)->GetHash();
|
||||
break;
|
||||
case 2:
|
||||
txid = std::get<CTransactionRef>(ptx)->GetHash();
|
||||
break;
|
||||
}
|
||||
uint256 blockhash;
|
||||
switch (pmerkle.index()) {
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
blockhash = std::get<Sidechain::Bitcoin::CMerkleBlock>(pmerkle).header.GetHash();
|
||||
break;
|
||||
case 2:
|
||||
blockhash = std::get<CMerkleBlock>(pmerkle).header.GetHash();
|
||||
break;
|
||||
}
|
||||
|
||||
// get the parent transaction fee, to calculate the fee rate
|
||||
if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
|
||||
UniValue params(UniValue::VARR);
|
||||
params.push_back(txid.GetHex());
|
||||
params.push_back(2);
|
||||
params.push_back(blockhash.GetHex());
|
||||
UniValue result = CallMainChainRPC("getrawtransaction", params);
|
||||
if (result["error"].isStr()) {
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "pegin-subsidy-mainchain-error", result["error"]["message"].get_str());
|
||||
} else {
|
||||
parent_vsize += result["result"]["vsize"].get_int64();
|
||||
if (result["result"]["fee"].isNum()) {
|
||||
// bitcoin core v25+ returns the fee amount
|
||||
parent_fee += static_cast<CAmount>(std::round(result["result"]["fee"].get_real() * COIN));
|
||||
} else if (result["result"]["fee"].isObject()) {
|
||||
// elements returns a fee object
|
||||
std::string asset = Params().GetConsensus().parent_pegged_asset.GetHex();
|
||||
if (result["result"]["fee"][asset].isNum()) {
|
||||
parent_fee += static_cast<CAmount>(std::round(result["result"]["fee"][asset].get_real() * COIN));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if the peg-in value meets the minimum amount
|
||||
PeginMinimum pegin_minimum = Params().GetPeginMinimum();
|
||||
if (m_active_chainstate.m_chain.Height() >= pegin_minimum.height && value < pegin_minimum.amount) {
|
||||
// peg-in value is lower than the minimum
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "pegin-value-too-low", strprintf("peg-in value: %d, minimum peg-in value: %d", FormatMoney(value), FormatMoney(pegin_minimum.amount)));
|
||||
}
|
||||
|
||||
if (m_active_chainstate.m_chain.Height() >= Params().GetPeginSubsidy().height) {
|
||||
// subsidy is required at this height
|
||||
// when parent feerate is less than 1 sat/vb, use 1 sat/vb for the calculation
|
||||
CFeeRate parent_feerate = std::max(CFeeRate{parent_fee, parent_vsize}, CFeeRate{1000});
|
||||
|
||||
// for peg-ins below the subsidy threshold, check for enough subsidy
|
||||
CAmount threshold = Params().GetPeginSubsidy().threshold;
|
||||
// calculate the subsidy as the amount required to spend the P2WSH output
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(m_active_chainstate.m_chain.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
int t = 0;
|
||||
int n = 0;
|
||||
if (fedpegscripts.size() == 0) return false;
|
||||
if (!ParseFedPegQuorum(fedpegscripts[0].second, t, n)) return false;
|
||||
|
||||
// each P2WSH input is 41 bytes: txid (32) + vout (4) + scriptsig len (1) + sequence (4)
|
||||
// the witness to spend is `t` signatures + the script size
|
||||
unsigned int weight = WITNESS_SCALE_FACTOR * (32 + 4 + 1 + 4) + (t * 72 + fedpegscripts[0].second.size());
|
||||
unsigned int vbytes = (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
|
||||
|
||||
// multiply by the number of peg-ins
|
||||
CAmount expected_subsidy = parent_feerate.GetFee(pegin_indices.size() * vbytes);
|
||||
if (value < threshold && subsidy < expected_subsidy) {
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "pegin-subsidy-too-low",
|
||||
strprintf("peg-in value: %d, subsidy threshold: %d, subsidy value: %d, expected subsidy: %d, parent feerate: %s",
|
||||
FormatMoney(value), FormatMoney(threshold), FormatMoney(subsidy), FormatMoney(expected_subsidy), parent_feerate.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// either validatepegin=0 and we can't calculate peg-in value and parent feerate
|
||||
// or we're below the subsidy height
|
||||
// or the peg-in value is above the subsidy threshold
|
||||
// or the peg-in paid enough subsidy
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
CTxMemPool& m_pool;
|
||||
CCoinsViewCache m_view;
|
||||
|
|
@ -795,6 +924,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
std::vector<std::pair<CScript, CScript>> fedpegscripts = GetValidFedpegScripts(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus(), true /* nextblock_validation */);
|
||||
|
||||
const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
|
||||
std::vector<unsigned int> pegin_indices;
|
||||
// do all inputs exist?
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||
const CTxIn& txin = tx.vin[i];
|
||||
|
|
@ -816,6 +946,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
if (m_view.IsPeginSpent(pegin)) {
|
||||
return state.Invalid(TxValidationResult::TX_CONSENSUS, "pegin-already-claimed");
|
||||
}
|
||||
pegin_indices.push_back(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -922,6 +1053,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
// ELEMENTS: accept discounted fees for Confidential Transactions only, if enabled.
|
||||
int64_t package_size = Params().GetAcceptDiscountCT() ? GetDiscountVirtualTransactionSize(tx) : ws.m_vsize;
|
||||
if (!bypass_limits && !CheckFeeRate(package_size, ws.m_modified_fees, state)) return false;
|
||||
// ELEMENTS: check if peg-in subsidy is required and min peg-in amount is met
|
||||
if (!CheckPeginSubsidyAndMinimum(state, tx, pegin_indices)) return false;
|
||||
|
||||
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);
|
||||
// Calculate in-mempool ancestors, up to a limit.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <block_proof.h>
|
||||
#include <core_io.h>
|
||||
#include <deploymentstatus.h>
|
||||
#include <dynafed.h>
|
||||
#include <issuance.h>
|
||||
#include <key_io.h>
|
||||
#include <mainchainrpc.h>
|
||||
|
|
@ -15,10 +16,11 @@
|
|||
#include <script/generic.hpp>
|
||||
#include <script/pegins.h>
|
||||
#include <secp256k1.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <wallet/coincontrol.h>
|
||||
#include <wallet/fees.h>
|
||||
#include <wallet/rpc/util.h>
|
||||
#include <wallet/receive.h>
|
||||
#include <wallet/rpc/util.h>
|
||||
#include <wallet/spend.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
|
|
@ -220,6 +222,25 @@ RPCHelpMan getpeginaddress()
|
|||
|
||||
ret.pushKV("mainchain_address", EncodeParentDestination(mainchain_dest));
|
||||
ret.pushKV("claim_script", HexStr(dest_script));
|
||||
|
||||
PeginMinimum pegin_minimum = Params().GetPeginMinimum();
|
||||
if (pegin_minimum.amount > 0) {
|
||||
ret.pushKV("pegin_min_amount", FormatMoney(pegin_minimum.amount));
|
||||
}
|
||||
if (pegin_minimum.height < std::numeric_limits<int>::max()) {
|
||||
ret.pushKV("pegin_min_height", pegin_minimum.height);
|
||||
ret.pushKV("pegin_min_active", wallet->chain().getTip()->nHeight >= pegin_minimum.height);
|
||||
}
|
||||
|
||||
PeginSubsidy pegin_subsidy = Params().GetPeginSubsidy();
|
||||
if (pegin_subsidy.threshold > 0) {
|
||||
ret.pushKV("pegin_subsidy_threshold", FormatMoney(pegin_subsidy.threshold));
|
||||
}
|
||||
if (pegin_subsidy.height < std::numeric_limits<int>::max()) {
|
||||
ret.pushKV("pegin_subsidy_height", pegin_subsidy.height);
|
||||
ret.pushKV("pegin_subsidy_active", wallet->chain().getTip()->nHeight >= pegin_subsidy.height);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
|
|
@ -432,7 +453,7 @@ RPCHelpMan initpegoutwallet()
|
|||
RPCHelpMan sendtomainchain_base()
|
||||
{
|
||||
return RPCHelpMan{"sendtomainchain",
|
||||
"\nSends sidechain funds to the given mainchain address, through the federated pegin mechanism\n"
|
||||
"\nSends sidechain funds to the given mainchain address, through the federated peg-in mechanism\n"
|
||||
+ wallet::HELP_REQUIRING_PASSPHRASE,
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination address on Bitcoin mainchain"},
|
||||
|
|
@ -787,7 +808,7 @@ RPCHelpMan sendtomainchain()
|
|||
extern UniValue signrawtransaction(const JSONRPCRequest& request);
|
||||
extern UniValue sendrawtransaction(const JSONRPCRequest& request);
|
||||
|
||||
template<typename T_tx_ref, typename T_merkle_block>
|
||||
template <typename T_tx_ref, typename T_merkle_block>
|
||||
static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef, T_merkle_block& merkleBlock)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
|
|
@ -821,9 +842,62 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
// Make the tx
|
||||
CMutableTransaction mtx;
|
||||
|
||||
// Construct pegin input
|
||||
// Construct peg-in input
|
||||
CreatePegInInput(mtx, 0, txBTCRef, merkleBlock, claim_scripts, txData, txOutProofData, wallet->chain().getTip());
|
||||
|
||||
// Get value for peg-in output
|
||||
CAmount value = 0;
|
||||
if (!GetAmountFromParentChainPegin(value, *txBTCRef, mtx.vin[0].prevout.n)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to peg-in must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
|
||||
}
|
||||
|
||||
const PeginMinimum pegin_minimum = Params().GetPeginMinimum();
|
||||
if (pwallet->chain().getTip()->nHeight >= pegin_minimum.height && value < pegin_minimum.amount) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Peg-in amount (%d) is lower than the minimum peg-in amount for this chain (%d).", FormatMoney(value), FormatMoney(pegin_minimum.amount)));
|
||||
}
|
||||
|
||||
const PeginSubsidy pegin_subsidy = Params().GetPeginSubsidy();
|
||||
bool subsidy_required = pwallet->chain().getTip()->nHeight >= pegin_subsidy.height && value < pegin_subsidy.threshold;
|
||||
if (subsidy_required && !gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain) && request.params[3].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Bitcoin transaction fee rate must be supplied, because validatepegin is off and this peg-in requires a burn subsidy.");
|
||||
}
|
||||
|
||||
CAmount fee = 0;
|
||||
uint32_t parent_vsize = 0;
|
||||
CFeeRate feerate = CFeeRate{0};
|
||||
if (gArgs.GetBoolArg("-validatepegin", false) && subsidy_required) {
|
||||
std::string txid = txBTCRef->GetHash().ToString();
|
||||
std::string blockhash = merkleBlock.header.GetHash().ToString();
|
||||
UniValue params(UniValue::VARR);
|
||||
params.push_back(txid);
|
||||
params.push_back(2);
|
||||
params.push_back(blockhash);
|
||||
UniValue result = CallMainChainRPC("getrawtransaction", params);
|
||||
if (result["error"].isStr()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, result["error"]["message"].get_str());
|
||||
} else {
|
||||
parent_vsize = result["result"]["vsize"].get_int64();
|
||||
if (result["result"]["fee"].isNum()) {
|
||||
fee = static_cast<CAmount>(std::round(result["result"]["fee"].get_real() * COIN));
|
||||
} else if (result["result"]["fee"].isObject()) {
|
||||
std::string asset = Params().GetConsensus().parent_pegged_asset.GetHex();
|
||||
if (result["result"]["fee"][asset].isNum()) {
|
||||
fee = static_cast<CAmount>(std::round(result["result"]["fee"][asset].get_real() * COIN));
|
||||
} else {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "No fee result for the parent pegged asset.");
|
||||
}
|
||||
} else {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Fee result is not a number or object.");
|
||||
}
|
||||
// when parent feerate is less than 1 sat/vb, use 1 sat/vb for the calculation
|
||||
feerate = std::max(CFeeRate{fee, parent_vsize}, CFeeRate{1000});
|
||||
}
|
||||
} else if (!request.params[3].isNull()) {
|
||||
// manual feerate, specified in sats/vb but CFeeRate takes sats/Kvb
|
||||
CAmount satsperk = static_cast<CAmount>(std::round(request.params[3].get_real() * 1000));
|
||||
feerate = std::max(CFeeRate{satsperk}, CFeeRate{1000});
|
||||
}
|
||||
|
||||
// Manually construct peg-in transaction, sign it, and send it off.
|
||||
// Decrement the output value as much as needed given the total vsize to
|
||||
// pay the fees.
|
||||
|
|
@ -838,19 +912,17 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
}
|
||||
|
||||
// Get value for output
|
||||
CAmount value = 0;
|
||||
if (!GetAmountFromParentChainPegin(value, *txBTCRef, mtx.vin[0].prevout.n)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
|
||||
}
|
||||
|
||||
// one wallet output and one fee output
|
||||
// add a wallet output for the peg-in value
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(wpkhash)));
|
||||
if (subsidy_required) {
|
||||
// add an op_return for the peg-in fee subsidy
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, 0, CScript() << OP_RETURN));
|
||||
}
|
||||
// add a fee output
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, 0, CScript()));
|
||||
|
||||
// Estimate fee for transaction, decrement fee output(including witness data)
|
||||
unsigned int nBytes = GetVirtualTransactionSize(CTransaction(mtx)) +
|
||||
(1+1+72+1+33)/WITNESS_SCALE_FACTOR;
|
||||
// Estimate fee for transaction, decrement fee output (including witness data)
|
||||
unsigned int nBytes = GetVirtualTransactionSize(CTransaction(mtx)) + (1 + 1 + 72 + 1 + 33) / WITNESS_SCALE_FACTOR;
|
||||
CCoinControl coin_control;
|
||||
FeeCalculation feeCalc;
|
||||
CAmount nFeeNeeded = GetMinimumFee(*pwallet, nBytes, coin_control, &feeCalc);
|
||||
|
|
@ -859,8 +931,35 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
|
||||
}
|
||||
|
||||
mtx.vout[0].nValue = mtx.vout[0].nValue.GetAmount() - nFeeNeeded;
|
||||
mtx.vout[1].nValue = mtx.vout[1].nValue.GetAmount() + nFeeNeeded;
|
||||
if (subsidy_required) {
|
||||
CHECK_NONFATAL(mtx.vout.size() == 3);
|
||||
|
||||
// calculate the subsidy as the amount required to spend the P2WSH output
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(pwallet->chain().getTip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
int t = 0;
|
||||
int n = 0;
|
||||
CHECK_NONFATAL(fedpegscripts.size() > 0);
|
||||
CHECK_NONFATAL(ParseFedPegQuorum(fedpegscripts[0].second, t, n));
|
||||
|
||||
// P2WSH input is 41 bytes: txid (32) + vout (4) + scriptsig len (1) + sequence (4)
|
||||
// the witness to spend is `t` signatures + the script size
|
||||
unsigned int weight = WITNESS_SCALE_FACTOR * (32 + 4 + 1 + 4) + (t * 72 + fedpegscripts[0].second.size());
|
||||
unsigned int vbytes = (weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
|
||||
|
||||
CAmount subsidy = feerate.GetFee(vbytes);
|
||||
|
||||
CAmount value = mtx.vout[0].nValue.GetAmount() - nFeeNeeded - subsidy;
|
||||
mtx.vout[0].nValue = value;
|
||||
mtx.vout[1].nValue = subsidy;
|
||||
mtx.vout[2].nValue = nFeeNeeded;
|
||||
if (IsDust(mtx.vout[0], pwallet->chain().relayDustFee())) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Peg-in transaction would create dust output.");
|
||||
}
|
||||
} else {
|
||||
CHECK_NONFATAL(mtx.vout.size() == 2);
|
||||
mtx.vout[0].nValue = mtx.vout[0].nValue.GetAmount() - nFeeNeeded;
|
||||
mtx.vout[1].nValue = nFeeNeeded;
|
||||
}
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
||||
|
|
@ -886,13 +985,14 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
RPCHelpMan createrawpegin()
|
||||
{
|
||||
return RPCHelpMan{"createrawpegin",
|
||||
"\nCreates a raw transaction to claim coins from the main chain by creating a pegin transaction with the necessary metadata after the corresponding Bitcoin transaction.\n"
|
||||
"\nCreates a raw transaction to claim coins from the main chain by creating a peg-in transaction with the necessary metadata after the corresponding Bitcoin transaction.\n"
|
||||
"Note that this call will not sign the transaction.\n"
|
||||
"If a transaction is not relayed it may require manual addition to a functionary mempool in order for it to be mined.\n",
|
||||
{
|
||||
{"bitcoin_tx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress"},
|
||||
{"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A rawtxoutproof (in hex) generated by the mainchain daemon's `gettxoutproof` containing a proof of only bitcoin_tx"},
|
||||
{"claim_script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The witness program generated by getpeginaddress. Only needed if not in wallet."},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "The fee rate of the Bitcoin transaction in sats/vb, only necessary when validatepegin=0."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
|
|
@ -935,13 +1035,14 @@ RPCHelpMan createrawpegin()
|
|||
RPCHelpMan claimpegin()
|
||||
{
|
||||
return RPCHelpMan{"claimpegin",
|
||||
"\nClaim coins from the main chain by creating a pegin transaction with the necessary metadata after the corresponding Bitcoin transaction.\n"
|
||||
"\nClaim coins from the main chain by creating a peg-in transaction with the necessary metadata after the corresponding Bitcoin transaction.\n"
|
||||
"Note that the transaction will not be relayed unless it is buried at least 102 blocks deep.\n"
|
||||
"If a transaction is not relayed it may require manual addition to a functionary mempool in order for it to be mined.\n",
|
||||
{
|
||||
{"bitcoin_tx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress"},
|
||||
{"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A rawtxoutproof (in hex) generated by the mainchain daemon's `gettxoutproof` containing a proof of only bitcoin_tx"},
|
||||
{"claim_script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The witness program generated by getpeginaddress. Only needed if not in wallet."},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "The fee rate of the Bitcoin transaction in sats/vb, only necessary when validatepegin=0."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::STR_HEX, "txid", "txid of the resulting sidechain transaction",
|
||||
|
|
@ -996,7 +1097,7 @@ RPCHelpMan claimpegin()
|
|||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
||||
}
|
||||
|
||||
// To check if it's not double spending an existing pegin UTXO, we check mempool acceptance.
|
||||
// To check if it's not double spending an existing peg-in UTXO, we check mempool acceptance.
|
||||
const MempoolAcceptResult res = pwallet->chain().testPeginClaimAcceptance(MakeTransactionRef(mtx));
|
||||
if (res.m_result_type != MempoolAcceptResult::ResultType::VALID) {
|
||||
bilingual_str error = Untranslated(strprintf("Error: The transaction was rejected! Reason given: %s", res.m_state.ToString()));
|
||||
|
|
@ -1202,7 +1303,7 @@ RPCHelpMan blindrawtransaction()
|
|||
for (size_t nIn = 0; nIn < tx.vin.size(); ++nIn) {
|
||||
COutPoint prevout = tx.vin[nIn].prevout;
|
||||
|
||||
// Special handling for pegin inputs: no blinds and explicit amount/asset.
|
||||
// Special handling for peg-in inputs: no blinds and explicit amount/asset.
|
||||
if (tx.vin[nIn].m_is_pegin) {
|
||||
std::string err;
|
||||
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, fedpegscripts, prevout, err, false)) {
|
||||
|
|
|
|||
921
test/functional/feature_pegin_subsidy.py
Executable file
921
test/functional/feature_pegin_subsidy.py
Executable file
|
|
@ -0,0 +1,921 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# can be run with parent bitcoind node
|
||||
# tested with bitcoind v28.2 and v29.0
|
||||
# test/functional/feature_pegin_subsidy.py --parent_bitcoin --parent_binpath="/path/to/bitcoind" --nosandbox
|
||||
|
||||
from decimal import Decimal
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_raises_rpc_error,
|
||||
find_vout_for_address,
|
||||
get_auth_cookie,
|
||||
get_datadir_path,
|
||||
rpc_port,
|
||||
p2p_port,
|
||||
assert_equal,
|
||||
)
|
||||
from test_framework import util
|
||||
|
||||
PEGIN_MINIMUM_HEIGHT = 102
|
||||
PEGIN_SUBSIDY_HEIGHT = 150
|
||||
|
||||
|
||||
def get_new_unconfidential_address(node, addr_type="bech32"):
|
||||
addr = node.getnewaddress("", addr_type)
|
||||
val_addr = node.getaddressinfo(addr)
|
||||
if "unconfidential" in val_addr:
|
||||
return val_addr["unconfidential"]
|
||||
return val_addr["address"]
|
||||
|
||||
|
||||
class PeginSubsidyTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 3
|
||||
self.disable_syscall_sandbox = True
|
||||
|
||||
def add_options(self, parser):
|
||||
parser.add_argument(
|
||||
"--parent_binpath",
|
||||
dest="parent_binpath",
|
||||
default="",
|
||||
help="Use a different binary for launching nodes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--parent_bitcoin",
|
||||
dest="parent_bitcoin",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Parent nodes are Bitcoin",
|
||||
)
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def setup_network(self, split=False):
|
||||
if self.options.parent_bitcoin and self.options.parent_binpath == "":
|
||||
raise Exception("Can't run with --parent_bitcoin without specifying --parent_binpath")
|
||||
|
||||
self.nodes = []
|
||||
# Setup parent nodes
|
||||
parent_chain = "elementsregtest" if not self.options.parent_bitcoin else "regtest"
|
||||
parent_binary = [self.options.parent_binpath] if self.options.parent_binpath != "" else None
|
||||
|
||||
extra_args = [
|
||||
"-port=" + str(p2p_port(0)),
|
||||
"-rpcport=" + str(rpc_port(0)),
|
||||
# to test minimum parent tx fee
|
||||
"-minrelaytxfee=0.00000100",
|
||||
"-blockmintxfee=0.00000100",
|
||||
"-mintxfee=0.00000100",
|
||||
]
|
||||
if self.options.parent_bitcoin:
|
||||
# bitcoind can't read elements.conf config files
|
||||
extra_args.extend(
|
||||
[
|
||||
"-regtest=1",
|
||||
"-printtoconsole=0",
|
||||
"-server=1",
|
||||
"-discover=0",
|
||||
"-keypool=1",
|
||||
"-listenonion=0",
|
||||
"-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version
|
||||
"-fallbackfee=0.0002",
|
||||
"-deprecatedrpc=create_bdb",
|
||||
]
|
||||
)
|
||||
self.expected_stderr = (
|
||||
f"Error: Unable to bind to 127.0.0.1:{p2p_port(1)} on this computer. Elements Core is probably already running."
|
||||
)
|
||||
else:
|
||||
extra_args.extend(
|
||||
[
|
||||
"-validatepegin=0",
|
||||
"-initialfreecoins=0",
|
||||
"-anyonecanspendaremine=1",
|
||||
"-signblockscript=51", # OP_TRUE
|
||||
"-dustrelayfee=0.00003000", # use the Bitcoin default dust relay fee rate for the parent nodes
|
||||
]
|
||||
)
|
||||
self.expected_stderr = ""
|
||||
|
||||
self.add_nodes(1, [extra_args], chain=[parent_chain], binary=parent_binary)
|
||||
self.start_node(0)
|
||||
self.log.info(f"Node 0 started (mainchain: {'bitcoind' if self.options.parent_bitcoin else 'elementsd'})")
|
||||
|
||||
# set hard-coded mining keys for non-Elements chains
|
||||
if self.options.parent_bitcoin:
|
||||
self.nodes[0].set_deterministic_priv_key(
|
||||
"2Mysp7FKKe52eoC2JmU46irt1dt58TpCvhQ",
|
||||
"cTNbtVJmhx75RXomhYWSZAafuNNNKPd1cr2ZiUcAeukLNGrHWjvJ",
|
||||
)
|
||||
|
||||
self.parentgenesisblockhash = self.nodes[0].getblockhash(0)
|
||||
if not self.options.parent_bitcoin:
|
||||
parent_pegged_asset = self.nodes[0].getsidechaininfo()["pegged_asset"]
|
||||
|
||||
# Setup sidechain nodes
|
||||
# use the current liquidv1 fedpegscript for testing purposes
|
||||
self.fedpegscript = "5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68"
|
||||
for n in range(2):
|
||||
validatepegin = "1" if n == 0 else "0"
|
||||
extra_args = [
|
||||
"-printtoconsole=0",
|
||||
"-port=" + str(p2p_port(1 + n)),
|
||||
"-rpcport=" + str(rpc_port(1 + n)),
|
||||
"-validatepegin=%s" % validatepegin,
|
||||
"-fallbackfee=0.00001000",
|
||||
"-fedpegscript=%s" % self.fedpegscript,
|
||||
"-minrelaytxfee=0",
|
||||
"-blockmintxfee=0",
|
||||
"-initialfreecoins=0",
|
||||
"-peginconfirmationdepth=10",
|
||||
"-mainchainrpchost=127.0.0.1",
|
||||
"-mainchainrpcport=%s" % rpc_port(0),
|
||||
"-parentgenesisblockhash=%s" % self.parentgenesisblockhash,
|
||||
"-parentpubkeyprefix=111",
|
||||
"-parentscriptprefix=196",
|
||||
"-parent_bech32_hrp=bcrt",
|
||||
# Turn of consistency checks that can cause assert when parent node stops
|
||||
# and a peg-in transaction fails this belt-and-suspenders check.
|
||||
# NOTE: This can cause spurious problems in regtest, and should be dealt with in a better way.
|
||||
"-checkmempool=0",
|
||||
"-peginsubsidyheight=%s" % PEGIN_SUBSIDY_HEIGHT,
|
||||
"-peginsubsidythreshold=2.0",
|
||||
"-peginminheight=%s" % PEGIN_MINIMUM_HEIGHT,
|
||||
"-peginminamount=1.0",
|
||||
]
|
||||
if not self.options.parent_bitcoin:
|
||||
extra_args.extend(
|
||||
[
|
||||
"-parentpubkeyprefix=235",
|
||||
"-parentscriptprefix=75",
|
||||
"-parent_bech32_hrp=ert",
|
||||
"-con_parent_chain_signblockscript=51",
|
||||
"-con_parent_pegged_asset=%s" % parent_pegged_asset,
|
||||
]
|
||||
)
|
||||
|
||||
# Use rpcuser auth only for first parent.
|
||||
if n == 0:
|
||||
# Extract username and password from cookie file and use directly.
|
||||
datadir = get_datadir_path(self.options.tmpdir, n)
|
||||
rpc_u, rpc_p = get_auth_cookie(datadir, parent_chain)
|
||||
extra_args.extend(
|
||||
[
|
||||
"-mainchainrpcuser=%s" % rpc_u,
|
||||
"-mainchainrpcpassword=%s" % rpc_p,
|
||||
]
|
||||
)
|
||||
else:
|
||||
# Need to specify where to find parent cookie file
|
||||
datadir = get_datadir_path(self.options.tmpdir, n)
|
||||
extra_args.append("-mainchainrpccookiefile=" + datadir + "/" + parent_chain + "/.cookie")
|
||||
|
||||
self.add_nodes(1, [extra_args], chain=["elementsregtest"])
|
||||
self.start_node(1 + n)
|
||||
self.log.info(f"Node {1 + n} started (sidechain: elementsd)")
|
||||
|
||||
# We only connect the same-chain nodes, so sync_all works correctly
|
||||
self.connect_nodes(1, 2)
|
||||
self.node_groups = [
|
||||
[self.nodes[0]],
|
||||
[self.nodes[1], self.nodes[2]],
|
||||
]
|
||||
for node_group in self.node_groups:
|
||||
self.sync_all(node_group)
|
||||
self.log.info("Setting up network done")
|
||||
|
||||
def run_test(self):
|
||||
self.import_deterministic_coinbase_privkeys() # Create wallets for all nodes
|
||||
|
||||
parent = self.nodes[0]
|
||||
sidechain = self.nodes[1]
|
||||
sidechain2 = self.nodes[2]
|
||||
|
||||
assert_equal(sidechain.getsidechaininfo()["pegin_confirmation_depth"], 10) # 10+2 confirms required to get into mempool and confirm
|
||||
|
||||
parent.importprivkey(privkey=parent.get_deterministic_priv_key().key, label="mining")
|
||||
sidechain.importprivkey(privkey=sidechain.get_deterministic_priv_key().key, label="mining")
|
||||
util.node_fastmerkle = sidechain
|
||||
|
||||
self.generate(parent, 101, sync_fun=self.no_op)
|
||||
self.generate(sidechain, 101, sync_fun=self.no_op)
|
||||
|
||||
def sync_sidechain():
|
||||
return self.sync_all([sidechain, sidechain2])
|
||||
|
||||
DEFAULT_FEERATE = 1.0
|
||||
|
||||
def parent_pegin(parent, node, amount=1.0, feerate=DEFAULT_FEERATE):
|
||||
address = node.getpeginaddress()
|
||||
mainchain_address, claim_script = (
|
||||
address["mainchain_address"],
|
||||
address["claim_script"],
|
||||
)
|
||||
txid = parent.sendtoaddress(address=mainchain_address, amount=amount, fee_rate=feerate)
|
||||
vout = find_vout_for_address(parent, txid, mainchain_address)
|
||||
self.generate(parent, 12, sync_fun=self.no_op)
|
||||
txoutproof = parent.gettxoutproof([txid])
|
||||
bitcoin_txhex = parent.gettransaction(txid)["hex"]
|
||||
return (
|
||||
txid,
|
||||
vout,
|
||||
txoutproof,
|
||||
bitcoin_txhex,
|
||||
claim_script,
|
||||
)
|
||||
|
||||
self.log.info("check new fields for getpeginaddress and getsidechaininfo")
|
||||
result = sidechain.getpeginaddress()
|
||||
assert_equal(result["pegin_min_amount"], "1.00")
|
||||
assert_equal(result["pegin_min_height"], PEGIN_MINIMUM_HEIGHT)
|
||||
assert_equal(result["pegin_min_active"], False)
|
||||
assert_equal(result["pegin_subsidy_threshold"], "2.00")
|
||||
assert_equal(result["pegin_subsidy_height"], PEGIN_SUBSIDY_HEIGHT)
|
||||
assert_equal(result["pegin_subsidy_active"], False)
|
||||
result = sidechain.getsidechaininfo()
|
||||
assert_equal(result["pegin_min_amount"], "1.00")
|
||||
assert_equal(result["pegin_min_height"], PEGIN_MINIMUM_HEIGHT)
|
||||
assert_equal(result["pegin_min_active"], False)
|
||||
assert_equal(result["pegin_subsidy_threshold"], "2.00")
|
||||
assert_equal(result["pegin_subsidy_height"], PEGIN_SUBSIDY_HEIGHT)
|
||||
assert_equal(result["pegin_subsidy_active"], False)
|
||||
|
||||
self.log.info("check min peg-in amount before minimum peg-in height")
|
||||
assert_equal(sidechain.getblockchaininfo()["blocks"], PEGIN_MINIMUM_HEIGHT - 1)
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=0.5)
|
||||
pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(pegin_tx["confirmations"], 1)
|
||||
|
||||
assert_equal(sidechain.getblockchaininfo()["blocks"], PEGIN_MINIMUM_HEIGHT)
|
||||
result = sidechain.getpeginaddress()
|
||||
assert_equal(result["pegin_min_amount"], "1.00")
|
||||
assert_equal(result["pegin_min_height"], PEGIN_MINIMUM_HEIGHT)
|
||||
assert_equal(result["pegin_min_active"], True)
|
||||
assert_equal(result["pegin_subsidy_threshold"], "2.00")
|
||||
assert_equal(result["pegin_subsidy_height"], PEGIN_SUBSIDY_HEIGHT)
|
||||
assert_equal(result["pegin_subsidy_active"], False)
|
||||
result = sidechain.getsidechaininfo()
|
||||
assert_equal(result["pegin_min_amount"], "1.00")
|
||||
assert_equal(result["pegin_min_height"], PEGIN_MINIMUM_HEIGHT)
|
||||
assert_equal(result["pegin_min_active"], True)
|
||||
assert_equal(result["pegin_subsidy_threshold"], "2.00")
|
||||
assert_equal(result["pegin_subsidy_height"], PEGIN_SUBSIDY_HEIGHT)
|
||||
assert_equal(result["pegin_subsidy_active"], False)
|
||||
|
||||
self.log.info("check min peg-in amount after minimum peg-in height")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=0.5)
|
||||
assert_raises_rpc_error(
|
||||
-4,
|
||||
"Peg-in amount (0.50) is lower than the minimum peg-in amount for this chain (1.00).",
|
||||
sidechain.claimpegin,
|
||||
bitcoin_txhex,
|
||||
txoutproof,
|
||||
claim_script,
|
||||
)
|
||||
# check manually constructed
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex,
|
||||
"pegin_txout_proof": txoutproof,
|
||||
"pegin_claim_script": claim_script,
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
outputs = [
|
||||
{sidechain.getnewaddress(): Decimal("0.5") - fee},
|
||||
{"fee": fee},
|
||||
]
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-value-too-low")
|
||||
|
||||
self.log.info("createrawpegin before enforcement, with validatepegin, below threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=1.0, feerate=2.0)
|
||||
pegintx = sidechain.createrawpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
signed = sidechain.signrawtransactionwithwallet(pegintx["hex"])
|
||||
assert_equal(signed["complete"], True)
|
||||
pegin_txid = sidechain.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("createrawpegin before enforcement, with validatepegin, above threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=2.0, feerate=2.0)
|
||||
pegintx = sidechain.createrawpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
signed = sidechain.signrawtransactionwithwallet(pegintx["hex"])
|
||||
assert_equal(signed["complete"], True)
|
||||
pegin_txid = sidechain.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("createrawpegin before enforcement, without validatepegin, below threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=1.0, feerate=2.0)
|
||||
pegintx = sidechain2.createrawpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
signed = sidechain2.signrawtransactionwithwallet(pegintx["hex"])
|
||||
assert_equal(signed["complete"], True)
|
||||
pegin_txid = sidechain2.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("createrawpegin before enforcement, without validatepegin, above threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=2.0, feerate=2.0)
|
||||
pegintx = sidechain2.createrawpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
signed = sidechain2.signrawtransactionwithwallet(pegintx["hex"])
|
||||
assert_equal(signed["complete"], True)
|
||||
pegin_txid = sidechain2.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin before enforcement, with validatepegin, below threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=1.0, feerate=2.0)
|
||||
pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin before enforcement, with validatepegin, above threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=2.0, feerate=2.0)
|
||||
pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin before enforcement, without validatepegin, below threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=1.0, feerate=2.0)
|
||||
pegin_txid = sidechain2.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin before enforcement, without validatepegin, above threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=2.0, feerate=2.0)
|
||||
pegin_txid = sidechain2.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
num = PEGIN_SUBSIDY_HEIGHT - sidechain.getblockchaininfo()["blocks"]
|
||||
assert num > 0
|
||||
self.generate(sidechain, num, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info(f"===== peg-in subsidy enforcement at height {PEGIN_SUBSIDY_HEIGHT} =====")
|
||||
assert_equal(sidechain.getblockchaininfo()["blocks"], PEGIN_SUBSIDY_HEIGHT)
|
||||
|
||||
self.log.info("check new fields for getpeginaddress and getsidechaininfo")
|
||||
result = sidechain.getpeginaddress()
|
||||
assert_equal(result["pegin_min_amount"], "1.00")
|
||||
assert_equal(result["pegin_min_height"], PEGIN_MINIMUM_HEIGHT)
|
||||
assert_equal(result["pegin_min_active"], True)
|
||||
assert_equal(result["pegin_subsidy_threshold"], "2.00")
|
||||
assert_equal(result["pegin_subsidy_height"], PEGIN_SUBSIDY_HEIGHT)
|
||||
assert_equal(result["pegin_subsidy_active"], True)
|
||||
result = sidechain.getsidechaininfo()
|
||||
assert_equal(result["pegin_min_amount"], "1.00")
|
||||
assert_equal(result["pegin_min_height"], PEGIN_MINIMUM_HEIGHT)
|
||||
assert_equal(result["pegin_min_active"], True)
|
||||
assert_equal(result["pegin_subsidy_threshold"], "2.00")
|
||||
assert_equal(result["pegin_subsidy_height"], PEGIN_SUBSIDY_HEIGHT)
|
||||
assert_equal(result["pegin_subsidy_active"], True)
|
||||
|
||||
# blinded pegins
|
||||
self.log.info("blinded pegin below threshold, with validatepegin, subsidy too low")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=1.0, feerate=1.0)
|
||||
addr = sidechain.getnewaddress(address_type="blech32")
|
||||
utxo = sidechain.listunspent()[0]
|
||||
changeaddr = sidechain.getrawchangeaddress(address_type="blech32")
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex,
|
||||
"pegin_txout_proof": txoutproof,
|
||||
"pegin_claim_script": claim_script,
|
||||
},
|
||||
{
|
||||
"txid": utxo["txid"],
|
||||
"vout": utxo["vout"],
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
subsidy = Decimal("0.00000395")
|
||||
outputs = [
|
||||
{addr: Decimal("1.0") - fee - subsidy},
|
||||
{changeaddr: utxo["amount"]},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
blinded = sidechain.blindrawtransaction(hexstring=raw, ignoreblindfail=False)
|
||||
signed = sidechain.signrawtransactionwithwallet(blinded)
|
||||
assert_equal(signed["complete"], True)
|
||||
# node 2 can't validatepegin but checks the minimum subsidy
|
||||
accept = sidechain2.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
# node 1 will reject
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
assert_raises_rpc_error(
|
||||
-26,
|
||||
"pegin-subsidy-too-low",
|
||||
sidechain.sendrawtransaction,
|
||||
signed["hex"],
|
||||
)
|
||||
|
||||
self.log.info("blinded peg-in above threshold, with validatepegin")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=2.0, feerate=1.0)
|
||||
addr = sidechain.getnewaddress(address_type="blech32")
|
||||
utxo = sidechain.listunspent()[0]
|
||||
changeaddr = sidechain.getrawchangeaddress(address_type="blech32")
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex,
|
||||
"pegin_txout_proof": txoutproof,
|
||||
"pegin_claim_script": claim_script,
|
||||
},
|
||||
{
|
||||
"txid": utxo["txid"],
|
||||
"vout": utxo["vout"],
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
outputs = [
|
||||
{addr: Decimal("2.0") - fee},
|
||||
{changeaddr: utxo["amount"]},
|
||||
{"fee": fee},
|
||||
]
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
blinded = sidechain.blindrawtransaction(hexstring=raw, ignoreblindfail=False)
|
||||
signed = sidechain.signrawtransactionwithwallet(blinded)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], True)
|
||||
# =======
|
||||
|
||||
self.log.info("createrawpegin after enforcement, with validatepegin, below threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain)
|
||||
pegintx = sidechain.createrawpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
signed = sidechain.signrawtransactionwithwallet(pegintx["hex"])
|
||||
pegin_txid = sidechain.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
# WSH input 41 bytes * 4 = 164 weight
|
||||
# Witness (11 * 72 bytes signatures + 626 bytes script size) = 1418 weight
|
||||
# (164 + 1418 + 3) / 4 = 396 vbytes
|
||||
assert_equal(pegin_tx["decoded"]["vout"][1]["value"], Decimal("0.00000396"))
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("createrawpegin after enforcement, with validatepegin, above threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=3.0, feerate=2.0)
|
||||
pegintx = sidechain.createrawpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
signed = sidechain.signrawtransactionwithwallet(pegintx["hex"])
|
||||
pegin_txid = sidechain.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("createrawpegin after enforcement, without validatepegin, below threshold")
|
||||
feerate = 2.0
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, 1.0, feerate)
|
||||
assert_raises_rpc_error(
|
||||
-8,
|
||||
"Bitcoin transaction fee rate must be supplied, because validatepegin is off and this peg-in requires a burn subsidy.",
|
||||
sidechain2.createrawpegin,
|
||||
bitcoin_txhex,
|
||||
txoutproof,
|
||||
claim_script,
|
||||
)
|
||||
|
||||
pegintx = sidechain2.createrawpegin(bitcoin_txhex, txoutproof, claim_script, feerate)
|
||||
signed = sidechain2.signrawtransactionwithwallet(pegintx["hex"])
|
||||
pegin_txid = sidechain2.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
assert_equal(pegin_tx["decoded"]["vout"][1]["value"], Decimal("0.00000792"))
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("createrawpegin after enforcement, without validatepegin, above threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=2.0, feerate=2.0)
|
||||
pegintx = sidechain2.createrawpegin(bitcoin_txhex, txoutproof, claim_script, feerate)
|
||||
signed = sidechain2.signrawtransactionwithwallet(pegintx["hex"])
|
||||
assert_equal(signed["complete"], True)
|
||||
pegin_txid = sidechain2.sendrawtransaction(signed["hex"])
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin after enforcement, with validatepegin, below threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=1.0, feerate=2.0)
|
||||
pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
assert_equal(pegin_tx["decoded"]["vout"][1]["value"], Decimal("0.00000792"))
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin after enforcement, with validatepegin, above threshold")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=2.0, feerate=2.0)
|
||||
pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin after enforcement, without validatepegin, below threshold")
|
||||
feerate = 2.0
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, 1.0, feerate)
|
||||
assert_raises_rpc_error(
|
||||
-8,
|
||||
"Bitcoin transaction fee rate must be supplied, because validatepegin is off and this peg-in requires a burn subsidy.",
|
||||
sidechain2.claimpegin,
|
||||
bitcoin_txhex,
|
||||
txoutproof,
|
||||
claim_script,
|
||||
)
|
||||
|
||||
pegin_txid = sidechain2.claimpegin(bitcoin_txhex, txoutproof, claim_script, feerate)
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
assert_equal(pegin_tx["decoded"]["vout"][1]["value"], Decimal("0.00000792"))
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin after enforcement, without validatepegin, above threshold")
|
||||
feerate = 2.0
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, 2.0, feerate)
|
||||
pegin_txid = sidechain2.claimpegin(bitcoin_txhex, txoutproof, claim_script, feerate)
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 2)
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("claimpegin after enforcement, without validatepegin, below threshold, with incorrect subsidy output")
|
||||
# should be accepted by sidechain2 but rejected by the node that is validating pegins
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=1.0, feerate=2.0)
|
||||
assert_raises_rpc_error(
|
||||
-8,
|
||||
"Bitcoin transaction fee rate must be supplied, because validatepegin is off and this peg-in requires a burn subsidy.",
|
||||
sidechain2.claimpegin,
|
||||
bitcoin_txhex,
|
||||
txoutproof,
|
||||
claim_script,
|
||||
)
|
||||
|
||||
low_feerate = 1.0
|
||||
pegin_txid = sidechain2.claimpegin(bitcoin_txhex, txoutproof, claim_script, low_feerate)
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
|
||||
accept = sidechain.testmempoolaccept([pegin_tx["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=1.99999999, feerate=2.0)
|
||||
assert_raises_rpc_error(
|
||||
-8,
|
||||
"Bitcoin transaction fee rate must be supplied, because validatepegin is off and this peg-in requires a burn subsidy.",
|
||||
sidechain2.claimpegin,
|
||||
bitcoin_txhex,
|
||||
txoutproof,
|
||||
claim_script,
|
||||
)
|
||||
|
||||
low_feerate = 1.0
|
||||
pegin_txid = sidechain2.claimpegin(bitcoin_txhex, txoutproof, claim_script, low_feerate)
|
||||
pegin_tx = sidechain2.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
|
||||
accept = sidechain.testmempoolaccept([pegin_tx["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
# sidechain2 should accept a 1 sat/vb subsidy for a higher feerate parent, but validating node should reject
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=1.0, feerate=2.0)
|
||||
# first try with less than 1 sat/vb subsidy
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex,
|
||||
"pegin_txout_proof": txoutproof,
|
||||
"pegin_claim_script": claim_script,
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
addr = get_new_unconfidential_address(sidechain)
|
||||
# subsidy less than 1 sat/vb
|
||||
subsidy = Decimal("0.00000395")
|
||||
outputs = [
|
||||
{addr: Decimal("1.0") - fee - subsidy},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
|
||||
raw = sidechain2.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain2.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
|
||||
accept = sidechain2.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
|
||||
# subsidy for 1 sat/vb accepted by sidechain2, but rejected by validating node
|
||||
subsidy = Decimal("0.00000396")
|
||||
outputs = [
|
||||
{addr: Decimal("1.0") - fee - subsidy},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
|
||||
raw = sidechain2.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain2.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
|
||||
accept = sidechain2.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], True)
|
||||
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
|
||||
# sub 1 sat/vb parent feerate should use 1 sat/vb for subsidy calculation
|
||||
self.log.info("claimpegin after enforcement, with validatepegin, below threshold, sub 1 sat/vb parent")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=1, feerate=0.1)
|
||||
pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
assert_equal(pegin_tx["decoded"]["vout"][1]["value"], Decimal("0.00000396"))
|
||||
|
||||
# check manually constructed peg-in from a sub 1 sat/vb parent
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=1, feerate=0.1)
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex,
|
||||
"pegin_txout_proof": txoutproof,
|
||||
"pegin_claim_script": claim_script,
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
addr = get_new_unconfidential_address(sidechain)
|
||||
# subsidy too low
|
||||
subsidy = Decimal("0.00000395")
|
||||
outputs = [
|
||||
{addr: Decimal("1.0") - fee - subsidy},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
|
||||
# subsidy accepted
|
||||
subsidy = Decimal("0.00000396")
|
||||
outputs = [
|
||||
{addr: Decimal("1.0") - fee - subsidy},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], True)
|
||||
sidechain.sendrawtransaction(signed["hex"])
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
# =================================
|
||||
|
||||
self.log.info("construct a multi-pegin tx, below threshold")
|
||||
feerate = 2.0
|
||||
txid1, vout1, txoutproof1, bitcoin_txhex1, claim_script1 = parent_pegin(parent, sidechain, 0.5, feerate)
|
||||
txid2, vout2, txoutproof2, bitcoin_txhex2, claim_script2 = parent_pegin(parent, sidechain, 1.0, feerate)
|
||||
|
||||
addr1 = get_new_unconfidential_address(sidechain)
|
||||
addr2 = get_new_unconfidential_address(sidechain)
|
||||
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid1,
|
||||
"vout": vout1,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex1,
|
||||
"pegin_txout_proof": txoutproof1,
|
||||
"pegin_claim_script": claim_script1,
|
||||
},
|
||||
{
|
||||
"txid": txid2,
|
||||
"vout": vout2,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex2,
|
||||
"pegin_txout_proof": txoutproof2,
|
||||
"pegin_claim_script": claim_script2,
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
subsidy = Decimal("0.00001583")
|
||||
outputs = [
|
||||
{addr1: Decimal("0.5") - fee - subsidy},
|
||||
{addr2: 1.0},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-subsidy-too-low")
|
||||
|
||||
subsidy = Decimal("0.00001584")
|
||||
outputs = [
|
||||
{addr1: Decimal("0.5") - fee - subsidy},
|
||||
{addr2: 1.0},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], True)
|
||||
sidechain.sendrawtransaction(signed["hex"])
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
self.log.info("construct a multi-pegin tx, above threshold")
|
||||
txid1, vout1, txoutproof1, bitcoin_txhex1, claim_script1 = parent_pegin(parent, sidechain, amount=0.5, feerate=1.0)
|
||||
txid2, vout2, txoutproof2, bitcoin_txhex2, claim_script2 = parent_pegin(parent, sidechain, amount=1.5, feerate=2.0)
|
||||
addr1 = get_new_unconfidential_address(sidechain)
|
||||
addr2 = get_new_unconfidential_address(sidechain)
|
||||
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid1,
|
||||
"vout": vout1,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex1,
|
||||
"pegin_txout_proof": txoutproof1,
|
||||
"pegin_claim_script": claim_script1,
|
||||
},
|
||||
{
|
||||
"txid": txid2,
|
||||
"vout": vout2,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex2,
|
||||
"pegin_txout_proof": txoutproof2,
|
||||
"pegin_claim_script": claim_script2,
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
outputs = [
|
||||
{addr1: 0.5},
|
||||
{addr2: Decimal("1.5") - fee},
|
||||
{"fee": fee},
|
||||
]
|
||||
raw = sidechain.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain.signrawtransactionwithwallet(raw)
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], True)
|
||||
sidechain.sendrawtransaction(signed["hex"])
|
||||
self.generate(sidechain2, 1, sync_fun=sync_sidechain)
|
||||
|
||||
# minimum peg-in amount is 1.0
|
||||
self.log.info("claimpegin below minimum peg-in amount")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount=0.99999999)
|
||||
assert_raises_rpc_error(
|
||||
-4,
|
||||
"Peg-in amount (0.99999999) is lower than the minimum peg-in amount for this chain (1.00).",
|
||||
sidechain2.claimpegin,
|
||||
bitcoin_txhex,
|
||||
txoutproof,
|
||||
claim_script,
|
||||
)
|
||||
|
||||
# check minimum peg-in amount in mempool validation by constructing manually
|
||||
self.log.info("rawtransaction below minimum peg-in amount")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=0.99999999)
|
||||
addr = sidechain2.getnewaddress()
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex,
|
||||
"pegin_txout_proof": txoutproof,
|
||||
"pegin_claim_script": claim_script,
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
subsidy = Decimal("0.00000396")
|
||||
outputs = [
|
||||
{addr: Decimal("0.99999999") - fee - subsidy},
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
|
||||
raw = sidechain2.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain2.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain2.testmempoolaccept([signed["hex"]])
|
||||
# node2 can check the min peg-in amount as the peg-in amount is in the witness
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-value-too-low")
|
||||
# node1 rejects below the min peg-in amount with validatepegin
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "pegin-value-too-low")
|
||||
assert_raises_rpc_error(
|
||||
-26,
|
||||
"pegin-value-too-low",
|
||||
sidechain.sendrawtransaction,
|
||||
signed["hex"],
|
||||
)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
# test various feerates
|
||||
self.log.info("claimpegin with validatepegin, below threshold, at various feerates")
|
||||
for feerate in [1.0, 1.5, 2.0, 2.3, 3.9, 4.6, 5.2, 6.1, 10.01, 20.7, 22.22, 24.18]:
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, 1.0, feerate)
|
||||
pegin_txid = sidechain.claimpegin(bitcoin_txhex, txoutproof, claim_script)
|
||||
pegin_tx = sidechain.gettransaction(pegin_txid, True, True)
|
||||
assert_equal(len(pegin_tx["decoded"]["vout"]), 3)
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
|
||||
# dust error
|
||||
# restart node1 with no min peg-in amount
|
||||
self.stop_node(1, expected_stderr=self.expected_stderr) # when running with bitcoind as parent node this stderr can occur
|
||||
self.start_node(1, extra_args=sidechain.extra_args + ["-peginminamount=0"])
|
||||
self.log.info("claimpegin dust error")
|
||||
amount = Decimal("0.00000546") if self.options.parent_bitcoin else Decimal("0.00000645")
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain, amount)
|
||||
assert_raises_rpc_error(
|
||||
-4,
|
||||
"Peg-in transaction would create dust output.",
|
||||
sidechain.claimpegin,
|
||||
bitcoin_txhex,
|
||||
txoutproof,
|
||||
claim_script,
|
||||
)
|
||||
# check dust mempool validation by constructing manually
|
||||
txid, vout, txoutproof, bitcoin_txhex, claim_script = parent_pegin(parent, sidechain2, amount=0.00001570)
|
||||
addr = sidechain2.getnewaddress()
|
||||
inputs = [
|
||||
{
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
"pegin_bitcoin_tx": bitcoin_txhex,
|
||||
"pegin_txout_proof": txoutproof,
|
||||
"pegin_claim_script": claim_script,
|
||||
},
|
||||
]
|
||||
fee = Decimal("0.00000363")
|
||||
subsidy = Decimal("0.00001194")
|
||||
outputs = [
|
||||
{addr: Decimal("0.00001570") - fee - subsidy}, # 14 sats is dust at 0.1 sat/vb dustrelayfee
|
||||
{"burn": subsidy},
|
||||
{"fee": fee},
|
||||
]
|
||||
|
||||
raw = sidechain2.createrawtransaction(inputs, outputs)
|
||||
signed = sidechain2.signrawtransactionwithwallet(raw)
|
||||
assert_equal(signed["complete"], True)
|
||||
accept = sidechain2.testmempoolaccept([signed["hex"]])
|
||||
# mempool validation already checks for dust outputs
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "dust")
|
||||
accept = sidechain.testmempoolaccept([signed["hex"]])
|
||||
assert_equal(accept[0]["allowed"], False)
|
||||
assert_equal(accept[0]["reject-reason"], "dust")
|
||||
assert_raises_rpc_error(
|
||||
-26,
|
||||
"dust",
|
||||
sidechain.sendrawtransaction,
|
||||
signed["hex"],
|
||||
)
|
||||
|
||||
# Manually stop sidechains first, then the parent chain.
|
||||
self.stop_node(2)
|
||||
self.stop_node(1, expected_stderr=self.expected_stderr) # when running with bitcoind as parent node this stderr can occur
|
||||
self.stop_node(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
PeginSubsidyTest().main()
|
||||
|
|
@ -191,6 +191,7 @@ BASE_SCRIPTS = [
|
|||
# ELEMENTS: discounted Confidential Transactions
|
||||
'feature_discount_ct.py',
|
||||
'feature_discount_ct_ordering.py',
|
||||
'feature_pegin_subsidy.py --legacy-wallet',
|
||||
'wallet_multiwallet.py --legacy-wallet',
|
||||
'wallet_multiwallet.py --descriptors',
|
||||
'wallet_multiwallet.py --usecli',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue