How I designed blocks and transactions on my Go blockchain

In order to ultimately produce a blockchain, and not just a database, we need to add 3 important elements to our project:

  • Description of data structure and block methods
  • Description of data structure and transaction methods
  • Blockchain functions that save blocks in the database and find them there by their hash or height (or something else).

image

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

Remembering the questions that readers asked me in the previous article of this series, it should be noted: in this case, the LevelDB database is used to store blockchain data, but it doesn’t interfere with using any other, say the same MySQL. And now we will understand the structure of this data.

Let's start with the 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
}

TX stores the data type (for transaction 2), the hash of this transaction, the type of transaction itself, the timestamp, and also the inputs and outputs. The TxIn inputs store the hash of the transaction to which the output is referenced, the number of this output and the bytecode, and the TxOut outputs store some value and also the bytecode.

Now let's see what actions a transaction can perform on its data, i.e. let's analyze the methods.

To create a transaction, use the transaction.NewTransaction (txtype byte) * TX function.

The AddTxIn method (thattxhash [] byte, txoutn int, code [] byte) (* TxIn, error) adds the input to the transaction.

The AddTxOut (value int, data [] byte) (* TxOut, error) method adds output to the transaction.

The ToBytes () [] byte method turns a transaction into a byte slice.

The internal function preByteHash (bytes [] byte) string is used in Build () and Check () for compatibility of the created transaction hash with transaction hashes generated from JavaScript applications.

The Build () method sets the transaction hash as follows: tx.TxHash = preByteHash (tx.ToBytes ()).

The ToJSON () method of a string converts a transaction into a JSON string.

The FromJSON (data [] byte) error method loads a transaction from the JSON format transmitted as a byte slice.

The Check () bool method compares the received hash from the hash field of the transaction with the hash obtained as a result of 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 voluminous:

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 stores a data type, a node on it, and detaches the block from a transaction or other data. For a block, this value is 1.

BlockHeight stores the height of the block.
Timestamp timestamp.
HeaderSize block size in bytes.
PrevBlockHash hash of the previous block, and SelfBlockHash - the current.
TxsHash is a common transaction hash.
MerkleRoot - The root of the Merkle tree.

Next in the fields is the public key of the creator of the block, the signature of the creator, the version of the block, the number of transactions in the block, and these transactions themselves.

Consider its methods:
The block.NewBlock () function is used to create a block: NewBlock (prevBlockHash string, height int) * Block, which accepts the hash of the previous block and the height set for the created block in the blockchain. The block type from the constant of the types package is also set:
b.DataType = types.BLOCK_TYPE.


The AddTx method (tx * transaction.TX) adds a transaction to the block.

The Build () method loads the values ​​into the fields of the block and generates and sets its current hash.

The ToBytesHeader () [] byte method translates the block header (without transactions) into a byte slice.

The ToJSON () method of the string translates the block into JSON format in the string representation of the data.

The FromJSON (data [] byte) error method loads data from JSON into the block structure.

The Check () bool method generates a block hash and compares it with the one specified in the block hash field.

The GetTxsHash () string method returns the common hash of all transactions in the block.

The GetMerkleRoot () method sets the root of the Merkle tree for 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 writes the block height to the block structure field.

The GetHeight () int method returns the height of the block as indicated in the corresponding field of the block structure.

The ToGOBBytes () [] byte method encodes the block in the GOB format and returns it as a byte slice.

The FromGOBBytes (data [] byte) error method writes block data to the block structure from the transmitted byte slice in GOB format.

The GetHash () method string returns the hash of this block.

The GetPrevHash () method string returns the hash of the previous block.

The SetPublicKey (pubk string) method writes the public key of the block creator to the block.

Thus, using the methods of the Block object, we can easily convert it to a format for transmission over the network and saving it to the LevelDB database.

The blockchain package functions 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 database connection is created once during initialization of the package in the init () function:
db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).

CloseDB () is a wrapper for db.Cloce () - called after working with the package functions to close the database connection.

The SetTargetBlockHash (hash string) error function writes the hash of the current block with the key specified by the BLOCK_HASH constant in the database.

The GetTargetBlockHash () (string, error) function returns the hash of the current block stored in the database.

The SetTargetBlockHeight (height int) error function writes to the database the value of the blockchain height for the node with the key specified by the BLOCK_HEIGHT constant.

The GetTargetBlockHeight () (int, error) function returns the blockchain height for a given node stored in the database.

The CheckBlock (block IBlock) bool function checks the block for correctness before adding this block to the blockchain.

The AddBlock (block IBlock) error function adds a block to the blockchain.

The functions for receiving and viewing blocks are located in the explore.go file of the blockchain package:

The GetBlockByHash (hash string) (* block.Block, error) function creates an empty block object, loads a block from the database whose hash is passed to it 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.

In the next article, we will talk about connecting to a client node using the WebSocket mechanism.

All Articles