How I Designed Blocks and Transactions in My Blockchain Using Go

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

How I Designed Blocks and Transactions in My Blockchain Using Go

This is the second article about blockchain for the industry. The first one here.

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: github.com/Rusldv/bcstartup/blob/master/transaction/builder.go

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: github.com/Rusldv/bcstartup/blob/master/block/builder.go

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: github.com/Rusldv/bcstartup/tree/master/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

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster