Para acabarmos com um blockchain e não apenas com um banco de dados, precisamos adicionar 3 elementos importantes ao nosso projeto:
- Descrição da estrutura e métodos de dados do bloco
- Descrição da estrutura de dados e métodos de transação
- Funções Blockchain que salvam blocos em um banco de dados e os encontram por hash ou altura (ou qualquer outra coisa).
Este é o segundo artigo sobre blockchain para a indústria, o primeiro
Lembrando as perguntas que os leitores me fizeram sobre o artigo anterior desta série, cabe ressaltar: neste caso, o banco de dados LevelDB é usado para armazenar dados blockchain, mas nada impede que você use qualquer outro, digamos, MySQL. Agora vamos dar uma olhada na estrutura desses dados.
Vamos começar com as 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
}
TX armazena o tipo de dados (para a transação 2), o hash dessa transação, o tipo da transação em si, um carimbo de data/hora e entradas e saídas. As entradas TxIn armazenam o hash da transação cuja saída é referenciada, o número desta saída e o bytecode, e as saídas TxOut armazenam algum valor e também o bytecode.
Agora vamos ver quais ações uma transação pode realizar em seus dados, ou seja, Vejamos os métodos.
Para criar uma transação, use 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 uma fatia de bytes.
A função interna preByteHash(bytes []byte) string é usada em Build() e Check() para tornar o hash de transação gerado compatível com hashes de transação gerados a partir de aplicativos JavaScript.
O método Build() define o hash da transação da seguinte forma: tx.TxHash = preByteHash(tx.ToBytes()).
O método de string ToJSON() converte uma transação em uma string JSON.
O método de erro FromJSON(data []byte) carrega uma transação do formato JSON passado como uma fatia de bytes.
O método bool Check() compara o hash resultante do campo hash da transação com o hash obtido como resultado do hash desta transação (ignorando o campo hash).
As transações são adicionadas ao bloco:
A estrutura de dados do bloco é mais volumosa:
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 armazena o tipo de dados, o nó o utiliza e distingue o bloco de uma transação ou outros dados. Para um bloco esse valor é 1.
BlockHeight armazena a altura do bloco.
Carimbo de data e hora.
HeaderSize é o tamanho do bloco em bytes.
PrevBlockHash é o hash do bloco anterior e SelfBlockHash é o hash do bloco atual.
TxsHash é um hash geral de transações.
MerkleRoot é a raiz da árvore Merkle.
Mais adiante nos campos 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.
Vejamos seus métodos:
Para criar um bloco, use a função block.NewBlock(): NewBlock(prevBlockHash string, height int) *Block, que pega o hash do bloco anterior e a altura definida para o bloco criado no blockchain. O tipo de bloco também é definido na constante do pacote types:
b.DataType = types.BLOCK_TYPE.
O método AddTx(tx *transaction.TX) adiciona uma transação a um bloco.
O método Build() carrega valores nos campos do bloco e gera e define seu hash atual.
O método ToBytesHeader() []byte converte o cabeçalho do bloco (sem transações) em uma fatia de bytes.
O método de string ToJSON() converte o bloco para o formato JSON em uma representação de string dos dados.
O método de erro FromJSON(data []byte) carrega dados de JSON em uma estrutura de bloco.
O método bool Check() gera um hash de bloco e o compara com aquele especificado no campo hash de bloco.
O método de string GetTxsHash() retorna o hash total de todas as transações no bloco.
O método GetMerkleRoot() especifica a raiz da árvore Merkle para transações em um bloco.
O método Sign(privk string) assina um bloco com a chave privada do criador do bloco.
O método SetHeight(height int) grava a altura do bloco no campo de estrutura do bloco.
O método GetHeight() int retorna a altura do bloco conforme especificado no campo correspondente da estrutura do bloco.
O método ToGOBBytes() []byte codifica um bloco no formato GOB e o retorna como uma fatia de bytes.
O método de erro FromGOBBytes(data []byte) grava dados de bloco na estrutura de bloco a partir da fatia de bytes passada no formato GOB.
O método de string GetHash() retorna o hash do bloco fornecido.
O método de string GetPrevHash() retorna o hash do bloco anterior.
O método SetPublicKey(pubk string) grava a chave pública do criador do bloco no bloco.
Assim, utilizando os métodos do objeto Block, podemos facilmente convertê-lo em um formato para transmissão pela rede e salvamento no banco de dados LevelDB.
As funções do pacote blockchain são responsáveis por salvar no blockchain:
Para fazer 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 quando o pacote é inicializado na função init():
db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).
CloseDB() é um wrapper para db.Cloce() - chamado após trabalhar com as funções do pacote para fechar a conexão com o banco de dados.
A função de erro SetTargetBlockHash(hash string) grava o hash do bloco atual com a chave especificada pela constante BLOCK_HASH no banco de dados.
A função GetTargetBlockHash() (string, error) retorna o hash do bloco atual armazenado no banco de dados.
A função de erro SetTargetBlockHeight(height int) grava no banco de dados o valor da altura do blockchain para o nó com a chave especificada pela constante BLOCK_HEIGHT.
A função GetTargetBlockHeight() (int, error) retorna a altura do blockchain para um determinado nó, armazenado no banco de dados.
A função bool CheckBlock(block IBlock) verifica a correção de um bloco antes de adicionar este bloco ao blockchain.
A função de erro AddBlock(block IBlock) adiciona um bloco ao blockchain.
As funções para recuperar 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 nele um bloco do banco de dados, cujo hash foi passado para ele, e retorna um ponteiro para ele.
A criação de um bloco genesis é realizada pela função de erro Genesis() do arquivo genesis.go do pacote blockchain.
O próximo artigo falará sobre como conectar clientes a um nó usando o mecanismo WebSocket.
Fonte: habr.com