aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorPieter Wuille <pieter.wuille@gmail.com>2015-11-06 01:42:38 +0100
committerPieter Wuille <pieter.wuille@gmail.com>2016-06-22 15:42:59 +0200
commit8b49040854be2e26b66366aeae1cba4716f93d93 (patch)
treeb217f3fb71bc0745c1f74ab239fbe0ebcf52a751 /src/main.cpp
parent449f9b8debcceb61a92043bc7031528a53627c47 (diff)
BIP141: Commitment structure and deployment
Includes a fix by Suhas Daftuar and LongShao007
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp146
1 files changed, 138 insertions, 8 deletions
diff --git a/src/main.cpp b/src/main.cpp
index d1945a5094..8d91d6d625 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -293,6 +293,8 @@ struct CNodeState {
bool fPreferHeaderAndIDs;
//! Whether this peer will send us cmpctblocks if we request them
bool fProvidesHeaderAndIDs;
+ //! Whether this peer can give us witnesses
+ bool fHaveWitness;
CNodeState() {
fCurrentlyConnected = false;
@@ -1119,6 +1121,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
return state.DoS(0, false, REJECT_NONSTANDARD, "premature-version2-tx");
}
+ // Don't accept witness transactions before the final threshold passes
+ if (!tx.wit.IsNull() && !IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus())) {
+ return state.DoS(0, false, REJECT_NONSTANDARD, "no-witness-yet", true);
+ }
+
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
@@ -1459,8 +1466,17 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
- if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
- return false; // state filled in by CheckInputs
+ if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true)) {
+ // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
+ // need to turn both off, and compare against just turning off CLEANSTACK
+ // to see if the failure is specifically due to witness validation.
+ if (CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true) &&
+ !CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS & ~SCRIPT_VERIFY_CLEANSTACK, true)) {
+ // Only the witness is wrong, so the transaction itself may be fine.
+ state.SetCorruptionPossible();
+ }
+ return false;
+ }
// Check again against just the consensus-critical mandatory script
// verification flags, in case of bugs in the standard flags that cause
@@ -2406,6 +2422,11 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE;
}
+ // Start enforcing WITNESS rules using versionbits logic.
+ if (IsWitnessEnabled(pindex->pprev, chainparams.GetConsensus())) {
+ flags |= SCRIPT_VERIFY_WITNESS;
+ }
+
int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1;
LogPrint("bench", " - Fork checks: %.2fms [%.2fs]\n", 0.001 * (nTime2 - nTime1), nTimeForks * 0.000001);
@@ -3441,6 +3462,71 @@ static bool CheckIndexAgainstCheckpoint(const CBlockIndex* pindexPrev, CValidati
return true;
}
+bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params)
+{
+ LOCK(cs_main);
+ return (VersionBitsState(pindexPrev, params, Consensus::DEPLOYMENT_SEGWIT, versionbitscache) == THRESHOLD_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)
+{
+ int commitpos = -1;
+ for (size_t o = 0; o < block.vtx[0].vout.size(); o++) {
+ if (block.vtx[0].vout[o].scriptPubKey.size() >= 38 && block.vtx[0].vout[o].scriptPubKey[0] == OP_RETURN && block.vtx[0].vout[o].scriptPubKey[1] == 0x24 && block.vtx[0].vout[o].scriptPubKey[2] == 0xaa && block.vtx[0].vout[o].scriptPubKey[3] == 0x21 && block.vtx[0].vout[o].scriptPubKey[4] == 0xa9 && block.vtx[0].vout[o].scriptPubKey[5] == 0xed) {
+ commitpos = o;
+ }
+ }
+ return commitpos;
+}
+
+void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams)
+{
+ int commitpos = GetWitnessCommitmentIndex(block);
+ static const std::vector<unsigned char> nonce(32, 0x00);
+ if (commitpos != -1 && IsWitnessEnabled(pindexPrev, consensusParams) && block.vtx[0].wit.IsEmpty()) {
+ block.vtx[0].wit.vtxinwit.resize(1);
+ block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.resize(1);
+ block.vtx[0].wit.vtxinwit[0].scriptWitness.stack[0] = nonce;
+ }
+}
+
+std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams)
+{
+ std::vector<unsigned char> commitment;
+ int commitpos = GetWitnessCommitmentIndex(block);
+ bool fHaveWitness = false;
+ for (size_t t = 1; t < block.vtx.size(); t++) {
+ if (!block.vtx[t].wit.IsNull()) {
+ fHaveWitness = true;
+ break;
+ }
+ }
+ std::vector<unsigned char> ret(32, 0x00);
+ if (fHaveWitness && IsWitnessEnabled(pindexPrev, consensusParams)) {
+ if (commitpos == -1) {
+ uint256 witnessroot = BlockWitnessMerkleRoot(block, NULL);
+ CHash256().Write(witnessroot.begin(), 32).Write(&ret[0], 32).Finalize(witnessroot.begin());
+ CTxOut out;
+ out.nValue = 0;
+ out.scriptPubKey.resize(38);
+ out.scriptPubKey[0] = OP_RETURN;
+ out.scriptPubKey[1] = 0x24;
+ out.scriptPubKey[2] = 0xaa;
+ out.scriptPubKey[3] = 0x21;
+ out.scriptPubKey[4] = 0xa9;
+ out.scriptPubKey[5] = 0xed;
+ memcpy(&out.scriptPubKey[6], witnessroot.begin(), 32);
+ commitment = std::vector<unsigned char>(out.scriptPubKey.begin(), out.scriptPubKey.end());
+ const_cast<std::vector<CTxOut>*>(&block.vtx[0].vout)->push_back(out);
+ block.vtx[0].UpdateHash();
+ }
+ }
+ UpdateUncommittedBlockStructures(block, pindexPrev, consensusParams);
+ return commitment;
+}
+
bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, CBlockIndex * const pindexPrev, int64_t nAdjustedTime)
{
// Check proof of work
@@ -3497,6 +3583,43 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn
}
}
+ // Validation for witness commitments.
+ // * We compute the witness hash (which is the hash including witnesses) of all the block's transactions, except the
+ // coinbase (where 0x0000....0000 is used instead).
+ // * The coinbase scriptWitness is a stack of a single 32-byte vector, containing a witness nonce (unconstrained).
+ // * We build a merkle tree with all those witness hashes as leaves (similar to the hashMerkleRoot in the block header).
+ // * There must be at least one output whose scriptPubKey is a single 36-byte push, the first 4 bytes of which are
+ // {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness nonce). In case there are
+ // multiple, the last one is used.
+ bool fHaveWitness = false;
+ if (IsWitnessEnabled(pindexPrev, consensusParams)) {
+ int commitpos = GetWitnessCommitmentIndex(block);
+ if (commitpos != -1) {
+ bool malleated = false;
+ uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated);
+ // The malleation check is ignored; as the transaction tree itself
+ // already does not permit it, it is impossible to trigger in the
+ // witness tree.
+ if (block.vtx[0].wit.vtxinwit.size() != 1 || block.vtx[0].wit.vtxinwit[0].scriptWitness.stack.size() != 1 || block.vtx[0].wit.vtxinwit[0].scriptWitness.stack[0].size() != 32) {
+ return state.DoS(100, error("%s : invalid witness nonce size", __func__), REJECT_INVALID, "bad-witness-nonce-size", true);
+ }
+ CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0].wit.vtxinwit[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin());
+ if (memcmp(hashWitness.begin(), &block.vtx[0].vout[commitpos].scriptPubKey[6], 32)) {
+ return state.DoS(100, error("%s : witness merkle commitment mismatch", __func__), REJECT_INVALID, "bad-witness-merkle-match", true);
+ }
+ fHaveWitness = true;
+ }
+ }
+
+ // No witness data is allowed in blocks that don't commit to witness data, as this would otherwise leave room for spam
+ if (!fHaveWitness) {
+ for (size_t i = 0; i < block.vtx.size(); i++) {
+ if (!block.vtx[i].wit.IsNull()) {
+ return state.DoS(100, error("%s : unexpected witness data found", __func__), REJECT_INVALID, "unexpected-witness", true);
+ }
+ }
+ }
+
return true;
}
@@ -5278,7 +5401,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
else if (!fMissingInputs2)
{
int nDos = 0;
- if (stateDummy.IsInvalid(nDos) && nDos > 0)
+ if (stateDummy.IsInvalid(nDos) && nDos > 0 && (!state.CorruptionPossible() || State(fromPeer)->fHaveWitness))
{
// Punish peer that gave us an invalid orphan tx
Misbehaving(fromPeer, nDos);
@@ -5289,8 +5412,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
// Probably non-standard or insufficient fee/priority
LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString());
vEraseQueue.push_back(orphanHash);
- assert(recentRejects);
- recentRejects->insert(orphanHash);
+ if (!stateDummy.CorruptionPossible()) {
+ assert(recentRejects);
+ recentRejects->insert(orphanHash);
+ }
}
mempool.check(pcoinsTip);
}
@@ -5325,8 +5450,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
LogPrint("mempool", "not keeping orphan with rejected parents %s\n",tx.GetHash().ToString());
}
} else {
- assert(recentRejects);
- recentRejects->insert(tx.GetHash());
+ if (!state.CorruptionPossible()) {
+ assert(recentRejects);
+ recentRejects->insert(tx.GetHash());
+ }
if (pfrom->fWhitelisted && GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
// Always relay transactions received from whitelisted peers, even
@@ -5355,8 +5482,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P
pfrom->PushMessage(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(),
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
- if (nDoS > 0)
+ if (nDoS > 0 && (!state.CorruptionPossible() || State(pfrom->id)->fHaveWitness)) {
+ // When a non-witness-supporting peer gives us a transaction that would
+ // be accepted if witness validation was off, we can't blame them for it.
Misbehaving(pfrom->GetId(), nDoS);
+ }
}
FlushStateToDisk(state, FLUSH_STATE_PERIODIC);
}