mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Merge #642: Dynamic federations
f7905d6dfGeneralize the number of epochs old a peg-in can be and still be valid (Gregory Sanders)b105ff45efeature_fedpeg.py: Run in various dynafed transition settings (Gregory Sanders)feef196a2Fix compilation of raw transaction operations with peg-in inputs (Gregory Sanders)953dd82eap2sh-wrap peg-in addresses if fedpeg_program is p2sh-wrapped (Gregory Sanders)e2be2356dRefactor IsPAKValid to not magically acquire chainparams (Gregory Sanders)ecdefc5cbRemove script/standard dependence of ContextualCheckDynaFedHeader (Gregory Sanders)59bed57a1Remove dyanfed.cpp's dependency on script/standard.h (Gregory Sanders)50a2b599cAfter a reorg, boot all peg-ins and peg-outs from mempool (Gregory Sanders)6000752a9Enforce PAK checks on dynafed proposals (Gregory Sanders)dce4cecddReplace fedpeg template init check for pak one (Gregory Sanders)afdf0239bUpdate python test framework for DynaFedParamEntry fedpeg program (Gregory Sanders)95d8339f4Add functional test for illegal proposals (Gregory Sanders)1baad6bf5Update dynafed functional test to new behavior (Gregory Sanders)a3cf151defixup proposal checks (Gregory Sanders)04cc76743getnewblockhex fills out proposal fedpeg program (Gregory Sanders)44f55d36cMake future parent segwit versions in fedpegscripts be vacuously true (Gregory Sanders)d7faffdcaAdd dynafed proposal restrictions (Gregory Sanders)37f90ba50Add description of NextBlockIsParameterTransition (Gregory Sanders)d9387cb5dRemove misleading comment for ContextualCheckDynaFedHeader use (Gregory Sanders)bd11097ceAdd fedpeg_program field in dynafed header (Gregory Sanders)4ad2b8018CreatePAKListFromExtensionSpace just uses FromBytes for correctness (Gregory Sanders)6a5eaa189Remove short-circuit dynafed vote fail for readability (Gregory Sanders)dddc040aas/m_dyna_params/m_dynafed_params/ (Gregory Sanders)6ed63c1fbLight explanation of dynamic federations fields (Gregory Sanders)15996384ds/ConsensusParamEntry/DynaFedParamEntry/ (Gregory Sanders)642260a12s/m_sbs_wit_limit/m_signblock_witness_limit/ (Gregory Sanders)b66804678s/HF_MASK/DYNAFED_HF_MASK/ (Gregory Sanders)0fd39edf5s/d_params/dynafed_params/ (Gregory Sanders)1779956e6signrawtransaction* should use up to date fedpegscript for peg-in signing (Gregory Sanders)47db75e39Dynafed RPC support, tests, and deployment for custom chains (Gregory Sanders)aac354b5bOP_TRUE outputs should be allowed to be segwit-ified by decodescript (Gregory Sanders)055514b68Have removeForBlock boot transactions when required (Gregory Sanders)525b24cf5Expose fedpeg fetching to consensus and mempool internals (Gregory Sanders)4ac437b87GetTransactionSigOpCost shouldn't need fedpegscript to evaluate (Gregory Sanders)5097c3880Add and update startup args for dynafed (Gregory Sanders)24535d6a9Add unused dynamic genesis block style in chainparams (Gregory Sanders)d10c42a26Create epoch length chainparam (Gregory Sanders)b996a425eSet default of multi_data_permitted to enforce_pak (Gregory Sanders)f6f6308c9Correct comment about liquidv1 fedpeg matching template (Gregory Sanders)4c00eb408Miner should stop trying to account for old PAK system (Gregory Sanders)0e10287f7Add miner ability to make dynafed blocks (Gregory Sanders)5d281e7e6ContextualCheckBlockHeader: dynafed doesn't call CheckChallenge (Gregory Sanders)9fe43cdf8Introduce contextual block checks for dynafed (Gregory Sanders)3605505c9Enable pak enforcement at mempool/block level when appropriate (Gregory Sanders)802a05519ScriptHasValidPAKProof takes fedpeg as arg (Gregory Sanders)89174e961Remove old mempool-booting logic (Gregory Sanders)edb865cc2Remove standardness checks for PAK (Gregory Sanders)804cd9f75Update PAK internals, helper functions (Gregory Sanders)16b87ba95Disable PAK loading from configuration on init, disable pak test (Gregory Sanders)918896d6fAdd inactive versionbits dynafed deployment (Gregory Sanders)0a8565bbfReadBlockFromDisk: do genesis block check before block proof check (Gregory Sanders)6f0d9244fBump last old block version for versonbits due to elements (Gregory Sanders)563cd93d4Add python implementation of dynafed block serialization (Gregory Sanders)8d3091bbcDeserialize merkle proofs without witness, which matters for dynafed (Gregory Sanders)830f91723Add fedpegscript-fetching helper (Gregory Sanders)b56fe1495calculate_contract: remove most template checks and assert in preperation for dynafed (Gregory Sanders)fd63cee1aAdd note on peg-in sigops (Gregory Sanders)f17d7de52Add dynafed helper functions (Gregory Sanders)4aa5f991fRefactor block_proof to support dynafed (Gregory Sanders)4e52f2c83Add dynamic federation blockheader fields to chaindb serialization (Gregory Sanders)a74accf7dAdd dynamic federations blockheader serialization with HF bit (Gregory Sanders)2845e7811Define dynamic federations primitives (Gregory Sanders) Pull request description: This is a proposal implementation of something myself and Andrew have been working on, to enable dynamic membership in the blocksigning set, fedpeg signing set, and under the same coordination mechanism, the PAK enforcement. At a really high level: 1) If 4/5 of last N(what we call an epoch length) blocks signal desire for a change in the parameters of the system, they are replaced with the proposed. These changes can be proposed/driven by `getnewblockhex`. 2) Once dynamic federations is active(versionbits deployment), signblockscripts can only be native segwit scripts, in other words, must be a version byte followed by the witness program. The blockheader now has a witness stack as well. 3) The fedpegscript of last N epochs are both allowed as a grace period for users putting money into the system. 4) PAK enforcement has been upgraded to consensus-enforcement once dynamic federations activates A design document is forthcoming. Tree-SHA512: 26c6e05c85adb77345d8d481f20e8095bc2f9e5ad7b9a8824008a056637af7c6e87e522b038a6c6c108889d60b2fd1d6ecbd3e0afe2b696579000d18b0f1ecad
This commit is contained in:
commit
5b2fe5563b
49 changed files with 1903 additions and 805 deletions
|
|
@ -134,6 +134,7 @@ BITCOIN_CORE_H = \
|
|||
core_io.h \
|
||||
core_memusage.h \
|
||||
cuckoocache.h \
|
||||
dynafed.h \
|
||||
fs.h \
|
||||
httprpc.h \
|
||||
httpserver.h \
|
||||
|
|
@ -401,10 +402,13 @@ libbitcoin_consensus_a_SOURCES = \
|
|||
arith_uint256.cpp \
|
||||
arith_uint256.h \
|
||||
asset.cpp \
|
||||
chain.h \
|
||||
chain.cpp \
|
||||
consensus/merkle.cpp \
|
||||
consensus/merkle.h \
|
||||
consensus/params.h \
|
||||
consensus/validation.h \
|
||||
dynafed.cpp \
|
||||
hash.cpp \
|
||||
hash.h \
|
||||
prevector.h \
|
||||
|
|
|
|||
|
|
@ -19,21 +19,18 @@ bool CheckChallenge(const CBlockHeader& block, const CBlockIndex& indexLast, con
|
|||
}
|
||||
}
|
||||
|
||||
void ResetChallenge(CBlockHeader& block, const CBlockIndex& indexLast, const Consensus::Params& params)
|
||||
static bool CheckProofGeneric(const CBlockHeader& block, const uint32_t max_block_signature_size, const CScript& challenge, const CScript& scriptSig, const CScriptWitness& witness)
|
||||
{
|
||||
block.proof.challenge = indexLast.proof.challenge;
|
||||
}
|
||||
|
||||
static bool CheckProofGeneric(const CBlockHeader& block, const Consensus::Params& params, const CScript& challenge)
|
||||
{
|
||||
if (block.GetHash() == params.hashGenesisBlock)
|
||||
return true;
|
||||
|
||||
if (block.proof.solution.size() > params.max_block_signature_size) {
|
||||
// scriptSig or witness will be nonempty, but not both, so just compare both limits
|
||||
if (scriptSig.size() > max_block_signature_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some anti-DoS flags, though consensus.max_block_signature_size caps the possible
|
||||
if (witness.GetSerializedSize() > max_block_signature_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some anti-DoS flags, though max_block_signature_size caps the possible
|
||||
// danger in malleation of the block witness data.
|
||||
unsigned int proof_flags = SCRIPT_VERIFY_P2SH // For cleanstack evalution under segwit flag
|
||||
| SCRIPT_VERIFY_STRICTENC // Minimally-sized DER sigs
|
||||
|
|
@ -42,15 +39,20 @@ static bool CheckProofGeneric(const CBlockHeader& block, const Consensus::Params
|
|||
| SCRIPT_VERIFY_MINIMALDATA // Pushes are minimally-sized
|
||||
| SCRIPT_VERIFY_SIGPUSHONLY // Witness is push-only
|
||||
| SCRIPT_VERIFY_LOW_S // Stop easiest signature fiddling
|
||||
| SCRIPT_VERIFY_WITNESS // Required for cleanstack eval in VerifyScript
|
||||
| SCRIPT_VERIFY_WITNESS // Witness and to enforce cleanstack
|
||||
| SCRIPT_NO_SIGHASH_BYTE; // non-Check(Multi)Sig signatures will not have sighash byte
|
||||
return GenericVerifyScript(block.proof.solution, challenge, proof_flags, block);
|
||||
return GenericVerifyScript(scriptSig, witness, challenge, proof_flags, block);
|
||||
}
|
||||
|
||||
bool CheckProof(const CBlockHeader& block, const Consensus::Params& params)
|
||||
{
|
||||
if (g_signed_blocks) {
|
||||
return CheckProofGeneric(block, params, params.signblockscript);
|
||||
const DynaFedParams& dynafed_params = block.m_dynafed_params;
|
||||
if (dynafed_params.IsNull()) {
|
||||
return CheckProofGeneric(block, params.max_block_signature_size, params.signblockscript, block.proof.solution, CScriptWitness());
|
||||
} else {
|
||||
return CheckProofGeneric(block, dynafed_params.m_current.m_signblock_witness_limit, dynafed_params.m_current.m_signblockscript, CScript(), block.m_signblock_witness);
|
||||
}
|
||||
} else {
|
||||
return CheckProofOfWork(block.GetHash(), block.nBits, params);
|
||||
}
|
||||
|
|
@ -58,10 +60,15 @@ bool CheckProof(const CBlockHeader& block, const Consensus::Params& params)
|
|||
|
||||
bool CheckProofSignedParent(const CBlockHeader& block, const Consensus::Params& params)
|
||||
{
|
||||
return CheckProofGeneric(block, params, params.parent_chain_signblockscript);
|
||||
}
|
||||
|
||||
void ResetProof(CBlockHeader& block)
|
||||
{
|
||||
block.proof.solution.clear();
|
||||
const DynaFedParams& dynafed_params = block.m_dynafed_params;
|
||||
if (dynafed_params.IsNull()) {
|
||||
return CheckProofGeneric(block, params.max_block_signature_size, params.parent_chain_signblockscript, block.proof.solution, CScriptWitness());
|
||||
} else {
|
||||
// Dynamic federations means we cannot validate the signer set
|
||||
// at least without tracking the parent chain more directly.
|
||||
// Note that we do not even serialize dynamic federation block witness data
|
||||
// currently for merkle proofs which is the only context in which
|
||||
// this function is currently used.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ class CScript;
|
|||
/** Check on header proof, depending on chain type, PoW or signed **/
|
||||
bool CheckProof(const CBlockHeader& block, const Consensus::Params&);
|
||||
bool CheckProofSignedParent(const CBlockHeader& block, const Consensus::Params&);
|
||||
void ResetProof(CBlockHeader& block);
|
||||
bool CheckChallenge(const CBlockHeader& block, const CBlockIndex& indexLast, const Consensus::Params&);
|
||||
void ResetChallenge(CBlockHeader& block, const CBlockIndex& indexLast, const Consensus::Params&);
|
||||
|
||||
#endif // BITCOIN_BLOCK_PROOF_H
|
||||
|
|
|
|||
36
src/chain.h
36
src/chain.h
|
|
@ -221,6 +221,9 @@ public:
|
|||
uint32_t nBits;
|
||||
uint32_t nNonce;
|
||||
CProof proof;
|
||||
// Dynamic federation fields
|
||||
DynaFedParams dynafed_params;
|
||||
CScriptWitness m_signblock_witness;
|
||||
|
||||
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
|
||||
int32_t nSequenceId;
|
||||
|
|
@ -250,6 +253,8 @@ public:
|
|||
nBits = 0;
|
||||
nNonce = 0;
|
||||
proof.SetNull();
|
||||
dynafed_params.SetNull();
|
||||
m_signblock_witness.SetNull();
|
||||
}
|
||||
|
||||
CBlockIndex()
|
||||
|
|
@ -267,6 +272,8 @@ public:
|
|||
nBits = block.nBits;
|
||||
nNonce = block.nNonce;
|
||||
proof = block.proof;
|
||||
dynafed_params = block.m_dynafed_params;
|
||||
m_signblock_witness = block.m_signblock_witness;
|
||||
}
|
||||
|
||||
CDiskBlockPos GetBlockPos() const {
|
||||
|
|
@ -301,6 +308,8 @@ public:
|
|||
block.nBits = nBits;
|
||||
block.nNonce = nNonce;
|
||||
block.proof = proof;
|
||||
block.m_dynafed_params = dynafed_params;
|
||||
block.m_signblock_witness = m_signblock_witness;
|
||||
return block;
|
||||
}
|
||||
|
||||
|
|
@ -423,13 +432,35 @@ public:
|
|||
READWRITE(VARINT(nUndoPos));
|
||||
|
||||
// block header
|
||||
READWRITE(this->nVersion);
|
||||
|
||||
// Detect dynamic federation block serialization using "HF bit",
|
||||
// or the signed bit which is invalid in Bitcoin
|
||||
bool is_dyna = false;
|
||||
int32_t nVersion;
|
||||
if (ser_action.ForRead()) {
|
||||
READWRITE(nVersion);
|
||||
is_dyna = nVersion < 0;
|
||||
this->nVersion = ~CBlockHeader::DYNAFED_HF_MASK & nVersion;
|
||||
} else {
|
||||
nVersion = this->nVersion;
|
||||
if (!dynafed_params.IsNull()) {
|
||||
nVersion |= CBlockHeader::DYNAFED_HF_MASK;
|
||||
is_dyna = true;
|
||||
}
|
||||
READWRITE(nVersion);
|
||||
}
|
||||
|
||||
READWRITE(hashPrev);
|
||||
READWRITE(hashMerkleRoot);
|
||||
READWRITE(nTime);
|
||||
// For compatibility with elements 0.14 based chains
|
||||
if (g_signed_blocks) {
|
||||
READWRITE(proof);
|
||||
if (is_dyna) {
|
||||
READWRITE(dynafed_params);
|
||||
READWRITE(m_signblock_witness.stack);
|
||||
} else {
|
||||
READWRITE(proof);
|
||||
}
|
||||
} else {
|
||||
READWRITE(nBits);
|
||||
READWRITE(nNonce);
|
||||
|
|
@ -449,6 +480,7 @@ public:
|
|||
block.nBits = nBits;
|
||||
block.nNonce = nNonce;
|
||||
block.proof = proof;
|
||||
block.m_dynafed_params = dynafed_params;
|
||||
return block.GetHash();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ public:
|
|||
g_signed_blocks = false;
|
||||
g_con_elementsmode = false;
|
||||
g_con_blockheightinheader = false;
|
||||
consensus.total_valid_epochs = 0;
|
||||
|
||||
/**
|
||||
* The message start string is designed to be unlikely to occur in normal data.
|
||||
|
|
@ -263,6 +264,7 @@ public:
|
|||
g_signed_blocks = false;
|
||||
g_con_elementsmode = false;
|
||||
g_con_blockheightinheader = false;
|
||||
consensus.total_valid_epochs = 0;
|
||||
|
||||
pchMessageStart[0] = 0x0b;
|
||||
pchMessageStart[1] = 0x11;
|
||||
|
|
@ -366,6 +368,7 @@ public:
|
|||
g_signed_blocks = false;
|
||||
g_con_elementsmode = false;
|
||||
g_con_blockheightinheader = false;
|
||||
consensus.total_valid_epochs = 0;
|
||||
|
||||
pchMessageStart[0] = 0xfa;
|
||||
pchMessageStart[1] = 0xbf;
|
||||
|
|
@ -482,6 +485,11 @@ class CCustomParams : public CRegTestParams {
|
|||
|
||||
consensus.nMinimumChainWork = uint256S(args.GetArg("-con_nminimumchainwork", "0x0"));
|
||||
consensus.defaultAssumeValid = uint256S(args.GetArg("-con_defaultassumevalid", "0x00"));
|
||||
// TODO: Embed in genesis block in nTime field with new genesis block type
|
||||
consensus.dynamic_epoch_length = args.GetArg("-dynamic_epoch_length", 10);
|
||||
// TODO: pass in serialized vector of byte vectors, parse into extension space
|
||||
// Junk keys for testing
|
||||
consensus.first_extension_space = {ParseHex("02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f")};
|
||||
|
||||
nPruneAfterHeight = (uint64_t)args.GetArg("-npruneafterheight", nPruneAfterHeight);
|
||||
fDefaultConsistencyChecks = args.GetBoolArg("-fdefaultconsistencychecks", fDefaultConsistencyChecks);
|
||||
|
|
@ -558,7 +566,7 @@ class CCustomParams : public CRegTestParams {
|
|||
enforce_pak = args.GetBoolArg("-enforce_pak", false);
|
||||
|
||||
// Allow multiple op_return outputs by relay policy
|
||||
multi_data_permitted = args.GetBoolArg("-multi_data_permitted", true);
|
||||
multi_data_permitted = args.GetBoolArg("-multi_data_permitted", enforce_pak);
|
||||
|
||||
// bitcoin regtest is the parent chain by default
|
||||
parentGenesisBlockHash = uint256S(args.GetArg("-parentgenesisblockhash", "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"));
|
||||
|
|
@ -577,6 +585,8 @@ class CCustomParams : public CRegTestParams {
|
|||
uint256 entropy;
|
||||
GenerateAssetEntropy(entropy, COutPoint(uint256(commit), 0), parentGenesisBlockHash);
|
||||
|
||||
consensus.total_valid_epochs = args.GetArg("-total_valid_epochs", 2);
|
||||
|
||||
// Elements serialization uses derivation, bitcoin serialization uses 0x00
|
||||
if (g_con_elementsmode) {
|
||||
CalculateAsset(consensus.pegged_asset, entropy);
|
||||
|
|
@ -600,6 +610,10 @@ class CCustomParams : public CRegTestParams {
|
|||
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = args.GetArg("-con_csv_deploy_start", Consensus::BIP9Deployment::ALWAYS_ACTIVE);
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
|
||||
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_DYNA_FED].bit = 25;
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_DYNA_FED].nStartTime = args.GetArg("-con_dyna_deploy_start", Consensus::BIP9Deployment::ALWAYS_ACTIVE);
|
||||
consensus.vDeployments[Consensus::DEPLOYMENT_DYNA_FED].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
|
||||
|
||||
}
|
||||
|
||||
void SetGenesisBlock() {
|
||||
|
|
@ -613,6 +627,11 @@ class CCustomParams : public CRegTestParams {
|
|||
if (initialFreeCoins != 0 || initial_reissuance_tokens != 0) {
|
||||
AppendInitialIssuance(genesis, COutPoint(uint256(commit), 0), parentGenesisBlockHash, (initialFreeCoins > 0) ? 1 : 0, initialFreeCoins, (initial_reissuance_tokens > 0) ? 1 : 0, initial_reissuance_tokens, CScript() << OP_TRUE);
|
||||
}
|
||||
} else if (consensus.genesis_style == "dynamic") {
|
||||
// Liquid v2 HF, from genesis. Upgrading networks still use "elements".
|
||||
// TODO fill out genesis block with special commitments including epoch
|
||||
// length in nTime
|
||||
throw std::runtime_error(strprintf("Invalid -genesis_style (%s)", consensus.genesis_style));
|
||||
} else {
|
||||
throw std::runtime_error(strprintf("Invalid -genesis_style (%s)", consensus.genesis_style));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,11 +42,13 @@ void SetupChainParamsBaseOptions()
|
|||
gArgs.AddArg("-con_parentpowlimit", "The proof-of-work limit value for the parent chain.", false, OptionsCategory::CHAINPARAMS);
|
||||
gArgs.AddArg("-con_parent_chain_signblockscript", "Whether parent chain uses pow or signed blocks. If the parent chain uses signed blocks, the challenge (scriptPubKey) script. If not, an empty string. (default: empty script [ie parent uses pow])", false, OptionsCategory::CHAINPARAMS);
|
||||
|
||||
gArgs.AddArg("-fedpegscript", "The script for the federated peg.", false, OptionsCategory::CHAINPARAMS);
|
||||
gArgs.AddArg("-enforce_pak", "Causes standardness checks to enforce Pegout Authorization Key(PAK) validation, and miner to include PAK commitments when configured. Can not be set when acceptnonstdtx is set to true.", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-multi_data_permitted", "Allow relay of multiple OP_RETURN outputs. (default: true)", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-pak", "Entries in the PAK list. Order of entries matter.", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-fedpegscript", "The script for the federated peg enforce from genesis block. This script may stop being enforced once dynamic federations activates.", false, OptionsCategory::CHAINPARAMS);
|
||||
gArgs.AddArg("-enforce_pak", "Causes standardness checks to enforce Pegout Authorization Key(PAK) validation before dynamic federations, and consensus enforcement after.", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-multi_data_permitted", "Allow relay of multiple OP_RETURN outputs. (default: -enforce_pak)", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-con_csv_deploy_start", "Starting height for CSV deployment. (default: -1, which means ACTIVE from genesis)", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-con_dyna_deploy_start", "Starting height for Dynamic Federations deployment. Once active, signblockscript becomes a BIP141 WSH scriptPubKey of the original signblockscript. All other dynamic parameters stay constant.(default: -1, which means ACTIVE from genesis)", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-dynamic_epoch_length", "Per-chain parameter that sets how many blocks dynamic federation voting and enforcement are in effect for.", false, OptionsCategory::ELEMENTS);
|
||||
gArgs.AddArg("-total_valid_epochs", "Per-chain parameter that sets how long a particular fedpegscript is in effect for.", false, OptionsCategory::ELEMENTS);
|
||||
// END ELEMENTS
|
||||
//
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ enum DeploymentPos
|
|||
DEPLOYMENT_TESTDUMMY,
|
||||
DEPLOYMENT_CSV, // Deployment of BIP68, BIP112, and BIP113.
|
||||
DEPLOYMENT_SEGWIT, // Deployment of BIP141, BIP143, and BIP147.
|
||||
DEPLOYMENT_DYNA_FED, // Deployment of dynamic federation
|
||||
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in versionbits.cpp
|
||||
MAX_VERSION_BITS_DEPLOYMENTS
|
||||
};
|
||||
|
|
@ -99,6 +100,13 @@ struct Params {
|
|||
CScript signblockscript;
|
||||
uint32_t max_block_signature_size;
|
||||
// g_signed_blocks - Whether blocks are signed or not, get around circular dep
|
||||
// Set positive to avoid division by 0
|
||||
// for non-dynafed chains and unit tests
|
||||
uint32_t dynamic_epoch_length = std::numeric_limits<uint32_t>::max();
|
||||
// Used to seed the extension space for first dynamic blocks
|
||||
std::vector<std::vector<unsigned char>> first_extension_space;
|
||||
// Used to allow M-epoch-old peg-in addresses as deposits
|
||||
size_t total_valid_epochs;
|
||||
};
|
||||
} // namespace Consensus
|
||||
|
||||
|
|
|
|||
|
|
@ -157,24 +157,28 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
|
|||
nSigOps += GetP2SHSigOpCount(tx, inputs) * WITNESS_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
// Note that we only count segwit sigops for peg-in inputs
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
||||
{
|
||||
CTxOut prevout;
|
||||
CScript scriptPubKey;
|
||||
if (tx.vin[i].m_is_pegin) {
|
||||
std::string err;
|
||||
// Make sure witness exists and is properly formatted
|
||||
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, tx.vin[i].prevout, err, false)) {
|
||||
// Make sure witness exists and has enough peg-in witness fields for
|
||||
// the claim_script
|
||||
if (tx.witness.vtxinwit.size() != tx.vin.size() ||
|
||||
tx.witness.vtxinwit[i].m_pegin_witness.stack.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
prevout = GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness);
|
||||
const auto pegin_witness = tx.witness.vtxinwit[i].m_pegin_witness;
|
||||
scriptPubKey = CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end());
|
||||
} else {
|
||||
const Coin& coin = inputs.AccessCoin(tx.vin[i].prevout);
|
||||
assert(!coin.IsSpent());
|
||||
prevout = coin.out;
|
||||
scriptPubKey = coin.out.scriptPubKey;
|
||||
}
|
||||
|
||||
const CScriptWitness* pScriptWitness = tx.witness.vtxinwit.size() > i ? &tx.witness.vtxinwit[i].scriptWitness : NULL;
|
||||
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, pScriptWitness, flags);
|
||||
nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, scriptPubKey, pScriptWitness, flags);
|
||||
}
|
||||
return nSigOps;
|
||||
}
|
||||
|
|
@ -239,7 +243,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
|
|||
}
|
||||
|
||||
namespace Consensus {
|
||||
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks)
|
||||
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
|
||||
{
|
||||
// are the actual inputs available?
|
||||
if (!inputs.HaveInputs(tx)) {
|
||||
|
|
@ -254,7 +258,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
|
|||
if (tx.vin[i].m_is_pegin) {
|
||||
// Check existence and validity of pegin witness
|
||||
std::string err;
|
||||
if (tx.witness.vtxinwit.size() <= i || !IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, prevout, err, true)) {
|
||||
if (tx.witness.vtxinwit.size() <= i || !IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, fedpegscripts, prevout, err, true)) {
|
||||
return state.DoS(0, false, REJECT_PEGIN, "bad-pegin-witness", false, err);
|
||||
}
|
||||
std::pair<uint256, COutPoint> pegin = std::make_pair(uint256(tx.witness.vtxinwit[i].m_pegin_witness.stack[2]), prevout);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace Consensus {
|
|||
* @param[out] fee_map Set to the transaction fee if successful.
|
||||
* Preconditions: tx.IsCoinBase() is false.
|
||||
*/
|
||||
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks);
|
||||
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks, const std::vector<std::pair<CScript, CScript>>& fedpegscripts);
|
||||
} // namespace Consensus
|
||||
|
||||
/** Auxiliary functions for transaction validation (ideally should not be exposed) */
|
||||
|
|
|
|||
102
src/dynafed.cpp
Normal file
102
src/dynafed.cpp
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
|
||||
#include <dynafed.h>
|
||||
#include <hash.h>
|
||||
|
||||
bool NextBlockIsParameterTransition(const CBlockIndex* pindexPrev, const Consensus::Params& consensus, DynaFedParamEntry& winning_entry)
|
||||
{
|
||||
uint32_t next_height = pindexPrev->nHeight + 1;
|
||||
assert(consensus.dynamic_epoch_length != 0);
|
||||
if (next_height % consensus.dynamic_epoch_length != 0) {
|
||||
winning_entry.SetNull();
|
||||
return false;
|
||||
}
|
||||
std::map<uint256, uint32_t> vote_tally;
|
||||
assert(next_height >= consensus.dynamic_epoch_length);
|
||||
for (int32_t height = next_height - 1; height >= (int32_t)(next_height - consensus.dynamic_epoch_length); --height) {
|
||||
const CBlockIndex* p_epoch_walk = pindexPrev->GetAncestor(height);
|
||||
assert(p_epoch_walk);
|
||||
const DynaFedParamEntry& proposal = p_epoch_walk->dynafed_params.m_proposed;
|
||||
const uint256 proposal_root = proposal.CalculateRoot();
|
||||
vote_tally[proposal_root]++;
|
||||
// Short-circuit once 4/5 threshhold is reached
|
||||
if (!proposal_root.IsNull() && vote_tally[proposal_root] >=
|
||||
(consensus.dynamic_epoch_length*4)/5) {
|
||||
winning_entry = proposal;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
winning_entry.SetNull();
|
||||
return false;
|
||||
}
|
||||
|
||||
DynaFedParamEntry ComputeNextBlockFullCurrentParameters(const CBlockIndex* pindexPrev, const Consensus::Params& consensus)
|
||||
{
|
||||
assert(pindexPrev);
|
||||
|
||||
uint32_t next_height = pindexPrev->nHeight+1;
|
||||
const uint32_t epoch_length = consensus.dynamic_epoch_length;
|
||||
uint32_t epoch_age = next_height % epoch_length;
|
||||
|
||||
DynaFedParamEntry winning_proposal;
|
||||
// Early return when there is a winning proposal
|
||||
if (NextBlockIsParameterTransition(pindexPrev, consensus, winning_proposal)) {
|
||||
assert(epoch_age == 0);
|
||||
return winning_proposal;
|
||||
}
|
||||
|
||||
// Since no transition took place, find most recent epoch start
|
||||
|
||||
// If next block is start of new epoch, walk backwards one epoch
|
||||
uint32_t epoch_start_height = next_height - epoch_age;
|
||||
if (epoch_age == 0) {
|
||||
epoch_start_height -= epoch_length;
|
||||
}
|
||||
|
||||
// We need to put in place the previous epoch's current which
|
||||
// may be pre-dynafed params
|
||||
const CBlockIndex* p_epoch_start = pindexPrev->GetAncestor(epoch_start_height);
|
||||
assert(p_epoch_start);
|
||||
if (p_epoch_start->dynafed_params.IsNull()) {
|
||||
// We need to construct the "full" current parameters of pre-dynafed
|
||||
// consensus
|
||||
|
||||
// Convert signblockscript to P2WSH
|
||||
uint256 signblock_witness_program;
|
||||
CSHA256().Write(p_epoch_start->proof.challenge.data(), p_epoch_start->proof.challenge.size()).Finalize(signblock_witness_program.begin());
|
||||
CScript p2wsh_signblock_script = CScript() << OP_0 << ToByteVector(signblock_witness_program);
|
||||
|
||||
// Make P2SH-P2WSH-ness of non-dynafed fedpegscript explicit
|
||||
uint256 fedpegscript_redeemscript;
|
||||
CSHA256().Write(consensus.fedpegScript.data(), consensus.fedpegScript.size()).Finalize(fedpegscript_redeemscript.begin());
|
||||
CScript fedpeg_p2sw = CScript() << OP_0 << ToByteVector(fedpegscript_redeemscript);
|
||||
uint160 fedpeg_p2sh(Hash160(fedpeg_p2sw.begin(), fedpeg_p2sw.end()));
|
||||
CScript sh_wsh_fedpeg_program = CScript() << OP_HASH160 << ToByteVector(fedpeg_p2sh) << OP_EQUAL;
|
||||
|
||||
// Put them in winning proposal
|
||||
winning_proposal = DynaFedParamEntry(p2wsh_signblock_script, consensus.max_block_signature_size+consensus.signblockscript.size(), sh_wsh_fedpeg_program, consensus.fedpegScript, consensus.first_extension_space);
|
||||
} else {
|
||||
winning_proposal = p_epoch_start->dynafed_params.m_current;
|
||||
}
|
||||
return winning_proposal;
|
||||
}
|
||||
|
||||
// TODO cache this in CBlockIndex itself?
|
||||
DynaFedParamEntry ComputeNextBlockCurrentParameters(const CBlockIndex* pindexPrev, const Consensus::Params& consensus)
|
||||
{
|
||||
assert(pindexPrev);
|
||||
|
||||
DynaFedParamEntry entry = ComputeNextBlockFullCurrentParameters(pindexPrev, consensus);
|
||||
|
||||
uint32_t next_height = pindexPrev->nHeight+1;
|
||||
const uint32_t epoch_length = consensus.dynamic_epoch_length;
|
||||
uint32_t epoch_age = next_height % epoch_length;
|
||||
|
||||
// Return appropriate format based on epoch age
|
||||
if (epoch_age > 0) {
|
||||
// TODO implement "prune" function to remove fields in place and change serialize type
|
||||
return DynaFedParamEntry(entry.m_signblockscript, entry.m_signblock_witness_limit);
|
||||
} else {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
20
src/dynafed.h
Normal file
20
src/dynafed.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
#ifndef BITCOIN_DYNAFED_H
|
||||
#define BITCOIN_DYNAFED_H
|
||||
|
||||
#include <chain.h>
|
||||
#include <chainparams.h>
|
||||
#include <primitives/block.h>
|
||||
|
||||
/* Returns true if the next block would be the first block of an epoch with new
|
||||
* parameters. It also returns the parameter set that is being transitioned to. */
|
||||
bool NextBlockIsParameterTransition(const CBlockIndex* pindexPrev, const Consensus::Params& consensus, DynaFedParamEntry& winning_entry);
|
||||
|
||||
/* Compute the next block's enforced parameters */
|
||||
DynaFedParamEntry ComputeNextBlockFullCurrentParameters(const CBlockIndex* pindexPrev, const Consensus::Params& consensus);
|
||||
/* Compute the next block's expected published parameters. Blocks at "epoch_age" of non-0 only
|
||||
* publish signblockscript-related fields */
|
||||
DynaFedParamEntry ComputeNextBlockCurrentParameters(const CBlockIndex* pindexPrev, const Consensus::Params& consensus);
|
||||
|
||||
|
||||
#endif // BITCOIN_DYNAFED_H
|
||||
84
src/init.cpp
84
src/init.cpp
|
|
@ -67,7 +67,6 @@
|
|||
#include <boost/thread.hpp>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#include <primitives/pak.h> // CPAKList
|
||||
#include <assetsdir.h> // InitGlobalAssetDir
|
||||
#include <pegins.h>
|
||||
|
||||
|
|
@ -1870,86 +1869,15 @@ bool AppInitMain(InitInterfaces& interfaces)
|
|||
return false;
|
||||
}
|
||||
|
||||
// ********************************************************* Step 13: Load PAK List
|
||||
|
||||
// First, make sure -enforce_pak and -acceptnonstdtxn aren't conflicting
|
||||
if (Params().GetEnforcePak() && gArgs.GetBoolArg("-acceptnonstdtxn", !Params().RequireStandard())) {
|
||||
return InitError("-enforce_pak can not be true if the network accepts non-standard transactions for relay.");
|
||||
}
|
||||
|
||||
//Entire list of PAK entries from conf must be of valid format
|
||||
std::vector<std::string> pak_list_str = gArgs.GetArgs("-pak");
|
||||
bool valid_paklist = true;
|
||||
bool is_reject = false;
|
||||
std::vector<std::vector<unsigned char> > offline_keys;
|
||||
std::vector<std::vector<unsigned char> > online_keys;
|
||||
for (unsigned int i = 0; i < pak_list_str.size(); i++) {
|
||||
if (pak_list_str[i] == "reject") {
|
||||
is_reject = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t colon_index = pak_list_str[i].find(":");
|
||||
if (colon_index == std::string::npos) {
|
||||
valid_paklist = false;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string offline = pak_list_str[i].substr(0, colon_index);
|
||||
std::string online = pak_list_str[i].substr(colon_index + 1);
|
||||
|
||||
if (!IsHex(offline) || !IsHex(online) || offline.size() != 66 || online.size() != 66) {
|
||||
valid_paklist = false;
|
||||
break;
|
||||
}
|
||||
|
||||
online_keys.push_back(ParseHex(online));
|
||||
offline_keys.push_back(ParseHex(offline));
|
||||
}
|
||||
// pak=reject must be alone
|
||||
if (is_reject && offline_keys.size() > 0)
|
||||
valid_paklist = false;
|
||||
if (!valid_paklist)
|
||||
return InitError(_("ERROR: Invalid PAK entries given in conf file."));
|
||||
if (is_reject || offline_keys.size() > 0) {
|
||||
CPAKList paklist;
|
||||
if(CPAKList::FromBytes(paklist, offline_keys, online_keys, is_reject)) {
|
||||
g_paklist_config = paklist;
|
||||
} else {
|
||||
return InitError(_("ERROR: Invalid PAK entries given in conf file."));
|
||||
}
|
||||
} else {
|
||||
g_paklist_config = boost::none;
|
||||
}
|
||||
|
||||
// Read and parse committed pak list from disk
|
||||
CPAKList paklist;
|
||||
offline_keys.resize(0);
|
||||
online_keys.resize(0);
|
||||
bool reject;
|
||||
if (pblocktree->ReadPAKList(offline_keys, online_keys, reject)) {
|
||||
if (CPAKList::FromBytes(paklist, offline_keys, online_keys, reject)) {
|
||||
g_paklist_blockchain = paklist;
|
||||
} else {
|
||||
return InitError(_("ERROR: Read invalid PAK list."));
|
||||
// ********************************************************* Step 13: Check PAK
|
||||
if (chainparams.GetEnforcePak()) {
|
||||
if (!chainparams.GetConsensus().first_extension_space.empty() &&
|
||||
CreatePAKListFromExtensionSpace(chainparams.GetConsensus().first_extension_space).IsReject()) {
|
||||
return InitError("PAK is being enforced but initial extension space has invalid entries.");
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 14: Check fedpeg
|
||||
// ELEMENTS:
|
||||
if (chainparams.GetConsensus().has_parent_chain) {
|
||||
// Will assert if not properly formatted
|
||||
const CScript& fedpeg_script = chainparams.GetConsensus().fedpegScript;
|
||||
unsigned int dummy_required;
|
||||
std::vector<std::vector<unsigned char>> dummy_keys;
|
||||
if (!MatchLiquidWatchman(fedpeg_script) &&
|
||||
fedpeg_script != CScript() << OP_TRUE &&
|
||||
!MatchMultisig(fedpeg_script, dummy_required, dummy_keys)) {
|
||||
return InitError(_("ERROR: Fedpegscript is not one of the accepted templates: OP_TRUE, CHECKMULTISIG, and Liquidv1"));
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 15: finished
|
||||
// ********************************************************* Step 14: finished
|
||||
|
||||
SetRPCWarmupFinished();
|
||||
|
||||
|
|
|
|||
|
|
@ -25,13 +25,22 @@
|
|||
#include <util/system.h>
|
||||
#include <validationinterface.h>
|
||||
|
||||
// ELEMENTS
|
||||
#include <block_proof.h> // ResetProof, ResetChallenge
|
||||
#include <dynafed.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
void ResetChallenge(CBlockHeader& block, const CBlockIndex& indexLast, const Consensus::Params& params)
|
||||
{
|
||||
block.proof.challenge = indexLast.proof.challenge;
|
||||
}
|
||||
|
||||
void ResetProof(CBlockHeader& block)
|
||||
{
|
||||
block.proof.solution.clear();
|
||||
}
|
||||
|
||||
int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev)
|
||||
{
|
||||
int64_t nOldTime = pblock->nTime;
|
||||
|
|
@ -93,7 +102,7 @@ void BlockAssembler::resetBlock()
|
|||
Optional<int64_t> BlockAssembler::m_last_block_num_txs{nullopt};
|
||||
Optional<int64_t> BlockAssembler::m_last_block_weight{nullopt};
|
||||
|
||||
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, int min_tx_age)
|
||||
std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, int min_tx_age, DynaFedParamEntry* proposed_entry)
|
||||
{
|
||||
assert(min_tx_age >= 0);
|
||||
int64_t nTimeStart = GetTimeMicros();
|
||||
|
|
@ -106,22 +115,6 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
|
|||
return nullptr;
|
||||
pblock = &pblocktemplate->block; // pointer for convenience
|
||||
|
||||
std::vector<CScript> commitments;
|
||||
|
||||
// ELEMENTS: PAK
|
||||
// Create block pak commitment if set in conf file and validating pegouts
|
||||
if (Params().GetEnforcePak() && g_paklist_config) {
|
||||
if (*g_paklist_config != g_paklist_blockchain) {
|
||||
g_paklist_config->CreateCommitments(commitments);
|
||||
}
|
||||
}
|
||||
|
||||
// Pad block weight to account for OP_RETURN commitments with two compressed pubkeys
|
||||
for (const auto& commitment : commitments) {
|
||||
CTxOut output(CAsset(), 0, commitment);
|
||||
nBlockWeight += ::GetSerializeSize(output, PROTOCOL_VERSION)*WITNESS_SCALE_FACTOR;
|
||||
}
|
||||
// END PAK
|
||||
|
||||
// Add dummy coinbase tx as first transaction
|
||||
pblock->vtx.emplace_back();
|
||||
|
|
@ -157,11 +150,19 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
|
|||
// transaction (which in most cases can be a no-op).
|
||||
fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus());
|
||||
|
||||
if (g_signed_blocks) {
|
||||
if (IsDynaFedEnabled(pindexPrev, chainparams.GetConsensus())) {
|
||||
DynaFedParamEntry current_params = ComputeNextBlockCurrentParameters(chainActive.Tip(), chainparams.GetConsensus());
|
||||
DynaFedParams block_params(current_params, proposed_entry ? *proposed_entry : DynaFedParamEntry());
|
||||
pblock->m_dynafed_params = block_params;
|
||||
nBlockWeight += ::GetSerializeSize(block_params, PROTOCOL_VERSION)*WITNESS_SCALE_FACTOR;
|
||||
nBlockWeight += current_params.m_signblock_witness_limit; // Note witness discount
|
||||
assert(pblock->proof.IsNull());
|
||||
|
||||
} else if (g_signed_blocks) {
|
||||
// Old style signed blocks
|
||||
// Pad block weight by block proof fields (including upper-bound of signature)
|
||||
nBlockWeight += chainparams.GetConsensus().signblockscript.size() * WITNESS_SCALE_FACTOR;
|
||||
nBlockWeight += chainparams.GetConsensus().max_block_signature_size * WITNESS_SCALE_FACTOR;
|
||||
// Reset block proof
|
||||
ResetProof(*pblock);
|
||||
ResetChallenge(*pblock, *pindexPrev, chainparams.GetConsensus());
|
||||
}
|
||||
|
|
@ -194,12 +195,6 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
|
|||
}
|
||||
}
|
||||
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
|
||||
// ELEMENTS: PAK
|
||||
// Add PAK transition commitments
|
||||
for (unsigned int i = 0; i < commitments.size(); i++) {
|
||||
coinbaseTx.vout.push_back(CTxOut(CAsset(), 0, commitments[i]));
|
||||
}
|
||||
// END PAK
|
||||
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
|
||||
pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
|
||||
pblocktemplate->vTxFees[0] = -nFees;
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ public:
|
|||
BlockAssembler(const CChainParams& params, const Options& options);
|
||||
|
||||
/** Construct a new block template with coinbase to scriptPubKeyIn. min_tx_age is in seconds */
|
||||
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn, int min_tx_age=0);
|
||||
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn, int min_tx_age=0, DynaFedParamEntry* = nullptr);
|
||||
|
||||
static Optional<int64_t> m_last_block_num_txs;
|
||||
static Optional<int64_t> m_last_block_weight;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <script/standard.h>
|
||||
#include <streams.h>
|
||||
#include <util/system.h>
|
||||
#include <dynafed.h>
|
||||
|
||||
//
|
||||
// ELEMENTS
|
||||
|
|
@ -70,16 +71,8 @@ bool GetAmountFromParentChainPegin(CAmount& amount, const CTransaction& txBTC, u
|
|||
// Takes federation redeem script and adds HMAC_SHA256(pubkey, scriptPubKey) as a tweak to each pubkey
|
||||
CScript calculate_contract(const CScript& federation_script, const CScript& scriptPubKey) {
|
||||
CScript scriptDestination;
|
||||
std::vector<std::vector<unsigned char> > solutions;
|
||||
unsigned int required;
|
||||
std::vector<std::vector<unsigned char>> keys;
|
||||
|
||||
bool is_liquidv1_watchman = MatchLiquidWatchman(federation_script);
|
||||
// Sanity check federation_script only to match 3 templates
|
||||
if (!is_liquidv1_watchman &&
|
||||
federation_script != CScript() << OP_TRUE &&
|
||||
!MatchMultisig(federation_script, required, keys)) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
CScript::const_iterator sdpc = federation_script.begin();
|
||||
std::vector<unsigned char> vch;
|
||||
|
|
@ -87,7 +80,8 @@ CScript calculate_contract(const CScript& federation_script, const CScript& scri
|
|||
bool liquid_op_else_found = false;
|
||||
while (federation_script.GetOp(sdpc, opcodeTmp, vch))
|
||||
{
|
||||
// For liquid watchman template, don't tweak emergency keys
|
||||
|
||||
// For liquidv1 initial watchman template, don't tweak emergency keys
|
||||
if (is_liquidv1_watchman && opcodeTmp == OP_ELSE) {
|
||||
liquid_op_else_found = true;
|
||||
}
|
||||
|
|
@ -147,7 +141,7 @@ CScript calculate_contract(const CScript& federation_script, const CScript& scri
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
static bool CheckPeginTx(const std::vector<unsigned char>& tx_data, T& pegtx, const COutPoint& prevout, const CAmount claim_amount, const CScript& claim_script)
|
||||
static bool CheckPeginTx(const std::vector<unsigned char>& tx_data, T& pegtx, const COutPoint& prevout, const CAmount claim_amount, const CScript& claim_script, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
|
||||
{
|
||||
try {
|
||||
CDataStream pegtx_stream(tx_data, SER_NETWORK, PROTOCOL_VERSION);
|
||||
|
|
@ -177,15 +171,29 @@ static bool CheckPeginTx(const std::vector<unsigned char>& tx_data, T& pegtx, co
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check that the witness program matches the p2ch on the p2sh-p2wsh transaction output
|
||||
CScript tweaked_fedpegscript = calculate_contract(Params().GetConsensus().fedpegScript, claim_script);
|
||||
CScript witness_output(GetScriptForWitness(tweaked_fedpegscript));
|
||||
CScript expected_script(CScript() << OP_HASH160 << ToByteVector(ScriptHash(CScriptID(witness_output))) << OP_EQUAL);
|
||||
if (pegtx->vout[prevout.n].scriptPubKey != expected_script) {
|
||||
return false;
|
||||
// Check that the witness program matches the p2ch on the (p2sh-)p2wsh
|
||||
// transaction output. We support multiple scripts as a grace period for peg-in users
|
||||
for (const auto& scripts : fedpegscripts) {
|
||||
int fedpeg_version = 0;
|
||||
std::vector<unsigned char> fedpeg_program;
|
||||
scripts.first.IsWitnessProgram(fedpeg_version, fedpeg_program);
|
||||
// We immediately return true if any fedpegscripts are unencumbered
|
||||
// by currently-known parent chain segwit versions.
|
||||
// TODO: Refactor for future versionbits deployment of parent-segwit version
|
||||
if (fedpeg_version > 0) {
|
||||
return true;
|
||||
}
|
||||
CScript tweaked_fedpegscript = calculate_contract(scripts.second, claim_script);
|
||||
// TODO: Remove script/standard.h dep for GetScriptFor*
|
||||
CScript expected_script(GetScriptForWitness(tweaked_fedpegscript));
|
||||
if (scripts.first.IsPayToScriptHash()) {
|
||||
expected_script = GetScriptForDestination(ScriptHash(expected_script));
|
||||
}
|
||||
if (pegtx->vout[prevout.n].scriptPubKey == expected_script) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
@ -194,7 +202,7 @@ static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash,
|
|||
try {
|
||||
std::vector<uint256> tx_hashes;
|
||||
std::vector<unsigned int> tx_indices;
|
||||
CDataStream merkle_block_stream(merkle_block_raw, SER_NETWORK, PROTOCOL_VERSION);
|
||||
CDataStream merkle_block_stream(merkle_block_raw, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||
merkle_block_stream >> merkle_block;
|
||||
block_hash = merkle_block.header.GetHash();
|
||||
|
||||
|
|
@ -232,7 +240,7 @@ bool CheckParentProofOfWork(uint256 hash, unsigned int nBits, const Consensus::P
|
|||
return true;
|
||||
}
|
||||
|
||||
bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& prevout, std::string& err_msg, bool check_depth) {
|
||||
bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const std::vector<std::pair<CScript, CScript>>& fedpegscripts, const COutPoint& prevout, std::string& err_msg, bool check_depth) {
|
||||
// 0) Return false if !consensus.has_parent_chain
|
||||
if (!Params().GetConsensus().has_parent_chain) {
|
||||
err_msg = "Parent chain is not enabled on this network.";
|
||||
|
|
@ -310,7 +318,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
|
|||
}
|
||||
|
||||
Sidechain::Bitcoin::CTransactionRef pegtx;
|
||||
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) {
|
||||
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script, fedpegscripts)) {
|
||||
err_msg = "Peg-in tx is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
|
@ -329,7 +337,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
|
|||
}
|
||||
|
||||
CTransactionRef pegtx;
|
||||
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script)) {
|
||||
if (!CheckPeginTx(stack[4], pegtx, prevout, value, claim_script, fedpegscripts)) {
|
||||
err_msg = "Peg-in tx is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
|
@ -450,3 +458,41 @@ bool MatchLiquidWatchman(const CScript& script)
|
|||
// No more pushes
|
||||
return (it == script.end());
|
||||
}
|
||||
|
||||
std::vector<std::pair<CScript, CScript>> GetValidFedpegScripts(const CBlockIndex* pblockindex, const Consensus::Params& params, bool nextblock_validation)
|
||||
{
|
||||
assert(pblockindex);
|
||||
|
||||
std::vector<std::pair<CScript, CScript>> fedpegscripts;
|
||||
|
||||
const int32_t epoch_length = params.dynamic_epoch_length;
|
||||
const int32_t epoch_age = pblockindex->nHeight % epoch_length;
|
||||
const int32_t epoch_start_height = pblockindex->nHeight - epoch_age;
|
||||
|
||||
// In mempool and general "enforced next block" RPC we need to look ahead one block
|
||||
// to see if we're on a boundary. If so, put that epoch's fedpegscript in place
|
||||
if (nextblock_validation && epoch_age == epoch_length - 1) {
|
||||
DynaFedParamEntry next_param = ComputeNextBlockFullCurrentParameters(pblockindex, params);
|
||||
fedpegscripts.push_back(std::make_pair(next_param.m_fedpeg_program, next_param.m_fedpegscript));
|
||||
}
|
||||
|
||||
// Next we walk backwards up to M epoch starts
|
||||
for (size_t i = 0; i < params.total_valid_epochs; i++) {
|
||||
|
||||
const CBlockIndex* p_epoch_start = pblockindex->GetAncestor(epoch_start_height-i*epoch_length);
|
||||
|
||||
// We're done here, for whatever reason.
|
||||
if (!p_epoch_start) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!p_epoch_start->dynafed_params.IsNull()) {
|
||||
fedpegscripts.push_back(std::make_pair(p_epoch_start->dynafed_params.m_current.m_fedpeg_program, p_epoch_start->dynafed_params.m_current.m_fedpegscript));
|
||||
} else {
|
||||
fedpegscripts.push_back(std::make_pair(GetScriptForDestination(ScriptHash(GetScriptForDestination(WitnessV0ScriptHash(params.fedpegScript)))), params.fedpegScript));
|
||||
}
|
||||
}
|
||||
// Only return up to the latest two of three possible fedpegscripts, which are enforced
|
||||
fedpegscripts.resize(std::min(fedpegscripts.size(), params.total_valid_epochs));
|
||||
return fedpegscripts;
|
||||
}
|
||||
|
|
|
|||
11
src/pegins.h
11
src/pegins.h
|
|
@ -10,6 +10,7 @@
|
|||
#include <primitives/bitcoin/transaction.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/script.h>
|
||||
#include <chain.h>
|
||||
|
||||
/** Calculates script necessary for p2ch peg-in transactions */
|
||||
CScript calculate_contract(const CScript& federationRedeemScript, const CScript& witnessProgram);
|
||||
|
|
@ -18,11 +19,11 @@ bool GetAmountFromParentChainPegin(CAmount& amount, const CTransaction& txBTC, u
|
|||
/** Check whether a parent chain block hash satisfies the proof-of-work requirement specified by nBits */
|
||||
bool CheckParentProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&);
|
||||
/** Checks pegin witness for validity */
|
||||
bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& prevout, std::string& err_msg, bool check_depth);
|
||||
bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const std::vector<std::pair<CScript, CScript>>& fedpegscripts, const COutPoint& prevout, std::string& err_msg, bool check_depth);
|
||||
// Constructs unblinded output to be used in amount and scriptpubkey checks during pegin
|
||||
CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness);
|
||||
|
||||
/* Belt-and-suspenders-only matching against telescoped multisig used on Liquid v1:
|
||||
/* Consensus-critical. Matching against telescoped multisig used on Liquid v1:
|
||||
* Pseudo-structure:
|
||||
* Check number of elements on stack
|
||||
* If enough for federation multisig, push all multisig args onto stack except OP_CMS
|
||||
|
|
@ -31,4 +32,10 @@ CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness);
|
|||
*/
|
||||
bool MatchLiquidWatchman(const CScript& script);
|
||||
|
||||
/** Get full fedpegscripts from two previous epoch starts based on given index
|
||||
* nextblock_validation is false when doing block validation, true for mempool
|
||||
* or "lookahead" purposes. Newest epochs first. The first returned element is the
|
||||
* scriptPubKey for the script, the second is the witnessScript. */
|
||||
std::vector<std::pair<CScript, CScript>> GetValidFedpegScripts(const CBlockIndex* pblockindex, const Consensus::Params& params, bool nextblock_validation);
|
||||
|
||||
#endif // BITCOIN_PEGINS_H
|
||||
|
|
|
|||
|
|
@ -139,13 +139,6 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason)
|
|||
|
||||
if (whichType == TX_NULL_DATA) {
|
||||
nDataOut++;
|
||||
if (params.GetEnforcePak() &&
|
||||
txout.scriptPubKey.IsPegoutScript(params.ParentGenesisBlockHash()) &&
|
||||
txout.nAsset.IsExplicit() && txout.nAsset.GetAsset() == Params().GetConsensus().pegged_asset &&
|
||||
(!ScriptHasValidPAKProof(txout.scriptPubKey, params.ParentGenesisBlockHash()))) {
|
||||
reason = "invalid-pegout-proof";
|
||||
return false;
|
||||
}
|
||||
} else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) {
|
||||
reason = "bare-multisig";
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -40,3 +40,30 @@ std::string CBlock::ToString() const
|
|||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
uint256 DynaFedParamEntry::CalculateRoot() const
|
||||
{
|
||||
if (IsNull()) {
|
||||
return uint256();
|
||||
}
|
||||
|
||||
std::vector<uint256> leaves;
|
||||
leaves.push_back(SerializeHash(m_signblockscript, SER_GETHASH, 0));
|
||||
leaves.push_back(SerializeHash(m_signblock_witness_limit, SER_GETHASH, 0));
|
||||
leaves.push_back(SerializeHash(m_fedpeg_program, SER_GETHASH, 0));
|
||||
leaves.push_back(SerializeHash(m_fedpegscript, SER_GETHASH, 0));
|
||||
leaves.push_back(SerializeHash(m_extension_space, SER_GETHASH, 0));
|
||||
return ComputeFastMerkleRoot(leaves);
|
||||
}
|
||||
|
||||
uint256 DynaFedParams::CalculateRoot() const
|
||||
{
|
||||
if (IsNull()) {
|
||||
return uint256();
|
||||
}
|
||||
|
||||
std::vector<uint256> leaves;
|
||||
leaves.push_back(m_current.CalculateRoot());
|
||||
leaves.push_back(m_proposed.CalculateRoot());
|
||||
return ComputeFastMerkleRoot(leaves);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,126 @@ public:
|
|||
std::string ToString() const;
|
||||
};
|
||||
|
||||
/*
|
||||
* Contains all the consensus parameters that can be voted for in dynamic federations
|
||||
*/
|
||||
class DynaFedParamEntry
|
||||
{
|
||||
public:
|
||||
unsigned char m_serialize_type; // Determines how it is serialized, defaults to null
|
||||
CScript m_signblockscript;
|
||||
uint32_t m_signblock_witness_limit; // Max block signature witness serialized size
|
||||
CScript m_fedpeg_program; // The "scriptPubKey" of the fedpegscript
|
||||
CScript m_fedpegscript; // The witnessScript for witness v0 or undefined otherwise.
|
||||
// No consensus meaning to the particular bytes, currently we interpret as PAK keys, details in pak.h
|
||||
std::vector<std::vector<unsigned char>> m_extension_space;
|
||||
|
||||
// Each constructor sets its own serialization type implicitly based on which
|
||||
// arguments are given
|
||||
DynaFedParamEntry() { m_signblock_witness_limit = 0; m_serialize_type = 0; };
|
||||
DynaFedParamEntry(const CScript& signblockscript_in, const uint32_t sbs_wit_limit_in) : m_signblockscript(signblockscript_in), m_signblock_witness_limit(sbs_wit_limit_in) { m_serialize_type = 1; };
|
||||
DynaFedParamEntry(const CScript& signblockscript_in, const uint32_t sbs_wit_limit_in, const CScript& fedpeg_program_in, const CScript& fedpegscript_in, const std::vector<std::vector<unsigned char>> extension_space_in) : m_signblockscript(signblockscript_in), m_signblock_witness_limit(sbs_wit_limit_in), m_fedpeg_program(fedpeg_program_in), m_fedpegscript(fedpegscript_in), m_extension_space(extension_space_in) { m_serialize_type = 2; };
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(m_serialize_type);
|
||||
switch(m_serialize_type) {
|
||||
case 0:
|
||||
/* Null entry, used to signal "no vote" proposal */
|
||||
break;
|
||||
case 1:
|
||||
READWRITE(m_signblockscript);
|
||||
READWRITE(m_signblock_witness_limit);
|
||||
break;
|
||||
case 2:
|
||||
READWRITE(m_signblockscript);
|
||||
READWRITE(m_signblock_witness_limit);
|
||||
READWRITE(m_fedpeg_program);
|
||||
READWRITE(m_fedpegscript);
|
||||
READWRITE(m_extension_space);
|
||||
break;
|
||||
default:
|
||||
throw std::ios_base::failure("Invalid consensus parameter entry type");
|
||||
}
|
||||
}
|
||||
|
||||
uint256 CalculateRoot() const;
|
||||
|
||||
bool IsNull() const
|
||||
{
|
||||
return m_serialize_type == 0 &&
|
||||
m_signblockscript.empty() &&
|
||||
m_signblock_witness_limit == 0 &&
|
||||
m_fedpeg_program.empty() &&
|
||||
m_fedpegscript.empty() &&
|
||||
m_extension_space.empty();
|
||||
|
||||
}
|
||||
|
||||
void SetNull()
|
||||
{
|
||||
m_serialize_type = 0;
|
||||
m_signblockscript.clear();
|
||||
m_signblock_witness_limit = 0;
|
||||
m_fedpeg_program.clear();
|
||||
m_fedpegscript.clear();
|
||||
m_extension_space.clear();
|
||||
}
|
||||
|
||||
bool operator==(const DynaFedParamEntry &other) const
|
||||
{
|
||||
return m_serialize_type == other.m_serialize_type &&
|
||||
m_signblockscript == other.m_signblockscript &&
|
||||
m_signblock_witness_limit == other.m_signblock_witness_limit &&
|
||||
m_fedpeg_program == other.m_fedpeg_program &&
|
||||
m_fedpegscript == other.m_fedpegscript &&
|
||||
m_extension_space == other.m_extension_space;
|
||||
}
|
||||
bool operator!=(const DynaFedParamEntry &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Encapsulation of the pair of dynamic federations parameters for "current" and "proposed"
|
||||
*/
|
||||
class DynaFedParams
|
||||
{
|
||||
public:
|
||||
|
||||
// Currently enforced by network, not all fields may be known
|
||||
DynaFedParamEntry m_current;
|
||||
// Proposed rules for next epoch
|
||||
DynaFedParamEntry m_proposed;
|
||||
|
||||
DynaFedParams() {};
|
||||
DynaFedParams(const DynaFedParamEntry& current, const DynaFedParamEntry& proposed) : m_current(current), m_proposed(proposed) {};
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(m_current);
|
||||
READWRITE(m_proposed);
|
||||
}
|
||||
|
||||
uint256 CalculateRoot() const;
|
||||
|
||||
bool IsNull() const
|
||||
{
|
||||
return m_current.IsNull() && m_proposed.IsNull();
|
||||
}
|
||||
|
||||
void SetNull()
|
||||
{
|
||||
m_current.SetNull();
|
||||
m_proposed.SetNull();
|
||||
}
|
||||
};
|
||||
|
||||
/** Nodes collect new transactions into a block, hash them into a hash tree,
|
||||
* and scan through nonce values to make the block's hash satisfy proof-of-work
|
||||
* requirements. When they solve the proof-of-work, they broadcast the block
|
||||
|
|
@ -72,29 +192,66 @@ public:
|
|||
uint32_t block_height;
|
||||
uint32_t nBits;
|
||||
uint32_t nNonce;
|
||||
// Only used pre-dynamic federation
|
||||
CProof proof;
|
||||
// Dynamic federation: Subsumes the proof field
|
||||
DynaFedParams m_dynafed_params;
|
||||
CScriptWitness m_signblock_witness;
|
||||
|
||||
CBlockHeader()
|
||||
{
|
||||
SetNull();
|
||||
}
|
||||
|
||||
// HF bit to detect dynamic federation blocks
|
||||
static const uint32_t DYNAFED_HF_MASK = 1 << 31;
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(this->nVersion);
|
||||
READWRITE(hashPrevBlock);
|
||||
READWRITE(hashMerkleRoot);
|
||||
READWRITE(nTime);
|
||||
if (g_con_blockheightinheader) {
|
||||
READWRITE(block_height);
|
||||
}
|
||||
if (g_signed_blocks) {
|
||||
READWRITE(proof);
|
||||
const bool fAllowWitness = !(s.GetVersion() & SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||
|
||||
// Detect dynamic federation block serialization using "HF bit",
|
||||
// or the signed bit which is invalid in Bitcoin
|
||||
bool is_dyna = false;
|
||||
int32_t nVersion;
|
||||
if (ser_action.ForRead()) {
|
||||
READWRITE(nVersion);
|
||||
is_dyna = nVersion < 0;
|
||||
this->nVersion = ~DYNAFED_HF_MASK & nVersion;
|
||||
} else {
|
||||
READWRITE(nBits);
|
||||
READWRITE(nNonce);
|
||||
nVersion = this->nVersion;
|
||||
if (!m_dynafed_params.IsNull()) {
|
||||
nVersion |= DYNAFED_HF_MASK;
|
||||
is_dyna = true;
|
||||
}
|
||||
READWRITE(nVersion);
|
||||
}
|
||||
|
||||
if (is_dyna) {
|
||||
READWRITE(hashPrevBlock);
|
||||
READWRITE(hashMerkleRoot);
|
||||
READWRITE(nTime);
|
||||
READWRITE(block_height);
|
||||
READWRITE(m_dynafed_params);
|
||||
// We do not serialize witness for hashes, or weight calculation
|
||||
if (!(s.GetType() & SER_GETHASH) && fAllowWitness) {
|
||||
READWRITE(m_signblock_witness.stack);
|
||||
}
|
||||
} else {
|
||||
READWRITE(hashPrevBlock);
|
||||
READWRITE(hashMerkleRoot);
|
||||
READWRITE(nTime);
|
||||
if (g_con_blockheightinheader) {
|
||||
READWRITE(block_height);
|
||||
}
|
||||
if (g_signed_blocks) {
|
||||
READWRITE(proof);
|
||||
} else {
|
||||
READWRITE(nBits);
|
||||
READWRITE(nNonce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +270,7 @@ public:
|
|||
bool IsNull() const
|
||||
{
|
||||
if (g_signed_blocks) {
|
||||
return proof.IsNull();
|
||||
return proof.IsNull() && m_dynafed_params.IsNull();
|
||||
} else {
|
||||
return (nBits == 0);
|
||||
}
|
||||
|
|
@ -174,6 +331,7 @@ public:
|
|||
block.nBits = nBits;
|
||||
block.nNonce = nNonce;
|
||||
block.proof = proof;
|
||||
block.m_dynafed_params = m_dynafed_params;
|
||||
return block;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <primitives/pak.h>
|
||||
#include <pubkey.h>
|
||||
#include <dynafed.h>
|
||||
|
||||
// ELEMENTS
|
||||
|
||||
|
|
@ -33,73 +34,9 @@ public:
|
|||
static CSecp256k1Init instance_of_csecp256k1;
|
||||
}
|
||||
|
||||
CScript CPAKList::Magic()
|
||||
{
|
||||
CScript scriptPubKey;
|
||||
scriptPubKey.resize(6);
|
||||
scriptPubKey[0] = OP_RETURN;
|
||||
scriptPubKey[1] = 0x04;
|
||||
scriptPubKey[2] = 0xab;
|
||||
scriptPubKey[3] = 0x22;
|
||||
scriptPubKey[4] = 0xaa;
|
||||
scriptPubKey[5] = 0xee;
|
||||
return scriptPubKey;
|
||||
}
|
||||
|
||||
std::vector<CScript> CPAKList::GenerateCoinbasePAKCommitments() const
|
||||
{
|
||||
std::vector<CScript> commitments;
|
||||
CScript scriptPubKey = CPAKList::Magic();
|
||||
|
||||
for (unsigned int i = 0; i < m_offline_keys.size(); i++) {
|
||||
CScript scriptCommitment(scriptPubKey);
|
||||
unsigned char pubkey[33];
|
||||
size_t outputlen = 33;
|
||||
secp256k1_ec_pubkey_serialize(secp256k1_ctx_pak, pubkey, &outputlen, &m_offline_keys[i], SECP256K1_EC_COMPRESSED);
|
||||
assert(outputlen == 33);
|
||||
scriptCommitment << std::vector<unsigned char>(pubkey, pubkey+outputlen);
|
||||
secp256k1_ec_pubkey_serialize(secp256k1_ctx_pak, pubkey, &outputlen, &m_online_keys[i], SECP256K1_EC_COMPRESSED);
|
||||
assert(outputlen == 33);
|
||||
scriptCommitment << std::vector<unsigned char>(pubkey, pubkey+outputlen);
|
||||
commitments.push_back(scriptCommitment);
|
||||
}
|
||||
|
||||
return commitments;
|
||||
}
|
||||
|
||||
std::vector<CScript> CPAKList::GenerateCoinbasePAKReject() const
|
||||
{
|
||||
CScript scriptPubKey = CPAKList::Magic();
|
||||
|
||||
std::vector<unsigned char> reject;
|
||||
reject.push_back('R');
|
||||
reject.push_back('E');
|
||||
reject.push_back('J');
|
||||
reject.push_back('E');
|
||||
reject.push_back('C');
|
||||
reject.push_back('T');
|
||||
|
||||
scriptPubKey << reject;
|
||||
|
||||
std::vector<CScript> commitment;
|
||||
commitment.push_back(scriptPubKey);
|
||||
return commitment;
|
||||
}
|
||||
|
||||
void CPAKList::CreateCommitments(std::vector<CScript> &commitments) const
|
||||
{
|
||||
if(reject) {
|
||||
commitments = GenerateCoinbasePAKReject();
|
||||
} else {
|
||||
commitments = GenerateCoinbasePAKCommitments();
|
||||
}
|
||||
}
|
||||
|
||||
bool CPAKList::operator==(const CPAKList &other) const
|
||||
{
|
||||
if (this->reject != other.reject) {
|
||||
return false;
|
||||
} else if (this->m_offline_keys.size() != other.m_offline_keys.size()) {
|
||||
if (this->m_offline_keys.size() != other.m_offline_keys.size()) {
|
||||
return false;
|
||||
} else {
|
||||
for (unsigned int i = 0; i < this->m_offline_keys.size(); i++) {
|
||||
|
|
@ -112,7 +49,7 @@ bool CPAKList::operator==(const CPAKList &other) const
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CPAKList::FromBytes(CPAKList &paklist, std::vector<std::vector<unsigned char> >& offline_keys_bytes, std::vector<std::vector<unsigned char> >& online_keys_bytes, bool is_reject)
|
||||
bool CPAKList::FromBytes(CPAKList &paklist, const std::vector<std::vector<unsigned char> >& offline_keys_bytes, const std::vector<std::vector<unsigned char> >& online_keys_bytes)
|
||||
{
|
||||
if(offline_keys_bytes.size() != online_keys_bytes.size()
|
||||
|| offline_keys_bytes.size() > SECP256K1_WHITELIST_MAX_N_KEYS) {
|
||||
|
|
@ -134,11 +71,11 @@ bool CPAKList::FromBytes(CPAKList &paklist, std::vector<std::vector<unsigned cha
|
|||
online_keys.push_back(pubkey2);
|
||||
}
|
||||
|
||||
paklist = CPAKList(offline_keys, online_keys, is_reject);
|
||||
paklist = CPAKList(offline_keys, online_keys);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CPAKList::ToBytes(std::vector<std::vector<unsigned char> >& offline_keys, std::vector<std::vector<unsigned char> >& online_keys, bool &is_reject) const
|
||||
void CPAKList::ToBytes(std::vector<std::vector<unsigned char> >& offline_keys, std::vector<std::vector<unsigned char> >& online_keys) const
|
||||
{
|
||||
offline_keys.resize(0);
|
||||
online_keys.resize(0);
|
||||
|
|
@ -151,23 +88,15 @@ void CPAKList::ToBytes(std::vector<std::vector<unsigned char> >& offline_keys, s
|
|||
secp256k1_ec_pubkey_serialize(secp256k1_ctx_pak, pubkey, &outputlen, &m_online_keys[i], SECP256K1_EC_COMPRESSED);
|
||||
online_keys.push_back(std::vector<unsigned char>(pubkey, pubkey+outputlen));
|
||||
}
|
||||
is_reject = reject;
|
||||
}
|
||||
|
||||
// Proof follows the OP_RETURN <genesis_block_hash> <destination_scriptpubkey>
|
||||
// in multiple pushes: <full_pubkey> <proof>
|
||||
bool ScriptHasValidPAKProof(const CScript& script, const uint256& genesis_hash)
|
||||
bool ScriptHasValidPAKProof(const CScript& script, const uint256& genesis_hash, const CPAKList& paklist)
|
||||
{
|
||||
assert(script.IsPegoutScript(genesis_hash));
|
||||
|
||||
CPAKList paklist;
|
||||
if (g_paklist_config) {
|
||||
paklist = *g_paklist_config;
|
||||
} else {
|
||||
paklist = g_paklist_blockchain;
|
||||
}
|
||||
|
||||
if (paklist.IsReject() || paklist.IsEmpty()) {
|
||||
if (paklist.IsReject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -243,3 +172,49 @@ bool ScriptHasValidPAKProof(const CScript& script, const uint256& genesis_hash)
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
CPAKList CreatePAKListFromExtensionSpace(const std::vector<std::vector<unsigned char>>& extension_space)
|
||||
{
|
||||
CPAKList paklist;
|
||||
std::vector<std::vector<unsigned char>> offline_keys;
|
||||
std::vector<std::vector<unsigned char>> online_keys;
|
||||
for (const auto& entry : extension_space) {
|
||||
if (entry.size() != 66) {
|
||||
return CPAKList();
|
||||
}
|
||||
// Dumbly tries to extract two pubkeys, relies on FromBytes to parse/validate
|
||||
offline_keys.emplace_back(entry.begin(), entry.begin()+33);
|
||||
online_keys.emplace_back(entry.begin()+33, entry.end());
|
||||
}
|
||||
if (!CPAKList::FromBytes(paklist, offline_keys, online_keys)) {
|
||||
return CPAKList();
|
||||
}
|
||||
return paklist;
|
||||
}
|
||||
|
||||
CPAKList GetActivePAKList(const CBlockIndex* pblockindex, const Consensus::Params& params)
|
||||
{
|
||||
assert(pblockindex);
|
||||
|
||||
return CreatePAKListFromExtensionSpace(ComputeNextBlockFullCurrentParameters(pblockindex, params).m_extension_space);
|
||||
}
|
||||
|
||||
bool IsPAKValidOutput(const CTxOut& txout, const CPAKList& paklist, const uint256& parent_gen_hash, const CAsset& peg_asset)
|
||||
{
|
||||
if (txout.scriptPubKey.IsPegoutScript(parent_gen_hash) &&
|
||||
txout.nAsset.IsExplicit() && txout.nAsset.GetAsset() == peg_asset &&
|
||||
(!ScriptHasValidPAKProof(txout.scriptPubKey, parent_gen_hash, paklist))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPAKValidTx(const CTransaction& tx, const CPAKList& paklist, const uint256& parent_gen_hash, const CAsset& peg_asset)
|
||||
{
|
||||
for (const auto& txout : tx.vout) {
|
||||
if (!IsPAKValidOutput(txout, paklist, parent_gen_hash, peg_asset)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,28 +8,22 @@
|
|||
#include <script/script.h>
|
||||
#include <secp256k1/include/secp256k1_whitelist.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <chain.h>
|
||||
|
||||
class CPAKList
|
||||
{
|
||||
private:
|
||||
std::vector<secp256k1_pubkey> m_offline_keys;
|
||||
std::vector<secp256k1_pubkey> m_online_keys;
|
||||
bool reject;
|
||||
|
||||
std::vector<CScript> GenerateCoinbasePAKCommitments() const;
|
||||
std::vector<CScript> GenerateCoinbasePAKReject() const;
|
||||
|
||||
public:
|
||||
CPAKList()
|
||||
{
|
||||
reject = true;
|
||||
}
|
||||
CPAKList() {}
|
||||
/**
|
||||
* Creates a new CPAKList. Requires that the number of offline keys is the same as the number of online keys
|
||||
* and that this number is not larger than SECP256K1_WHITELIST_MAX_N_KEYS.
|
||||
*/
|
||||
CPAKList(std::vector<secp256k1_pubkey> offline_keys, std::vector<secp256k1_pubkey> online_keys, bool reject) :
|
||||
m_offline_keys(offline_keys), m_online_keys(online_keys), reject(reject) {
|
||||
CPAKList(std::vector<secp256k1_pubkey> offline_keys, std::vector<secp256k1_pubkey> online_keys) :
|
||||
m_offline_keys(offline_keys), m_online_keys(online_keys) {
|
||||
assert(m_offline_keys.size() == m_online_keys.size());
|
||||
assert(m_offline_keys.size() <= SECP256K1_WHITELIST_MAX_N_KEYS);
|
||||
}
|
||||
|
|
@ -41,11 +35,7 @@ public:
|
|||
}
|
||||
bool IsReject() const
|
||||
{
|
||||
return reject;
|
||||
}
|
||||
bool IsEmpty() const
|
||||
{
|
||||
return !reject && this->size() == 0;
|
||||
return size()==0;
|
||||
}
|
||||
std::vector<secp256k1_pubkey> OnlineKeys() const
|
||||
{
|
||||
|
|
@ -60,24 +50,23 @@ public:
|
|||
return m_offline_keys.size();
|
||||
}
|
||||
|
||||
static CScript Magic();
|
||||
/** Produce a list of scripts to add to the coinbase to signal changes in PAK list or rejection of any pak proofs to nodes */
|
||||
void CreateCommitments(std::vector<CScript> &commitments) const;
|
||||
|
||||
static bool FromBytes(CPAKList &paklist, std::vector<std::vector<unsigned char> >& offline_keys, std::vector<std::vector<unsigned char> >& online_keys, bool is_reject);
|
||||
void ToBytes(std::vector<std::vector<unsigned char> >& offline_keys, std::vector<std::vector<unsigned char> >& online_keys, bool &is_reject) const;
|
||||
static bool FromBytes(CPAKList &paklist, const std::vector<std::vector<unsigned char> >& offline_keys, const std::vector<std::vector<unsigned char> >& online_keys);
|
||||
void ToBytes(std::vector<std::vector<unsigned char> >& offline_keys, std::vector<std::vector<unsigned char> >& online_keys) const;
|
||||
};
|
||||
|
||||
/**
|
||||
** Returns true if the script includes valid pegout proof
|
||||
** given the PAK list loaded. Two pushes after regular pegout script:
|
||||
** given the PAK list. Two pushes after regular pegout script:
|
||||
** <full_pubkey> <proof>
|
||||
**/
|
||||
bool ScriptHasValidPAKProof(const CScript& script, const uint256& genesis_hash);
|
||||
bool ScriptHasValidPAKProof(const CScript& script, const uint256& genesis_hash, const CPAKList& paklist);
|
||||
|
||||
// ELEMENTS:
|
||||
extern boost::optional<CPAKList> g_paklist_config;
|
||||
extern CPAKList g_paklist_blockchain;
|
||||
///////////
|
||||
CPAKList CreatePAKListFromExtensionSpace(const std::vector<std::vector<unsigned char>>& extension_space);
|
||||
|
||||
CPAKList GetActivePAKList(const CBlockIndex* pblockindex, const Consensus::Params& params);
|
||||
|
||||
bool IsPAKValidOutput(const CTxOut& txout, const CPAKList& paklist, const uint256& parent_gen_hash, const CAsset& peg_asset);
|
||||
|
||||
bool IsPAKValidTx(const CTransaction& tx, const CPAKList& paklist, const uint256& parent_gen_hash, const CAsset& peg_asset);
|
||||
|
||||
#endif // BITCOIN_PRIMITIVES_PAK_H
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
#include <validationinterface.h>
|
||||
#include <versionbitsinfo.h>
|
||||
#include <warnings.h>
|
||||
#include <pegins.h>
|
||||
#include <dynafed.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
|
@ -79,6 +81,29 @@ double GetDifficulty(const CBlockIndex* blockindex)
|
|||
return dDiff;
|
||||
}
|
||||
|
||||
UniValue paramEntryToJSON(const DynaFedParamEntry& entry)
|
||||
{
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("signblockscript", HexStr(entry.m_signblockscript));
|
||||
result.pushKV("max_block_witness", (uint64_t)entry.m_signblock_witness_limit);
|
||||
result.pushKV("fedpegscript", HexStr(entry.m_fedpegscript));
|
||||
UniValue result_extension(UniValue::VARR);
|
||||
for (auto& item : entry.m_extension_space) {
|
||||
result_extension.push_back(HexStr(item));
|
||||
}
|
||||
result.pushKV("extension_space", result_extension);
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue dynaParamsToJSON(const DynaFedParams& dynafed_params)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.pushKV("current", paramEntryToJSON(dynafed_params.m_current));
|
||||
ret.pushKV("proposed", paramEntryToJSON(dynafed_params.m_proposed));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* blockindex, const CBlockIndex*& next)
|
||||
{
|
||||
next = tip->GetAncestor(blockindex->nHeight + 1);
|
||||
|
|
@ -110,6 +135,9 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex
|
|||
} else {
|
||||
result.pushKV("signblock_witness_asm", ScriptToAsmStr(blockindex->proof.solution));
|
||||
result.pushKV("signblock_witness_hex", HexStr(blockindex->proof.solution));
|
||||
if (!blockindex->dynafed_params.IsNull()) {
|
||||
result.pushKV("dynamic_parameters", dynaParamsToJSON(blockindex->dynafed_params));
|
||||
}
|
||||
}
|
||||
result.pushKV("nTx", (uint64_t)blockindex->nTx);
|
||||
if (blockindex->pprev)
|
||||
|
|
@ -154,9 +182,13 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn
|
|||
result.pushKV("difficulty", GetDifficulty(blockindex));
|
||||
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
|
||||
} else {
|
||||
result.pushKV("signblock_witness_asm", ScriptToAsmStr(blockindex->proof.solution));
|
||||
result.pushKV("signblock_witness_hex", HexStr(blockindex->proof.solution));
|
||||
result.pushKV("signblock_challenge", HexStr(blockindex->proof.challenge));
|
||||
if (block.m_dynafed_params.IsNull()) {
|
||||
result.pushKV("signblock_witness_asm", ScriptToAsmStr(blockindex->proof.solution));
|
||||
result.pushKV("signblock_witness_hex", HexStr(blockindex->proof.solution));
|
||||
result.pushKV("signblock_challenge", HexStr(blockindex->proof.challenge));
|
||||
} else {
|
||||
result.pushKV("dynamic_parameters", dynaParamsToJSON(block.m_dynafed_params));
|
||||
}
|
||||
}
|
||||
result.pushKV("nTx", (uint64_t)blockindex->nTx);
|
||||
|
||||
|
|
@ -771,6 +803,24 @@ static UniValue getblockheader(const JSONRPCRequest& request)
|
|||
" \"nTx\" : n, (numeric) The number of transactions in the block.\n"
|
||||
" \"signblock_witness_asm\" : \"xxxx\", (string) ASM of sign block witness data.\n"
|
||||
" \"signblock_witness_hex\" : \"xxxx\", (string) Hex of sign block witness data.\n"
|
||||
" \"dynamic_parameters\" : (obj) Dynamic federation parameters in the block, if any.\n"
|
||||
" {\n"
|
||||
" \"current\" : (obj) enforced dynamic federation parameters. Do note that only the signblockscript is published for each block, while others are published only at epoch start.\n"
|
||||
" {\n"
|
||||
" \"signblockscript\" : \"xxxx\", (string) signblock script in hex\n"
|
||||
" \"max_block_witness\" : x, (numeric) Maximum serialized size of the block witness stack\n"
|
||||
" \"fedpegscript\" : \"xxxx\", (string) fedpegscript in hex\n"
|
||||
" \"extension_space\" : (array) Array of hex-encoded strings\n"
|
||||
" [\n"
|
||||
" xxxx,\n"
|
||||
" ...\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
" \"proposed\" : (obj) Proposed paramaters. Uninforced. Must be published in full.\n"
|
||||
" {\n"
|
||||
" ... same entries as current\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
|
||||
" \"nextblockhash\" : \"hash\", (string) The hash of the next block\n"
|
||||
"}\n"
|
||||
|
|
@ -873,6 +923,24 @@ static UniValue getblock(const JSONRPCRequest& request)
|
|||
" \"nTx\" : n, (numeric) The number of transactions in the block.\n"
|
||||
" \"signblock_witness_asm\" : \"xxxx\", (string) ASM of sign block witness data.\n"
|
||||
" \"signblock_witness_hex\" : \"xxxx\", (string) Hex of sign block witness data.\n"
|
||||
" \"dynamic_parameters\" : (obj) Dynamic federation parameters in the block, if any.\n"
|
||||
" {\n"
|
||||
" \"current\" : (obj) enforced dynamic federation parameters. Do note that only the signblockscript is published for each block, while others are published only at epoch start.\n"
|
||||
" {\n"
|
||||
" \"signblockscript\" : \"xxxx\", (string) signblock script in hex\n"
|
||||
" \"max_block_witness\" : x, (numeric) Maximum serialized size of the block witness stack\n"
|
||||
" \"fedpegscript\" : \"xxxx\", (string) fedpegscript in hex\n"
|
||||
" \"extension_space\" : (array) Array of hex-encoded strings\n"
|
||||
" [\n"
|
||||
" xxxx,\n"
|
||||
" ...\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
" \"proposed\" : (obj) Proposed paramaters. Uninforced. Must be published in full.\n"
|
||||
" {\n"
|
||||
" ... same entries as current\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
|
||||
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
|
||||
"}\n"
|
||||
|
|
@ -1310,8 +1378,15 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
|
|||
" \"chainwork\": \"xxxx\" (string) total amount of work in active chain, in hexadecimal\n"
|
||||
" \"size_on_disk\": xxxxxx, (numeric) the estimated size of the block and undo files on disk\n"
|
||||
" \"pruned\": xx, (boolean) if the blocks are subject to pruning\n"
|
||||
" \"signblock_asm\" : \"xxxx\", (string) ASM of sign block challenge data.\n"
|
||||
" \"signblock_hex\" : \"xxxx\", (string) Hex of sign block challenge data.\n"
|
||||
" \"signblock_asm\" : \"xxxx\", (string) ASM of sign block challenge data from genesis block.\n"
|
||||
" \"signblock_hex\" : \"xxxx\", (string) Hex of sign block challenge data from genesis block.\n"
|
||||
" \"current_signblock_asm\" : \"xxxx\", (string) ASM of sign block challenge data enforced on the next block.\n"
|
||||
" \"current_signblock_hex\" : \"xxxx\", (string) Hex of sign block challenge data enforced on the next block.\n"
|
||||
" \"max_block_witness\" : xx, (numeric) maximum sized block witness serialized size for the next block.\n"
|
||||
" \"epoch_length\" : xx, (numeric) Length of dynamic federations epoch, or signaling period\n"
|
||||
" \"total_valid_epochs\" : xx, (numeric) Number of epochs a given fedpscript is valid for, defined per chain.\n"
|
||||
" \"epoch_age\" : xx, (numeric) number of blocks into a dynamic federation epoch chain tip is. This number is between 0 to epoch_length-1\n"
|
||||
" \"extension_space\" : [\"xxxx\", ...], (array) Array of extension fields in dynamic blockheader\n"
|
||||
" \"pruneheight\": xxxxxx, (numeric) lowest-height complete block stored (only present if pruning is enabled)\n"
|
||||
" \"automatic_pruning\": xx, (boolean) whether automatic pruning is enabled (only present if pruning is enabled)\n"
|
||||
" \"prune_target_size\": xxxxxx, (numeric) the target size used by pruning (only present if automatic pruning is enabled)\n"
|
||||
|
|
@ -1374,6 +1449,29 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
|
|||
CScript sign_block_script = chainparams.GetConsensus().signblockscript;
|
||||
obj.pushKV("signblock_asm", ScriptToAsmStr(sign_block_script));
|
||||
obj.pushKV("signblock_hex", HexStr(sign_block_script));
|
||||
if (!IsDynaFedEnabled(chainActive.Tip(), chainparams.GetConsensus())) {
|
||||
obj.pushKV("current_signblock_asm", ScriptToAsmStr(sign_block_script));
|
||||
obj.pushKV("current_signblock_hex", HexStr(sign_block_script));
|
||||
obj.pushKV("max_block_witness", (uint64_t)chainparams.GetConsensus().max_block_signature_size);
|
||||
UniValue arr(UniValue::VARR);
|
||||
for (const auto& extension : chainparams.GetConsensus().first_extension_space) {
|
||||
arr.push_back(HexStr(extension));
|
||||
}
|
||||
obj.pushKV("extension_space", arr);
|
||||
} else {
|
||||
const DynaFedParamEntry entry = ComputeNextBlockFullCurrentParameters(chainActive.Tip(), chainparams.GetConsensus());
|
||||
obj.pushKV("current_signblock_asm", ScriptToAsmStr(entry.m_signblockscript));
|
||||
obj.pushKV("current_signblock_hex", HexStr(entry.m_signblockscript));
|
||||
obj.pushKV("max_block_witness", (uint64_t)entry.m_signblock_witness_limit);
|
||||
UniValue arr(UniValue::VARR);
|
||||
for (const auto& extension : entry.m_extension_space) {
|
||||
arr.push_back(HexStr(extension));
|
||||
}
|
||||
obj.pushKV("extension_space", arr);
|
||||
obj.pushKV("epoch_length", (uint64_t)chainparams.GetConsensus().dynamic_epoch_length);
|
||||
obj.pushKV("total_valid_epochs", (uint64_t)chainparams.GetConsensus().total_valid_epochs);
|
||||
obj.pushKV("epoch_age", (uint64_t)(chainActive.Tip()->nHeight % chainparams.GetConsensus().dynamic_epoch_length));
|
||||
}
|
||||
}
|
||||
|
||||
if (fPruneMode) {
|
||||
|
|
@ -2407,7 +2505,17 @@ UniValue getsidechaininfo(const JSONRPCRequest& request)
|
|||
{},
|
||||
RPCResult{
|
||||
"{\n"
|
||||
" \"fedpegscript\": \"xxxx\", (string) The fedpegscript in hex\n"
|
||||
" \"fedpegscript\": \"xxxx\", (string) The fedpegscript in hex from genesis block\n"
|
||||
" \"current_fedpegscripts\": (array) The currently-enforced fedpegscripts in hex. Peg-ins for any entries on this list are honored by consensus and policy. Oldest first. Two total entries are possible.\n"
|
||||
" [\n"
|
||||
" \"xxxx\", (string) Hex-encoded active fedpegscript\n"
|
||||
" ...\n"
|
||||
" ]\n"
|
||||
" \"current_fedpeg_programs\": (array) The currently-enforced fedpegscript scriptPubKeys in hex. Prior to a transition this may be P2SH scriptpubkey, otherwise it will be a native segwit script. Results are paired in-order with current_fedpegscripts.\n"
|
||||
" [\n"
|
||||
" \"xxxx\", (string) Hex-encoded active fedpegscriptscriptPubKeys\n"
|
||||
" ...\n"
|
||||
" ]\n"
|
||||
" \"pegged_asset\" : \"xxxx\", (string) Pegged asset type in hex\n"
|
||||
" \"min_peg_diff\" : \"xxxx\", (string) The minimum difficulty parent chain header target. Peg-in headers that have less work will be rejected as an anti-Dos measure.\n"
|
||||
" \"parent_blockhash\" : \"xxxx\", (string) The parent genesis blockhash as source of pegged-in funds.\n"
|
||||
|
|
@ -2432,6 +2540,16 @@ UniValue getsidechaininfo(const JSONRPCRequest& request)
|
|||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("fedpegscript", HexStr(consensus.fedpegScript.begin(), consensus.fedpegScript.end()));
|
||||
// We use mempool_validation as true to show what is enforced for *next* block
|
||||
std::vector<std::pair<CScript, CScript>> fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), consensus, true /* nextblock_validation */);
|
||||
UniValue fedpeg_prog_entries(UniValue::VARR);
|
||||
UniValue fedpeg_entries(UniValue::VARR);
|
||||
for (const auto& scripts : fedpegscripts) {
|
||||
fedpeg_prog_entries.push_back(HexStr(scripts.first));
|
||||
fedpeg_entries.push_back(HexStr(scripts.second));
|
||||
}
|
||||
obj.pushKV("current_fedpeg_programs", fedpeg_prog_entries);
|
||||
obj.pushKV("current_fedpegscripts", fedpeg_entries);
|
||||
obj.pushKV("pegged_asset", consensus.pegged_asset.GetHex());
|
||||
obj.pushKV("min_peg_diff", consensus.parentChainPowLimit.GetHex());
|
||||
obj.pushKV("parent_blockhash", parent_blockhash.GetHex());
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "rawissueasset", 1, "issuances" },
|
||||
{ "rawreissueasset", 1, "reissuances" },
|
||||
{ "getnewblockhex", 0, "min_tx_age" },
|
||||
{ "getnewblockhex", 1, "proposed_parameters" },
|
||||
{ "testproposedblock", 1, "acceptnonstd" },
|
||||
{ "issueasset", 0, "assetamount" },
|
||||
{ "issueasset", 1, "tokenamount" },
|
||||
|
|
|
|||
|
|
@ -141,6 +141,14 @@ UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGen
|
|||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill out block witness if dynamic federation is enabled
|
||||
// since we are assuming WSH(OP_TRUE)
|
||||
if (!pblock->m_dynafed_params.IsNull()) {
|
||||
CScript op_true(OP_TRUE);
|
||||
pblock->m_signblock_witness.stack.push_back(std::vector<unsigned char>(op_true.begin(), op_true.end()));
|
||||
}
|
||||
|
||||
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
|
||||
if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
|
||||
|
|
@ -989,15 +997,27 @@ static UniValue estimaterawfee(const JSONRPCRequest& request)
|
|||
|
||||
UniValue getnewblockhex(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() > 1)
|
||||
if (request.fHelp || request.params.size() > 2)
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"getnewblockhex",
|
||||
"\nGets hex representation of a proposed, unmined new block\n",
|
||||
{
|
||||
{"min_tx_age", RPCArg::Type::NUM, /* default */ "0", "How many seconds a transaction must have been in the mempool to be inluded in the block proposal. This may help with faster block convergence among functionaries using compact blocks."},
|
||||
{"proposed_parameters", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED , "Parameters to be used in dynamic federations blocks as proposals. During a period of `-dynamic_epoch_length` blocks, 4/5 of total blocks must signal these parameters for the proposal to become activated in the next epoch.",
|
||||
{
|
||||
{"signblockscript", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex-encoded block signing script to propose"},
|
||||
{"max_block_witness", RPCArg::Type::NUM, RPCArg::Optional::NO, "Total size in witness bytes that are allowed in the dynamic federations block witness for blocksigning"},
|
||||
{"fedpegscript", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex-encoded fedpegscript for dynamic block proposal. This is interpreted as a v0 segwit witnessScript, and fills out the fedpeg_program as such."},
|
||||
{"extension_space", RPCArg::Type::ARR, RPCArg::Optional::NO, "Array of additional fields to embed in the dynamic blockheader. Has no consensus meaning aside from serialized size changes. This space is currently is only used for PAK enforcement.",
|
||||
{
|
||||
{"", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Hex encoded string for extension entries."},
|
||||
},
|
||||
},
|
||||
},
|
||||
"proposed_parameters"},
|
||||
},
|
||||
RPCResult{
|
||||
"blockhex (hex) The block hex\n"
|
||||
"blockhex (hex) The block hex\n"
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getnewblockhex", ""),
|
||||
|
|
@ -1009,9 +1029,49 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
|
|||
throw JSONRPCError(RPC_INVALID_PARAMETER, "min_tx_age must be non-negative.");
|
||||
}
|
||||
|
||||
// Construct proposed parameter entry, if any
|
||||
DynaFedParamEntry proposed;
|
||||
if (!request.params[1].isNull()) {
|
||||
if (!IsDynaFedEnabled(chainActive.Tip(), Params().GetConsensus())) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Dynamic federations is not active on this network. Proposed parameters are not needed.");
|
||||
}
|
||||
|
||||
UniValue prop = request.params[1].get_obj();
|
||||
|
||||
std::string sbs_str = prop["signblockscript"].get_str();
|
||||
if (!IsHex(sbs_str)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "signblockscript must be hex");
|
||||
}
|
||||
std::vector<unsigned char> signblock_bytes = ParseHex(sbs_str);
|
||||
proposed.m_signblockscript = CScript(signblock_bytes.begin(), signblock_bytes.end());
|
||||
|
||||
int max_sbs_wit = prop["max_block_witness"].get_int();
|
||||
if (max_sbs_wit < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "max_block_witness must be non-negative");
|
||||
}
|
||||
proposed.m_signblock_witness_limit = max_sbs_wit;
|
||||
|
||||
std::string fps_str = prop["fedpegscript"].get_str();
|
||||
if (!IsHex(fps_str)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "fedpegscript must be hex");
|
||||
}
|
||||
std::vector<unsigned char> fedpeg_bytes = ParseHex(fps_str);
|
||||
proposed.m_fedpegscript = CScript(fedpeg_bytes.begin(), fedpeg_bytes.end());
|
||||
// Compute the P2WSH scriptPubKey of this fedpegscript
|
||||
proposed.m_fedpeg_program = GetScriptForDestination(WitnessV0ScriptHash(proposed.m_fedpegscript));
|
||||
|
||||
UniValue extension_array = prop["extension_space"].get_array();
|
||||
for (unsigned int i = 0; i < extension_array.size(); i++) {
|
||||
std::string extension_str = extension_array[i].get_str();
|
||||
proposed.m_extension_space.push_back(ParseHex(extension_str));
|
||||
}
|
||||
// All proposals are full serializations
|
||||
proposed.m_serialize_type = 2;
|
||||
}
|
||||
|
||||
CScript feeDestinationScript = Params().GetConsensus().mandatory_coinbase_destination;
|
||||
if (feeDestinationScript == CScript()) feeDestinationScript = CScript() << OP_TRUE;
|
||||
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(feeDestinationScript, required_wait));
|
||||
std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(feeDestinationScript, required_wait, &proposed));
|
||||
if (!pblocktemplate.get()) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet keypool empty");
|
||||
}
|
||||
|
|
@ -1023,6 +1083,13 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
|
|||
IncrementExtraNonce(&pblocktemplate->block, chainActive.Tip(), nExtraNonce);
|
||||
}
|
||||
|
||||
// If WSH(OP_TRUE) block, fill in witness
|
||||
CScript op_true(OP_TRUE);
|
||||
if (pblocktemplate->block.m_dynafed_params.m_current.m_signblockscript ==
|
||||
GetScriptForDestination(WitnessV0ScriptHash(op_true))) {
|
||||
pblocktemplate->block.m_signblock_witness.stack.push_back(std::vector<unsigned char>(op_true.begin(), op_true.end()));
|
||||
}
|
||||
|
||||
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssBlock << pblocktemplate->block;
|
||||
return HexStr(ssBlock.begin(), ssBlock.end());
|
||||
|
|
@ -1030,7 +1097,7 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
|
|||
|
||||
UniValue combineblocksigs(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() != 2)
|
||||
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"combineblocksigs",
|
||||
"\nMerges signatures on a block proposal\n",
|
||||
|
|
@ -1046,6 +1113,7 @@ UniValue combineblocksigs(const JSONRPCRequest& request)
|
|||
},
|
||||
},
|
||||
},
|
||||
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded witnessScript for the signblockscript"},
|
||||
},
|
||||
RPCResult{
|
||||
"{\n"
|
||||
|
|
@ -1087,9 +1155,22 @@ UniValue combineblocksigs(const JSONRPCRequest& request)
|
|||
sig_data.signatures[pubkey.GetID()] = std::make_pair(pubkey, sig_bytes);
|
||||
}
|
||||
|
||||
// Finalizes the signatures, has no access to keys
|
||||
ProduceSignature(keystore, signature_creator, block.proof.challenge, sig_data, SCRIPT_NO_SIGHASH_BYTE);
|
||||
block.proof.solution = sig_data.scriptSig;
|
||||
if (!block.m_dynafed_params.IsNull()) {
|
||||
if (request.params[2].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Signing dynamic blocks requires the witnessScript argument");
|
||||
}
|
||||
std::vector<unsigned char> witness_bytes(ParseHex(request.params[2].get_str()));
|
||||
if (!witness_bytes.empty()) {
|
||||
keystore.AddCScript(CScript(witness_bytes.begin(), witness_bytes.end()));
|
||||
}
|
||||
// Finalizes the signatures, has no access to keys
|
||||
ProduceSignature(keystore, signature_creator, block.m_dynafed_params.m_current.m_signblockscript, sig_data, SCRIPT_NO_SIGHASH_BYTE);
|
||||
block.m_signblock_witness = sig_data.scriptWitness;
|
||||
} else {
|
||||
// Finalizes the signatures, has no access to keys
|
||||
ProduceSignature(keystore, signature_creator, block.proof.challenge, sig_data, SCRIPT_NO_SIGHASH_BYTE);
|
||||
block.proof.solution = sig_data.scriptSig;
|
||||
}
|
||||
|
||||
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
|
||||
ssBlock << block;
|
||||
|
|
@ -1350,25 +1431,6 @@ UniValue testproposedblock(const JSONRPCRequest& request)
|
|||
const CChainParams& chainparams = Params();
|
||||
const bool acceptnonstd = !request.params[1].isNull() ? request.params[1].get_bool() : gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
|
||||
if (!acceptnonstd) {
|
||||
|
||||
// Get PAK commitment, if any
|
||||
boost::optional<CPAKList> paklist_block = GetPAKKeysFromCommitment(*block.vtx[0]);
|
||||
|
||||
// Possible PAK commitment mismatch between blocks and config
|
||||
if (chainparams.GetEnforcePak() && g_paklist_config) {
|
||||
if(paklist_block) {
|
||||
if (*paklist_block != *g_paklist_config) {
|
||||
throw JSONRPCError(RPC_VERIFY_ERROR, "Proposal PAK commitment and config PAK do not match.");
|
||||
}
|
||||
// else it may be an unnecessary commitment but that's ok.
|
||||
} else {
|
||||
// Waiting for block that has commitment to config list
|
||||
if (*g_paklist_config != g_paklist_blockchain) {
|
||||
throw JSONRPCError(RPC_VERIFY_ERROR, "Proposal does not have required PAK commitment.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& transaction : block.vtx) {
|
||||
if (transaction->IsCoinBase()) continue;
|
||||
std::string reason;
|
||||
|
|
@ -1395,7 +1457,7 @@ static const CRPCCommand commands[] =
|
|||
{ "mining", "getblocktemplate", &getblocktemplate, {"template_request"} },
|
||||
{ "generating", "combineblocksigs", &combineblocksigs, {"blockhex","signatures"} },
|
||||
{ "mining", "submitheader", &submitheader, {"hexdata"} },
|
||||
{ "generating", "getnewblockhex", &getnewblockhex, {"min_tx_age"} },
|
||||
{ "generating", "getnewblockhex", &getnewblockhex, {"min_tx_age", "proposed_parameters"} },
|
||||
{ "generating", "getcompactsketch", &getcompactsketch, {"block_hex"} },
|
||||
{ "generating", "consumecompactsketch", &consumecompactsketch, {"sketch"} },
|
||||
{ "generating", "consumegetblocktxn", &consumegetblocktxn, {"full_block", "block_tx_req"} },
|
||||
|
|
|
|||
|
|
@ -667,8 +667,7 @@ UniValue FormatPAKList(CPAKList &paklist) {
|
|||
UniValue paklist_value(UniValue::VOBJ);
|
||||
std::vector<std::vector<unsigned char> > offline_keys;
|
||||
std::vector<std::vector<unsigned char> > online_keys;
|
||||
bool is_reject;
|
||||
paklist.ToBytes(offline_keys, online_keys, is_reject);
|
||||
paklist.ToBytes(offline_keys, online_keys);
|
||||
|
||||
UniValue retOnline(UniValue::VARR);
|
||||
UniValue retOffline(UniValue::VARR);
|
||||
|
|
@ -679,7 +678,7 @@ UniValue FormatPAKList(CPAKList &paklist) {
|
|||
}
|
||||
paklist_value.pushKV("online", retOnline);
|
||||
paklist_value.pushKV("offline", retOffline);
|
||||
paklist_value.pushKV("reject", is_reject);
|
||||
paklist_value.pushKV("reject", retOffline.empty());
|
||||
return paklist_value;
|
||||
}
|
||||
|
||||
|
|
@ -688,12 +687,11 @@ UniValue getpakinfo(const JSONRPCRequest& request)
|
|||
if (request.fHelp || request.params.size() != 0)
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"getpakinfo",
|
||||
"\nReturns relevant pegout authorization key (PAK) information about this node, both from command line arguments and blockchain data.\n",
|
||||
"\nReturns relevant pegout authorization key (PAK) information about this node, both from blockchain data.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
"{\n"
|
||||
"\"config_paklist\" (array) The PAK list loaded from beta.conf at startup\n"
|
||||
"\"block_paklist\" (array) The PAK list loaded from latest block commitment\n"
|
||||
"\"block_paklist\" (array) The PAK list loaded from latest epoch\n"
|
||||
"}\n"
|
||||
},
|
||||
RPCExamples{""},
|
||||
|
|
@ -701,13 +699,9 @@ UniValue getpakinfo(const JSONRPCRequest& request)
|
|||
|
||||
LOCK(cs_main);
|
||||
|
||||
UniValue paklist_value(UniValue::VOBJ);
|
||||
if (g_paklist_config) {
|
||||
paklist_value = FormatPAKList(*g_paklist_config);
|
||||
}
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.pushKV("config_paklist", paklist_value);
|
||||
ret.pushKV("block_paklist", FormatPAKList(g_paklist_blockchain));
|
||||
CPAKList paklist = GetActivePAKList(chainActive.Tip(), Params().GetConsensus());
|
||||
ret.pushKV("block_paklist", FormatPAKList(paklist));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -754,7 +754,7 @@ static UniValue decodescript(const JSONRPCRequest& request)
|
|||
r.pushKV("p2sh", EncodeDestination(ScriptHash(CScriptID(script))));
|
||||
// P2SH and witness programs cannot be wrapped in P2WSH, if this script
|
||||
// is a witness program, don't return addresses for a segwit programs.
|
||||
if (type.get_str() == "pubkey" || type.get_str() == "pubkeyhash" || type.get_str() == "multisig" || type.get_str() == "nonstandard") {
|
||||
if (type.get_str() == "pubkey" || type.get_str() == "pubkeyhash" || type.get_str() == "multisig" || type.get_str() == "nonstandard" || type.get_str() == "true") {
|
||||
std::vector<std::vector<unsigned char>> solutions_data;
|
||||
txnouttype which_type = Solver(script, solutions_data);
|
||||
// Uncompressed pubkeys cannot be used with segwit checksigs.
|
||||
|
|
@ -988,6 +988,8 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
|
|||
// Script verification errors
|
||||
UniValue vErrors(UniValue::VARR);
|
||||
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
|
||||
// ELEMENTS:
|
||||
// Track an immature peg-in that's otherwise valid, give warning
|
||||
bool immature_pegin = false;
|
||||
|
|
@ -1006,12 +1008,12 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
|
|||
if (!txin.m_is_pegin && coin.IsSpent()) {
|
||||
TxInErrorToJSON(txin, inWitness, vErrors, "Input not found or already spent");
|
||||
continue;
|
||||
} else if (txin.m_is_pegin && (txConst.witness.vtxinwit.size() <= i || !IsValidPeginWitness(txConst.witness.vtxinwit[i].m_pegin_witness, txin.prevout, err, false))) {
|
||||
} else if (txin.m_is_pegin && (txConst.witness.vtxinwit.size() <= i || !IsValidPeginWitness(txConst.witness.vtxinwit[i].m_pegin_witness, fedpegscripts, txin.prevout, err, false))) {
|
||||
TxInErrorToJSON(txin, inWitness, vErrors, "Peg-in input has invalid proof.");
|
||||
continue;
|
||||
}
|
||||
// Report warning about immature peg-in though
|
||||
if(txin.m_is_pegin && !IsValidPeginWitness(txConst.witness.vtxinwit[i].m_pegin_witness, txin.prevout, err, true)) {
|
||||
if(txin.m_is_pegin && !IsValidPeginWitness(txConst.witness.vtxinwit[i].m_pegin_witness, fedpegscripts, txin.prevout, err, true)) {
|
||||
assert(err == "Needs more confirmations.");
|
||||
immature_pegin = true;
|
||||
}
|
||||
|
|
@ -2460,6 +2462,8 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
|
|||
"Invalid parameter: one (potentially empty) input asset blind for each input must be provided");
|
||||
}
|
||||
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
|
||||
std::vector<CAmount> input_amounts;
|
||||
std::vector<uint256> input_blinds;
|
||||
std::vector<uint256> input_asset_blinds;
|
||||
|
|
@ -2472,7 +2476,7 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
|
|||
// Special handling for pegin 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, tx.vin[nIn].prevout, err, false)) {
|
||||
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, fedpegscripts, tx.vin[nIn].prevout, err, false)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction contains invalid peg-in input: %s", err));
|
||||
}
|
||||
CTxOut pegin_output = GetPeginOutputFromWitness(tx.witness.vtxinwit[nIn].m_pegin_witness);
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ public:
|
|||
};
|
||||
|
||||
template<typename T>
|
||||
bool GenericVerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const T& data)
|
||||
bool GenericVerifyScript(const CScript& scriptSig, const CScriptWitness& witness, const CScript& scriptPubKey, unsigned int flags, const T& data)
|
||||
{
|
||||
return VerifyScript(scriptSig, scriptPubKey, NULL, flags, SimpleSignatureChecker(SerializeHash(data)));
|
||||
return VerifyScript(scriptSig, scriptPubKey, &witness, flags, SimpleSignatureChecker(SerializeHash(data)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,11 @@ std::string CScriptWitness::ToString() const
|
|||
return ret + ")";
|
||||
}
|
||||
|
||||
uint32_t CScriptWitness::GetSerializedSize() const
|
||||
{
|
||||
return ::GetSerializeSize(stack, 0);
|
||||
}
|
||||
|
||||
bool CScript::HasValidOps() const
|
||||
{
|
||||
CScript::const_iterator it = begin();
|
||||
|
|
|
|||
|
|
@ -603,6 +603,8 @@ struct CScriptWitness
|
|||
void SetNull() { stack.clear(); stack.shrink_to_fit(); }
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
uint32_t GetSerializedSize() const;
|
||||
};
|
||||
|
||||
class CReserveScript
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin, const CScriptWitness& pegin_witness);
|
||||
int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin, const CScriptWitness& pegin_witness, const std::vector<std::pair<CScript, CScript>>& fedpegscripts);
|
||||
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);
|
||||
|
||||
namespace
|
||||
|
|
@ -410,7 +410,8 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
if (!tx.IsCoinBase()) {
|
||||
const COutPoint &out = tx.vin[0].prevout;
|
||||
Coin coin = undo.vprevout[0];
|
||||
ApplyTxInUndo(std::move(coin), *(stack.back()), out, tx.vin[0], CScriptWitness());
|
||||
std::vector<std::pair<CScript, CScript>> fedpegscripts;
|
||||
ApplyTxInUndo(std::move(coin), *(stack.back()), out, tx.vin[0], CScriptWitness(), fedpegscripts);
|
||||
}
|
||||
// Store as a candidate for reconnection
|
||||
disconnected_coins.insert(utxod->first);
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ std::vector<unsigned char> pegin_transaction = ParseHex("020000000101f321df97906
|
|||
|
||||
COutPoint prevout(uint256S("ce9b0ee70f82e48f78e2a2e66e61ee4281df74419c23673cc33b639097df21f3"), 1);
|
||||
|
||||
const std::string fedpegscript_str = "512103dff4923d778550cc13ce0d887d737553b4b58f4e8e886507fc39f5e447b2186451ae";
|
||||
|
||||
// Needed for easier parent PoW check, and setting fedpegscript
|
||||
struct FedpegSetup : public BasicTestingSetup {
|
||||
FedpegSetup() : BasicTestingSetup("custom", "512103dff4923d778550cc13ce0d887d737553b4b58f4e8e886507fc39f5e447b2186451ae") {}
|
||||
FedpegSetup() : BasicTestingSetup("custom", fedpegscript_str) {}
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(pegin_witness_tests, FedpegSetup)
|
||||
|
|
@ -50,7 +52,15 @@ BOOST_AUTO_TEST_CASE(witness_valid)
|
|||
|
||||
std::string err;
|
||||
|
||||
bool valid = IsValidPeginWitness(witness, prevout, err, false);
|
||||
std::vector<unsigned char> fedpegscript_bytes = ParseHex(fedpegscript_str);
|
||||
CScript fedpegscript(fedpegscript_bytes.begin(), fedpegscript_bytes.end());
|
||||
// Test sample was generated as "legacy" with p2sh-p2wsh fedpegscript
|
||||
CScript fedpeg_program(GetScriptForDestination(ScriptHash(GetScriptForDestination(WitnessV0ScriptHash(fedpegscript)))));
|
||||
std::vector<std::pair<CScript, CScript>> fedpegscripts;
|
||||
// TODO test with additional scripts
|
||||
fedpegscripts.push_back(std::make_pair(fedpeg_program, fedpegscript));
|
||||
|
||||
bool valid = IsValidPeginWitness(witness, fedpegscripts, prevout, err, false);
|
||||
BOOST_CHECK(err == "");
|
||||
BOOST_CHECK(valid);
|
||||
|
||||
|
|
@ -58,32 +68,32 @@ BOOST_AUTO_TEST_CASE(witness_valid)
|
|||
// This will break deserialization and other data-matching checks
|
||||
for (unsigned int i = 0; i < witness.stack.size(); i++) {
|
||||
witness.stack[i].pop_back();
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, prevout, err, false));
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, fedpegscripts, prevout, err, false));
|
||||
witness.stack = witness_stack;
|
||||
BOOST_CHECK(IsValidPeginWitness(witness, prevout, err, false));
|
||||
BOOST_CHECK(IsValidPeginWitness(witness, fedpegscripts, prevout, err, false));
|
||||
}
|
||||
|
||||
// Test mismatched but valid nOut to proof
|
||||
COutPoint fake_prevout = prevout;
|
||||
fake_prevout.n = 0;
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, fake_prevout, err, false));
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, fedpegscripts, fake_prevout, err, false));
|
||||
|
||||
// Test mistmatched but valid txid
|
||||
fake_prevout = prevout;
|
||||
fake_prevout.hash = uint256S("2f103ee04a5649eecb932b4da4ca9977f53a12bbe04d9d1eb5ccc0f4a06334");
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, fake_prevout, err, false));
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, fedpegscripts, fake_prevout, err, false));
|
||||
|
||||
// Ensure that all witness stack sizes are handled
|
||||
BOOST_CHECK(IsValidPeginWitness(witness, prevout, err, false));
|
||||
BOOST_CHECK(IsValidPeginWitness(witness, fedpegscripts, prevout, err, false));
|
||||
for (unsigned int i = 0; i < witness.stack.size(); i++) {
|
||||
witness.stack.pop_back();
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, prevout, err, false));
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, fedpegscripts, prevout, err, false));
|
||||
}
|
||||
witness.stack = witness_stack;
|
||||
|
||||
// Extra element causes failure
|
||||
witness.stack.push_back(witness.stack.back());
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, prevout, err, false));
|
||||
BOOST_CHECK(!IsValidPeginWitness(witness, fedpegscripts, prevout, err, false));
|
||||
witness.stack = witness_stack;
|
||||
|
||||
// Check validation of peg-in transaction's inputs and balance
|
||||
|
|
@ -103,7 +113,7 @@ BOOST_AUTO_TEST_CASE(witness_valid)
|
|||
BOOST_CHECK(tx.vin[0].m_is_pegin);
|
||||
// Check that serialization doesn't cause issuance to become non-null
|
||||
BOOST_CHECK(tx.vin[0].assetIssuance.IsNull());
|
||||
BOOST_CHECK(IsValidPeginWitness(tx.witness.vtxinwit[0].m_pegin_witness, prevout, err, false));
|
||||
BOOST_CHECK(IsValidPeginWitness(tx.witness.vtxinwit[0].m_pegin_witness, fedpegscripts, prevout, err, false));
|
||||
|
||||
CAmountMap fee_map;
|
||||
|
||||
|
|
@ -111,7 +121,9 @@ BOOST_AUTO_TEST_CASE(witness_valid)
|
|||
CValidationState state;
|
||||
CCoinsView coinsDummy;
|
||||
CCoinsViewCache coins(&coinsDummy);
|
||||
BOOST_CHECK(Consensus::CheckTxInputs(tx, state, coins, 0, fee_map, setPeginsSpent, NULL, false, true));
|
||||
// Get the latest block index to look up fedpegscripts
|
||||
// For these tests, should be genesis-block-hardcoded consensus.fedpegscript
|
||||
BOOST_CHECK(Consensus::CheckTxInputs(tx, state, coins, 0, fee_map, setPeginsSpent, NULL, false, true, fedpegscripts));
|
||||
BOOST_CHECK(setPeginsSpent.size() == 1);
|
||||
setPeginsSpent.clear();
|
||||
|
||||
|
|
@ -119,7 +131,7 @@ BOOST_AUTO_TEST_CASE(witness_valid)
|
|||
CMutableTransaction mtxn(tx);
|
||||
mtxn.witness.vtxinwit[0].m_pegin_witness.SetNull();
|
||||
CTransaction tx2(mtxn);
|
||||
BOOST_CHECK(!Consensus::CheckTxInputs(tx2, state, coins, 0, fee_map, setPeginsSpent, NULL, false, true));
|
||||
BOOST_CHECK(!Consensus::CheckTxInputs(tx2, state, coins, 0, fee_map, setPeginsSpent, NULL, false, true, fedpegscripts));
|
||||
BOOST_CHECK(setPeginsSpent.empty());
|
||||
|
||||
// Invalidate peg-in (and spending) authorization by pegin marker.
|
||||
|
|
@ -128,7 +140,7 @@ BOOST_AUTO_TEST_CASE(witness_valid)
|
|||
CMutableTransaction mtxn2(tx);
|
||||
mtxn2.vin[0].m_is_pegin = false;
|
||||
CTransaction tx3(mtxn2);
|
||||
BOOST_CHECK(!Consensus::CheckTxInputs(tx3, state, coins, 0, fee_map, setPeginsSpent, NULL, false, true));
|
||||
BOOST_CHECK(!Consensus::CheckTxInputs(tx3, state, coins, 0, fee_map, setPeginsSpent, NULL, false, true, fedpegscripts));
|
||||
BOOST_CHECK(setPeginsSpent.empty());
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -318,12 +318,14 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams,
|
|||
pindexNew->proof = diskindex.proof;
|
||||
pindexNew->nStatus = diskindex.nStatus;
|
||||
pindexNew->nTx = diskindex.nTx;
|
||||
pindexNew->dynafed_params = diskindex.dynafed_params;
|
||||
pindexNew->m_signblock_witness = diskindex.m_signblock_witness;
|
||||
|
||||
const uint256 block_hash = pindexNew->GetBlockHash();
|
||||
// Only validate one of every 1000 block header for sanity check
|
||||
if (pindexNew->nHeight % 1000 == 0 &&
|
||||
!CheckProof(pindexNew->GetBlockHeader(), consensusParams) &&
|
||||
block_hash != consensusParams.hashGenesisBlock) {
|
||||
block_hash != consensusParams.hashGenesisBlock &&
|
||||
!CheckProof(pindexNew->GetBlockHeader(), consensusParams)) {
|
||||
return error("%s: CheckProof: %s, %s", __func__, block_hash.ToString(), pindexNew->ToString());
|
||||
}
|
||||
pcursor->Next();
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <util/moneystr.h>
|
||||
#include <util/time.h>
|
||||
#include <chainparams.h> // removeForBlock paklist transition
|
||||
#include <pegins.h>
|
||||
|
||||
CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
|
||||
int64_t _nTime, unsigned int _entryHeight,
|
||||
|
|
@ -520,6 +521,24 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
|
|||
if (!validLP) {
|
||||
mapTx.modify(it, update_lock_points(lp));
|
||||
}
|
||||
|
||||
// On re-org, remove *all* peg-in and PAK-based peg-outs due to possible
|
||||
// invalidity from dynafed transitions
|
||||
// TODO: Only boot out now-invalid transactions. Re-orgs are very rare in
|
||||
// federated systems but can occasionally happen due to consensus algorithm.
|
||||
|
||||
// Little hack to quickly check if any outputs are PAK ones
|
||||
// by sending in empty(reject) list.
|
||||
if (!IsPAKValidTx(tx, CPAKList(), Params().ParentGenesisBlockHash(), Params().GetConsensus().pegged_asset)) {
|
||||
txToRemove.insert(it);
|
||||
continue;
|
||||
}
|
||||
for (const auto& input : tx.vin) {
|
||||
if (input.m_is_pegin) {
|
||||
txToRemove.insert(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setEntries setAllRemoves;
|
||||
for (txiter it : txToRemove) {
|
||||
|
|
@ -548,7 +567,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
|
|||
/**
|
||||
* Called when a block is connected. Removes from mempool and updates the miner fee estimator.
|
||||
*/
|
||||
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight, bool pak_transition)
|
||||
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight, const CBlockIndex* p_block_index_new)
|
||||
{
|
||||
LOCK(cs);
|
||||
std::vector<const CTxMemPoolEntry*> entries;
|
||||
|
|
@ -574,28 +593,40 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
|
|||
ClearPrioritisation(tx->GetHash());
|
||||
}
|
||||
|
||||
// Eject any newly-invalid peg-outs based on changing block commitment
|
||||
const CChainParams& chainparams = Params();
|
||||
if (pak_transition && chainparams.GetEnforcePak()) {
|
||||
std::vector<CTransactionRef> tx_to_remove;
|
||||
for (const auto& entry : mapTx) {
|
||||
for (const auto& out : entry.GetTx().vout) {
|
||||
if (out.scriptPubKey.IsPegoutScript(Params().ParentGenesisBlockHash()) &&
|
||||
!ScriptHasValidPAKProof(out.scriptPubKey, Params().ParentGenesisBlockHash())) {
|
||||
const uint256 tx_id = entry.GetTx().GetHash();
|
||||
txiter it = mapTx.find(tx_id);
|
||||
const CTransaction& tx = it->GetTx();
|
||||
// Eject transactions that are invalid for *following* block due to transition
|
||||
// We check every epoch_length blocks due to peg-ins expiring an epoch after
|
||||
// being changed
|
||||
if (p_block_index_new) {
|
||||
const CChainParams& chainparams = Params();
|
||||
uint32_t epoch_length = chainparams.GetConsensus().dynamic_epoch_length;
|
||||
if ((p_block_index_new->nHeight+1) % epoch_length == 0) {
|
||||
CPAKList enforced_paklist = GetActivePAKList(p_block_index_new, chainparams.GetConsensus());
|
||||
std::vector<CTransactionRef> tx_to_remove;
|
||||
for (const auto& entry : mapTx) {
|
||||
const CTransaction& tx = entry.GetTx();
|
||||
if (chainparams.GetEnforcePak() && !IsPAKValidTx(tx, enforced_paklist, chainparams.ParentGenesisBlockHash(), chainparams.GetConsensus().pegged_asset)) {
|
||||
tx_to_remove.push_back(MakeTransactionRef(tx));
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(p_block_index_new, chainparams.GetConsensus(), true /* nextblock_validation */);
|
||||
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
|
||||
const CTxIn& in = tx.vin[nIn];
|
||||
std::string err;
|
||||
if (in.m_is_pegin && (!tx.HasWitness() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, fedpegscripts, in.prevout, err, true /* check_depth */))) {
|
||||
tx_to_remove.push_back(MakeTransactionRef(tx));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& tx : tx_to_remove) {
|
||||
const uint256 tx_id = tx->GetHash();
|
||||
removeRecursive(*tx, MemPoolRemovalReason::BLOCK);
|
||||
ClearPrioritisation(tx_id);
|
||||
for (auto& tx : tx_to_remove) {
|
||||
const uint256 tx_id = tx->GetHash();
|
||||
removeRecursive(*tx, MemPoolRemovalReason::BLOCK);
|
||||
ClearPrioritisation(tx_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastRollingFeeUpdate = GetTime();
|
||||
blockSinceLastRollingFeeBump = true;
|
||||
}
|
||||
|
|
@ -625,7 +656,8 @@ static void CheckInputsAndUpdateCoins(const CTxMemPoolEntry& entry, CCoinsViewCa
|
|||
CValidationState state;
|
||||
CAmountMap fee_map;
|
||||
std::set<std::pair<uint256, COutPoint> > setPeginsSpent;
|
||||
bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, spendheight, fee_map, setPeginsSpent, NULL, false, true);
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, spendheight, fee_map, setPeginsSpent, NULL, false, true, fedpegscripts);
|
||||
assert(fCheckResult);
|
||||
UpdateCoins(tx, mempoolDuplicate, 1000000);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <indirectmap.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <primitives/pak.h>
|
||||
#include <sync.h>
|
||||
#include <random.h>
|
||||
|
||||
|
|
@ -588,7 +589,7 @@ public:
|
|||
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
void removeConflicts(const CTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight,
|
||||
bool pak_transition=false);
|
||||
const CBlockIndex* p_block_index_new = nullptr);
|
||||
|
||||
void clear();
|
||||
void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
// ELEMENTS
|
||||
#include <block_proof.h> // CheckChallenge, CheckProof
|
||||
#include <dynafed.h>
|
||||
|
||||
#include <future>
|
||||
#include <sstream>
|
||||
|
|
@ -608,6 +609,13 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
if (fRequireStandard && !IsStandardTx(tx, reason))
|
||||
return state.DoS(0, false, REJECT_NONSTANDARD, reason);
|
||||
|
||||
// And now do PAK checks. Filtered by next blocks' enforced list
|
||||
if (chainparams.GetEnforcePak()) {
|
||||
if (!IsPAKValidTx(tx, GetActivePAKList(chainActive.Tip(), chainparams.GetConsensus()), chainparams.ParentGenesisBlockHash(), chainparams.GetConsensus().pegged_asset)) {
|
||||
return state.DoS(0, false, REJECT_NONSTANDARD, "invalid-pegout-proof");
|
||||
}
|
||||
}
|
||||
|
||||
// Do not work on transactions that are too small.
|
||||
// A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes.
|
||||
// Transactions smaller than this are not relayed to reduce unnecessary malloc overhead.
|
||||
|
|
@ -691,6 +699,9 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
}
|
||||
}
|
||||
|
||||
// Used when checking peg-ins
|
||||
std::vector<std::pair<CScript, CScript>> fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), chainparams.GetConsensus(), true /* nextblock_validation */);
|
||||
|
||||
// do all inputs exist?
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||
const CTxIn& txin = tx.vin[i];
|
||||
|
|
@ -703,7 +714,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
// Peg-in witness is required, check here without validating existence in parent chain
|
||||
std::string err_msg = "no peg-in witness attached";
|
||||
if (tx.witness.vtxinwit.size() != tx.vin.size() ||
|
||||
!IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, tx.vin[i].prevout, err_msg, false)) {
|
||||
!IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, fedpegscripts, tx.vin[i].prevout, err_msg, false)) {
|
||||
return state.Invalid(false, REJECT_INVALID, "pegin-no-witness", err_msg);
|
||||
}
|
||||
|
||||
|
|
@ -749,7 +760,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final");
|
||||
|
||||
CAmountMap fee_map;
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), fee_map, setPeginsSpent, NULL, true, true)) {
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), fee_map, setPeginsSpent, NULL, true, true, fedpegscripts)) {
|
||||
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
|
|
@ -1148,8 +1159,8 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus:
|
|||
|
||||
// Check the header
|
||||
const uint256 block_hash = block.GetHash();
|
||||
if (!CheckProof(block, consensusParams) &&
|
||||
block_hash != consensusParams.hashGenesisBlock) {
|
||||
if (block_hash != consensusParams.hashGenesisBlock &&
|
||||
!CheckProof(block, consensusParams)) {
|
||||
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
|
||||
}
|
||||
|
||||
|
|
@ -1623,7 +1634,7 @@ static bool AbortNode(CValidationState& state, const std::string& strMessage, co
|
|||
* @param out The out point that corresponds to the tx input.
|
||||
* @return A DisconnectResult as an int
|
||||
*/
|
||||
int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin, const CScriptWitness& pegin_witness)
|
||||
int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin, const CScriptWitness& pegin_witness, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
|
||||
{
|
||||
bool fClean = true;
|
||||
|
||||
|
|
@ -1655,7 +1666,7 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, cons
|
|||
view.AddCoin(out, std::move(undo), !fClean);
|
||||
} else {
|
||||
std::string err;
|
||||
if (!IsValidPeginWitness(pegin_witness, txin.prevout, err, false)) {
|
||||
if (!IsValidPeginWitness(pegin_witness, fedpegscripts, txin.prevout, err, false)) {
|
||||
fClean = fClean && error("%s: peg-in occurred without proof", __func__);
|
||||
} else {
|
||||
std::pair<uint256, COutPoint> outpoint = std::make_pair(uint256(pegin_witness.stack[2]), txin.prevout);
|
||||
|
|
@ -1717,6 +1728,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI
|
|||
}
|
||||
|
||||
// restore inputs
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(pindex, Params().GetConsensus(), false /* nextblock_validation */);
|
||||
if (i > 0) { // not coinbases
|
||||
CTxUndo &txundo = blockUndo.vtxundo[i-1];
|
||||
if (txundo.vprevout.size() != tx.vin.size()) {
|
||||
|
|
@ -1726,7 +1738,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI
|
|||
for (unsigned int j = tx.vin.size(); j-- > 0;) {
|
||||
const COutPoint &out = tx.vin[j].prevout;
|
||||
const CScriptWitness& pegin_wit = tx.witness.vtxinwit.size() > j ? tx.witness.vtxinwit[j].m_pegin_witness : CScriptWitness();
|
||||
int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out, tx.vin[j], pegin_wit);
|
||||
int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out, tx.vin[j], pegin_wit, fedpegscripts);
|
||||
if (res == DISCONNECT_FAILED) return DISCONNECT_FAILED;
|
||||
fClean = fClean && res != DISCONNECT_UNCLEAN;
|
||||
}
|
||||
|
|
@ -2117,9 +2129,24 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
|
|||
txdata.reserve(block.vtx.size()); // Required so that pointers to individual PrecomputedTransactionData don't get invalidated
|
||||
|
||||
// ELEMENTS:
|
||||
|
||||
// Enforce PAK post-dynafed
|
||||
if (chainparams.GetEnforcePak() && !block.m_dynafed_params.IsNull()) {
|
||||
// GetActivePAKList computes for the following block, so use previous index
|
||||
CPAKList paklist = GetActivePAKList(pindex->pprev, chainparams.GetConsensus());
|
||||
for (const auto& tx : block.vtx) {
|
||||
if (!IsPAKValidTx(*tx, paklist, chainparams.ParentGenesisBlockHash(), chainparams.GetConsensus().pegged_asset)) {
|
||||
return state.DoS(100, error("ConnectBlock(): Bad PAK transaction"), REJECT_INVALID, "bad-pak-tx");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used when ConnectBlock() results are unneeded for mempool ejection
|
||||
std::set<std::pair<uint256, COutPoint>> setPeginsSpentDummy;
|
||||
|
||||
// Used when checking peg-ins
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(pindex, chainparams.GetConsensus(), false /* nextblock_validation */);
|
||||
|
||||
for (unsigned int i = 0; i < block.vtx.size(); i++)
|
||||
{
|
||||
const CTransaction &tx = *(block.vtx[i]);
|
||||
|
|
@ -2132,7 +2159,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
|
|||
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, fee_map,
|
||||
setPeginsSpent == NULL ? setPeginsSpentDummy : *setPeginsSpent,
|
||||
nScriptCheckThreads ? &vChecks : NULL, fCacheResults, fScriptChecks)) {
|
||||
nScriptCheckThreads ? &vChecks : NULL, fCacheResults, fScriptChecks, fedpegscripts)) {
|
||||
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
|
||||
}
|
||||
control.Add(vChecks);
|
||||
|
|
@ -2628,24 +2655,12 @@ bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainp
|
|||
if (!FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED))
|
||||
return false;
|
||||
|
||||
// Get PAK commitment from coinbase, if it exists
|
||||
boost::optional<CPAKList> paklist = GetPAKKeysFromCommitment(*blockConnecting.vtx[0]);
|
||||
if (paklist) {
|
||||
std::vector<std::vector<unsigned char> > offline_keys;
|
||||
std::vector<std::vector<unsigned char> > online_keys;
|
||||
bool is_reject;
|
||||
paklist->ToBytes(offline_keys, online_keys, is_reject);
|
||||
pblocktree->WritePAKList(offline_keys, online_keys, is_reject);
|
||||
g_paklist_blockchain = *paklist;
|
||||
}
|
||||
|
||||
int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
|
||||
LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal);
|
||||
// Remove conflicting transactions from the mempool.;
|
||||
// ELEMENTS: We also eject now-invalid peg-outs based on block transition if not config list set
|
||||
// If config is set, this means all peg-outs have been filtered for that list already and other
|
||||
// functionaries aren't matching your list. Operator should restart with no list or new matching list.
|
||||
mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight, (paklist && !g_paklist_config));
|
||||
// ELEMENTS: We also eject peg-outs with now-invalid PAK proofs
|
||||
// as well as peg-in inputs during transitional periods.
|
||||
mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight, pindexNew);
|
||||
disconnectpool.removeForBlock(blockConnecting.vtx);
|
||||
// Update chainActive & related variables.
|
||||
chainActive.SetTip(pindexNew);
|
||||
|
|
@ -3371,6 +3386,12 @@ bool IsNullDummyEnabled(const CBlockIndex* pindexPrev, const Consensus::Params&
|
|||
return (VersionBitsState(pindexPrev, params, Consensus::DEPLOYMENT_SEGWIT, versionbitscache) == ThresholdState::ACTIVE);
|
||||
}
|
||||
|
||||
bool IsDynaFedEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
return (VersionBitsState(pindexPrev, params, Consensus::DEPLOYMENT_DYNA_FED, versionbitscache) == ThresholdState::ACTIVE);
|
||||
}
|
||||
|
||||
// Compute at which vout of the block's coinbase transaction the witness
|
||||
// commitment occurs, or -1 if not found.
|
||||
static int GetWitnessCommitmentIndex(const CBlock& block)
|
||||
|
|
@ -3442,74 +3463,84 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc
|
|||
}
|
||||
|
||||
// ELEMENTS
|
||||
boost::optional<CPAKList> GetPAKKeysFromCommitment(const CTransaction& coinbase)
|
||||
|
||||
|
||||
static bool ContextualCheckDynaFedHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& params, const CBlockIndex* pindexPrev)
|
||||
{
|
||||
std::vector<std::vector<unsigned char> > offline_keys;
|
||||
std::vector<std::vector<unsigned char> > online_keys;
|
||||
bool is_reject = false;
|
||||
for (unsigned int i = 0; i < coinbase.vout.size(); i++) {
|
||||
const CScript& scriptPubKey = coinbase.vout[i].scriptPubKey;
|
||||
|
||||
// OP + push + 4 bytes + push + 33 bytes + push + 33 bytes
|
||||
// or
|
||||
// OP + push + 4 bytes + push + 6 bytes (REJECT)
|
||||
|
||||
CScript::const_iterator pc = scriptPubKey.begin();
|
||||
std::vector<unsigned char> data;
|
||||
opcodetype opcode;
|
||||
|
||||
if (!scriptPubKey.GetOp(pc, opcode, data) || opcode != OP_RETURN){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!scriptPubKey.GetOp(pc, opcode, data) || data.size() != 4 ||
|
||||
data[0] != 0xab || data[1] != 0x22 || data[2] != 0xaa || data[3] != 0xee) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!scriptPubKey.GetOp(pc, opcode, data)){
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for pak list reject signal
|
||||
// Returns an empty list regardless of other commitments
|
||||
if (data.size() == 6 && data[0] == 'R' && data[1] == 'E' && data[2] == 'J' && data[3] == 'E' && data[4] == 'C' && data[5] == 'T') {
|
||||
is_reject = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for offline key
|
||||
if (data.size() != 33) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for online key
|
||||
std::vector<unsigned char> data_online;
|
||||
if (!scriptPubKey.GetOp(pc, opcode, data_online) || data_online.size() != 33) {
|
||||
continue;
|
||||
}
|
||||
|
||||
offline_keys.push_back(data);
|
||||
online_keys.push_back(data_online);
|
||||
// When not active, it's a NOP
|
||||
if (!IsDynaFedEnabled(pindexPrev, params.GetConsensus())) {
|
||||
return true;
|
||||
}
|
||||
if (is_reject) {
|
||||
offline_keys.clear();
|
||||
online_keys.clear();
|
||||
|
||||
const DynaFedParams& dynafed_params = block.m_dynafed_params;
|
||||
|
||||
// Dynamic blocks must at least publish current signblockscript in full
|
||||
if (dynafed_params.m_current.IsNull()) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "dynamic block headers must have non-empty current signblockscript field");
|
||||
}
|
||||
if (!is_reject && offline_keys.size() == 0) {
|
||||
return boost::none;
|
||||
|
||||
// Make sure extension bits aren't active, reserved for future HF
|
||||
uint32_t reserved_mask = (1<<23) | (1<<24) | (1<<25) | (1<<26);
|
||||
if ((block.nVersion & reserved_mask) != 0) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "dynamic block header has unknown HF extension bits set");
|
||||
}
|
||||
CPAKList paklist;
|
||||
if (!CPAKList::FromBytes(paklist, offline_keys, online_keys, is_reject)) {
|
||||
return boost::none;
|
||||
} else {
|
||||
return paklist;
|
||||
|
||||
const DynaFedParamEntry expected_current_params = ComputeNextBlockCurrentParameters(pindexPrev, params.GetConsensus());
|
||||
|
||||
if (expected_current_params != dynafed_params.m_current) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "dynamic block header's current parameters do not match expected");
|
||||
}
|
||||
|
||||
// Lastly, enforce rules on proposals.
|
||||
const DynaFedParamEntry& proposed = dynafed_params.m_proposed;
|
||||
if (!proposed.IsNull()) {
|
||||
|
||||
// signblockscript proposals *must* be segwit versions
|
||||
int block_version = 0;
|
||||
std::vector<unsigned char> block_program;
|
||||
if (!proposed.m_signblockscript.IsWitnessProgram(block_version, block_program)) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "proposed signblockscript must be native segwit scriptPubkey");
|
||||
}
|
||||
|
||||
int fedpeg_version = 0;
|
||||
std::vector<unsigned char> fedpeg_program;
|
||||
if (!proposed.m_fedpeg_program.IsWitnessProgram(fedpeg_version, fedpeg_program)) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "proposed fedpegs program must be native segwit scriptPubkey");
|
||||
}
|
||||
|
||||
// for v0, fedpegscript's scriptPubKey must match. v1+ is unencumbered.
|
||||
if (fedpeg_version == 0) {
|
||||
uint256 fedpeg_program;
|
||||
CSHA256().Write(proposed.m_fedpegscript.data(), proposed.m_fedpegscript.size()).Finalize(fedpeg_program.begin());
|
||||
CScript computed_program = CScript() << OP_0 << ToByteVector(fedpeg_program);
|
||||
if (computed_program != proposed.m_fedpeg_program) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "proposed v0 segwit fedpegscript must match proposed fedpeg witness program");
|
||||
}
|
||||
|
||||
// fedpegscript proposals *must not* start with OP_DEPTH
|
||||
// This forbids the first Liquid watchman script which is a hack.
|
||||
// Use miniscript, which doesn't even have OP_DEPTH.
|
||||
// We don't encumber future segwit versions as opcodes may change.
|
||||
if (!proposed.m_fedpegscript.empty() &&
|
||||
proposed.m_fedpegscript.front() == OP_DEPTH) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "Proposed fedpegscript starts with OP_DEPTH, which is illegal");
|
||||
}
|
||||
}
|
||||
|
||||
// When enforcing PAK, extension_space must give non-empty PAK list when
|
||||
// the vector itself is non-empty. Otherwise this means there were "junk"
|
||||
// entries
|
||||
if (params.GetEnforcePak()) {
|
||||
if (!proposed.m_extension_space.empty() &&
|
||||
CreatePAKListFromExtensionSpace(proposed.m_extension_space).IsReject()) {
|
||||
return state.Invalid(false, REJECT_INVALID, "invalid-dyna-fed", "Extension space is not list of valid PAK entries");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** Context-dependent validity checks.
|
||||
* By "context", we mean only the previous block headers, but not the UTXO
|
||||
* set; UTXO-related validity checks are done in ConnectBlock().
|
||||
|
|
@ -3524,9 +3555,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta
|
|||
assert(pindexPrev != nullptr);
|
||||
const int nHeight = pindexPrev->nHeight + 1;
|
||||
|
||||
// Check proof of work if necessary
|
||||
// Check proof of work target or non-dynamic signblockscript if necessary
|
||||
const Consensus::Params& consensusParams = params.GetConsensus();
|
||||
if (!CheckChallenge(block, *pindexPrev, consensusParams))
|
||||
if (!IsDynaFedEnabled(pindexPrev, consensusParams) && !CheckChallenge(block, *pindexPrev, consensusParams))
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false, "incorrect proof of work");
|
||||
|
||||
// Check against checkpoints
|
||||
|
|
@ -3560,6 +3591,10 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta
|
|||
return state.Invalid(false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion),
|
||||
strprintf("rejected nVersion=0x%08x block", block.nVersion));
|
||||
|
||||
if (!ContextualCheckDynaFedHeader(block, state, params, pindexPrev)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
|
||||
#include <atomic>
|
||||
|
||||
#include <boost/optional.hpp> // GetPAKKeysFromCommitment
|
||||
#include <primitives/pak.h> // CPAKList
|
||||
|
||||
class CBlockIndex;
|
||||
|
|
@ -410,6 +409,9 @@ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& pa
|
|||
/** Check whether NULLDUMMY (BIP 147) has activated. */
|
||||
bool IsNullDummyEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params);
|
||||
|
||||
/** Check whether Dynamic Federation has activated. */
|
||||
bool IsDynaFedEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params);
|
||||
|
||||
/** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */
|
||||
bool RewindBlockIndex(const CChainParams& params);
|
||||
|
||||
|
|
@ -420,8 +422,6 @@ void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPr
|
|||
std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams);
|
||||
|
||||
// ELEMENTS
|
||||
/** Extract pak commitment from coinbase, if it exists. List must be ordered, but not necessarily consecutive in output index */
|
||||
boost::optional<CPAKList> GetPAKKeysFromCommitment(const CTransaction& coinbase);
|
||||
|
||||
/** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */
|
||||
class CVerifyDB {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <map>
|
||||
|
||||
/** What block version to use for new blocks (pre versionbits) */
|
||||
static const int32_t VERSIONBITS_LAST_OLD_BLOCK_VERSION = 4;
|
||||
static const int32_t VERSIONBITS_LAST_OLD_BLOCK_VERSION = 5;
|
||||
/** What bits to set in version for versionbits blocks */
|
||||
static const int32_t VERSIONBITS_TOP_BITS = 0x20000000UL;
|
||||
/** What bitmask determines whether versionbits is in use */
|
||||
|
|
|
|||
|
|
@ -18,5 +18,9 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
|
|||
{
|
||||
/*.name =*/ "segwit",
|
||||
/*.gbt_force =*/ true,
|
||||
}
|
||||
},
|
||||
{
|
||||
/*.name =*/ "dynafed",
|
||||
/*.gbt_force =*/ true,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4730,12 +4730,13 @@ UniValue signblock(const JSONRPCRequest& request)
|
|||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (request.fHelp || request.params.size() != 1)
|
||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"signblock",
|
||||
"\nSigns a block proposal, checking that it would be accepted first. Errors if it cannot sign the block.\n",
|
||||
"\nSigns a block proposal, checking that it would be accepted first. Errors if it cannot sign the block. Note that this call adds the witnessScript to your wallet for signing purposes! This function is intended for QA and testing.\n",
|
||||
{
|
||||
{"blockhex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded block from getnewblockhex"},
|
||||
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded witness script. Required for dynamic federation blocks. Argument is \"\" when the block is P2WPKH."},
|
||||
},
|
||||
RPCResult{
|
||||
"[\n"
|
||||
|
|
@ -4781,7 +4782,20 @@ UniValue signblock(const JSONRPCRequest& request)
|
|||
|
||||
// Expose SignatureData internals in return value in lieu of "Partially Signed Bitcoin Blocks"
|
||||
SignatureData block_sigs;
|
||||
GenericSignScript(*pwallet, block.GetBlockHeader(), block.proof.challenge, block_sigs);
|
||||
if (block.m_dynafed_params.IsNull()) {
|
||||
GenericSignScript(*pwallet, block.GetBlockHeader(), block.proof.challenge, block_sigs);
|
||||
} else {
|
||||
if (request.params[1].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Signing dynamic blocks requires the witnessScript argument");
|
||||
}
|
||||
std::vector<unsigned char> witness_bytes(ParseHex(request.params[1].get_str()));
|
||||
// Note that we're adding the signblockscript to the wallet so it can actually
|
||||
// satisfy witness program scriptpubkeys
|
||||
if (!witness_bytes.empty()) {
|
||||
pwallet->AddCScript(CScript(witness_bytes.begin(), witness_bytes.end()));
|
||||
}
|
||||
GenericSignScript(*pwallet, block.GetBlockHeader(), block.m_dynafed_params.m_current.m_signblockscript, block_sigs);
|
||||
}
|
||||
|
||||
// Error if sig data didn't "grow"
|
||||
if (!block_sigs.complete && block_sigs.signatures.empty()) {
|
||||
|
|
@ -4842,8 +4856,15 @@ UniValue getpeginaddress(const JSONRPCRequest& request)
|
|||
// Also add raw scripts to index to recognize later.
|
||||
pwallet->AddCScript(dest_script);
|
||||
|
||||
// Get P2CH deposit address on mainchain.
|
||||
CTxDestination mainchain_dest(ScriptHash(GetScriptForWitness(calculate_contract(Params().GetConsensus().fedpegScript, dest_script))));
|
||||
// Get P2CH deposit address on mainchain from most recent fedpegscript.
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
CTxDestination mainchain_dest(WitnessV0ScriptHash(calculate_contract(fedpegscripts.front().second, dest_script)));
|
||||
// P2SH-wrapped is the only valid choice for non-dynafed chains but still an
|
||||
// option for dynafed-enabled ones as well
|
||||
if (!IsDynaFedEnabled(chainActive.Tip(), Params().GetConsensus()) ||
|
||||
fedpegscripts.front().first.IsPayToScriptHash()) {
|
||||
mainchain_dest = ScriptHash(GetScriptForDestination(mainchain_dest));
|
||||
}
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
||||
|
|
@ -5198,10 +5219,7 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
subtract_fee = request.params[2].get_bool();
|
||||
}
|
||||
|
||||
CPAKList paklist = g_paklist_blockchain;
|
||||
if (g_paklist_config) {
|
||||
paklist = *g_paklist_config;
|
||||
}
|
||||
CPAKList paklist = GetActivePAKList(chainActive.Tip(), Params().GetConsensus());
|
||||
if (paklist.IsReject()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pegout freeze is under effect to aid a pak transition to a new list. Please consult the network operator.");
|
||||
}
|
||||
|
|
@ -5339,8 +5357,7 @@ UniValue sendtomainchain_pak(const JSONRPCRequest& request)
|
|||
CTxDestination address(nulldata);
|
||||
assert(GetScriptForDestination(nulldata).IsPegoutScript(genesisBlockHash));
|
||||
|
||||
txnouttype txntype;
|
||||
if (!IsStandard(GetScriptForDestination(nulldata), txntype)) {
|
||||
if (!ScriptHasValidPAKProof(GetScriptForDestination(nulldata), Params().ParentGenesisBlockHash(), paklist)) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Resulting scriptPubKey is non-standard. Ensure pak=reject is not set");
|
||||
}
|
||||
|
||||
|
|
@ -5375,15 +5392,19 @@ extern UniValue signrawtransaction(const JSONRPCRequest& request);
|
|||
extern UniValue sendrawtransaction(const JSONRPCRequest& request);
|
||||
|
||||
template<typename T_tx>
|
||||
unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram)
|
||||
unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
|
||||
{
|
||||
unsigned int nOut = 0;
|
||||
//Call contracthashtool
|
||||
CScript mainchain_script = GetScriptForDestination(ScriptHash(GetScriptForWitness(calculate_contract(Params().GetConsensus().fedpegScript, witnessProgram))));
|
||||
for (; nOut < txn.vout.size(); nOut++)
|
||||
if (txn.vout[nOut].scriptPubKey == mainchain_script)
|
||||
break;
|
||||
return nOut;
|
||||
for (const auto & scripts : fedpegscripts) {
|
||||
CScript mainchain_script = GetScriptForWitness(calculate_contract(scripts.second, witnessProgram));
|
||||
if (scripts.first.IsPayToScriptHash()) {
|
||||
mainchain_script = GetScriptForDestination(ScriptHash(mainchain_script));
|
||||
}
|
||||
for (unsigned int nOut = 0; nOut < txn.vout.size(); nOut++)
|
||||
if (txn.vout[nOut].scriptPubKey == mainchain_script) {
|
||||
return nOut;
|
||||
}
|
||||
}
|
||||
return txn.vout.size();
|
||||
}
|
||||
|
||||
template<typename T_tx_ref, typename T_tx, typename T_merkle_block>
|
||||
|
|
@ -5433,7 +5454,7 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
T_tx txBTC(*txBTCRef);
|
||||
|
||||
std::vector<unsigned char> txOutProofData = ParseHex(request.params[1].get_str());
|
||||
CDataStream ssTxOutProof(txOutProofData, SER_NETWORK, PROTOCOL_VERSION);
|
||||
CDataStream ssTxOutProof(txOutProofData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||
try {
|
||||
ssTxOutProof >> merkleBlock;
|
||||
}
|
||||
|
|
@ -5455,6 +5476,7 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
|
||||
CScript witness_script;
|
||||
unsigned int nOut = txBTC.vout.size();
|
||||
const auto fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
if (request.params.size() > 2) {
|
||||
const std::string claim_script = request.params[2].get_str();
|
||||
if (!IsHex(claim_script)) {
|
||||
|
|
@ -5463,7 +5485,7 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
// If given manually, no need for it to be a witness script
|
||||
std::vector<unsigned char> witnessBytes(ParseHex(claim_script));
|
||||
witness_script = CScript(witnessBytes.begin(), witnessBytes.end());
|
||||
nOut = GetPeginTxnOutputIndex(txBTC, witness_script);
|
||||
nOut = GetPeginTxnOutputIndex(txBTC, witness_script, fedpegscripts);
|
||||
if (nOut == txBTC.vout.size()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script does not match the given Bitcoin transaction.");
|
||||
}
|
||||
|
|
@ -5472,7 +5494,7 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
// Look for known wpkh address in wallet
|
||||
for (std::map<CTxDestination, CAddressBookData>::const_iterator iter = pwallet->mapAddressBook.begin(); iter != pwallet->mapAddressBook.end(); ++iter) {
|
||||
CScript dest_script = GetScriptForDestination(iter->first);
|
||||
nOut = GetPeginTxnOutputIndex(txBTC, dest_script);
|
||||
nOut = GetPeginTxnOutputIndex(txBTC, dest_script, fedpegscripts);
|
||||
if (nOut != txBTC.vout.size()) {
|
||||
witness_script = dest_script;
|
||||
break;
|
||||
|
|
@ -5549,7 +5571,7 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
// Peg-in witness isn't valid, even though the block header is(without depth check)
|
||||
// We re-check depth before returning with more descriptive result
|
||||
std::string err;
|
||||
if (!IsValidPeginWitness(pegin_witness, mtx.vin[0].prevout, err, false)) {
|
||||
if (!IsValidPeginWitness(pegin_witness, fedpegscripts, mtx.vin[0].prevout, err, false)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Constructed peg-in witness is invalid: %s", err));
|
||||
}
|
||||
|
||||
|
|
@ -5869,6 +5891,8 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
|
|||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
|
||||
std::vector<uint256> input_blinds;
|
||||
std::vector<uint256> input_asset_blinds;
|
||||
std::vector<CAsset> input_assets;
|
||||
|
|
@ -5880,7 +5904,7 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
|
|||
// Special handling for pegin 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, prevout, err, false)) {
|
||||
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, fedpegscripts, prevout, err, false)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction contains invalid peg-in input: %s", err));
|
||||
}
|
||||
CTxOut pegin_output = GetPeginOutputFromWitness(tx.witness.vtxinwit[nIn].m_pegin_witness);
|
||||
|
|
@ -6482,10 +6506,7 @@ UniValue generatepegoutproof(const JSONRPCRequest& request)
|
|||
if (!secp256k1_ec_pubkey_parse(secp256k1_ctx, &onlinepubkey_secp, &onlinepubkeybytes[0], onlinepubkeybytes.size()))
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Invalid online pubkey");
|
||||
|
||||
CPAKList paklist = g_paklist_blockchain;
|
||||
if (g_paklist_config) {
|
||||
paklist = *g_paklist_config;
|
||||
}
|
||||
CPAKList paklist = GetActivePAKList(chainActive.Tip(), Params().GetConsensus());
|
||||
if (paklist.IsReject()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pegout freeze is under effect to aid a pak transition to a new list. Please consult the network operator.");
|
||||
}
|
||||
|
|
@ -6701,7 +6722,7 @@ static const CRPCCommand commands[] =
|
|||
{ "wallet", "dumpblindingkey", &dumpblindingkey, {"address"}},
|
||||
{ "wallet", "dumpmasterblindingkey", &dumpmasterblindingkey, {}},
|
||||
{ "wallet", "dumpissuanceblindingkey", &dumpissuanceblindingkey, {"txid", "vin"}},
|
||||
{ "wallet", "signblock", &signblock, {"blockhex"}},
|
||||
{ "wallet", "signblock", &signblock, {"blockhex", "witnessScript"}},
|
||||
{ "wallet", "listissuances", &listissuances, {"asset"}},
|
||||
{ "wallet", "issueasset", &issueasset, {"assetamount", "tokenamount", "blind"}},
|
||||
{ "wallet", "reissueasset", &reissueasset, {"asset", "assetamount"}},
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ def initialize_datadir(dirname, n):
|
|||
f.write("pubkeyprefix=111\n")
|
||||
f.write("scriptprefix=196\n")
|
||||
f.write("bech32_hrp=bcrt\n")
|
||||
f.write("con_dyna_deploy_start="+str(2**31)+"\n") # Never starts
|
||||
os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True)
|
||||
os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True)
|
||||
return datadir
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ from test_framework import (
|
|||
# Generate wallet import format from private key.
|
||||
def wif(pk):
|
||||
# Base58Check version for regtest WIF keys is 0xef = 239
|
||||
return address.byte_to_base58(pk, 239)
|
||||
pk_compressed = pk + bytes([0x1])
|
||||
return address.byte_to_base58(pk_compressed, 239)
|
||||
|
||||
# The signblockscript is a Bitcoin Script k-of-n multisig script.
|
||||
def make_signblockscript(num_nodes, required_signers, keys):
|
||||
|
|
@ -22,11 +23,10 @@ def make_signblockscript(num_nodes, required_signers, keys):
|
|||
script = "{}".format(50 + required_signers)
|
||||
for i in range(num_nodes):
|
||||
k = keys[i]
|
||||
script += "41"
|
||||
script += "21"
|
||||
script += codecs.encode(k.get_pubkey(), 'hex_codec').decode("utf-8")
|
||||
script += "{}".format(50 + num_nodes) # num keys
|
||||
script += "ae" # OP_CHECKMULTISIG
|
||||
print('signblockscript', script)
|
||||
return script
|
||||
|
||||
class BlockSignTest(BitcoinTestFramework):
|
||||
|
|
@ -45,6 +45,10 @@ class BlockSignTest(BitcoinTestFramework):
|
|||
|
||||
As well as syncing blocks over p2p
|
||||
|
||||
This test covers both pre-dynafed and post.
|
||||
|
||||
TODO: Show block max witness actually limits the witness
|
||||
|
||||
"""
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
|
|
@ -58,12 +62,10 @@ class BlockSignTest(BitcoinTestFramework):
|
|||
k = key.CECKey()
|
||||
pk_bytes = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).digest()
|
||||
k.set_secretbytes(pk_bytes)
|
||||
k.set_compressed(True)
|
||||
w = wif(pk_bytes)
|
||||
print("generated key {}: \n pub: {}\n wif: {}".format(i+1,
|
||||
codecs.encode(k.get_pubkey(), 'hex_codec').decode("utf-8"),
|
||||
w))
|
||||
self.keys.append(k)
|
||||
self.wifs.append(wif(pk_bytes))
|
||||
self.wifs.append(w)
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 5
|
||||
|
|
@ -72,10 +74,12 @@ class BlockSignTest(BitcoinTestFramework):
|
|||
self.setup_clean_chain = True
|
||||
self.init_keys(self.num_nodes-1) # Last node cannot sign and is connected to all via p2p
|
||||
signblockscript = make_signblockscript(self.num_keys, self.required_signers, self.keys)
|
||||
self.witnessScript = signblockscript # post-dynafed this becomes witnessScript
|
||||
self.extra_args = [[
|
||||
"-signblockscript={}".format(signblockscript),
|
||||
"-con_max_block_sig_size={}".format(self.required_signers*74),
|
||||
"-anyonecanspendaremine=1"
|
||||
"-anyonecanspendaremine=1",
|
||||
"-con_dyna_deploy_start=0",
|
||||
]] * self.num_nodes
|
||||
|
||||
def setup_network(self):
|
||||
|
|
@ -97,9 +101,16 @@ class BlockSignTest(BitcoinTestFramework):
|
|||
miner_next = self.nodes[mineridx_next]
|
||||
blockcount = miner.getblockcount()
|
||||
|
||||
# If dynafed is enabled, this means signblockscript has been WSH-wrapped
|
||||
blockchain_info = self.nodes[0].getblockchaininfo()
|
||||
is_dyna = blockchain_info['bip9_softforks']['dynafed']['status'] == "active"
|
||||
if is_dyna:
|
||||
wsh_wrap = self.nodes[0].decodescript(self.witnessScript)['segwit']['hex']
|
||||
assert_equal(wsh_wrap, blockchain_info['current_signblock_hex'])
|
||||
assert blockchain_info['current_signblock_hex'] != blockchain_info['signblock_hex']
|
||||
|
||||
# Make a few transactions to make non-empty blocks for compact transmission
|
||||
if make_transactions:
|
||||
print(mineridx)
|
||||
for i in range(5):
|
||||
miner.sendtoaddress(miner_next.getnewaddress(), int(miner.getbalance()['bitcoin']/10), "", "", True)
|
||||
# miner makes a block
|
||||
|
|
@ -121,20 +132,20 @@ class BlockSignTest(BitcoinTestFramework):
|
|||
self.nodes[i].testproposedblock(final_block)
|
||||
|
||||
# non-signing node can not sign
|
||||
assert_raises_rpc_error(-25, "Could not sign the block.", self.nodes[-1].signblock, block)
|
||||
assert_raises_rpc_error(-25, "Could not sign the block.", self.nodes[-1].signblock, block, self.witnessScript)
|
||||
|
||||
# collect num_keys signatures from signers, reduce to required_signers sigs during combine
|
||||
sigs = []
|
||||
for i in range(self.num_keys):
|
||||
result = miner.combineblocksigs(block, sigs)
|
||||
sigs = sigs + self.nodes[i].signblock(block)
|
||||
result = miner.combineblocksigs(block, sigs, self.witnessScript)
|
||||
sigs = sigs + self.nodes[i].signblock(block, self.witnessScript)
|
||||
assert_equal(result["complete"], i >= self.required_signers)
|
||||
# submitting should have no effect pre-threshhold
|
||||
if i < self.required_signers:
|
||||
miner.submitblock(result["hex"])
|
||||
self.check_height(blockcount)
|
||||
|
||||
result = miner.combineblocksigs(block, sigs)
|
||||
result = miner.combineblocksigs(block, sigs, self.witnessScript)
|
||||
assert_equal(result["complete"], True)
|
||||
|
||||
# All signing nodes must submit... we're not connected!
|
||||
|
|
@ -160,11 +171,11 @@ class BlockSignTest(BitcoinTestFramework):
|
|||
self.check_height(0)
|
||||
|
||||
# mine a block with no transactions
|
||||
print("Mining and signing 101 blocks to unlock funds")
|
||||
self.log.info("Mining and signing 101 blocks to unlock funds")
|
||||
self.mine_blocks(101, False)
|
||||
|
||||
# mine blocks with transactions
|
||||
print("Mining and signing non-empty blocks")
|
||||
self.log.info("Mining and signing non-empty blocks")
|
||||
self.mine_blocks(10, True)
|
||||
|
||||
# Height check also makes sure non-signing, p2p connected node gets block
|
||||
|
|
@ -185,5 +196,18 @@ class BlockSignTest(BitcoinTestFramework):
|
|||
assert_equal(info['signblock_asm'], self.nodes[0].decodescript(signblockscript)['asm'])
|
||||
assert_equal(info['signblock_hex'], signblockscript)
|
||||
|
||||
assert_equal(info['bip9_softforks']['dynafed']['status'], "defined")
|
||||
|
||||
# Next let's activate dynafed
|
||||
blocks_til_dynafed = 431 - self.nodes[0].getblockcount()
|
||||
self.mine_blocks(blocks_til_dynafed, False)
|
||||
self.check_height(111+blocks_til_dynafed)
|
||||
|
||||
assert_equal(self.nodes[0].getblockchaininfo()['bip9_softforks']['dynafed']['status'], "active")
|
||||
|
||||
self.log.info("Mine some dynamic federation blocks without and with txns")
|
||||
self.mine_blocks(50, False)
|
||||
self.mine_blocks(50, True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
BlockSignTest().main()
|
||||
|
|
|
|||
441
test/functional/feature_dynafed.py
Executable file
441
test/functional/feature_dynafed.py
Executable file
|
|
@ -0,0 +1,441 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2019 The Elements Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test dynamic federations state machine logic
|
||||
|
||||
NOTE: This test is not testing the behavior not related to transitions themselves.
|
||||
That is for other tests such as feature_pak, feature_fedpeg, feature_blocksign
|
||||
|
||||
1) Test "legacy" params are still in play before versionbits activation
|
||||
2) Test transition to dynafed preserves expected chainparams
|
||||
3) Test a full epoch with no votes
|
||||
4) Test full epoch with just under 4/5 votes, with competing random proposals
|
||||
5) Test full epoch with just at 4/5 votes, with competing random proposals
|
||||
6) Test full epoch with 5/5 votes
|
||||
7) Test that peg-outs(PAK) and peg-ins are ejected from mempool block before transition
|
||||
and rejected when re-submitted if there is a parameter mis-match
|
||||
8) Test that reorging a transition results in transitions being undone,
|
||||
previously ejected transactions are allowed back into the mempool when appropriate
|
||||
|
||||
"""
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_raises_rpc_error, assert_equal, sync_blocks
|
||||
|
||||
# Hardcoded PAK that's in chainparams to make sure PAK is enforced even when dynafed is not
|
||||
initial_online = "02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f"
|
||||
# Random key to make new pak
|
||||
initial_offline = "03808355deeb0555203b53df7ef8f36edaf66ab0207ca1b11968a7ac421554e621"
|
||||
initial_extension = [initial_online+initial_online]
|
||||
new_extension = [initial_offline+initial_online]
|
||||
|
||||
def go_to_epoch_end(node):
|
||||
epoch_info = node.getblockchaininfo()
|
||||
blocks_to_mine = epoch_info["epoch_length"] - epoch_info["epoch_age"] - 1
|
||||
node.generatetoaddress(blocks_to_mine, node.getnewaddress())
|
||||
|
||||
def validate_no_vote_op_true(node, block):
|
||||
|
||||
block_info = node.getblock(block)
|
||||
dynamic_parameters = block_info["dynamic_parameters"]
|
||||
block_height = block_info["height"]
|
||||
assert "current" in dynamic_parameters
|
||||
assert "proposed" in dynamic_parameters
|
||||
# signblockscript is now the P2WSH-ification of OP_TRUE
|
||||
WSH_OP_TRUE = node.decodescript("51")["segwit"]["hex"]
|
||||
assert_equal(dynamic_parameters["current"]["signblockscript"], WSH_OP_TRUE)
|
||||
if block_height % 10 == 0:
|
||||
assert_equal(dynamic_parameters["current"]["fedpegscript"], "51")
|
||||
assert_equal(dynamic_parameters["current"]["extension_space"], initial_extension)
|
||||
else:
|
||||
assert_equal(dynamic_parameters["current"]["fedpegscript"], "")
|
||||
assert_equal(dynamic_parameters["current"]["extension_space"], [])
|
||||
# TODO workshop this bump, or commit to new value in chainparams instead
|
||||
assert_equal(dynamic_parameters["current"]["max_block_witness"], 75)
|
||||
# nothing was proposed, null fields make impossible to be valid blockheader
|
||||
# due to script rules requiring bool true on stack
|
||||
assert_equal(dynamic_parameters["proposed"]["signblockscript"], "")
|
||||
assert_equal(dynamic_parameters["proposed"]["fedpegscript"], "")
|
||||
assert_equal(dynamic_parameters["proposed"]["max_block_witness"], 0)
|
||||
assert_equal(dynamic_parameters["proposed"]["extension_space"], [])
|
||||
|
||||
class DynaFedTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 2
|
||||
# We want to test activation of dynafed
|
||||
self.extra_args = [["-con_dyna_deploy_start=0", "-enforce_pak=1", "-con_parent_chain_signblockscript=51", "-peginconfirmationdepth=1", "-parentscriptprefix=75", "-parent_bech32_hrp=ert"] for i in range(self.num_nodes)]
|
||||
# second node will not mine transactions
|
||||
self.extra_args[1].append("-blocksonly=1")
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def test_legacy_params(self):
|
||||
self.log.info("Testing legacy parameters...")
|
||||
for i in range(self.num_nodes):
|
||||
assert_equal(self.nodes[i].getblockcount(), 0)
|
||||
|
||||
# Check deployment exists and is not active
|
||||
dyna_activate = self.nodes[i].getblockchaininfo()["bip9_softforks"]["dynafed"]
|
||||
assert_equal(dyna_activate["status"], "defined")
|
||||
|
||||
# fedpegscript is OP_TRUE
|
||||
legacy_sc_info = self.nodes[i].getsidechaininfo()
|
||||
assert_equal(legacy_sc_info["fedpegscript"], "51")
|
||||
# No history yet, only one "live" fedpegscript
|
||||
assert_equal(legacy_sc_info["current_fedpegscripts"], ["51"])
|
||||
|
||||
# blocksigner is OP_TRUE, extension space is hardcoded one in chainparams
|
||||
signblock_info = self.nodes[i].getblockchaininfo()
|
||||
assert_equal(signblock_info["signblock_hex"], "51")
|
||||
assert_equal(signblock_info["current_signblock_hex"], "51")
|
||||
assert_equal(signblock_info["max_block_witness"], 74)
|
||||
assert_equal(signblock_info["extension_space"], initial_extension)
|
||||
|
||||
pak_info = self.nodes[i].getpakinfo()
|
||||
assert_equal(pak_info["block_paklist"]["reject"], False)
|
||||
assert_equal(pak_info["block_paklist"]["online"], [initial_online])
|
||||
assert_equal(pak_info["block_paklist"]["offline"], [initial_online])
|
||||
|
||||
# can not put proposed params into blockheader pre-dynafed
|
||||
assert_raises_rpc_error(-8, "Dynamic federations is not active on this network. Proposed parameters are not needed.", self.nodes[i].getnewblockhex, 0, {})
|
||||
|
||||
# TODO Reject serialized dynamic federations blocks before activation
|
||||
|
||||
def test_dynafed_activation(self):
|
||||
self.log.info("Testing dynafed versionbits activation...")
|
||||
|
||||
# Move chain forward to activation, any new blocks will be enforced
|
||||
blocks = self.nodes[0].generatetoaddress(431, self.nodes[0].getnewaddress())
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockchaininfo()["bip9_softforks"]["dynafed"]["status"], "active")
|
||||
|
||||
# Existing blocks should have null dynafed fields
|
||||
for block in blocks:
|
||||
assert "dynamic_parameters" not in self.nodes[0].getblock(block)
|
||||
|
||||
# Next block is first dynamic federation block
|
||||
block = self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())[0]
|
||||
self.sync_all()
|
||||
for i in range(self.num_nodes):
|
||||
validate_no_vote_op_true(self.nodes[i], block)
|
||||
|
||||
def test_illegal_proposals(self):
|
||||
|
||||
WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"]
|
||||
# fedpegscript proposals starting with OP_DEPTH(0x74) are illegal when witness v0
|
||||
assert_raises_rpc_error(-1, "invalid-dyna-fed, Proposed fedpegscript starts with OP_DEPTH, which is illegal", self.nodes[0].getnewblockhex, 0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":100, "fedpegscript":"74", "extension_space":[]})
|
||||
# but it's ok to have the opcode elsewhere
|
||||
self.nodes[0].getnewblockhex(0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":100, "fedpegscript":"0074", "extension_space":[]})
|
||||
|
||||
# signblockscript proposals must be native segwit scriptpubkeys
|
||||
assert_raises_rpc_error(-1, "invalid-dyna-fed, proposed signblockscript must be native segwit scriptPubkey", self.nodes[0].getnewblockhex, 0, {"signblockscript":"51", "max_block_witness":100, "fedpegscript":"51", "extension_space":[]})
|
||||
assert_raises_rpc_error(-1, "invalid-dyna-fed, proposed signblockscript must be native segwit scriptPubkey", self.nodes[0].getnewblockhex, 0, {"signblockscript":"00"+WSH_OP_TRUE, "max_block_witness":100, "fedpegscript":"51", "extension_space":[]})
|
||||
|
||||
# Since we're enforcing PAK, extension space entries *must* be 66 bytes
|
||||
# each 33 of which are serialized compressed pubkeys
|
||||
assert_raises_rpc_error(-1, "invalid-dyna-fed, Extension space is not list of valid PAK entries", self.nodes[0].getnewblockhex, 0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":100, "fedpegscript":"51", "extension_space":["00"]})
|
||||
assert_raises_rpc_error(-1, "invalid-dyna-fed, Extension space is not list of valid PAK entries", self.nodes[0].getnewblockhex, 0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":100, "fedpegscript":"51", "extension_space":["", initial_extension[0]]})
|
||||
|
||||
def test_no_vote(self):
|
||||
self.log.info("Testing no-vote epoch...")
|
||||
go_to_epoch_end(self.nodes[0])
|
||||
|
||||
# Mine epoch_length blocks with no proposals
|
||||
blocks = self.nodes[0].generatetoaddress(10, self.nodes[0].getnewaddress())
|
||||
self.sync_all()
|
||||
|
||||
for i in range(self.num_nodes):
|
||||
for block in blocks:
|
||||
validate_no_vote_op_true(self.nodes[i], block)
|
||||
|
||||
# Now transition using vanilla getnewblockhex, nothing changed
|
||||
block = self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())[0]
|
||||
self.sync_all()
|
||||
|
||||
for i in range(self.num_nodes):
|
||||
validate_no_vote_op_true(self.nodes[i], block)
|
||||
|
||||
def test_under_vote(self):
|
||||
self.log.info("Testing failed voting epoch...")
|
||||
go_to_epoch_end(self.nodes[0])
|
||||
|
||||
# Mine 7 blocks with agreeing proposals for single-sig, falls short of 4/5 of 10
|
||||
new_signblock = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress("", "bech32"))["scriptPubKey"]
|
||||
cur_height = self.nodes[0].getblockcount()
|
||||
for _ in range(7):
|
||||
prop_block = self.nodes[0].getnewblockhex(0, {"signblockscript":new_signblock, "max_block_witness":100, "fedpegscript":"52", "extension_space":new_extension})
|
||||
self.nodes[0].submitblock(prop_block)
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockcount(), cur_height+7)
|
||||
# Now mine 3 blank blocks
|
||||
self.nodes[0].generatetoaddress(3, self.nodes[0].getnewaddress())
|
||||
# No transition will take place, generatetoaddress still works for new epoch
|
||||
block = self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())[0]
|
||||
self.sync_all()
|
||||
|
||||
for i in range(self.num_nodes):
|
||||
validate_no_vote_op_true(self.nodes[i], block)
|
||||
|
||||
def test_four_fifth_vote(self):
|
||||
self.log.info("Testing just-successful transition epoch...")
|
||||
go_to_epoch_end(self.nodes[0])
|
||||
|
||||
# Mine 8 blocks with agreeing proposals for single-sig, triggering transition
|
||||
new_signblock = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress("", "bech32"))["scriptPubKey"]
|
||||
cur_height = self.nodes[0].getblockcount()
|
||||
WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"]
|
||||
for _ in range(8):
|
||||
# Check that things don't change until the 10th block is submitted
|
||||
for i in range(self.num_nodes):
|
||||
chain_info = self.nodes[i].getblockchaininfo()
|
||||
fedpeg_info = self.nodes[i].getsidechaininfo()
|
||||
assert_equal(chain_info["current_signblock_hex"], WSH_OP_TRUE)
|
||||
assert_equal(chain_info["max_block_witness"], 75)
|
||||
assert_equal(chain_info["extension_space"], initial_extension)
|
||||
assert_equal(fedpeg_info["current_fedpegscripts"], ["51", "51"])
|
||||
|
||||
prop_block = self.nodes[0].getnewblockhex(0, {"signblockscript":new_signblock, "max_block_witness":107, "fedpegscript":"52", "extension_space":new_extension})
|
||||
self.nodes[0].submitblock(prop_block)
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockcount(), cur_height+8)
|
||||
|
||||
# Now mine 1 blank block
|
||||
self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())
|
||||
self.sync_all()
|
||||
|
||||
# Old parameters still enforced for next block...
|
||||
for i in range(self.num_nodes):
|
||||
chain_info = self.nodes[i].getblockchaininfo()
|
||||
fedpeg_info = self.nodes[i].getsidechaininfo()
|
||||
assert_equal(chain_info["current_signblock_hex"], WSH_OP_TRUE)
|
||||
assert_equal(chain_info["max_block_witness"], 75)
|
||||
assert_equal(chain_info["extension_space"], initial_extension)
|
||||
assert_equal(fedpeg_info["current_fedpegscripts"], ["51", "51"])
|
||||
|
||||
# Last blank block
|
||||
self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())
|
||||
self.sync_all()
|
||||
|
||||
# We have now transitioned, next block must have signature
|
||||
unsigned_block = self.nodes[0].getnewblockhex()
|
||||
assert_equal(self.nodes[0].submitblock(unsigned_block), "block-proof-invalid")
|
||||
assert_equal(self.nodes[0].getblockcount(), cur_height+10)
|
||||
|
||||
# New params now enforced
|
||||
for i in range(self.num_nodes):
|
||||
chain_info = self.nodes[i].getblockchaininfo()
|
||||
fedpeg_info = self.nodes[i].getsidechaininfo()
|
||||
assert_equal(chain_info["current_signblock_hex"], new_signblock)
|
||||
assert_equal(chain_info["max_block_witness"], 107) # 72+33+2
|
||||
assert_equal(chain_info["extension_space"], new_extension)
|
||||
assert_equal(fedpeg_info["current_fedpegscripts"], ["52", "51"])
|
||||
|
||||
def test_all_vote(self):
|
||||
self.log.info("Testing unanimous transition epoch...")
|
||||
# We have now transitioned to single-sig blocks from node 0
|
||||
# Let's transition node 1's key with all votes to it
|
||||
|
||||
cur_height = self.nodes[0].getblockcount()
|
||||
|
||||
new_signblock = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress("", "bech32"))["scriptPubKey"]
|
||||
for _ in range(10):
|
||||
# Check that things don't change until the 10th block is submitted
|
||||
for i in range(self.num_nodes):
|
||||
chain_info = self.nodes[i].getblockchaininfo()
|
||||
fedpeg_info = self.nodes[i].getsidechaininfo()
|
||||
assert chain_info["current_signblock_hex"] != new_signblock
|
||||
assert_equal(chain_info["max_block_witness"], 107)
|
||||
assert_equal(chain_info["extension_space"], new_extension)
|
||||
assert_equal(fedpeg_info["current_fedpegscripts"], ["52", "51"])
|
||||
|
||||
block = self.nodes[1].getnewblockhex(0, {"signblockscript":new_signblock, "max_block_witness":108, "fedpegscript":"53", "extension_space":new_extension})
|
||||
sig = self.nodes[0].signblock(block, "")
|
||||
|
||||
assert_raises_rpc_error(-25, "Could not sign the block.", self.nodes[1].signblock, block, "")
|
||||
comb_result = self.nodes[0].combineblocksigs(block, sig, "")
|
||||
assert comb_result["complete"]
|
||||
self.nodes[1].submitblock(comb_result["hex"])
|
||||
self.sync_all()
|
||||
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockcount(), cur_height+10)
|
||||
|
||||
chain_info = self.nodes[0].getblockchaininfo()
|
||||
fedpeg_info = self.nodes[0].getsidechaininfo()
|
||||
assert_equal(chain_info["current_signblock_hex"], new_signblock)
|
||||
assert_equal(chain_info["max_block_witness"], 108)
|
||||
assert_equal(chain_info["extension_space"], new_extension)
|
||||
assert_equal(fedpeg_info["current_fedpegscripts"], ["53", "52"])
|
||||
|
||||
# Now node 1 is the signer
|
||||
block = self.nodes[0].getnewblockhex()
|
||||
sig = self.nodes[1].signblock(block, "")
|
||||
assert_raises_rpc_error(-25, "Could not sign the block.", self.nodes[0].signblock, block, "")
|
||||
comb_result = self.nodes[1].combineblocksigs(block, sig, "")
|
||||
assert comb_result["complete"]
|
||||
self.nodes[0].submitblock(comb_result["hex"])
|
||||
assert_equal(self.nodes[0].getblockcount(), cur_height+11)
|
||||
|
||||
def test_transition_mempool_eject(self):
|
||||
self.log.info("Testing mempool (r)ejection policy on transitions...")
|
||||
# node 1 is still signer, let's transition to something we can PAK peg-out to
|
||||
# and OP_TRUE fedpegscript, and set signblockscript back to OP_TRUE
|
||||
|
||||
WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"]
|
||||
xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"
|
||||
init_details = self.nodes[0].initpegoutwallet(xpub)
|
||||
pak_entry = init_details["pakentry"]
|
||||
# stitch the extension space together using the relevant keys
|
||||
extension_space = [pak_entry[4:4+66]+pak_entry[4+66+1:]]
|
||||
pak_prop = {"signblockscript":WSH_OP_TRUE, "max_block_witness":3, "fedpegscript":"51", "extension_space":extension_space}
|
||||
|
||||
epoch_info = self.nodes[0].getblockchaininfo()
|
||||
blocks_to_end = epoch_info["epoch_length"] - epoch_info["epoch_age"] - 1
|
||||
for _ in range(blocks_to_end):
|
||||
block = self.nodes[1].getnewblockhex(0, pak_prop)
|
||||
sig = self.nodes[1].signblock(block, "")
|
||||
comb_result = self.nodes[1].combineblocksigs(block, sig, "")
|
||||
assert comb_result["complete"]
|
||||
self.nodes[1].submitblock(comb_result["hex"])
|
||||
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[1].getblockchaininfo()["current_signblock_hex"], WSH_OP_TRUE)
|
||||
assert_equal(self.nodes[1].getsidechaininfo()["current_fedpegscripts"], ["51", "53"])
|
||||
|
||||
# Transactions
|
||||
|
||||
# Peg-in prep:
|
||||
# hack: since we're not validating peg-ins in parent chain, just make
|
||||
# both the funding and claim tx on same chain (printing money)
|
||||
fund_info = self.nodes[0].getpeginaddress()
|
||||
peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1)
|
||||
peg_tx = self.nodes[0].gettransaction(peg_id)["hex"]
|
||||
self.nodes[0].testmempoolaccept([peg_tx])
|
||||
# only one confirm needed in this setup, we do 10 to sync with epoch_length
|
||||
self.nodes[0].generatetoaddress(10, self.nodes[0].getnewaddress())
|
||||
proof = self.nodes[0].gettxoutproof([peg_id])
|
||||
raw_tx = self.nodes[0].gettransaction(peg_id)["hex"]
|
||||
|
||||
# Now, peg-in and PAK peg-out in node 0 mempool
|
||||
|
||||
# We need this transaction to get into the mempool, then transition
|
||||
# to new fedpegscript, then wait another epoch, to get dumped.
|
||||
claim_id = self.nodes[0].claimpegin(raw_tx, proof, fund_info["claim_script"])
|
||||
# saving for re-submission later
|
||||
raw_claim = self.nodes[0].gettransaction(claim_id)["hex"]
|
||||
# This transaction will be dumped as soon as transition activates
|
||||
pegout_id = self.nodes[0].sendtomainchain("", 1)["txid"]
|
||||
# Chain payment, this should get "recursively" kicked on transition
|
||||
pegout_child_id = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[0].getbalance()['bitcoin'], "", "", True)
|
||||
raw_pegout = self.nodes[0].gettransaction(pegout_id)["hex"]
|
||||
raw_pool = self.nodes[0].getrawmempool()
|
||||
assert claim_id in raw_pool
|
||||
assert pegout_id in raw_pool
|
||||
assert pegout_child_id in raw_pool
|
||||
|
||||
# node 1 is blocksonly, no mempool so it won't mine node 0's transactions
|
||||
assert_equal(self.nodes[1].getrawmempool(), [])
|
||||
# Now generate an epoch of blocks on node 1 to show that non-transitions don't dump
|
||||
# PAK or peg-in transactions from mempool
|
||||
self.nodes[1].generatetoaddress(10, self.nodes[1].getnewaddress())
|
||||
sync_blocks(self.nodes)
|
||||
assert_equal(self.nodes[0].getblockchaininfo()["epoch_age"], 9)
|
||||
|
||||
# Transactions are still in mempool
|
||||
raw_pool = self.nodes[0].getrawmempool()
|
||||
assert claim_id in raw_pool
|
||||
assert pegout_id in raw_pool
|
||||
assert pegout_child_id in raw_pool
|
||||
|
||||
# Now have node 1 transition to exact same pak and fedpegscript
|
||||
for _ in range(10):
|
||||
block = self.nodes[1].getnewblockhex(0, pak_prop)
|
||||
assert_equal(self.nodes[1].submitblock(block), None)
|
||||
|
||||
sync_blocks(self.nodes)
|
||||
assert_equal(self.nodes[0].getblockchaininfo()["epoch_age"], 9)
|
||||
|
||||
# After the 10th block, nothing gets the boot
|
||||
raw_pool = self.nodes[0].getrawmempool()
|
||||
assert claim_id in raw_pool
|
||||
assert pegout_id in raw_pool
|
||||
assert pegout_child_id in raw_pool
|
||||
|
||||
# Now have node 1 transition to new pak and fedpegscript
|
||||
pak_prop["fedpegscript"] = "52"
|
||||
pak_prop["extension_space"] = initial_extension
|
||||
for _ in range(10):
|
||||
raw_pool = self.nodes[0].getrawmempool()
|
||||
assert claim_id in raw_pool
|
||||
assert pegout_id in raw_pool
|
||||
assert pegout_child_id in raw_pool
|
||||
block = self.nodes[1].getnewblockhex(0, pak_prop)
|
||||
assert_equal(self.nodes[1].submitblock(block), None)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
assert_equal(self.nodes[0].getblockchaininfo()["epoch_age"], 9)
|
||||
|
||||
# After 10 blocks, PAK and child is booted, peg-in still lingers for 1 more epoch
|
||||
raw_pool = self.nodes[0].getrawmempool()
|
||||
assert claim_id in raw_pool
|
||||
assert pegout_id not in raw_pool
|
||||
assert pegout_child_id not in raw_pool
|
||||
|
||||
# Re-submission fails
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[0].sendrawtransaction, raw_pegout)
|
||||
|
||||
for _ in range(10):
|
||||
assert claim_id in self.nodes[0].getrawmempool()
|
||||
self.nodes[1].submitblock(self.nodes[1].getnewblockhex())
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
# After 10 blocks(no proposal), peg-in is finally dumped
|
||||
assert claim_id not in self.nodes[0].getrawmempool()
|
||||
|
||||
# Both claim and peg-out rejected from submission as well
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[0].sendrawtransaction, raw_pegout)
|
||||
assert_raises_rpc_error(-26, "pegin-no-witness, Peg-in tx is invalid.", self.nodes[0].sendrawtransaction, raw_claim)
|
||||
|
||||
# Now we test reorg behavior
|
||||
best_blockhash = self.nodes[0].getbestblockhash()
|
||||
|
||||
# Invalidate tip, peg-in should be allowed back into mempool but not pegout
|
||||
self.nodes[0].invalidateblock(best_blockhash)
|
||||
self.nodes[0].sendrawtransaction(raw_claim)
|
||||
assert claim_id in self.nodes[0].getrawmempool()
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[0].sendrawtransaction, raw_pegout)
|
||||
|
||||
# Reconsider best block, should be booted and invalid again
|
||||
self.nodes[0].reconsiderblock(best_blockhash)
|
||||
assert claim_id not in self.nodes[0].getrawmempool()
|
||||
|
||||
# Go back 20 blocks to let peg-out back in
|
||||
old_blockhash = self.nodes[0].getblockhash(self.nodes[0].getblockcount()-20)
|
||||
self.nodes[0].invalidateblock(old_blockhash)
|
||||
self.nodes[0].sendrawtransaction(raw_claim)
|
||||
self.nodes[0].sendrawtransaction(raw_pegout)
|
||||
assert claim_id in self.nodes[0].getrawmempool()
|
||||
assert pegout_id in self.nodes[0].getrawmempool()
|
||||
|
||||
# Again go back to tip, both booted and not let back in
|
||||
self.nodes[0].reconsiderblock(best_blockhash)
|
||||
assert claim_id not in self.nodes[0].getrawmempool()
|
||||
assert pegout_id not in self.nodes[0].getrawmempool()
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[0].sendrawtransaction, raw_pegout)
|
||||
assert_raises_rpc_error(-26, "pegin-no-witness, Peg-in tx is invalid.", self.nodes[0].sendrawtransaction, raw_claim)
|
||||
|
||||
|
||||
def run_test(self):
|
||||
self.test_legacy_params()
|
||||
self.test_dynafed_activation()
|
||||
self.test_illegal_proposals()
|
||||
self.test_no_vote()
|
||||
self.test_under_vote()
|
||||
self.test_four_fifth_vote()
|
||||
self.test_all_vote()
|
||||
self.test_transition_mempool_eject()
|
||||
|
||||
if __name__ == '__main__':
|
||||
DynaFedTest().main()
|
||||
|
|
@ -52,6 +52,10 @@ class FedPegTest(BitcoinTestFramework):
|
|||
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")
|
||||
parser.add_argument("--pre_transition", dest="pre_transition", default=False, action="store_true",
|
||||
help="Run test in dynafed activated chain, without a transition")
|
||||
parser.add_argument("--post_transition", dest="post_transition", default=False, action="store_true",
|
||||
help="Run test in dynafed activated chain, after transition and additional epoch to invalidate old fedpegscript")
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
|
@ -134,6 +138,10 @@ class FedPegTest(BitcoinTestFramework):
|
|||
'-con_parent_pegged_asset=%s' % parent_pegged_asset,
|
||||
])
|
||||
|
||||
# Immediate activation of dynafed when requested versus "never" from conf
|
||||
if self.options.pre_transition or self.options.post_transition:
|
||||
extra_args.extend(["-con_dyna_deploy_start=-1"])
|
||||
|
||||
# Use rpcuser auth only for first parent.
|
||||
if n==0:
|
||||
# Extract username and password from cookie file and use directly.
|
||||
|
|
@ -182,6 +190,22 @@ class FedPegTest(BitcoinTestFramework):
|
|||
#parent2 = self.nodes[1]
|
||||
sidechain = self.nodes[2]
|
||||
sidechain2 = self.nodes[3]
|
||||
|
||||
# If we're testing post-transition, force a fedpegscript transition and
|
||||
# getting rid of old fedpegscript by making at least another epoch pass by
|
||||
WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"]
|
||||
# We just randomize the keys a bit to get another valid fedpegscript
|
||||
new_fedpegscript = sidechain.tweakfedpegscript("f00dbabe")["script"]
|
||||
if self.options.post_transition:
|
||||
print("Running test post-transition")
|
||||
for _ in range(30):
|
||||
block_hex = sidechain.getnewblockhex(0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":10, "fedpegscript":new_fedpegscript, "extension_space":[]})
|
||||
sidechain.submitblock(block_hex)
|
||||
assert_equal(sidechain.getsidechaininfo()["current_fedpegscripts"], [new_fedpegscript]*2)
|
||||
|
||||
if self.options.pre_transition:
|
||||
print("Running test pre-transition, dynafed activated from first block")
|
||||
|
||||
for node in self.nodes:
|
||||
node.importprivkey(privkey=node.get_deterministic_priv_key().key, label="mining")
|
||||
util.node_fastmerkle = sidechain
|
||||
|
|
@ -294,6 +318,10 @@ class FedPegTest(BitcoinTestFramework):
|
|||
if sidechain.gettransaction(pegtxid1)["confirmations"] != 0:
|
||||
raise Exception("Peg-in didn't unconfirm after invalidateblock call.")
|
||||
|
||||
# Re-org causes peg-ins to get booted(wallet will resubmit in 10 minutes)
|
||||
assert_equal(sidechain.getrawmempool(), [])
|
||||
sidechain.sendrawtransaction(tx1["hex"])
|
||||
|
||||
# Create duplicate claim, put it in block along with current one in mempool
|
||||
# to test duplicate-in-block claims between two txs that are in the same block.
|
||||
raw_pegin = sidechain.createrawpegin(raw, proof)["hex"]
|
||||
|
|
|
|||
|
|
@ -3,206 +3,33 @@
|
|||
# Distributed under the MIT/X11 software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, Decimal
|
||||
import copy
|
||||
import time
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, Decimal, assert_greater_than, sync_mempools
|
||||
|
||||
def pak_to_option(pak):
|
||||
return list(map(lambda x: "-pak=%s:%s" % (x[0], x[1]), pak))
|
||||
'''
|
||||
This test focuses on enforcement of PAK, not on transitioning lists
|
||||
or RPC return values for PAK enforcement settings which is covered
|
||||
in feature_dynafed.py
|
||||
|
||||
# This tests a PAK list transition from the genesis state ('reject') to pak1 to
|
||||
# 'reject' and finally to pak2. There are 5 nodes each with different
|
||||
# configurations
|
||||
# All nodes validate pegouts but the first one
|
||||
|
||||
# The node at index 0 doesn't validate pegouts, just normal standardness
|
||||
i_novalidate = 0
|
||||
# The node at index 1 has no paklist in config
|
||||
i_undefined = 1
|
||||
# Paklist 1 in config
|
||||
i_pak1 = 2
|
||||
# Paklist 2 in config
|
||||
i_pak2 = 3
|
||||
# Reject in config
|
||||
i_reject = 4
|
||||
|
||||
# The two conflicting pak lists
|
||||
pak1 = [("02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f", "02a28b3078b6fe9d2b0f098ffb491b8e98a7fe56ebe321ba52f90becdd06507bbf"),
|
||||
("02101bed11081c19b25e02dd618da53af1ba29849bbe4006fb3d6e2d3b0d874405", "02c9cf4bdef23d38e6c9ae73b83001711debea113573cfbe0fb729ff81638549da")]
|
||||
pak2 = [("03767a74373b7207c5ae1214295197a88ec2abdf92e9e2a29daf024c322fae9fcb", "033e4740d0ba639e28963f3476157b7cf2fb7c6fdf4254f97099cf8670b505ea59"),
|
||||
("02f4a7445f9c48ee8590a930d3fc4f0f5763e3d1d003fdf5fc822e7ba18f380632", "036b3786f029751ada9f02f519a86c7e02fb2963a7013e7e668eb5f7ec069b9e7e")]
|
||||
|
||||
# Args that will be re-used in slightly different ways across runs
|
||||
args = [["-acceptnonstdtxn=0", "-initialfreecoins=100000000", "-parent_bech32_hrp=lol", "-pubkeyprefix=112", "-scriptprefix=197"]] \
|
||||
+ [["-acceptnonstdtxn=0", "-enforce_pak=1", "-initialfreecoins=100000000", "-parent_bech32_hrp=lol", "-pubkeyprefix=112", "-scriptprefix=197"]]*4
|
||||
args[i_reject] = args[i_reject] + ['-pak=reject']
|
||||
# Novalidate has pak entry, should not act on it ever
|
||||
args[i_novalidate] = args[i_novalidate] + pak_to_option(pak1)
|
||||
TODO: Test non-activated dynafed means no block enforcement of PAK
|
||||
'''
|
||||
|
||||
class PAKTest (BitcoinTestFramework):
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 5
|
||||
self.num_nodes = 3
|
||||
self.setup_clean_chain = True
|
||||
self.extra_args = copy.deepcopy(args)
|
||||
self.extra_args[i_pak1] = self.extra_args[i_pak1] + pak_to_option(pak1)
|
||||
self.extra_args[i_pak2] = self.extra_args[i_pak2] + pak_to_option(pak2)
|
||||
self.extra_args = [["-enforce_pak=1", "-con_dyna_deploy_start=-1", "-initialfreecoins=210000000000000", "-anyonecanspendaremine=1", "-parent_bech32_hrp=lol", "-pubkeyprefix=112", "-scriptprefix=197", "-con_connect_genesis_outputs=1"] for i in range(self.num_nodes)]
|
||||
# First node doesn't enforce PAK, a "HF" of the other two nodes
|
||||
self.extra_args[0] = self.extra_args[0][1:]
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
for node in self.nodes:
|
||||
addr = node.getnewaddress()
|
||||
unconf = node.validateaddress(addr)["unconfidential"]
|
||||
privkey = node.dumpprivkey(addr)
|
||||
node.set_deterministic_priv_key(unconf, privkey)
|
||||
|
||||
# Give novalidate 50 BTC
|
||||
self.nodes[i_novalidate].generate(101)
|
||||
self.sync_all()
|
||||
# Transitioning PAK lists and checking lists in RPC tested in feature_dynafed
|
||||
|
||||
# This function tests the result of the getpakinfo RPC.
|
||||
# *_pak is either False (undefined paklist), "reject" or a list of
|
||||
# (online, offline) tuples
|
||||
def test_pak(node, config_pak, block_pak):
|
||||
getpakinfo = node.getpakinfo()
|
||||
|
||||
def compare(actual, expected):
|
||||
if expected is False:
|
||||
assert_equal(actual, {})
|
||||
elif "reject" in expected:
|
||||
assert_equal(actual['offline'], [])
|
||||
assert_equal(actual['online'], [])
|
||||
assert_equal(actual['reject'], True)
|
||||
else:
|
||||
offline = list(map(lambda x: x[0], expected))
|
||||
online = list(map(lambda x: x[1], expected))
|
||||
assert_equal(actual['offline'], offline)
|
||||
assert_equal(actual['online'], online)
|
||||
assert_equal(actual['reject'], False)
|
||||
compare(getpakinfo['config_paklist'], config_pak)
|
||||
compare(getpakinfo['block_paklist'], block_pak)
|
||||
|
||||
# In the beginning the blockchain paklist is "reject"
|
||||
test_pak(self.nodes[i_novalidate], pak1, "reject")
|
||||
test_pak(self.nodes[i_undefined], False, "reject")
|
||||
test_pak(self.nodes[i_pak1], pak1, "reject")
|
||||
test_pak(self.nodes[i_pak2], pak2, "reject")
|
||||
test_pak(self.nodes[i_reject], "reject", "reject")
|
||||
|
||||
# i_novalidate creates block without a commitment
|
||||
block_proposal = self.nodes[i_novalidate].getnewblockhex()
|
||||
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_pak1].testproposedblock, block_proposal)
|
||||
|
||||
# i_undefined creates a block without a commitment
|
||||
block_proposal = self.nodes[i_undefined].getnewblockhex()
|
||||
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_pak1].testproposedblock, block_proposal)
|
||||
|
||||
|
||||
# PAK transition: reject -> pak1
|
||||
# Create a new block with node i_pak1. Because it contains a commitment
|
||||
# to pak1 it should be rejected by i_pak2 and i_reject.
|
||||
block_proposal = self.nodes[i_pak1].getnewblockhex()
|
||||
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_pak1].testproposedblock(block_proposal), None)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_pak2].testproposedblock, block_proposal)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_reject].testproposedblock, block_proposal)
|
||||
|
||||
# Submit block with commitment to pak1 and check each node's state.
|
||||
self.nodes[i_undefined].submitblock(block_proposal)
|
||||
self.sync_all()
|
||||
test_pak(self.nodes[i_novalidate], pak1, pak1)
|
||||
test_pak(self.nodes[i_undefined], False, pak1)
|
||||
test_pak(self.nodes[i_pak1], pak1, pak1)
|
||||
test_pak(self.nodes[i_pak2], pak2, pak1)
|
||||
test_pak(self.nodes[i_reject], "reject", pak1)
|
||||
# Check that another block by i_pak1 (without a commitment) is valid to
|
||||
# i_pak1 but invalid to i_pak2 and i_reject
|
||||
block_proposal = self.nodes[i_undefined].getnewblockhex()
|
||||
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_pak1].testproposedblock(block_proposal), None)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_pak2].testproposedblock, block_proposal)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_reject].testproposedblock, block_proposal)
|
||||
|
||||
# PAK transition: pak1 -> reject
|
||||
# Create a new block with i_reject which should have a "reject" commitment
|
||||
# and check that it's correctly rejected or accepted.
|
||||
block_proposal = self.nodes[i_reject].getnewblockhex()
|
||||
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_pak1].testproposedblock, block_proposal)
|
||||
|
||||
assert_equal(self.nodes[i_reject].testproposedblock(block_proposal), None)
|
||||
# Submit "reject" block and check state.
|
||||
self.nodes[i_undefined].submitblock(block_proposal)
|
||||
self.sync_all()
|
||||
test_pak(self.nodes[i_novalidate], pak1, "reject")
|
||||
test_pak(self.nodes[i_undefined], False, "reject")
|
||||
test_pak(self.nodes[i_pak1], pak1, "reject")
|
||||
test_pak(self.nodes[i_pak2], pak2, "reject")
|
||||
test_pak(self.nodes[i_reject], "reject", "reject")
|
||||
# Check that another block by i_reject (without a commitment) is valid to i_reject.
|
||||
block_proposal = self.nodes[i_reject].getnewblockhex()
|
||||
assert_equal(self.nodes[i_reject].testproposedblock(block_proposal), None)
|
||||
|
||||
# Check that i_undefined can't peg-out because of the pegout freeze.
|
||||
assert_raises_rpc_error(-5, "Pegout freeze is under effect", self.nodes[i_undefined].sendtomainchain, "", 1)
|
||||
|
||||
assert_raises_rpc_error(-3, "`address` argument must be \"\" for PAK-enabled networks as the address is generated automatically.", self.nodes[i_undefined].sendtomainchain, "n3NkSZqoPMCQN5FENxUBw4qVATbytH6FDK", 1)
|
||||
|
||||
# PAK transition: reject -> pak2
|
||||
# Restart nodes while putting pak2 in i_pak1's config instead of pak1.
|
||||
self.stop_nodes()
|
||||
extra_args = copy.deepcopy(args)
|
||||
extra_args[i_pak1] = extra_args[i_pak1] + pak_to_option(pak2)
|
||||
extra_args[i_pak2] = extra_args[i_pak2] + pak_to_option(pak2)
|
||||
# Also test novalidate behaves correctly when set to reject after removing
|
||||
# the two pak entries
|
||||
extra_args[i_novalidate] = extra_args[i_novalidate][:-2] + ['-pak=reject']
|
||||
|
||||
# Restart and connect peers
|
||||
self.start_nodes(extra_args)
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,2,3)
|
||||
connect_nodes_bi(self.nodes,3,4)
|
||||
|
||||
# Check current state of i_pak1
|
||||
test_pak(self.nodes[i_pak1], pak2, "reject")
|
||||
# Create a new block with i_pak1 which should have a commitment to pak2
|
||||
# and check that it's correctly rejected or accepted.
|
||||
block_proposal = self.nodes[i_pak1].getnewblockhex()
|
||||
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_pak1].testproposedblock(block_proposal), None)
|
||||
assert_equal(self.nodes[i_pak2].testproposedblock(block_proposal), None)
|
||||
|
||||
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_reject].testproposedblock, block_proposal)
|
||||
|
||||
# Submit block with commitment to pak2 and check state.
|
||||
self.nodes[i_pak1].submitblock(block_proposal)
|
||||
self.sync_all()
|
||||
test_pak(self.nodes[i_novalidate], "reject", pak2)
|
||||
test_pak(self.nodes[i_undefined], False, pak2)
|
||||
test_pak(self.nodes[i_pak1], pak2, pak2)
|
||||
test_pak(self.nodes[i_pak2], pak2, pak2)
|
||||
test_pak(self.nodes[i_reject], "reject", pak2)
|
||||
|
||||
# Reset PAK conf arguments to start to test mempool acceptance and wallet
|
||||
self.log.info("Test wallet PAK")
|
||||
|
||||
# We will re-use the same xpub, but each wallet will create its own online pak
|
||||
# so the lists will be incompatible, even if all else was synced
|
||||
|
|
@ -210,7 +37,7 @@ class PAKTest (BitcoinTestFramework):
|
|||
xpub_desc = "pkh("+xpub+"/0/*)" # Transform this into a descriptor
|
||||
init_results = []
|
||||
info_results = []
|
||||
for i in range(5):
|
||||
for i in range(self.num_nodes):
|
||||
if i == 0:
|
||||
assert_raises_rpc_error(-8, "PAK enforcement is not enabled on this network.", self.nodes[i].initpegoutwallet, xpub)
|
||||
init_results += [None]
|
||||
|
|
@ -232,127 +59,133 @@ class PAKTest (BitcoinTestFramework):
|
|||
|
||||
# Use custom derivation counter values, check if stored correctly,
|
||||
# address lookahead looks correct and that new liquid_pak was chosen
|
||||
assert_raises_rpc_error(-8, "bip32_counter must be between 0 and 1,000,000,000, inclusive.", self.nodes[i_undefined].initpegoutwallet, xpub, -1)
|
||||
assert_raises_rpc_error(-8, "bip32_counter must be between 0 and 1,000,000,000, inclusive.", self.nodes[1].initpegoutwallet, xpub, -1)
|
||||
|
||||
assert_raises_rpc_error(-8, "bip32_counter must be between 0 and 1,000,000,000, inclusive.", self.nodes[1].initpegoutwallet, xpub, 1000000001)
|
||||
|
||||
assert_raises_rpc_error(-8, "bip32_counter must be between 0 and 1,000,000,000, inclusive.", self.nodes[i_undefined].initpegoutwallet, xpub, 1000000001)
|
||||
new_init = self.nodes[1].initpegoutwallet(xpub, 2)
|
||||
assert_equal(self.nodes[1].getwalletpakinfo()["bip32_counter"], "2")
|
||||
assert_equal(new_init["address_lookahead"][0], init_results[1]["address_lookahead"][2])
|
||||
assert(new_init["liquid_pak"] != init_results[1]["liquid_pak"])
|
||||
|
||||
new_init = self.nodes[i_undefined].initpegoutwallet(xpub, 2)
|
||||
assert_equal(self.nodes[i_undefined].getwalletpakinfo()["bip32_counter"], "2")
|
||||
assert_equal(new_init["address_lookahead"][0], init_results[i_undefined]["address_lookahead"][2])
|
||||
assert(new_init["liquid_pak"] != init_results[i_undefined]["liquid_pak"])
|
||||
|
||||
# Load additional pak entry for each, restart (reject node disallows pak list in conf)
|
||||
# By adding different pak entries, all nodes that validate the list should conflict
|
||||
# Restart and connect peers to check wallet persistence
|
||||
self.stop_nodes()
|
||||
extra_args = copy.deepcopy(args)
|
||||
extra_args[i_pak1] = extra_args[i_pak1]+["-"+init_results[i_pak1]["pakentry"]]
|
||||
extra_args[i_pak2] = extra_args[i_pak2]+["-"+init_results[i_pak2]["pakentry"]]
|
||||
|
||||
# Restart and connect peers
|
||||
self.start_nodes(extra_args)
|
||||
self.start_nodes()
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,2,3)
|
||||
connect_nodes_bi(self.nodes,3,4)
|
||||
|
||||
# Check PAK settings persistence in wallet across restart
|
||||
restarted_info = self.nodes[i_undefined].getwalletpakinfo()
|
||||
restarted_info = self.nodes[1].getwalletpakinfo()
|
||||
assert_equal(restarted_info["bitcoin_descriptor"], xpub_desc)
|
||||
assert_equal(restarted_info["liquid_pak"], new_init["liquid_pak"])
|
||||
assert_equal(restarted_info["bip32_counter"], "2")
|
||||
|
||||
# Have nodes send pegouts, check it fails to enter mempool of other nodes with incompatible
|
||||
# PAK settings
|
||||
self.nodes[i_novalidate].sendmany("", {self.nodes[i_undefined].getnewaddress():10, self.nodes[i_pak1].getnewaddress():10, self.nodes[i_pak2].getnewaddress():10, self.nodes[i_reject].getnewaddress():10})
|
||||
self.nodes[i_novalidate].generate(1)
|
||||
# Compile list of extension space entries for pak enforcement
|
||||
extension_space_proposal = []
|
||||
for entry in init_results:
|
||||
if entry is not None:
|
||||
pakentry = entry["pakentry"]
|
||||
extension_space_proposal += [pakentry[4:4+66]+pakentry[4+66+1:]]
|
||||
|
||||
self.log.info("Test mempool enforcement of PAK peg-outs")
|
||||
|
||||
# Transition to a pak list that only node 1 can peg-out to
|
||||
WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"]
|
||||
for _ in range(9):
|
||||
block = self.nodes[1].getnewblockhex(0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":3, "fedpegscript":"51", "extension_space":[extension_space_proposal[0]]})
|
||||
assert_equal(self.nodes[1].submitblock(block), None)
|
||||
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[0].getblockchaininfo()["extension_space"], [extension_space_proposal[0]])
|
||||
|
||||
# pak1 generates a block, creating block commitment
|
||||
self.nodes[i_pak1].generate(1)
|
||||
self.sync_all()
|
||||
# node 1 has wrong pak entry in wallet
|
||||
assert_raises_rpc_error(-4, "Given online key is not in Pegout Authorization Key List", self.nodes[1].sendtomainchain, "", 1)
|
||||
|
||||
# pak1 will now create a pegout.
|
||||
pak1_pegout_txid = self.nodes[i_pak1].sendtomainchain("", 1)["txid"]
|
||||
assert_equal(self.nodes[i_pak1].getwalletpakinfo()["bip32_counter"], "1")
|
||||
# Also spend the change to make chained payment that will be rejected as well
|
||||
pak1_child_txid = self.nodes[i_pak1].sendtoaddress(self.nodes[i_pak1].getnewaddress(), self.nodes[i_pak1].getbalance()['bitcoin'], "", "", True)
|
||||
# put back init_info version that's in pak list
|
||||
self.nodes[1].initpegoutwallet(xpub, 0, init_results[1]["liquid_pak"])
|
||||
|
||||
# Node 1 will now make a PAK peg-out, accepted in all mempools and blocks
|
||||
pegout_info = self.nodes[1].sendtomainchain("", 1)
|
||||
raw_node1_pegout = self.nodes[1].gettransaction(pegout_info["txid"])["hex"]
|
||||
self.sync_all() # mempool sync
|
||||
self.nodes[1].generatetoaddress(1, self.nodes[0].getnewaddress())
|
||||
self.sync_all() # block sync
|
||||
assert_greater_than(self.nodes[1].gettransaction(pegout_info["txid"])["confirmations"], 0)
|
||||
|
||||
# Wait for node("follow the leader" conf-undefined) to get transaction in
|
||||
time_to_wait = 15
|
||||
while time_to_wait > 0:
|
||||
# novalidate doesn't allow >80 byte op_return outputs due to no enforce_pak
|
||||
if (pak1_pegout_txid not in self.nodes[i_novalidate].getrawmempool() and
|
||||
pak1_pegout_txid in self.nodes[i_undefined].getrawmempool() and
|
||||
pak1_pegout_txid not in self.nodes[i_pak2].getrawmempool() and
|
||||
pak1_pegout_txid not in self.nodes[i_reject].getrawmempool()):
|
||||
break
|
||||
time_to_wait -= 1
|
||||
time.sleep(1)
|
||||
assert(time_to_wait > 0)
|
||||
# Re-org keep node 1 peg-out unconfirmed and transition to "full list"
|
||||
# then check peg-out fails
|
||||
|
||||
# pak_reject will make a block commitment, causing all validating nodes to dump
|
||||
# the peg transaction
|
||||
self.nodes[i_reject].generate(1)
|
||||
# Invalidate back to block 1, then make 9 new blocks to hit transition
|
||||
# If you roll back to genesis block p2p code gets flakey
|
||||
num_block_rollback = self.nodes[1].getblockcount()-2
|
||||
fork_hash = self.nodes[1].getblockhash(2)
|
||||
for i in range(self.num_nodes):
|
||||
self.nodes[i].invalidateblock(fork_hash)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
assert_equal(pak1_pegout_txid in self.nodes[i_novalidate].getrawmempool(), False)
|
||||
assert_equal(pak1_pegout_txid in self.nodes[i_undefined].getrawmempool(), False)
|
||||
assert_equal(pak1_pegout_txid in self.nodes[i_pak1].getrawmempool(), True)
|
||||
assert_equal(pak1_pegout_txid in self.nodes[i_pak2].getrawmempool(), False)
|
||||
assert_equal(pak1_pegout_txid in self.nodes[i_reject].getrawmempool(), False)
|
||||
for _ in range(num_block_rollback):
|
||||
block = self.nodes[1].getnewblockhex(0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":3, "fedpegscript":"51", "extension_space":extension_space_proposal})
|
||||
self.nodes[1].submitblock(block)
|
||||
|
||||
assert_equal(self.nodes[i_pak1].gettransaction(pak1_pegout_txid)["confirmations"], 0)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
# Make sure child payment also bumped from mempool
|
||||
assert_equal(pak1_child_txid in self.nodes[i_novalidate].getrawmempool(), False)
|
||||
assert_equal(pak1_child_txid in self.nodes[i_undefined].getrawmempool(), False)
|
||||
assert_equal(pak1_child_txid in self.nodes[i_pak1].getrawmempool(), True)
|
||||
assert_equal(pak1_child_txid in self.nodes[i_pak2].getrawmempool(), False)
|
||||
assert_equal(pak1_child_txid in self.nodes[i_reject].getrawmempool(), False)
|
||||
# node 0 puts the peg-out back in its mempool, can't sync all
|
||||
self.nodes[0].sendrawtransaction(raw_node1_pegout)
|
||||
sync_mempools(self.nodes[1:])
|
||||
|
||||
# rejected in mempool
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[1].sendrawtransaction, raw_node1_pegout)
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[2].sendrawtransaction, raw_node1_pegout)
|
||||
# node 0 tries to make bad block
|
||||
wrong_pak_prop = self.nodes[0].getnewblockhex()
|
||||
# rejected in blocks
|
||||
assert_raises_rpc_error(-25, "bad-pak-tx", self.nodes[1].testproposedblock, wrong_pak_prop, True)
|
||||
assert_raises_rpc_error(-25, "bad-pak-tx", self.nodes[2].testproposedblock, wrong_pak_prop, True)
|
||||
|
||||
self.log.info("Test various RPC arguments")
|
||||
|
||||
assert_equal(self.nodes[i_pak1].gettransaction(pak1_child_txid)["confirmations"], 0)
|
||||
# Fail to peg-out too-small value
|
||||
assert_raises_rpc_error(-8, "Invalid amount for send, must send more than 0.0001 BTC", self.nodes[i_undefined].sendtomainchain, "", Decimal('0.0009'))
|
||||
assert_raises_rpc_error(-8, "Invalid amount for send, must send more than 0.0001 BTC", self.nodes[1].sendtomainchain, "", Decimal('0.0009'))
|
||||
|
||||
# Use wrong network's extended pubkey
|
||||
mainnetxpub = "xpub6AATBi58516uxLogbuaG3jkom7x1qyDoZzMN2AePBuQnMFKUV9xC2BW9vXsFJ9rELsvbeGQcFWhtbyM4qDeijM22u3AaSiSYEvuMZkJqtLn"
|
||||
assert_raises_rpc_error(-8, "bitcoin_descriptor is not a valid descriptor string.", self.nodes[i_undefined].initpegoutwallet, mainnetxpub)
|
||||
assert_raises_rpc_error(-8, "bitcoin_descriptor is not a valid descriptor string.", self.nodes[1].initpegoutwallet, mainnetxpub)
|
||||
|
||||
# Test fixed online pubkey
|
||||
init_info = self.nodes[i_pak1].initpegoutwallet(xpub)
|
||||
init_info2 = self.nodes[i_pak1].initpegoutwallet(xpub, 0, init_info['liquid_pak'])
|
||||
init_info = self.nodes[1].initpegoutwallet(xpub)
|
||||
init_info2 = self.nodes[1].initpegoutwallet(xpub, 0, init_info['liquid_pak'])
|
||||
assert_equal(init_info, init_info2)
|
||||
init_info3 = self.nodes[i_pak1].initpegoutwallet(xpub)
|
||||
init_info3 = self.nodes[1].initpegoutwallet(xpub)
|
||||
assert(init_info != init_info3)
|
||||
|
||||
# Test Descriptor PAK Support
|
||||
|
||||
# Non-supported descriptors
|
||||
assert_raises_rpc_error(-8, "bitcoin_descriptor is not of any type supported: pkh(<xpub>), sh(wpkh(<xpub>)), wpkh(<xpub>), or <xpub>.", self.nodes[i_pak1].initpegoutwallet, "pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/*)")
|
||||
assert_raises_rpc_error(-8, "bitcoin_descriptor is not of any type supported: pkh(<xpub>), sh(wpkh(<xpub>)), wpkh(<xpub>), or <xpub>.", self.nodes[1].initpegoutwallet, "pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/*)")
|
||||
|
||||
assert_raises_rpc_error(-8, "bitcoin_descriptor must be a ranged descriptor.", self.nodes[i_pak1].initpegoutwallet, "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)")
|
||||
assert_raises_rpc_error(-8, "bitcoin_descriptor must be a ranged descriptor.", self.nodes[1].initpegoutwallet, "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)")
|
||||
|
||||
# Peg out with each new type, check that destination script matches
|
||||
wpkh_desc = "wpkh("+xpub+"/0/*)"
|
||||
wpkh_info = self.nodes[i_pak1].initpegoutwallet(wpkh_desc)
|
||||
wpkh_pak_info = self.nodes[i_pak1].getwalletpakinfo()
|
||||
wpkh_info = self.nodes[1].initpegoutwallet(wpkh_desc)
|
||||
wpkh_pak_info = self.nodes[1].getwalletpakinfo()
|
||||
|
||||
# Add to pak list for pak1, restart
|
||||
self.stop_nodes()
|
||||
extra_args = copy.deepcopy(args)
|
||||
extra_args[i_pak1] = extra_args[i_pak1]+["-"+wpkh_info["pakentry"]]
|
||||
self.start_nodes(extra_args)
|
||||
# Transition to wpkh entry list
|
||||
wpkh_pak_entry = wpkh_info["pakentry"]
|
||||
wpkh_pak_prop = [wpkh_pak_entry[4:4+66]+wpkh_pak_entry[4+66+1:]]
|
||||
for _ in range(10):
|
||||
block = self.nodes[1].getnewblockhex(0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":3, "fedpegscript":"51", "extension_space":wpkh_pak_prop})
|
||||
self.nodes[1].submitblock(block)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
# Make block commitment and get some block subsidy
|
||||
self.nodes[i_pak1].generate(101)
|
||||
wpkh_stmc = self.nodes[i_pak1].sendtomainchain("", 1)
|
||||
# Get some block subsidy and send off
|
||||
self.nodes[1].generatetoaddress(101, self.nodes[1].getnewaddress())
|
||||
wpkh_stmc = self.nodes[1].sendtomainchain("", 1)
|
||||
wpkh_txid = wpkh_stmc['txid']
|
||||
|
||||
# Also check some basic return fields of sendtomainchain with pak
|
||||
assert_equal(wpkh_stmc["bitcoin_address"], wpkh_info["address_lookahead"][0])
|
||||
validata = self.nodes[i_pak1].validateaddress(wpkh_stmc["bitcoin_address"])
|
||||
validata = self.nodes[1].validateaddress(wpkh_stmc["bitcoin_address"])
|
||||
assert(not validata["isvalid"])
|
||||
assert(validata["isvalid_parent"])
|
||||
assert(not validata["parent_address_info"]["isscript"])
|
||||
|
|
@ -361,32 +194,28 @@ class PAKTest (BitcoinTestFramework):
|
|||
assert_equal(wpkh_pak_info["bitcoin_descriptor"], wpkh_stmc["bitcoin_descriptor"])
|
||||
|
||||
sh_wpkh_desc = "sh(wpkh("+xpub+"/0/1/*))"
|
||||
sh_wpkh_info = self.nodes[i_pak1].initpegoutwallet(sh_wpkh_desc)
|
||||
sh_wpkh_info = self.nodes[1].initpegoutwallet(sh_wpkh_desc)
|
||||
|
||||
validata = self.nodes[i_pak1].validateaddress(sh_wpkh_info["address_lookahead"][0])
|
||||
validata = self.nodes[1].validateaddress(sh_wpkh_info["address_lookahead"][0])
|
||||
assert(not validata["isvalid"])
|
||||
assert(validata["isvalid_parent"])
|
||||
assert(validata["parent_address_info"]["isscript"])
|
||||
assert(not validata["parent_address_info"]["iswitness"])
|
||||
|
||||
# Add to pak list for pak1, restart
|
||||
self.stop_nodes()
|
||||
extra_args = copy.deepcopy(args)
|
||||
extra_args[i_pak1] = extra_args[i_pak1]+["-"+sh_wpkh_info["pakentry"]]
|
||||
# Transition to sh_wpkh entry list
|
||||
sh_wpkh_pak_entry = sh_wpkh_info["pakentry"]
|
||||
sh_wpkh_pak_prop = [sh_wpkh_pak_entry[4:4+66]+sh_wpkh_pak_entry[4+66+1:]]
|
||||
for _ in range(10):
|
||||
block = self.nodes[1].getnewblockhex(0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":3, "fedpegscript":"51", "extension_space":sh_wpkh_pak_prop})
|
||||
self.nodes[1].submitblock(block)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
# Restart and connect peers
|
||||
self.start_nodes(extra_args)
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,2,3)
|
||||
connect_nodes_bi(self.nodes,3,4)
|
||||
|
||||
self.nodes[i_pak1].generate(1)
|
||||
sh_wpkh_txid = self.nodes[i_pak1].sendtomainchain("", 1)['txid']
|
||||
self.nodes[1].generatetoaddress(1, self.nodes[1].getnewaddress())
|
||||
sh_wpkh_txid = self.nodes[1].sendtomainchain("", 1)['txid']
|
||||
|
||||
# Make sure peg-outs look correct
|
||||
wpkh_raw = self.nodes[i_pak1].decoderawtransaction(self.nodes[i_pak1].gettransaction(wpkh_txid)['hex'])
|
||||
sh_wpkh_raw = self.nodes[i_pak1].decoderawtransaction(self.nodes[i_pak1].gettransaction(sh_wpkh_txid)['hex'])
|
||||
wpkh_raw = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(wpkh_txid)['hex'])
|
||||
sh_wpkh_raw = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(sh_wpkh_txid)['hex'])
|
||||
|
||||
peg_out_found = False
|
||||
for output in wpkh_raw["vout"]:
|
||||
|
|
@ -410,9 +239,36 @@ class PAKTest (BitcoinTestFramework):
|
|||
raise Exception("Found unexpected peg-out output")
|
||||
assert(peg_out_found)
|
||||
|
||||
# Make sure they all confirm
|
||||
self.nodes[1].generatetoaddress(1, self.nodes[0].getnewaddress())
|
||||
for tx_id in [wpkh_txid, sh_wpkh_txid]:
|
||||
assert_greater_than(self.nodes[1].gettransaction(tx_id)["confirmations"], 0)
|
||||
|
||||
self.log.info("Test that pak-less pegouts are rejected")
|
||||
|
||||
# Last test of a pak-less peg-out failing to get into mempool/block
|
||||
# Note it leaves a transaction in node 0's mempool, so sync_all cannot
|
||||
# work after unless it's somehow booted.
|
||||
|
||||
# node 0 will now create a pegout, will fail to enter mempool of node 1 or 2
|
||||
# since it's pak-less
|
||||
nopak_pegout_txid = self.nodes[0].sendtomainchain("n3NkSZqoPMCQN5FENxUBw4qVATbytH6FDK", 1)
|
||||
raw_pakless_pegout = self.nodes[0].gettransaction(nopak_pegout_txid)["hex"]
|
||||
assert nopak_pegout_txid in self.nodes[0].getrawmempool()
|
||||
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[1].sendrawtransaction, raw_pakless_pegout)
|
||||
assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[2].sendrawtransaction, raw_pakless_pegout)
|
||||
|
||||
# node 0 makes a block that includes the pakless pegout, rejected by others
|
||||
# by consensus
|
||||
bad_prop = self.nodes[0].getnewblockhex()
|
||||
assert_raises_rpc_error(-25, "bad-pak-tx", self.nodes[1].testproposedblock, bad_prop, True)
|
||||
assert_raises_rpc_error(-25, "bad-pak-tx", self.nodes[2].testproposedblock, bad_prop, True)
|
||||
|
||||
# Test that subtracting fee from output works
|
||||
self.nodes[i_pak1].sendtomainchain("", self.nodes[i_pak1].getbalance()["bitcoin"], True)
|
||||
assert_equal(self.nodes[i_pak1].getbalance()["bitcoin"], 0)
|
||||
self.nodes[1].generatetoaddress(101, self.nodes[1].getnewaddress())
|
||||
self.nodes[1].sendtomainchain("", self.nodes[1].getbalance()["bitcoin"], True)
|
||||
assert_equal(self.nodes[1].getbalance()["bitcoin"], 0)
|
||||
|
||||
# TODO: create rawsendtomainchain to do transaction surgery for testing
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
|||
'-txindex',
|
||||
'-reindex', # Need reindex for txindex
|
||||
'-acceptnonstdtxn=0', # Try to mimic main-net
|
||||
'-multi_data_permitted=1', # Elements test
|
||||
]] * self.num_nodes
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
|
|
|
|||
|
|
@ -63,8 +63,12 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
'bip9_softforks',
|
||||
'blocks',
|
||||
'chain',
|
||||
'current_signblock_asm',
|
||||
'current_signblock_hex',
|
||||
'extension_space',
|
||||
'headers',
|
||||
'initialblockdownload',
|
||||
'max_block_witness',
|
||||
'mediantime',
|
||||
'pruned',
|
||||
'signblock_asm',
|
||||
|
|
@ -92,6 +96,7 @@ class BlockchainTest(BitcoinTestFramework):
|
|||
|
||||
self.restart_node(0, ['-stopatheight=207'])
|
||||
res = self.nodes[0].getblockchaininfo()
|
||||
|
||||
# should have exact keys
|
||||
assert_equal(sorted(res.keys()), keys)
|
||||
|
||||
|
|
|
|||
|
|
@ -835,9 +835,106 @@ class CProof:
|
|||
return "CProof(challenge=%s solution=%s)" \
|
||||
% (self.challenge, self.solution)
|
||||
|
||||
class DynaFedParamEntry:
|
||||
__slots__ = ("m_serialize_type", "m_signblockscript", "m_signblock_witness_limit", "m_fedpeg_program", "m_fedpegscript", "m_extension_space")
|
||||
|
||||
# Constructor args will define serialization type:
|
||||
# null = 0
|
||||
# signblock-related fields = 1, required for m_current on non-epoch-starts
|
||||
# all fields = 2, required for epoch starts
|
||||
def __init__(self, m_signblockscript=b"", m_signblock_witness_limit=0, m_fedpeg_program=b"", m_fedpegscript=b"", m_extension_space=[]):
|
||||
self.m_signblockscript = m_signblockscript
|
||||
self.m_signblock_witness_limit = m_signblock_witness_limit
|
||||
self.m_fedpeg_program = m_fedpeg_program
|
||||
self.m_fedpegscript = m_fedpegscript
|
||||
self.m_extension_space = m_extension_space
|
||||
if self.is_null():
|
||||
self.m_serialize_type = 0
|
||||
elif m_fedpegscript==b"" and m_extension_space == []:
|
||||
self.m_serialize_type = 1
|
||||
else:
|
||||
self.m_serialize_type = 2
|
||||
|
||||
def set_null(self):
|
||||
self.m_signblockscript = b""
|
||||
self.m_signblock_witness_limit = 0
|
||||
self.m_fedpeg_program = b""
|
||||
self.m_fedpegscript = b""
|
||||
self.m_extension_space = []
|
||||
self.m_serialize_type = 0
|
||||
|
||||
def is_null(self):
|
||||
return self.m_signblockscript == b"" and self.m_signblock_witness_limit == 0 and \
|
||||
self.m_fedpeg_program == b"" and self.m_fedpegscript == b"" and \
|
||||
self.m_extension_space == []
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("B", self.m_serialize_type)
|
||||
if self.m_serialize_type == 1:
|
||||
r += ser_string(self.m_signblockscript)
|
||||
r += struct.pack("<I", self.m_signblock_witness_limit)
|
||||
elif self.m_serialize_type == 2:
|
||||
r += ser_string(self.m_signblockscript)
|
||||
r += struct.pack("<I", self.m_signblock_witness_limit)
|
||||
r += ser_string(self.m_fedpeg_program)
|
||||
r += ser_string(self.m_fedpegscript)
|
||||
r += ser_string_vector(self.m_extension_space)
|
||||
elif self.m_serialize_type > 2:
|
||||
raise Exception("Invalid serialization type for DynaFedParamEntry")
|
||||
return r
|
||||
|
||||
def deserialize(self, f):
|
||||
self.m_serialize_type = struct.unpack("B", f.read(1))[0]
|
||||
if self.m_serialize_type == 1:
|
||||
self.m_signblockscript = deser_string(f)
|
||||
self.m_signblock_witness_limit = struct.unpack("<I", f.read(4))[0]
|
||||
elif self.m_serialize_type == 2:
|
||||
self.m_signblockscript = deser_string(f)
|
||||
self.m_signblock_witness_limit = struct.unpack("<I", f.read(4))[0]
|
||||
self.m_fedpeg_program = deser_string(f)
|
||||
self.m_fedpegscript = deser_string(f)
|
||||
self.m_extension_space = deser_string_vector(f)
|
||||
|
||||
def __repr__(self):
|
||||
return "DynaFedParamEntry(m_signblockscript=%s m_fedpegscript=%s m_extension_space=%s)" \
|
||||
% (self.m_signblockscript, self.m_fedpegscript, self.m_extension_space)
|
||||
|
||||
class DynaFedParams:
|
||||
__slots__ = ("m_current", "m_proposed")
|
||||
|
||||
def __init__(self, m_current=DynaFedParamEntry(), m_proposed=DynaFedParamEntry()):
|
||||
self.m_current = m_current
|
||||
self.m_proposed = m_proposed
|
||||
|
||||
def set_null(self):
|
||||
self.m_current.set_null()
|
||||
self.m_proposed.set_null()
|
||||
|
||||
def is_null(self):
|
||||
return self.m_current.is_null() and self.m_proposed.is_null()
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += self.m_current.serialize()
|
||||
r += self.m_proposed.serialize()
|
||||
return r
|
||||
|
||||
def deserialize(self, f):
|
||||
self.m_current.deserialize(f)
|
||||
self.m_proposed.deserialize(f)
|
||||
|
||||
def __repr__(self):
|
||||
return "DynaFedParams(m_current=%s m_proposed=%s)" \
|
||||
% (self.m_current, self.m_proposed)
|
||||
|
||||
|
||||
HEADER_HF_BIT = 1 << 31
|
||||
HEADER_DYNAFED_HF_MASK = 0x7fffffff
|
||||
class CBlockHeader:
|
||||
__slots__ = ("hash", "hashMerkleRoot", "hashPrevBlock", "nBits", "nNonce",
|
||||
"nTime", "nVersion", "sha256", "block_height", "proof")
|
||||
"nTime", "nVersion", "sha256", "block_height", "proof", "m_dynafed_params",
|
||||
"m_signblock_witness")
|
||||
|
||||
def __init__(self, header=None):
|
||||
if header is None:
|
||||
|
|
@ -851,6 +948,8 @@ class CBlockHeader:
|
|||
self.proof = header.proof
|
||||
self.sha256 = header.sha256
|
||||
self.hash = header.hash
|
||||
self.m_dynafed_params = header.m_dynafed_params
|
||||
self.m_signblock_witness = header.m_signblock_witness
|
||||
self.calc_sha256()
|
||||
|
||||
def set_null(self):
|
||||
|
|
@ -860,38 +959,69 @@ class CBlockHeader:
|
|||
self.nTime = 0
|
||||
self.block_height = 0
|
||||
self.proof = CProof()
|
||||
self.m_dynafed_params = DynaFedParams()
|
||||
self.m_signblock_witness = CScriptWitness()
|
||||
self.sha256 = None
|
||||
self.hash = None
|
||||
|
||||
def deserialize(self, f):
|
||||
self.nVersion = struct.unpack("<i", f.read(4))[0]
|
||||
is_dyna = False
|
||||
|
||||
if self.nVersion < 0:
|
||||
is_dyna = True
|
||||
self.nVersion = HEADER_DYNAFED_HF_MASK & self.nVersion
|
||||
|
||||
self.hashPrevBlock = deser_uint256(f)
|
||||
self.hashMerkleRoot = deser_uint256(f)
|
||||
self.nTime = struct.unpack("<I", f.read(4))[0]
|
||||
self.block_height = struct.unpack("<I", f.read(4))[0]
|
||||
self.proof.deserialize(f)
|
||||
if is_dyna:
|
||||
self.m_dynafed_params.deserialize(f)
|
||||
self.m_signblock_witness.stack = deser_string_vector(f)
|
||||
else:
|
||||
self.proof.deserialize(f)
|
||||
self.sha256 = None
|
||||
self.hash = None
|
||||
|
||||
def serialize(self):
|
||||
r = b""
|
||||
r += struct.pack("<i", self.nVersion)
|
||||
nVersion = self.nVersion
|
||||
is_dyna = False
|
||||
if not self.m_dynafed_params.is_null():
|
||||
nVersion -= HEADER_HF_BIT
|
||||
is_dyna = True
|
||||
|
||||
r += struct.pack("<i", nVersion)
|
||||
r += ser_uint256(self.hashPrevBlock)
|
||||
r += ser_uint256(self.hashMerkleRoot)
|
||||
r += struct.pack("<I", self.nTime)
|
||||
r += struct.pack("<I", self.block_height)
|
||||
r += self.proof.serialize()
|
||||
if is_dyna:
|
||||
r += self.m_dynafed_params.serialize()
|
||||
r += ser_string_vector(self.m_signblock_witness.stack)
|
||||
else:
|
||||
r += self.proof.serialize()
|
||||
return r
|
||||
|
||||
def calc_sha256(self):
|
||||
if self.sha256 is None:
|
||||
nVersion = self.nVersion
|
||||
is_dyna = False
|
||||
if not self.m_dynafed_params.is_null():
|
||||
nVersion -= HEADER_HF_BIT
|
||||
is_dyna = True
|
||||
|
||||
r = b""
|
||||
r += struct.pack("<i", self.nVersion)
|
||||
r += struct.pack("<i", nVersion)
|
||||
r += ser_uint256(self.hashPrevBlock)
|
||||
r += ser_uint256(self.hashMerkleRoot)
|
||||
r += struct.pack("<I", self.nTime)
|
||||
r += struct.pack("<I", self.block_height)
|
||||
r += self.proof.serialize_for_hash()
|
||||
if is_dyna:
|
||||
r += self.m_dynafed_params.serialize()
|
||||
else:
|
||||
r += self.proof.serialize_for_hash()
|
||||
self.sha256 = uint256_from_str(hash256(r))
|
||||
self.hash = encode(hash256(r)[::-1], 'hex_codec').decode('ascii')
|
||||
|
||||
|
|
@ -976,9 +1106,9 @@ class CBlock(CBlockHeader):
|
|||
# self.rehash()
|
||||
|
||||
def __repr__(self):
|
||||
return "CBlock(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s vtx=%s)" \
|
||||
return "CBlock(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s vtx=%s m_dynafed_params=%s)" \
|
||||
% (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot,
|
||||
time.ctime(self.nTime), repr(self.vtx))
|
||||
time.ctime(self.nTime), repr(self.vtx), self.m_dynafed_params)
|
||||
|
||||
|
||||
class PrefilledTransaction:
|
||||
|
|
|
|||
|
|
@ -330,6 +330,7 @@ def initialize_datadir(dirname, n, chain):
|
|||
f.write("con_bip66height=1251\n")
|
||||
f.write("con_csv_deploy_start=0\n") # Enhance tests if removing this line
|
||||
f.write("blindedaddresses=0\n") # Set to minimize broken tests in favor of custom
|
||||
f.write("con_dyna_deploy_start="+str(2**31)+"\n") # Never starts unless overridden
|
||||
#f.write("pubkeyprefix=111\n")
|
||||
#f.write("scriptprefix=196\n")
|
||||
#f.write("bech32_hrp=bcrt\n")
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ BASE_SCRIPTS = [
|
|||
# Scripts that are run by the travis build process.
|
||||
# vv First elements tests vv
|
||||
'feature_fedpeg.py',
|
||||
'feature_fedpeg.py --pre_transition',
|
||||
'feature_fedpeg.py --post_transition',
|
||||
'feature_mandatory_coinbase.py',
|
||||
'feature_block_subsidy.py',
|
||||
'feature_connect_genesis_outputs.py',
|
||||
|
|
@ -85,6 +87,7 @@ BASE_SCRIPTS = [
|
|||
'feature_assetsdir.py',
|
||||
'feature_initial_reissuance_token.py',
|
||||
'feature_progress.py',
|
||||
'feature_dynafed.py',
|
||||
# Longest test should go first, to favor running tests in parallel
|
||||
'wallet_hd.py',
|
||||
'wallet_backup.py',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue