简介
前两次已经实现了区块链的雏形,并且用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表示。在一个交易中,锁定脚本相当于是加密难题,解锁脚本是解开锁定脚本的题解。但解锁脚本的题解是针对上一笔交易输出中产生的加密难题的题解,而并非当前交易。
时间原因具体代码实现下次再贴。。