Merge #642: Dynamic federations

f7905d6df Generalize the number of epochs old a peg-in can be and still be valid (Gregory Sanders)
b105ff45e feature_fedpeg.py: Run in various dynafed transition settings (Gregory Sanders)
feef196a2 Fix compilation of raw transaction operations with peg-in inputs (Gregory Sanders)
953dd82ea p2sh-wrap peg-in addresses if fedpeg_program is p2sh-wrapped (Gregory Sanders)
e2be2356d Refactor IsPAKValid to not magically acquire chainparams (Gregory Sanders)
ecdefc5cb Remove script/standard dependence of ContextualCheckDynaFedHeader (Gregory Sanders)
59bed57a1 Remove dyanfed.cpp's dependency on script/standard.h (Gregory Sanders)
50a2b599c After a reorg, boot all peg-ins and peg-outs from mempool (Gregory Sanders)
6000752a9 Enforce PAK checks on dynafed proposals (Gregory Sanders)
dce4cecdd Replace fedpeg template init check for pak one (Gregory Sanders)
afdf0239b Update python test framework for DynaFedParamEntry fedpeg program (Gregory Sanders)
95d8339f4 Add functional test for illegal proposals (Gregory Sanders)
1baad6bf5 Update dynafed functional test to new behavior (Gregory Sanders)
a3cf151de fixup proposal checks (Gregory Sanders)
04cc76743 getnewblockhex fills out proposal fedpeg program (Gregory Sanders)
44f55d36c Make future parent segwit versions in fedpegscripts be vacuously true (Gregory Sanders)
d7faffdca Add dynafed proposal restrictions (Gregory Sanders)
37f90ba50 Add description of NextBlockIsParameterTransition (Gregory Sanders)
d9387cb5d Remove misleading comment for ContextualCheckDynaFedHeader use (Gregory Sanders)
bd11097ce Add fedpeg_program field in dynafed header (Gregory Sanders)
4ad2b8018 CreatePAKListFromExtensionSpace just uses FromBytes for correctness (Gregory Sanders)
6a5eaa189 Remove short-circuit dynafed vote fail for readability (Gregory Sanders)
dddc040aa s/m_dyna_params/m_dynafed_params/ (Gregory Sanders)
6ed63c1fb Light explanation of dynamic federations fields (Gregory Sanders)
15996384d s/ConsensusParamEntry/DynaFedParamEntry/ (Gregory Sanders)
642260a12 s/m_sbs_wit_limit/m_signblock_witness_limit/ (Gregory Sanders)
b66804678 s/HF_MASK/DYNAFED_HF_MASK/ (Gregory Sanders)
0fd39edf5 s/d_params/dynafed_params/ (Gregory Sanders)
1779956e6 signrawtransaction* should use up to date fedpegscript for peg-in signing (Gregory Sanders)
47db75e39 Dynafed RPC support, tests, and deployment for custom chains (Gregory Sanders)
aac354b5b OP_TRUE outputs should be allowed to be segwit-ified by decodescript (Gregory Sanders)
055514b68 Have removeForBlock boot transactions when required (Gregory Sanders)
525b24cf5 Expose fedpeg fetching to consensus and mempool internals (Gregory Sanders)
4ac437b87 GetTransactionSigOpCost shouldn't need fedpegscript to evaluate (Gregory Sanders)
5097c3880 Add and update startup args for dynafed (Gregory Sanders)
24535d6a9 Add unused dynamic genesis block style in chainparams (Gregory Sanders)
d10c42a26 Create epoch length chainparam (Gregory Sanders)
b996a425e Set default of multi_data_permitted to enforce_pak (Gregory Sanders)
f6f6308c9 Correct comment about liquidv1 fedpeg matching template (Gregory Sanders)
4c00eb408 Miner should stop trying to account for old PAK system (Gregory Sanders)
0e10287f7 Add miner ability to make dynafed blocks (Gregory Sanders)
5d281e7e6 ContextualCheckBlockHeader: dynafed doesn't call CheckChallenge (Gregory Sanders)
9fe43cdf8 Introduce contextual block checks for dynafed (Gregory Sanders)
3605505c9 Enable pak enforcement at mempool/block level when appropriate (Gregory Sanders)
802a05519 ScriptHasValidPAKProof takes fedpeg as arg (Gregory Sanders)
89174e961 Remove old mempool-booting logic (Gregory Sanders)
edb865cc2 Remove standardness checks for PAK (Gregory Sanders)
804cd9f75 Update PAK internals, helper functions (Gregory Sanders)
16b87ba95 Disable PAK loading from configuration on init, disable pak test (Gregory Sanders)
918896d6f Add inactive versionbits dynafed deployment (Gregory Sanders)
0a8565bbf ReadBlockFromDisk: do genesis block check before block proof check (Gregory Sanders)
6f0d9244f Bump last old block version for versonbits due to elements (Gregory Sanders)
563cd93d4 Add python implementation of dynafed block serialization (Gregory Sanders)
8d3091bbc Deserialize merkle proofs without witness, which matters for dynafed (Gregory Sanders)
830f91723 Add fedpegscript-fetching helper (Gregory Sanders)
b56fe1495 calculate_contract: remove most template checks and assert in preperation for dynafed (Gregory Sanders)
fd63cee1a Add note on peg-in sigops (Gregory Sanders)
f17d7de52 Add dynafed helper functions (Gregory Sanders)
4aa5f991f Refactor block_proof to support dynafed (Gregory Sanders)
4e52f2c83 Add dynamic federation blockheader fields to chaindb serialization (Gregory Sanders)
a74accf7d Add dynamic federations blockheader serialization with HF bit (Gregory Sanders)
2845e7811 Define 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:
Steven Roose 2019-09-06 12:01:59 +01:00
commit 5b2fe5563b
No known key found for this signature in database
GPG key ID: 2F2A88D7F8D68E87
49 changed files with 1903 additions and 805 deletions

View file

@ -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 \

View file

@ -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;
}
}

View file

@ -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

View file

@ -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();
}

View file

@ -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));
}

View file

@ -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
//
}

View file

@ -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

View file

@ -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);

View file

@ -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
View 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
View 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

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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());

View file

@ -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" },

View file

@ -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"} },

View file

@ -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;
}

View file

@ -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);

View file

@ -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>

View file

@ -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();

View file

@ -603,6 +603,8 @@ struct CScriptWitness
void SetNull() { stack.clear(); stack.shrink_to_fit(); }
std::string ToString() const;
uint32_t GetSerializedSize() const;
};
class CReserveScript

View file

@ -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);

View file

@ -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());

View file

@ -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();

View file

@ -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);

View file

@ -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

View file

@ -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;
}

View file

@ -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 {

View file

@ -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 */

View file

@ -18,5 +18,9 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
{
/*.name =*/ "segwit",
/*.gbt_force =*/ true,
}
},
{
/*.name =*/ "dynafed",
/*.gbt_force =*/ true,
},
};

View file

@ -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"}},

View file

@ -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

View file

@ -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()

View 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()

View file

@ -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"]

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

@ -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:

View file

@ -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")

View file

@ -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',