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

简介

前两次已经实现了区块链的雏形,并且用kv数据库实现了持久化。作为一个数字货币,我觉得不可能让使用者每次都在main函数里输入一系列方法再go build go run来进行交易,因此一个交互系统是必要的。其次,也是时候完善之前留下的坑了,也就是Transaction,数字货币中最最核心的部分。

CLI

关于命令行 我所做的只是将部分函数和方法解耦 再调一下go的包处理参数
没什么好说的 直接贴代码

CLI.go

type CLI struct {}

func isArgsValid() {
	if len(os.Args) < 2{
		viewUsage()
		os.Exit(1)
	}
}

func (cli *CLI) Run() {
	isArgsValid()
	addBlockCmd := flag.NewFlagSet("add-block",flag.ExitOnError)
	viewChainCmd := flag.NewFlagSet("view-chain",flag.ExitOnError)
	createBlockChainCmd := flag.NewFlagSet("create-block-chain",flag.ExitOnError)

	createBlockChainParam := createBlockChainCmd.String("data",
		"Genesis block data......","Genesis-block-data")
	addBlockParam := addBlockCmd.String("data",
		"New block data......", "New-block-data")

	switch os.Args[1] {
	case "create-block-chain":
		err := createBlockChainCmd.Parse(os.Args[2:])
		if err != nil {
			log.Panic(err)
		}
	case "add-block":
		err := addBlockCmd.Parse(os.Args[2:])
		if err != nil {
			log.Panic(err)
		}
	case "view-chain":
		err := viewChainCmd.Parse(os.Args[2:])
		if err != nil {
			log.Panic(err)
		}
	default:
		viewUsage()
		os.Exit(1)
	}

	if addBlockCmd.Parsed() {
		if *addBlockParam == "" {
			viewUsage()
			os.Exit(1)
		}
		cli.addBlock(*addBlockParam)
	}
	if createBlockChainCmd.Parsed() {
		if *createBlockChainParam == "" {
			viewUsage()
			os.Exit(1)
		}
		cli.createBlockChain(*createBlockChainParam)
	}
	if viewChainCmd.Parsed() {
		cli.viewChain()
	}
}

func viewUsage() {
	fmt.Println("[#] Usage:")
	fmt.Println("[#] create-block-chain -data DATA -- Create a new block chain")
	fmt.Println("[#] add-block -data Data -- Add a new block")
	fmt.Println("[#] view-chain -- See the info of the chain")
}

func (cli *CLI) createBlockChain(data string) {
	CreateBlockChain(data)
}

func (cli *CLI) viewChain() {
	blockChain := GetBlockChain()
	if blockChain == nil {
		fmt.Println("[ ERROR ] Block chain not found......")
		return
	}
	defer blockChain.DB.Close()
	blockChain.viewBlockChain()
}

func (cli *CLI)addBlock(data string){
	blockChain := GetBlockChain()
	if blockChain == nil{
		fmt.Println("[ Error ] Genesis block not found......")
		os.Exit(1)
	}
	defer blockChain.DB.Close()
	blockChain.AddNewBlock(data)
	fmt.Println("[ SUCCESS ] Successfully add a block to the chain......")
}

其中我对一些方法做了封装,如下

func (blockChain *BlockChain) viewBlockChain() {
	iterator := GetIterator(blockChain)
	block := iterator.Next()
	for  {
		fmt.Println()
		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.Printf("[#] Data : %s\n", block.Data)
		fmt.Println()
		if big.NewInt(0).Cmp(new(big.Int).SetBytes(block.PrevHash)) == 0 {
			break
		}
		block = iterator.Next()
	}
}

这里我们将区块链的创建和获取分离,便于维护

func GetBlockChain() *BlockChain {
	if !isDBExist() {
		fmt.Println("[ ERROR ] Database not found......")
		return nil
	}

	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
}
func CreateBlockChain(data string) {
	if isDBExist() {
		fmt.Println("[ ERROR ] Database already exists......")
		return
		//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)
		//}
	}

	fmt.Println("[ SUCCESS ] Creating a database ......")
	genesisBlock := CreateGenesisBlock(data)
	db, err := bolt.Open(DBNAME, 0600, nil)
	defer db.Close()
	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("[ ERROR ] Failed to add the genesis block ......")
			}
			b.Put([]byte("last_hash"), genesisBlock.Hash)
		}
		return nil
	})
	if err != nil {
		log.Panic(err)
	}
	return
}

CLI部分大致就是这样,编译后得到bc文件,执行效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将数据文件删除后则会报错:
在这里插入图片描述

交易

首先我们要引入UTXO的概念。UTXO(Unspent Transaction Outputs)是未花费的交易输出,它是比特币交易生成及验证的一个核心概念。交易构成了一组链式结构,所有合法的比特币交易都可以追溯到前向一个或多个交易的输出,这些链条的源头都是挖矿奖励,末尾则是当前未花费的交易输出。

先来看一下传统的基于账户的交易是怎样的过程:
A想支付100元给B,步骤如下
1、检查A的账户余额是否大于100元,否则报错
2、A的账户余额减去( 100元 + 手续费 )
3、B的账户余额增加100元
现代的大多数场景如银行等都是基于账户的交易机制,由关系型数据库存储账户数据。

在比特币中,区块链里存储的是一笔又一笔的交易记录。每笔交易都有若干交易输入( inputs ),也就是资金来源,也都有若干笔交易输出( outputs ),也就是资金去向。一般来说,每一笔交易都要花费(spend)一笔输入,产生一笔输出,而其所产生的输出,就是“未花费过的交易输出”,也就是 UTXO。

Transaction 指一个经过签名运算的,表达价值转移的数据结构;包含一个输入列表和一个输出列表。输入用来追溯这笔钱的来源,输出则用来进行二次加密,使得只有收款者才有能力解密并使用这笔钱。输入中包含解锁脚本(unlocking script),输出中包含锁定脚本(locking script),锁定脚本往往含有一个公钥或比特币地址,所以曾经被称为公钥脚本,在代码中常用scriptPubKey表示。解锁脚本往往是支付方用自己的私钥所做的签名,曾被称为签名脚本,代码中用scriptSig表示。在一个交易中,锁定脚本相当于是加密难题,解锁脚本是解开锁定脚本的题解。但解锁脚本的题解是针对上一笔交易输出中产生的加密难题的题解,而并非当前交易。

时间原因具体代码实现下次再贴。。

发布了6 篇原创文章 · 获赞 4 · 访问量 646

猜你喜欢

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