Per ottenere un blockchain e non semplicemente un database, è necessario aggiungere al proprio progetto 3 elementi importanti:
- Descrizione della struttura dei dati e dei metodi del blocco
- Descrizione della struttura dei dati e dei metodi della transazione
- Funzioni del blockchain che salvano i blocchi nel database e li cercano lì tramite l'hash o l'altezza (o in altro modo).

Questo è il secondo articolo sul blockchain per l'industria, il primo .
Ricordando le domande che i lettori mi hanno posto riguardo al precedente articolo di questo ciclo, va notato che per memorizzare i dati del blockchain in questo caso viene utilizzato il database LevelDB, ma non c'è nulla che impedisca di usare qualsiasi altro, ad esempio MySQL. Adesso analizziamo la struttura di questi dati.
Iniziamo con le transazioni:
Ecco la sua struttura dati:
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 vengono memorizzati i tipi di dati (per la transazione 2), l'hash di questa transazione, il tipo della transazione stessa, il timestamp, oltre agli input e agli output. Gli input TxIn contengono l'hash della transazione a cui si riferiscono, il numero di questo output e il bytecode, mentre gli output TxOut memorizzano un valore e anch'esso bytecode.
Adesso vediamo quali azioni può compiere una transazione sui propri dati, ovvero analizziamo i metodi.
La funzione che serve a creare una transazione è transaction.NewTransaction(txtype byte) *TX.
Il metodo AddTxIn(thattxhash []byte, txoutn int, code []byte) (*TxIn, error) aggiunge un input alla transazione.
Il metodo AddTxOut(value int, data []byte) (*TxOut, error) aggiunge un output alla transazione.
Il metodo ToBytes() []byte trasforma la transazione in un array di byte.
La funzione interna preByteHash(bytes []byte) string è utilizzata in Build() e Check() per garantire la compatibilità dell'hash delle transazioni create con gli hash delle transazioni generate da applicazioni in JavaScript.
Il metodo Build() imposta l'hash della transazione nel seguente modo: tx.TxHash = preByteHash(tx.ToBytes()).
Il metodo ToJSON() string trasforma la transazione in una stringa JSON.
Il metodo FromJSON(data []byte) error carica la transazione dal formato JSON passato come slice di byte.
Il metodo Check() bool confronta l'hash ottenuto dal campo hash della transazione con l'hash ottenuto dal hashing di questa transazione (escludendo il campo hash).
Le transazioni vengono aggiunte al blocco:
La struttura dei dati del blocco è più complessa:
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 memorizza il tipo di dati, consentendo al nodo di distinguere tra un blocco, una transazione o altri dati. Per un blocco, questo valore è pari a 1.
BlockHeight memorizza l'altezza del blocco.
Timestamp rappresenta la marca temporale.
HeaderSize indica la dimensione del blocco in byte.
PrevBlockHash è l'hash del blocco precedente, mentre SelfBlockHash è quello attuale.
TxsHash è l'hash totale delle transazioni.
MerkleRoot è la radice dell'albero di Merkle.
Successivamente, nei campi ci sono la chiave pubblica del creatore del blocco, la firma del creatore, la versione del blocco, il numero di transazioni nel blocco e le rispettive transazioni.
Esaminiamo i suoi metodi:
Per creare un blocco si usa la funzione block.NewBlock(): NewBlock(prevBlockHash string, height int) *Block, che accetta l'hash del blocco precedente e l'altezza stabilita per il blocco creato nella blockchain. Viene inoltre specificato il tipo di blocco dalla costante del pacchetto types:
b.DataType = types.BLOCK_TYPE.Il metodo AddTx(tx *transaction.TX) aggiunge una transazione al blocco.
Il metodo Build() carica i valori nei campi del blocco e genera e imposta il suo attuale hash.
Il metodo ToBytesHeader() []byte converte l'intestazione del blocco (senza transazioni) in uno slice di byte.
Il metodo ToJSON() string converte il blocco nel formato JSON nella rappresentazione stringa dei dati.
Il metodo FromJSON(data []byte) error carica i dati da JSON nella struttura del blocco.
Il metodo Check() bool genera l'hash del blocco e lo confronta con quello specificato nel campo dell'hash del blocco.
Il metodo GetTxsHash() string restituisce l'hash complessivo di tutte le transazioni nel blocco.
Il metodo GetMerkleRoot() imposta la radice dell'albero di Merkle per le transazioni nel blocco.
Il metodo Sign(privk string) firma il blocco con la chiave privata del creatore del blocco.
Il metodo SetHeight(height int) registra l'altezza del blocco nel campo della struttura del blocco.
Il metodo GetHeight() int restituisce l'altezza del blocco come specificato nel campo corrispondente della struttura del blocco.
Il metodo ToGOBBytes() []byte codifica un blocco nel formato GOB e lo restituisce come un array di byte.
Il metodo FromGOBBytes(data []byte) error scrive i dati del blocco nella struttura del blocco dall'array di byte fornito nel formato GOB.
Il metodo GetHash() string restituisce l'hash di questo blocco.
Il metodo GetPrevHash() string restituisce l'hash del blocco precedente.
Il metodo SetPublicKey(pubk string) scrive nel blocco la chiave pubblica del creatore del blocco.
In questo modo, grazie ai metodi dell'oggetto Block, possiamo facilmente convertirlo nel formato per la trasmissione in rete e per il salvataggio nel database LevelDB.
Le funzioni del pacchetto blockchain si occupano del salvataggio nella blockchain:
Per questo il blocco deve implementare l'interfaccia IBlock:
type IGOBBytes interface {
ToGOBBytes() []byte
FromGOBBytes(data []byte) error
}
type IBlock interface {
IGOBBytes
GetHash() string
GetPrevHash() string
GetHeight() int
Check() bool
}La connessione al database è creata una sola volta durante l'inizializzazione del pacchetto nella funzione init():
db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).CloseDB() è un wrapper per db.Close() — viene chiamato dopo aver lavorato con le funzioni del pacchetto per chiudere la connessione al DB.
La funzione SetTargetBlockHash(hash string) error scrive nel DB l'hash del blocco corrente con la chiave definita dalla costante BLOCK_HASH.
La funzione GetTargetBlockHash() (stringa, errore) restituisce l'hash dell'attuale blocco, memorizzato nel database.
La funzione SetTargetBlockHeight(height int) errore scrive nel database il valore dell'altezza della blockchain per il nodo con la chiave definita dalla costante BLOCK_HEIGHT.
La funzione GetTargetBlockHeight() (int, errore) restituisce l'altezza della blockchain per questo nodo, memorizzata nel database.
La funzione CheckBlock(block IBlock) bool esegue un controllo del blocco per verificarne la correttezza prima di aggiungere il blocco alla blockchain.
La funzione AddBlock(block IBlock) errore aggiunge un blocco alla blockchain.
Le funzioni per ottenere e visualizzare i blocchi si trovano nel file explore.go del pacchetto blockchain:
La funzione GetBlockByHash(hash string) (*block.Block, errore) crea un oggetto blocco vuoto, carica il blocco dal database il cui hash è stato fornito e restituisce un puntatore ad esso.
La creazione del blocco genesi è effettuata dalla funzione Genesis() errore nel file genesis.go del pacchetto blockchain.
Nel prossimo articolo si parlerà della connessione ai nodi dei clienti attraverso il meccanismo WebSocket.
Fonte: habr.com
