and
. The account stored information about the user, including his personal information and balance, and the transaction stores information about the transfer of money from one account to another. In Bitcoin, payments are made in a completely different way: type Transaction struct { ID []byte Vin []TXInput Vout []TXOutput }
type TXOutput struct { Value int ScriptPubKey string }
Value
field above). Means are blocked by a special puzzle that is stored in the ScriptPubKey
. Inside, Bitcoin uses a scripting language, Script
, which is used to determine the logic for blocking and unlocking outputs. The language is quite primitive (this is done intentionally to avoid possible hacking), but we will not discuss it in detail. You can read more about him here .In Bitcoin, the value field holds the amount of satoshi, not the amount of BTC. 1 satoshi = 0.00000001 BTC. Thus, it is the smallest unit of currency in Bitcoin (as, for example, a cent).As we have no addresses, for the time being we will avoid all the logic associated with scripts. To begin, the
ScriptPubKey
will store an arbitrary string (user wallet address).By the way, the presence of such a scripting language means that Bitcoin can be used as a platform for smart contracts.One important thing you need to know about exits is that they are indivisible , which means that you cannot refer to a part of your value. When the output refers to a new transaction, it is consumed completely. And if its value is greater than required, a difference is generated and a new value is sent back to the sender. It’s like a real world situation when you pay, say, $ 5 dollars for what costs $ 1, and you get a change of $ 4.
type TXInput struct { Txid []byte Vout int ScriptSig string }
Txid
stores the identifier of such a transaction, and Vout
stores the exit index of the transaction. ScriptSig
is a script that provides data that will be further used in the ScriptPubKey
script. If the data is correct, the output can be unblocked, and its value can be used to generate new outputs; if not, the input cannot refer to the output. This mechanism ensures that users cannot spend coins belonging to other people.ScriptSig
save only an arbitrary user wallet address. We will create a public key and signature verification in the next article.ScriptSig
field) that is used in the exit unlock script to unlock it and use its value to create new exits. 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 }
Txid
empty, and Vout
is -1. In addition, the coinbase transaction does not store the script in ScriptSig
. Instead, arbitrary data is stored there.In Bitcoin, the very first coinbase transaction contains the following message: “The Times 03 / Jan / 2009 Chancellor for brink of second bailout for banks”. You yourself can look at it .
subsidy
is the amount of the reward. In Bitcoin, this number is not stored anywhere and is calculated only on the basis of the total number of blocks: the number of blocks is divided by 210,000 . Mining a block of genesis brings 50 BTC, and every 210,000 blocks the reward is halved. In our implementation, we will store the reward as a constant (at least for the time being). type Block struct { Timestamp int64 Transactions []*Transaction PrevBlockHash []byte Hash []byte Nonce int }
NewBlock
and NewGenesisBlock
should also be changed accordingly. func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} ... } func NewGenesisBlock(coinbase *Transaction) *Block { return NewBlock([]*Transaction{coinbase}, []byte{}) }
CreateBlockchain
func CreateBlockchain(address string) *Blockchain { ... err = db.Update(func(tx *bolt.Tx) error { cbtx := NewCoinbaseTX(address, genesisCoinbaseData) genesis := NewGenesisBlock(cbtx) b, err := tx.CreateBucket([]byte(blocksBucket)) err = b.Put(genesis.Hash, genesis.Serialize()) ... }) ... }
ProofOfWork.prepareData
method: func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, pow.block.HashTransactions(), // This line was changed IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), }, []byte{}, ) return data }
pow.block.Data
we now add pow.block.HashTransactions ()
: func (b *Block) HashTransactions() []byte { var txHashes [][]byte var txHash [32]byte for _, tx := range b.Transactions { txHashes = append(txHashes, tx.ID) } txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) return txHash[:] }
Bitcoin uses a more complex technique: it presents all transactions contained in a block as a hash tree , and uses the root hash of the tree in the Proof-of-Work system. This approach allows you to quickly check if a block contains a specific transaction that has only a root hash and does not load all transactions.Check the correctness of the work:
$ blockchain_go createblockchain -address Ivan 00000093450837f8b52b78c25f8163bb6137caf43ff4d9a01d1b731fa8ddcc8a Done!
func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { return in.ScriptSig == unlockingData } func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { return out.ScriptPubKey == unlockingData }
ScriptSig
to unlockingData
. In the next article we will improve them after we implement addresses based on private keys. func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { var unspentTXs []Transaction spentTXOs := make(map[string][]int) bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { txID := hex.EncodeToString(tx.ID) Outputs: for outIdx, out := range tx.Vout { // Was the output spent? if spentTXOs[txID] != nil { for _, spentOut := range spentTXOs[txID] { if spentOut == outIdx { continue Outputs } } } if out.CanBeUnlockedWith(address) { unspentTXs = append(unspentTXs, *tx) } } if tx.IsCoinbase() == false { for _, in := range tx.Vin { if in.CanUnlockOutputWith(address) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } } } } if len(block.PrevBlockHash) == 0 { break } } return unspentTXs }
if out.CanBeUnlockedWith(address) { unspentTXs = append(unspentTXs, tx) }
if spentTXOs[txID] != nil { for _, spentOut := range spentTXOs[txID] { if spentOut == outIdx { continue Outputs } } }
if tx.IsCoinbase() == false { for _, in := range tx.Vin { if in.CanUnlockOutputWith(address) { inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) } } }
func (bc *Blockchain) FindUTXO(address string) []TXOutput { var UTXOs []TXOutput unspentTransactions := bc.FindUnspentTransactions(address) for _, tx := range unspentTransactions { for _, out := range tx.Vout { if out.CanBeUnlockedWith(address) { UTXOs = append(UTXOs, out) } } } return UTXOs }
getbalance
command getbalance
func (cli *CLI) getBalance(address string) { bc := NewBlockchain(address) defer bc.db.Close() balance := 0 UTXOs := bc.FindUTXO(address) for _, out := range UTXOs { balance += out.Value } fmt.Printf("Balance of '%s': %d\n", address, balance) }
$ blockchain_go getbalance -address Ivan Balance of 'Ivan': 10
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 }
FindSpendableOutputs
method. After that, for each output found, an input is created that references it. Then we create two outputs:FindSpendableOutputs
method FindSpendableOutputs
based on the FindUnspentTransactions
method, which we defined earlier: func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) unspentTXs := bc.FindUnspentTransactions(address) accumulated := 0 Work: 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 }
Blockchain.MineBlock
method: func (bc *Blockchain) MineBlock(transactions []*Transaction) { ... newBlock := NewBlock(transactions, lastHash) ... }
send
command: 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!") }
$ 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
$ blockchain_go send -from Pedro -to Helen -amount 2 00000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdf Success! $ blockchain_go send -from Ivan -to Helen -amount 2 000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aa Success!
$ 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
$ 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
Source: https://habr.com/ru/post/351752/
All Articles