Validierung ist der Prozess, mit dem ein Bitcoin Node überprüft, ob empfangene
Transaktionen und Blöcke den Regeln des Bitcoin-Protokolls
entsprechen.
Die folgenden Abschnitte orientieren sich an der Referenzimplementierung
Bitcoin Core. Andere Implementierungen können interne Abläufe
unterschiedlich organisieren, müssen jedoch dieselben Konsensregeln durchsetzen, um mit
dem Bitcoin-Netzwerk kompatibel zu bleiben.
Dabei vertraut der Node weder dem Sender noch anderen Teilnehmern im
P2P-Netzwerk. Jede Transaktion und jeder
Block werden lokal gegen das Regelwerk geprüft, bevor sie akzeptiert, gespeichert oder
weitergeleitet werden.
Ungültige Daten werden verworfen.
Gültige Daten können in den Mempool aufgenommen, an andere Peers weitergeleitet
oder Teil der lokalen Blockchain werden.
Die Validierung besteht aus zwei getrennten Prozessen:
Beide verwenden dieselben Konsensregeln, prüfen jedoch unterschiedliche Datenstrukturen.
Jeder vollständig validierende Node führt diese Prüfungen selbstständig aus.
Dadurch entsteht Konsens nicht durch Vertrauen in andere Teilnehmer, sondern durch die
unabhängige Anwendung desselben Regelwerks auf vielen Rechnern gleichzeitig.
Die Validierungspipeline
Transaktionen und Blöcke durchlaufen getrennte Validierungspfade. Bei Blöcken laufen viele Prüfungen vor
ConnectBlock() die endgültige Konsensentscheidung und die Anwendung des neuen
Chainstate fallen in ConnectBlock() zusammen.
Dabei handelt es sich nicht um eine einzelne Prüfung, sondern um mehrere aufeinanderfolgende Validierungsschritte.
Schlägt einer dieser Schritte fehl, wird die Transaktion verworfen.
Die Grafik zeigt vereinfacht, welche Prüfungen eine Transaktion bei der Validierung durchläuft.
Sie orientiert sich an Bitcoin Core v29, fasst jedoch mehrere
interne Funktionen zu größeren Prüfschritten zusammen.
Die tatsächliche Implementierung ist detaillierter und verteilt die Prüfungen auf verschiedene Komponenten.
Für das Verständnis ist vor allem wichtig, in welcher Reihenfolge die Prüfungen grundsätzlich stattfinden.
Einfache und schnelle Prüfungen werden zuerst durchgeführt. Aufwendigere Prüfungen folgen erst, wenn die vorherigen
Schritte erfolgreich waren.
Die folgenden Abschnitte orientieren sich an dieser Reihenfolge.
Deserialisierung
Bevor ein Node UTXOs, Signaturen oder Beträge prüfen kann, muss er die
empfangene Transaktion zunächst lesen und in ihre einzelnen Bestandteile zerlegen.
Dazu wird die empfangene Bytefolge deserialisiert und als interne CTransaction interpretiert.
Schlägt das Einlesen fehl, wird die Transaktion verworfen, ohne CheckTransaction() aufzurufen.
Serialisierung und Feldaufbau:
Wie ist eine Transaktion aufgebaut?.
Bevor Konsensprüfungen greifen, muss der Node die empfangenen Bytes in eine CTransaction einlesen.
Schlägt das Deserialisieren fehl, wird die Transaktion verworfen, ohne CheckTransaction() aufzurufen.
Aufbau und Feldreihenfolge:
Wie ist eine Transaktion aufgebaut?.
void UnserializeTransaction(TxType& tx, Stream& s, ...)
s >> tx.version;
s >> tx.vin;
...
s >> tx.nLockTime;
Nicht-kanonische Kodierungen oder Werte über MAX_SIZE führen beim Einlesen zu einem Fehler. Das betrifft auch extrem
große Input- oder Output-Anzahlen.
uint64_t ReadCompactSize(Stream& is, bool range_check = true)
if (range_check && nSizeRet > MAX_SIZE)
throw std::ios_base::failure("ReadCompactSize(): size too large");
Bei SegWit-Transaktionen folgen nach Inputs und Outputs die Witness-Stacks pro Input.
Marker, Flag und Witness-Format: SegWit Marker und Flag,
Witness-Daten, SegWit.
if ((flags & 1) && fAllowWitness)
for (size_t i = 0; i < tx.vin.size(); i++)
s >> tx.vin[i].scriptWitness.stack;
if (!tx.HasWitness())
throw std::ios_base::failure("Superfluous witness record");
Liegt eine gültig deserialisierte CTransaction vor, prüft CheckTransaction() grundlegende Strukturregeln, die nur
von der Transaktionsform abhängen, nicht vom aktuellen UTXO-Set oder von
Script-Ergebnissen.
Jede Transaktion muss mindestens einen Input besitzen.
Ohne diese Regel könnten beliebige Transaktionen ohne Referenz auf bestehende UTXOs erzeugt
werden.
if (tx.vin.empty())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vin-empty");
Auch in CheckTransaction() wird geprüft, ob eine Transaktion die Konsensgrenze für das
Transaktionsgewicht einhält.
CheckTransaction() bewertet hier die serialisierte Transaktion ohne Witness, multipliziert mit dem
Witness-Skalierungsfaktor 4, und vergleicht mit MAX_BLOCK_WEIGHT (bad-txns-oversize).
Das ist eine frühe Obergrenze: Das vollständige Transaktionsgewicht (Non-Witness-Bytes × 4 plus Witness-Bytes × 1)
wird in späteren Schritten der Pipeline und beim Block zusätzlich geprüft. Details: SegWit.
if (::GetSerializeSize(TX_NO_WITNESS(tx)) * WITNESS_SCALE_FACTOR
> MAX_BLOCK_WEIGHT)
return state.Invalid(..., "bad-txns-oversize");
Jeder Output trägt einen Betrag in Satoshis (nValue). Diese Werte müssen im gültigen Bereich liegen und dürfen
zusammen keine Geldmenge erzeugen.
Die ersten drei Prüfungen laufen in CheckTransaction() noch ohne UTXO-Kontext. Die Bilanzprüfung (Inputs decken
Outputs) folgt später mit Consensus::CheckTxInputs(), wenn die referenzierten UTXO-Werte bekannt sind.
Jeder Output-Betrag muss null oder positiv sein.
Negative nValue-Felder sind ungültig (CVE-2010-5139).
if (txout.nValue < 0)
return state.Invalid(..., "bad-txns-vout-negative");
In amount.h definiert Core CAmount als Satoshis und setzt COIN auf 100.000.000 Satoshis pro BTC. MAX_MONEY ist
21_000_000 × COIN, also 2.100.000.000.000.000 Satoshis (nominell
21 Millionen BTC).
Das ist die obere Grenze für einen einzelnen Output-Betrag (nValue). Sie ist eine Konsens-Sicherheitsgrenze gegen
absurd große Werte und Überläufe, nicht die aktuelle Umlaufmenge.
Nach CheckTransaction() braucht der Node den Chainstate (UTXO-Set):
Für jeden Input muss die referenzierte UTXO existieren, unausgegeben sein und bei
Coinbase die Reifezeit erfüllen.
Consensus::CheckTxInputs() in tx_verify.cpp prüft das gegen eine CCoinsViewCache. Ob eine UTXO bereits ausgegeben
wurde, prüft der Node beim Lesen (HaveCoin()). Das Entfernen aus dem UTXO-Set erfolgt erst in
ConnectBlock() beim Anwenden des Zustands (SpendCoin()), nicht als eigene
Konsensregel.
Jeder Input verweist über prevout (TXID +
vout) auf eine Ausgabe einer früheren Transaktion.
HaveInputs() prüft für jeden Input, ob diese COutPoint im aktuellen UTXO-Set gefunden wird.
if (!inputs.HaveInputs(tx))
return state.Invalid(..., "bad-txns-inputs-missingorspent");
Eine UTXO, die bereits in einer anderen Transaktion ausgegeben wurde, gilt als spent.
HaveCoin() liefert nur dann true, wenn der Eintrag existiert und IsSpent() false ist. Sonst schlägt HaveInputs()
fehl (bad-txns-inputs-missingorspent).
Jeder Input muss das scriptPubKey der referenzierten
UTXO erfüllen. Core führt das in CheckInputScripts() aus; pro Input wird
VerifyScript() mit Konsens-Flags aufgerufen.
CScriptCheck ruft VerifyScript() auf. Dabei werden scriptSig und scriptPubKey ausgewertet; bei
Witness- und Taproot-Ausgaben kommen
zusätzliche Prüfpfade dazu.
OP_CHECKSIG / OP_CHECKSIGVERIFY (Legacy, SegWit v0) und
Schnorr-Prüfungen (ECDSA, Taproot) müssen zum jeweiligen scriptPubKey
und zum Transaktionsinhalt passen.
if (VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags,
CachingTransactionSignatureChecker(...), &error))
return std::nullopt;
Mit Flag SCRIPT_VERIFY_WITNESS prüft VerifyScript() Witness v0 Programme
(P2WPKH,
P2WSH, P2SH-P2WSH).
Witness-Stacks müssen zum scriptPubKey passen; unerwartete
Witness-Daten ohne Witness-Programm sind ungültig
(SCRIPT_ERR_WITNESS_UNEXPECTED).
if (flags & SCRIPT_VERIFY_WITNESS) {
if (scriptPubKey.IsWitnessProgram(...))
VerifyWitnessProgram(...);
if (!hadWitness && !witness->IsNull())
return SCRIPT_ERR_WITNESS_UNEXPECTED;}
Witness v1 mit 32-Byte-Programm ist Taproot (BIP 341). Mit SCRIPT_VERIFY_TAPROOT prüft VerifyWitnessProgram() den
Key Path (Schnorr-Signatur) oder den Script Path (Leaf-Script + Merkle-Commitment).
Erst wenn Deserialisierung, Strukturprüfung, Betrags-, UTXO- und
Script-Regeln erfolgreich waren, gilt die Transaktion als konsensgültig.
Anschließend können zusätzliche Policy-Regeln angewendet werden. Diese bestimmen, ob die
Transaktion im Mempool gespeichert und an andere Peers weitergeleitet wird. Sie
können strenger sein als Konsens, dürfen aber nicht lockerer.
Policy-Regeln
Konsensgültige Transaktionen können trotzdem abgelehnt werden, wenn sie lokale Policy nicht
erfüllen (Standardness, Relay-Gebühr). Das betrifft Mempool-Aufnahme und
Weiterleitung, nicht die Gültigkeit in einem Block.
Einzelne Transaktionen brauchen eine ausreichende Fee-Rate (modified_fees / vsize), sonst werden sie nicht in den
Mempool aufgenommen und nicht weitergeleitet.
Standard-Default in policy.h: DEFAULT_MIN_RELAY_TX_FEE = 1000 Satoshis pro kvB (konfigurierbar mit
-minrelaytxfee). Zusätzlich kann der Mempool eine dynamische Mindestgebühr setzen (mempool min fee not met).
if (ws.m_modified_fees < m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize))
return state.Invalid(..., "min relay fee not met");
Besteht die Konsens- und Policy-Prüfung, kann die Transaktion in den lokalen Mempool und von dort an
Peers weitergegeben werden.
AcceptToMemoryPool() ist der zentrale Einstieg: CheckTransaction(), Policy (IsStandardTx, Gebühren), UTXO- und
Script-Prüfungen, danach StageAddition() in CTxMemPool.
Scheitert ein Schritt, landet die Transaktion nicht im Mempool (Log: MempoolRejectedTx).
Nach erfolgreicher Aufnahme kündigt der Node die Transaktion anderen Peers per inv an (RelayTransaction()).
Peers laden die TX bei Bedarf mit getdata. Zusätzlich filtert BIP 133 feefilter: Peers mit zu niedriger Fee-Rate
erhalten keine Ankündigung. Mehr: Relay.
RBF (Replace-by-Fee): Konflikt mit Mempool-TXs nur, wenn die Ersatz-TX höhere Feerate zahlt und BIP-125-Regeln
erfüllt (ReplacementChecks(), PaysForRBF() mit incrementalrelayfee). Signal: nSequence in Inputs. Details:
RBF.
CPFP (Child-Pays-for-Parent): Ein Kind mit hoher Gebühr kann ein Eltern-Paket rentabel machen
(ProcessNewPackage(), CPFP-Carve-out bei too-long-mempool-chain). Details: CPFP.
Bevor ein Block Teil der aktiven Blockchain wird, prüft der Node die
empfangene Bytefolge in mehreren Schritten. Schlägt ein Schritt fehl, wird der Block verworfen.
Die Pipeline ist analog zur Transaktionsvalidierung, endet in Core aber in
ConnectBlock(): Dort laufen die letzten kontextabhängigen Konsensprüfungen gegen das
UTXO-Set und unmittelbar danach die Anwendung des neuen Zustands. Erst wenn
ConnectBlock() erfolgreich war, gilt der Block als gültig verbunden.
Die Grafik ist eine vereinfachte Übersicht über Bitcoin Core v29. Mehrere interne Funktionen (CheckBlock,
ContextualCheckBlockHeader, ConnectBlock und weitere) sind zu größeren Schritten zusammengefasst. Die
Implementierung ist detaillierter und verteilt Prüfungen auf verschiedene Komponenten.
Wichtig: SpendCoin() und AddCoins() sind keine Konsensregeln, sondern die Zustandsänderung nach bestandenen
Prüfungen. Policy für Mempool und Relay ist hier nicht
beschrieben.
Deserialisierung
Bevor ein Node Blockheader, Proof of Work oder Transaktionen prüfen
kann, liest er die empfangene Bytefolge ein und erzeugt daraus eine interne CBlock-Struktur.
Nach der Deserialisierung liegen nVersion, hashPrevBlock, hashMerkleRoot, nTime, nBits
und nNonce in CBlockHeader vor (Felder: Blockheader). Dieser
Abschnitt enthält nur Konsensprüfungen gegen die bisherige Kette, vor allem in AcceptBlockHeader() und
ContextualCheckBlockHeader().
hashPrevBlock muss auf einen bekannten, gültigen CBlockIndex zeigen
(prev-blk-not-found, bad-prevblk).
Ein Block ohne Anknüpfung an die lokale Chain kann nicht als Fortsetzung akzeptiert werden.
BlockMap::iterator mi = m_blockman.m_block_index.find(block.hashPrevBlock);
if (mi == m_blockman.m_block_index.end())
return state.Invalid(..., "prev-blk-not-found");
if (pindexPrev->nStatus & BLOCK_FAILED_MASK)
return state.Invalid(..., "bad-prevblk");
Der Zeitstempel darf höchstens zwei Stunden in der Zukunft der lokalen Systemuhr liegen (MAX_FUTURE_BLOCK_TIME,
time-too-new).
Das begrenzt vorspätige Blöcke und hält die Median-Time-Past-Logik der Nachfolger stabil.
if (block.Time() > NodeClock::now() + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME})
return state.Invalid(..., "time-too-new", "block timestamp too far in the future");
Liegt eine gültig deserialisierte CBlock vor, prüft CheckBlock() grundlegende Struktur- und Größenregeln, die nur
von der Blockform abhängen, nicht vom aktuellen UTXO-Set.
Ein Block ohne Transaktionen ist ungültig (bad-blk-length).
Ab Position 1 darf keine weitere Coinbase vorkommen (bad-cb-multiple).
Die Blocksubsidy wird genau einmal pro Block ausgezahlt.
for (unsigned int i = 1; i < block.vtx.size(); i++)
if (block.vtx[i]->IsCoinBase())
return state.Invalid(..., "bad-cb-multiple", "more than one coinbase");
Die maßgebliche Konsensgrenze ist MAX_BLOCK_WEIGHT = 4.000.000 Gewichtseinheiten (SegWit: Non-Witness-Bytes × 4
plus Witness-Bytes × 1).
CheckBlock() enthält frühe Größenprüfungen (bad-blk-length, u. a. Transaktionsanzahl und serialisierte Größe mit
WITNESS_SCALE_FACTOR). Die eigentliche Gewichtsprüfung erfolgt in ContextualCheckBlock() über
GetBlockWeight(block) und lehnt mit bad-blk-weight ab. Diese spätere Prüfung ist die bindende Obergrenze für das
vollständige Blockgewicht inklusive Witness.
Die erste Transaktion eines Blocks besitzt Sonderregeln: Sie erzeugt neue Coins
(Subsidy plus Gebühren) und referenziert keine frühere UTXO.
Dass genau eine Coinbase an erster Position steht, prüft Blockstruktur
(bad-cb-missing, bad-cb-multiple).
Nach BIP34 muss das Coinbase-scriptSig mit der serialisierten Blockhöhe beginnen (bad-cb-height).
Das verhindert unter anderem identische Coinbase-IDs bei gleicher TX-Struktur auf verschiedenen Höhen.
BlockMerkleRoot() setzt mutated, wenn der Baum-Algorithmus zwei identische Hashes am Listenende verdoppelt
(CVE-2012-2459).
Das ist kein erneuter Abgleich mit hashMerkleRoot, sondern ein Schutz gegen historische Merkle-Malleability:
verdoppelte TXID-Sequenzen können denselben Root erzeugen. Core lehnt solche Blöcke ab (bad-txns-duplicate).
if (mutated)
return state.Invalid(..., "bad-txns-duplicate", "duplicate transaction");
Jede Transaktion des Blocks wird über die vollständige Transaktionsvalidierung geprüft.
CheckBlock() wendet CheckTransaction() kontextfrei an; die UTXO- und Script-Prüfungen gegen den
Chainstate laufen in ConnectBlock() mit CheckTxInputs() und
CheckInputScripts().
Die einzelnen RuleCards stehen im Abschnitt Transaktionsvalidierung. Hier die kompakte
Pipeline pro Transaktion:
Zusätzlich zu den Betragsregeln einzelner Transaktionen wird die Geldmenge auf Blockebene geprüft. Diese Prüfungen
laufen in ConnectBlock() (nach dem Durchlauf der Block-Transaktionen). Neue Coins dürfen nur über die Coinbase
entstehen, und nur in der Höhe von Subsidy plus gesammelten Gebühren.
Das ist der zentrale Inflationsschutz pro Block: GetBlockSubsidy() begrenzt
die Neuausgabe je Höhe (Halving); die Coinbase-Auszahlung wird mit bad-cb-amount gegen
Subsidy + Gebühren geprüft.
GetBlockSubsidy() liefert die maximal erlaubte Neuausgabe für diese Blockhöhe (abnehmend durch Halvings bis 0).
Normale Transaktionen dürfen keine neuen Coins erzeugen (bad-txns-in-belowout in CheckTxInputs).
Auf Blockebene verhindert bad-cb-amount, dass die Coinbase die erlaubte Emission pro Block überschreitet. Langfristig
deckeln Subsidy-Schedule und MAX_MONEY die Gesamtmenge.
ConnectBlock() bündelt die letzten Konsensprüfungen gegen den aktuellen Chainstate und
die Anwendung des neuen Zustands. Ein Block ist erst dann endgültig konsensgültig verbunden, wenn diese Funktion
ohne Fehler durchläuft.
Dazu gehören unter anderem CheckTxInputs(), CheckInputScripts() und die Block-Betragsregeln
(bad-cb-amount), siehe Transaktionen validieren. Scheitert eine Prüfung, bricht
ConnectBlock() ab und der Chainstate bleibt unverändert.
Nur ein vollständig erfolgreicher ConnectBlock()-Lauf bestätigt, dass der Block alle kontextabhängigen
Konsensprüfungen besteht. Die Zustandsanwendung muss in demselben Aufruf ebenfalls erfolgreich sein.
Innerhalb von ConnectBlock() wendet Core den geprüften Block auf das UTXO-Set
an. Die folgenden Schritte sind keineKonsensregeln, sondern die Anwendung eines
bereits validierten Zustands.
SpendCoin(), AddCoins() und CTxUndo ändern den Chainstate; ob Inputs gültig sind, wurde
zuvor durch UTXO-Regeln und HaveCoin() geprüft.
Nach jeder Transaktion im Block zeigt der CCoinsViewCache den aktualisierten Stand: ausgegebene Inputs weg, neue
Outputs drin. Die Coinbase-Betragsprüfung (bad-cb-amount) folgt nach diesem Durchlauf noch in demselben
ConnectBlock()-Aufruf.
UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
Die vorherigen Abschnitte beschreiben die einzelnen Prüfungen, die Bitcoin Core vor und in
ConnectBlock() ausführt.
Erst ein erfolgreicher Durchlauf von ConnectBlock() bestätigt, dass auch die letzten
kontextabhängigen Konsensprüfungen gegen den aktuellen Chainstate bestanden wurden.
Gleichzeitig wird der neue Zustand auf das UTXO-Set angewendet.
SpendCoin() und AddCoins() sind dabei keine eigenen Konsensregeln, sondern die Anwendung des bereits validierten
Zustands. Scheitert eine Prüfung, wird der Block nicht verbunden und der Chainstate bleibt
unverändert.
Verstöße führen zur Ablehnung der Transaktion oder des Blocks, unabhängig vom Sender.
Konsensregeln müssen von allen Teilnehmern identisch interpretiert werden, sonst
entstehen gespaltene Chains.
Beispiele auf dieser Seite: CheckTransaction(), CheckBlock(), ConnectBlock(), MAX_BLOCK_WEIGHT, Script-Flags.
Konsens entscheidet, was in der Blockchain stehen darf. Details zum Regelwerk:
Konsensregeln, Script-Engine.
Policy
Policy sind lokale Regeln für Mempool und Relay. Sie
betreffen, ob ein Node eine Transaktion speichert oder an Peers weitergibt,
nicht ob sie in einem gültigen Block erlaubt wäre.
Policy kann zwischen Nodes variieren (Gebührenschwellen, Standardness, RBF-Einstellungen).
Policy darf strenger sein als Konsens (mehr ablehnen).
Policy darf niemals lockerer sein als Konsens (keine konsens-ungültige TX durchlassen).
Eine Transaktion kann konsensgültig sein und trotzdem wegen Policy nicht in den
Mempool aufgenommen oder nicht per Relay weitergegeben werden.
Nimmt ein Miner die Transaktion dennoch in einen Block auf und der Block besteht
ConnectBlock(), müssen Full Nodes sie akzeptieren: Die Entscheidung fällt dann
über Konsens, nicht über die lokale Policy des einzelnen Nodes.
Das Verhältnis in Kurzform:
Ebene
Frage
Wer entscheidet
Konsens
Darf die TX oder der Block Teil einer gültigen Blockchain sein?
Nicht jeder Client validiert auf dem gleichen Niveau. Der Unterschied betrifft vor allem, wie viel ein Client selbst
prüft und wem er für die restliche Aussage vertrauen muss.
Full Validation
Ein Full Node mit vollständiger Validierung (wie Bitcoin Core im Standardbetrieb):
Full Validation validiert die Regeln selbst anhand vollständiger Block- und
Transaktionsdaten plus Chainstate.
SPV ist leichter und schneller, Full Validation ist die Grundlage für trustless Teilnahme am Netzwerk. Beide können
dieselbe Header-Kette sehen; nur der Full Node kann alle darin enthaltenen
Zahlungen unabhängig verifizieren.
Ein SPV-Client kann feststellen, dass eine Transaktion in einer langen
Proof-of-Work-Kette enthalten ist. Er kann jedoch nicht selbst überprüfen, ob die Transaktion,
die ausgegebenen Inputs oder die enthaltenen Scripts
tatsächlich alle Konsensregeln erfüllen. Dafür fehlen ihm das vollständige
UTXO-Set sowie die vollständigen Block- und Transaktionsdaten. Ein Full Node
führt diese Prüfungen selbst aus.
Konsens entsteht durch unabhängige Validierung
Bitcoin-Konsens entsteht nicht dadurch, dass Nodes über die Gültigkeit
von Blöcken abstimmen. Jeder Full Node validiert Blöcke und
Transaktionen selbstständig gegen dieselben Konsensregeln.
Wenn viele Nodes unabhängig zum gleichen Ergebnis gelangen, entsteht daraus der globale Konsens des Netzwerks.