Introduce assumevalid setting to skip presumed valid scripts.
This disentangles the script validation skipping from checkpoints. A new option is introduced "assumevalid" which specifies a block whos ancestors we assume all have valid scriptsigs and so we do not check them when they are also burried under the best header by two weeks worth of work. Unlike checkpoints this has no influence on consensus unless you set it to a block with an invalid history. Because of this it can be easily be updated without risk of influencing the network consensus. This results in a massive IBD speedup. This approach was independently recommended by Peter Todd and Luke-Jr since POW based signature skipping (see PR#9180) does not have the verifiable properties of a specific hash and may create bad incentives. The downside is that, like checkpoints, the defaults bitrot and older releases will sync slower. On the plus side users can provide their own value here, and if they set it to something crazy all that will happen is more time will be spend validating signatures. Checkblocks and checklevel are also moved to the hidden debug options: Especially now that checkblocks has a low default there is little need to change these settings, and users frequently misunderstand them as influencing security or IBD speed. By hiding them we offset the space added by this new option.
This commit is contained in:
parent
02e5308c1b
commit
e440ac7ef3
7 changed files with 75 additions and 12 deletions
|
@ -49,7 +49,7 @@ Low-level RPC changes
|
||||||
than two arguments.
|
than two arguments.
|
||||||
|
|
||||||
Removal of Priority Estimation
|
Removal of Priority Estimation
|
||||||
------------------------------
|
-------------------------------
|
||||||
|
|
||||||
- Estimation of "priority" needed for a transaction to be included within a target
|
- Estimation of "priority" needed for a transaction to be included within a target
|
||||||
number of blocks has been removed. The rpc calls are deprecated and will either
|
number of blocks has been removed. The rpc calls are deprecated and will either
|
||||||
|
@ -71,6 +71,27 @@ P2P connection management
|
||||||
|
|
||||||
- New connections to manually added peers are much faster.
|
- New connections to manually added peers are much faster.
|
||||||
|
|
||||||
|
Introduction of assumed-valid blocks
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
- A significant portion of the initial block download time is spent verifying
|
||||||
|
scripts/signatures. Although the verification must pass to ensure the security
|
||||||
|
of the system, no other result from this verification is needed: If the node
|
||||||
|
knew the history of a given block were valid it could skip checking scripts
|
||||||
|
for its ancestors.
|
||||||
|
|
||||||
|
- A new configuration option 'assumevalid' is provided to express this knowledge
|
||||||
|
to the software. Unlike the 'checkpoints' in the past this setting does not
|
||||||
|
force the use of a particular chain: chains that are consistent with it are
|
||||||
|
processed quicker, but other chains are still accepted if they'd otherwise
|
||||||
|
be chosen as best. Also unlike 'checkpoints' the user can configure which
|
||||||
|
block history is assumed true, this means that even outdated software can
|
||||||
|
sync more quickly if the setting is updated by the user.
|
||||||
|
|
||||||
|
- Because the validity of a chain history is a simple objective fact it is much
|
||||||
|
easier to review this setting. As a result the software ships with a default
|
||||||
|
value adjusted to match the current chain shortly before release. The use
|
||||||
|
of this default value can be disabled by setting -assumevalid=0
|
||||||
|
|
||||||
0.14.0 Change log
|
0.14.0 Change log
|
||||||
=================
|
=================
|
||||||
|
|
|
@ -13,6 +13,11 @@ Before every minor and major release:
|
||||||
* Update version in sources (see below)
|
* Update version in sources (see below)
|
||||||
* Write release notes (see below)
|
* Write release notes (see below)
|
||||||
* Update `src/chainparams.cpp` nMinimumChainWork with information from the getblockchaininfo rpc.
|
* Update `src/chainparams.cpp` nMinimumChainWork with information from the getblockchaininfo rpc.
|
||||||
|
* Update `src/chainparams.cpp` defaultAssumeValid with information from the getblockhash rpc.
|
||||||
|
- The selected value must not be orphaned so it may be useful to set the value two blocks back from the tip.
|
||||||
|
- Testnet should be set some tens of thousands back from the tip due to reorgs there.
|
||||||
|
- This update should be reviewed with a reindex-chainstate with assumevalid=0 to catch any defect
|
||||||
|
that causes rejection of blocks in the past history.
|
||||||
|
|
||||||
Before every major release:
|
Before every major release:
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,9 @@ public:
|
||||||
// The best chain should have at least this much work.
|
// The best chain should have at least this much work.
|
||||||
consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000002cb971dd56d1c583c20f90");
|
consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000002cb971dd56d1c583c20f90");
|
||||||
|
|
||||||
|
// By default assume that the signatures in ancestors of this block are valid.
|
||||||
|
consensus.defaultAssumeValid = uint256S("0x0000000000000000030abc968e1bd635736e880b946085c93152969b9a81a6e2"); //447235
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message start string is designed to be unlikely to occur in normal data.
|
* The message start string is designed to be unlikely to occur in normal data.
|
||||||
* The characters are rarely used upper ASCII, not valid as UTF-8, and produce
|
* The characters are rarely used upper ASCII, not valid as UTF-8, and produce
|
||||||
|
@ -201,6 +204,9 @@ public:
|
||||||
// The best chain should have at least this much work.
|
// The best chain should have at least this much work.
|
||||||
consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000000000198b4def2baa9338d6");
|
consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000000000198b4def2baa9338d6");
|
||||||
|
|
||||||
|
// By default assume that the signatures in ancestors of this block are valid.
|
||||||
|
consensus.defaultAssumeValid = uint256S("0x000000000871ee6842d3648317ccc8a435eb8cc3c2429aee94faff9ba26b05a0"); //1043841
|
||||||
|
|
||||||
pchMessageStart[0] = 0x0b;
|
pchMessageStart[0] = 0x0b;
|
||||||
pchMessageStart[1] = 0x11;
|
pchMessageStart[1] = 0x11;
|
||||||
pchMessageStart[2] = 0x09;
|
pchMessageStart[2] = 0x09;
|
||||||
|
@ -283,6 +289,9 @@ public:
|
||||||
// The best chain should have at least this much work.
|
// The best chain should have at least this much work.
|
||||||
consensus.nMinimumChainWork = uint256S("0x00");
|
consensus.nMinimumChainWork = uint256S("0x00");
|
||||||
|
|
||||||
|
// By default assume that the signatures in ancestors of this block are valid.
|
||||||
|
consensus.defaultAssumeValid = uint256S("0x00");
|
||||||
|
|
||||||
pchMessageStart[0] = 0xfa;
|
pchMessageStart[0] = 0xfa;
|
||||||
pchMessageStart[1] = 0xbf;
|
pchMessageStart[1] = 0xbf;
|
||||||
pchMessageStart[2] = 0xb5;
|
pchMessageStart[2] = 0xb5;
|
||||||
|
|
|
@ -62,6 +62,7 @@ struct Params {
|
||||||
int64_t nPowTargetTimespan;
|
int64_t nPowTargetTimespan;
|
||||||
int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }
|
int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; }
|
||||||
uint256 nMinimumChainWork;
|
uint256 nMinimumChainWork;
|
||||||
|
uint256 defaultAssumeValid;
|
||||||
};
|
};
|
||||||
} // namespace Consensus
|
} // namespace Consensus
|
||||||
|
|
||||||
|
|
11
src/init.cpp
11
src/init.cpp
|
@ -329,8 +329,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||||
strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)"));
|
strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)"));
|
||||||
if (showDebug)
|
if (showDebug)
|
||||||
strUsage += HelpMessageOpt("-blocksonly", strprintf(_("Whether to operate in a blocks only mode (default: %u)"), DEFAULT_BLOCKSONLY));
|
strUsage += HelpMessageOpt("-blocksonly", strprintf(_("Whether to operate in a blocks only mode (default: %u)"), DEFAULT_BLOCKSONLY));
|
||||||
strUsage += HelpMessageOpt("-checkblocks=<n>", strprintf(_("How many blocks to check at startup (default: %u, 0 = all)"), DEFAULT_CHECKBLOCKS));
|
strUsage +=HelpMessageOpt("-assumevalid=<hex>", strprintf(_("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s)"), Params(CBaseChainParams::MAIN).GetConsensus().defaultAssumeValid.GetHex(), Params(CBaseChainParams::TESTNET).GetConsensus().defaultAssumeValid.GetHex()));
|
||||||
strUsage += HelpMessageOpt("-checklevel=<n>", strprintf(_("How thorough the block verification of -checkblocks is (0-4, default: %u)"), DEFAULT_CHECKLEVEL));
|
|
||||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
|
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
|
||||||
if (mode == HMM_BITCOIND)
|
if (mode == HMM_BITCOIND)
|
||||||
{
|
{
|
||||||
|
@ -420,6 +419,8 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||||
strUsage += HelpMessageOpt("-uacomment=<cmt>", _("Append comment to the user agent string"));
|
strUsage += HelpMessageOpt("-uacomment=<cmt>", _("Append comment to the user agent string"));
|
||||||
if (showDebug)
|
if (showDebug)
|
||||||
{
|
{
|
||||||
|
strUsage += HelpMessageOpt("-checkblocks=<n>", strprintf(_("How many blocks to check at startup (default: %u, 0 = all)"), DEFAULT_CHECKBLOCKS));
|
||||||
|
strUsage += HelpMessageOpt("-checklevel=<n>", strprintf(_("How thorough the block verification of -checkblocks is (0-4, default: %u)"), DEFAULT_CHECKLEVEL));
|
||||||
strUsage += HelpMessageOpt("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, chainActive and mapBlocksUnlinked occasionally. Also sets -checkmempool (default: %u)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks()));
|
strUsage += HelpMessageOpt("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, chainActive and mapBlocksUnlinked occasionally. Also sets -checkmempool (default: %u)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks()));
|
||||||
strUsage += HelpMessageOpt("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks()));
|
strUsage += HelpMessageOpt("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u)", Params(CBaseChainParams::MAIN).DefaultConsistencyChecks()));
|
||||||
strUsage += HelpMessageOpt("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED));
|
strUsage += HelpMessageOpt("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED));
|
||||||
|
@ -920,6 +921,12 @@ bool AppInitParameterInteraction()
|
||||||
fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
|
fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
|
||||||
fCheckpointsEnabled = GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
|
fCheckpointsEnabled = GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
|
||||||
|
|
||||||
|
hashAssumeValid = uint256S(GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex()));
|
||||||
|
if (!hashAssumeValid.IsNull())
|
||||||
|
LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex());
|
||||||
|
else
|
||||||
|
LogPrintf("Validating signatures for all blocks.\n");
|
||||||
|
|
||||||
// mempool limits
|
// mempool limits
|
||||||
int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
|
int64_t nMempoolSizeMax = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
|
||||||
int64_t nMempoolSizeMin = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000 * 40;
|
int64_t nMempoolSizeMin = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000 * 40;
|
||||||
|
|
|
@ -78,6 +78,7 @@ uint64_t nPruneTarget = 0;
|
||||||
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
|
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
|
||||||
bool fEnableReplacement = DEFAULT_ENABLE_REPLACEMENT;
|
bool fEnableReplacement = DEFAULT_ENABLE_REPLACEMENT;
|
||||||
|
|
||||||
|
uint256 hashAssumeValid;
|
||||||
|
|
||||||
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
|
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
|
||||||
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
|
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
|
||||||
|
@ -1389,11 +1390,10 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
|
||||||
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
|
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
|
||||||
// Helps prevent CPU exhaustion attacks.
|
// Helps prevent CPU exhaustion attacks.
|
||||||
|
|
||||||
// Skip ECDSA signature verification when connecting blocks before the
|
// Skip script verification when connecting blocks under the
|
||||||
// last block chain checkpoint. Assuming the checkpoints are valid this
|
// assumedvalid block. Assuming the assumedvalid block is valid this
|
||||||
// is safe because block merkle hashes are still computed and checked,
|
// is safe because block merkle hashes are still computed and checked,
|
||||||
// and any change will be caught at the next checkpoint. Of course, if
|
// Of course, if an assumed valid block is invalid due to false scriptSigs
|
||||||
// the checkpoint is for a chain that's invalid due to false scriptSigs
|
|
||||||
// this optimization would allow an invalid chain to be accepted.
|
// this optimization would allow an invalid chain to be accepted.
|
||||||
if (fScriptChecks) {
|
if (fScriptChecks) {
|
||||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||||
|
@ -1721,11 +1721,28 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fScriptChecks = true;
|
bool fScriptChecks = true;
|
||||||
if (fCheckpointsEnabled) {
|
if (!hashAssumeValid.IsNull()) {
|
||||||
CBlockIndex *pindexLastCheckpoint = Checkpoints::GetLastCheckpoint(chainparams.Checkpoints());
|
// We've been configured with the hash of a block which has been externally verified to have a valid history.
|
||||||
if (pindexLastCheckpoint && pindexLastCheckpoint->GetAncestor(pindex->nHeight) == pindex) {
|
// A suitable default value is included with the software and updated from time to time. Because validity
|
||||||
// This block is an ancestor of a checkpoint: disable script checks
|
// relative to a piece of software is an objective fact these defaults can be easily reviewed.
|
||||||
fScriptChecks = false;
|
// This setting doesn't force the selection of any particular chain but makes validating some faster by
|
||||||
|
// effectively caching the result of part of the verification.
|
||||||
|
BlockMap::const_iterator it = mapBlockIndex.find(hashAssumeValid);
|
||||||
|
if (it != mapBlockIndex.end()) {
|
||||||
|
if (it->second->GetAncestor(pindex->nHeight) == pindex &&
|
||||||
|
pindexBestHeader->GetAncestor(pindex->nHeight) == pindex &&
|
||||||
|
pindexBestHeader->nChainWork >= UintToArith256(chainparams.GetConsensus().nMinimumChainWork)) {
|
||||||
|
// This block is a member of the assumed verified chain and an ancestor of the best header.
|
||||||
|
// The equivalent time check discourages hashpower from extorting the network via DOS attack
|
||||||
|
// into accepting an invalid block through telling users they must manually set assumevalid.
|
||||||
|
// Requiring a software change or burying the invalid block, regardless of the setting, makes
|
||||||
|
// it hard to hide the implication of the demand. This also avoids having release candidates
|
||||||
|
// that are hardly doing any signature verification at all in testing without having to
|
||||||
|
// artificially set the default assumed verified block further back.
|
||||||
|
// The test against nMinimumChainWork prevents the skipping when denied access to any chain at
|
||||||
|
// least as good as the expected chain.
|
||||||
|
fScriptChecks = (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, chainparams.GetConsensus()) <= 60 * 60 * 24 * 7 * 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -186,6 +186,9 @@ extern CAmount maxTxFee;
|
||||||
extern int64_t nMaxTipAge;
|
extern int64_t nMaxTipAge;
|
||||||
extern bool fEnableReplacement;
|
extern bool fEnableReplacement;
|
||||||
|
|
||||||
|
/** Block hash whose ancestors we will assume to have valid scripts without checking them. */
|
||||||
|
extern uint256 hashAssumeValid;
|
||||||
|
|
||||||
/** Best header we've seen so far (used for getheaders queries' starting points). */
|
/** Best header we've seen so far (used for getheaders queries' starting points). */
|
||||||
extern CBlockIndex *pindexBestHeader;
|
extern CBlockIndex *pindexBestHeader;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue