Blockchain 8 min read

Build a Mini Blockchain in Go: Transaction Design, Mempool, and Block Validation

This tutorial walks through extending a Go mini‑blockchain with transaction structures, hashing, signatures, a mempool, block verification, proof‑of‑work consensus, mining rewards, and a balance‑query example, providing complete source code for each step.

Code Wrench
Code Wrench
Code Wrench
Build a Mini Blockchain in Go: Transaction Design, Mempool, and Block Validation

1. Transaction Design

Define a transaction struct with fields for a unique hash (ID), sender address (From), receiver address (To), amount, and a signature. The hash is computed with SHA‑256 over the concatenated sender, receiver and amount. The Sign method uses an ECDSA private key to generate a signature and stores it in the transaction.

type Transaction struct {
    ID        []byte
    From      string
    To        string
    Amount    float64
    Signature []byte
}

func HashTransaction(tx Transaction) []byte {
    data := tx.From + "|" + tx.To + "|" + strconv.FormatFloat(tx.Amount, 'f', -1, 64)
    h := sha256.Sum256([]byte(data))
    return h[:]
}

func (tx *Transaction) Sign(privKey *ecdsa.PrivateKey) error {
    tx.ID = HashTransaction(*tx)
    r, s, err := ecdsa.Sign(rand.Reader, privKey, tx.ID)
    if err != nil {
        return err
    }
    tx.Signature = append(r.Bytes(), s.Bytes()...)
    return nil
}

2. Mempool

A simple in‑memory pool stores pending transactions. Mining nodes retrieve transactions from this pool when constructing a new block.

type TxPool struct {
    Transactions []*Transaction
}

func (p *TxPool) AddTransaction(tx *Transaction) {
    p.Transactions = append(p.Transactions, tx)
}

3. Block Structure and Transaction Verification

The block structure is extended to include a slice of transaction IDs (or full transactions). When a block is added, each transaction is verified for a valid signature and basic business rules such as sufficient balance.

type Block struct {
    Index        int      `json:"index"`
    Timestamp    int64    `json:"timestamp"`
    Transactions []string `json:"transactions"` // transaction IDs
    PrevHash     string   `json:"prev_hash"`
    Nonce        int64    `json:"nonce"`
    Hash         string   `json:"hash"`
}

Verification extracts the sender’s public key from the From field (assumed to be a hex‑encoded elliptic‑curve point), decodes the signature, recomputes the transaction hash and calls ecdsa.VerifyASN1.

func VerifyTransaction(tx Transaction) bool {
    pubBytes, err := hex.DecodeString(tx.From)
    if err != nil {
        return false
    }
    x, y := elliptic.Unmarshal(elliptic.P256(), pubBytes)
    if x == nil {
        return false
    }
    pub := ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}
    sigBytes, err := hex.DecodeString(tx.Signature)
    if err != nil {
        return false
    }
    h := HashTransaction(tx)
    return ecdsa.VerifyASN1(&pub, h, sigBytes)
}

4. Consensus (Proof‑of‑Work)

Nodes use a PoW algorithm with a difficulty target defined by a required prefix of zero bits. The longest valid chain is accepted as the canonical chain.

func AddBlock(b Block) bool {
    chainMutex.Lock()
    defer chainMutex.Unlock()

    last := blockchain[len(blockchain)-1]
    if b.PrevHash != last.Hash {
        return false
    }
    if CalculateHash(b) != b.Hash {
        return false
    }
    if !strings.HasPrefix(b.Hash, strings.Repeat("0", difficulty)) {
        return false
    }
    blockchain = append(blockchain, b)
    return true
}

5. Mining Reward (Coinbase Transaction)

A special transaction with no inputs creates new coins for the miner. It is identified by a Txid of "0" and a Vout of –1.

func CoinbaseTx(coinbaseData, to string, amount int) UTXOTx {
    inputs := []TxInput{{
        Txid:      "0",
        Vout:      -1,
        Signature: coinbaseData,
        PubKey:    "coinbase",
    }}
    outputs := []TxOutput{{
        Address: to,
        Amount:  amount,
    }}
    return UTXOTx{Inputs: inputs, Outputs: outputs}
}

6. Balance Query Example

Iterate over all blocks and their transactions, subtracting amounts sent from the address and adding amounts received.

func (bc *Blockchain) GetBalance(address string) float64 {
    var balance float64
    for _, block := range bc.Blocks {
        for _, tx := range block.Transactions {
            if tx.From == address {
                balance -= tx.Amount
            }
            if tx.To == address {
                balance += tx.Amount
            }
        }
    }
    return balance
}

7. Summary of Implemented Features

Transaction struct with hashing and ECDSA signing.

In‑memory mempool for pending transactions.

Block format extended to store transaction IDs and verification logic.

Proof‑of‑Work consensus with longest‑chain rule.

Coinbase transaction for mining rewards.

Simple balance calculation based on transaction history.

Source code repositories (for reference):

https://github.com/louis-xie-programmer/mini_chain

https://gitee.com/louis_xie/mini_chain

transactionGoblockchainProof of WorkMempool
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.