Como projetei blocos e transações em meu blockchain usando Go

Para que, no final das contas, consigamos ter um blockchain e não apenas um banco de dados, precisamos adicionar ao nosso projeto 3 elementos importantes:

  • Descrição da estrutura de dados e métodos do bloco
  • Descrição da estrutura de dados e métodos da transação
  • Funções do blockchain que salvam blocos no banco de dados e os encontram lá pelo seu hash ou altura (ou de outra forma).

Como projetei blocos e transações em meu blockchain usando Go

Este é o segundo artigo sobre blockchain para a indústria, o primeiro aqui.

Relembrando as perguntas que recebi dos leitores sobre o artigo anterior desta série, vale destacar: para armazenar os dados do blockchain, neste caso, é utilizado o banco de dados LevelDB, mas nada impede o uso de qualquer outro, digamos o mesmo MySQL. Agora vamos analisar a estrutura desses dados.

Começaremos pelas transações: github.com/Rusldv/bcstartup/blob/master/transaction/builder.go

Aqui está sua estrutura de dados:

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
}

No TX estão armazenados o tipo de dados (para a transação 2), o hash desta transação, o tipo da própria transação, o timestamp, bem como as entradas e saídas. As entradas TxIn armazenam o hash da transação da qual se referem, o número dessa saída e o bytecode, enquanto as saídas TxOut armazenam um valor e também bytecode.

Agora vamos ver quais ações uma transação pode realizar sobre seus dados, ou seja, vamos analisar os métodos.

Para criar uma transação, existe a função transaction.NewTransaction(txtype byte) *TX.

O método AddTxIn(thattxhash []byte, txoutn int, code []byte) (*TxIn, error) adiciona uma entrada à transação.

O método AddTxOut(value int, data []byte) (*TxOut, error) adiciona uma saída à transação.

O método ToBytes() []byte transforma a transação em um slice de bytes.

A função interna preByteHash(bytes []byte) string é usada em Build() e Check() para garantir a compatibilidade do hash das transações geradas com os hashes das transações gerados por aplicativos em JavaScript.

O método Build() define o hash da transação da seguinte forma: tx.TxHash = preByteHash(tx.ToBytes()).

O método ToJSON() string converte a transação em uma string JSON.

O método FromJSON(data []byte) error carrega a transação no formato JSON fornecido como um slice de bytes.

O método Check() bool compara o hash obtido do campo hash da transação com o hash gerado como resultado da hashização desta transação (sem considerar o campo hash).

As transações são adicionadas ao bloco: github.com/Rusldv/bcstartup/blob/master/block/builder.go

A estrutura de dados do bloco é mais volumosa:

tipo 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 armazena o tipo de dados, com base no qual o nó distingue o bloco de uma transação ou outros dados. Para um bloco, esse valor é igual a 1.

BlockHeight armazena a altura do bloco.
Timestamp é o carimbo de data/hora.
HeaderSize é o tamanho do bloco em bytes.
PrevBlockHash é o hash do bloco anterior, enquanto SelfBlockHash é o hash do bloco atual.
TxsHash é o hash total das transações.
MerkleRoot é a raiz da árvore de Merkle.

Nos campos a seguir estão a chave pública do criador do bloco, a assinatura do criador, a versão do bloco, o número de transações no bloco e as próprias transações.

Vamos considerar seus métodos:
Para criar um bloco, utiliza-se a função block.NewBlock(): NewBlock(prevBlockHash string, height int) *Block, que recebe o hash do bloco anterior e a altura definida para o bloco criado na blockchain. Também é definido o tipo de bloco a partir da constante do pacote types:

b.DataType = types.BLOCK_TYPE.

O método AddTx(tx *transaction.TX) adiciona uma transação ao bloco.

O método Build() carrega valores nos campos do bloco e gera e estabelece seu hash atual.

O método ToBytesHeader() []byte converte o cabeçalho do bloco (sem transações) em um slice de bytes.

O método ToJSON() string converte o bloco para o formato JSON em representação de string.

O método FromJSON(data []byte) error carrega dados do JSON para a estrutura do bloco.

O método Check() bool gera o hash do bloco e o compara com o que está no campo do hash do bloco.

O método GetTxsHash() string retorna o hash total de todas as transações no bloco.

O método GetMerkleRoot() define a raiz da árvore de Merkle para as transações no bloco.

O método Sign(privk string) assina o bloco com a chave privada do criador do bloco.

O método SetHeight(height int) registra a altura do bloco no campo da estrutura do bloco.

O método GetHeight() int retorna a altura do bloco conforme indicado no campo correspondente da estrutura do bloco.

O método ToGOBBytes() []byte codifica o bloco no formato GOB e o retorna como um slice de bytes.

O método FromGOBBytes(data []byte) error grava os dados do bloco na estrutura do bloco a partir do slice de bytes passado no formato GOB.

O método GetHash() string retorna o hash deste bloco.

O método GetPrevHash() string retorna o hash do bloco anterior.

O método SetPublicKey(pubk string) grava a chave pública do criador do bloco no bloco.

Assim, usando os métodos do objeto Block, podemos facilmente converter para um formato adequado para transmissão pela rede e armazenamento no banco de dados LevelDB.

As funções do pacote blockchain são responsáveis pela gravação no blockchain: github.com/Rusldv/bcstartup/tree/master/blockchain

Para isso, o bloco deve implementar a interface IBlock:

type IGOBBytes interface {
	ToGOBBytes() []byte
	FromGOBBytes(data []byte) error
}

type IBlock interface {
	IGOBBytes
	GetHash() string
	GetPrevHash() string
	GetHeight() int
	Check() bool

}

A conexão com o banco de dados é criada uma vez durante a inicialização do pacote na função init():

db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).

CloseDB() é um wrapper para db.Close() — chamado após o uso das funções do pacote para fechar a conexão com o banco de dados.

A função SetTargetBlockHash(hash string) error grava no banco de dados o hash do bloco atual com a chave definida pela constante BLOCK_HASH.

A função GetTargetBlockHash() (string, error) retorna o hash do bloco atual armazenado no banco de dados.

A função SetTargetBlockHeight(height int) error grava no banco de dados o valor da altura do blockchain para o nó com a chave definida pela constante BLOCK_HEIGHT.

A função GetTargetBlockHeight() (int, error) retorna a altura do blockchain para este nó, armazenada no banco de dados.

A função CheckBlock(block IBlock) bool realiza uma verificação do bloco quanto à validade antes de adicionar o bloco ao blockchain.

A função AddBlock(block IBlock) error adiciona um bloco ao blockchain.

As funções para obter e visualizar blocos estão no arquivo explore.go do pacote blockchain:

A função GetBlockByHash(hash string) (*block.Block, error) cria um objeto de bloco vazio, carrega o bloco do banco de dados cujo hash foi passado e retorna um ponteiro para ele.

A criação do bloco gênesis é realizada pela função Genesis() error do arquivo genesis.go do pacote blockchain.

No próximo artigo, abordaremos a conexão dos nós dos clientes usando o mecanismo WebSocket.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster