背景知识
1.UTXO账户模型
产生背景:
为了解决第一类双花问题(一笔钱花两次)
原理介绍:
我们先来介绍传统的金融模式,你有10元存款,想转给我3元,银行会怎么操作?
很显然,他会将你的账户减3元,将我的账户加3元。
这种交易模式记录的是 交易结果
而UTXO账户模型记录的是 交易过程 下面是简单的例子:
还拿上述例子,你给我转账10元,那么这个机制会做出如下记录:
- 初始状态:你的账户有10元,由一个未花费交易输出(UTXO)组成,价值为10元。
- 转账3元给我:你想要向我转账3元。这个过程会创建一笔新的交易,包含一个输入和一个输出。
- 输入:指向之前未花费的10元交易输出,消费掉这个UTXO。
- 输出:一个新的UTXO,价值为7元,指向我的地址。
- 交易记录:区块链账本会记录这笔交易,包含以下信息:
- 交易ID:由交易内容计算得出的唯一标识符。
- 输入:指向之前未花费的10元交易输出的引用。
- 输出:新的7元交易输出,指向我的地址。
- 其他信息:例如交易的签名等。
- 更新你的账户余额:在这笔交易被确认并添加到区块链后,你的账户余额将更新为7元,因为之前的10元交易输出已经被消费掉了。
这样做的好处
-
结合共识机制,解决了第一类双花问题
-
使区块链的每一笔交易都可以溯源
如何解决双花问题?
- 交易机制会先检测账户是否存在这一笔资金
- 账户会记录下交易源头
- 通过区块链网络将这一交易广播到每一个账户
在这一机制下,第二笔交易会因为账户资金不足而被判定失败
本文只简单介绍一下,至于后续添加节点以及为什么不用时间戳来判断先后的问题以后再说。
2.补档bytes.Buffer
该类型在 Go 标准库中提供了一系列方法,用于对字节序列进行操作。以下是其中一些常用的方法:
Write(p []byte) (n int, err error): 将字节序列p写入到缓冲区。WriteByte(c byte) error: 将单个字节c写入到缓冲区。WriteRune(r rune) (n int, err error): 将 Unicode 字符r及其 UTF-8 编码写入到缓冲区。WriteString(s string) (n int, err error): 将字符串s的字节写入到缓冲区。Read(p []byte) (n int, err error): 从缓冲区读取字节到p中。ReadByte() (byte, error): 从缓冲区读取单个字节。ReadRune() (r rune, size int, err error): 从缓冲区读取一个 UTF-8 编码的 Unicode 字符。ReadString(delim byte) (string, error): 从缓冲区读取直到遇到分隔符delim的字符串。Bytes() []byte: 返回缓冲区的字节切片。Reset(): 清空缓冲区,使其长度为 0,但不释放底层的缓冲区。Len() int: 返回缓冲区中的字节数。Cap() int: 返回缓冲区的容量。
3.补档——range遍历
在Go语言中,range关键字用于迭代数组、切片、字符串、映射等数据结构。
1. 迭代数组和切片
package main
import "fmt"
func main() {
// 迭代数组
nums := [3]int{
1, 2, 3}
for _, num := range nums {
fmt.Println(num)
}
// 迭代切片
fruits := []string{
"apple", "banana", "orange"}
for index, fruit := range fruits {
fmt.Printf("Index: %d, Fruit: %s\n", index, fruit)
}
}
//1
//2
//3
//Index: 0, Fruit: apple
//Index: 1, Fruit: banana
//Index: 2, Fruit: orange
在这个例子中,range被用于迭代数组和切片。对于数组,range会返回索引和值,而对于切片,则返回索引和对应元素的值。
2. 迭代字符串
package main
import "fmt"
func main() {
// 迭代字符串
str := "hello"
for index, char := range str {
fmt.Printf("Index: %d, Character: %c\n", index, char)
}
}
//Index: 0, Character: h
//Index: 1, Character: e
//Index: 2, Character: l
//Index: 3, Character: l
//Index: 4, Character: o
在这个例子中,range被用于迭代字符串。在Go中,字符串被视为UTF-8编码的字节数组,因此range会返回每个字符的索引和对应的Unicode码点。
3. 迭代映射(map)
package main
import "fmt"
func main() {
// 迭代映射
ages := map[string]int{
"Alice": 30,
"Bob": 25,
"Carol": 35,
}
for name, age := range ages {
fmt.Printf("%s is %d years old\n", name, age)
}
}
//Alice is 30 years old
//Bob is 25 years old
//Carol is 35 years old
在这个例子中,range被用于迭代映射。range会返回映射中的键值对,其中键用于标识映射中的条目,而值则是相应键的值。
源代码
package main
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/gob"
"encoding/hex"
"fmt"
"log"
"math"
"math/big"
"time"
)
// 常量
const (
Difficulty = 12
InitCoin = 1000
)
// 输出值 货币价值和接收者
type TxOutput struct {
Value int
ToAddress []byte
}
// 输入值 输入所引用的前一笔交易的唯一标识符(哈希) 位置索引 货币来源
type TxInput struct {
TxID []byte
OutIdx int
FromAddress []byte
}
// 交易 交易本身的唯一标识符(哈希) 输入值 输出值
type Transaction struct {
ID []byte
Inputs []TxInput
Outputs []TxOutput
}
type Block struct {
Timestamp int64
Hash []byte
PrevHash []byte
Target []byte
Nonce int64
Transactions []*Transaction
}
type BlockChain struct {
Blocks []*Block
}
// 下面两个函数实现 将整数转化为16进制 并且最后返回字节切片
func Handle(err error) {
if err != nil {
log.Panic(err)
}
}
func ToHexInt(num int64) []byte {
buff := new(bytes.Buffer)
err := binary.Write(buff, binary.BigEndian, num)
Handle(err)
return buff.Bytes()
}
// 下面两个方法 实现了交易ID的创建
func (tx *Transaction) TxHash() []byte {
var encoded bytes.Buffer
var hash [32]byte
encoder := gob.NewEncoder(&encoded)
err := encoder.Encode(tx)
Handle(err)
hash = sha256.Sum256(encoded.Bytes())
return hash[:]
}
func (tx *Transaction) SetID() {
tx.ID = tx.TxHash()
}
// 创建每个区块链的第一个交易——基本交易
func BaseTx(toaddress []byte) *Transaction {
txIn := TxInput{
[]byte{
}, -1, []byte{
}}
txOut := TxOutput{
InitCoin, toaddress}
tx := Transaction{
[]byte("This is the Base Transaction!"), []TxInput{
txIn}, []TxOutput{
txOut}}
return &tx
}
// 检查输入的地址是否与 TxInput 结构体中的 FromAddress 字段相匹配
func (in *TxInput) FromAddressRight(address []byte) bool {
return bytes.Equal(in.FromAddress, address)
}
//检查输出的地址是否与 TxOutput 结构体中的 ToAddress 字段相匹配
func (out *TxOutput) ToAddressRight(address []byte) bool {
return bytes.Equal(out.ToAddress, address)
}
func CreateBlock(prevhash []byte, txs []*Transaction) *Block {
block := Block{
time.Now().Unix(), []byte{
}, prevhash, []byte{
}, 0, txs}
block.Target = block.GetTarget()
block.Nonce = block.FindNonce()
block.SetHash()
return &block
}
func GenesisBlock() *Block {
tx := BaseTx([]byte("Leo Cao"))
return CreateBlock([]byte{
}, []*Transaction{
tx})
}
// 生成一个包含区块中所有交易的唯一标识符 ID 的摘要
func (b *Block) BackTrasactionSummary() []byte {
txIDs := make([][]byte, 0)
for _, tx := range b.Transactions {
txIDs = append(txIDs, tx.ID)
}
summary := bytes.Join(txIDs, []byte{
})
return summary
}
func (b *Block) SetHash() {
information := bytes.Join([][]byte{
ToHexInt(b.Timestamp), b.PrevHash, b.Target, ToHexInt(b.Nonce), b.BackTrasactionSummary()}, []byte{
})
hash := sha256.Sum256(information)
b.Hash = hash[:]
}
func (b *Block) GetTarget() []byte {
target := big.NewInt(1)
target.Lsh(target, uint(256-Difficulty))
return target.Bytes()
}
func (b *Block) GetBase4Nonce(nonce int64) []byte {
data := bytes.Join([][]byte{
ToHexInt(b.Timestamp),
b.PrevHash,
ToHexInt(int64(nonce)),
b.Target,
b.BackTrasactionSummary(),
}, []byte{
})
return data

本文介绍了区块链UTXO账户模型,其产生背景是解决双花问题,记录交易过程,结合共识机制可使交易溯源。还补充了Go标准库对字节序列操作方法及range遍历知识。同时给出区块链代码,按功能分成多个包,介绍了各包作用及关键概念如位置索引、TxID等。
——交易&spm=1001.2101.3001.5002&articleId=137382553&d=1&t=3&u=bfe4c5be52254587826cf8f47b563bab)
1812

被折叠的 条评论
为什么被折叠?



