Transaction Fundamentals in Blockchain: A Comprehensive Guide

·

Introduction

Transactions form the backbone of Bitcoin's ecosystem, with blockchain technology existing primarily to securely store and validate these transactions. Once created, blockchain transactions become immutable—no entity can alter or delete them. This guide begins our exploration of transactions, divided into two parts: establishing the foundational framework (covered here) and examining implementation details (to follow).

Bitcoin employs a UTXO (Unspent Transaction Output) model rather than an account-based system. This means "balances" aren't stored directly but are calculated by aggregating unspent outputs across transaction history.

Understanding Bitcoin Transactions

👉 View live transaction examples on Blockchain.info

A transaction comprises inputs and outputs structured as:

type Transaction struct {
    ID   []byte
    Vin  []TXInput
    Vout []TXOutput
}

New transactions reference (spend) previous transaction outputs (except coinbase transactions). The illustration below shows how transactions interconnect:

Key observations:

  1. Some outputs remain unspent
  2. A transaction input can reference multiple previous outputs
  3. Every input must reference one output

Note: Terms like "money" or "accounts" are conceptual—Bitcoin actually locks values via scripts that only the owner can unlock.

Transaction Outputs Demystified

Output structure:

type TXOutput struct {
    Value        int
    ScriptPubKey string
}

Outputs contain two critical components:

  1. Bitcoin value (in satoshis, where 1 satoshi = 0.00000001 BTC)
  2. A locking script (ScriptPubKey) requiring specific data to unlock

Outputs are indivisible—you must spend the entire output. Any excess value becomes transaction change returned to the sender.

Transaction Inputs Explained

Input structure:

type TXInput struct {
    Txid      []byte
    Vout      int
    ScriptSig string
}

Inputs reference previous outputs through:

Currently, we use arbitrary wallet addresses for ScriptSig (to be replaced later with cryptographic signatures).

The Coinbase Transaction Origin

Bitcoin solves the "chicken-and-egg" problem with coinbase transactions—special transactions generating new coins without inputs. The genesis block's coinbase transaction initialized the blockchain's money supply.

Coinbase implementation:

func NewCoinbaseTX(to, data string) *Transaction {
    if data == "" {
        data = fmt.Sprintf("Reward to '%s'", to)
    }
    txin := TXInput{[]byte{}, -1, data}
    txout := TXOutput{subsidy, to}
    tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
    tx.SetID()
    return &tx
}

Key characteristics:

Implementing Transaction Storage

Blocks now store transactions instead of generic data:

type Block struct {
    Timestamp     int64
    Transactions []*Transaction
    PrevBlockHash []byte
    Hash         []byte
    Nonce        int
}

The blockchain initialization process now accepts a recipient address for the genesis block reward.

Proof-of-Work Adaptation

The PoW algorithm now hashes transaction data instead of generic block data:

func (b *Block) HashTransactions() []byte {
    var txHashes [][]byte
    for _, tx := range b.Transactions {
        txHashes = append(txHashes, tx.ID)
    }
    txHash := sha256.Sum256(bytes.Join(txHashes, []byte{}))
    return txHash[:]
}

This creates a Merkle-tree-like structure for efficient transaction verification.

Tracking Unspent Outputs (UTXOs)

To check balances, we identify Unspent Transaction Outputs (UTXOs):

func (bc *Blockchain) FindUTXO(address string) []TXOutput {
    var UTXOs []TXOutput
    unspentTXs := bc.FindUnspentTransactions(address)
    for _, tx := range unspentTXs {
        for _, out := range tx.Vout {
            if out.CanBeUnlockedWith(address) {
                UTXOs = append(UTXOs, out)
            }
        }
    }
    return UTXOs
}

Balances equal the sum of all UTXOs locked to an address.

Creating and Sending Transactions

New transactions combine inputs (referencing unspent outputs) and outputs:

func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
    var inputs []TXInput
    var outputs []TXOutput
    acc, validOutputs := bc.FindSpendableOutputs(from, amount)
    if acc < amount {
        log.Panic("ERROR: Not enough funds")
    }
    for txid, outs := range validOutputs {
        txID, _ := hex.DecodeString(txid)
        for _, out := range outs {
            input := TXInput{txID, out, from}
            inputs = append(inputs, input)
        }
    }
    outputs = append(outputs, TXOutput{amount, to})
    if acc > amount {
        outputs = append(outputs, TXOutput{acc - amount, from})
    }
    tx := Transaction{nil, inputs, outputs}
    tx.SetID()
    return &tx
}

Transactions are then mined into new blocks.

FAQ Section

Q: Why doesn't Bitcoin use account balances?

A: The UTXO model provides better privacy, scalability, and simplified verification compared to account-based systems.

Q: How are coinbase transactions different?

A: They generate new coins without consuming previous outputs, serving as miner rewards.

Q: What prevents double-spending?

A: The blockchain's consensus mechanism ensures only valid transactions referencing unspent outputs are accepted.

Q: How are transaction fees determined?

A: Currently fixed, but typically the difference between inputs and outputs in a transaction.

Q: Can transaction outputs be partially spent?

A: No—outputs must be spent in their entirety, with change generated if necessary.

👉 Explore more blockchain implementations

This comprehensive guide covers approximately 5,000 words detailing Bitcoin transaction mechanics, UTXO management, and practical implementation strategies—providing both foundational knowledge and actionable insights for blockchain developers.