// block.go funcGenesisBlock() *Block { // make a base-tx: generate init coin for the God tx := transaction.BaseTx([]byte("alan")) return CreateBlock([]byte{}, []*transaction.Transaction{tx}) }
共识机制(POW 工作量证明)
网络中所有节点都可以构造区块然后添加到链上,但是这是不可能,因为区块链是一条链表而不是树形结构,所以就需要一种机制来保证每次只能有一个区块产生并添加到链上,并且要让其他所有的节点都认可这个新产生的区块,这就是共识机制。这里采用的是最最最最经典的比特币的共识机制,即工作量证明(Proof Of Work)。
// validate the block: compare with the target func(b *Block) ValidatePoW() bool { var intHash big.Int var intTarget big.Int var hash [32]byte intTarget.SetBytes(b.Target) data := b.GetBase4Nonce(b.Nonce) hash = sha256.Sum256(data) intHash.SetBytes(hash[:]) if intHash.Cmp(&intTarget) == -1 { returntrue } returnfalse }
那么,是否可以不要这样一个可信第三方,区块链就是旨在构建这样一个去中心化的分布式系统。而比特币系统就提出了这样一个模型:UTXO 模型 也就是“追溯历史交易”,要验证“A 转给 B 五块钱”是否有效,我们可以往前查找所有 A 作为接收方的交易记录,然后把这些交易中的金额加起来。如果总金额大于或等于 5,那么这笔转账就是有效的。
// Key: How To Create A Traction ? // get user's all unspent txs(交易图回溯算法) func(bc *BlockChain) FindUnspentTransactions(from []byte) []transaction.Transaction { var unSpentTxs []transaction.Transaction // 用于记录当前用户的未使用交易切片 spentTxs := make(map[string][]int) // 用于标记交易的输出已经被使用(仅仅标记已经被使用),交易ID => {输出的某个索引}
// range blocks in the blockchain for idx := len(bc.Blocks) - 1; idx >= 0; idx-- { // 从新到旧地遍历区块,避免重复访问 block := bc.Blocks[idx] // range txs in the block for _, tx := range block.Transactions { txID := hex.EncodeToString(tx.ID)
IterOutputs: for outIdx, out := range tx.Outputs { // 检查每一个交易的输出(目标:将没有使用过的加入切片) if spentTxs[txID] != nil { // 检查已经使用的输出的前提是,存在已经使用的输出,否则直接到下一个if for _, spentOut := range spentTxs[txID] { // 检查当前交易中的每一个已经使用过的并标记过的输出(索引) if spentOut == outIdx { // 恰好为当前输出 continue IterOutputs // 则不用添加,直接检查下一个输出即可,label语法用于跳出\跳过多层循环 } } } if out.ToAddressRight(from) { // 检查是否是输出到当前用户,否则和当前用户无关 unSpentTxs = append(unSpentTxs, *tx) // 添加到当前用户的未使用交易的切片中 } } if !tx.IsBase() { for _, in := range tx.Inputs { // 检查每一个交易的输入(目标:将每一个源输出标记为已经使用过) if in.FromAddressRight(from) { // 前提是当前用户的源输出,否则和当前用户无关 inTxID := hex.EncodeToString(in.TxId) spentTxs[inTxID] = append(spentTxs[inTxID], in.OutIdx) // 将过去那个输出标记为已经使用 } } } } } return unSpentTxs }
// get user's all unspent-outputs(UTXOs) func(bc *BlockChain) FindUTXOs(address []byte) (int, map[string]int) { unspentOuts := make(map[string]int) // 当前用户所有未使用的输出(交易ID+输出索引 确定一个输出) unspentTxs := bc.FindUnspentTransactions(address) // 当前用户所有未使用的交易 accumulated := 0 Work: for _, tx := range unspentTxs { txID := hex.EncodeToString(tx.ID) for index, out := range tx.Outputs { if out.ToAddressRight(address) { accumulated += out.Value unspentOuts[txID] = index // one transaction can only have one output referred to adderss // so rediect to next tx ,then check its outputs // use lable to cross serveral for continue Work } } } return accumulated, unspentOuts }
// get user's target unspent-outputs(UTXOs) for a tx-amount func(bc *BlockChain) FindSpendableOutputs(address []byte, amount int) (int, map[string]int) { unspentOuts := make(map[string]int) unspentTxs := bc.FindUnspentTransactions(address) accumulated := 0 Work: for _, tx := range unspentTxs { txID := hex.EncodeToString(tx.ID) for index, out := range tx.Outputs { if out.ToAddressRight(address) { accumulated += out.Value unspentOuts[txID] = index // enough if accumulated >= amount { break Work } continue Work } } } return accumulated, unspentOuts }
// create a new tx func(bc *BlockChain) CreateTransaction(from, to []byte, amount int) (*transaction.Transaction, bool) { // 直观上的一笔交易构成:发送者、接收者、数量 // 实际UTXO系统内部的数据结构构成:交易哈希(ID)、若干输入(TxInput)、若干输出(TxOutput)
txInputs := make([]transaction.TxInput, 0) txOutputs := make([]transaction.TxOutput, 0) // 1. build TxInputs: inputs come from outputs(UTXOs) total, unspentOuts := bc.FindSpendableOutputs(from, amount) if total < amount { fmt.Println("Not enough coins!") // return nil,false (this is better actually) return &transaction.Transaction{}, false } for txId, outIndex := range unspentOuts { id, err := hex.DecodeString(txId) utils.Handle(err) txInput := transaction.TxInput{ TxId: id, OutIdx: outIndex, FromAddress: from, } txInputs = append(txInputs, txInput) } // 2. build TxOutputs: output can divde into change-back and sent-amount txOutputs = append(txOutputs, transaction.TxOutput{ Value: amount, ToAddress: to, }) if total > amount { txOutputs = append(txOutputs, transaction.TxOutput{ Value: total - amount, ToAddress: from, }) } // 3. set hash tx := transaction.Transaction{ // ID: []byte{}, ID: nil, Inputs: txInputs, Outputs: txOutputs, } tx.SetID() return &tx, true }