How I Designed Blocks and Transactions on My Go Blockchain

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

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

How I Designed Blocks and Transactions on My Go Blockchain

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

Recalling the questions that readers asked me for the previous article in this series, it should be noted: in this case, the LevelDB database is used to store blockchain data, but nothing prevents using any other, say the same MySQL. Now let's 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
}

TX stores the type of data (for transaction 2), the hash of this transaction, the type of the transaction itself, the timestamp, as well as inputs and outputs. The TxIn inputs store the hash of the transaction whose 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 take a look at the methods.

The transaction.NewTransaction(txtype byte) *TX function is used to create a transaction.

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

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

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

The preByteHash(bytes []byte) string internal function is used in Build() and Check() to ensure that the generated transaction hash is compatible with transaction hashes generated from JavaScript applications.

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

The ToJSON() string method converts the transaction to a JSON string.

The FromJSON(data []byte) error method loads a transaction from the JSON format passed 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 block data structure 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 the type of data, according to which the node separates the block from the 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 is the hash of the previous block, and SelfBlockHash is the hash of the current block.
TxsHash is the total transaction hash.
MerkleRoot is the root of the Merkle tree.

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

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

b.DataType = types.BLOCK_TYPE.

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

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

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

The ToJSON() string method converts the block to JSON format in a string representation of the data.

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

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

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

The GetMerkleRoot() method sets the root of the Merkle tree for transactions in a block.

The Sign(privk string) method signs the block with the private key of the block creator.

The SetHeight(height int) method writes the height of the block to 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 a block in GOB format and returns it as a byte slice.

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

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

The GetPrevHash() string method 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 into a format for transmission over the network and saving it to the LevelDB database.

The functions of the blockchain package are responsible for saving in the blockchain: github.com/Rusldv/bcstartup/tree/master/blockchain

To do 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

}

The database connection is created once when the package is initialized in the init() function:

db, err = leveldb.OpenFile(BLOCKCHAIN_DB_DEBUG, nil).

CloseDB() is a wrapper for db.Cloce() - called after working with 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 to 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 blockchain height value for the node with the key specified by the BLOCK_HEIGHT constant.

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

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

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

The functions for getting 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 there from the database whose hash was passed to it, and returns a pointer to it.

The creation of the genesis block is carried out by the Genesis() error function from the genesis.go file of the blockchain package.

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

Source: habr.com

Add a comment