写BUG | 从零实现简易数字货币 ( 2 )

简介

上一次已经实现了区块链的基本结构和功能,但只能运行在内存中,一关闭程序就会失去所有的数据,作为一个数字货币这显然是不行的。事实上区块链本来就是一个分布式的数据库,当作为数字货币时便用来存储交易信息。由于时间限制,这次就先将我们的区块链在本地的数据库中持久化,以达到”记账“的目的。同时,由于我们所需要的功能只是简单的K/V存储,所以并不选用MYSQL等常见的多功能的数据库,而采用同样基于go编写的较为简单的boltDB。

先来改变一下chain的结构,由于是存储在数据库中,我们弃用原本的array结构,在数据库中额外指定一个"last_hash"键用来标记最新Block的Hash,从而方便地连接新的Block或是遍历查询历史Block。

type BlockChain struct {
	//BlockChain []*Block
	LastHash []byte
	DB *bolt.DB
}

由于是存储在数据库中,Block需要添加相应的序列化和反序列化方法

func (block *Block) SerializeBlock() []byte {
	var result bytes.Buffer
	encoder := gob.NewEncoder(&result)
	err := encoder.Encode(block)
	if err != nil {
		log.Panic(err)
	}
	return result.Bytes()
}

func DeserializeBlock(blockBytes []byte) *Block{
	var block Block
	var reader = bytes.NewReader(blockBytes)
	decoder := gob.NewDecoder(reader)
	err := decoder.Decode(&block)
	if err != nil {
		log.Panic(err)
	}
	return &block
}

创建区块链的方法也相应改写

func CreateBlockChain(data string) *BlockChain{
	if isDBExist() {
		fmt.Println("[#] Database already exists......")
		db, err := bolt.Open(DBNAME, 0600, nil)
		if err != nil {
			log.Fatal(err)
		}
		var blockChain *BlockChain
		err = db.View(func(tx *bolt.Tx) error {
			b := tx.Bucket([]byte(BLOCKTABLENAME))
			if b != nil {
				hash := b.Get([]byte("last_hash"))
				blockChain = &BlockChain{hash, db}
			}
			return nil
		})
		if err != nil {
			log.Fatal(err)
		}
		return blockChain
	}

	fmt.Println("[#] Creating a database ......")
	genesisBlock := CreateGenesisBlock(data)
	db, err := bolt.Open(DBNAME, 0600, nil)
	if err != nil {
		log.Fatal(err)
	}
	err = db.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucket([]byte(BLOCKTABLENAME))
		if err != nil {
			log.Panic(err)
		}
		if b != nil {
			err = b.Put(genesisBlock.Hash, genesisBlock.SerializeBlock())
			if err != nil {
				log.Panic("[#] Failed to add the block ......")
			}
			b.Put([]byte("last_hash"), genesisBlock.Hash)
		}
		return nil
	})
	if err != nil {
		log.Panic(err)
	}
	return &BlockChain{genesisBlock.Hash, db}
}

添加新的区块只需通过"last_hash"键拿到最近的Block,再把本区块的prevHash设为它的hash,将height递增即可。

//添加新区块
func (blockChain *BlockChain) AddNewBlock(data string) {
	err := blockChain.DB.Update(func(tx *bolt.Tx) error {
		table := tx.Bucket([]byte(BLOCKTABLENAME))
		if table != nil {
			blockBytes := table.Get(blockChain.LastHash)
			lastBlock := DeserializeBlock(blockBytes)
			newBlock := NewBlock(data, lastBlock.Hash, lastBlock.Height + 1)
			err := table.Put(newBlock.Hash, newBlock.SerializeBlock())
			if err != nil {
				log.Panic(err)
			}
			table.Put([]byte("last_hash"), newBlock.Hash)
			blockChain.LastHash = newBlock.Hash
		}
		return nil
	})

	if err != nil {
		log.Panic(err)
	}
}

针对区块链的遍历,另外设计一个迭代器结构

type Iterator struct {
	CurrentHash [] byte //当前区块的hash
	DB          *bolt.DB
}

func GetInterator(chain *BlockChain) *Iterator{
	return &Iterator{
		CurrentHash: chain.LastHash,
		DB:          chain.DB,
	}
}

通过Next方法便可以不断地得到前一个Block,可类比链表的遍历。

func (bcIterator *Iterator) Next() *Block {
	block := &Block{}
	err := bcIterator.DB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(BLOCKTABLENAME))
		if b != nil{
			blockBytes := b.Get(bcIterator.CurrentHash)
			block = DeserializeBlock(blockBytes)
			bcIterator.CurrentHash = block.PrevHash
		}
		return nil
	})
	if err != nil{
		log.Panic(err)
	}
	return block
}

最后放到main里跑了几次

func main() {
	blockChain := BlockChain.CreateBlockChain("[#] genesis block created......")
	fmt.Println("[#] genesis block created......")
	blockChain.AddNewBlock("[#] first block created......")
	fmt.Println("[#] first block created......")
	iterator := BlockChain.GetInterator(blockChain)
	block := iterator.Next()
	for big.NewInt(0).Cmp(new(big.Int).SetBytes(block.PrevHash)) != 0 {
		fmt.Println("[#] Height : ", block.Height)
		fmt.Println("[#] Nonce : ", block.Nonce)
		fmt.Println("[#] Time stamp : ", block.TimeStamp)
		fmt.Printf("[#] Previous hash : %x\n", block.PrevHash)
		fmt.Printf("[#] Hash : %x\n", block.Hash)
		fmt.Println("[#] Data : ", block.Data)
		block = iterator.Next()
	}
	defer blockChain.DB.Close()
	//db, err := bolt.Open("block_chain.db", 0600, nil)
	//if err != nil {
	//	log.Fatal(err)
	//}
	//defer db.Close()
}

结果如下,说明我们的持久化和遍历都已经具备正常功能。

测试
那么持久化到这里就结束了。下一步将会考虑将”交易(Transaction)“引入,以及为这个数字货币系统提供相对友好的交互界面。

发布了6 篇原创文章 · 获赞 0 · 访问量 254

猜你喜欢

转载自blog.csdn.net/zzl200012/article/details/105251040