Previous topic

Rust notes

Next topic

uniswap

This Page

实现区块链

https://jeiwan.net/

原型

简化的区块模型.

  • block

    • prev_hash

    • data

    • hash

      • sha256(prev_hash+data+timestamp)

  • 创世区块

PoW

  • 寻找一个符合条件的hash

    • Hashcash算法

      • 一个hash的前20位要为0.

        • btc中这个要求位数不停变化

  • 达到每10分钟出一个块的目标.

核心代码:

type POW struct {
	b      *Block
	target *big.Int
}

func NewPOW(b *Block) *POW {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-TargetBits))
	return &POW{b, target}
}

func (p *POW) Run() (int, Hash) {
	hash := Hash{}
	var hashInt big.Int
	nonce := 0
	log.Printf("mining: %s", p.b.Data)
	for nonce < MaxNonce {
		data := p.prepare(nonce)
		hash = sha256.Sum256(data)
		hashInt.SetBytes(hash[:])
		if hashInt.Cmp(p.target) == -1 { // -1 if hash < target
			log.Printf("pow: %x", hash)
			break
		} else {
			nonce++
		}
	}
	return nonce, hash
}

func (p *POW) Validate() bool {
	var hashInt big.Int
	data := p.prepare(p.b.Nonce)
	hash := sha256.Sum256(data)
	hashInt.SetBytes(hash[:])
	return hashInt.Cmp(p.target) == -1
}

func (p *POW) prepare(nonce int) []byte {
	return bytes.Join(
		[][]byte{
			p.b.PrevHash[:],
			p.b.Data,
			HexInt(p.b.Timestamp),
			HexInt(int64(TargetBits)),
			HexInt(int64(nonce)),
		},
		[]byte{},
	)
}

持久化

使用bbolt.

go get go.etcd.io/bbolt

简单来说,Bitcoin Core 使用两个 “bucket” 来存储数据:

  1. 其中一个 bucket 是 blocks,它存储了描述一条链中所有块的元数据

  2. 另一个 bucket 是 chainstate,存储了一条链的状态,也就是当前所有的未花费的交易输出,和一些元数据.

只存储块,简单定义以下存储结构:

  • bucket: blocks

    • keys

      • tip: l

        • 当前最新的块hash

      • <hash, json(block)>

        • key为块的hash, 值为块序列化后的字节数组.

定义一个命令行工具. 使用

go get github.com/urfave/cli/v2

实现命令add, 和print.

app := cli.App{
	Commands: []*cli.Command{
		{
			Name:    "add",
			Aliases: []string{"a"},
			Usage:   "add block",
			Action: func(ctx *cli.Context) error {
				c.Add(ctx.Args().First())
				return nil
			},
		},
		{
			Name:    "print",
			Aliases: []string{"p"},
			Usage:   "print all blocks",
			Action: func(ctx *cli.Context) error {
				it := c.Iterator()
				for {
					b := it.Next()
					log.Println(b)

					if b.PrevHash == ZeroHash {
						break
					}
				}
				return nil
			},
		},
	},
}
if err := app.Run(os.Args); err != nil {
	log.Fatal(err)
}

试验:

➜  tmpchain ./tmpchain add "send 1 btc to alice"
2022/08/20 20:23:07 mining: send 1 btc to alice
2022/08/20 20:23:07 pow: 000000693ac0aa277b3b82811e4bca217b029d2aafea153c8dc9d85b7f556c52
➜  tmpchain ./tmpchain add "send 2 btcs to tom"
2022/08/20 20:23:18 mining: send 2 btcs to tom
2022/08/20 20:23:29 pow: 000000f7876c0c6267e0110cae45397b08eab567969960dd017b5baa49f0f186
➜  tmpchain ./tmpchain p
2022/08/20 20:23:42 Prev: 000000693ac0aa277b3b82811e4bca217b029d2aafea153c8dc9d85b7f556c52
Data:send 2 btcs to tom
Hash: 000000f7876c0c6267e0110cae45397b08eab567969960dd017b5baa49f0f186
2022/08/20 20:23:42 Prev: 000000d25ccd8758d7af3098796909a03e9947242e31ca6a923f69c38bbed307
Data:send 1 btc to alice
Hash: 000000693ac0aa277b3b82811e4bca217b029d2aafea153c8dc9d85b7f556c52
2022/08/20 20:23:42 Prev: 0000000000000000000000000000000000000000000000000000000000000000
Data:Genesis Block
Hash: 000000d25ccd8758d7af3098796909a03e9947242e31ca6a923f69c38bbed307

交易1

  • UTXO 模型

    • unspent transaction output

    • 交易输出

      • 即实际使用的币

      • 交易的输出构成了另一个交易的输入.

      • 构成

        • value

          • btc中存放币的数量, 单位是_satoshi_, 一亿分之一btc

        • script pubkey

    • 交易输入

      • 构成

        • txid

          • 上一笔的交易id

        • vout

          • 上一笔交易的输出索引(一个交易有多个输出)

        • script sig

    • 创世交易

  • btc交易示例

    • https://www.blockchain.com/btc/tx/b6f6b339b546a13822192b06ccbdd817afea5311845f769702ae2912f7d94ab5

从头开始调整区块的数据结构.

  • 块中不存储data, 存的是一组打包的交易

    • Txs []*TX

  • 由于没有了data, 调整Pow的prepare方法, 给块内的所有交易取hash.

  • balance 计算

    • 遍历链上区块

      • 遍历区块内的多个交易

        • 找到其中未被消费的TXOutput

          • 就是没有TXInput链接的out.

        • 加总其中包含的Value

  • send(from, to, amount)操作

    • 取出所有属于from的未被消费的TXOutput

    • 生成一条交易,把凑够amountTXOutput指给to.

    • 如果多个txoutput的扣额大于amount, 把差额生成一条交易,退给from.

地址