Um letztendlich eine Blockchain und nicht nur eine Datenbank zu erhalten, müssen wir drei wichtige Elemente zu unserem Projekt hinzufügen:
- Beschreibung der Datenstruktur und Methoden des Blocks
- Beschreibung der Datenstruktur und Methoden der Transaktion
- Blockchain-Funktionen, die Blöcke in der Datenbank speichern und sie dort anhand ihres Hashes oder ihrer Höhe (oder auf andere Weise) finden.

Dies ist der zweite Artikel über Blockchain für die Industrie, der erste .
Wenn ich an die Fragen zurückdenke, die mir Leser zum vorherigen Artikel dieser Reihe gestellt haben, sollte erwähnt werden: Für die Speicherung von Blockchain-Daten wird in diesem Fall die LevelDB-Datenbank verwendet, aber es steht nichts im Wege, jede andere wie beispielsweise MySQL zu nutzen. Lassen Sie uns nun die Struktur dieser Daten untersuchen.
Beginnen wir mit den Transaktionen:
Hier ist ihre Datenstruktur:
type TX struct {
DataType byte
TxHash string
TxType byte
Timestamp int64
INs []TxIn
OUTs []TxOut
}
type TxIn struct {
ThatTxHash string
TxOutN int
ByteCode string
}
type TxOut struct {
Value int
ByteCode string
}In TX werden die Datentypen (für Transaktion 2), der Hash dieser Transaktion, der Typ der Transaktion selbst, der Zeitstempel sowie Eingaben und Ausgaben gespeichert. Die Eingaben TxIn enthalten den Hash der Transaktion, auf deren Ausgabe verwiesen wird, die Nummer dieser Ausgabe und den Bytecode, während TxOut-Ausgaben Werte und ebenfalls Bytecode speichern.
Schauen wir uns nun an, welche Aktionen eine Transaktion mit ihren Daten durchführen kann, d.h. wir werden die Methoden betrachten.
Die Funktion transaction.NewTransaction(txtype byte) *TX dient zur Erstellung einer Transaktion.
Die Methode AddTxIn(thattxhash []byte, txoutn int, code []byte) (*TxIn, error) fügt einen Eingang zur Transaktion hinzu.
Die Methode AddTxOut(value int, data []byte) (*TxOut, error) fügt eine Ausgabe zur Transaktion hinzu.
Die Methode ToBytes() []byte wandelt die Transaktion in ein Byte-Array um.
Die interne Funktion preByteHash(bytes []byte) string wird in Build() und Check() verwendet, um die Kompatibilität des generierten Transaktionshashs mit Hashes zu gewährleisten, die aus JavaScript-Anwendungen generiert werden.
Die Methode Build() setzt den Transaktionshash wie folgt: tx.TxHash = preByteHash(tx.ToBytes()).
Die Methode ToJSON() string konvertiert die Transaktion in eine JSON-Zeichenkette.
Die Methode FromJSON(data []byte) error lädt die Transaktion im JSON-Format, das als Byte-Slice übergeben wurde.
Die Methode Check() bool vergleicht den erhaltenen Hash aus dem Transaktionshash-Feld mit dem Hash, der durch das Hashing dieser Transaktion (ohne das Hashfeld) erlangt wurde.
Transaktionen werden in den Block hinzugefügt:
Die Datenstruktur des Blocks ist umfangreicher:
type Block struct {
DataType byte
BlockHeight int
Timestamp int64
HeaderSize int
PrevBlockHash string
SelfBlockHash string
TxsHash string
MerkleRoot string
CreatorPublicKey string
CreatorSig string
Version int
TxsN int
Txs []transaction.TX
}DataType speichert den Datentyp, anhand dessen der Knoten den Block von einer Transaktion oder anderen Daten unterscheidet. Für einen Block hat dieser Wert den Wert 1.
BlockHeight speichert die Höhe des Blocks.
Timestamp ist der Zeitstempel.
HeaderSize ist die Größe des Blocks in Byte.
PrevBlockHash ist der Hash des vorherigen Blocks, während SelfBlockHash der aktuelle ist.
TxsHash ist der Gesamt-Hash der Transaktionen.
MerkleRoot ist die Wurzel des Merkle-Baums.
Darüber hinaus befinden sich in den Feldern der öffentliche Schlüssel des Blockerstellers, die Signatur des Erstellers, die Version des Blocks, die Anzahl der Transaktionen im Block und die Transaktionen selbst.
Betrachten wir seine Methoden:
Die Funktion block.NewBlock() wird verwendet, um einen Block zu erstellen: NewBlock(prevBlockHash string, height int) *Block. Diese Funktion nimmt den Hash des vorherigen Blocks sowie die für den neuen Block im Blockchain festgelegte Höhe entgegen. Außerdem wird der Blocktyp aus der Konstante des Pakets types festgelegt:
b.DataType = types.BLOCK_TYPE.Die Methode AddTx(tx *transaction.TX) fügt dem Block eine Transaktion hinzu.
Die Methode Build() lädt die Werte in die Blockfelder und generiert und setzt den aktuellen Hash.
Die Methode ToBytesHeader() []byte konvertiert den Header des Blocks (ohne Transaktionen) in einen Byte-Slice.
Die Methode ToJSON() string konvertiert den Block in das JSON-Format in einer string-basierten Datenrepräsentation.
Die Methode FromJSON(data []byte) error lädt Daten aus JSON in die Blockstruktur.
Die Methode Check() bool generiert den Hash des Blocks und vergleicht ihn mit dem im Hash-Feld des Blocks angegebenen Wert.
Die Methode GetTxsHash() string gibt den Gesamthash aller Transaktionen im Block zurück.
Die Methode GetMerkleRoot() legt die Wurzel des Merkle-Baums für die Transaktionen im Block fest.
Die Methode Sign(privk string) signiert den Block mit dem privaten Schlüssel des Erstellers.
Die Methode SetHeight(height int) speichert die Höhe des Blocks im entsprechenden Feld der Blockstruktur.
Die Methode GetHeight() int gibt die Höhe des Blocks zurück, wie im entsprechenden Feld der Blockstruktur angegeben.
Die Methode ToGOBBytes() []byte kodiert einen Block im GOB-Format und gibt ihn als Byte-Slice zurück.
Die Methode FromGOBBytes(data []byte) error schreibt die Daten des Blocks aus dem übergebenen Byte-Slice im GOB-Format in die Blockstruktur.
Die Methode GetHash() string gibt den Hash dieses Blocks zurück.
Die Methode GetPrevHash() string gibt den Hash des vorherigen Blocks zurück.
Die Methode SetPublicKey(pubk string) speichert den öffentlichen Schlüssel des Blockerstellers im Block.
Somit können wir mit Hilfe der Methoden des Objekts Block diesen leicht in ein Format konvertieren, das für die Übertragung über das Netzwerk und die Speicherung in der LevelDB geeignet ist.
Für die Speicherung in der Blockchain sind die Funktionen des Pakets blockchain verantwortlich:
Dafür muss der Block das Interface IBlock implementieren:
type IGOBBytes interface {
ToGOBBytes() []byte
FromGOBBytes(data []byte) error
}
type IBlock interface {
IGOBBytes
GetHash() string
GetPrevHash() string
GetHeight() int
Check() bool
}Die Verbindung zur Datenbank wird einmalig bei der Initialisierung des Pakets in der Funktion init() hergestellt:
db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).CloseDB() ist ein Wrapper für db.Close() – wird nach der Verwendung der Funktionen des Pakets aufgerufen, um die Verbindung zur DB zu schließen.
Die Funktion SetTargetBlockHash(hash string) error schreibt den Hash des aktuellen Blocks mit dem Schlüssel, der durch die Konstante BLOCK_HASH festgelegt ist, in die DB.
Die Funktion GetTargetBlockHash() (string, error) gibt den Hash des aktuellen Blocks zurück, der in der Datenbank gespeichert ist.
Die Funktion SetTargetBlockHeight(height int) error schreibt den Wert der Blockhöhe für den Knoten mit dem Schlüssel, der durch die Konstante BLOCK_HEIGHT definiert ist, in die Datenbank.
Die Funktion GetTargetBlockHeight() (int, error) gibt die Blockhöhe für diesen Knoten zurück, die in der Datenbank gespeichert ist.
Die Funktion CheckBlock(block IBlock) bool überprüft die Korrektheit des Blocks, bevor dieser Block in die Blockchain eingefügt wird.
Die Funktion AddBlock(block IBlock) error fügt einen Block zur Blockchain hinzu.
Funktionen zum Abrufen und Anzeigen von Blöcken befinden sich in der Datei explore.go des Pakets blockchain:
Die Funktion GetBlockByHash(hash string) (*block.Block, error) erstellt ein leeres Blockobjekt, lädt den Block mit dem übergebenen Hash aus der Datenbank und gibt einen Verweis darauf zurück.
Die Erstellung des Genesis-Blocks erfolgt durch die Funktion Genesis() error aus der Datei genesis.go des Pakets blockchain.
Im nächsten Artikel wird die Verbindung von Clients zur Node über den WebSocket-Mechanismus behandelt.
Quelle: habr.com
