Mit der Bitcoin-Blase 2017 (und Ende 2020) wurden beide Themen aktuell. Bestimmte Dinge sollen hier untersucht werden.
Blockchain ([ErLo18], Kapitel 11)
Blockchain ist ein Beispiel für eine replizierte (auf mehrere Rechner verteilte) Datenbank, welche aus einer Reihe miteinander verketteter Blöcke basiert (mit Zeitstempel versehen, kryptographisch abgesichert), welche eine unveränderliche Historie darstellen.
Bitcoin basiert auf einer Blockchain mit dem 'Proof of work', also "Minen"(=Schürfen von neuen Bitcoin-Blöcken).
Bitcoin
Mehrere Transaktionen werden zu einem Block zusammengefasst und vorher auf Gültigkeit überprüft, mit einem Zeitstempel versehen sowie dem Hashwert des Vorgängerblocks. Im Block gibt es ein 32-Bit-Feld (nonce, number used once), welches von den sog. Minern variiert wird
zur Berechnung des Hash mit SHA256, so dass der Hashwert eine Schwierigkeit (difficulty, Anzahl 0en, mit dem der
Hashwert beginnen muss) erreicht.
Block ([ErLo18],Kap. 11.9)
4-Byte-Kennzeichnung als Bitcoin-Block
Angabe Blockgrösse
80-Byte Blockheader
Transaktionszähler
Die zu bestätigenden Transaktionen
Blockheader ([ErLo18],Kap. 11.10)
4 Byte
Versionsnummer
32 Byte
Hashwert des Headers des Vorgängerblocks
32 Byte
Hash über die nachfolgenden Transaktionen (Merkle Tree)
4 Byte
Zeitstempel
4 Byte
Codierter Schwellenwert(difficulty)
4 Byte
Nonce
Miner suchen im Netzwert nach zu bestätigenden Transaktionen, prüfen diese auf Plausibilität, bilden einen Block und berechnen Hashwerte. Wenn ein Block gefunden wurde, wird der Block an das Netzwerk weitergeleitet, dort geprüft und ggf. an die Blockchain angehängt, und die Miner erhalten eine Belohnung, was den finanziellen Anreiz für Minen darstellt
Es gibt also 4,29*109 Hash-Werte zu berechnen. Die Wahrscheinlichkeit für einen Treffer ist 1 / 232,
und es ist auch möglich, dass alle Hashwerte nicht den Schwellwert unterschreiten, dann war alles Energieverschwendung. Auf einem modernen PC/notebooks schafft man heute ca. 200.000 Hashes pro Sekunde (unabhängig von Blockgrösse, da nur ein zweifaches sha256 auf den Header
ausgeführt wird). Ohne Asics sind das dann ca. 8 Stunden, um alle Nonce-Werte zu durchlaufen.
Mit python kann man das gut vereinfacht darstellen:
Falls zwei oder mehr Miner parallel einen gültigen Block finden, wird dann immer mit der längsten Block-Kette weitergemacht, und damit auch ggf. schon bestätigte Transaktionen wieder aufgelöst. Die Schwierigkeit
wird laufend so angepasst, dass eine möglichst konstante Rate neuer Blockbestätigungen vorkommt.
Wie kann man sich Bitcoin-Transaktionen anzeigen lassen
Es sind maximal ca. 21 Mio. Bitcoins möglich, berechenbar anhand der Belohnungen pro Block.
Für die ersten 210.000 Blöcke gab es 50 Bitcoins Belohnung, für die nächsten 210.000 Blöcke
nur noch 25 Bitcoins u.s.w. Damit ergibt sich:
∑i=132 (100 / 2i) * 210.000 = (1 - 1 / 232) * 21.000.000 = 20.999.999
Im Mai 2020 war das letzte sog. Bitcoin-Halving, wodurch sich die Belohnung halbiert hat.
Was sind die Vor- und Nachteile einer Blockchain
Vorteil ist, es wird kein steuernder Intermediator gebraucht, z.B. eine Bank oder ein Makler. Nachteil ist, dass bei 'Proof of work' nur der erste Finder eine Belohnung erhält, die anderen haben umsonst gearbeitet und Energie verschwendet. Und in einer Blockchain können Daten sichtbar sein, welche nicht für jedermann
einsehbar sein sollten.
Gibt es Alternativen zu sequentiellem hashing ?
Da die Veränderung von einem Bit des Eingangswertes alle Ausgangsbits den Hashes beinflusst, sind keine statistischen Methoden bekannt, Eingabewerte für Nonce anders zu bestimmen.
Wie funktioniert bitcoin-core
Bitcoin-core ist die Referenzimplementierung von Satoshi Nakamoto, wird laufend weiterentwickelt und kann unter github abgerufen werden. Es besteht auf einem Daemon-Prozess (bitcoind), welcher z.B. die Blockchain herunter lädt, mit den anderen mining-nodes kommuniziert und einem Aufruf-Programm (bitcoin-cli), mit welchem man z.B. Transaktionen oder Blöcke anzeigen und den mining-Prozess durchführen kann
Infos über Blöcke
Unter src/primitives/block.h ist die Deklaration eines Blockheaders und Blocks.
class CBlock : public CBlockHeader
{
public:
// network and disk
std::vector<CTransactionRef> vtx;
// memory only
mutable bool fChecked;
...
Damit ist auch klar, dass man im Quelltext nach "nNonce" suchen muss, wenn man die Zugriffe auf die Nonce eines Block finden will. Oder nach "hashMerkleRoot", wenn man erkennen will, wo die Merkle-root gesetzt wird.
Infos über Transaktionen
Unter src/primitives/transaction.h ist die Deklaration einer Transaktion.
die nSequence-Nummer wird gesetzt, wenn die Transaktionen in einen Block eingefügt wurde.
Wie kann man im Quelltext suchen ohne IDE wie Eclipse
Im Ablageverzeichnis von bitcoin-core kann man mit fgrep rekursiv suchen, in der Implementierung den *.cpp-Klassen, oder in Deklarationen den *.h-Klassen. Suche nach generateBlocks z.B.:
Dies ist die Startklasse des Daemon-Prozesses, hier ist die Methode AppInitMain. Diese
prüft den Zustand der lokal in Dateien abgespeicherten Blockchain und nimmt hier laufende Aktualisierungen vor.
Hier finden sich Programmcode zur Validierung/Bau von Blöcken, Transaktionen und Blockchain.
rpc/mining.cpp
Hier finden sich die Aufrufmöglichkeiten durch bitcoin-cli
Ort des Minings, also Variation der Nonce
unter src/rpc/mining.cpp findet man den Zugriff auf nNonce(s.o.) in der statischen Funktion generateBlocks(). Hier ist die while-Schleife, wo Nonce hochgezählt wird (++pblock->nNonce;).
Wie kann man generateBlocks aufrufen ? Die einzige Verwendung in *.cpp-Dateien ist in mining.cpp selbst, und zwar in den durch bitcoin-cli aufrufbaren Methoden generatetodescriptor oder generatetoaddress.
Oben wird mit nGenerate und nMaxTries übergeben, wie viele Blöcke man minen möchte und wie viele Versuche bis zum Abbruch gemacht werden sollen. Mit nHeight wird zuerst die Anzahl der Blöcke der blockchain ermittelt. Aus dem Mempool(unbestätigte Transaktionen) und dem coinbase-Skript wird dann ein neuer Block erstmal zusammengebaut:
Zudem wird das minen beendet, wenn der Benutzer bitcoind z.B. mit CTRL-C beendet hat, dann liefert ShutdownRequested() == wahr zurück.
Wenn ein Treffer, also eine passende nNonce gefunden wurde, kommt man hinter die if-Anweisungen:
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");
++nHeight;
Mit ProcessNewBlock(...)[aus validation.cpp] wird der geminte pblock an das Netzwerk geschickt und auf eine Rückmeldung gewartet, ob ein anderer mining-node diesen akzeptiert hat. Wenn ja, wird die Anzahl der Blöcke hochgezählt. Zurückgegeben wird dann eine ggf. leere Liste an Block-Hashes zu neuen Blöcken.
Ausgaben in bitcoind
Im Ordner mit den Einstellungen werden die Ausgaben in debug.log geschrieben, eine Liste der anderen mining-nodes im Netz in peers.dat, unter Beachtung der bitcoin.conf.
Man kann erst minen, wenn die gesamte Blockchain heruntergeladen worden ist, ggf. im prune-Modus, wo alte Blöcke entfernt wurden. Ruft man mit RPC trotzdem getblocktemplate auf, kommt folgende Fehlermeldung: bitcoin core is in initial sync and waiting for blocks ...
if (::ChainstateActive().IsInitialBlockDownload())
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
Die Methode IsInitialBlockDownload() gehört der Klasse CChainState aus validation.cpp.
Zur Vermeidung der Fehlermeldung Bitcoin Core is in initial sync and waiting for blocks
in rpc/mining.cpp/Methode getblocktemplate:
if (false) // ::ChainstateActive().IsInitialBlockDownload())
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
LogPrintf("Huerde passiert\n");
Zur Vermeidung der Fehlermeldung CreateNewBlock: TestBlockValidity failed: bad-fork-prior-to-checkpoint
In valdation.cpp/Methode ContextualCheckBlockHeader:
if (false) { // pcheckpoint && nHeight < pcheckpoint->nHeight) {
LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight);
return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint");
}
Ein wirkliches sicheres und sinnvolles Minen liegt aber nur vor, wenn die gesamte Blockchain vorliegt.
Es werden immer paarweise zwei hashes einer Transaktion in einem Block aneinander gekettet und dann ein neuer hash mit sha256(doppelt) gebildet. Dazu werden Ebenen gebildet, deren untere Ebene die hashes aller Transaktionen des Blockes sind und auf oberster Ebene der Wurzel-Hash, genannt Merkle-Root. Die Merkle-Root ist im Header enthalten, sie Transaktionen im Block. bitcoin-core nimmt die Berechnung der Merkle-Root schon ab.
Mempool/Transactionpool
Darunter versteht man lokal vorhandene Transaktionen, welche in einen Block einzubauen sind.
bitcoind prüft am Start, ob es in mempool.dat schon welche gibt
("Imported mempool transactions from disk", Methode LoadMempool() von validation.cpp). Eine neue
Transaktion wird durch AcceptToMemoryPool/validation.cpp addiert. Empfangen werden neue
Transaktionen in der Nachricht NetMsgType::TX/ProcessMessage in net_processing.cpp.
Nachrichtensystem
Netzwerknachrichten kann man sich anzeigen lassen, indem man bitcoind mit der Option -debug=net startet.
Das Empfangen eintreffender Nachrichten von peer nodes geschieht in bool static ProcessMessage(...) von net_processing.cpp bei der Auswertung von strCommand (Daten liegen in Variable vRecv):
if (strCommand == NetMsgType::HEADERS)
...
Dort können dann erhaltene Daten wie neue Header/neue Blöcke weiter verarbeitet werden. Das Senden eigener Nachrichten kann mit dem ConnectionManager connman
gemacht werden, hier gibt man den peer node Id an, den Nachrichtentyp (hier schicke Header an peer) und eine Liste mit Infos, in diesem Fall
die Hashwerte des Headers in vHeaders.
Um eigene Header anzufordern schickt man die Nachricht MsgType::GETHEADERS
und erhält dann eine Nachricht MsgType::HEADERS in net_processing.cpp
if (strCommand == NetMsgType::HEADERS)
...
for (unsigned int n = 0; n < nCount; n++) {
vRecv >> headers[n];
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
}
return ProcessHeadersMessage(pfrom, connman, headers, chainparams, /*via_compact_block=*/false);
Oder ein anderer Node schickt eine Anfrage an einen selbst, das landet dann in net_processing.cpp
if (strCommand == NetMsgType::GETHEADERS) {
...
Wenn die Blockchain nicht vollständig geladen ist, wird aber nicht weiter gemacht:
if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->HasPermission(PF_NOBAN)) {
LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom->GetId());
return true;
}
Um eigene Blöcke anzufordern schickt man die Nachricht MsgType::GETDATA
Man sieht, dass weitere Transaktionen nur erhalten werden (RequestTx()), wenn die Blockchain vollständig heruntergeladen wurde.
AlreadyHave() prüft zum Hash, ob schon was im Mempool ist.
Aktualisierungsprozess der Blockchain
Beim Start von bitcoind erfolgt immer ein Untersuchen, wie die aktuelle Höhe der Blockchain ist der Peer nodes im Vergleich zur
lokal gespeicherten blockchain (ProcessNewBlockHeaders() von validation.cpp).
Momentan werden neue Blöcke zuerst durch neue BlockHeader über das Netzwerk propagiert, damit kann man sich dann die ganzen Blöcke holen.
In FindNextBlocksToDownload/net_processing.cpp werden die neuen Blöcke bestimmt, was von PeerLogicValidation::SendMessages/net_processing.cpp aufgerufen wird.
in vGetData stehen die Hashwerte der zu ladenden Blöcke drin.
Zusammenhang zwischen private key und public key ([AnAn18], Kap. 4)
Der private Schlüssel besteht aus 256 zufällig gewählten Bits. Der öffentliche Schlüssel wird nach dem
Standard secp256k1 vom NIST bestimmt. Es gibt einen Basispunkt auf einer elliptischen Kurve,
welcher mit dem privaten Schlüssel verknüpft wird. Der Zielpunkt besteht aus (x,y), der public key ist dann (04 x y).
Wenn man zu einem bekannten public key den private key bestimmen muss, entspricht das dem Logarithmieren über
elliptischen Kurven, was sehr schwer ist. Ein Beispielprogramm dazu ist hier.
Abgeleitete Bitcoin-Blockchains
Von dem Original-Bitcoin gab es in der Vergangenheit Ableitungen(forks), welche ab einem bestimmten Zeitpunkt
eine andere Blockchain nützen. Diese verwenden weiterhin den SHA256d-Algorithmus.
Bitcoin (BTC), hashrate* 108 ExtraH/sek
Bitcoin-Cash(BCH), hashrate* 2,8 ExtraH/sek
Bitcoin-SV(BSV), hashrate* 1,3 ExtraH/sek
* Hashrate des Netzwerks 10/2020.
Litecoin
Litecoin (LTC) ist auch eine Ableitung, verwendet aber statt sha256d das Verfahren scrypt, was Mining
mit ASICS erschweren soll. Im Jahr 2021 will sich das Projekt mehr in einen private coin umwandeln. Das letzte
litecoin-halving war im August 2019.