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
}