A blockchain instance analysis based on PoS consensus algorithm (upgrade version)

Author: ReganYue

Source: Hang Seng LIGHT Cloud Community

I. Introduction

Earlier we briefly introduced an example based on the PoS consensus algorithm. Today we will analyze an example of an upgraded version. If you like the blogger, remember to like, follow, and favorite~

2. Some 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)

The first is to define a block structure Block, and then define a blockchain Blockchain, which is actually an array of blocks. This tempBlocks is the block buffer. candidateBlocks are candidate blocks, and when any node proposes a new block, it will be sent to this pipeline. announcements is the channel to broadcast. validators is a list of validators, storing the node address and the tokens he owns.

3. 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)
}

This is really talked about in every previous example. I really don’t want to talk about it here. For those who don’t understand it, you can take a look at the examples in the front of this column.

Fourth, the 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)
    }
}

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

image-20211117174759924

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

spew.Dump(genesisBlock)

It is to format the output of the genesis block through the command line.

Blockchain = append(Blockchain, genesisBlock)

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

port := os.Getenv("PORT")

As mentioned earlier, the port number is stored in the .env file. Here, the port number in this file is obtained into the port variable.

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

defer server.Close()

It is necessary to develop the habit of writing delayed shutdown when starting the service, otherwise you will forget to release resources later.

Then there is the concurrent operation, which reads the candidateBlocks in a loop, and as soon as this pipeline has a block in, it immediately reads it into the buffer. Then concurrently determine which node should mine.

Then it continuously receives the connection of the validator node, and when connected, the information sent by the terminal is processed.

5. The node that obtains the accounting 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()
}

This is the essence of PoS. The node with the accounting right is determined according to the number of tokens.

First, every time a node with accounting rights is selected, it has to rest for 30 seconds.

Each time before selecting a node with accounting rights, copy a part of the block in the buffer, and then operate the copy.

We start by declaring a lottery pool to hold validator addresses.

Then judge whether the buffer is empty. If the buffer copy is not empty, traverse the buffer copy, and then continue to traverse if the validator of the block is in the lottery pool, and execute the following content if not.

Then get a copy of the validator list, get the number of token tokens of the validator nodes that are not in the lottery pool, and then add as many validator address strings to the lottery pool as the number of tokens into the lottery pool.

Once the lottery pool is filled, the lucky draw begins. It is selected by random numbers, then the winner's block is added to the blockchain, and then the winner's block message is broadcast.

If the temporary buffer is empty, we will make it equal to an empty block.

6. Handling command line requests

func handleConn(conn net.Conn) {

	defer conn.Close()

	go func() {
		for {
			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
}

The first is to delay the release of connection resources.

defer conn.Close()

The lucky winner is then read from the pipe and output to the connection conn.

Then receive the number of tokens for the node in the command line window.

The validator's address is then generated based on the current time.

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

Then save the validator address and the tokens he owns into validators.

Then enter the transaction information according to the prompts. If the entered transaction information is illegal, the node will be deleted.

delete(validators, address)
conn.Close()

The logic after that is to take the previous block, then generate new block information, and then simply verify whether the block is legal. If it is legal, put the block into the candidateBlocks pipeline and wait for the lucky ones to be drawn.

The method to verify whether the block is legal here is very simple, that is, to verify whether the height of the current block is increased by one from the previous block, and then to determine whether the PrevHash of the new block is equal to the hash value of the previous block. Then check again whether the hash value is correct.

7. Running results

image-20211117202628046


Want to learn more from the tech giants? Where are the problems encountered in development discussed? How to obtain the massive resources of financial technology?

Hang Seng LIGHT Cloud Community , a professional financial technology community platform built by Hang Seng Electronics, shares practical technical dry goods, resource data, financial technology industry trends, and embraces all financial developers.

Scan the QR code of the applet below to join us!

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324134824&siteId=291194637