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

Este é o segundo artigo sobre blockchain para a indústria, o primeiro .
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:
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:
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:
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
