├── README.md ├── main.go ├── base58.go ├── block.go ├── merkle_tree.go ├── pow.go ├── wallet.go ├── utxo.go ├── transaction.go ├── CLI.go └── blockchain.go /README.md: -------------------------------------------------------------------------------- 1 | # A_golang_blockchain 2 | //基于golang重写区块链 3 | 此代码为博客上的第六章的代码。 4 | 此时已经实现了区块链中几乎所有的属性和功能,就差区块链网路没有实现,在下一章节我们会实现区块链网路的功能,来完成一个真正的分布式的区块链网络模型。 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go_code/A_golang_blockchain/CLI" 5 | ) 6 | 7 | func main() { 8 | // bc := Blockchain.NewBlockchain() 9 | // defer bc.Db().Close() 10 | 11 | cli := CLI.CLI{} 12 | cli.Run() 13 | 14 | } 15 | -------------------------------------------------------------------------------- /base58.go: -------------------------------------------------------------------------------- 1 | package base58 2 | 3 | import ( 4 | "bytes" 5 | "math/big" 6 | ) 7 | var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") 8 | 9 | //将字节数组编码为Base58 10 | func Base58Encode(input []byte) []byte { 11 | var result []byte 12 | x := big.NewInt(0).SetBytes(input) 13 | base := big.NewInt(int64(len(b58Alphabet))) 14 | zero := big.NewInt(0) 15 | mod := &big.Int{} 16 | for x.Cmp(zero) != 0 { 17 | x.DivMod(x, base, mod) 18 | result = append(result, b58Alphabet[mod.Int64()]) 19 | } 20 | ReverseBytes(result) 21 | for b := range input { 22 | if b == 0x00 { 23 | result = append([]byte{b58Alphabet[0]}, result...) 24 | } else { 25 | break 26 | } 27 | } 28 | return result 29 | } 30 | 31 | //解码Base58编码的数据 32 | func Base58Decode(input []byte) []byte { 33 | result := big.NewInt(0) 34 | zeroBytes := 0 35 | for b := range input { 36 | if b == 0x00 { 37 | zeroBytes++ 38 | } 39 | } 40 | payload := input[zeroBytes:] 41 | for _, b := range payload { 42 | charIndex := bytes.IndexByte(b58Alphabet, b) 43 | result.Mul(result, big.NewInt(58)) 44 | result.Add(result, big.NewInt(int64(charIndex))) 45 | } 46 | decoded := result.Bytes() 47 | decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...) 48 | return decoded 49 | } 50 | 51 | // 反转字节数组 52 | func ReverseBytes(data []byte) { 53 | for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 { 54 | data[i], data[j] = data[j], data[i] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /block.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | _"crypto/sha256" 5 | "encoding/gob" 6 | "bytes" 7 | "log" 8 | "go_code/A_golang_blockchain/transaction" 9 | "go_code/A_golang_blockchain/merkle_tree" 10 | ) 11 | //区块的结构体 12 | type Block struct { 13 | Timestamp int64 14 | Transactions []*transaction.Transaction 15 | PrevBlockHash []byte 16 | Hash []byte 17 | Nonce int 18 | } 19 | 20 | //区块交易字段的哈希 21 | func (b *Block) HashTransactions() []byte { 22 | //var txHash [32]byte 23 | //var txHashes [][]byte 24 | var transactions [][]byte 25 | 26 | for _,tx := range b.Transactions { 27 | //txHashes = append(txHashes,tx.Hash()) 28 | transactions = append(transactions,tx.Serialize()) 29 | } 30 | //txHash = sha256.Sum256(bytes.Join(txHashes,[]byte{})) 31 | mTree := merkle_tree.NewMerkleTree(transactions) 32 | 33 | //return txHash[:] 34 | return mTree.RootNode.Data 35 | } 36 | 37 | //0.3 实现Block的序列化 38 | func (b *Block) Serialize() []byte { 39 | //首先定义一个buffer存储序列化后的数据 40 | var result bytes.Buffer 41 | //实例化一个序列化实例,结果保存到result中 42 | encoder := gob.NewEncoder(&result) 43 | //对区块进行实例化 44 | err := encoder.Encode(b) 45 | if err != nil { 46 | log.Panic(err) 47 | } 48 | return result.Bytes() 49 | } 50 | 51 | //0.3 实现反序列化函数 52 | func DeserializeBlock(d []byte) *Block { 53 | var block Block 54 | decoder := gob.NewDecoder(bytes.NewReader(d)) 55 | err := decoder.Decode(&block) 56 | if err != nil { 57 | log.Panic(err) 58 | } 59 | return &block 60 | } 61 | -------------------------------------------------------------------------------- /merkle_tree.go: -------------------------------------------------------------------------------- 1 | package merkle_tree 2 | 3 | import ( 4 | "crypto/sha256" 5 | 6 | ) 7 | 8 | //创建结构体 9 | type MerkleTree struct { 10 | RootNode *MerkleNode 11 | } 12 | 13 | type MerkleNode struct { 14 | Left *MerkleNode 15 | Right *MerkleNode 16 | Data []byte 17 | } 18 | 19 | //创建一个新的节点 20 | func NewMerkleNode(left,right *MerkleNode,data []byte) *MerkleNode { 21 | mNode := MerkleNode{} 22 | 23 | if left == nil && right == nil { 24 | //叶子节点 25 | hash := sha256.Sum256(data) 26 | mNode.Data = hash[:] 27 | } else { 28 | prevHashes := append(left.Data,right.Data...) 29 | hash := sha256.Sum256(prevHashes) 30 | mNode.Data = hash[:] 31 | } 32 | 33 | mNode.Left = left 34 | mNode.Right = right 35 | 36 | return &mNode 37 | } 38 | 39 | //生成一颗新树 40 | func NewMerkleTree(data [][]byte) *MerkleTree { 41 | var nodes []MerkleNode 42 | 43 | //输入的交易个数如果是单数的话,就复制最后一个,成为复数 44 | if len(data) % 2 != 0 { 45 | data = append(data,data[len(data) - 1]) 46 | } 47 | 48 | //通过数据生成叶子节点 49 | for _,datum := range data { 50 | node := NewMerkleNode(nil,nil,datum) 51 | nodes = append(nodes,*node) 52 | } 53 | 54 | //循环一层一层的生成节点,知道到最上面的根节点为止 55 | for i := 0; i < len(data)/2; i++ { 56 | var newLevel []MerkleNode 57 | 58 | for j := 0; j < len(nodes); j += 2 { 59 | node := NewMerkleNode(&nodes[j],&nodes[j+1],nil) 60 | newLevel = append(newLevel,*node) 61 | } 62 | 63 | nodes = newLevel 64 | } 65 | 66 | mTree := MerkleTree{&nodes[0]} 67 | 68 | return &mTree 69 | 70 | } 71 | -------------------------------------------------------------------------------- /pow.go: -------------------------------------------------------------------------------- 1 | package pow 2 | 3 | import ( 4 | "fmt" 5 | "crypto/sha256" 6 | "strconv" 7 | "bytes" 8 | "math/big" 9 | "go_code/A_golang_blockchain/block" 10 | "go_code/A_golang_blockchain/transaction" 11 | "math" 12 | "time" 13 | ) 14 | //在实际的比特币区块链中,加入一个区块是非常困难的事情,其中运用得到的就是工作量证明 15 | 16 | //创建一个工作量证明的结构体 17 | type ProofOfWork struct { 18 | block *block.Block //要证明的区块 19 | target *big.Int //难度值 20 | } 21 | //声明一个挖矿难度 22 | const targetBits = 10 23 | 24 | //实例化一个工作量证明 25 | func NewProofOfWork(b *block.Block) *ProofOfWork { 26 | target := big.NewInt(1) 27 | target.Lsh(target,uint(256 - targetBits)) 28 | 29 | pow := &ProofOfWork{b,target} 30 | return pow 31 | } 32 | 33 | //准备需要进行哈希的数据 34 | func (pow *ProofOfWork) prepareData(nonce int) []byte { 35 | data := bytes.Join( 36 | [][]byte{ 37 | pow.block.PrevBlockHash, 38 | pow.block.HashTransactions(), //这里被修改,把之前的Data字段修改成交易字段的哈希 39 | []byte(strconv.FormatInt(pow.block.Timestamp,10)), 40 | []byte(strconv.FormatInt(targetBits,10)), 41 | []byte(strconv.FormatInt(int64(nonce),10)), 42 | }, 43 | []byte{}, 44 | ) 45 | return data 46 | } 47 | 48 | //进行工作量证明,证明成功会返回随机数和区块哈希 49 | func (pow *ProofOfWork) Run() (int,[]byte) { 50 | nonce := 0 51 | var hash [32]byte 52 | var hashInt big.Int 53 | for nonce < math.MaxInt64 { 54 | data := pow.prepareData(nonce) 55 | hash = sha256.Sum256(data) 56 | hashInt.SetBytes(hash[:]) 57 | 58 | //把哈希后的数据与难度值进行比较 59 | if hashInt.Cmp(pow.target) == -1 { 60 | fmt.Printf("工作量证明成功 hash= %x nonce = %v\n",hash,nonce) 61 | break 62 | }else{ 63 | nonce ++ 64 | } 65 | } 66 | fmt.Println() 67 | 68 | return nonce,hash[:] 69 | } 70 | 71 | //实例化一个区块 /更改data为transaction/ 72 | func NewBlock(transactions []*transaction.Transaction,prevBlockHash []byte) *block.Block { 73 | block := &block.Block{time.Now().Unix(),transactions,prevBlockHash,[]byte{},0} 74 | // block.SetHash() 75 | 76 | pow := NewProofOfWork(block) 77 | nonce,hash := pow.Run() 78 | block.Hash = hash 79 | block.Nonce = nonce 80 | return block 81 | } 82 | 83 | //其他节点验证nonce是否正确 84 | func (pow *ProofOfWork) Validate() bool { 85 | var hashInt big.Int 86 | 87 | data := pow.prepareData(pow.block.Nonce) 88 | hash := sha256.Sum256(data) 89 | hashInt.SetBytes(hash[:]) 90 | 91 | isValid := hashInt.Cmp(pow.target) == -1 92 | return isValid 93 | } 94 | -------------------------------------------------------------------------------- /wallet.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "crypto/elliptic" 7 | "crypto/ecdsa" 8 | "crypto/rand" 9 | "log" 10 | "os" 11 | "fmt" 12 | "io/ioutil" 13 | "encoding/gob" 14 | "golang.org/x/crypto/ripemd160" 15 | "go_code/A_golang_blockchain/base58" 16 | 17 | ) 18 | const version = byte(0x00) 19 | const walletFile = "wallet.dat" 20 | const addressChecksumLen = 4 //对校验位一般取4位 21 | 22 | //创建一个钱包结构体,钱包里面只装公钥和私钥 23 | type Wallet struct { 24 | PrivateKey ecdsa.PrivateKey 25 | PublicKey []byte 26 | } 27 | 28 | //实例化一个钱包 29 | func NewWallet() *Wallet { 30 | //生成秘钥对 31 | private , public := newKeyPair() 32 | wallet := &Wallet{private,public} 33 | return wallet 34 | } 35 | //生成密钥对函数 36 | func newKeyPair() (ecdsa.PrivateKey,[]byte) { 37 | //返回一个实现了P-256的曲线 38 | curve := elliptic.P256() 39 | //通过椭圆曲线 随机生成一个私钥 40 | private ,err := ecdsa.GenerateKey(curve,rand.Reader) 41 | if err != nil { 42 | log.Panic(err) 43 | } 44 | pubKey := append(private.PublicKey.X.Bytes(),private.PublicKey.Y.Bytes()...) 45 | 46 | return *private,pubKey 47 | } 48 | 49 | //生成一个地址 50 | func (w Wallet) GetAddress() []byte { 51 | //调用公钥哈希函数,实现RIPEMD160(SHA256(Public Key)) 52 | pubKeyHash := HashPubKey(w.PublicKey) 53 | //存储version和公钥哈希的切片 54 | versionedPayload := append([]byte{version},pubKeyHash...) 55 | //调用checksum函数,对上面的切片进行双重哈希后,取出哈希后的切片的前面部分作为检验位的值 56 | checksum := checksum(versionedPayload) 57 | //把校验位加到上面切片后面 58 | fullPayload := append(versionedPayload,checksum...) 59 | //通过base58编码上述切片得到地址 60 | address := base58.Base58Encode(fullPayload) 61 | 62 | return address 63 | } 64 | 65 | //公钥哈希函数,实现RIPEMD160(SHA256(Public Key)) 66 | func HashPubKey(pubKey []byte) []byte { 67 | //先hash公钥 68 | publicSHA256 := sha256.Sum256(pubKey) 69 | //对公钥哈希值做 ripemd160运算 70 | RIPEMD160Hasher := ripemd160.New() 71 | _,err := RIPEMD160Hasher.Write(publicSHA256[:]) 72 | if err != nil { 73 | log.Panic(err) 74 | } 75 | publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) 76 | 77 | return publicRIPEMD160 78 | } 79 | //校验位checksum,双重哈希运算 80 | func checksum(payload []byte) []byte { 81 | //下面双重哈希payload,在调用中,所引用的payload为(version + Pub Key Hash) 82 | firstSHA := sha256.Sum256(payload) 83 | secondSHA := sha256.Sum256(firstSHA[:]) 84 | 85 | //addressChecksumLen代表保留校验位长度 86 | return secondSHA[:addressChecksumLen] 87 | } 88 | 89 | //判断输入的地址是否有效,主要是检查后面的校验位是否正确 90 | func ValidateAddress(address string) bool { 91 | //解码base58编码过的地址 92 | pubKeyHash := base58.Base58Decode([]byte(address)) 93 | //拆分pubKeyHash,pubKeyHash组成形式为:(一个字节的version) + (Public key hash) + (Checksum) 94 | actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:] 95 | version := pubKeyHash[0] 96 | pubKeyHash = pubKeyHash[1:len(pubKeyHash)-addressChecksumLen] 97 | targetChecksum := checksum(append([]byte{version},pubKeyHash...)) 98 | //比较拆分出的校验位与计算出的目标校验位是否相等 99 | return bytes.Compare(actualChecksum,targetChecksum) == 0 100 | } 101 | 102 | //创建一个钱包集合的结构体 103 | type Wallets struct { 104 | Wallets map[string]*Wallet 105 | } 106 | 107 | // 实例化一个钱包集合, 108 | func NewWallets() (*Wallets, error) { 109 | wallets := Wallets{} 110 | wallets.Wallets = make(map[string]*Wallet) 111 | err := wallets.LoadFromFile() 112 | 113 | return &wallets, err 114 | } 115 | 116 | // 将 Wallet 添加进 Wallets 117 | func (ws *Wallets) CreateWallet() string { 118 | wallet := NewWallet() 119 | address := fmt.Sprintf("%s", wallet.GetAddress()) 120 | ws.Wallets[address] = wallet 121 | return address 122 | } 123 | 124 | // 得到存储在wallets里的地址 125 | func (ws *Wallets) GetAddresses() []string { 126 | var addresses []string 127 | for address := range ws.Wallets { 128 | addresses = append(addresses, address) 129 | } 130 | return addresses 131 | } 132 | // 通过地址返回出钱包 133 | func (ws Wallets) GetWallet(address string) Wallet { 134 | return *ws.Wallets[address] 135 | } 136 | 137 | // 从文件中加载钱包s 138 | func (ws *Wallets) LoadFromFile() error { 139 | if _, err := os.Stat(walletFile); os.IsNotExist(err) { 140 | return err 141 | } 142 | fileContent, err := ioutil.ReadFile(walletFile) 143 | if err != nil { 144 | log.Panic(err) 145 | } 146 | var wallets Wallets 147 | gob.Register(elliptic.P256()) 148 | decoder := gob.NewDecoder(bytes.NewReader(fileContent)) 149 | err = decoder.Decode(&wallets) 150 | if err != nil { 151 | log.Panic(err) 152 | } 153 | ws.Wallets = wallets.Wallets 154 | return nil 155 | } 156 | 157 | // 将钱包s保存到文件 158 | func (ws Wallets) SaveToFile() { 159 | var content bytes.Buffer 160 | gob.Register(elliptic.P256()) 161 | encoder := gob.NewEncoder(&content) 162 | err := encoder.Encode(ws) 163 | if err != nil { 164 | log.Panic(err) 165 | } 166 | err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) 167 | if err != nil { 168 | log.Panic(err) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /utxo.go: -------------------------------------------------------------------------------- 1 | package utxo 2 | 3 | import ( 4 | "go_code/A_golang_blockchain/transaction" 5 | "encoding/hex" 6 | "github.com/boltdb/bolt" 7 | "go_code/A_golang_blockchain/blockchain" 8 | "go_code/A_golang_blockchain/block" 9 | "log" 10 | ) 11 | const utxoBucket = "chainstate" 12 | 13 | //创建一个结构体,代表UTXO集 14 | type UTXOSet struct { 15 | Blockchain *blockchain.Blockchain 16 | } 17 | 18 | //构建UTXO集的索引并存储在数据库的bucket中 19 | func (u UTXOSet) Reindex() { 20 | //调用区块链中的数据库,这里的Db()是格式工厂Blockchain结构体中的字段db 21 | db := u.Blockchain.Db() 22 | //桶名 23 | bucketName := []byte(utxoBucket) 24 | //对数据库进行读写操作 25 | err := db.Update(func(tx *bolt.Tx) error { 26 | err := tx.DeleteBucket(bucketName) //因为我们是要哦重新建一个桶,所以如果原来的数据库中有相同名字的桶,则删除 27 | if err != nil && err != bolt.ErrBucketNotFound { 28 | log.Panic(err) 29 | } 30 | //创建新桶 31 | _,err = tx.CreateBucket(bucketName) 32 | if err != nil { 33 | log.Panic(err) 34 | } 35 | return nil 36 | }) 37 | if err != nil { 38 | log.Panic(err) 39 | } 40 | 41 | //返回链上所有未花费交易中的交易输出 42 | UTXO := u.Blockchain.FindUTXO() 43 | 44 | //把未花费交易中的交易输出集合写入桶中 45 | err = db.Update(func(tx *bolt.Tx) error { 46 | b := tx.Bucket(bucketName) 47 | 48 | //写入键值对 49 | for txID,outs := range UTXO { 50 | key,err := hex.DecodeString(txID) 51 | if err != nil { 52 | log.Panic(err) 53 | } 54 | err = b.Put(key,outs.Serialize()) 55 | if err != nil { 56 | log.Panic(err) 57 | } 58 | } 59 | return nil 60 | }) 61 | } 62 | 63 | //查询并返回被用于这次花费的输出,找到的输出的总额要刚好大于要花费的输入额 64 | func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte,amount int) (int,map[string][]int) { 65 | //存储找到的未花费输出集合 66 | unspentOutputs := make(map[string][]int) 67 | //记录找到的未花费输出中累加的值 68 | accumulated := 0 69 | db := u.Blockchain.Db() 70 | 71 | err := db.View(func(tx *bolt.Tx) error { 72 | b := tx.Bucket([]byte(utxoBucket)) 73 | //声明一个游标,类似于我们之前构造的迭代器 74 | c := b.Cursor() 75 | 76 | //用游标来遍历这个桶里的数据,这个桶里装的是链上所有的未花费输出集合 77 | for k,v := c.First(); k != nil; k,v =c.Next() { 78 | txID := hex.EncodeToString(k) 79 | outs := transaction.DeserializeOutputs(v) 80 | 81 | for outIdx,out := range outs.Outputs { 82 | if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { 83 | accumulated += out.Value 84 | unspentOutputs[txID] = append(unspentOutputs[txID],outIdx) 85 | } 86 | } 87 | } 88 | return nil 89 | }) 90 | if err != nil { 91 | log.Panic(err) 92 | } 93 | 94 | return accumulated,unspentOutputs 95 | } 96 | 97 | //查询对应的地址的未花费输出 98 | func (u UTXOSet) FindUTXO(pubKeyHash []byte) []transaction.TXOutput { 99 | var UTXOs []transaction.TXOutput 100 | db := u.Blockchain.Db() 101 | 102 | err := db.View(func(tx *bolt.Tx) error { 103 | b := tx.Bucket([]byte(utxoBucket)) 104 | c := b.Cursor() 105 | 106 | for k,v := c.First();k != nil;k,v = c.Next() { 107 | outs := transaction.DeserializeOutputs(v) 108 | 109 | for _,out := range outs.Outputs { 110 | if out.IsLockedWithKey(pubKeyHash) { 111 | UTXOs = append(UTXOs,out) 112 | } 113 | } 114 | } 115 | return nil 116 | }) 117 | if err != nil { 118 | log.Panic(err) 119 | } 120 | return UTXOs 121 | } 122 | 123 | //当区块链中的区块增加后,要同步更新UTXO集,这里引入的区块为新加入的区块。 124 | func (u UTXOSet) Update(block *block.Block) { 125 | db := u.Blockchain.Db() 126 | 127 | err := db.Update(func(tx *bolt.Tx) error { 128 | b := tx.Bucket([]byte(utxoBucket)) 129 | 130 | for _,tx := range block.Transactions { 131 | if tx.IsCoinbase() == false { 132 | for _,vin := range tx.Vin { 133 | //实例化结构体TXOutputs 134 | updatedOuts := transaction.TXOutputs{} 135 | outsBytes := b.Get(vin.Txid) 136 | outs := transaction.DeserializeOutputs(outsBytes) 137 | 138 | for outIdx,out := range outs.Outputs { 139 | if outIdx != vin.Vout { 140 | updatedOuts.Outputs = append(updatedOuts.Outputs,out) 141 | } 142 | } 143 | if len(updatedOuts.Outputs) == 0 { 144 | err := b.Delete(vin.Txid) 145 | if err != nil { 146 | log.Panic(err) 147 | } 148 | }else{ 149 | err := b.Put(vin.Txid,updatedOuts.Serialize()) 150 | if err != nil { 151 | log.Panic(err) 152 | } 153 | } 154 | } 155 | } 156 | newOutputs := transaction.TXOutputs{} 157 | for _,out := range tx.Vout { 158 | newOutputs.Outputs = append(newOutputs.Outputs,out) 159 | } 160 | 161 | err := b.Put(tx.ID,newOutputs.Serialize()) 162 | if err != nil { 163 | log.Panic(err) 164 | } 165 | } 166 | return nil 167 | }) 168 | if err != nil { 169 | log.Panic(err) 170 | } 171 | } 172 | //返回UTXO集中的交易数 173 | func (u UTXOSet) CountTransactions() int { 174 | db := u.Blockchain.Db() 175 | counter := 0 176 | 177 | err := db.View(func(tx *bolt.Tx) error { 178 | b := tx.Bucket([]byte(utxoBucket)) 179 | c := b.Cursor() 180 | 181 | for k,_ := c.First(); k != nil; k,_ = c.Next() { 182 | counter++ 183 | } 184 | return nil 185 | }) 186 | if err != nil { 187 | log.Panic(err) 188 | } 189 | return counter 190 | } 191 | -------------------------------------------------------------------------------- /transaction.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "strings" 5 | "math/big" 6 | "crypto/elliptic" 7 | "encoding/hex" 8 | "crypto/ecdsa" 9 | "crypto/sha256" 10 | "crypto/rand" 11 | "encoding/gob" 12 | "bytes" 13 | "fmt" 14 | "log" 15 | "go_code/A_golang_blockchain/wallet" 16 | "go_code/A_golang_blockchain/base58" 17 | 18 | ) 19 | 20 | var subsidy int = 50 //挖矿奖励 21 | /*创建一个交易的数据结构,交易是由交易ID、交易输入、交易输出组成的, 22 | 一个交易有多个输入和多个输出,所以这里的交易输入和输出应该是切片类型的 23 | */ 24 | type Transaction struct { 25 | ID []byte 26 | Vin []TXInput 27 | Vout []TXOutput 28 | } 29 | 30 | /* 31 | 1、每一笔交易的输入都会引用之前交易的一笔或多笔交易输出 32 | 2、交易输出保存了输出的值和锁定该输出的信息 33 | 3、交易输入保存了引用之前交易输出的交易ID、具体到引用 34 | 该交易的第几个输出、能正确解锁引用输出的签名信息 35 | */ 36 | //交易输出 37 | type TXOutput struct { 38 | Value int //输出的值(可以理解为金额) 39 | //ScriptPubKey string // 锁定该输出的脚本(目前还没实现地址,所以还不能锁定该输出为具体哪个地址所有) 40 | PubkeyHash []byte //存储“哈希”后的公钥,这里的哈希不是单纯的sha256 41 | } 42 | //交易输入 43 | type TXInput struct { 44 | Txid []byte //引用的之前交易的ID 45 | Vout int //引用之前交易输出的具体是哪个输出(一个交易中输出一般有很多) 46 | //ScriptSig string // 能解锁引用输出交易的签名脚本(目前还没实现地址,所以本章不能实现此功能) 47 | Signature []byte //签名脚本 48 | PubKey []byte // 公钥,这里的公钥是正真的公钥 49 | } 50 | 51 | /* 52 | 区块链上存储的交易都是由这些输入输出交易所组成的, 53 | 一个输入交易必须引用之前的输出交易,一个输出交易会被之后的输入所引用。 54 | 问题来了,在最开始的区块链上是先有输入还是先有输出喃? 55 | 答案是先有输出,因为是区块链的创世区块产生了第一个输出, 56 | 这个输出也就是我们常说的挖矿奖励-狗头金,每一个区块都会有一个这样的输出, 57 | 这是奖励给矿工的交易输出,这个输出是凭空产生的。 58 | */ 59 | //现在我们来创建一个这样的coinbase挖矿输出 60 | //to 代表此输出奖励给谁,一般都是矿工地址,data是交易附带的信息 61 | func NewCoinbaseTX(to,data string) *Transaction { 62 | if data == "" { 63 | data = fmt.Sprintf("奖励给 '%s'",to) 64 | } 65 | //此交易中的交易输入,没有交易输入信息 66 | //txin := TXInput{[]byte{},-1,[]byte{},} 67 | txin := TXInput{[]byte{},-1,nil,[]byte(data)} 68 | //交易输出,subsidy为奖励矿工的币的数量 69 | txout := NewTXOutput(subsidy,to) 70 | //组成交易 71 | //tx := Transaction{nil,[]TXInput{txin},[]TXOutput{txout}} 72 | tx := Transaction{nil,[]TXInput{txin},[]TXOutput{*txout}} 73 | 74 | //设置该交易的ID 75 | //tx.SetID() 76 | tx.ID = tx.Hash() 77 | return &tx 78 | } 79 | 80 | ////设置交易ID,交易ID是序列化tx后再哈希 81 | //func (tx *Transaction) SetID() { 82 | //返回一个序列化后的交易 83 | func (tx Transaction) Serialize() []byte { 84 | //var hash [32]byte 85 | var encoder bytes.Buffer 86 | 87 | enc := gob.NewEncoder(&encoder) 88 | err := enc.Encode(tx) 89 | if err != nil { 90 | log.Panic(err) 91 | } 92 | //hash = sha256.Sum256(encoder.Bytes()) 93 | //tx.ID = hash[:] 94 | return encoder.Bytes() 95 | } 96 | //返回交易的哈希值 97 | func (tx *Transaction) Hash() []byte { 98 | var hash [32]byte 99 | 100 | txCopy := *tx 101 | txCopy.ID = []byte{} 102 | 103 | hash = sha256.Sum256(txCopy.Serialize()) 104 | 105 | return hash[:] 106 | } 107 | 108 | /* 109 | 1、每一个区块至少存储一笔coinbase交易,所以我们在区块的字段中把Data字段换成交易。 110 | 2、把所有涉及之前Data字段都要换了,比如NewBlock()、GenesisBlock()、pow里的函数 111 | */ 112 | 113 | //定义在输入和输出上的锁定和解锁方法,目的是让用户只能花自己所用于地址上的币 114 | //输入上锁定的秘钥,表示能引用的输出是unlockingData 115 | // func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { 116 | // return in.ScriptSig == unlockingData 117 | // } 118 | //方法检查输入是否使用了指定密钥来解锁一个输出 119 | func (in *TXInput) UsesKey(pubKeyHash []byte) bool { 120 | lockingHash := wallet.HashPubKey(in.PubKey) 121 | 122 | return bytes.Compare(lockingHash,pubKeyHash) == 0 123 | } 124 | 125 | //输出上的解锁秘钥,表示能被引用的输入是unlockingData 126 | // func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { 127 | // return out.ScriptPubKey == unlockingData 128 | // } 129 | 130 | //锁定交易输出到固定的地址,代表该输出只能由指定的地址引用 131 | func (out *TXOutput) Lock(address []byte) { 132 | pubKeyHash := base58.Base58Decode(address) 133 | pubKeyHash = pubKeyHash[1:len(pubKeyHash)-4] 134 | out.PubkeyHash = pubKeyHash 135 | } 136 | 137 | //判断输入的公钥"哈希"能否解锁该交易输出 138 | func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { 139 | return bytes.Compare(out.PubkeyHash,pubKeyHash) == 0 140 | } 141 | //创建一个新的交易输出 142 | func NewTXOutput(value int,address string) *TXOutput { 143 | txo := &TXOutput{value,nil} 144 | txo.Lock([]byte(address)) 145 | 146 | return txo 147 | } 148 | 149 | //判断是否为coinbase交易 150 | func (tx *Transaction) IsCoinbase() bool { 151 | return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 152 | } 153 | 154 | //对交易签名 155 | func (tx *Transaction) Sign(privKey ecdsa.PrivateKey,prevTXs map[string]Transaction) { 156 | if tx.IsCoinbase() { 157 | return 158 | } 159 | 160 | for _,vin := range tx.Vin { 161 | if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { 162 | log.Panic("ERROR: Previous transaction is not correct") 163 | } 164 | } 165 | txCopy := tx.TrimmedCopy() 166 | 167 | for inID,vin := range txCopy.Vin { 168 | prevTx := prevTXs[hex.EncodeToString(vin.Txid)] 169 | txCopy.Vin[inID].Signature = nil 170 | txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubkeyHash 171 | txCopy.ID = txCopy.Hash() 172 | txCopy.Vin[inID].PubKey = nil 173 | 174 | r,s,err := ecdsa.Sign(rand.Reader,&privKey,txCopy.ID) 175 | if err != nil { 176 | log.Panic(err) 177 | } 178 | signature := append(r.Bytes(),s.Bytes()...) 179 | 180 | tx.Vin[inID].Signature = signature 181 | } 182 | 183 | } 184 | 185 | //验证 交易输入的签名 186 | func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { 187 | if tx.IsCoinbase() { 188 | return true 189 | } 190 | for _,vin := range tx.Vin { 191 | //遍历输入交易,如果发现输入交易引用的上一交易的ID不存在,则Panic 192 | if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { 193 | log.Panic("ERROR: Previous transaction is not correct") 194 | } 195 | } 196 | txCopy := tx.TrimmedCopy() //修剪后的副本 197 | curve := elliptic.P256() //椭圆曲线实例 198 | 199 | for inID,vin := range tx.Vin { 200 | prevTX := prevTXs[hex.EncodeToString(vin.Txid)] 201 | txCopy.Vin[inID].Signature = nil //双重验证 202 | txCopy.Vin[inID].PubKey = prevTX.Vout[vin.Vout].PubkeyHash 203 | txCopy.ID = txCopy.Hash() 204 | txCopy.Vin[inID].PubKey = nil 205 | 206 | r := big.Int{} 207 | s := big.Int{} 208 | sigLen := len(vin.Signature) 209 | r.SetBytes(vin.Signature[:(sigLen / 2)]) 210 | s.SetBytes(vin.Signature[(sigLen / 2):]) 211 | 212 | x := big.Int{} 213 | y := big.Int{} 214 | keyLen := len(vin.PubKey) 215 | x.SetBytes(vin.PubKey[:(keyLen / 2)]) 216 | y.SetBytes(vin.PubKey[(keyLen / 2):]) 217 | 218 | rawPubKey := ecdsa.PublicKey{curve,&x,&y} 219 | if ecdsa.Verify(&rawPubKey,txCopy.ID,&r,&s) == false { 220 | return false 221 | } 222 | } 223 | return true 224 | } 225 | 226 | //创建在签名中修剪后的交易副本,之所以要这个副本是因为简化了输入交易本身的签名和公钥 227 | func (tx *Transaction) TrimmedCopy() Transaction { 228 | var inputs []TXInput 229 | var outputs []TXOutput 230 | 231 | for _,vin := range tx.Vin { 232 | inputs = append(inputs,TXInput{vin.Txid,vin.Vout,nil,nil}) 233 | } 234 | 235 | for _,vout := range tx.Vout { 236 | outputs = append(outputs,TXOutput{vout.Value,vout.PubkeyHash}) 237 | } 238 | 239 | txCopy := Transaction{tx.ID,inputs,outputs} 240 | 241 | return txCopy 242 | } 243 | 244 | //把交易转换成我们能正常读的形式 245 | func (tx Transaction) String() string { 246 | var lines []string 247 | lines = append(lines, fmt.Sprintf("--Transaction %x:", tx.ID)) 248 | for i, input := range tx.Vin { 249 | lines = append(lines, fmt.Sprintf(" -Input %d:", i)) 250 | lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) 251 | lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) 252 | lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) 253 | lines = append(lines, fmt.Sprintf(" PubKey:%x", input.PubKey)) 254 | } 255 | for i, output := range tx.Vout { 256 | lines = append(lines, fmt.Sprintf(" -Output %d:", i)) 257 | lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) 258 | lines = append(lines, fmt.Sprintf(" Script: %x", output.PubkeyHash)) 259 | } 260 | return strings.Join(lines,"\n") 261 | 262 | } 263 | 264 | //创建一个结构体,用于表示TXOutput集 265 | type TXOutputs struct { 266 | Outputs []TXOutput 267 | } 268 | //序列化此集合 269 | func(outs TXOutputs) Serialize() []byte { 270 | var buff bytes.Buffer 271 | 272 | enc := gob.NewEncoder(&buff) 273 | err := enc.Encode(outs) 274 | if err != nil { 275 | log.Panic(err) 276 | } 277 | return buff.Bytes() 278 | } 279 | 280 | //反序列化 281 | func DeserializeOutputs(data []byte) TXOutputs { 282 | var outputs TXOutputs 283 | 284 | dec := gob.NewDecoder(bytes.NewReader(data)) 285 | err := dec.Decode(&outputs) 286 | if err != nil { 287 | log.Panic(err) 288 | } 289 | 290 | return outputs 291 | } 292 | -------------------------------------------------------------------------------- /CLI.go: -------------------------------------------------------------------------------- 1 | package CLI 2 | 3 | import ( 4 | "go_code/A_golang_blockchain/base58" 5 | "go_code/A_golang_blockchain/transaction" 6 | "fmt" 7 | "os" 8 | "encoding/hex" 9 | "flag" 10 | "go_code/A_golang_blockchain/blockchain" 11 | "go_code/A_golang_blockchain/pow" 12 | "go_code/A_golang_blockchain/wallet" 13 | "go_code/A_golang_blockchain/utxo" 14 | "strconv" 15 | "log" 16 | ) 17 | //首先我们想要拥有这些命令 1.加入区块命令 2.打印区块链命令 18 | 19 | //创建一个CLI结构体 20 | type CLI struct { 21 | //BC *blockchain.Blockchain 22 | } 23 | 24 | 25 | //加入输入格式错误信息提示 26 | func (cli *CLI) printUsage() { 27 | fmt.Println("Usage:") 28 | // fmt.Println(" getbalance -address ADDRESS 得到该地址的余额") 29 | fmt.Println(" createblockchain -address ADDRESS 创建一条链并且该地址会得到狗头金") 30 | fmt.Println(" createwallet - 创建一个钱包,里面放着一对秘钥") 31 | fmt.Println(" getbalance -address ADDRESS 得到该地址的余额") 32 | fmt.Println(" listaddresses - Lists all addresses from the wallet file") 33 | fmt.Println(" printchain - 打印链") 34 | fmt.Println(" reindexutxo - Rebuilds the UTXO set") 35 | fmt.Println(" send -from FROM -to TO -amount AMOUNT 地址from发送amount的币给地址to") 36 | } 37 | 38 | //判断命令行参数,如果没有输入参数则显示提示信息 39 | func (cli *CLI) validateArgs() { 40 | if len(os.Args) < 2 { 41 | cli.printUsage() 42 | os.Exit(1) 43 | } 44 | } 45 | 46 | // //加入区块函数调用 47 | // func (cli *CLI) addBlock(data string) { 48 | // cli.BC.MineBlock(data) 49 | // fmt.Println("成功加入区块...") 50 | // } 51 | 52 | 53 | 54 | //创建一条链 55 | func (cli *CLI) createBlockchain(address string) { 56 | if !wallet.ValidateAddress(address) { 57 | log.Panic("ERROR: Address is not valid") 58 | } 59 | 60 | bc := blockchain.CreateBlockchain(address) 61 | defer bc.Db().Close() 62 | 63 | UTXOSet := utxo.UTXOSet{bc} 64 | UTXOSet.Reindex() 65 | fmt.Println("Done!") 66 | } 67 | 68 | //创建钱包函数 69 | func (cli *CLI) createWallet() { 70 | wallets, _ := wallet.NewWallets() 71 | address := wallets.CreateWallet() 72 | wallets.SaveToFile() 73 | fmt.Printf("Your new address: %s\n", address) 74 | } 75 | 76 | //求账户余额 77 | func (cli *CLI) getBalance(address string) { 78 | if !wallet.ValidateAddress(address) { 79 | log.Panic("ERROR: Address is not valid") 80 | } 81 | bc := blockchain.NewBlockchain() 82 | UTXOSet := utxo.UTXOSet{bc} 83 | defer bc.Db().Close() 84 | 85 | balance := 0 86 | pubKeyHash := base58.Base58Decode([]byte(address)) 87 | pubKeyHash = pubKeyHash[1:len(pubKeyHash)-4] //这里的4是校验位字节数,这里就不在其他包调过来了 88 | 89 | UTXOs := UTXOSet.FindUTXO(pubKeyHash) 90 | 91 | //遍历UTXOs中的交易输出out,得到输出字段out.Value,求出余额 92 | for _,out := range UTXOs { 93 | balance += out.Value 94 | } 95 | 96 | fmt.Printf("Balance of '%s':%d\n",address,balance) 97 | } 98 | 99 | //列出地址名单,钱包集合中的地址有哪些 100 | func (cli *CLI) listAddresses() { 101 | wallets, err := wallet.NewWallets() 102 | if err != nil { 103 | log.Panic(err) 104 | } 105 | addresses := wallets.GetAddresses() 106 | for _, address := range addresses { 107 | fmt.Println(address) 108 | } 109 | } 110 | 111 | //打印区块链函数调用 112 | func (cli *CLI) printChain() { 113 | //实例化一条链 114 | bc := blockchain.NewBlockchain() //因为已经有了链,不会重新创建链,所以接收的address设置为空 115 | defer bc.Db().Close() 116 | 117 | //这里需要用到迭代区块链的思想 118 | //创建一个迭代器 119 | bci := bc.Iterator() 120 | 121 | for { 122 | 123 | block := bci.Next() //从顶端区块向前面的区块迭代 124 | 125 | fmt.Printf("------======= 区块 %x ============\n", block.Hash) 126 | fmt.Printf("时间戳:%v\n",block.Timestamp) 127 | fmt.Printf("PrevHash:%x\n",block.PrevBlockHash) 128 | //fmt.Printf("Data:%s\n",block.Data) 129 | //fmt.Printf("Hash:%x\n",block.Hash) 130 | //验证当前区块的pow 131 | pow := pow.NewProofOfWork(block) 132 | boolen := pow.Validate() 133 | fmt.Printf("POW is %s\n",strconv.FormatBool(boolen)) 134 | 135 | for _,tx := range block.Transactions { 136 | transaction := (*tx).String() 137 | fmt.Printf("%s\n",transaction) 138 | } 139 | fmt.Printf("\n\n") 140 | 141 | if len(block.PrevBlockHash) == 0 { 142 | break 143 | } 144 | } 145 | } 146 | //查找UTXO集中的交易数 147 | func (cli *CLI) reindexUTXO() { 148 | bc := blockchain.NewBlockchain() 149 | UTXOSet := utxo.UTXOSet{bc} 150 | UTXOSet.Reindex() //在现实中如果能保证自己下载的链节点是完整的,可以忽略。 151 | count := UTXOSet.CountTransactions() 152 | fmt.Printf("Done!!! There are %d transactions in the UTXO set.\n", count) 153 | } 154 | 155 | //send方法 156 | func (cli *CLI) send(from,to string,amount int) { 157 | if !wallet.ValidateAddress(from) { 158 | log.Panic("ERROR: Address is not valid") 159 | } 160 | if !wallet.ValidateAddress(to) { 161 | log.Panic("ERROR: Address is not valid") 162 | } 163 | 164 | bc := blockchain.NewBlockchain() 165 | UTXOSet := utxo.UTXOSet{bc} 166 | defer bc.Db().Close() 167 | 168 | //tx := NewUTXOTransaction(from,to,amount,bc) 169 | ////挖矿奖励的交易,把挖矿的奖励发送给矿工,这里的矿工默认为发送交易的地址 170 | //cbtx := transaction.NewCoinbaseTX(from,"") 171 | 172 | //挖出一个包含该交易的区块,此时区块还包含了-挖矿奖励的交易 173 | //bc.MineBlock([]*transaction.Transaction{cbtx,tx}) 174 | tx := NewUTXOTransaction(from, to, amount, &UTXOSet) 175 | cbTx := transaction.NewCoinbaseTX(from, "") 176 | txs := []*transaction.Transaction{cbTx, tx} 177 | newBlock := bc.MineBlock(txs) 178 | UTXOSet.Update(newBlock) 179 | fmt.Println("发送成功...") 180 | } 181 | 182 | //入口函数 183 | func (cli *CLI) Run() { 184 | //判断命令行输入参数的个数,如果没有输入任何参数则打印提示输入参数信息 185 | cli.validateArgs() 186 | //实例化flag集合 187 | getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError) 188 | createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) 189 | createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) 190 | listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError) 191 | reindexUTXOCmd := flag.NewFlagSet("reindexutxo", flag.ExitOnError) 192 | sendCmd := flag.NewFlagSet("send", flag.ExitOnError) 193 | printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) 194 | //注册flag标志符 195 | getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for") 196 | createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to") 197 | sendFrom := sendCmd.String("from", "", "Source wallet address") 198 | sendTo := sendCmd.String("to", "", "Destination wallet address") 199 | sendAmount := sendCmd.Int("amount", 0, "Amount to send") 200 | 201 | switch os.Args[1] { //os.Args为一个保存输入命令的切片 202 | case "getbalance": 203 | err := getBalanceCmd.Parse(os.Args[2:]) 204 | if err != nil { 205 | log.Panic(err) 206 | } 207 | case "createblockchain": 208 | err := createBlockchainCmd.Parse(os.Args[2:]) 209 | if err != nil { 210 | log.Panic(err) 211 | } 212 | case "createwallet": 213 | err := createWalletCmd.Parse(os.Args[2:]) 214 | if err != nil { 215 | log.Panic(err) 216 | } 217 | case "listaddresses": 218 | err := listAddressesCmd.Parse(os.Args[2:]) 219 | if err != nil { 220 | log.Panic(err) 221 | } 222 | case "printchain": 223 | err := printChainCmd.Parse(os.Args[2:]) 224 | if err != nil { 225 | log.Panic(err) 226 | } 227 | case "send": 228 | err := sendCmd.Parse(os.Args[2:]) 229 | if err != nil { 230 | log.Panic(err) 231 | } 232 | case "reindexutxo": 233 | err := reindexUTXOCmd.Parse(os.Args[2:]) 234 | if err != nil { 235 | log.Panic(err) 236 | } 237 | default: 238 | cli.printUsage() 239 | os.Exit(1) 240 | } 241 | 242 | //进入被解析出的命令,进一步操作 243 | if getBalanceCmd.Parsed() { 244 | if *getBalanceAddress == "" { 245 | getBalanceCmd.Usage() 246 | os.Exit(1) 247 | } 248 | cli.getBalance(*getBalanceAddress) 249 | } 250 | 251 | if createBlockchainCmd.Parsed() { 252 | if *createBlockchainAddress == "" { 253 | createBlockchainCmd.Usage() 254 | os.Exit(1) 255 | } 256 | cli.createBlockchain(*createBlockchainAddress) 257 | } 258 | 259 | if createWalletCmd.Parsed() { 260 | cli.createWallet() 261 | } 262 | if listAddressesCmd.Parsed() { 263 | cli.listAddresses() 264 | } 265 | 266 | if printChainCmd.Parsed() { 267 | cli.printChain() 268 | } 269 | 270 | if reindexUTXOCmd.Parsed() { 271 | cli.reindexUTXO() 272 | } 273 | 274 | if sendCmd.Parsed() { 275 | if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 { 276 | sendCmd.Usage() 277 | os.Exit(1) 278 | } 279 | cli.send(*sendFrom, *sendTo, *sendAmount) 280 | } 281 | } 282 | 283 | 284 | //发送币操作,相当于创建一笔未花费输出交易 285 | func NewUTXOTransaction(from,to string,amount int,UTXOSet *utxo.UTXOSet) *transaction.Transaction { 286 | var inputs []transaction.TXInput 287 | var outputs []transaction.TXOutput 288 | //validOutputs是一个存放要用到的未花费输出的交易/输出的map 289 | //acc,validOutputs := bc.FindSpendableOutputs(from,amount) 290 | wallets,err := wallet.NewWallets() 291 | if err != nil { 292 | log.Panic(err) 293 | } 294 | _wallet := wallets.GetWallet(from) 295 | pubKeyHash := wallet.HashPubKey(_wallet.PublicKey) 296 | acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount) 297 | if acc < amount { 298 | log.Panic("ERROR:Not enough tokens...") 299 | } 300 | //通过validOutputs里面的数据来放入建立一个输入列表 301 | for txid,outs := range validOutputs { 302 | //反序列化得到txID 303 | txID,err := hex.DecodeString(txid) 304 | if err != nil { 305 | log.Panic(err) 306 | } 307 | //遍历输出outs切片,得到TXInput里的Vout字段值 308 | for _,out := range outs { 309 | //input := transaction.TXInput{txID,out,from} 310 | input := transaction.TXInput{txID,out,nil,_wallet.PublicKey} 311 | inputs = append(inputs,input) 312 | } 313 | } 314 | //建立一个输出列表 315 | //outputs = append(outputs,transaction.TXOutput{amount,to}) 316 | outputs = append(outputs,*transaction.NewTXOutput(amount,to)) 317 | if acc > amount { 318 | //outputs = append(outputs,transaction.TXOutput{acc - amount,from}) //相当于找零 319 | outputs = append(outputs,*transaction.NewTXOutput(acc - amount,from)) //相当于找零 320 | } 321 | tx := transaction.Transaction{nil,inputs,outputs} 322 | //tx.SetID() 323 | tx.ID = tx.Hash() 324 | UTXOSet.Blockchain.SignTransaction(&tx, _wallet.PrivateKey) 325 | 326 | return &tx 327 | } 328 | -------------------------------------------------------------------------------- /blockchain.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "os" 5 | "bytes" 6 | "crypto/ecdsa" 7 | "encoding/hex" 8 | "fmt" 9 | "github.com/boltdb/bolt" 10 | "go_code/A_golang_blockchain/block" 11 | "go_code/A_golang_blockchain/pow" 12 | "go_code/A_golang_blockchain/transaction" 13 | 14 | 15 | "log" 16 | "errors" 17 | ) 18 | /* 19 | 区块链实现 20 | */ 21 | const dbFile = "blockchain.db" 22 | const blocksBucket = "blocks" 23 | const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" 24 | //区块链 25 | type Blockchain struct { 26 | tip []byte 27 | db *bolt.DB 28 | } 29 | 30 | //工厂模式db 31 | func(bc *Blockchain) Db() *bolt.DB { 32 | return bc.db 33 | } 34 | 35 | //把区块添加进区块链,挖矿 36 | func (bc *Blockchain) MineBlock(transactions []*transaction.Transaction) *block.Block { 37 | var lastHash []byte 38 | 39 | //在一笔交易被放入一个块之前进行验证 40 | for _, tx := range transactions { 41 | if bc.VerifyTransaction(tx) != true { 42 | log.Panic("ERROR: 无效 transaction") 43 | } 44 | } 45 | //只读的方式浏览数据库,获取当前区块链顶端区块的哈希,为加入下一区块做准备 46 | err := bc.db.View(func(tx *bolt.Tx) error { 47 | b := tx.Bucket([]byte(blocksBucket)) 48 | lastHash = b.Get([]byte("l")) //通过键"l"拿到区块链顶端区块哈希 49 | 50 | return nil 51 | }) 52 | if err != nil { 53 | log.Panic(err) 54 | } 55 | 56 | //prevBlock := bc.Blocks[len(bc.Blocks)-1] 57 | //求出新区块 58 | newBlock := pow.NewBlock(transactions,lastHash) 59 | // bc.Blocks = append(bc.Blocks,newBlock) 60 | //把新区块加入到数据库区块链中 61 | err = bc.db.Update(func(tx *bolt.Tx) error { 62 | b := tx.Bucket([]byte(blocksBucket)) 63 | err := b.Put(newBlock.Hash,newBlock.Serialize()) 64 | if err != nil { 65 | log.Panic(err) 66 | } 67 | err = b.Put([]byte("l"),newBlock.Hash) 68 | bc.tip = newBlock.Hash 69 | 70 | return nil 71 | }) 72 | if err != nil { 73 | log.Panic(err) 74 | } 75 | 76 | return newBlock 77 | } 78 | 79 | //创建创世区块 /修改/ 80 | func NewGenesisBlock(coinbase *transaction.Transaction) *block.Block { 81 | return pow.NewBlock([]*transaction.Transaction{coinbase},[]byte{}) 82 | } 83 | 84 | //创建区块链数据库 85 | func CreateBlockchain(address string) *Blockchain { 86 | var tip []byte 87 | //此时的创世区块就要包含交易coinbaseTx 88 | cbtx := transaction.NewCoinbaseTX(address, genesisCoinbaseData) 89 | genesis := NewGenesisBlock(cbtx) 90 | 91 | db,err := bolt.Open(dbFile,0600,nil) 92 | if err != nil { 93 | log.Panic(err) 94 | } 95 | //读写操作数据库 96 | err = db.Update(func(tx *bolt.Tx) error{ 97 | b := tx.Bucket([]byte(blocksBucket)) 98 | //查看名字为blocksBucket的Bucket是否存在 99 | if b != nil { 100 | fmt.Println("Blockchain 已经存在...") 101 | os.Exit(1) 102 | } 103 | //否则,则重新创建 104 | b, err := tx.CreateBucket([]byte(blocksBucket)) 105 | if err != nil { 106 | log.Panic(err) 107 | } 108 | 109 | err = b.Put(genesis.Hash, genesis.Serialize())//写入键值对,区块哈希对应序列化后的区块 110 | if err != nil { 111 | log.Panic(err) 112 | } 113 | err = b.Put([]byte("l"), genesis.Hash)//"l"键对应区块链顶端区块的哈希 114 | if err != nil { 115 | log.Panic(err) 116 | } 117 | tip = genesis.Hash //指向最后一个区块,这里也就是创世区块 118 | return nil 119 | }) 120 | if err != nil { 121 | log.Panic(err) 122 | } 123 | 124 | bc := Blockchain{tip, db} 125 | 126 | return &bc 127 | } 128 | 129 | //实例化一个区块链,默认存储了创世区块 ,接收一个地址为挖矿奖励地址 /修改/ 130 | func NewBlockchain() *Blockchain { 131 | //return &Blockchain{[]*block.Block{NewGenesisBlock()}} 132 | var tip []byte 133 | //打开一个数据库文件,如果文件不存在则创建该名字的文件 134 | db,err := bolt.Open(dbFile,0600,nil) 135 | if err != nil { 136 | log.Panic(err) 137 | } 138 | //读写操作数据库 139 | err = db.Update(func(tx *bolt.Tx) error{ 140 | b := tx.Bucket([]byte(blocksBucket)) 141 | //查看名字为blocksBucket的Bucket是否存在 142 | if b == nil { 143 | //不存在 144 | fmt.Println("不存在区块链,需要重新创建一个区块链...") 145 | os.Exit(1) 146 | } 147 | //如果存在blocksBucket桶,也就是存在区块链 148 | //通过键"l"映射出顶端区块的Hash值 149 | tip = b.Get([]byte("l")) 150 | 151 | return nil 152 | }) 153 | if err != nil { 154 | log.Panic(err) 155 | } 156 | 157 | bc := Blockchain{tip,db} //此时Blockchain结构体字段已经变成这样了 158 | return &bc 159 | } 160 | 161 | //分割线——————迭代器—————— 162 | type BlockchainIterator struct { 163 | currentHash []byte 164 | db *bolt.DB 165 | } 166 | //当需要遍历当前区块链时,创建一个此区块链的迭代器 167 | func (bc *Blockchain) Iterator() *BlockchainIterator { 168 | bci := &BlockchainIterator{bc.tip,bc.db} 169 | 170 | return bci 171 | } 172 | 173 | //迭代器的任务就是返回链中的下一个区块 174 | func (i *BlockchainIterator) Next() *block.Block { 175 | var Block *block.Block 176 | 177 | //只读方式打开区块链数据库 178 | err := i.db.View(func(tx *bolt.Tx) error { 179 | b := tx.Bucket([]byte(blocksBucket)) 180 | //获取数据库中当前区块哈希对应的被序列化后的区块 181 | encodeBlock := b.Get(i.currentHash) 182 | //反序列化,获得区块 183 | Block = block.DeserializeBlock(encodeBlock) 184 | 185 | return nil 186 | }) 187 | if err != nil { 188 | log.Panic(err) 189 | } 190 | 191 | //把迭代器中的当前区块哈希设置为上一区块的哈希,实现迭代的作用 192 | i.currentHash =Block.PrevBlockHash 193 | 194 | return Block 195 | 196 | } 197 | 198 | // //在区块链上找到每一个区块中属于address用户的未花费交易输出,返回未花费输出的交易切片 199 | // func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []transaction.Transaction { 200 | // var unspentTXs []transaction.Transaction 201 | // //创建一个map,存储已经花费了的交易输出 202 | // spentTXOs := make(map[string][]int) 203 | // //因为要在链上遍历区块,所以要使用到迭代器 204 | // bci := bc.Iterator() 205 | 206 | // for { 207 | // block := bci.Next() //迭代 208 | 209 | // //遍历当前区块上的交易 210 | // for _,tx := range block.Transactions { 211 | // txID := hex.EncodeToString(tx.ID) //把交易ID转换成string类型,方便存入map中 212 | 213 | // //标签 214 | // Outputs: 215 | // //遍历当前交易中的输出切片,取出交易输出 216 | // for outIdx,out := range tx.Vout { 217 | // //在已经花费了的交易输出map中,如果没有找到对应的交易输出,则表示当前交易的输出未花费 218 | // //反之如下 219 | // if spentTXOs[txID] != nil { 220 | // //存在当前交易的输出中有已经花费的交易输出, 221 | // //则我们遍历map中保存的该交易ID对应的输出的index 222 | // //提示:(这里的已经花费的交易输出index其实就是输入TXInput结构体中的Vout字段) 223 | // for _,spentOutIdx := range spentTXOs[txID] { 224 | // //首先要清楚当前交易输出是一个切片,里面有很多输出, 225 | // //如果map里存储的引用的输出和我们当前遍历到的输出index重合,则表示该输出被引用了 226 | // if spentOutIdx == outIdx { 227 | // continue Outputs //我们就继续遍历下一轮,找到未被引用的输出 228 | // } 229 | // } 230 | // } 231 | // //到这里是得到此交易输出切片中未被引用的输出 232 | 233 | // // //这里就要从这些未被引用的输出中筛选出属于该用户address地址的输出 234 | // // if out.IsLockedWithKey(pubKeyHash) { 235 | // // unspentTXs = append(unspentTXs,*tx) 236 | // // } 237 | 238 | // } 239 | // //判断是否为coinbase交易 240 | // if tx.IsCoinbase() == false { 241 | // //如果不是,则遍历当前交易的输入 242 | // for _,in := range tx.Vin { 243 | // //如果当前交易的输入是被该地址address所花费的,就会有对应的该地址的引用输出 244 | // //则在map上记录该输入引用的该地址对应的交易输出 245 | // if in.UsesKey(pubKeyHash) { 246 | // inTxID := hex.EncodeToString(in.Txid) 247 | // spentTXOs[inTxID] = append(spentTXOs[inTxID],in.Vout) 248 | // } 249 | // } 250 | // } 251 | // } 252 | // //退出for循环的条件就是遍历到的创世区块后 253 | // if len(block.PrevBlockHash) == 0 { 254 | // break 255 | // } 256 | // } 257 | // return unspentTXs 258 | // } 259 | 260 | //通过找到未花费输出交易的集合,我们返回集合中的所有未花费交易的交易输出集合 261 | func (bc *Blockchain) FindUTXO() map[string]transaction.TXOutputs { 262 | //var UTXOs []transaction.TXOutput 263 | UTXO := make(map[string]transaction.TXOutputs) 264 | //找到address地址下的未花费交易输出的交易的集合 265 | //unspentTransactions := bc.FindUnspentTransactions(pubKeyHash) 266 | //创建一个map,存储已经花费了的交易输出 267 | spentTXOs := make(map[string][]int) 268 | //因为要在链上遍历区块,所以要使用到迭代器 269 | bci := bc.Iterator() 270 | 271 | for { 272 | block := bci.Next() //迭代 273 | 274 | //遍历当前区块上的交易 275 | for _,tx := range block.Transactions { 276 | txID := hex.EncodeToString(tx.ID) //把交易ID转换成string类型,方便存入map中 277 | 278 | //标签 279 | Outputs: 280 | //遍历当前交易中的输出切片,取出交易输出 281 | for outIdx,out := range tx.Vout { 282 | //在已经花费了的交易输出map中,如果没有找到对应的交易输出,则表示当前交易的输出未花费 283 | //反之如下 284 | if spentTXOs[txID] != nil { 285 | //存在当前交易的输出中有已经花费的交易输出, 286 | //则我们遍历map中保存的该交易ID对应的输出的index 287 | //提示:(这里的已经花费的交易输出index其实就是输入TXInput结构体中的Vout字段) 288 | for _,spentOutIdx := range spentTXOs[txID] { 289 | //首先要清楚当前交易输出是一个切片,里面有很多输出, 290 | //如果map里存储的引用的输出和我们当前遍历到的输出index重合,则表示该输出被引用了 291 | if spentOutIdx == outIdx { 292 | continue Outputs //我们就继续遍历下一轮,找到未被引用的输出 293 | } 294 | } 295 | } 296 | outs := UTXO[txID] 297 | outs.Outputs = append(outs.Outputs,out) 298 | UTXO[txID] = outs 299 | } 300 | //判断是否为coinbase交易 301 | if tx.IsCoinbase() == false { 302 | //如果不是,则遍历当前交易的输入 303 | for _,in := range tx.Vin { 304 | inTxID := hex.EncodeToString(in.Txid) 305 | spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) 306 | } 307 | } 308 | } 309 | //退出for循环的条件就是遍历到的创世区块后 310 | if len(block.PrevBlockHash) == 0 { 311 | break 312 | } 313 | } 314 | // //遍历交易集合得到交易,从交易中提取出输出字段Vout,从输出字段中提取出属于address的输出 315 | // for _,tx := range unspentTransactions { 316 | // for _, out := range tx.Vout { 317 | // if out.IsLockedWithKey(pubKeyHash) { 318 | // UTXOs = append(UTXOs,out) 319 | // } 320 | // } 321 | // } 322 | //返回未花费交易输出 323 | return UTXO 324 | } 325 | 326 | 327 | 328 | // //找到可以花费的交易输出,这是基于上面的FindUnspentTransactions 方法 329 | // func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte,amount int) (int,map[string][]int) { 330 | // //未花费交易输出map集合 331 | // unspentOutputs := make(map[string][]int) 332 | // //未花费交易 333 | // unspentTXs := bc.FindUnspentTransactions(pubKeyHash) 334 | // accumulated := 0 //累加未花费交易输出中的Value值 335 | 336 | // Work: 337 | // for _,tx := range unspentTXs { 338 | // txID := hex.EncodeToString(tx.ID) 339 | 340 | // for outIdx,out := range tx.Vout { 341 | // if out.IsLockedWithKey(pubKeyHash) && accumulated < amount { 342 | // accumulated += out.Value 343 | // unspentOutputs[txID] = append(unspentOutputs[txID],outIdx) 344 | 345 | // if accumulated >= amount { 346 | // break Work 347 | // } 348 | // } 349 | // } 350 | // } 351 | // return accumulated,unspentOutputs 352 | // } 353 | 354 | //通过交易ID找到一个交易 355 | func (bc *Blockchain) FindTransaction(ID []byte) (transaction.Transaction,error) { 356 | bci := bc.Iterator() 357 | for { 358 | block := bci.Next() 359 | 360 | for _,tx := range block.Transactions { 361 | if bytes.Compare(tx.ID,ID) == 0 { 362 | return *tx,nil 363 | } 364 | } 365 | if len(block.PrevBlockHash) == 0 { 366 | break 367 | } 368 | } 369 | return transaction.Transaction{},errors.New("Transaction is not found") 370 | } 371 | //对交易输入进行签名 372 | func (bc *Blockchain) SignTransaction(tx *transaction.Transaction,privKey ecdsa.PrivateKey) { 373 | prevTXs := make(map[string]transaction.Transaction) 374 | for _,vin :=range tx.Vin { 375 | prevTX,err := bc.FindTransaction(vin.Txid) //找到输入引用的输出所在的交易 376 | if err != nil { 377 | log.Panic(err) 378 | } 379 | prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX 380 | } 381 | tx.Sign(privKey,prevTXs) 382 | } 383 | 384 | //验证交易 385 | func (bc *Blockchain) VerifyTransaction(tx *transaction.Transaction) bool { 386 | if tx.IsCoinbase() { 387 | return true 388 | } 389 | prevTXs := make(map[string]transaction.Transaction) 390 | 391 | for _, vin := range tx.Vin { 392 | prevTX,err := bc.FindTransaction(vin.Txid) 393 | if err != nil { 394 | log.Panic(err) 395 | } 396 | prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX 397 | } 398 | return tx.Verify(prevTXs) //验证签名 399 | } 400 | --------------------------------------------------------------------------------