
Blockchain Notes
- ruiyu
- Golang , Blockchain
- 2025年5月21日
Table of Contents
Blockchain Notes
1. Create block struct
block struct
type Block struct { // 1. block height Height int64 // 2. Previous block hash PrevBlockHash []byte // 3. Data,In the future, it will be used to store data such as transactions Data []byte // 4. Timestamp Timestamp int64 // 5. Block Hash Hash []byte }
Set Block Hash Function
// 2. Set Block hash func (block *Block) SetHash() { // 1. Convert Height to a byte array // use IntToHex() Function heightBytes := IntToHex(block.Height) // 2.Converts Timestamp to a byte array // 2.1 strconv.FormatInt() // The first parameter converts int64 to a string // The second parameter ranges from 2 to 36, representing the base system timeString := strconv.FormatInt(block.Timestamp, 2) timebytes := []byte(timeString) fmt.Println("SetHash timeString", timeString, "\n timebytes := ", timebytes, "\n heightBytes:=", heightBytes) // 3.Concatenate all properties blockbytes := bytes.Join([][]byte{heightBytes, block.PrevBlockHash, block.Data, timebytes, block.Hash}, []byte{}) // 4.Generating hash HashValue := sha256.Sum256(blockbytes) // Processing HashValue is 32 bytes block.Hash = HashValue[:] } // utils.go // Convert int64 to byte Array func IntToHex(num int64) []byte { buff := new(bytes.Buffer) err := binary.Write(buff, binary.BigEndian, num) if err != nil { log.Panic("IntToHex error", err) } return buff.Bytes() }
Create block function
func NewBlock(data string, height int64, prevBlockHash []byte) *Block { // CreateBlock block := &Block{ Height: height, PrevBlockHash: prevBlockHash, Data: []byte(data), Timestamp: time.Now().Unix(), Hash: nil, } fmt.Println("old block = ", block) // Sethash block.SetHash() return block }
2. Create Genesis Block
The Genesis block is the first block of the blockchain.
It usually has a height of 1 and a block Hash of 0 as a 32-bit array
// Create Genesis Block func CreateGenesisBlock(data string) *Block { // create [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] // 32-bit byte array var ZeroBlockHash [32]byte return NewBlock(data, 1, ZeroBlockHash[:]) } // main.go block := block.CreateGenesisBlock("Genesis Block") fmt.Println("Genesis block = ", block)
3. Add Genesis Block to Blockchain
A blockchain is composed of many blocks, and the genesis block is the first block of the blockchain.
Blockchain.go
Blockchain struct
type Blockchain struct { Blocks []*Block // Stores ordered blocks }
Create blockchain with Genesis block
func CreateBlockchainWithGenesisBlock() *Blockchain { GenesisBlock := CreateGenesisBlock("Genesis block Data...") return &Blockchain{Blocks: []*Block{GenesisBlock}} }
Add block to blckchain
func (blockchain *Blockchain) AddBlockToBlockchain(data string, height int64, prevHash []byte) { newBlock := NewBlock(data, height, prevHash) // Adds blocks to the chain array blockchain.Blocks = append(blockchain.Blocks, newBlock) }
4. Proof Of Work(POW)
Proof of Work (PoW) is the earliest consensus algorithm adopted in blockchain technology.
How It Works
Puzzle Solving
难题计算Simple Verification
验证简单Reward Mechanism
奖励机制
Create Proof Of Work Struct
type ProofOfWork struct { Block *Block // The block to validate Target *big.Int // Big Data Storage // Represents the difficulty of our data // int64 may overflow its range larger }
Difficulty
After running, 0000 0000 0000 0000 1001 0001 0000 …. 0001 is shifted 256-targetBit to the left
// Difficulty // 0000 0000 0000 0000 1001 0001 0000 .... 0001 // A 256-bits Hash must have at least 16/targetBit zeroes in front of it const targetBit = 16
Run()
- Concatenate the properties of the Block into a byte array
- Generate hash
- If the hash is valid, the result is returned
func (proofOfWork *ProofOfWork) Run() ([]byte, int64) { var hashInt big.Int // Store our newly generated hash value var hash [32]byte for nonce := 0; ; nonce++ { // 1. prepare Data dataBytes := proofOfWork.prepareData(nonce) // 2. Generate hash hash = sha256.Sum256(dataBytes) fmt.Printf("\r%x", hash) // 2.2. Store in HashInt hashInt.SetBytes(hash[:]) // 3. Checking the Validity of generate hash /* func (x *big.Int) Cmp(y *big.Int) (r int) Cmp compares x and y and returns: -1 if x < y 0 if x == y +1 if x > y */ if proofOfWork.Target.Cmp(&hashInt) == 1 { fmt.Println() return hash[:], int64(nonce) } } } // Concatenate the properties of the Block into a byte array. func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.Block.PrevBlockHash, pow.Block.Data, IntToHex(pow.Block.Timestamp), IntToHex(int64(pow.Block.Height)), IntToHex(int64(targetBit)), IntToHex(int64(nonce)), }, []byte{}, ) return data }
Create a new proof of work object
func NewProofOfWork(Block *Block) *ProofOfWork { /* target two 0 0000 0001 Shift left(8-2 =6) bit 0100 0000 =64 0010 0000 =32 < Just move it two places to the left ,32 */ // 1.Create a taget with an initial value of 1 target := big.NewInt(1) // 2.Shift 256-target Bit to the left target = target.Lsh(target, 256-targetBit) return &ProofOfWork{Block: Block, Target: target} }
Verify that the hash is valid
// Determine whether the generated hash is preceded by Target zeros func (proofOfWork *ProofOfWork) IsVaild() bool { /* proofOfWork.Block.Hash proofOfWork.Target */ var hashInt big.Int hashInt.SetBytes(proofOfWork.Block.Hash) /* func (x *big.Int) Cmp(y *big.Int) (r int) mp compares x and y and returns: -1 if x < y 0 if x == y +1 if x > y */ return proofOfWork.Target.Cmp(&hashInt) == 1 } func main() { block1 := block.NewBlock("text", 1, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) fmt.Println("block.nonce=", block1.Nonce) fmt.Println("block.hash =", block1.Hash) // The upper block has been verified, quick verification pow := block.NewProofOfWork(block1) fmt.Println("is vaied", pow.IsVaild()) //true }
5. Serialize
To store blocks on disk, serialization is needed
block.go
// use "encoding/gob" // Serialize the block into a byte array func (block *Block) Serialize() []byte { var result bytes.Buffer encoder := gob.NewEncoder(&result) err := encoder.Encode(block) if err != nil { log.Panic(err) } return result.Bytes() } // Deserializing the byte array returns the block structure func DeSerializeBlock(blockBytes []byte) *Block { var block Block decoder := gob.NewDecoder(bytes.NewReader(blockBytes)) err := decoder.Decode(&block) if err != nil { log.Panic(err) } return &block }
6. DataBase
“github.com/boltdb/bolt”
Introduction
CreateTable
package main import ( "github.com/boltdb/bolt") func main() { // Open the my.db data file in your current directory. // It will be created if it doesn't exist. db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } err = db.Update(func(tx *bolt.Tx) error { // 1. create BlockBucket Table b, err := tx.CreateBucket([]byte("BlockBucket")) if err != nil {return fmt.Errorf("create bucket error,%s", err)} // 2. Putting data into a table, in key-value pairs if b != nil { err := b.Put([]byte("1"), []byte("Send 100 to 2")) if err != nil {log.Panic("Save data to bucket error", err)} } return nil }) defer db.Close() }
GetTable
func main() { // Open the my.db data file in your current directory. // It will be created if it doesn't exist. db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } err = db.Update(func(tx *bolt.Tx) error { // 1. Get BlockBucket Table b := tx.Bucket([]byte("BlockBucket")) // 2. Putting data into a table, in key-value pairs if b != nil { err := b.Put([]byte("1"), []byte("Send 100 to 2")) if err != nil {log.Panic("Save data to bucket error", err)} } return nil }) defer db.Close() }
ViewTable
// ViewTable err = db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("BlockBucket")) if b != nil { key1 := b.Get([]byte("1")) fmt.Println(key1) } return nil })
7. Save Genesis Block To BoltDB
Persistence store
Blockchain uses db object, just need to add db object to blockchain property.
Modify Blockchain Struct
const dbName = "blockchain.db" const blockTableName = "block" type Blockchain struct { Tip []byte // The hash of the latest block DB *bolt.DB // Database }
Modify CreateBlockchainWithGenesisBlock() Function
func CreateBlockchainWithGenesisBlock() *Blockchain { var blockHash []byte // Create or open the database db, err := bolt.Open(dbName, 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() err = db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte(blockTableName)) if err != nil { log.Panic(err) } //The table exists if b != nil { // Create GenisisBlock GenesisBlock := CreateGenesisBlock("Genesis block Data...") // Store the Genesis block in a table err := b.Put(GenesisBlock.Hash, GenesisBlock.Serialize()) if err != nil { log.Panic(err) } // Store the hash of the latest block err = b.Put([]byte("TipBlockHash"), GenesisBlock.Hash) if err != nil { log.Panic(err) } blockHash = GenesisBlock.Hash } return nil }) return &Blockchain{blockHash, db} }
8. Add New Block To BoltDb
func (blockchain *Blockchain) AddBlockToBlockchain(data string) { blockchain.DB.Update(func(tx *bolt.Tx) error { // 1. Get Bucket b := tx.Bucket([]byte(blockTableName)) // 2. Create New Block if b != nil { // 1. **Get The Latest Block** TipBlockBytes := b.Get(blockchain.Tip) // 2. DeSerialize TipBlock := DeSerializeBlock(TipBlockBytes) // 3. Create New Block newBlock := NewBlock(data, TipBlock.Height+1, TipBlock.Hash) // 4. Serialize New Block ,place it in the obtained table err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { log.Panic(err) } // Store the hash of the latest block err = b.Put([]byte("TipBlockHash"), newBlock.Hash) if err != nil { log.Panic(err) } } return nil }) }
9. Print Blockchain Info
- Iterate over all blocks to output information
func (blockchain *Blockchain) PrintBlockchain() { var block *Block var currentHash []byte = blockchain.Tip for { err := blockchain.DB.Update(func(tx *bolt.Tx) error { // 1.Get Bucket b := tx.Bucket([]byte(blockTableName)) if b != nil { // Gets the byte array of the current block blockBytes := b.Get(currentHash) // DeSerializeBlock block = DeSerializeBlock(blockBytes) fmt.Printf("Height:%d\n", block.Height) fmt.Printf("PrevBlockHash:%x\n", block.PrevBlockHash) fmt.Printf("Data:%s\n", block.Data) //fmt.Printf("Timestamp:%s\n", time.Unix(block.Timestamp, 0).Format("2006-01-02 03:04:05 PM")) fmt.Printf("Timestamp:%s\n", time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05 PM")) fmt.Printf("Hash:%x\n", block.Hash) fmt.Printf("Nonce:%d\n\n", block.Nonce) } return nil }) if err != nil { log.Panic(err) } var hashInt big.Int hashInt.SetBytes(block.PrevBlockHash) if big.NewInt(0).Cmp(&hashInt) == 0 { break } // Iterate currentHash = block.PrevBlockHash } }
10. Iterator
Write an iterator that optimizes the above code to reduce repetition
Iterator Struct
type BlockchainIterator struct { CurrentHash []byte // The latest Block Hash DB *bolt.DB // DB } // Get the next block func (blockchainIterator *BlockchainIterator) NextPrevBlock() *Block { var currentBlock *Block err := blockchainIterator.DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockTableName)) if b != nil { currentBlockBytes := b.Get(blockchainIterator.CurrentHash) // The block corresponding to the current iterator Hash is retrieved currentBlock = DeSerializeBlock(currentBlockBytes) blockchainIterator.CurrentHash = currentBlock.PrevBlockHash } return nil }) if err != nil { log.Panic(err) } return currentBlock } // Get the blockchain iterator object func (blockchain *Blockchain) Iterator() *BlockchainIterator { return &BlockchainIterator{blockchain.Tip, blockchain.DB} }
Modify PrintBlockchain()
func (blockchain *Blockchain) PrintBlockchain() { blockchainIterator := blockchain.Iterator() for { block := blockchainIterator.NextPrevBlock() fmt.Printf("Height:%d\n", block.Height) fmt.Printf("PrevBlockHash:%x\n", block.PrevBlockHash) fmt.Printf("Data:%s\n", block.Data) fmt.Printf("Timestamp:%s\n", time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05 PM")) fmt.Printf("Hash:%x\n", block.Hash) fmt.Printf("Nonce:%d\n\n", block.Nonce) var hashInt big.Int hashInt.SetBytes(block.PrevBlockHash) if big.NewInt(0).Cmp(&hashInt) == 0 { break } } }
11. Cli
11.1 os.Args
Input
- main.exe addBlock -data “block1”
// main.exe addBlock -data "block1" func main() { args := os.Args fmt.Printf("%v\n", args) // [D:\dev\main.exe addblock -data block1] fmt.Printf("%v\n", args[1]) // addblock fmt.Printf("%v\n", args[2]) // -data fmt.Printf("%v\n", args[2:]) // [-data block1] }
11.2 flag
input
flagString := flag.String("printchain", "string", "Print out all the block information") flagInt := flag.Int("number", 5, "Printing an integer") flagBool := flag.Bool("open", false, "Judging true and false") flag.Parse() fmt.Printf("%s\n", *flagString) // string fmt.Printf("%d\n", *flagInt) // 5 fmt.Printf("%v\n", *flagBool) // false
11.3 Cli Struct
Cli Struct
type Cli struct{}
Cli function
Check OsArgs Valid
func isVaildArgs() { if len(os.Args) < 2 { printUsage() os.Exit(1) } }
Print Cli Cmd
func printUsage() { fmt.Println("Usage:") fmt.Println("\t createBlockchain -address address") fmt.Println("\t send -from FROM -to TO -amount AMOUNT -----transaction") fmt.Println("\t printchain -----print block chain info") fmt.Println("\t getbalance -address ----get address balance") }
func (cli *Cli) Run() { isVaildArgs() // 1.printchain printchainCMD := flag.NewFlagSet("printchain", flag.ExitOnError) // 2.send //.\main.exe send -from '[\"address1","address2\"]' -to '[\"address3\",\"address4\"]' -amount '[\"2\",\"3\"]' sendBlockCMD := flag.NewFlagSet("send", flag.ExitOnError) flagFrom := sendBlockCMD.String("from", "", "from address....") flagTo := sendBlockCMD.String("to", "", "to address....") flagAmount := sendBlockCMD.String("amount", "", "amount....") //3.createblockchain createBlockChainCMD := flag.NewFlagSet("createBlockchain", flag.ExitOnError) createBlockChainWithAddress := createBlockChainCMD.String("address", "", "Genesis Block Address....") //4.getbalance getbalanceCMD := flag.NewFlagSet("getbalance", flag.ExitOnError) getbalanceCMDWithAddress := getbalanceCMD.String("address", "", "Get Address Amount") switch os.Args[1] { case "send": err := sendBlockCMD.Parse(os.Args[2:]) if err != nil { log.Panic(err) } case "printchain": err := printchainCMD.Parse(os.Args[2:]) if err != nil { log.Panic(err) } case "createBlockchain": err := createBlockChainCMD.Parse(os.Args[2:]) if err != nil { log.Panic(err) } case "getbalance": err := getbalanceCMD.Parse(os.Args[2:]) if err != nil { log.Panic(err) } default: printUsage() os.Exit(1) } if createBlockChainCMD.Parsed() { if *createBlockChainWithAddress == "" { fmt.Println("Address == "") printUsage() os.Exit(1) } cli.CreateGenesisBlockchain(*createBlockChainWithAddress) } if sendBlockCMD.Parsed() { if *flagFrom == "" || *flagTo == "" || *flagAmount == "" { printUsage() os.Exit(1) } from := block.JSONToArray(*flagFrom) to := block.JSONToArray(*flagTo) amount := block.JSONToArray(*flagAmount) cli.send(from, to, amount) } if printchainCMD.Parsed() { cli.Printchain() } if getbalanceCMD.Parsed() { if *getbalanceCMDWithAddress == "" { fmt.Println("Address == "") printUsage() os.Exit(1) } cli.getBalance(*getbalanceCMDWithAddress) } }
11.4 Cli Function
Create Genesis Blockchain
func (cli *Cli) CreateGenesisBlockchain(address string) { blockchain := block.CreateBlockchainWithGenesisBlock(address) defer blockchain.DB.Close() }
// creates a blockchain with a genesis block func CreateBlockchainWithGenesisBlock(genesisBlockAddress string) *Blockchain { // Exit if the database exists if DbExists() { fmt.Println("Genesis block already exists") os.Exit(1) return nil } // Create or open the database db, err := bolt.Open(dbName, 0600, nil) if err != nil { log.Fatal(err) } // Close the database when the function exits defer db.Close() var GenesisBlockHash []byte err = db.Update(func(tx *bolt.Tx) error { // Create a bucket b, err := tx.CreateBucket([]byte(blockTableName)) if err != nil { log.Panic(err) } // Bucket already exists if b != nil { // Create the GenesisBlock // Create a Coinbase Transaction txCoinbase := NewCoinbaseTransaction(genesisBlockAddress) GenesisBlock := CreateGenesisBlock([]*Transaction{txCoinbase}) // Store the genesis block in the bucket err := b.Put(GenesisBlock.Hash, GenesisBlock.Serialize()) if err != nil { log.Panic(err) } // Store the hash of the latest block err = b.Put([]byte("TipBlockHash"), GenesisBlock.Hash) if err != nil { log.Panic(err) } // Use to Return GenesisBlockHash = GenesisBlock.Hash } return nil }) if err != nil { log.Panic(err) } return &Blockchain{GenesisBlockHash, db} }
Print Block Info
func (cli *Cli) Printchain() { if !block.DbExists() { fmt.Println("Database does not exist") os.Exit(1) return } blockchain := block.GetBlockObject() defer blockchain.DB.Close() blockchain.PrintBlockchain() }
// PrintBlockchain prints information for all blocks in the blockchain func (blockchain *Blockchain) PrintBlockchain() { // Initialize the iterator to start from the latest block blockchainIterator := blockchain.Iterator() // Iterate over all blocks in the blockchain for { // Get the next previous block in the chain block := blockchainIterator.NextPrevBlock() // Print the block header information fmt.Printf("---------------Block Height %d------------------\n", block.Height) fmt.Printf("PrevBlockHash: %x\n", block.PrevBlockHash) fmt.Printf("Timestamp: %s\n", time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05 PM")) fmt.Printf("Hash: %x\n", block.Hash) fmt.Printf("Nonce: %d\n", block.Nonce) // Print the transactions in the block fmt.Printf("---------------Block %d - Txs: %v---------------\n", block.Height, len(block.Txs)) for _, tx := range block.Txs { fmt.Printf("Txs: tx.hash=%x\n", tx.TxHash) fmt.Printf("Vins → ") for _, in := range tx.Vins { fmt.Printf("in.TxHash: %x, Signature: %x, (in.Vout Index): %d\n", in.TxHash, in.ScriptSig, in.Vout) } fmt.Printf("Vouts→ \n") for i, out := range tx.Vouts { fmt.Printf("tx.%d Value: %d, ScriptPublicKey: %s\n", i, out.Value, out.ScriptPublicKey) } fmt.Printf("--------------End of Block %d - Tx Ended--------------\n", block.Height) } // If the current block's previous hash is zero, it means we've reached the genesis block if bytes.Equal(block.PrevBlockHash, []byte{}) { break } } }
That’s pretty easy, now we’re going to write some trading code.
12. Transaction
What is a Blockchain Transaction
- A blockchain transaction refers to a data operation that occurs within a blockchain network, typically representing the transfer of assets or information from one address to another.
- 区块链交易是指在区块链网络中发生的一次数据操作,通常用于表示资产或信息从一个地址转移到另一个地址的过程。
- Each transaction is recorded in a block and permanently stored on the blockchain after being validated through a consensus mechanism (such as Proof of Work or Proof of Stake).
- 每个交易都会被记录在一个区块中,并通过共识机制验证后永久存储在区块链上。
- A blockchain transaction refers to a data operation that occurs within a blockchain network, typically representing the transfer of assets or information from one address to another.
Basic Structure of a Transaction
- A typical blockchain transaction usually contains the following key components:
- Inputs(输入)
- Outputs(输出)
- Signatures(签名)
- Transaction Fee(交易费用)
- Timestamp(时间戳)
- Other Fields
- Data field (for storing smart contract code or additional information)
- A typical blockchain transaction usually contains the following key components:
Transaction Workflow
- Create Transaction
- Broadcast Transaction(广播交易)
- Validate Transaction(验证交易)
- Package Transaction(打包交易)
- Confirm Transaction(确认交易)
12.1 UTXO Model vs. Account Model
UTXO Model (used by Bitcoin)
Each transaction consumes previous unspent outputs (UTXOs) and creates new UTXOs.
- 每笔交易会消耗之前的未花费输出(UTXO),并创建新的UTXO。
Advantages: Strong privacy, independent transactions.
- 优点:隐私性强,交易独立。
Disadvantages: Managing multiple UTXOs is complex.
- 缺点:管理多个UTXO较为复杂。
Account Model (used by Ethereum)
- Each address has a balance, and transactions directly modify the balance of the address.
- 每个地址都有一个余额,交易直接修改地址的余额。
- Advantages: Simple and easy to understand, suitable for smart contracts.
- 优点:简单易懂,适合智能合约。
- Disadvantages: Lower privacy.
- 缺点:隐私性较低。
- Each address has a balance, and transactions directly modify the balance of the address.
Smart Contract Transactions
In blockchains that support smart contracts (such as Ethereum), transactions can also trigger the execution of smart contracts. These transactions typically include the following:
Contract Address: Specifies the address of the smart contract to be called.
Function Call: Specifies the contract function to be executed and its parameters.
Gas Fee: Payment for computational resources required to execute the contract code.
在支持智能合约的区块链(如以太坊)中,交易还可以触发智能合约的执行。这类交易通常包含以下内容:
调用地址:指定要调用的智能合约地址。
函数调用:指定要执行的合约函数及其参数。
Gas费用:为执行合约代码支付的计算资源费用。
Characteristics of Transactions
- Immutability: Once a transaction is written to the blockchain, it cannot be altered.
- 不可篡改性:一旦交易被写入区块链,就无法更改。
- Transparency: All transactions are publicly visible to everyone on the network.
- 透明性:所有交易对全网公开,任何人都可以查看。
- Decentralization: Transactions are validated and recorded by a distributed network, without the need for a centralized trust institution.
- 去中心化:交易由分布式网络共同验证和记录,无需中心化的信任机构。
- Immutability: Once a transaction is written to the blockchain, it cannot be altered.
12.2 UTXO
UTXO
- unspend transaction output
Transaction Struct
type Transaction struct { //1. transaction hash TxHash []byte //2. inputs Vins []*TxInput //3. outputs Vouts []*TxOutput } type TxOutput struct { Value int64 ScriptPublicKey string }
utxo struct
type UTXO struct { TxHash []byte Index int Output *TxOutput } // The balance must be greater than 0 func (utxo *UTXO) IsValid() bool { return utxo.Output.Value > 0 }
vin
type TxInput struct { // 1.transaction hash TxHash []byte // 2. store TxOutput,in Vout`s index Vout int // 3. address/Signatures ScriptSig string } // Determine which wallet address the current purchase is coming from func (TxInput *TxInput) UnlockWithAddress(address string) bool { return TxInput.ScriptSig == address }
vout
type TxOutput struct { Value int64 ScriptPublicKey string } func (txOutput *TxOutput) UnlockWithAddress(address string) bool { return txOutput.ScriptPublicKey == address }
12.3 Transaction Function
Serialize the block into a byte array
func (tx *Transaction) HashTransactionSerialize() { var result bytes.Buffer encoder := gob.NewEncoder(&result) err := encoder.Encode(tx) if err != nil { log.Panic(err) } hash := sha256.Sum256(result.Bytes()) tx.TxHash = hash[:] }
Determine if this transaction is the transaction in the Genesis block
// Check if Coinbase trades func (tx *Transaction) IsCoinbaseTransaction() bool { return len(tx.Vins[0].TxHash) == 0 && tx.Vins[0].Vout == -1 }
There are two types of Transaction creation
// 1. create Transaction when creating the Genesis block func NewCoinbaseTransaction(address string) *Transaction { // Representative consumption txInput := &TxInput{[]byte{}, -1, "Genesis Data"} // Is for unspent (need to judge, had this amount of money) txOutput := &TxOutput{10, address} txCoinbase := &Transaction{[]byte{}, []*TxInput{txInput}, []*TxOutput{txOutput}} //Set hash txCoinbase.HashTransactionSerialize() return txCoinbase }
// 2. Transactions that occur when a money transfer is made func NewSimpleTransaction(from string, to string, amount int64, blockchain *Blockchain, txs []*Transaction) *Transaction { //1. Create a function that returns the Transaction corresponding to all unspent transaction outputs for the from address //unSpentTransactionOutputUTXO := blockchain.UnSpentTransactionsOutputWithAddress(from) // Both cases, same block // {hash1,[0,1]} // difficult block // {hash1,[0],hash2,[1]} // By a function that returns the amount of money I can spend money, SpendAbleUTXO_Dic := blockchain.FindSpendAbleUTXO(from, amount, txs) //dic {hash,[0:2],hash,[1,4]} var txIntups []*TxInput var txOutputs []*TxOutput //By a function that returns the amount of money I can spend //TxInput := &TxInput{[]byte("c2f026bb3e904c79e3be268e1fe04dcafec29f147f04dd3b896c15060c540140"), 0, from} //That's a mistake for txHash, indexArray := range SpendAbleUTXO_Dic { bytes, _ := hex.DecodeString(txHash) for _, index := range indexArray { //Create Input to consume Output TxInput := &TxInput{bytes, index, from} txIntups = append(txIntups, TxInput) //str := hex.EncodeToString(bytes) //fmt.Println("InputHash===========", str) } } //TxInput := &TxInput{bytes, 0, from} // Transfer txOutput := &TxOutput{amount, to} txOutputs = append(txOutputs, txOutput) // Make change txOutput = &TxOutput{money - amount, from} txOutputs = append(txOutputs, txOutput) txCoinbase := &Transaction{[]byte{}, txIntups, txOutputs} //Set hash txCoinbase.HashTransactionSerialize() return txCoinbase }
blockchain func
// Find available UTXOs when transferring money func (blockchain *Blockchain) FindSpendAbleUTXO(form string, amount int64, txs []*Transaction) (int64, map[string][]int) { // 1.Get all UTXOs UTXOs := blockchain.UnSpentTransactionsOutputWithAddress(form, txs) var SpendAbleUTXO map[string][]int = make(map[string][]int) // 2. Iterate over utxos var value int64 for _, utxo := range UTXOs { value = value + utxo.Output.Value hash := hex.EncodeToString(utxo.TxHash) SpendAbleUTXO[hash] = append(SpendAbleUTXO[hash], utxo.Index) if value >= amount { break } } if value < amount { fmt.Println(form, "'s Fund is not enough") os.Exit(1) } return value, SpendAbleUTXO }
Cli
func (cli *Cli) Run(){ sendBlockCMD := flag.NewFlagSet("send", flag.ExitOnError) flagFrom := sendBlockCMD.String("from", "", "From Address....") flagTo := sendBlockCMD.String("to", "", "to Address....") flagAmount := sendBlockCMD.String("amount", "", "Transfer amount....") switch os.Args[1] { case "send": err := sendBlockCMD.Parse(os.Args[2:]) if err != nil { log.Panic(err) } default: printUsage() os.Exit(1) } if sendBlockCMD.Parsed() { if *flagFrom == "" || *flagTo == "" || *flagAmount == "" { printUsage() os.Exit(1) } from := block.JSONToArray(*flagFrom) to := block.JSONToArray(*flagTo) amount := block.JSONToArray(*flagAmount) cli.send(from, to, amount) } } // SendCli func (cli *Cli) send(from []string, to []string, amount []string) { if !block.DbExists() { fmt.Println("Database does not exist") os.Exit(1) return } blockchain := block.GetBlockObject() defer blockchain.DB.Close() blockchain.MineNewBlock(from, to, amount) }