current position:Home>A blockchain instance analysis based on POS consensus algorithm (upgraded version)

A blockchain instance analysis based on POS consensus algorithm (upgraded version)

2022-01-31 19:10:21 ReganYue

「 This is my participation 11 The fourth of the yuegengwen challenge 17 God , Check out the activity details :2021 One last more challenge 」.

One is based on PoS Blockchain instance analysis of consensus algorithm ( Upgraded version )

This article is included in my column : Talk about blockchain in detail

This column will talk about the blockchain consensus algorithm and Ethereum smart contract 、 Super ledger smart contract 、EOS Knowledge about smart contracts , Several practical projects will also be introduced in detail . If possible , We can also read the source code of Ethereum together . If you are interested, let's learn blockchain Technology ~

One 、 Preface

Earlier, we briefly introduced a system based on PoS Examples of consensus algorithms , Today, let's analyze an upgraded example . If you like bloggers , Remember the praise. , Focus on , Collection ~

Two 、 Some of the data structures in this example

type Block struct {
    Index     int
    TimeStamp string
    BPM       int
    HashCode  string
    PrevHash  string
    Validator string
}
​
var Blockchain []Block
var tempBlocks []Block
​
var candidateBlocks = make(chan Block)
​
var announcements = make(chan string)
​
var validators = make(map[string]int)
 Copy code 

The first is to define a block structure Block, Then define a blockchain Blockchain, It's actually a block array . This tempBlocks It's a block buffer .candidateBlocks Is a candidate block , When any node proposes a new block , Will send it to this pipeline .announcements It's the channel to broadcast .validators It's a list of verifiers , Save the node address and what he owns tokens.

3、 ... and 、 Generate blocks and calculate hashes

func generateBlock(oldBlock Block, BPM int, address string) Block {
    var newBlock Block
    newBlock.Index = oldBlock.Index + 1
    newBlock.TimeStamp = time.Now().String()
    newBlock.BPM = BPM
    newBlock.PrevHash = oldBlock.HashCode
    newBlock.Validator = address
    newBlock.HashCode = GenerateHashValue(newBlock)
    return newBlock
}
​
func GenerateHashValue(block Block) string {
    var hashcode = block.PrevHash +
        block.TimeStamp + block.Validator +
        strconv.Itoa(block.BPM) + strconv.Itoa(block.Index)
    return calculateHash(hashcode)
}
​
func calculateHash(s string) string {
    var sha = sha256.New()
    sha.Write([]byte(s))
    hashed := sha.Sum(nil)
    return hex.EncodeToString(hashed)
}
 Copy code 

This is really what every previous example is talking about , I really don't want to talk about it here , If you don't understand, you can take a look at the examples in front of this column .

Four 、 Main logic

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }
​
    genesisBlock := Block{}
    genesisBlock = Block{0, time.Now().String(), 0,
        GenerateHashValue(genesisBlock), "", ""}
    spew.Dump(genesisBlock)
​
    Blockchain = append(Blockchain, genesisBlock)
​
    port := os.Getenv("PORT")
​
    server, err := net.Listen("tcp", ":"+port)
    if err != nil {
        log.Fatal(err)
    }
​
    log.Println("HTTP Server Listening on port :", port)
​
    defer server.Close()
​
    go func() {
        for cadidate := range candidateBlocks {
​
            mutex.Lock()
​
            tempBlocks = append(tempBlocks, cadidate)
            mutex.Unlock()
        }
    }()
​
    go func() {
        for {
​
            pickWinner()
        }
    }()
​
    for {
        conn, err := server.Accept()
        if err != nil {
            log.Fatal(err)
        }
        go handleConn(conn)
    }
}
 Copy code 

Let's first look at the main logic , First, load the local .env file , This file can store many parameters , Here we store a port number 9000.

image-20211117174759924

Then create the creation block , Note that the height of the genesis block is 0.

spew.Dump(genesisBlock)
 Copy code 

Is to format the creation block through the command line .

Blockchain = append(Blockchain, genesisBlock)
 Copy code 

This line of code is to add the genesis block to the blockchain .

port := os.Getenv("PORT")
 Copy code 

I said before .env The port number is stored in the file , Here, get the port number in this file to port variable .

Then start the service process to listen to the port obtained above .

defer server.Close()
 Copy code 

To get into the habit of writing delayed shutdown when starting the service , Otherwise, you will forget to release resources later .

Then there are concurrent operations , Cyclic reading candidateBlocks, Once a block of this pipeline enters , Read it into the buffer immediately . Then determine which node should go mining concurrently .

Then continuously receive the connection of the verifier node , Connect and process the information sent by the terminal .

5、 ... and 、 Node for obtaining bookkeeping right

func pickWinner() {
    time.Sleep(30 * time.Second)
    mutex.Lock()
    temp := tempBlocks
    mutex.Unlock()
​
    lotteryPool := []string{}
    if len(temp) > 0 {
    OUTER:
        for _, block := range temp {
            for _, node := range lotteryPool {
                if block.Validator == node {
                    continue OUTER
                }
            }
​
​
            mutex.Lock()
​
            setValidators := validators
            mutex.Unlock()
​
            k, ok := setValidators[block.Validator]
            if ok {
​
                for i := 0; i < k; i++ {
                    lotteryPool = append(lotteryPool, block.Validator)
                }
            }
        }
​
        s := rand.NewSource(time.Now().Unix())
        r := rand.New(s)
​
        lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))]
​
        for _, block := range temp {
            if block.Validator == lotteryWinner {
                mutex.Lock()
                Blockchain = append(Blockchain, block)
                mutex.Unlock()
                for _ = range validators {
                    announcements <- "\nvalidator:" + lotteryWinner + "\n"
                }
                break
            }
        }
​
    }
    mutex.Lock()
    tempBlocks = []Block{}
    mutex.Unlock()
}
 Copy code 

Here is the PoS The essence of , According to the token tokens Quantity to determine the nodes with bookkeeping rights .

First, every time you select a node with bookkeeping rights, you have to rest 30 second , You can't keep choosing .

Before each node with bookkeeping right is selected , Make a partial copy of the block in the buffer , Then manipulate the copy .

We first specify a lottery pool to place the verifier's address .

Then determine whether the buffer is empty , If the buffer copy is not empty , Just traverse the buffer copy , Then if the verifier of the block is in the lottery pool, it continues to traverse , If not, execute the following contents .

Then get a copy of the verifier list , Get the name of the verifier node above that is not in the lottery pool token Number of tokens , Then add as many verifier address strings as the number of tokens to the lottery pool and put them into the lottery pool .

After the lottery pool is filled , Started choosing the lucky ones . Select by random number , Then add the winner's block to the blockchain , Broadcast the winner's block message again .

If the temporary buffer is empty , We'll make it equal to an empty block .

6、 ... and 、 Handle command line requests


func handleConn(conn net.Conn) {

    defer conn.Close()

    go func() {

            msg := <-announcements
            io.WriteString(conn, msg)
        }
    }()

    var address string

    io.WriteString(conn, "Enter token balance:")

    scanBalance := bufio.NewScanner(conn)
    for scanBalance.Scan() {

        balance, err := strconv.Atoi(scanBalance.Text())
        if err != nil {
            log.Printf("%v not a number: %v", scanBalance.Text(), err)
            return
        }

        address = calculateHash(time.Now().String())
   
        validators[address] = balance
        fmt.Println(validators)
        break
    }

    io.WriteString(conn, "\nEnter a new BPM:")

    scanBPM := bufio.NewScanner(conn)
    go func() {

        for {
            for scanBPM.Scan() {
                bmp, err := strconv.Atoi(scanBPM.Text())
                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 := generateBlock(oldLastIndex, bmp, address)
                if err != nil {
                    log.Println(err)
                    continue
                }
     
                if isBlockValid(newBlock, oldLastIndex) {
         
                    candidateBlocks <- newBlock
                }
            }
        }
    }()
​
    for {
        time.Sleep(time.Second * 20)
        mutex.Lock()
        output, err := json.Marshal(Blockchain)
        mutex.Unlock()
        if err != nil {
            log.Fatal(err)
        }
 
        io.WriteString(conn, string(output)+"\n")
    }
​
}
​
func isBlockValid(newBlock, oldBlock Block) bool {
    if oldBlock.Index+1 != newBlock.Index {
        return false
    }
    if oldBlock.HashCode != newBlock.PrevHash {
        return false
    }
    if GenerateHashValue(newBlock) != newBlock.HashCode {
        return false
    }
    return true
}
 Copy code 

First, delay the release of connection resources .

defer conn.Close()
 Copy code 

Then read the message of the lucky one from the pipeline , And output it to the connection conn.

And then receive the information of the node in the command line window tokens Number .

Then generate the verifier's address according to the current time .

address = calculateHash(time.Now().String())
 Copy code 

Then the verifier's address and what he owns tokens Deposit in validators in .

Then enter the transaction information according to the prompt . If the transaction information entered is illegal , Delete the node .

delete(validators, address)
conn.Close()
 Copy code 

The following logic is to take a block , Then generate new block information , Then simply verify whether the block is legal , If it's legal, put the block in candidateBlocks The Conduit , Wait for the lucky ones .

The method to verify whether the block is legal here is simple , This is to verify whether the height of the current block is one of the previous module , Then judge the of the new block PrevHash Is it equal to the hash value of the previous block . Then verify again that the hash value is correct .

7、 ... and 、 Running results

image-20211117202628046

image-20211117202650550

copyright notice
author[ReganYue],Please bring the original link to reprint, thank you.
https://en.netfreeman.com/2022/01/202201311910194907.html

Random recommended