[Blockchain Research Lab] Transaction of Bitcoin's Core Technology Development (Part 1)

foreword

Transactions are the core of Bitcoin, and the only purpose of the blockchain is to store transactions securely and reliably. In the blockchain, once a transaction is created, no one can modify or delete it. Today, we will begin to implement transactions. However, since trading is such a big topic, I will break it down into two parts: In today's section, we will implement the basic framework of trading. In the second part, we will continue to discuss some of its details.


Since Bitcoin adopts the UTXO model, not the account model, the concept of "balance" does not directly exist, and the balance needs to be obtained by traversing the entire transaction history.


Bitcoin transaction

Check out the transaction information in the image below at blockchain.info.


A transaction consists of a combination of inputs and outputs:

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

For each new transaction, its input will reference the output of the previous transaction (there is an exception here, the coinbase transaction), and the reference is the meaning of spending. The so-called reference to a previous output, that is, to include a previous output in the input of another transaction, is to spend the previous transaction output. The output of the transaction is where the coins are actually stored. The following diagram illustrates the interconnection between transactions:


Notice:

  1. Some outputs are not linked to an input

  2. The input of a transaction can refer to the output of multiple previous transactions

  3. An input must refer to an output

Throughout this article, we will use words like "money", "coin", "spend", "send", "account", etc. But in Bitcoin, there is no such concept. Transactions are simply a script that locks values ​​that can only be unlocked by the person who locked them.


Every Bitcoin transaction creates an output, and the output is recorded on the blockchain. Sending bitcoins to someone actually means creating a new UTXO and registering it with that person's address, which can be used by him.

transaction output

Let's start with the output:

type TXOutput struct {
   Value        int
   ScriptPubKey string
   }

The output mainly consists of two parts:

  1. A certain amount of Bitcoin ( Value)

  2. A lock script ( ScriptPubKey) that must be unlocked to spend this money.

In fact, it is the output that stores "coins" (note, the  Value field above). The storage here refers to locking the output with a mathematical puzzle, and the puzzle is stored in  ScriptPubKey it. Internally, Bitcoin uses a   scripting language called Script , which defines the logic for locking and unlocking outputs. While the language is fairly primitive (this is intentional to avoid potential hacking and abuse) and uncomplicated, we won't discuss its details here. You can  find a detailed explanation here .

In Bitcoin, the value field stores the amount of  satoshi  , not the amount of BTC. One  satoshi  is equal to one millionth of BTC (0.00000001 BTC), which is the smallest monetary unit in Bitcoin (like a 1 cent coin).

Since addresses are not implemented yet, we will avoid full scripts involving logic for now. ScriptPubKey An arbitrary string (user-defined wallet address) will be stored.

By the way, having such a scripting language also means that Bitcoin can actually be used as a smart contract platform.

A very important point about outputs is that they are indivisible . That is, you can't cite just one part of it. Either don't use it, if you want to use it, you must use it up at one time. When a new transaction references an output, that output must be spent in full. If its value is greater than required, then a change is generated and the change is returned to the sender. This is very similar to the real world scenario, when you want to pay, if something is worth $1 and you give a $5 bill, you get a $4 change.

send coins

Now, we want to send some coins to other people. To do this, we need to create a new transaction, put it in a block, and then mine the block. Before we only implemented the coinbase transaction (which is a special kind of transaction), now we need a general general transaction:

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")
   }

   // Build a list of inputs
   for txid, outs := range validOutputs {
       txID, err := hex.DecodeString(txid)

       for _, out := range outs {
           input := TXInput{txID, out, from}
           inputs = append(inputs, input)
       }
   }

   // Build a list of outputs
   outputs = append(outputs, TXOutput{amount, to})
   if acc > amount {
       outputs = append(outputs, TXOutput{acc - amount, from}) // a change
   }

   tx := Transaction{nil, inputs, outputs}
   tx.SetID()

   return &tx}

Before creating new outputs, we first have to find all unspent outputs and make sure they have enough value, and that's what  FindSpendableOutputs methods do. Then, for each found output, an input is created that refers to that output. Next, we create two outputs:

  1. One is locked by the recipient address. This is the coins actually transferred to other addresses.

  2. One is locked by the sender address. This is a change. Generated only when unspent output exceeds what is required for a new transaction. Remember: outputs are indivisible .

FindSpendableOutputs The method is based on the previously defined  FindUnspentTransactions method:

func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
   unspentOutputs := make(map[string][]int)
   unspentTXs := bc.FindUnspentTransactions(address)
   accumulated := 0Work:
   for _, tx := range unspentTXs {
       txID := hex.EncodeToString(tx.ID)

       for outIdx, out := range tx.Vout {
           if out.CanBeUnlockedWith(address) && accumulated < amount {
               accumulated += out.Value
               unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)

               if accumulated >= amount {
                   break Work                }
           }
       }
   }

   return accumulated, unspentOutputs}

This method iterates over all unspent transactions and accumulates their values. When the accumulated value is greater than or equal to the value we want to transfer, it stops and returns the accumulated value along with the output index grouped by transaction ID. All we have to do is withdraw enough money to pay.

Now, we can modify the  Blockchain.MineBlock method:

func (bc *Blockchain) MineBlock(transactions []*Transaction) {
   ...
   newBlock := NewBlock(transactions, lastHash)
   ...}

Finally, let's implement the  send method:

func (cli *CLI) send(from, to string, amount int) {
   bc := NewBlockchain(from)
   defer bc.db.Close()

   tx := NewUTXOTransaction(from, to, amount, bc)
   bc.MineBlock([]*Transaction{tx})
   fmt.Println("Success!")}

Sending coins means creating new transactions and packing them into the blockchain by mining new blocks. However, Bitcoin is not a chain of things that does these things at once (although our current implementation does). Instead, it puts all new transactions into a mempool, and when the miner is ready to mine a new block, it takes all the transactions from the mempool and creates a candidate block. Only after the block containing these transactions has been mined and added to the blockchain do the transactions in it begin to be confirmed.

Let's check if sending coins works:

$ blockchain_go send -from Ivan -to Pedro -amount 6
00000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37

Success!$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 4

$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 6

very good! Now, let's create more transactions, making sure that sending coins from multiple outputs also works:

$ blockchain_go send -from Pedro -to Helen -amount 2
00000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdf

Success!$ blockchain_go send -from Ivan -to Helen -amount 2
000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aa

Success!

Now, Helen's coins are locked in two outputs: one from Pedro and one from Ivan. Let's send them to others:

$ blockchain_go send -from Helen -to Rachel -amount 3
000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0

Success!$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 2

$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 4

$ blockchain_go getbalance -address Helen
Balance of 'Helen': 1

$ blockchain_go getbalance -address Rachel
Balance of 'Rachel': 3

It looks ok! Now, to test some failure cases:

$ blockchain_go send -from Pedro -to Ivan -amount 5
panic: ERROR: Not enough funds

$ blockchain_go getbalance -address Pedro
Balance of 'Pedro': 4

$ blockchain_go getbalance -address Ivan
Balance of 'Ivan': 2


Summarize

It wasn't easy, but now it's finally a deal! However, we are still missing some key features like Bitcoin:

  1. address. We don't yet have a real address based on a private key.

  2. reward. Now mining is definitely not profitable!

  3. UTXO set. Getting the balance requires scanning the entire blockchain, which can take a long time when there are a lot of blocks. And, if we want to validate subsequent transactions, it also takes a long time. The UTXO set is to solve these problems and speed up transaction-related operations.

  4. memory pool (mempool). Transactions are stored in the mempool before they are packaged into blocks. In our current implementation, a block contains only one transaction, which is rather inefficient.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324517882&siteId=291194637