mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-15 12:51:00 +02:00
Merge 7791d31bcf into merged_master (Elements PR #1499)
This commit is contained in:
commit
9e5aaef647
13 changed files with 1392 additions and 26 deletions
|
|
@ -12,12 +12,14 @@
|
|||
#include <chainparamsbase.h>
|
||||
#include <common/args.h>
|
||||
#include <consensus/params.h>
|
||||
#include <crypto/sha256.h>
|
||||
#include <deploymentinfo.h>
|
||||
#include <issuance.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <logging.h>
|
||||
#include <tinyformat.h>
|
||||
#include <util/chaintype.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/string.h>
|
||||
#include <crypto/sha256.h>
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -712,7 +712,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);
|
||||
|
|
@ -725,6 +725,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);
|
||||
|
||||
// Add the hidden options
|
||||
argsman.AddHiddenArgs(hidden_args);
|
||||
|
|
@ -2073,7 +2077,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));
|
||||
|
|
@ -2084,6 +2088,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"].getInt<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
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <script/script.h>
|
||||
#include <uint256.h>
|
||||
#include <util/chaintype.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <issuance.h>
|
||||
#include <deploymentinfo.h>
|
||||
|
|
@ -249,6 +250,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;
|
||||
|
|
@ -394,6 +397,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;
|
||||
|
|
@ -557,6 +562,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;
|
||||
|
|
@ -669,6 +676,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;
|
||||
|
|
@ -837,6 +846,38 @@ void UpdateActivationParametersFromArgs(const ArgsManager& args, Consensus::Par
|
|||
}
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
|
|
@ -976,6 +1017,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, m_chain_type.chain_name);
|
||||
|
|
@ -1219,6 +1265,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();
|
||||
|
|
@ -1574,6 +1625,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", ""));
|
||||
|
|
|
|||
|
|
@ -78,6 +78,27 @@ struct ChainTxData {
|
|||
double dTxRate; //!< estimated number of transactions per second after that timestamp
|
||||
};
|
||||
|
||||
// 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();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* CChainParams defines various tweakable parameters of a given instance of the
|
||||
* Bitcoin system.
|
||||
|
|
@ -150,6 +171,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; }
|
||||
|
||||
/**
|
||||
* SigNetOptions holds configurations for creating a signet CChainParams.
|
||||
|
|
@ -218,6 +241,8 @@ protected:
|
|||
bool multi_data_permitted;
|
||||
bool accept_discount_ct;
|
||||
bool create_discount_ct;
|
||||
PeginSubsidy pegin_subsidy;
|
||||
PeginMinimum pegin_minimum;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_KERNEL_CHAINPARAMS_H
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
#include <univalue.h>
|
||||
#include <util/check.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
|
|
@ -3183,6 +3184,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;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -342,6 +342,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()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -706,6 +706,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"].getInt<uint64_t>();
|
||||
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;
|
||||
|
|
@ -834,6 +963,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];
|
||||
|
|
@ -855,6 +985,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;
|
||||
}
|
||||
|
||||
|
|
@ -980,6 +1111,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
// blocks and transactions in a package. Package transactions will be checked using package
|
||||
// feerate later.
|
||||
if (!bypass_limits && !args.m_package_feerates && !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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -20,10 +21,11 @@
|
|||
#include <secp256k1.h>
|
||||
#include <timedata.h>
|
||||
#include <util/signalinterrupt.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>
|
||||
|
||||
|
|
@ -228,6 +230,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;
|
||||
},
|
||||
};
|
||||
|
|
@ -440,7 +461,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"},
|
||||
|
|
@ -795,7 +816,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);
|
||||
|
|
@ -829,9 +850,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"].getInt<uint64_t>();
|
||||
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.
|
||||
|
|
@ -845,19 +919,17 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(wpkhash).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.emplace_back(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(*wpkhash));
|
||||
if (subsidy_required) {
|
||||
// add an op_return for the peg-in fee subsidy
|
||||
mtx.vout.emplace_back(Params().GetConsensus().pegged_asset, 0, CScript() << OP_RETURN);
|
||||
}
|
||||
// add a fee output
|
||||
mtx.vout.emplace_back(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);
|
||||
|
|
@ -866,8 +938,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);
|
||||
|
||||
|
|
@ -893,13 +992,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, "The witness program generated by getpeginaddress. Only needed if not in wallet."},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "The fee rate of the Bitcoin transaction in sats/vb, only necessary when validatepegin=0."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
|
|
@ -942,13 +1042,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, "The witness program generated by getpeginaddress. Only needed if not in wallet."},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "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",
|
||||
|
|
@ -1003,7 +1104,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()));
|
||||
|
|
@ -1209,7 +1310,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)) {
|
||||
|
|
|
|||
926
test/functional/feature_pegin_subsidy.py
Executable file
926
test/functional/feature_pegin_subsidy.py
Executable file
|
|
@ -0,0 +1,926 @@
|
|||
#!/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):
|
||||
self.add_wallet_options(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=" + str(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"],
|
||||
maxburnamount="1.0",
|
||||
)
|
||||
|
||||
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"], maxburnamount="1.0")
|
||||
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 = sidechain.sendrawtransaction(signed["hex"], maxburnamount="1.0")
|
||||
self.generate(sidechain, 1, sync_fun=sync_sidechain)
|
||||
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"], maxburnamount="1.0")
|
||||
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"], maxburnamount="1.0")
|
||||
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"],
|
||||
maxburnamount="1.0",
|
||||
)
|
||||
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", "-reindex-chainstate"]) # ELEMENTS FIXME: figure out why we need to reindex chanstate for this restart
|
||||
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"],
|
||||
maxburnamount="1.0",
|
||||
)
|
||||
|
||||
# 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()
|
||||
|
|
@ -244,6 +244,7 @@ BASE_SCRIPTS = [
|
|||
'feature_discount_ct_ordering.py',
|
||||
'p2p_block_sync.py',
|
||||
'p2p_block_sync.py --v2transport',
|
||||
'feature_pegin_subsidy.py --legacy-wallet',
|
||||
'wallet_createwallet.py --legacy-wallet',
|
||||
'wallet_createwallet.py --usecli',
|
||||
'wallet_createwallet.py --descriptors',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue