Jak zaprojektowałem bloki i transakcje w moim blockchainie na Go

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).

Jak zaprojektowałem bloki i transakcje w moim blockchainie na Go

To drugi artykuł na temat blockchaina dla przemysłu, pierwszy tutaj.

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: github.com/Rusldv/bcstartup/blob/master/transaction/builder.go

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: github.com/Rusldv/bcstartup/blob/master/block/builder.go

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: github.com/Rusldv/bcstartup/tree/master/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

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster