mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Validation of pegin inputs
This commit is contained in:
parent
f4adb464e8
commit
5cbb014fe4
15 changed files with 369 additions and 79 deletions
|
|
@ -172,7 +172,8 @@ public:
|
|||
// Block (dis)connection on a given view:
|
||||
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view);
|
||||
bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex,
|
||||
CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false);
|
||||
CCoinsViewCache& view, const CChainParams& chainparams,
|
||||
std::set<std::pair<uint256, COutPoint>>* setPeginsSpent, bool fJustCheck = false);
|
||||
|
||||
// Block disconnection on our pcoinsTip:
|
||||
bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool);
|
||||
|
|
@ -311,7 +312,10 @@ enum class FlushStateMode {
|
|||
static bool FlushStateToDisk(const CChainParams& chainParams, CValidationState &state, FlushStateMode mode, int nManualPruneHeight=0);
|
||||
static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight);
|
||||
static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight);
|
||||
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr);
|
||||
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs,
|
||||
bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore,
|
||||
PrecomputedTransactionData& txdata,
|
||||
std::vector<CScriptCheck> *pvChecks = nullptr);
|
||||
static FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
|
||||
|
||||
bool CheckFinalTx(const CTransaction &tx, int flags)
|
||||
|
|
@ -661,6 +665,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
{
|
||||
CCoinsView dummy;
|
||||
CCoinsViewCache view(&dummy);
|
||||
std::set<std::pair<uint256, COutPoint> > setPeginsSpent;
|
||||
|
||||
LockPoints lp;
|
||||
CCoinsViewMemPool viewMemPool(pcoinsTip.get(), pool);
|
||||
|
|
@ -708,7 +713,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final");
|
||||
|
||||
CAmount nFees = 0;
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) {
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees, setPeginsSpent)) {
|
||||
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
|
|
@ -742,7 +747,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
}
|
||||
|
||||
CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, chainActive.Height(),
|
||||
fSpendsCoinbase, nSigOpsCost, lp);
|
||||
fSpendsCoinbase, nSigOpsCost, lp, setPeginsSpent);
|
||||
unsigned int nSize = entry.GetTxSize();
|
||||
|
||||
// Check that the transaction doesn't have an excessive number of
|
||||
|
|
@ -931,10 +936,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
|||
// need to turn both off, and compare against just turning off CLEANSTACK
|
||||
// to see if the failure is specifically due to witness validation.
|
||||
CValidationState stateDummy; // Want reported failures to be from first CheckInputs
|
||||
if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
|
||||
!CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
|
||||
// Only the witness is missing, so the transaction itself may be fine.
|
||||
state.SetCorruptionPossible();
|
||||
if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata)) {
|
||||
if (!CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
|
||||
// Only the witness is missing, so the transaction itself may be fine.
|
||||
state.SetCorruptionPossible();
|
||||
}
|
||||
}
|
||||
return false; // state filled in by CheckInputs
|
||||
}
|
||||
|
|
@ -1352,9 +1358,16 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
|
|||
if (!tx.IsCoinBase()) {
|
||||
txundo.vprevout.reserve(tx.vin.size());
|
||||
for (const CTxIn &txin : tx.vin) {
|
||||
txundo.vprevout.emplace_back();
|
||||
bool is_spent = inputs.SpendCoin(txin.prevout, &txundo.vprevout.back());
|
||||
assert(is_spent);
|
||||
if (txin.m_is_pegin) {
|
||||
std::pair<uint256, COutPoint> outpoint = std::make_pair(uint256(txin.m_pegin_witness.stack[2]), txin.prevout);
|
||||
inputs.SetPeginSpent(outpoint, true);
|
||||
// Dummy undo
|
||||
txundo.vprevout.emplace_back();
|
||||
} else {
|
||||
txundo.vprevout.emplace_back();
|
||||
bool is_spent = inputs.SpendCoin(txin.prevout, &txundo.vprevout.back());
|
||||
assert(is_spent);
|
||||
}
|
||||
}
|
||||
}
|
||||
// add outputs
|
||||
|
|
@ -1441,7 +1454,16 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
|
|||
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||
const COutPoint &prevout = tx.vin[i].prevout;
|
||||
const Coin& coin = inputs.AccessCoin(prevout);
|
||||
|
||||
// ELEMENTS:
|
||||
// If input is peg-in, create "coin" to evaluate against
|
||||
Coin pegin_coin;
|
||||
if (tx.vin[i].m_is_pegin) {
|
||||
// Height of "output" in script evaluation will be 0
|
||||
pegin_coin = Coin(GetPeginOutputFromWitness(tx.vin[i].m_pegin_witness), 0, false);
|
||||
}
|
||||
|
||||
const Coin& coin = tx.vin[i].m_is_pegin ? pegin_coin : inputs.AccessCoin(prevout);
|
||||
assert(!coin.IsSpent());
|
||||
|
||||
// We very carefully only pass in things to CScriptCheck which
|
||||
|
|
@ -1465,8 +1487,9 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
|
|||
// non-upgraded nodes.
|
||||
CScriptCheck check2(coin.out, tx, i,
|
||||
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
|
||||
if (check2())
|
||||
if (check2()) {
|
||||
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
|
||||
}
|
||||
}
|
||||
// Failures of other flags indicate a transaction that is
|
||||
// invalid in new blocks, e.g. an invalid P2SH. We DoS ban
|
||||
|
|
@ -1577,29 +1600,43 @@ 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)
|
||||
int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin, const CScriptWitness& pegin_witness)
|
||||
{
|
||||
bool fClean = true;
|
||||
|
||||
if (view.HaveCoin(out)) fClean = false; // overwriting transaction output
|
||||
if (!txin.m_is_pegin) {
|
||||
if (view.HaveCoin(out)) fClean = false; // overwriting transaction output
|
||||
|
||||
if (undo.nHeight == 0) {
|
||||
// Missing undo metadata (height and coinbase). Older versions included this
|
||||
// information only in undo records for the last spend of a transactions'
|
||||
// outputs. This implies that it must be present for some other output of the same tx.
|
||||
const Coin& alternate = AccessByTxid(view, out.hash);
|
||||
if (!alternate.IsSpent()) {
|
||||
undo.nHeight = alternate.nHeight;
|
||||
undo.fCoinBase = alternate.fCoinBase;
|
||||
if (undo.nHeight == 0) {
|
||||
// Missing undo metadata (height and coinbase). Older versions included this
|
||||
// information only in undo records for the last spend of a transactions'
|
||||
// outputs. This implies that it must be present for some other output of the same tx.
|
||||
const Coin& alternate = AccessByTxid(view, out.hash);
|
||||
if (!alternate.IsSpent()) {
|
||||
undo.nHeight = alternate.nHeight;
|
||||
undo.fCoinBase = alternate.fCoinBase;
|
||||
} else {
|
||||
return DISCONNECT_FAILED; // adding output for transaction without known metadata
|
||||
}
|
||||
}
|
||||
// The potential_overwrite parameter to AddCoin is only allowed to be false if we know for
|
||||
// sure that the coin did not already exist in the cache. As we have queried for that above
|
||||
// using HaveCoin, we don't need to guess. When fClean is false, a coin already existed and
|
||||
// it is an overwrite.
|
||||
view.AddCoin(out, std::move(undo), !fClean);
|
||||
} else {
|
||||
if (!IsValidPeginWitness(pegin_witness, txin.prevout)) {
|
||||
fClean = fClean && error("%s: peg-in occurred without proof", __func__);
|
||||
} else {
|
||||
return DISCONNECT_FAILED; // adding output for transaction without known metadata
|
||||
std::pair<uint256, COutPoint> outpoint = std::make_pair(uint256(pegin_witness.stack[2]), txin.prevout);
|
||||
bool fSpent = view.IsPeginSpent(outpoint);
|
||||
if (!fSpent) {
|
||||
fClean = fClean && error("%s: peg-in bitcoin txid not marked spent", __func__);
|
||||
} else {
|
||||
view.SetPeginSpent(outpoint, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The potential_overwrite parameter to AddCoin is only allowed to be false if we know for
|
||||
// sure that the coin did not already exist in the cache. As we have queried for that above
|
||||
// using HaveCoin, we don't need to guess. When fClean is false, a coin already existed and
|
||||
// it is an overwrite.
|
||||
view.AddCoin(out, std::move(undo), !fClean);
|
||||
|
||||
return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;
|
||||
}
|
||||
|
|
@ -1649,7 +1686,8 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI
|
|||
}
|
||||
for (unsigned int j = tx.vin.size(); j-- > 0;) {
|
||||
const COutPoint &out = tx.vin[j].prevout;
|
||||
int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out);
|
||||
const CScriptWitness& pegin_wit = tx.vin[j].m_pegin_witness;
|
||||
int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out, tx.vin[j], pegin_wit);
|
||||
if (res == DISCONNECT_FAILED) return DISCONNECT_FAILED;
|
||||
fClean = fClean && res != DISCONNECT_UNCLEAN;
|
||||
}
|
||||
|
|
@ -1833,7 +1871,7 @@ static int64_t nBlocksTotal = 0;
|
|||
* Validity checks that depend on the UTXO set are also done; ConnectBlock()
|
||||
* can fail if those validity checks fail (among other reasons). */
|
||||
bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex,
|
||||
CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck)
|
||||
CCoinsViewCache& view, const CChainParams& chainparams, std::set<std::pair<uint256, COutPoint>>* setPeginsSpent, bool fJustCheck)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
assert(pindex);
|
||||
|
|
@ -2037,6 +2075,11 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
|
|||
blockundo.vtxundo.reserve(block.vtx.size() - 1);
|
||||
std::vector<PrecomputedTransactionData> txdata;
|
||||
txdata.reserve(block.vtx.size()); // Required so that pointers to individual PrecomputedTransactionData don't get invalidated
|
||||
|
||||
// ELEMENTS:
|
||||
// Used when ConnectBlock() results are unneeded for mempool ejection
|
||||
std::set<std::pair<uint256, COutPoint>> setPeginsSpentDummy;
|
||||
|
||||
for (unsigned int i = 0; i < block.vtx.size(); i++)
|
||||
{
|
||||
const CTransaction &tx = *(block.vtx[i]);
|
||||
|
|
@ -2046,7 +2089,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
|
|||
if (!tx.IsCoinBase())
|
||||
{
|
||||
CAmount txfee = 0;
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) {
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee,
|
||||
setPeginsSpent == NULL ? setPeginsSpentDummy : *setPeginsSpent)) {
|
||||
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
|
||||
}
|
||||
nFees += txfee;
|
||||
|
|
@ -2493,13 +2537,42 @@ bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainp
|
|||
int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1;
|
||||
int64_t nTime3;
|
||||
LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO);
|
||||
|
||||
// ELEMENTS:
|
||||
// For mempool removal with pegin conflicts
|
||||
std::set<std::pair<uint256, COutPoint>> setPeginsSpent;
|
||||
|
||||
{
|
||||
CCoinsViewCache view(pcoinsTip.get());
|
||||
bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, chainparams);
|
||||
bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, chainparams, &setPeginsSpent);
|
||||
GetMainSignals().BlockChecked(blockConnecting, state);
|
||||
if (!rv) {
|
||||
if (state.IsInvalid())
|
||||
if (state.IsInvalid()) {
|
||||
InvalidBlockFound(pindexNew, state);
|
||||
|
||||
// ELEMENTS:
|
||||
// Possibly result of RPC to mainchain bitcoind failure
|
||||
// or unseen Bitcoin blocks.
|
||||
// These blocks are later re-evaluated at an interval
|
||||
// set by `-recheckpeginblockinterval`.
|
||||
if (state.GetRejectCode() == REJECT_PEGIN) {
|
||||
//Write queue of invalid blocks that
|
||||
//must be cleared to continue operation
|
||||
std::vector<uint256> vinvalidBlocks;
|
||||
pblocktree->ReadInvalidBlockQueue(vinvalidBlocks);
|
||||
bool blockAlreadyInvalid = false;
|
||||
for (uint256& hash : vinvalidBlocks) {
|
||||
if (hash == blockConnecting.GetHash()) {
|
||||
blockAlreadyInvalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!blockAlreadyInvalid) {
|
||||
vinvalidBlocks.push_back(blockConnecting.GetHash());
|
||||
pblocktree->WriteInvalidBlockQueue(vinvalidBlocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
return error("ConnectTip(): ConnectBlock %s failed", pindexNew->GetBlockHash().ToString());
|
||||
}
|
||||
nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2;
|
||||
|
|
@ -2515,7 +2588,7 @@ bool CChainState::ConnectTip(CValidationState& state, const CChainParams& chainp
|
|||
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.;
|
||||
mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
|
||||
mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight, setPeginsSpent);
|
||||
disconnectpool.removeForBlock(blockConnecting.vtx);
|
||||
// Update chainActive & related variables.
|
||||
chainActive.SetTip(pindexNew);
|
||||
|
|
@ -3642,7 +3715,7 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams,
|
|||
return error("%s: Consensus::CheckBlock: %s", __func__, FormatStateMessage(state));
|
||||
if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev))
|
||||
return error("%s: Consensus::ContextualCheckBlock: %s", __func__, FormatStateMessage(state));
|
||||
if (!g_chainstate.ConnectBlock(block, state, &indexDummy, viewNew, chainparams, true))
|
||||
if (!g_chainstate.ConnectBlock(block, state, &indexDummy, viewNew, chainparams, NULL, true))
|
||||
return false;
|
||||
assert(state.IsValid());
|
||||
|
||||
|
|
@ -4112,7 +4185,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
|
|||
CBlock block;
|
||||
if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus()))
|
||||
return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
|
||||
if (!g_chainstate.ConnectBlock(block, state, pindex, coins, chainparams))
|
||||
if (!g_chainstate.ConnectBlock(block, state, pindex, coins, chainparams, NULL))
|
||||
return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), FormatStateMessage(state));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue