【go共识算法】-POS
pos介绍
pos概念
Proof of Stake,股权证明。
PoS核心概念为币龄,即持有货币的时间。例如有10个币、持有90天,即拥有900币天的币龄。
另外使用币,即意味着币龄的销毁。
在PoS中有一种特殊的交易称为利息币,即持有人可以消耗币龄获得利息,同时获得为网络产生区块、以及PoS造币的优先权。
点点币应用
第一个基于PoS的虚拟币是点点币。鉴于PoW的缺陷,2012年Sunny King提出了PoS,并基于PoW和PoS的混合机制发布了点点币PPCoin。前期采用PoW挖矿开采和分配货币,以保证公平。后期采用PoS机制,保障网络安全,即拥有51%货币难度更大,从而防止51%攻击。
点点币(Peercoin)是首先采用权益证明的货币,点点币在SHA256的哈希运算的难度方面引入了币龄的概念,使得难度与交易输入的币龄成反比。在点点币中,币龄被定义为币的数量与币所拥有的天数的乘积,这使得币龄能够反映交易时刻用户所拥有的货币数量。实际上,点点币的权益证明机制结合了随机化与币龄的概念,未使用至少30天的币可以参与竞争下一区块,越久和越大的币集有更大的可能去签名下一区块。
然而,一旦币的权益被用于签名一个区块,则币龄将清为零,这样必须等待至少30日才能签署另一区块。同时,为防止非常老或非常大的权益控制区块链,寻找下一区块的最大概率在90天后达到最大值,这一过程保护了网络,并随着时间逐渐生成新的币而无需消耗大量的计算能力。点点币的开发者声称这将使得恶意攻击变得困难,因为没有中心化的挖矿池需求,而且购买半数以上的币的开销似乎超过获得51%的工作量证明的哈希计算能力。
优缺点
- 优点: 缩短了共识达成的时间,链中共识块的速度更快,不再需要大量消耗能源挖矿。作弊得不尝失,因为如果一名持有 51% 以上股权的人作弊,相当于他坑了自己,因为他是拥有股权最多的人,作弊导致的结果往往是拥有着越多的损失越多。
- 缺点: 攻击成本低,只有节点有物品数量,例如代币数量,就能发起脏数据的区块攻击,另外拥有代币数量大的节点获得记账权的概率会更大,会使得网络共识受少数富裕账户支配,从而失去公正性。
go实现pos算法
PORT=9000
main.go
package main import ( "bufio" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "log" "math/rand" "net" "os" "strconv" "sync" "time" "github.com/davecgh/go-spew/spew" "github.com/joho/godotenv" ) // Block represents each 'item' in the blockchain type Block struct { Index int Timestamp string BPM int Hash string PrevHash string Validator string } var Blockchain []Block // Blockchain is a series of validated Blocks var tempBlocks []Block // tempBlocks是临时存储单元,在区块被选出来并添加到BlockChain之前,临时存储在这里 // candidateBlocks handles incoming blocks for validation var candidateBlocks = make(chan Block) // announcements broadcasts winning validator to all nodes var announcements = make(chan string) var mutex = &sync.Mutex{} // validators keeps track of open validators and balances var validators = make(map[string]int) func main() { err := godotenv.Load("./test/example.env") if err != nil { log.Fatal(err) } // create genesis block t := time.Now() genesisBlock := Block{} genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""} spew.Dump(genesisBlock) Blockchain = append(Blockchain, genesisBlock) httpPort := os.Getenv("PORT") // start TCP and serve TCP server server, err := net.Listen("tcp", ":"+httpPort) if err != nil { log.Fatal(err) } log.Println("HTTP Server Listening on port :", httpPort) defer server.Close() go func() { for candidate := range candidateBlocks { mutex.Lock() tempBlocks = append(tempBlocks, candidate) mutex.Unlock() } }() go func() { for { pickWinner() } }() for { conn, err := server.Accept() if err != nil { log.Fatal(err) } go handleConn(conn) } } // pickWinner creates a lottery pool of validators and chooses the validator who gets to forge a block to the blockchain // by random selecting from the pool, weighted by amount of tokens staked func pickWinner() { time.Sleep(30 * time.Second) mutex.Lock() temp := tempBlocks mutex.Unlock() lotteryPool := []string{} if len(temp) > 0 { // slightly modified traditional proof of stake algorithm // from all validators who submitted a block, weight them by the number of staked tokens // in traditional proof of stake, validators can participate without submitting a block to be forged OUTER: for _, block := range temp { // if already in lottery pool, skip for _, node := range lotteryPool { if block.Validator == node { continue OUTER } } // lock list of validators to prevent data race mutex.Lock() setValidators := validators mutex.Unlock() k, ok := setValidators[block.Validator] if ok { for i := 0; i < k; i++ { lotteryPool = append(lotteryPool, block.Validator) } } } // randomly pick winner from lottery pool s := rand.NewSource(time.Now().Unix()) r := rand.New(s) lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))] // add block of winner to blockchain and let all the other nodes know for _, block := range temp { if block.Validator == lotteryWinner { mutex.Lock() Blockchain = append(Blockchain, block) mutex.Unlock() for _ = range validators { announcements <- "\nwinning validator: " + lotteryWinner + "\n" } break } } } mutex.Lock() tempBlocks = []Block{} mutex.Unlock() } func handleConn(conn net.Conn) { defer conn.Close() go func() { for { msg := <-announcements io.WriteString(conn, msg) } }() // validator address var address string // tokens数量由用户从控制台输入 io.WriteString(conn, "Enter token balance:") scanBalance := bufio.NewScanner(conn) // 获取用户输入的balance值,并打印出来 for scanBalance.Scan() { balance, err := strconv.Atoi(scanBalance.Text()) if err != nil { log.Printf("%v not a number: %v", scanBalance.Text(), err) return } t := time.Now() address = calculateHash(t.String()) validators[address] = balance fmt.Println(validators) break // 只循环一次 } // 循环输入BPM io.WriteString(conn, "\nEnter a new BPM:") scanBPM := bufio.NewScanner(conn) go func() { for { // take in BPM from stdin and add it to blockchain after conducting necessary validation for scanBPM.Scan() { bpm, err := strconv.Atoi(scanBPM.Text()) // if malicious party tries to mutate the chain with a bad input, delete them as a validator and they lose their staked tokens if err != nil { log.Printf("%v not a number: %v", scanBPM.Text(), err) delete(validators, address) conn.Close() } mutex.Lock() oldLastIndex := Blockchain[len(Blockchain)-1] mutex.Unlock() // 生成新的区块 newBlock, err := generateBlock(oldLastIndex, bpm, address) if err != nil { log.Println(err) continue } if isBlockValid(newBlock, oldLastIndex) { // main func 中 for candidate := range candidateBlocks ,将newBlock 追加到 tempBlocks中 candidateBlocks <- newBlock } io.WriteString(conn, "\nEnter a new BPM:") } } }() // simulate receiving broadcast for { time.Sleep(time.Minute) mutex.Lock() output, err := json.Marshal(Blockchain) mutex.Unlock() if err != nil { log.Fatal(err) } io.WriteString(conn, string(output)+"\n") } } // isBlockValid makes sure block is valid by checking index // and comparing the hash of the previous block func isBlockValid(newBlock, oldBlock Block) bool { if oldBlock.Index+1 != newBlock.Index { return false } if oldBlock.Hash != newBlock.PrevHash { return false } if calculateBlockHash(newBlock) != newBlock.Hash { return false } return true } // SHA256 hasing // calculateHash is a simple SHA256 hashing function func calculateHash(s string) string { h := sha256.New() h.Write([]byte(s)) hashed := h.Sum(nil) return hex.EncodeToString(hashed) } //calculateBlockHash returns the hash of all block information func calculateBlockHash(block Block) string { record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash return calculateHash(record) } // generateBlock creates a new block using previous block's hash func generateBlock(oldBlock Block, BPM int, address string) (Block, error) { var newBlock Block t := time.Now() newBlock.Index = oldBlock.Index + 1 newBlock.Timestamp = t.String() newBlock.BPM = BPM newBlock.PrevHash = oldBlock.Hash newBlock.Hash = calculateBlockHash(newBlock) newBlock.Validator = address return newBlock, nil }