138 lines
3.4 KiB
Go
138 lines
3.4 KiB
Go
package parlia
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
)
|
|
|
|
func TestImpactOfValidatorOutOfService(t *testing.T) {
|
|
testCases := []struct {
|
|
totalValidators int
|
|
downValidators int
|
|
}{
|
|
{3, 1},
|
|
{5, 2},
|
|
{10, 1},
|
|
{10, 4},
|
|
{21, 1},
|
|
{21, 3},
|
|
{21, 5},
|
|
{21, 10},
|
|
}
|
|
for _, tc := range testCases {
|
|
simulateValidatorOutOfService(tc.totalValidators, tc.downValidators)
|
|
}
|
|
}
|
|
|
|
func simulateValidatorOutOfService(totalValidators int, downValidators int) {
|
|
downBlocks := 10000
|
|
recoverBlocks := 10000
|
|
recents := make(map[uint64]int)
|
|
|
|
validators := make(map[int]bool, totalValidators)
|
|
down := make([]int, totalValidators)
|
|
for i := 0; i < totalValidators; i++ {
|
|
validators[i] = true
|
|
down[i] = i
|
|
}
|
|
rand.Shuffle(totalValidators, func(i, j int) {
|
|
down[i], down[j] = down[j], down[i]
|
|
})
|
|
for i := 0; i < downValidators; i++ {
|
|
delete(validators, down[i])
|
|
}
|
|
isRecentSign := func(idx int) bool {
|
|
for _, signIdx := range recents {
|
|
if signIdx == idx {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
isInService := func(idx int) bool {
|
|
return validators[idx]
|
|
}
|
|
|
|
downDelay := time.Duration(0)
|
|
for h := 1; h <= downBlocks; h++ {
|
|
if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit {
|
|
delete(recents, uint64(h)-limit)
|
|
}
|
|
proposer := h % totalValidators
|
|
if !isInService(proposer) || isRecentSign(proposer) {
|
|
candidates := make(map[int]bool, totalValidators/2)
|
|
for v := range validators {
|
|
if !isRecentSign(v) {
|
|
candidates[v] = true
|
|
}
|
|
}
|
|
if len(candidates) == 0 {
|
|
panic("can not test such case")
|
|
}
|
|
idx, delay := producerBlockDelay(candidates, totalValidators)
|
|
downDelay = downDelay + delay
|
|
recents[uint64(h)] = idx
|
|
} else {
|
|
recents[uint64(h)] = proposer
|
|
}
|
|
}
|
|
fmt.Printf("average delay is %v when there is %d validators and %d is down \n",
|
|
downDelay/time.Duration(downBlocks), totalValidators, downValidators)
|
|
|
|
for i := 0; i < downValidators; i++ {
|
|
validators[down[i]] = true
|
|
}
|
|
|
|
recoverDelay := time.Duration(0)
|
|
lastseen := downBlocks
|
|
for h := downBlocks + 1; h <= downBlocks+recoverBlocks; h++ {
|
|
if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit {
|
|
delete(recents, uint64(h)-limit)
|
|
}
|
|
proposer := h % totalValidators
|
|
if !isInService(proposer) || isRecentSign(proposer) {
|
|
lastseen = h
|
|
candidates := make(map[int]bool, totalValidators/2)
|
|
for v := range validators {
|
|
if !isRecentSign(v) {
|
|
candidates[v] = true
|
|
}
|
|
}
|
|
if len(candidates) == 0 {
|
|
panic("can not test such case")
|
|
}
|
|
idx, delay := producerBlockDelay(candidates, totalValidators)
|
|
recoverDelay = recoverDelay + delay
|
|
recents[uint64(h)] = idx
|
|
} else {
|
|
recents[uint64(h)] = proposer
|
|
}
|
|
}
|
|
fmt.Printf("total delay is %v after recover when there is %d validators down ever, last seen not proposer at height %d\n",
|
|
recoverDelay, downValidators, lastseen)
|
|
}
|
|
|
|
func producerBlockDelay(candidates map[int]bool, numOfValidators int) (int, time.Duration) {
|
|
minDur := time.Duration(0)
|
|
minIdx := 0
|
|
wiggle := time.Duration(numOfValidators/2+1) * wiggleTime
|
|
for idx := range candidates {
|
|
sleepTime := rand.Int63n(int64(wiggle))
|
|
if int64(minDur) < sleepTime {
|
|
minDur = time.Duration(rand.Int63n(int64(wiggle)))
|
|
minIdx = idx
|
|
}
|
|
}
|
|
return minIdx, minDur
|
|
}
|
|
|
|
func randomAddress() common.Address {
|
|
addrBytes := make([]byte, 20)
|
|
rand.Read(addrBytes)
|
|
return common.BytesToAddress(addrBytes)
|
|
}
|