Aby ostatecznie otrzymać blockchain, a nie tylko bazę danych, musimy dodać do naszego projektu 3 ważne elementy:
- Opis struktury danych i metod bloku
- Opis struktury danych i metod transakcji
- Funkcje blockchaina, które zapisują bloki w bazie danych i znajdują je tam po ich hashu lub wysokości (lub w inny sposób).

To drugi artykuł na temat blockchaina dla przemysłu, pierwszy .
Przypominając pytania, które zadawali mi czytelnicy do poprzedniego artykułu z tego cyklu, należy zauważyć: do przechowywania danych blockchaina w tym przypadku używana jest baza danych LevelDB, ale nic nie stoi na przeszkodzie, aby używać innej, na przykład MySQL. A teraz przyjrzyjmy się strukturze tych danych.
Zacznijmy od transakcji:
Oto jej struktura danych:
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
}W TX przechowywane są typ danych (dla transakcji 2), hash tej transakcji, typ samej transakcji, znacznik czasu oraz wejścia i wyjścia. Wejścia TxIn przechowują hash transakcji, do której się odnoszą, numer tego wyjścia i kod bajtowy, a wyjścia TxOut przechowują jakąś wartość oraz również kod bajtowy.
Teraz zobaczmy, jakie działania może wykonywać transakcja nad swoimi danymi, tzn. przeanalizujmy metody.
Funkcja do tworzenia transakcji to transaction.NewTransaction(txtype byte) *TX.
Metoda AddTxIn(thattxhash []byte, txoutn int, code []byte) (*TxIn, error) dodaje wejście do transakcji.
Metoda AddTxOut(value int, data []byte) (*TxOut, error) dodaje wyjście do transakcji.
Metoda ToBytes() []byte zmienia transakcję w bajtowy wektor.
Wewnętrzna funkcja preByteHash(bytes []byte) string jest stosowana w Build() i Check() w celu zapewnienia zgodności tworzonego hasha transakcji z hashami transakcji generowanymi przez aplikacje w JavaScript.
Metoda Build() ustawia hash transakcji w następujący sposób: tx.TxHash = preByteHash(tx.ToBytes()).
Metoda ToJSON() string przekształca transakcję w łańcuch JSON.
Metoda FromJSON(data []byte) error ładuje transakcję z formatu JSON, przekazanego jako bajtowy wektor.
Metoda Check() bool porównuje otrzymany hash z pola hasha transakcji z hashem uzyskanym w wyniku haszowania tej transakcji (bez uwzględnienia pola hasha).
Transakcje są dodawane do bloku:
Struktura danych bloku jest bardziej rozbudowana:
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 przechowuje typ danych, według którego węzeł rozróżnia blok od transakcji lub innych danych. Dla bloku ta wartość wynosi 1.
BlockHeight przechowuje wysokość bloku.
Timestamp to znacznik czasu.
HeaderSize to rozmiar bloku w bajtach.
PrevBlockHash to hasz poprzedniego bloku, a SelfBlockHash to aktualny.
TxsHash to общий хеш транзакций.
MerkleRoot to korzeń drzewa Merkle.
Dalej znajdują się pola z publicznym kluczem twórcy bloku, podpisem twórcy, wersją bloku, liczbą transakcji w bloku oraz tymi transakcjami.
Rozważmy jego metody:
Do utworzenia bloku stosuje się funkcję block.NewBlock(): NewBlock(prevBlockHash string, height int) *Block, która przyjmuje hasz poprzedniego bloku i wysokość ustawioną dla utworzonego bloku w łańcuchu bloków. Określa się również typ bloku z konstanta pakietu types:
b.DataType = types.BLOCK_TYPE.Metoda AddTx(tx *transaction.TX) dodaje transakcję do bloku.
Metoda Build() ładuje wartości do pól bloku oraz generuje i ustawia jego aktualny hasz.
Metoda ToBytesHeader() []byte konwertuje nagłówek bloku (bez transakcji) na tablicę bajtów.
Metoda ToJSON() string konwertuje blok na format JSON w postaci tekstowej.
Metoda FromJSON(data []byte) error ładowa dane z JSON do struktury bloku.
Metoda Check() bool generuje hasz bloku i porównuje go z danym w polu hasza bloku.
Metoda GetTxsHash() string zwraca łączny hasz wszystkich transakcji w bloku.
Metoda GetMerkleRoot() ustawia korzeń drzewa Merkle dla transakcji w bloku.
Metoda Sign(privk string) podpisuje blok prywatnym kluczem twórcy bloku.
Metoda SetHeight(height int) zapisuje wysokość bloku w polu struktury bloku.
Metoda GetHeight() int zwraca wysokość bloku tak, jak jest określona w odpowiednim polu struktury bloku.
Metoda ToGOBBytes() []byte koduje blok w formacie GOB i zwraca go jako tablicę bajtów.
Metoda FromGOBBytes(data []byte) error zapisuje dane bloku w strukturze bloku z przekazanej tablicy bajtów w formacie GOB.
Metoda GetHash() string zwraca hasz danego bloku.
Metoda GetPrevHash() string zwraca hasz poprzedniego bloku.
Metoda SetPublicKey(pubk string) zapisuje w bloku publiczny klucz twórcy bloku.
W ten sposób, za pomocą metod obiektu Block, możemy łatwo przekonwertować go na format do przesyłania przez sieć i przechowywania w bazie danych LevelDB.
Za przechowywanie w blockchainie odpowiadają funkcje pakietu blockchain:
W tym celu blok musi implementować interfejs IBlock:
type IGOBBytes interface {
ToGOBBytes() []byte
FromGOBBytes(data []byte) error
}
type IBlock interface {
IGOBBytes
GetHash() string
GetPrevHash() string
GetHeight() int
Check() bool
}Połączenie z bazą danych jest tworzone raz podczas inicjalizacji pakietu w funkcji init():
db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).CloseDB() to wrapper dla db.Close() — wywoływana po pracy z funkcjami pakietu, aby zamknąć połączenie z DB.
Funkcja SetTargetBlockHash(hash string) error zapisuje w DB hash bieżącego bloku z kluczem określonym stałą BLOCK_HASH.
Funkcja GetTargetBlockHash() (string, error) zwraca hash bieżącego bloku przechowywany w DB.
Funkcja SetTargetBlockHeight(height int) error zapisuje w DB wartość wysokości blockchainu dla węzła z kluczem określonym stałą BLOCK_HEIGHT.
Funkcja GetTargetBlockHeight() (int, error) zwraca wysokość blockchainu dla danego węzła, przechowywaną w DB.
Funkcja CheckBlock(block IBlock) bool wykonuje weryfikację bloku pod kątem poprawności przed dodaniem tego bloku do blockchainu.
Funkcja AddBlock(block IBlock) error dodaje blok do blockchainu.
Funkcje do uzyskiwania i przeglądania bloków znajdują się w pliku explore.go pakietu blockchain:
Funkcja GetBlockByHash(hash string) (*block.Block, error) tworzy pusty obiekt bloku, ładuje do niego blok z DB, którego hash został podany i zwraca wskaźnik do niego.
Tworzenie bloku generycznego odbywa się za pomocą funkcji Genesis() error z pliku genesis.go pakietu blockchain.
W następnym artykule omówimy połączenie z węzłem klientów za pomocą mechanizmu WebSocket.
Źródło: habr.com
