To ultimately achieve a blockchain and not just a database, we need to add three essential elements to our project:
- Description of the block's data structure and methods
- Description of the transaction's data structure and methods
- Functions of the blockchain that store blocks in the database and retrieve them by their hash or height (or in another way).

This is the second article about blockchain for the industry. The first one .
Recalling the questions that readers asked about the previous article in this series, it should be noted: for storing blockchain data, LevelDB is used in this case, but there is nothing to prevent the use of any other database, such as MySQL. Now, let's take a closer look at the structure of this data.
Let's start with transactions:
Here is its data structure:
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, the data type (2 for transaction), the hash of this transaction, the type of the transaction itself, the timestamp, as well as inputs and outputs are stored. Inputs TxIn store the hash of the transaction being referenced, the number of this output, and the bytecode, while outputs TxOut store some value and also bytecode.
Now let's look at what actions transactions can perform on their data, i.e., we will examine the methods.
The function transaction.NewTransaction(txtype byte) *TX serves to create a transaction.
The method AddTxIn(thattxhash []byte, txoutn int, code []byte) (*TxIn, error) adds an input to the transaction.
The method AddTxOut(value int, data []byte) (*TxOut, error) adds an output to the transaction.
The method ToBytes() []byte converts the transaction into a byte slice.
The internal function preByteHash(bytes []byte) string is used in Build() and Check() for compatibility of the generated transaction hash with the hashes of transactions generated from JavaScript applications.
The method Build() sets the transaction hash as follows: tx.TxHash = preByteHash(tx.ToBytes()).
The method ToJSON() string converts the transaction into a JSON string.
The method FromJSON(data []byte) error loads the transaction from the JSON format provided as a byte slice.
The method Check() bool compares the obtained hash from the transaction hash field with the hash obtained from hashing this transaction (excluding the hash field).
Transactions are added to the block:
The data structure of the block is more extensive:
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 holds the data type, which distinguishes the node and differentiates the block from transactions or other data. For blocks, this value equals 1.
BlockHeight stores the height of the block.
Timestamp holds the time stamp.
HeaderSize is the size of the block in bytes.
PrevBlockHash is the hash of the previous block, while SelfBlockHash is the current one.
TxsHash is the aggregate hash of transactions.
MerkleRoot is the root of the Merkle tree.
The subsequent fields contain the public key of the block creator, the creator's signature, the block version, the number of transactions in the block, and the transactions themselves.
Let's examine its methods:
To create a block, the function block.NewBlock() is used: NewBlock(prevBlockHash string, height int) *Block, which takes the hash of the previous block and the height set for the new block in the blockchain. The block type is also specified from the constants in the types package:
b.DataType = types.BLOCK_TYPE.The AddTx(tx *transaction.TX) method adds a transaction to the block.
The Build() method loads values into the block fields and generates and sets its current hash.
The ToBytesHeader() []byte method converts the block header (without transactions) into a byte slice.
The ToJSON() string method converts the block into JSON format in string representation.
The FromJSON(data []byte) error method loads data from JSON into the block structure.
The Check() bool method generates the block hash and compares it with the one specified in the block's hash field.
The GetTxsHash() string method returns the aggregate hash of all transactions in the block.
The GetMerkleRoot() method sets the Merkle root for the transactions in the block.
The Sign(privk string) method signs the block with the private key of the block creator.
The SetHeight(height int) method records the height of the block in the block structure field.
The GetHeight() int method returns the height of the block as specified in the corresponding field of the block structure.
The ToGOBBytes() []byte method encodes the block in GOB format and returns it as a byte slice.
The FromGOBBytes(data []byte) error method writes the block data into the block structure from the provided byte slice in GOB format.
The GetHash() string method returns the hash of this block.
The GetPrevHash() string method returns the hash of the previous block.
The SetPublicKey(pubk string) method records the public key of the block creator in the block.
Thus, using the methods of the Block object, we can easily convert it to a format suitable for network transmission and storage in the LevelDB database.
The functions of the blockchain package are responsible for saving to the blockchain:
For this, the block must implement the IBlock interface:
type IGOBBytes interface {
ToGOBBytes() []byte
FromGOBBytes(data []byte) error
}
type IBlock interface {
IGOBBytes
GetHash() string
GetPrevHash() string
GetHeight() int
Check() bool
}A connection to the database is created once during the package initialization in the init() function:
db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).CloseDB() is a wrapper for db.Close() — called after working with package functions to close the connection to the database.
The function SetTargetBlockHash(hash string) error writes the hash of the current block to the database with the key defined by the constant BLOCK_HASH.
The function GetTargetBlockHash() (string, error) returns the hash of the current block stored in the database.
The function SetTargetBlockHeight(height int) error writes the blockchain height value for the node to the database with the key defined by the constant BLOCK_HEIGHT.
The function GetTargetBlockHeight() (int, error) returns the blockchain height for the current node stored in the database.
The function CheckBlock(block IBlock) bool verifies the correctness of the block before adding it to the blockchain.
The function AddBlock(block IBlock) error adds the block to the blockchain.
Functions for retrieving and viewing blocks are located in the file explore.go of the blockchain package:
The function GetBlockByHash(hash string) (*block.Block, error) creates an empty block object, loads the block from the database whose hash is provided, and returns a pointer to it.
The genesis block is created by the Genesis() error function from the genesis.go file of the blockchain package.
The next article will discuss connecting client nodes using the WebSocket mechanism.
Source: habr.com
