mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-21 06:04:33 +00:00
commit
22479f11ff
99 changed files with 10111 additions and 32486 deletions
50
README.md
50
README.md
|
|
@ -1 +1,51 @@
|
|||
# XDPoSChain
|
||||
## XinFin Hybrid Blockchain
|
||||
|
||||
XinFin Hybrid Blockchain is an Enterprise ready Blockchain for global trade and finance
|
||||
|
||||
Visit: [XinFin.org](https://xinfin.org)
|
||||
Contribute: [Developer Docs](https://docs.xinfin.org)
|
||||
|
||||
## XinFin Network XDPoS is community driven project to achieve the following
|
||||
|
||||
- XinFin DPOS (XDPoS) consensus that selects 108 set of Masternodes to achieve a high throughput Energy efficient consensus with instant block finality
|
||||
|
||||
- KYC Enforcement on Masternodes for Enterprise Adoption and compliance
|
||||
|
||||
- Ability to port/relay limited set of data and transactions from privacy channels to public channel
|
||||
|
||||
- Interoperability between applications hosted on Private Blockchains like Corda, Hyperledger, Quorum(JP Morgan) using relayers to XinFin Network
|
||||
|
||||
- Customer Centric and consortium driven Governance to equally benefit the validators as well as providing comfort for large scale enterprise applications to be hosted on the Network. This achieves
|
||||
|
||||
- Rapid Upgradability
|
||||
|
||||
- DApps Standardisation for rapid commercialisation
|
||||
|
||||
- Compliance with major global jurisdictions.
|
||||
|
||||
### KYC for masternodes
|
||||
|
||||
#### OVERVIEW
|
||||
|
||||
To add a layer of KYC for masternodes in the current system and a sense of ownership amongst the masternodes hence tying such a cluster of masternodes to physical entity which can held accountable for its actions.
|
||||
|
||||
#### Design
|
||||
|
||||
We established a bidirectional connection between a candidate and its owner inorder to retrieve a candidate belonging to a specific owner & vice versa.
|
||||
|
||||
All the masternodes are recognized by the KYC of their owners and hence are considered as a single verified entity ( for eg. while voting for invalid KYC, only one vote is considered per such cluster )
|
||||
|
||||
The contract is very strict in handing out penalty for invalid KYC, it results loss of all funds invested in all of its candidates.
|
||||
|
||||
For eg. say A proposes condidates B,C,D by paying for its proposal cost.
|
||||
If at a later stage if some predecided amount of owners ( investors ) vote that a KYC for a A is invalid then A & all of its candidates (B,C,D) will lose their position & all their funds will be lost ( will remain with contract wallet ).
|
||||
|
||||
### For developers
|
||||
|
||||
### To contribute
|
||||
|
||||
Simple create a pull request along with proper reasoning, we'll get back to you.
|
||||
|
||||
Our Channels : [Telegram Developer Group](https://t.me/XinFinDevelopers) or [Public Slack Group](https://launchpass.com/xinfin-public)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -29,7 +31,9 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
|
|
@ -69,6 +73,28 @@ type SimulatedBackend struct {
|
|||
config *params.ChainConfig
|
||||
}
|
||||
|
||||
func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) {
|
||||
veryLightScryptN := 2
|
||||
veryLightScryptP := 1
|
||||
dir, _ := ioutil.TempDir("", "eth-SimulateWalletAddressAndSignFn-test")
|
||||
|
||||
new := func(kd string) *keystore.KeyStore {
|
||||
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
ks := new(dir)
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.NewAccount(pass)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
if err := ks.Unlock(a1, ""); err != nil {
|
||||
return a1.Address, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return a1.Address, ks.SignHash, nil
|
||||
}
|
||||
|
||||
// XDC simulated backend for testing purpose.
|
||||
func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
|
||||
// database := ethdb.NewMemDatabase()
|
||||
|
|
|
|||
|
|
@ -19,13 +19,15 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/console"
|
||||
|
|
@ -207,6 +209,12 @@ func importChain(ctx *cli.Context) error {
|
|||
if len(ctx.Args()) < 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
}
|
||||
|
||||
// Start metrics export if enabled
|
||||
utils.SetupMetrics(ctx)
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
|
||||
stack, _ := makeFullNode(ctx)
|
||||
chain, chainDb := utils.MakeChain(ctx, stack)
|
||||
defer chainDb.Close()
|
||||
|
|
@ -368,7 +376,7 @@ func copyDb(ctx *cli.Context) error {
|
|||
chain, chainDb := utils.MakeChain(ctx, stack)
|
||||
|
||||
syncmode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode)
|
||||
dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil)
|
||||
dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil, nil)
|
||||
|
||||
// Create a source peer to satisfy downloader requests from
|
||||
db, err := rawdb.NewLevelDBDatabase(ctx.Args().First(), ctx.GlobalInt(utils.CacheFlag.Name), 256, "")
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func TestConsoleWelcome(t *testing.T) {
|
|||
XDC.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||
XDC.SetTemplateFunc("gover", runtime.Version)
|
||||
XDC.SetTemplateFunc("XDCver", func() string { return params.Version })
|
||||
XDC.SetTemplateFunc("niltime", func() string { return time.Unix(1544771829, 0).Format(time.RFC1123) })
|
||||
XDC.SetTemplateFunc("niltime", func() string { return time.Unix(1559211559, 0).Format(time.RFC1123) })
|
||||
XDC.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||
|
||||
// Verify the actual welcome message to the required template
|
||||
|
|
@ -137,7 +137,7 @@ func testAttachWelcome(t *testing.T, XDC *testXDC, endpoint, apis string) {
|
|||
attach.SetTemplateFunc("gover", runtime.Version)
|
||||
attach.SetTemplateFunc("XDCver", func() string { return params.Version })
|
||||
attach.SetTemplateFunc("etherbase", func() string { return XDC.Etherbase })
|
||||
attach.SetTemplateFunc("niltime", func() string { return time.Unix(1544771829, 0).Format(time.RFC1123) })
|
||||
attach.SetTemplateFunc("niltime", func() string { return time.Unix(1559211559, 0).Format(time.RFC1123) })
|
||||
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||
attach.SetTemplateFunc("datadir", func() string { return XDC.Datadir })
|
||||
attach.SetTemplateFunc("apis", func() string { return apis })
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ var (
|
|||
utils.RPCVirtualHostsFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.MetricsEnabledFlag,
|
||||
utils.MetricsHTTPFlag,
|
||||
utils.MetricsPortFlag,
|
||||
//utils.FakePoWFlag,
|
||||
//utils.NoCompactionFlag,
|
||||
//utils.GpoBlocksFlag,
|
||||
|
|
@ -290,6 +292,11 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
|
|||
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
||||
utils.Fatalf("Light clients do not support staking")
|
||||
}
|
||||
// Start metrics export if enabled
|
||||
utils.SetupMetrics(ctx)
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
|
||||
var ethereum *eth.Ethereum
|
||||
if err := stack.Service(ðereum); err != nil {
|
||||
utils.Fatalf("Ethereum service not running: %v", err)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||
"github.com/XinFinOrg/XDPoSChain/metrics/exp"
|
||||
"github.com/XinFinOrg/XDPoSChain/node"
|
||||
"github.com/XinFinOrg/XDPoSChain/p2p"
|
||||
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
|
||||
|
|
@ -355,6 +356,20 @@ var (
|
|||
Name: "ethstats",
|
||||
Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)",
|
||||
}
|
||||
// MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint.
|
||||
// Since the pprof service enables sensitive/vulnerable behavior, this allows a user
|
||||
// to enable a public-OK metrics endpoint without having to worry about ALSO exposing
|
||||
// other profiling behavior or information.
|
||||
MetricsHTTPFlag = cli.StringFlag{
|
||||
Name: "metrics.addr",
|
||||
Usage: "Enable stand-alone metrics HTTP server listening interface",
|
||||
Value: metrics.DefaultConfig.HTTP,
|
||||
}
|
||||
MetricsPortFlag = cli.IntFlag{
|
||||
Name: "metrics.port",
|
||||
Usage: "Metrics HTTP server listening port",
|
||||
Value: metrics.DefaultConfig.Port,
|
||||
}
|
||||
MetricsEnabledFlag = cli.BoolFlag{
|
||||
Name: metrics.MetricsEnabledFlag,
|
||||
Usage: "Enable metrics collection and reporting",
|
||||
|
|
@ -1330,3 +1345,15 @@ func WalkMatch(root, pattern string) ([]string, error) {
|
|||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func SetupMetrics(ctx *cli.Context) {
|
||||
if metrics.Enabled {
|
||||
log.Info("Enabling metrics collection")
|
||||
|
||||
if ctx.GlobalIsSet(MetricsHTTPFlag.Name) {
|
||||
address := fmt.Sprintf("%s:%d", ctx.GlobalString(MetricsHTTPFlag.Name), ctx.GlobalInt(MetricsPortFlag.Name))
|
||||
log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address)
|
||||
exp.Setup(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const (
|
|||
MaxMasternodes = 18
|
||||
MaxMasternodesV2 = 108
|
||||
LimitPenaltyEpoch = 4
|
||||
LimitPenaltyEpochV2 = 0
|
||||
BlocksPerYearTest = uint64(200000)
|
||||
BlocksPerYear = uint64(15768000)
|
||||
LimitThresholdNonceInQueue = 10
|
||||
|
|
|
|||
87
common/countdown/countdown.go
Normal file
87
common/countdown/countdown.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// A countdown timer that will mostly be used by XDPoS v2 consensus engine
|
||||
package countdown
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
type CountdownTimer struct {
|
||||
lock sync.RWMutex // Protects the Initilised field
|
||||
resetc chan int
|
||||
quitc chan chan struct{}
|
||||
initilised bool
|
||||
timeoutDuration time.Duration
|
||||
// Triggered when the countdown timer timeout for the `timeoutDuration` period, it will pass current timestamp to the callback function
|
||||
OnTimeoutFn func(time time.Time, i interface{}) error
|
||||
}
|
||||
|
||||
func NewCountDown(duration time.Duration) *CountdownTimer {
|
||||
return &CountdownTimer{
|
||||
resetc: make(chan int),
|
||||
quitc: make(chan chan struct{}),
|
||||
initilised: false,
|
||||
timeoutDuration: duration,
|
||||
}
|
||||
}
|
||||
|
||||
// Completely stop the countdown timer from running.
|
||||
func (t *CountdownTimer) StopTimer() {
|
||||
q := make(chan struct{})
|
||||
t.quitc <- q
|
||||
<-q
|
||||
}
|
||||
|
||||
// Reset will start the countdown timer if it's already stopped, or simply reset the countdown time back to the defual `duration`
|
||||
func (t *CountdownTimer) Reset(i interface{}) {
|
||||
if !t.isInitilised() {
|
||||
t.setInitilised(true)
|
||||
go t.startTimer(i)
|
||||
} else {
|
||||
t.resetc <- 0
|
||||
}
|
||||
}
|
||||
|
||||
// A long running process that
|
||||
func (t *CountdownTimer) startTimer(i interface{}) {
|
||||
// Make sure we mark Initilised to false when we quit the countdown
|
||||
defer t.setInitilised(false)
|
||||
timer := time.NewTimer(t.timeoutDuration)
|
||||
// We start with a inf loop
|
||||
for {
|
||||
select {
|
||||
case q := <-t.quitc:
|
||||
log.Debug("Quit countdown timer")
|
||||
close(q)
|
||||
return
|
||||
case <-timer.C:
|
||||
log.Debug("Countdown time reached!")
|
||||
go func() {
|
||||
err := t.OnTimeoutFn(time.Now(), i)
|
||||
if err != nil {
|
||||
log.Error("OnTimeoutFn error", "error", err)
|
||||
}
|
||||
log.Debug("OnTimeoutFn processed")
|
||||
}()
|
||||
timer.Reset(t.timeoutDuration)
|
||||
case <-t.resetc:
|
||||
log.Debug("Reset countdown timer")
|
||||
timer.Reset(t.timeoutDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the desired value to Initilised with lock to avoid race condition
|
||||
func (t *CountdownTimer) setInitilised(value bool) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.initilised = value
|
||||
}
|
||||
|
||||
func (t *CountdownTimer) isInitilised() bool {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
return t.initilised
|
||||
}
|
||||
157
common/countdown/countdown_test.go
Normal file
157
common/countdown/countdown_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package countdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCountdownWillCallback(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown := NewCountDown(1000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
countdown.Reset(fakeI)
|
||||
<-called
|
||||
t.Log("Times up, successfully called OnTimeoutFn")
|
||||
}
|
||||
|
||||
func TestCountdownShouldReset(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
// Check countdown did not start
|
||||
assert.False(t, countdown.isInitilised())
|
||||
countdown.Reset(fakeI)
|
||||
// Now the countdown should already started
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
|
||||
resetTimer := time.NewTimer(4000 * time.Millisecond)
|
||||
|
||||
firstReset:
|
||||
for {
|
||||
select {
|
||||
case <-called:
|
||||
if time.Now().After(expectedCalledTime) {
|
||||
// Make sure the countdown runs forever
|
||||
assert.True(t, countdown.isInitilised())
|
||||
t.Log("Correctly reset the countdown once")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly first time")
|
||||
}
|
||||
break firstReset
|
||||
case <-resetTimer.C:
|
||||
countdown.Reset(fakeI)
|
||||
}
|
||||
}
|
||||
|
||||
// Now the countdown is paused after calling the callback function, let's reset it again
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedTimeAfterReset := time.Now().Add(5000 * time.Millisecond)
|
||||
<-called
|
||||
// Always initilised
|
||||
assert.True(t, countdown.isInitilised())
|
||||
if time.Now().After(expectedTimeAfterReset) {
|
||||
t.Log("Correctly reset the countdown second time")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly second time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountdownShouldResetEvenIfErrored(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return fmt.Errorf("ERROR!")
|
||||
}
|
||||
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
// Check countdown did not start
|
||||
assert.False(t, countdown.isInitilised())
|
||||
countdown.Reset(fakeI)
|
||||
// Now the countdown should already started
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
|
||||
resetTimer := time.NewTimer(4000 * time.Millisecond)
|
||||
|
||||
firstReset:
|
||||
for {
|
||||
select {
|
||||
case <-called:
|
||||
if time.Now().After(expectedCalledTime) {
|
||||
// Make sure the countdown runs forever
|
||||
assert.True(t, countdown.isInitilised())
|
||||
t.Log("Correctly reset the countdown once")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly first time")
|
||||
}
|
||||
break firstReset
|
||||
case <-resetTimer.C:
|
||||
countdown.Reset(fakeI)
|
||||
}
|
||||
}
|
||||
|
||||
// Now the countdown is paused after calling the callback function, let's reset it again
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedTimeAfterReset := time.Now().Add(5000 * time.Millisecond)
|
||||
<-called
|
||||
// Always initilised
|
||||
assert.True(t, countdown.isInitilised())
|
||||
if time.Now().After(expectedTimeAfterReset) {
|
||||
t.Log("Correctly reset the countdown second time")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly second time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountdownShouldBeAbleToStop(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
// Check countdown did not start
|
||||
assert.False(t, countdown.isInitilised())
|
||||
countdown.Reset(fakeI)
|
||||
// Now the countdown should already started
|
||||
assert.True(t, countdown.isInitilised())
|
||||
// Try manually stop the timer before it triggers the callback
|
||||
stopTimer := time.NewTimer(4000 * time.Millisecond)
|
||||
<-stopTimer.C
|
||||
countdown.StopTimer()
|
||||
assert.False(t, countdown.isInitilised())
|
||||
}
|
||||
|
||||
func TestCountdownShouldAvoidDeadlock(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
countdown.Reset(fakeI)
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
countdown.Reset(fakeI)
|
||||
<-called
|
||||
}
|
||||
|
|
@ -279,19 +279,23 @@ func (a UnprefixedAddress) MarshalText() ([]byte, error) {
|
|||
|
||||
// Extract validators from byte array.
|
||||
func RemoveItemFromArray(array []Address, items []Address) []Address {
|
||||
// Create newArray to stop append change array value
|
||||
newArray := make([]Address, len(array))
|
||||
copy(newArray, array)
|
||||
|
||||
if len(items) == 0 {
|
||||
return array
|
||||
return newArray
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
for i := len(array) - 1; i >= 0; i-- {
|
||||
if array[i] == item {
|
||||
array = append(array[:i], array[i+1:]...)
|
||||
for i := len(newArray) - 1; i >= 0; i-- {
|
||||
if newArray[i] == item {
|
||||
newArray = append(newArray[:i], newArray[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array
|
||||
return newArray
|
||||
}
|
||||
|
||||
// Extract validators from byte array.
|
||||
|
|
|
|||
|
|
@ -158,8 +158,12 @@ func BenchmarkAddressHex(b *testing.B) {
|
|||
func TestRemoveItemInArray(t *testing.T) {
|
||||
array := []Address{HexToAddress("0x0000003"), HexToAddress("0x0000001"), HexToAddress("0x0000002"), HexToAddress("0x0000003")}
|
||||
remove := []Address{HexToAddress("0x0000002"), HexToAddress("0x0000004"), HexToAddress("0x0000003")}
|
||||
array = RemoveItemFromArray(array, remove)
|
||||
if len(array) != 1 {
|
||||
newArray := RemoveItemFromArray(array, remove)
|
||||
|
||||
if array[0] != HexToAddress("0x0000003") || array[2] != HexToAddress("0x0000002") {
|
||||
t.Error("should keep the original item from array address")
|
||||
}
|
||||
if len(newArray) != 1 {
|
||||
t.Error("fail remove item from array address")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/event"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/clique"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
|
|
@ -35,8 +36,13 @@ import (
|
|||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
func SigHash(header *types.Header) (hash common.Hash) {
|
||||
return utils.SigHash(header)
|
||||
func (x *XDPoS) SigHash(header *types.Header) (hash common.Hash) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.SignHash(header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.SigHash(header)
|
||||
}
|
||||
}
|
||||
|
||||
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
|
||||
|
|
@ -48,34 +54,53 @@ type XDPoS struct {
|
|||
// Transaction cache, only make sense for adaptor level
|
||||
signingTxsCache *lru.Cache
|
||||
|
||||
// Share Channel
|
||||
WaitPeriodCh chan int // Miner wait Period Channel
|
||||
|
||||
// Trading and lending service
|
||||
GetXDCXService func() utils.TradingService
|
||||
GetLendingService func() utils.LendingService
|
||||
|
||||
// The exact consensus engine with different versions
|
||||
EngineV1 engine_v1.XDPoS_v1
|
||||
EngineV2 engine_v2.XDPoS_v2
|
||||
EngineV1 *engine_v1.XDPoS_v1
|
||||
EngineV2 *engine_v2.XDPoS_v2
|
||||
}
|
||||
|
||||
// Subscribe to consensus engines forensics events. Currently only exist for engine v2
|
||||
func (x *XDPoS) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription {
|
||||
return x.EngineV2.ForensicsProcessor.SubscribeForensicsEvent(ch)
|
||||
}
|
||||
|
||||
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
|
||||
// signers set to the ones provided by the user.
|
||||
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS {
|
||||
log.Info("[New] initial conensus engines")
|
||||
// Set any missing consensus parameters to their defaults
|
||||
conf := *config
|
||||
if conf.Epoch == 0 {
|
||||
conf.Epoch = utils.EpochLength
|
||||
if config.Epoch == 0 {
|
||||
config.Epoch = utils.EpochLength
|
||||
}
|
||||
|
||||
// For testing and testing project, default to mainnet config
|
||||
if config.V2 == nil {
|
||||
config.V2 = params.XDPoSV2Config
|
||||
}
|
||||
|
||||
log.Info("xdc config loading", "config", config)
|
||||
|
||||
waitPeriodCh := make(chan int)
|
||||
|
||||
// Allocate the snapshot caches and create the engine
|
||||
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
||||
|
||||
return &XDPoS{
|
||||
config: &conf,
|
||||
config: config,
|
||||
db: db,
|
||||
|
||||
WaitPeriodCh: waitPeriodCh,
|
||||
|
||||
signingTxsCache: signingTxsCache,
|
||||
EngineV1: *engine_v1.New(&conf, db),
|
||||
EngineV2: *engine_v2.New(&conf, db),
|
||||
EngineV1: engine_v1.New(config, db),
|
||||
EngineV2: engine_v2.New(config, db, waitPeriodCh),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +114,8 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
|
|||
conf = chainConfig.XDPoS
|
||||
}
|
||||
|
||||
waitPeriodCh := make(chan int)
|
||||
|
||||
// Allocate the snapshot caches and create the engine
|
||||
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
||||
|
||||
|
|
@ -96,13 +123,27 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
|
|||
config: conf,
|
||||
db: db,
|
||||
|
||||
WaitPeriodCh: waitPeriodCh,
|
||||
|
||||
GetXDCXService: func() utils.TradingService { return nil },
|
||||
GetLendingService: func() utils.LendingService { return nil },
|
||||
|
||||
signingTxsCache: signingTxsCache,
|
||||
EngineV1: *engine_v1.NewFaker(db, conf),
|
||||
EngineV2: *engine_v2.NewFaker(db, conf),
|
||||
EngineV1: engine_v1.NewFaker(db, conf),
|
||||
EngineV2: engine_v2.New(conf, db, waitPeriodCh),
|
||||
}
|
||||
return fakeEngine
|
||||
}
|
||||
|
||||
func (x *XDPoS) Initial(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Initial(chain, header)
|
||||
default: // Default "v1"
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Eth Consensus engine interface implementation
|
||||
*/
|
||||
|
|
@ -131,6 +172,8 @@ func (x *XDPoS) Author(header *types.Header) (common.Address, error) {
|
|||
// VerifyHeader checks whether a header conforms to the consensus rules.
|
||||
func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.VerifyHeader(chain, header, fullVerify)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.VerifyHeader(chain, header, fullVerify)
|
||||
}
|
||||
|
|
@ -140,14 +183,38 @@ func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header,
|
|||
// method returns a quit channel to abort the operations and a results channel to
|
||||
// retrieve the async verifications (the order is that of the input slice).
|
||||
func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) {
|
||||
// TODO: (Hashlab) This funciton is a special case
|
||||
return x.EngineV1.VerifyHeaders(chain, headers, fullVerifies)
|
||||
abort := make(chan struct{})
|
||||
results := make(chan error, len(headers))
|
||||
|
||||
// Split the headers list into v1 and v2 buckets
|
||||
var v1headers []*types.Header
|
||||
var v2headers []*types.Header
|
||||
|
||||
for _, header := range headers {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
v2headers = append(v2headers, header)
|
||||
default: // Default "v1"
|
||||
v1headers = append(v1headers, header)
|
||||
}
|
||||
}
|
||||
|
||||
if v1headers != nil {
|
||||
x.EngineV1.VerifyHeaders(chain, v1headers, fullVerifies, abort, results)
|
||||
}
|
||||
if v2headers != nil {
|
||||
x.EngineV2.VerifyHeaders(chain, v2headers, fullVerifies, abort, results)
|
||||
}
|
||||
|
||||
return abort, results
|
||||
}
|
||||
|
||||
// VerifyUncles implements consensus.Engine, always returning an error for any
|
||||
// uncles as this consensus mechanism doesn't permit uncles.
|
||||
func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
||||
switch x.config.BlockConsensusVersion(block.Number()) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.VerifyUncles(chain, block)
|
||||
}
|
||||
|
|
@ -157,6 +224,8 @@ func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) er
|
|||
// in the header satisfies the consensus protocol requirements.
|
||||
func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.VerifySeal(chain, header)
|
||||
}
|
||||
|
|
@ -166,6 +235,8 @@ func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) er
|
|||
// header for running the transactions on top.
|
||||
func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Prepare(chain, header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.Prepare(chain, header)
|
||||
}
|
||||
|
|
@ -175,6 +246,8 @@ func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error
|
|||
// rewards given, and returns the final block.
|
||||
func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts)
|
||||
}
|
||||
|
|
@ -184,6 +257,8 @@ func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, stat
|
|||
// the local signing credentials.
|
||||
func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
|
||||
switch x.config.BlockConsensusVersion(block.Number()) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Seal(chain, block, stop)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.Seal(chain, block, stop)
|
||||
}
|
||||
|
|
@ -194,11 +269,22 @@ func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha
|
|||
// current signer.
|
||||
func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||
switch x.config.BlockConsensusVersion(parent.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.CalcDifficulty(chain, time, parent)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.CalcDifficulty(chain, time, parent)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) HandleProposedBlock(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.ProposedBlockHandler(chain, header)
|
||||
default: // Default "v1"
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
XDC specific methods
|
||||
*/
|
||||
|
|
@ -208,28 +294,49 @@ func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent
|
|||
func (x *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) {
|
||||
// Authorize each consensus individually
|
||||
x.EngineV1.Authorize(signer, signFn)
|
||||
x.EngineV2.Authorize(signer, signFn)
|
||||
}
|
||||
|
||||
func (x *XDPoS) GetPeriod() uint64 {
|
||||
return x.config.Period
|
||||
}
|
||||
|
||||
func (x *XDPoS) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool {
|
||||
func (x *XDPoS) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.IsAuthorisedAddress(chain, header, address)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.IsAuthorisedAddress(header, chain, address)
|
||||
return x.EngineV1.IsAuthorisedAddress(chain, header, address)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetMasternodes(chain, header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetMasternodes(chain, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
||||
switch x.config.BlockConsensusVersion(parent.Number) {
|
||||
func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber uint64) []common.Address {
|
||||
blockHeader := chain.GetHeaderByNumber(blockNumber)
|
||||
if blockHeader == nil {
|
||||
log.Error("[GetMasternodesByNumber] Unable to find block", "Num", blockNumber)
|
||||
return []common.Address{}
|
||||
}
|
||||
switch x.config.BlockConsensusVersion(big.NewInt(int64(blockNumber))) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetMasternodes(chain, blockHeader)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetMasternodes(chain, blockHeader)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
|
||||
switch x.config.BlockConsensusVersion(big.NewInt(parent.Number.Int64() + 1)) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.YourTurn(chain, parent, signer)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.YourTurn(chain, parent, signer)
|
||||
}
|
||||
|
|
@ -237,13 +344,16 @@ func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, sign
|
|||
|
||||
func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
default: // Default "v1"
|
||||
default: // Default "v1", v2 does not need this function
|
||||
return x.EngineV1.GetValidator(creator, chain, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
|
||||
// fmt.Println("UpdateMasternodes")
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.UpdateMasternodes(chain, header, ms)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.UpdateMasternodes(chain, header, ms)
|
||||
}
|
||||
|
|
@ -251,6 +361,8 @@ func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Hea
|
|||
|
||||
func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return common.Address{}, nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.RecoverSigner(header)
|
||||
}
|
||||
|
|
@ -258,16 +370,39 @@ func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) {
|
|||
|
||||
func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return common.Address{}, nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.RecoverValidator(header)
|
||||
}
|
||||
}
|
||||
|
||||
// Get master nodes over extra data of previous checkpoint block.
|
||||
func (x *XDPoS) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
|
||||
switch x.config.BlockConsensusVersion(preCheckpointHeader.Number) {
|
||||
func (x *XDPoS) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
switch x.config.BlockConsensusVersion(checkpointHeader.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetMasternodesFromEpochSwitchHeader(checkpointHeader)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetMasternodesFromCheckpointHeader(preCheckpointHeader, n, e)
|
||||
return x.EngineV1.GetMasternodesFromCheckpointHeader(checkpointHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// Check is epoch switch (checkpoint) block
|
||||
func (x *XDPoS) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.IsEpochSwitch(header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.IsEpochSwitch(header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNumber *big.Int) (uint64, uint64, error) {
|
||||
switch x.config.BlockConsensusVersion(blockNumber) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetCurrentEpochSwitchBlock(chain, blockNumber)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetCurrentEpochSwitchBlock(blockNumber)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,6 +413,13 @@ func (x *XDPoS) GetDb() ethdb.Database {
|
|||
|
||||
func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
sp, err := x.EngineV2.GetSnapshot(chain, header)
|
||||
return &utils.PublicApiSnapshot{
|
||||
Number: sp.Number,
|
||||
Hash: sp.Hash,
|
||||
Signers: sp.GetMappedMasterNodes(),
|
||||
}, err
|
||||
default: // Default "v1"
|
||||
sp, err := x.EngineV1.GetSnapshot(chain, header)
|
||||
// Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API
|
||||
|
|
@ -294,11 +436,26 @@ func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (
|
|||
|
||||
func (x *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return []common.Address{}, nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetAuthorisedSignersFromSnapshot(chain, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) FindParentBlockToAssign(chain consensus.ChainReader, currentBlock *types.Block) *types.Block {
|
||||
switch x.config.BlockConsensusVersion(currentBlock.Number()) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
block := x.EngineV2.FindParentBlockToAssign(chain)
|
||||
if block == nil {
|
||||
return currentBlock
|
||||
}
|
||||
return block
|
||||
default: // Default "v1"
|
||||
return currentBlock
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Caching
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -91,6 +91,11 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
|
|||
return api.XDPoS.GetAuthorisedSignersFromSnapshot(api.chain, header)
|
||||
}
|
||||
|
||||
// Get the latest v2 committed block information. Note: This only applies to v2 engine. it doesn't make sense for v1
|
||||
func (api *API) GetLatestCommittedBlockHeader() *types.BlockInfo {
|
||||
return api.XDPoS.EngineV2.GetLatestCommittedBlockInfo()
|
||||
}
|
||||
|
||||
func (api *API) NetworkInformation() NetworkInformation {
|
||||
info := NetworkInformation{}
|
||||
info.NetworkId = api.chain.Config().ChainId
|
||||
|
|
|
|||
|
|
@ -28,30 +28,12 @@ import (
|
|||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// ecrecover extracts the Ethereum account address from a signed header.
|
||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||
// If the signature's already cached, return that
|
||||
hash := header.Hash()
|
||||
if address, known := sigcache.Get(hash); known {
|
||||
return address.(common.Address), nil
|
||||
}
|
||||
// Retrieve the signature from the header extra-data
|
||||
if len(header.Extra) < utils.ExtraSeal {
|
||||
return common.Address{}, utils.ErrMissingSignature
|
||||
}
|
||||
signature := header.Extra[len(header.Extra)-utils.ExtraSeal:]
|
||||
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), signature)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
|
||||
sigcache.Add(hash, signer)
|
||||
return signer, nil
|
||||
}
|
||||
const (
|
||||
// timeout waiting for M1
|
||||
waitPeriod = 10
|
||||
// timeout for checkpoint.
|
||||
waitPeriodCheckpoint = 20
|
||||
)
|
||||
|
||||
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
|
||||
// Ethereum testnet following the Ropsten attacks.
|
||||
|
|
@ -78,6 +60,23 @@ type XDPoS_v1 struct {
|
|||
HookGetSignersFromContract func(blockHash common.Hash) ([]common.Address, error)
|
||||
}
|
||||
|
||||
/* V1 Block
|
||||
SignerFn is a signer callback function to request a hash to be signed by a
|
||||
backing account.
|
||||
type SignerFn func(accounts.Account, []byte) ([]byte, error)
|
||||
|
||||
sigHash returns the hash which is used as input for the delegated-proof-of-stake
|
||||
signing. It is the hash of the entire header apart from the 65 byte signature
|
||||
contained at the end of the extra data.
|
||||
|
||||
Note, the method requires the extra data to be at least 65 bytes, otherwise it
|
||||
panics. This is done to avoid accidentally using both forms (signature present
|
||||
or not), which could be abused to produce different hashes for the same header.
|
||||
*/
|
||||
func (x *XDPoS_v1) SigHash(header *types.Header) (hash common.Hash) {
|
||||
return sigHash(header)
|
||||
}
|
||||
|
||||
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
|
||||
// signers set to the ones provided by the user.
|
||||
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 {
|
||||
|
|
@ -117,10 +116,7 @@ func (x *XDPoS_v1) VerifyHeader(chain consensus.ChainReader, header *types.Heade
|
|||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
|
||||
// method returns a quit channel to abort the operations and a results channel to
|
||||
// retrieve the async verifications (the order is that of the input slice).
|
||||
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) {
|
||||
abort := make(chan struct{})
|
||||
results := make(chan error, len(headers))
|
||||
|
||||
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool, abort <-chan struct{}, results chan<- error) {
|
||||
go func() {
|
||||
for i, header := range headers {
|
||||
err := x.verifyHeaderWithCache(chain, header, headers[:i], fullVerifies[i])
|
||||
|
|
@ -132,7 +128,6 @@ func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.H
|
|||
}
|
||||
}
|
||||
}()
|
||||
return abort, results
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
||||
|
|
@ -153,7 +148,7 @@ func (x *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *ty
|
|||
// a batch of new headers.
|
||||
func (x *XDPoS_v1) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
||||
// If we're running a engine faking, accept any block as valid
|
||||
if x.config.SkipValidation {
|
||||
if x.config.SkipV1Validation {
|
||||
return nil
|
||||
}
|
||||
if common.IsTestnet {
|
||||
|
|
@ -321,7 +316,7 @@ func (x *XDPoS_v1) checkSignersOnCheckpoint(chain consensus.ChainReader, header
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool {
|
||||
func (x *XDPoS_v1) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool {
|
||||
snap, err := x.GetSnapshot(chain, header)
|
||||
if err != nil {
|
||||
log.Error("[IsAuthorisedAddress] Can't get snapshot with at ", "number", header.Number, "hash", header.Hash().Hex(), "err", err)
|
||||
|
|
@ -355,32 +350,33 @@ func (x *XDPoS_v1) StoreSnapshot(snap *SnapshotV1) error {
|
|||
return snap.store(x.db)
|
||||
}
|
||||
|
||||
func position(list []common.Address, x common.Address) int {
|
||||
for i, item := range list {
|
||||
if item == x {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||
n := header.Number.Uint64()
|
||||
e := x.config.Epoch
|
||||
switch {
|
||||
case n%e == 0:
|
||||
return x.GetMasternodesFromCheckpointHeader(header, n, e)
|
||||
return x.GetMasternodesFromCheckpointHeader(header)
|
||||
case n%e != 0:
|
||||
h := chain.GetHeaderByNumber(n - (n % e))
|
||||
return x.GetMasternodesFromCheckpointHeader(h, n, e)
|
||||
if h == nil {
|
||||
log.Warn("[GetMasternodes v1] epoch switch block header nil", "BlockNum", n)
|
||||
}
|
||||
return x.GetMasternodesFromCheckpointHeader(h)
|
||||
default:
|
||||
return []common.Address{}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetCurrentEpochSwitchBlock(blockNumber *big.Int) (uint64, uint64, error) {
|
||||
currentBlockNum := blockNumber.Uint64()
|
||||
currentCheckpointNumber := currentBlockNum - currentBlockNum%x.config.Epoch
|
||||
epochNumber := currentBlockNum / x.config.Epoch
|
||||
return currentCheckpointNumber, epochNumber, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetPeriod() uint64 { return x.config.Period }
|
||||
|
||||
func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error) {
|
||||
func (x *XDPoS_v1) whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error) {
|
||||
if header.Number.Uint64() == 0 {
|
||||
return common.Address{}, errors.New("Don't take block 0")
|
||||
}
|
||||
|
|
@ -390,8 +386,41 @@ func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error
|
|||
}
|
||||
return m, nil
|
||||
}
|
||||
func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
|
||||
len, preIndex, curIndex, ok, err := x.yourTurn(chain, parent, signer)
|
||||
|
||||
func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
||||
if err != nil {
|
||||
log.Warn("Failed when trying to commit new work", "err", err)
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
// in case some nodes are down
|
||||
if preIndex == -1 {
|
||||
// first block
|
||||
return false, nil
|
||||
}
|
||||
if curIndex == -1 {
|
||||
// you're not allowed to create this block
|
||||
return false, nil
|
||||
}
|
||||
h := utils.Hop(len, preIndex, curIndex)
|
||||
gap := waitPeriod * int64(h)
|
||||
// Check nearest checkpoint block in hop range.
|
||||
nearest := x.config.Epoch - (parent.Number.Uint64() % x.config.Epoch)
|
||||
if uint64(h) >= nearest {
|
||||
gap = waitPeriodCheckpoint * int64(h)
|
||||
}
|
||||
log.Info("Distance from the parent block", "seconds", gap, "hops", h)
|
||||
waitedTime := time.Now().Unix() - parent.Time.Int64()
|
||||
if gap > waitedTime {
|
||||
return false, nil
|
||||
}
|
||||
log.Info("Wait enough. It's my turn", "waited seconds", waitedTime)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) yourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
||||
masternodes := x.GetMasternodes(chain, parent)
|
||||
|
||||
// if common.IsTestnet {
|
||||
|
|
@ -415,13 +444,13 @@ func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, s
|
|||
// masternode[0] has chance to create block 1
|
||||
preIndex := -1
|
||||
if parent.Number.Uint64() != 0 {
|
||||
pre, err = whoIsCreator(snap, parent)
|
||||
pre, err = x.whoIsCreator(snap, parent)
|
||||
if err != nil {
|
||||
return 0, 0, 0, false, err
|
||||
}
|
||||
preIndex = position(masternodes, pre)
|
||||
preIndex = utils.Position(masternodes, pre)
|
||||
}
|
||||
curIndex := position(masternodes, signer)
|
||||
curIndex := utils.Position(masternodes, signer)
|
||||
if signer == x.signer {
|
||||
log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex)
|
||||
}
|
||||
|
|
@ -643,7 +672,7 @@ func (x *XDPoS_v1) GetValidator(creator common.Address, chain consensus.ChainRea
|
|||
return common.Address{}, fmt.Errorf("couldn't find checkpoint header")
|
||||
}
|
||||
}
|
||||
m, err := GetM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
|
||||
m, err := getM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
|
@ -876,7 +905,7 @@ func (x *XDPoS_v1) Seal(chain consensus.ChainReader, block *types.Block, stop <-
|
|||
default:
|
||||
}
|
||||
// Sign all the things!
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, utils.SigHash(header).Bytes())
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, x.SigHash(header).Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -900,10 +929,10 @@ func (x *XDPoS_v1) CalcDifficulty(chain consensus.ChainReader, time uint64, pare
|
|||
|
||||
func (x *XDPoS_v1) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int {
|
||||
// If we're running a engine faking, skip calculation
|
||||
if x.config.SkipValidation {
|
||||
if x.config.SkipV1Validation {
|
||||
return big.NewInt(1)
|
||||
}
|
||||
len, preIndex, curIndex, _, err := x.YourTurn(chain, parent, signer)
|
||||
len, preIndex, curIndex, _, err := x.yourTurn(chain, parent, signer)
|
||||
if err != nil {
|
||||
return big.NewInt(int64(len + curIndex - preIndex))
|
||||
}
|
||||
|
|
@ -926,7 +955,7 @@ func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error
|
|||
return common.Address{}, consensus.ErrFailValidatorSignature
|
||||
}
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), header.Validator)
|
||||
pubkey, err := crypto.Ecrecover(x.SigHash(header).Bytes(), header.Validator)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
|
@ -937,18 +966,13 @@ func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error
|
|||
return signer, nil
|
||||
}
|
||||
|
||||
// Get master nodes over extra data of previous checkpoint block.
|
||||
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
|
||||
if preCheckpointHeader == nil {
|
||||
log.Info("Previous checkpoint's header is empty", "block number", n, "epoch", e)
|
||||
// Get master nodes over extra data of checkpoint block.
|
||||
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
if checkpointHeader == nil {
|
||||
log.Warn("Checkpoint's header is empty", "Header", checkpointHeader)
|
||||
return []common.Address{}
|
||||
}
|
||||
masternodes := make([]common.Address, (len(preCheckpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], preCheckpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
|
||||
return masternodes
|
||||
return decodeMasternodesFromHeaderExtra(checkpointHeader)
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetDb() ethdb.Database {
|
||||
|
|
@ -970,30 +994,6 @@ func removePenaltiesFromBlock(chain consensus.ChainReader, masternodes []common.
|
|||
return masternodes
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header.
|
||||
func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
// Get m2 list from checkpoint block.
|
||||
func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
|
||||
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
|
||||
return nil, errors.New("This block is not checkpoint block epoc.")
|
||||
}
|
||||
// Get signers from this block.
|
||||
masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader)
|
||||
validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators)
|
||||
m1m2, _, err := utils.GetM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
return map[common.Address]common.Address{}, err
|
||||
}
|
||||
return m1m2, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) {
|
||||
startGapBlockHeader := checkpointHeader
|
||||
number := checkpointHeader.Number.Uint64()
|
||||
|
|
@ -1028,3 +1028,10 @@ func NewFaker(db ethdb.Database, config *params.XDPoSConfig) *XDPoS_v1 {
|
|||
}
|
||||
return fakeEngine
|
||||
}
|
||||
|
||||
// Epoch Switch is also known as checkpoint in v1
|
||||
func (x *XDPoS_v1) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
||||
epochNumber := header.Number.Uint64() / x.config.Epoch
|
||||
blockNumInEpoch := header.Number.Uint64() % x.config.Epoch
|
||||
return blockNumInEpoch == 0, epochNumber, nil
|
||||
}
|
||||
|
|
|
|||
112
consensus/XDPoS/engines/engine_v1/utils.go
Normal file
112
consensus/XDPoS/engines/engine_v1/utils.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package engine_v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// Get masternodes address from checkpoint Header.
|
||||
func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
// Get m2 list from checkpoint block.
|
||||
func getM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
|
||||
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
|
||||
return nil, errors.New("This block is not checkpoint block epoc.")
|
||||
}
|
||||
// Get signers from this block.
|
||||
masternodes := decodeMasternodesFromHeaderExtra(checkpointHeader)
|
||||
validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators)
|
||||
m1m2, _, err := getM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
return map[common.Address]common.Address{}, err
|
||||
}
|
||||
return m1m2, nil
|
||||
}
|
||||
|
||||
func getM1M2(masternodes []common.Address, validators []int64, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, uint64, error) {
|
||||
m1m2 := map[common.Address]common.Address{}
|
||||
maxMNs := len(masternodes)
|
||||
moveM2 := uint64(0)
|
||||
if len(validators) < maxMNs {
|
||||
return nil, moveM2, errors.New("len(m2) is less than len(m1)")
|
||||
}
|
||||
if maxMNs > 0 {
|
||||
isForked := config.IsTIPRandomize(currentHeader.Number)
|
||||
if isForked {
|
||||
moveM2 = ((currentHeader.Number.Uint64() % config.XDPoS.Epoch) / uint64(maxMNs)) % uint64(maxMNs)
|
||||
}
|
||||
for i, m1 := range masternodes {
|
||||
m2Index := uint64(validators[i] % int64(maxMNs))
|
||||
m2Index = (m2Index + moveM2) % uint64(maxMNs)
|
||||
m1m2[m1] = masternodes[m2Index]
|
||||
}
|
||||
}
|
||||
return m1m2, moveM2, nil
|
||||
}
|
||||
|
||||
func sigHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := sha3.NewKeccak256()
|
||||
|
||||
err := rlp.Encode(hasher, []interface{}{
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("Fail to encode", err)
|
||||
}
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
}
|
||||
|
||||
// ecrecover extracts the Ethereum account address from a signed header.
|
||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||
// If the signature's already cached, return that
|
||||
hash := header.Hash()
|
||||
if address, known := sigcache.Get(hash); known {
|
||||
return address.(common.Address), nil
|
||||
}
|
||||
// Retrieve the signature from the header extra-data
|
||||
if len(header.Extra) < utils.ExtraSeal {
|
||||
return common.Address{}, utils.ErrMissingSignature
|
||||
}
|
||||
signature := header.Extra[len(header.Extra)-utils.ExtraSeal:]
|
||||
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
|
||||
sigcache.Add(hash, signer)
|
||||
return signer, nil
|
||||
}
|
||||
49
consensus/XDPoS/engines/engine_v1/utils_test.go
Normal file
49
consensus/XDPoS/engines/engine_v1/utils_test.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package engine_v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
)
|
||||
|
||||
func TestGetM1M2FromCheckpointHeader(t *testing.T) {
|
||||
masternodes := []common.Address{
|
||||
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
common.StringToAddress("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
|
||||
common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc"),
|
||||
}
|
||||
validators := []int64{
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
}
|
||||
epoch := uint64(900)
|
||||
config := ¶ms.ChainConfig{
|
||||
XDPoS: ¶ms.XDPoSConfig{
|
||||
Epoch: uint64(epoch),
|
||||
},
|
||||
}
|
||||
testMoveM2 := []uint64{0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2}
|
||||
//try from block 3410001 to 3410018
|
||||
for i := uint64(3464001); i <= 3464018; i++ {
|
||||
currentNumber := int64(i)
|
||||
currentHeader := &types.Header{
|
||||
Number: big.NewInt(currentNumber),
|
||||
}
|
||||
m1m2, moveM2, err := getM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
t.Error("can't get m1m2", "err", err)
|
||||
}
|
||||
fmt.Printf("block: %v, moveM2: %v\n", currentHeader.Number.Int64(), moveM2)
|
||||
for _, k := range masternodes {
|
||||
fmt.Printf("m1: %v - m2: %v\n", k.Str(), m1m2[k].Str())
|
||||
}
|
||||
if moveM2 != testMoveM2[i-3464001] {
|
||||
t.Error("wrong moveM2", "currentNumber", currentNumber, "want", testMoveM2[i-3464001], "have", moveM2)
|
||||
}
|
||||
}
|
||||
}
|
||||
14
consensus/XDPoS/engines/engine_v2/difficulty.go
Normal file
14
consensus/XDPoS/engines/engine_v2/difficulty.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
)
|
||||
|
||||
// TODO: what should be new difficulty
|
||||
func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int {
|
||||
return big.NewInt(1)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
140
consensus/XDPoS/engines/engine_v2/epochSwitch.go
Normal file
140
consensus/XDPoS/engines/engine_v2/epochSwitch.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// get epoch switch of the previous `limit` epoch
|
||||
func (x *XDPoS_v2) getPreviousEpochSwitchInfoByHash(chain consensus.ChainReader, hash common.Hash, limit int) (*types.EpochSwitchInfo, error) {
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash)
|
||||
if err != nil {
|
||||
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, epochSwitchInfo.EpochSwitchParentBlockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
|
||||
// Given header and its hash, get epoch switch info from the epoch switch block of that epoch,
|
||||
// header is allow to be nil.
|
||||
func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types.Header, hash common.Hash) (*types.EpochSwitchInfo, error) {
|
||||
e, ok := x.epochSwitches.Get(hash)
|
||||
if ok {
|
||||
epochSwitchInfo := e.(*types.EpochSwitchInfo)
|
||||
log.Debug("[getEpochSwitchInfo] cache hit", "number", epochSwitchInfo.EpochSwitchBlockInfo.Number, "hash", hash.Hex())
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
h := header
|
||||
if h == nil {
|
||||
log.Debug("[getEpochSwitchInfo] header doesn't provide, get header by hash", "hash", hash.Hex())
|
||||
h = chain.GetHeaderByHash(hash)
|
||||
if h == nil {
|
||||
log.Warn("[getEpochSwitchInfo] can not find header from db", "hash", hash.Hex())
|
||||
return nil, fmt.Errorf("[getEpochSwitchInfo] can not find header from db hash %v", hash.Hex())
|
||||
}
|
||||
}
|
||||
isEpochSwitch, _, err := x.IsEpochSwitch(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isEpochSwitch {
|
||||
log.Debug("[getEpochSwitchInfo] header is epoch switch", "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
quorumCert, round, masternodes, err := x.getExtraFields(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
epochSwitchInfo := &types.EpochSwitchInfo{
|
||||
Masternodes: masternodes,
|
||||
EpochSwitchBlockInfo: &types.BlockInfo{
|
||||
Hash: hash,
|
||||
Number: h.Number,
|
||||
Round: round,
|
||||
},
|
||||
}
|
||||
if quorumCert != nil {
|
||||
epochSwitchInfo.EpochSwitchParentBlockInfo = quorumCert.ProposedBlockInfo
|
||||
}
|
||||
|
||||
x.epochSwitches.Add(hash, epochSwitchInfo)
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, h.ParentHash)
|
||||
if err != nil {
|
||||
log.Error("[getEpochSwitchInfo] recursive error", "err", err, "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("[getEpochSwitchInfo] get epoch switch info recursively", "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
x.epochSwitches.Add(hash, epochSwitchInfo)
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
|
||||
// IsEpochSwitchAtRound() is used by miner to check whether it mines a block in the same epoch with parent
|
||||
func (x *XDPoS_v2) isEpochSwitchAtRound(round types.Round, parentHeader *types.Header) (bool, uint64, error) {
|
||||
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch
|
||||
// if parent is last v1 block and this is first v2 block, this is treated as epoch switch
|
||||
if parentHeader.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
return true, epochNum, nil
|
||||
}
|
||||
|
||||
_, parentRound, _, err := x.getExtraFields(parentHeader)
|
||||
if err != nil {
|
||||
log.Error("[IsEpochSwitch] decode header error", "err", err, "header", parentHeader, "extra", common.Bytes2Hex(parentHeader.Extra))
|
||||
return false, 0, err
|
||||
}
|
||||
if round <= parentRound {
|
||||
// this round is no larger than parentRound, should return false
|
||||
return false, epochNum, nil
|
||||
}
|
||||
|
||||
epochStartRound := round - round%types.Round(x.config.Epoch)
|
||||
return parentRound < epochStartRound, epochNum, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum *big.Int) (uint64, uint64, error) {
|
||||
header := chain.GetHeaderByNumber(blockNum.Uint64())
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash())
|
||||
if err != nil {
|
||||
log.Error("[GetCurrentEpochSwitchBlock] Fail to get epoch switch info", "Num", header.Number, "Hash", header.Hash())
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
currentCheckpointNumber := epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()
|
||||
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch
|
||||
return currentCheckpointNumber, epochNum, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
||||
// Return true directly if we are examing the last v1 block. This could happen if the calling function is examing parent block
|
||||
if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
log.Info("[IsEpochSwitch] examing last v1 block")
|
||||
return true, header.Number.Uint64() / x.config.Epoch, nil
|
||||
}
|
||||
|
||||
quorumCert, round, _, err := x.getExtraFields(header)
|
||||
if err != nil {
|
||||
log.Error("[IsEpochSwitch] decode header error", "err", err, "header", header, "extra", common.Bytes2Hex(header.Extra))
|
||||
return false, 0, err
|
||||
}
|
||||
parentRound := quorumCert.ProposedBlockInfo.Round
|
||||
epochStartRound := round - round%types.Round(x.config.Epoch)
|
||||
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch
|
||||
// if parent is last v1 block and this is first v2 block, this is treated as epoch switch
|
||||
if quorumCert.ProposedBlockInfo.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
log.Info("[IsEpochSwitch] true, parent equals V2.SwitchBlock", "round", round, "number", header.Number.Uint64(), "hash", header.Hash())
|
||||
return true, epochNum, nil
|
||||
}
|
||||
log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash())
|
||||
return parentRound < epochStartRound, epochNum, nil
|
||||
}
|
||||
382
consensus/XDPoS/engines/engine_v2/forensics.go
Normal file
382
consensus/XDPoS/engines/engine_v2/forensics.go
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/event"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
const (
|
||||
NUM_OF_FORENSICS_QC = 3
|
||||
)
|
||||
|
||||
// Forensics instance. Placeholder for future properties to be added
|
||||
type Forensics struct {
|
||||
HighestCommittedQCs []types.QuorumCert
|
||||
forensicsFeed event.Feed
|
||||
scope event.SubscriptionScope
|
||||
}
|
||||
|
||||
// Initiate a forensics process
|
||||
func NewForensics() *Forensics {
|
||||
return &Forensics{}
|
||||
}
|
||||
|
||||
// SubscribeForensicsEvent registers a subscription of ForensicsEvent and
|
||||
// starts sending event to the given channel.
|
||||
func (f *Forensics) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription {
|
||||
return f.scope.Track(f.forensicsFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
func (f *Forensics) ForensicsMonitoring(chain consensus.ChainReader, engine *XDPoS_v2, headerQcToBeCommitted []types.Header, incomingQC types.QuorumCert) error {
|
||||
f.ProcessForensics(chain, engine, incomingQC)
|
||||
return f.SetCommittedQCs(headerQcToBeCommitted, incomingQC)
|
||||
}
|
||||
|
||||
// Set the forensics committed QCs list. The order is from grandparent to current header. i.e it shall follow the QC in its header as follow [hcqc1, hcqc2, hcqc3]
|
||||
func (f *Forensics) SetCommittedQCs(headers []types.Header, incomingQC types.QuorumCert) error {
|
||||
// highestCommitQCs is an array, assign the parentBlockQc and its child as well as its grandchild QC into this array for forensics purposes.
|
||||
if len(headers) != NUM_OF_FORENSICS_QC-1 {
|
||||
log.Error("[SetCommittedQcs] Received input length not equal to 2", len(headers))
|
||||
return fmt.Errorf("received headers length not equal to 2 ")
|
||||
}
|
||||
|
||||
var committedQCs []types.QuorumCert
|
||||
for i, h := range headers {
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the qc1 and qc2
|
||||
err := utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[SetCommittedQCs] Fail to decode extra when committing QC to forensics", "err", err, "index", i)
|
||||
return err
|
||||
}
|
||||
if i != 0 {
|
||||
if decodedExtraField.QuorumCert.ProposedBlockInfo.Hash != headers[i-1].Hash() {
|
||||
log.Error("[SetCommittedQCs] Headers shall be on the same chain and in the right order", "parentHash", h.ParentHash.Hex(), "headers[i-1].Hash()", headers[i-1].Hash().Hex())
|
||||
return fmt.Errorf("headers shall be on the same chain and in the right order")
|
||||
} else if i == len(headers)-1 { // The last header shall be pointed by the incoming QC
|
||||
if incomingQC.ProposedBlockInfo.Hash != h.Hash() {
|
||||
log.Error("[SetCommittedQCs] incomingQc is not pointing at the last header received", "hash", h.Hash().Hex(), "incomingQC.ProposedBlockInfo.Hash", incomingQC.ProposedBlockInfo.Hash.Hex())
|
||||
return fmt.Errorf("incomingQc is not pointing at the last header received")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
committedQCs = append(committedQCs, *decodedExtraField.QuorumCert)
|
||||
}
|
||||
f.HighestCommittedQCs = append(committedQCs, incomingQC)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Entry point for processing forensics.
|
||||
Triggered once processQC is successfully.
|
||||
Forensics runs in a seperate go routine as its no system critical
|
||||
Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/97878029/Forensics+Diagram+flow
|
||||
*/
|
||||
func (f *Forensics) ProcessForensics(chain consensus.ChainReader, engine *XDPoS_v2, incomingQC types.QuorumCert) error {
|
||||
log.Debug("Received a QC in forensics", "QC", incomingQC)
|
||||
// Clone the values to a temporary variable
|
||||
highestCommittedQCs := f.HighestCommittedQCs
|
||||
if len(highestCommittedQCs) != NUM_OF_FORENSICS_QC {
|
||||
log.Error("[ProcessForensics] HighestCommittedQCs value not set", "incomingQcProposedBlockHash", incomingQC.ProposedBlockInfo.Hash, "incomingQcProposedBlockNumber", incomingQC.ProposedBlockInfo.Number.Uint64(), "incomingQcProposedBlockRound", incomingQC.ProposedBlockInfo.Round)
|
||||
return fmt.Errorf("HighestCommittedQCs value not set")
|
||||
}
|
||||
// Find the QC1 and QC2. We only care 2 parents in front of the incomingQC. The returned value contains QC1, QC2 and QC3(the incomingQC)
|
||||
incomingQuorunCerts, err := f.findAncestorQCs(chain, incomingQC, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isOnTheChain, err := f.checkQCsOnTheSameChain(chain, highestCommittedQCs, incomingQuorunCerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isOnTheChain {
|
||||
// Passed the checking, nothing suspecious.
|
||||
log.Debug("[ProcessForensics] Passed forensics checking, nothing suspecious need to be reported", "incomingQcProposedBlockHash", incomingQC.ProposedBlockInfo.Hash, "incomingQcProposedBlockNumber", incomingQC.ProposedBlockInfo.Number.Uint64(), "incomingQcProposedBlockRound", incomingQC.ProposedBlockInfo.Round)
|
||||
return nil
|
||||
}
|
||||
// Trigger the safety Alarm if failed
|
||||
// First, find the QC in the two sets that have the same round
|
||||
foundSameRoundQC, sameRoundHCQC, sameRoundQC := f.findQCsInSameRound(highestCommittedQCs, incomingQuorunCerts)
|
||||
|
||||
if foundSameRoundQC {
|
||||
f.SendForensicProof(chain, engine, sameRoundHCQC, sameRoundQC)
|
||||
} else {
|
||||
// Not found, need a more complex approach to find the two QC
|
||||
ancestorQC, lowerRoundQCs, _, err := f.findAncestorQcThroughRound(chain, highestCommittedQCs, incomingQuorunCerts)
|
||||
if err != nil {
|
||||
log.Error("[ProcessForensics] Error while trying to find ancestor QC through round number", "err", err)
|
||||
}
|
||||
f.SendForensicProof(chain, engine, ancestorQC, lowerRoundQCs[NUM_OF_FORENSICS_QC-1])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Last step of forensics which sends out detailed proof to report service.
|
||||
func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS_v2, firstQc types.QuorumCert, secondQc types.QuorumCert) error {
|
||||
// Re-order the QC by its round number to make the function cleaner.
|
||||
lowerRoundQC := firstQc
|
||||
higherRoundQC := secondQc
|
||||
|
||||
if secondQc.ProposedBlockInfo.Round < firstQc.ProposedBlockInfo.Round {
|
||||
lowerRoundQC = secondQc
|
||||
higherRoundQC = firstQc
|
||||
}
|
||||
|
||||
// Find common ancestor block
|
||||
ancestorHash, ancestorToLowerRoundPath, ancestorToHigherRoundPath, err := f.FindAncestorBlockHash(chain, lowerRoundQC.ProposedBlockInfo, higherRoundQC.ProposedBlockInfo)
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] Error while trying to find ancestor block hash", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if two QCs are across epoch, this is used as a indicator for the "prone to attack" scenario
|
||||
lowerRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, lowerRoundQC.ProposedBlockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] Errir while trying to find lowerRoundQcEpochSwitchInfo", "lowerRoundQC.ProposedBlockInfo.Hash", lowerRoundQC.ProposedBlockInfo.Hash, "err", err)
|
||||
return err
|
||||
}
|
||||
higherRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, higherRoundQC.ProposedBlockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] Errir while trying to find higherRoundQcEpochSwitchInfo", "higherRoundQC.ProposedBlockInfo.Hash", higherRoundQC.ProposedBlockInfo.Hash, "err", err)
|
||||
return err
|
||||
}
|
||||
accrossEpoches := false
|
||||
if lowerRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash != higherRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash {
|
||||
accrossEpoches = true
|
||||
}
|
||||
|
||||
ancestorBlock := chain.GetHeaderByHash(ancestorHash)
|
||||
|
||||
if ancestorBlock == nil {
|
||||
log.Error("[SendForensicProof] Unable to find the ancestor block by its hash", "Hash", ancestorHash)
|
||||
return fmt.Errorf("Can't find ancestor block via hash")
|
||||
}
|
||||
|
||||
content, err := json.Marshal(&types.ForensicsContent{
|
||||
DivergingBlockHash: ancestorHash.Hex(),
|
||||
AcrossEpoch: accrossEpoches,
|
||||
DivergingBlockNumber: ancestorBlock.Number.Uint64(),
|
||||
SmallerRoundInfo: &types.ForensicsInfo{
|
||||
HashPath: ancestorToLowerRoundPath,
|
||||
QuorumCert: lowerRoundQC,
|
||||
SignerAddresses: f.getQcSignerAddresses(lowerRoundQC),
|
||||
},
|
||||
LargerRoundInfo: &types.ForensicsInfo{
|
||||
HashPath: ancestorToHigherRoundPath,
|
||||
QuorumCert: higherRoundQC,
|
||||
SignerAddresses: f.getQcSignerAddresses(higherRoundQC),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] fail to json stringify forensics content", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
forensicsProof := &types.ForensicProof{
|
||||
Id: generateForensicsId(ancestorHash.Hex(), &lowerRoundQC, &higherRoundQC),
|
||||
ForensicsType: "QC",
|
||||
Content: string(content),
|
||||
}
|
||||
log.Info("Forensics proof report generated, sending to the stats server", "forensicsProof", forensicsProof)
|
||||
go f.forensicsFeed.Send(types.ForensicsEvent{ForensicsProof: forensicsProof})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Utils function to help find the n-th previous QC. It returns an array of QC in ascending order including the currentQc as the last item in the array
|
||||
func (f *Forensics) findAncestorQCs(chain consensus.ChainReader, currentQc types.QuorumCert, distanceFromCurrrentQc int) ([]types.QuorumCert, error) {
|
||||
var quorumCerts []types.QuorumCert
|
||||
quorumCertificate := currentQc
|
||||
// Append the initial value
|
||||
quorumCerts = append(quorumCerts, quorumCertificate)
|
||||
// Append the parents
|
||||
for i := 0; i < distanceFromCurrrentQc; i++ {
|
||||
parentHash := quorumCertificate.ProposedBlockInfo.Hash
|
||||
parentHeader := chain.GetHeaderByHash(parentHash)
|
||||
if parentHeader == nil {
|
||||
log.Error("[findAncestorQCs] Forensics findAncestorQCs unable to find its parent block header", "BlockNum", parentHeader.Number.Int64(), "ParentHash", parentHash.Hex())
|
||||
return nil, fmt.Errorf("unable to find parent block header in forensics")
|
||||
}
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[findAncestorQCs] Error while trying to decode from parent block extra", "BlockNum", parentHeader.Number.Int64(), "ParentHash", parentHash.Hex())
|
||||
}
|
||||
quorumCertificate = *decodedExtraField.QuorumCert
|
||||
quorumCerts = append(quorumCerts, quorumCertificate)
|
||||
}
|
||||
// The quorumCerts is in the reverse order, we need to flip it
|
||||
var quorumCertsInAscendingOrder []types.QuorumCert
|
||||
for i := len(quorumCerts) - 1; i >= 0; i-- {
|
||||
quorumCertsInAscendingOrder = append(quorumCertsInAscendingOrder, quorumCerts[i])
|
||||
}
|
||||
return quorumCertsInAscendingOrder, nil
|
||||
}
|
||||
|
||||
// Check whether two provided QC set are on the same chain
|
||||
func (f *Forensics) checkQCsOnTheSameChain(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingQCandItsParents []types.QuorumCert) (bool, error) {
|
||||
// Re-order two sets of QCs by block Number
|
||||
lowerBlockNumQCs := highestCommittedQCs
|
||||
higherBlockNumQCs := incomingQCandItsParents
|
||||
if incomingQCandItsParents[0].ProposedBlockInfo.Number.Cmp(highestCommittedQCs[0].ProposedBlockInfo.Number) == -1 {
|
||||
lowerBlockNumQCs = incomingQCandItsParents
|
||||
higherBlockNumQCs = highestCommittedQCs
|
||||
}
|
||||
|
||||
proposedBlockInfo := higherBlockNumQCs[0].ProposedBlockInfo
|
||||
for i := 0; i < int((big.NewInt(0).Sub(higherBlockNumQCs[0].ProposedBlockInfo.Number, lowerBlockNumQCs[0].ProposedBlockInfo.Number)).Int64()); i++ {
|
||||
parentHeader := chain.GetHeaderByHash(proposedBlockInfo.Hash)
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[checkQCsOnTheSameChain] Fail to decode extra when checking the two QCs set on the same chain", "err", err)
|
||||
return false, err
|
||||
}
|
||||
proposedBlockInfo = decodedExtraField.QuorumCert.ProposedBlockInfo
|
||||
}
|
||||
// Check the final proposed blockInfo is the same as what we have from lowerBlockNumQCs[0]
|
||||
if reflect.DeepEqual(proposedBlockInfo, lowerBlockNumQCs[0].ProposedBlockInfo) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Given the two QCs set, find if there are any QC that have the same round
|
||||
func (f *Forensics) findQCsInSameRound(quorumCerts1 []types.QuorumCert, quorumCerts2 []types.QuorumCert) (bool, types.QuorumCert, types.QuorumCert) {
|
||||
for _, quorumCert1 := range quorumCerts1 {
|
||||
for _, quorumCert2 := range quorumCerts2 {
|
||||
if quorumCert1.ProposedBlockInfo.Round == quorumCert2.ProposedBlockInfo.Round {
|
||||
return true, quorumCert1, quorumCert2
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, types.QuorumCert{}, types.QuorumCert{}
|
||||
}
|
||||
|
||||
// Find the signer list from QC signatures
|
||||
func (f *Forensics) getQcSignerAddresses(quorumCert types.QuorumCert) []string {
|
||||
var signerList []string
|
||||
|
||||
// The QC signatures are signed by votes special struct VoteForSign
|
||||
quorumCertSignedHash := types.VoteSigHash(&types.VoteForSign{
|
||||
ProposedBlockInfo: quorumCert.ProposedBlockInfo,
|
||||
GapNumber: quorumCert.GapNumber,
|
||||
})
|
||||
for _, signature := range quorumCert.Signatures {
|
||||
var signerAddress common.Address
|
||||
pubkey, err := crypto.Ecrecover(quorumCertSignedHash.Bytes(), signature)
|
||||
if err != nil {
|
||||
log.Error("[getQcSignerAddresses] Fail to Ecrecover signer from the quorumCertSignedHash", "quorumCert.GapNumber", quorumCert.GapNumber, "quorumCert.ProposedBlockInfo", quorumCert.ProposedBlockInfo)
|
||||
}
|
||||
|
||||
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
signerList = append(signerList, signerAddress.Hex())
|
||||
}
|
||||
return signerList
|
||||
}
|
||||
|
||||
// Check whether the given QCs are on the same chain as the stored committed QCs(f.HighestCommittedQCs) regardless their orders
|
||||
func (f *Forensics) findAncestorQcThroughRound(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingQCandItsParents []types.QuorumCert) (types.QuorumCert, []types.QuorumCert, []types.QuorumCert, error) {
|
||||
/*
|
||||
Re-order two sets of QCs by Round number
|
||||
*/
|
||||
lowerRoundQCs := highestCommittedQCs
|
||||
higherRoundQCs := incomingQCandItsParents
|
||||
if incomingQCandItsParents[0].ProposedBlockInfo.Round < highestCommittedQCs[0].ProposedBlockInfo.Round {
|
||||
lowerRoundQCs = incomingQCandItsParents
|
||||
higherRoundQCs = highestCommittedQCs
|
||||
}
|
||||
|
||||
// Find the ancestorFromIncomingQC1 that matches round number < lowerRoundQCs3
|
||||
ancestorQC := higherRoundQCs[0]
|
||||
for ancestorQC.ProposedBlockInfo.Round >= lowerRoundQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round {
|
||||
proposedBlock := chain.GetHeaderByHash(ancestorQC.ProposedBlockInfo.Hash)
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(proposedBlock.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[findAncestorQcThroughRound] Error while trying to decode extra field", "ProposedBlockInfo.Hash", ancestorQC.ProposedBlockInfo.Hash)
|
||||
return ancestorQC, lowerRoundQCs, higherRoundQCs, err
|
||||
}
|
||||
// Found the ancestor QC
|
||||
if decodedExtraField.QuorumCert.ProposedBlockInfo.Round < lowerRoundQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round {
|
||||
return ancestorQC, lowerRoundQCs, higherRoundQCs, nil
|
||||
}
|
||||
ancestorQC = *decodedExtraField.QuorumCert
|
||||
}
|
||||
return ancestorQC, lowerRoundQCs, higherRoundQCs, fmt.Errorf("[findAncestorQcThroughRound] Could not find ancestor QC")
|
||||
}
|
||||
|
||||
func (f *Forensics) FindAncestorBlockHash(chain consensus.ChainReader, firstBlockInfo *types.BlockInfo, secondBlockInfo *types.BlockInfo) (common.Hash, []string, []string, error) {
|
||||
// Re-arrange by block number
|
||||
lowerBlockNumHash := firstBlockInfo.Hash
|
||||
higherBlockNumberHash := secondBlockInfo.Hash
|
||||
|
||||
var lowerBlockNumToAncestorHashPath []string
|
||||
var higherBlockToAncestorNumHashPath []string
|
||||
orderSwapped := false
|
||||
|
||||
blockNumberDifference := big.NewInt(0).Sub(secondBlockInfo.Number, firstBlockInfo.Number).Int64()
|
||||
if blockNumberDifference < 0 {
|
||||
lowerBlockNumHash = secondBlockInfo.Hash
|
||||
higherBlockNumberHash = firstBlockInfo.Hash
|
||||
blockNumberDifference = -blockNumberDifference // and make it positive
|
||||
orderSwapped = true
|
||||
}
|
||||
lowerBlockNumToAncestorHashPath = append(lowerBlockNumToAncestorHashPath, lowerBlockNumHash.Hex())
|
||||
higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, higherBlockNumberHash.Hex())
|
||||
|
||||
// First, make their block number the same to start with
|
||||
for i := 0; i < int(blockNumberDifference); i++ {
|
||||
ph := chain.GetHeaderByHash(higherBlockNumberHash)
|
||||
if ph == nil {
|
||||
return common.Hash{}, lowerBlockNumToAncestorHashPath, higherBlockToAncestorNumHashPath, fmt.Errorf("unable to find parent block of hash %v", higherBlockNumberHash)
|
||||
}
|
||||
higherBlockNumberHash = ph.ParentHash
|
||||
higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, ph.ParentHash.Hex())
|
||||
}
|
||||
|
||||
// Now, they are on the same starting line, we try find the common ancestor
|
||||
for lowerBlockNumHash.Hex() != higherBlockNumberHash.Hex() {
|
||||
lowerBlockNumHash = chain.GetHeaderByHash(lowerBlockNumHash).ParentHash
|
||||
higherBlockNumberHash = chain.GetHeaderByHash(higherBlockNumberHash).ParentHash
|
||||
// Append the path
|
||||
lowerBlockNumToAncestorHashPath = append(lowerBlockNumToAncestorHashPath, lowerBlockNumHash.Hex())
|
||||
higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, higherBlockNumberHash.Hex())
|
||||
}
|
||||
|
||||
// Reverse the list order as it's from ancestor to X block path.
|
||||
ancestorToLowerBlockNumHashPath := reverse(lowerBlockNumToAncestorHashPath)
|
||||
ancestorToHigherBlockNumHashPath := reverse(higherBlockToAncestorNumHashPath)
|
||||
// Swap back the order. We must return in the order that matches what we acceptted in the parameter of firstBlock & secondBlock
|
||||
if orderSwapped {
|
||||
return lowerBlockNumHash, ancestorToHigherBlockNumHashPath, ancestorToLowerBlockNumHashPath, nil
|
||||
}
|
||||
return lowerBlockNumHash, ancestorToLowerBlockNumHashPath, ancestorToHigherBlockNumHashPath, nil
|
||||
}
|
||||
|
||||
func generateForensicsId(divergingHash string, qc1 *types.QuorumCert, qc2 *types.QuorumCert) string {
|
||||
keysList := []string{divergingHash, qc1.ProposedBlockInfo.Hash.Hex(), qc2.ProposedBlockInfo.Hash.Hex()}
|
||||
return strings.Join(keysList[:], ":")
|
||||
}
|
||||
|
||||
func reverse(ss []string) []string {
|
||||
last := len(ss) - 1
|
||||
for i := 0; i < len(ss)/2; i++ {
|
||||
ss[i], ss[last-i] = ss[last-i], ss[i]
|
||||
}
|
||||
return ss
|
||||
}
|
||||
145
consensus/XDPoS/engines/engine_v2/forensics_test.go
Normal file
145
consensus/XDPoS/engines/engine_v2/forensics_test.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Utils to help mocking the signing of signatures
|
||||
var (
|
||||
signer1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
signer2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
signer3, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
)
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func SignHashByPK(pk *ecdsa.PrivateKey, itemToSign []byte) []byte {
|
||||
signer, signFn, err := getSignerAndSignFn(pk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, itemToSign)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return signedHash
|
||||
}
|
||||
func RandStringBytes(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getSignerAndSignFn(pk *ecdsa.PrivateKey) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) {
|
||||
veryLightScryptN := 2
|
||||
veryLightScryptP := 1
|
||||
dir, _ := ioutil.TempDir("", fmt.Sprintf("eth-getSignerAndSignFn-test-%v", RandStringBytes(5)))
|
||||
|
||||
new := func(kd string) *keystore.KeyStore {
|
||||
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
ks := new(dir)
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.ImportECDSA(pk, pass)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
if err := ks.Unlock(a1, ""); err != nil {
|
||||
return a1.Address, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return a1.Address, ks.SignHash, nil
|
||||
}
|
||||
|
||||
func TestFindQCsInSameRound(t *testing.T) {
|
||||
forensics := &Forensics{}
|
||||
gapNumber := 450
|
||||
|
||||
// If ONE in common
|
||||
var sig []types.Signature
|
||||
qc1 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc1"),
|
||||
Round: types.Round(10),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc2 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc2"),
|
||||
Round: types.Round(12),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc3 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc3"),
|
||||
Round: types.Round(13),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc4 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc4"),
|
||||
Round: types.Round(12),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc5 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc5"),
|
||||
Round: types.Round(13),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc6 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc6"),
|
||||
Round: types.Round(15),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
var qcSet1 []types.QuorumCert
|
||||
var qcSet2 []types.QuorumCert
|
||||
|
||||
found, first, second := forensics.findQCsInSameRound(append(qcSet1, *qc1, *qc2, *qc3), append(qcSet2, *qc4, *qc5, *qc6))
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, *qc2, first)
|
||||
assert.Equal(t, *qc4, second)
|
||||
}
|
||||
|
||||
// TODO: Add test for FindAncestorBlockHash
|
||||
61
consensus/XDPoS/engines/engine_v2/mining.go
Normal file
61
consensus/XDPoS/engines/engine_v2/mining.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Using parent and current round to find the finalised master node list(with penalties applied from last epoch)
|
||||
func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, parent *types.Header, signer common.Address) (bool, error) {
|
||||
if round <= x.highestSelfMinedRound {
|
||||
log.Warn("[yourturn] Already mined on this round", "Round", round, "highestSelfMinedRound", x.highestSelfMinedRound, "ParentHash", parent.Hash().Hex(), "ParentNumber", parent.Number)
|
||||
return false, utils.ErrAlreadyMined
|
||||
}
|
||||
|
||||
isEpochSwitch, _, err := x.isEpochSwitchAtRound(round, parent)
|
||||
if err != nil {
|
||||
log.Error("[yourturn] check epoch switch at round failed", "Error", err)
|
||||
return false, err
|
||||
}
|
||||
var masterNodes []common.Address
|
||||
if isEpochSwitch {
|
||||
masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash())
|
||||
if err != nil {
|
||||
log.Error("[yourturn] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number)
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
// this block and parent belong to the same epoch
|
||||
masterNodes = x.GetMasternodes(chain, parent)
|
||||
}
|
||||
|
||||
if len(masterNodes) == 0 {
|
||||
log.Error("[yourturn] Fail to find any master nodes from current block round epoch", "Hash", parent.Hash(), "CurrentRound", round, "Number", parent.Number)
|
||||
return false, errors.New("masternodes not found")
|
||||
}
|
||||
|
||||
curIndex := utils.Position(masterNodes, signer)
|
||||
if curIndex == -1 {
|
||||
log.Warn("[yourturn] I am not in masternodes list", "Hash", parent.Hash(), "signer", signer)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for i, s := range masterNodes {
|
||||
log.Debug("[yourturn] Masternode:", "index", i, "address", s.String(), "parentBlockNum", parent.Number)
|
||||
}
|
||||
|
||||
leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes))
|
||||
if masterNodes[leaderIndex] != signer {
|
||||
log.Info("[yourturn] Not my turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", parent.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "signer", signer)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Info("[yourturn] Yes, it's my turn based on parent block", "ParentHash", parent.Hash().Hex(), "ParentBlockNumber", parent.Number.Uint64())
|
||||
return true, nil
|
||||
}
|
||||
|
|
@ -1 +1,102 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Snapshot is the state of the smart contract validator list
|
||||
// The validator list is used on next epoch master nodes
|
||||
// If we don't have the snapshot, then we have to trace back the gap block smart contract state which is very costly
|
||||
type SnapshotV2 struct {
|
||||
Number uint64 `json:"number"` // Block number where the snapshot was created
|
||||
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
|
||||
|
||||
// MasterNodes will get assigned on updateM1
|
||||
NextEpochMasterNodes []common.Address `json:"masterNodes"` // Set of authorized master nodes at this moment for next epoch
|
||||
}
|
||||
|
||||
// create new snapshot for next epoch to use
|
||||
func newSnapshot(number uint64, hash common.Hash, masternodes []common.Address) *SnapshotV2 {
|
||||
snap := &SnapshotV2{
|
||||
Number: number,
|
||||
Hash: hash,
|
||||
NextEpochMasterNodes: masternodes,
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
// loadSnapshot loads an existing snapshot from the database.
|
||||
func loadSnapshot(db ethdb.Database, hash common.Hash) (*SnapshotV2, error) {
|
||||
blob, err := db.Get(append([]byte("XDPoS-V2-"), hash[:]...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snap := new(SnapshotV2)
|
||||
if err := json.Unmarshal(blob, snap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// store inserts the SnapshotV2 into the database.
|
||||
func storeSnapshot(s *SnapshotV2, db ethdb.Database) error {
|
||||
blob, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Put(append([]byte("XDPoS-V2-"), s.Hash[:]...), blob)
|
||||
}
|
||||
|
||||
// retrieves master nodes list in map type
|
||||
func (s *SnapshotV2) GetMappedMasterNodes() map[common.Address]struct{} {
|
||||
ms := make(map[common.Address]struct{})
|
||||
for _, n := range s.NextEpochMasterNodes {
|
||||
ms[n] = struct{}{}
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
func (s *SnapshotV2) IsMasterNodes(address common.Address) bool {
|
||||
for _, n := range s.NextEpochMasterNodes {
|
||||
if n.String() == address.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// snapshot retrieves the authorization snapshot at a given point in time.
|
||||
func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64, isGapNumber bool) (*SnapshotV2, error) {
|
||||
var gapBlockNum uint64
|
||||
if isGapNumber {
|
||||
gapBlockNum = number
|
||||
} else {
|
||||
gapBlockNum = number - number%x.config.Epoch - x.config.Gap
|
||||
}
|
||||
|
||||
gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash()
|
||||
log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex())
|
||||
|
||||
// If an in-memory SnapshotV2 was found, use that
|
||||
if s, ok := x.snapshots.Get(gapBlockHash); ok {
|
||||
snap := s.(*SnapshotV2)
|
||||
log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash)
|
||||
return snap, nil
|
||||
}
|
||||
// If an on-disk checkpoint snapshot can be found, use that
|
||||
snap, err := loadSnapshot(x.db, gapBlockHash)
|
||||
if err != nil {
|
||||
log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash)
|
||||
x.snapshots.Add(snap.Hash, snap)
|
||||
return snap, nil
|
||||
}
|
||||
|
|
|
|||
46
consensus/XDPoS/engines/engine_v2/snapshot_test.go
Normal file
46
consensus/XDPoS/engines/engine_v2/snapshot_test.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb/leveldb"
|
||||
)
|
||||
|
||||
func TestGetMasterNodes(t *testing.T) {
|
||||
masterNodes := []common.Address{{0x4}, {0x3}, {0x2}, {0x1}}
|
||||
snap := newSnapshot(1, common.Hash{}, masterNodes)
|
||||
|
||||
for _, address := range masterNodes {
|
||||
if _, ok := snap.GetMappedMasterNodes()[address]; !ok {
|
||||
t.Error("should get master node from map", address.Hex(), snap.GetMappedMasterNodes())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreLoadSnapshot(t *testing.T) {
|
||||
snap := newSnapshot(1, common.Hash{0x1}, nil)
|
||||
dir, err := ioutil.TempDir("", "snapshot-test")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("can't create temporary directory: %v", err))
|
||||
}
|
||||
db, err := leveldb.New(dir, 256, 0, "")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("can't create temporary database: %v", err))
|
||||
}
|
||||
lddb := rawdb.NewDatabase(db)
|
||||
|
||||
err = storeSnapshot(snap, lddb)
|
||||
if err != nil {
|
||||
t.Error("store snapshot failed", err)
|
||||
}
|
||||
|
||||
restoredSnapshot, err := loadSnapshot(lddb, snap.Hash)
|
||||
if err != nil || restoredSnapshot.Hash != snap.Hash {
|
||||
t.Error("load snapshot failed", err)
|
||||
}
|
||||
}
|
||||
88
consensus/XDPoS/engines/engine_v2/testing_utils.go
Normal file
88
consensus/XDPoS/engines/engine_v2/testing_utils.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
)
|
||||
|
||||
/*
|
||||
Testing tools
|
||||
*/
|
||||
|
||||
func (x *XDPoS_v2) SetNewRoundFaker(blockChainReader consensus.ChainReader, newRound types.Round, resetTimer bool) {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
// Reset a bunch of things
|
||||
if resetTimer {
|
||||
x.timeoutWorker.Reset(blockChainReader)
|
||||
}
|
||||
x.currentRound = newRound
|
||||
}
|
||||
|
||||
// for test only
|
||||
func (x *XDPoS_v2) ProcessQCFaker(chain consensus.ChainReader, qc *types.QuorumCert) error {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
return x.processQC(chain, qc)
|
||||
}
|
||||
|
||||
// Utils for test to check currentRound value
|
||||
func (x *XDPoS_v2) GetCurrentRoundFaker() types.Round {
|
||||
x.lock.RLock()
|
||||
defer x.lock.RUnlock()
|
||||
return x.currentRound
|
||||
}
|
||||
|
||||
// Utils for test to get current Pool size
|
||||
func (x *XDPoS_v2) GetVotePoolSizeFaker(vote *types.Vote) int {
|
||||
return x.votePool.Size(vote)
|
||||
}
|
||||
|
||||
// Utils for test to get Timeout Pool Size
|
||||
func (x *XDPoS_v2) GetTimeoutPoolSizeFaker(timeout *types.Timeout) int {
|
||||
return x.timeoutPool.Size(timeout)
|
||||
}
|
||||
|
||||
// WARN: This function is designed for testing purpose only!
|
||||
// Utils for test to check currentRound values
|
||||
func (x *XDPoS_v2) GetPropertiesFaker() (types.Round, *types.QuorumCert, *types.QuorumCert, *types.TimeoutCert, types.Round, *types.BlockInfo) {
|
||||
x.lock.RLock()
|
||||
defer x.lock.RUnlock()
|
||||
return x.currentRound, x.lockQuorumCert, x.highestQuorumCert, x.highestTimeoutCert, x.highestVotedRound, x.highestCommitBlock
|
||||
}
|
||||
|
||||
// WARN: This function is designed for testing purpose only!
|
||||
// Utils for tests to set engine specific values
|
||||
func (x *XDPoS_v2) SetPropertiesFaker(highestQC *types.QuorumCert, highestTC *types.TimeoutCert) {
|
||||
x.highestQuorumCert = highestQC
|
||||
x.highestTimeoutCert = highestTC
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) HygieneVotePoolFaker() {
|
||||
x.hygieneVotePool()
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetVotePoolKeyListFaker() []string {
|
||||
return x.votePool.PoolObjKeysList()
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) HygieneTimeoutPoolFaker() {
|
||||
x.hygieneTimeoutPool()
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetTimeoutPoolKeyListFaker() []string {
|
||||
return x.timeoutPool.PoolObjKeysList()
|
||||
}
|
||||
|
||||
// Fake the signer address, the signing function is incompatible
|
||||
func (x *XDPoS_v2) AuthorizeFaker(signer common.Address) {
|
||||
x.signLock.Lock()
|
||||
defer x.signLock.Unlock()
|
||||
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetForensicsFaker() *Forensics {
|
||||
return x.ForensicsProcessor
|
||||
}
|
||||
251
consensus/XDPoS/engines/engine_v2/timeout.go
Normal file
251
consensus/XDPoS/engines/engine_v2/timeout.go
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
func (x *XDPoS_v2) timeoutHandler(blockChainReader consensus.ChainReader, timeout *types.Timeout) error {
|
||||
// checkRoundNumber
|
||||
if timeout.Round != x.currentRound {
|
||||
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
|
||||
Type: "timeout",
|
||||
IncomingRound: timeout.Round,
|
||||
CurrentRound: x.currentRound,
|
||||
}
|
||||
}
|
||||
// Collect timeout, generate TC
|
||||
isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout)
|
||||
log.Debug("[timeoutHandler] collect timeout", "number", numberOfTimeoutsInPool)
|
||||
|
||||
// Threshold reached
|
||||
if isThresholdReached {
|
||||
log.Info(fmt.Sprintf("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool))
|
||||
err := x.onTimeoutPoolThresholdReached(blockChainReader, pooledTimeouts, timeout, timeout.GapNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// clean up timeout message, regardless its GapNumber or round
|
||||
x.timeoutPool.Clear()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Function that will be called by timeoutPool when it reached threshold.
|
||||
In the engine v2, we will need to:
|
||||
1. Genrate TC
|
||||
2. processTC()
|
||||
3. generateSyncInfo()
|
||||
*/
|
||||
func (x *XDPoS_v2) onTimeoutPoolThresholdReached(blockChainReader consensus.ChainReader, pooledTimeouts map[common.Hash]utils.PoolObj, currentTimeoutMsg utils.PoolObj, gapNumber uint64) error {
|
||||
signatures := []types.Signature{}
|
||||
for _, v := range pooledTimeouts {
|
||||
signatures = append(signatures, v.(*types.Timeout).Signature)
|
||||
}
|
||||
// Genrate TC
|
||||
timeoutCert := &types.TimeoutCert{
|
||||
Round: currentTimeoutMsg.(*types.Timeout).Round,
|
||||
Signatures: signatures,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
// Process TC
|
||||
err := x.processTC(blockChainReader, timeoutCert)
|
||||
if err != nil {
|
||||
log.Error("Error while processing TC in the Timeout handler after reaching pool threshold", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures), "GapNumber", gapNumber, "Error", err)
|
||||
return err
|
||||
}
|
||||
// Generate and broadcast syncInfo
|
||||
syncInfo := x.getSyncInfo()
|
||||
x.broadcastToBftChannel(syncInfo)
|
||||
|
||||
log.Info("Successfully processed the timeout message and produced TC & SyncInfo!", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.TimeoutCert) error {
|
||||
/*
|
||||
1. Get epoch master node list by gapNumber
|
||||
2. Check number of signatures > threshold, as well as it's format. (Same as verifyQC)
|
||||
2. Verify signer signature: (List of signatures)
|
||||
- Use ecRecover to get the public key
|
||||
- Use the above public key to find out the xdc address
|
||||
- Use the above xdc address to check against the master node list from step 1(For the received TC epoch)
|
||||
*/
|
||||
snap, err := x.getSnapshot(chain, timeoutCert.GapNumber, true)
|
||||
if err != nil {
|
||||
log.Error("[verifyTC] Fail to get snapshot when verifying TC!", "TCGapNumber", timeoutCert.GapNumber)
|
||||
return fmt.Errorf("[verifyTC] Unable to get snapshot")
|
||||
}
|
||||
if snap == nil || len(snap.NextEpochMasterNodes) == 0 {
|
||||
log.Error("[verifyTC] Something wrong with the snapshot from gapNumber", "messageGapNumber", timeoutCert.GapNumber, "snapshot", snap)
|
||||
return fmt.Errorf("Empty master node lists from snapshot")
|
||||
}
|
||||
|
||||
if timeoutCert == nil {
|
||||
log.Warn("[verifyTC] TC is Nil")
|
||||
return utils.ErrInvalidTC
|
||||
} else if timeoutCert.Signatures == nil || (len(timeoutCert.Signatures) < x.config.V2.CertThreshold) {
|
||||
log.Warn("[verifyTC] Invalid TC Signature is nil or empty", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures))
|
||||
return utils.ErrInvalidTC
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(timeoutCert.Signatures))
|
||||
var haveError error
|
||||
|
||||
signedTimeoutObj := types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: timeoutCert.Round,
|
||||
GapNumber: timeoutCert.GapNumber,
|
||||
})
|
||||
|
||||
for _, signature := range timeoutCert.Signatures {
|
||||
go func(sig types.Signature) {
|
||||
defer wg.Done()
|
||||
verified, _, err := x.verifyMsgSignature(signedTimeoutObj, sig, snap.NextEpochMasterNodes)
|
||||
if err != nil {
|
||||
log.Error("[verifyTC] Error while verfying TC message signatures", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures), "Error", err)
|
||||
haveError = fmt.Errorf("Error while verfying TC message signatures")
|
||||
return
|
||||
}
|
||||
if !verified {
|
||||
log.Warn("[verifyTC] Signature not verified doing TC verification", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures))
|
||||
haveError = fmt.Errorf("Fail to verify TC due to signature mis-match")
|
||||
return
|
||||
}
|
||||
}(signature)
|
||||
}
|
||||
wg.Wait()
|
||||
if haveError != nil {
|
||||
return haveError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
1. Update highestTC
|
||||
2. Check TC round >= node's currentRound. If yes, call setNewRound
|
||||
*/
|
||||
func (x *XDPoS_v2) processTC(blockChainReader consensus.ChainReader, timeoutCert *types.TimeoutCert) error {
|
||||
if timeoutCert.Round > x.highestTimeoutCert.Round {
|
||||
x.highestTimeoutCert = timeoutCert
|
||||
}
|
||||
if timeoutCert.Round >= x.currentRound {
|
||||
x.setNewRound(blockChainReader, timeoutCert.Round+1)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate and send timeout into BFT channel.
|
||||
/*
|
||||
1. timeout.round = currentRound
|
||||
2. Sign the signature
|
||||
3. send to broadcast channel
|
||||
*/
|
||||
func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error {
|
||||
// Construct the gapNumber
|
||||
var gapNumber uint64
|
||||
currentBlockHeader := chain.CurrentHeader()
|
||||
isEpochSwitch, epochNum, err := x.isEpochSwitchAtRound(x.currentRound, currentBlockHeader)
|
||||
if err != nil {
|
||||
log.Error("[sendTimeout] Error while checking if the currentBlock is epoch switch", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
|
||||
return err
|
||||
}
|
||||
|
||||
if isEpochSwitch {
|
||||
// Notice this +1 is because we expect a block whos is the child of currentHeader
|
||||
currentNumber := currentBlockHeader.Number.Uint64() + 1
|
||||
gapNumber = currentNumber - currentNumber%x.config.Epoch - x.config.Gap
|
||||
log.Debug("[sendTimeout] is epoch switch when sending out timeout message", "currentNumber", currentNumber, "gapNumber", gapNumber)
|
||||
} else {
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentBlockHeader, currentBlockHeader.Hash())
|
||||
if err != nil {
|
||||
log.Error("[sendTimeout] Error when trying to get current epoch switch info for a non-epoch block", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
|
||||
}
|
||||
gapNumber = epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() - epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()%x.config.Epoch - x.config.Gap
|
||||
log.Debug("[sendTimeout] non-epoch-switch block found its epoch block and calculated the gapNumber", "epochSwitchInfo.EpochSwitchBlockInfo.Number", epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64(), "gapNumber", gapNumber)
|
||||
}
|
||||
|
||||
signedHash, err := x.signSignature(types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: x.currentRound,
|
||||
GapNumber: gapNumber,
|
||||
}))
|
||||
if err != nil {
|
||||
log.Error("[sendTimeout] signSignature when sending out TC", "Error", err)
|
||||
return err
|
||||
}
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: x.currentRound,
|
||||
Signature: signedHash,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
log.Info("[sendTimeout] Timeout message generated, ready to send!", "timeoutMsgRound", timeoutMsg.Round, "timeoutMsgGapNumber", timeoutMsg.GapNumber)
|
||||
err = x.timeoutHandler(chain, timeoutMsg)
|
||||
if err != nil {
|
||||
log.Error("TimeoutHandler error", "TimeoutRound", timeoutMsg.Round, "Error", err)
|
||||
return err
|
||||
}
|
||||
x.broadcastToBftChannel(timeoutMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Function that will be called by timer when countdown reaches its threshold.
|
||||
In the engine v2, we would need to broadcast timeout messages to other peers
|
||||
*/
|
||||
func (x *XDPoS_v2) OnCountdownTimeout(time time.Time, chain interface{}) error {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
|
||||
// Check if we are within the master node list
|
||||
allow := x.allowedToSend(chain.(consensus.ChainReader), chain.(consensus.ChainReader).CurrentHeader(), "timeout")
|
||||
if !allow {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := x.sendTimeout(chain.(consensus.ChainReader))
|
||||
if err != nil {
|
||||
log.Error("Error while sending out timeout message at time: ", time)
|
||||
return err
|
||||
}
|
||||
|
||||
x.timeoutCount++
|
||||
if x.timeoutCount%x.config.V2.TimeoutSyncThreshold == 0 {
|
||||
log.Info("[OnCountdownTimeout] timeout sync threadhold reached, send syncInfo message")
|
||||
syncInfo := x.getSyncInfo()
|
||||
x.broadcastToBftChannel(syncInfo)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) hygieneTimeoutPool() {
|
||||
x.lock.RLock()
|
||||
currentRound := x.currentRound
|
||||
x.lock.RUnlock()
|
||||
timeoutPoolKeys := x.timeoutPool.PoolObjKeysList()
|
||||
|
||||
// Extract round number
|
||||
for _, k := range timeoutPoolKeys {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
if err != nil {
|
||||
log.Error("[hygieneTimeoutPool] Error while trying to get keyedRound inside pool", "Error", err)
|
||||
continue
|
||||
}
|
||||
// Clean up any timeouts round that is 10 rounds older
|
||||
if keyedRound < int64(currentRound)-utils.PoolHygieneRound {
|
||||
log.Debug("[hygieneTimeoutPool] Cleaned timeout pool at round", "Round", keyedRound, "CurrentRound", currentRound, "Key", k)
|
||||
x.timeoutPool.ClearByPoolKey(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
143
consensus/XDPoS/engines/engine_v2/utils.go
Normal file
143
consensus/XDPoS/engines/engine_v2/utils.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
func sigHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := sha3.NewKeccak256()
|
||||
|
||||
err := rlp.Encode(hasher, []interface{}{
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra,
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
header.Validators,
|
||||
header.Penalties,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("Fail to encode", err)
|
||||
}
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
}
|
||||
|
||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||
// If the signature's already cached, return that
|
||||
hash := header.Hash()
|
||||
if address, known := sigcache.Get(hash); known {
|
||||
return address.(common.Address), nil
|
||||
}
|
||||
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), header.Validator)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
|
||||
sigcache.Add(hash, signer)
|
||||
return signer, nil
|
||||
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header. Only used for v1 last block
|
||||
func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
func UniqueSignatures(signatureSlice []types.Signature) ([]types.Signature, []types.Signature) {
|
||||
keys := make(map[string]bool)
|
||||
list := []types.Signature{}
|
||||
duplicates := []types.Signature{}
|
||||
for _, signature := range signatureSlice {
|
||||
hexOfSig := common.Bytes2Hex(signature)
|
||||
if _, value := keys[hexOfSig]; !value {
|
||||
keys[hexOfSig] = true
|
||||
list = append(list, signature)
|
||||
} else {
|
||||
duplicates = append(duplicates, signature)
|
||||
}
|
||||
}
|
||||
return list, duplicates
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) signSignature(signingHash common.Hash) (types.Signature, error) {
|
||||
// Don't hold the signFn for the whole signing operation
|
||||
x.signLock.RLock()
|
||||
signer, signFn := x.signer, x.signFn
|
||||
x.signLock.RUnlock()
|
||||
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, signingHash.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error while signing hash")
|
||||
}
|
||||
return signedHash, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signature types.Signature, masternodes []common.Address) (bool, common.Address, error) {
|
||||
var signerAddress common.Address
|
||||
if len(masternodes) == 0 {
|
||||
return false, signerAddress, fmt.Errorf("Empty masternode list detected when verifying message signatures")
|
||||
}
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature)
|
||||
if err != nil {
|
||||
return false, signerAddress, fmt.Errorf("Error while verifying message: %v", err)
|
||||
}
|
||||
|
||||
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
for _, mn := range masternodes {
|
||||
if mn == signerAddress {
|
||||
return true, signerAddress, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, signerAddress, fmt.Errorf("Masternodes list does not contain signer address, Signer address: %v", signerAddress.Hex())
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) getExtraFields(header *types.Header) (*types.QuorumCert, types.Round, []common.Address, error) {
|
||||
|
||||
var masternodes []common.Address
|
||||
|
||||
// last v1 block
|
||||
if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
masternodes = decodeMasternodesFromHeaderExtra(header)
|
||||
return nil, types.Round(0), masternodes, nil
|
||||
}
|
||||
|
||||
// v2 block
|
||||
masternodes = x.GetMasternodesFromEpochSwitchHeader(header)
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
return nil, types.Round(0), masternodes, err
|
||||
}
|
||||
return decodedExtraField.QuorumCert, decodedExtraField.Round, masternodes, nil
|
||||
}
|
||||
174
consensus/XDPoS/engines/engine_v2/verifyHeader.go
Normal file
174
consensus/XDPoS/engines/engine_v2/verifyHeader.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Verify individual header
|
||||
func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
||||
// If we're running a engine faking, accept any block as valid
|
||||
if x.config.V2.SkipV2Validation {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !x.isInitilised {
|
||||
if err := x.initial(chain, header); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, check := x.verifiedHeaders.Get(header.Hash())
|
||||
if check {
|
||||
return nil
|
||||
}
|
||||
|
||||
if header.Number == nil {
|
||||
return utils.ErrUnknownBlock
|
||||
}
|
||||
|
||||
if len(header.Validator) == 0 {
|
||||
return consensus.ErrNoValidatorSignature
|
||||
}
|
||||
|
||||
if fullVerify {
|
||||
// Don't waste time checking blocks from the future
|
||||
if header.Time.Int64() > time.Now().Unix() {
|
||||
return consensus.ErrFutureBlock
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the block's timestamp isn't too close to it's parent
|
||||
var parent *types.Header
|
||||
number := header.Number.Uint64()
|
||||
|
||||
if len(parents) > 0 {
|
||||
parent = parents[len(parents)-1]
|
||||
} else {
|
||||
parent = chain.GetHeader(header.ParentHash, number-1)
|
||||
}
|
||||
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time.Uint64()+uint64(x.config.V2.MinePeriod) > header.Time.Uint64() {
|
||||
return utils.ErrInvalidTimestamp
|
||||
}
|
||||
|
||||
// Verify this is truely a v2 block first
|
||||
quorumCert, round, _, err := x.getExtraFields(header)
|
||||
if err != nil {
|
||||
return utils.ErrInvalidV2Extra
|
||||
}
|
||||
if round <= quorumCert.ProposedBlockInfo.Round {
|
||||
return utils.ErrRoundInvalid
|
||||
}
|
||||
|
||||
err = x.verifyQC(chain, quorumCert, parent)
|
||||
if err != nil {
|
||||
log.Warn("[verifyHeader] fail to verify QC", "QCNumber", quorumCert.ProposedBlockInfo.Number, "QCsigLength", len(quorumCert.Signatures))
|
||||
return err
|
||||
}
|
||||
// Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
|
||||
if !bytes.Equal(header.Nonce[:], utils.NonceAuthVote) && !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
|
||||
return utils.ErrInvalidVote
|
||||
}
|
||||
// Ensure that the mix digest is zero as we don't have fork protection currently
|
||||
if header.MixDigest != (common.Hash{}) {
|
||||
return utils.ErrInvalidMixDigest
|
||||
}
|
||||
// Ensure that the block doesn't contain any uncles which are meaningless in XDPoS_v1
|
||||
if header.UncleHash != utils.UncleHash {
|
||||
return utils.ErrInvalidUncleHash
|
||||
}
|
||||
|
||||
if header.Difficulty.Cmp(big.NewInt(1)) != 0 {
|
||||
return utils.ErrInvalidDifficulty
|
||||
}
|
||||
|
||||
var masterNodes []common.Address
|
||||
isEpochSwitch, _, err := x.IsEpochSwitch(header) // Verify v2 block that is on the epoch switch
|
||||
if err != nil {
|
||||
log.Error("[verifyHeader] error when checking if header is epoch switch header", "Hash", header.Hash(), "Number", header.Number, "Error", err)
|
||||
return err
|
||||
}
|
||||
if isEpochSwitch {
|
||||
if !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
|
||||
return utils.ErrInvalidCheckpointVote
|
||||
}
|
||||
if header.Validators == nil || len(header.Validators) == 0 {
|
||||
return utils.ErrEmptyEpochSwitchValidators
|
||||
}
|
||||
if len(header.Validators)%common.AddressLength != 0 {
|
||||
return utils.ErrInvalidCheckpointSigners
|
||||
}
|
||||
|
||||
localMasterNodes, localPenalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash)
|
||||
masterNodes = localMasterNodes
|
||||
if err != nil {
|
||||
log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash())
|
||||
return err
|
||||
}
|
||||
|
||||
validatorsAddress := common.ExtractAddressFromBytes(header.Validators)
|
||||
if !utils.CompareSignersLists(localMasterNodes, validatorsAddress) {
|
||||
return utils.ErrValidatorsNotLegit
|
||||
}
|
||||
|
||||
penaltiesAddress := common.ExtractAddressFromBytes(header.Penalties)
|
||||
if !utils.CompareSignersLists(localPenalties, penaltiesAddress) {
|
||||
return utils.ErrPenaltiesNotLegit
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(header.Validators) != 0 {
|
||||
log.Warn("[verifyHeader] Validators shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "header.Validators", header.Validators)
|
||||
return utils.ErrInvalidFieldInNonEpochSwitch
|
||||
}
|
||||
if len(header.Penalties) != 0 {
|
||||
log.Warn("[verifyHeader] Penalties shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "header.Penalties", header.Penalties)
|
||||
return utils.ErrInvalidFieldInNonEpochSwitch
|
||||
}
|
||||
masterNodes = x.GetMasternodes(chain, header)
|
||||
}
|
||||
|
||||
// If all checks passed, validate any special fields for hard forks
|
||||
if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check its validator
|
||||
verified, validatorAddress, err := x.verifyMsgSignature(sigHash(header), header.Validator, masterNodes)
|
||||
if err != nil {
|
||||
for index, mn := range masterNodes {
|
||||
log.Error("[verifyHeader] masternode list during validator verification", "Masternode Address", mn.Hex(), "index", index)
|
||||
}
|
||||
log.Error("[verifyHeader] Error while verifying header validator signature", "BlockNumber", header.Number, "Hash", header.Hash().Hex(), "validator in hex", common.ToHex(header.Validator))
|
||||
return err
|
||||
}
|
||||
if !verified {
|
||||
log.Warn("[verifyHeader] Fail to verify the block validator as the validator address not within the masternode list", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex())
|
||||
return utils.ErrValidatorNotWithinMasternodes
|
||||
}
|
||||
if validatorAddress != header.Coinbase {
|
||||
log.Warn("[verifyHeader] Header validator and coinbase address not match", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex(), "coinbase", header.Coinbase.Hex())
|
||||
return utils.ErrCoinbaseAndValidatorMismatch
|
||||
}
|
||||
// Check the proposer is the leader
|
||||
curIndex := utils.Position(masterNodes, validatorAddress)
|
||||
leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes))
|
||||
if masterNodes[leaderIndex] != validatorAddress {
|
||||
log.Warn("[verifyHeader] Invalid blocker proposer, not its turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", header.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "validatorAddress", validatorAddress)
|
||||
return utils.ErrNotItsTurn
|
||||
}
|
||||
|
||||
x.verifiedHeaders.Add(header.Hash(), true)
|
||||
return nil
|
||||
}
|
||||
230
consensus/XDPoS/engines/engine_v2/vote.go
Normal file
230
consensus/XDPoS/engines/engine_v2/vote.go
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Once Hot stuff voting rule has verified, this node can then send vote
|
||||
func (x *XDPoS_v2) sendVote(chainReader consensus.ChainReader, blockInfo *types.BlockInfo) error {
|
||||
// First step: Update the highest Voted round
|
||||
// Second step: Generate the signature by using node's private key(The signature is the blockInfo signature)
|
||||
// Third step: Construct the vote struct with the above signature & blockinfo struct
|
||||
// Forth step: Send the vote to broadcast channel
|
||||
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chainReader, nil, blockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("getEpochSwitchInfo when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err)
|
||||
return err
|
||||
}
|
||||
epochSwitchNumber := epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()
|
||||
gapNumber := epochSwitchNumber - epochSwitchNumber%x.config.Epoch - x.config.Gap
|
||||
signedHash, err := x.signSignature(types.VoteSigHash(&types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: gapNumber,
|
||||
}))
|
||||
if err != nil {
|
||||
log.Error("signSignature when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
x.highestVotedRound = x.currentRound
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
|
||||
err = x.voteHandler(chainReader, voteMsg)
|
||||
if err != nil {
|
||||
log.Error("sendVote error", "BlockInfoHash", blockInfo.Hash, "Error", err)
|
||||
return err
|
||||
}
|
||||
x.broadcastToBftChannel(voteMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *types.Vote) error {
|
||||
|
||||
// checkRoundNumber
|
||||
if (voteMsg.ProposedBlockInfo.Round != x.currentRound) && (voteMsg.ProposedBlockInfo.Round != x.currentRound+1) {
|
||||
return &utils.ErrIncomingMessageRoundTooFarFromCurrentRound{
|
||||
Type: "vote",
|
||||
IncomingRound: voteMsg.ProposedBlockInfo.Round,
|
||||
CurrentRound: x.currentRound,
|
||||
}
|
||||
}
|
||||
|
||||
// Collect vote
|
||||
thresholdReached, numberOfVotesInPool, pooledVotes := x.votePool.Add(voteMsg)
|
||||
log.Debug("[voteHandler] collect votes", "number", numberOfVotesInPool)
|
||||
if thresholdReached {
|
||||
log.Info(fmt.Sprintf("[voteHandler] Vote pool threashold reached: %v, number of items in the pool: %v", thresholdReached, numberOfVotesInPool))
|
||||
|
||||
// Check if the block already exist, otherwise we try luck with the next vote
|
||||
proposedBlockHeader := chain.GetHeaderByHash(voteMsg.ProposedBlockInfo.Hash)
|
||||
if proposedBlockHeader == nil {
|
||||
log.Warn("[voteHandler] The proposed block from vote message does not exist yet, wait for the next vote to try again", "blockNum", voteMsg.ProposedBlockInfo.Number, "Hash", voteMsg.ProposedBlockInfo.Hash, "Round", voteMsg.ProposedBlockInfo.Round)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := x.VerifyBlockInfo(chain, voteMsg.ProposedBlockInfo, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Function that will be called by votePool when it reached threshold.
|
||||
In the engine v2, we will need to generate and process QC
|
||||
*/
|
||||
func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, pooledVotes map[common.Hash]utils.PoolObj, currentVoteMsg utils.PoolObj, proposedBlockHeader *types.Header) error {
|
||||
|
||||
masternodes := x.GetMasternodes(chain, proposedBlockHeader)
|
||||
|
||||
// Filter out non-Master nodes signatures
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(pooledVotes))
|
||||
signatureSlice := make([]types.Signature, len(pooledVotes))
|
||||
counter := 0
|
||||
for h, vote := range pooledVotes {
|
||||
go func(hash common.Hash, v *types.Vote, i int) {
|
||||
defer wg.Done()
|
||||
verified, _, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{
|
||||
ProposedBlockInfo: v.ProposedBlockInfo,
|
||||
GapNumber: v.GapNumber,
|
||||
}), v.Signature, masternodes)
|
||||
if !verified || err != nil {
|
||||
log.Warn("[onVotePoolThresholdReached] Skip not verified vote signatures when building QC", "Error", err.Error(), "verified", verified)
|
||||
} else {
|
||||
signatureSlice[i] = v.Signature
|
||||
}
|
||||
}(h, vote.(*types.Vote), counter)
|
||||
counter++
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// The signature list may contain empty entey. we only care the ones with values
|
||||
var validSignatureSlice []types.Signature
|
||||
for _, v := range signatureSlice {
|
||||
if len(v) != 0 {
|
||||
validSignatureSlice = append(validSignatureSlice, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip and wait for the next vote to process again if valid votes is less than what we required
|
||||
if len(validSignatureSlice) < x.config.V2.CertThreshold {
|
||||
log.Warn("[onVotePoolThresholdReached] Not enough valid signatures to generate QC", "VotesSignaturesAfterFilter", validSignatureSlice, "NumberOfValidVotes", len(validSignatureSlice), "NumberOfVotes", len(pooledVotes))
|
||||
return nil
|
||||
}
|
||||
// Genrate QC
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: currentVoteMsg.(*types.Vote).ProposedBlockInfo,
|
||||
Signatures: validSignatureSlice,
|
||||
GapNumber: currentVoteMsg.(*types.Vote).GapNumber,
|
||||
}
|
||||
err := x.processQC(chain, quorumCert)
|
||||
if err != nil {
|
||||
log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Successfully processed the vote and produced QC!", "QcRound", quorumCert.ProposedBlockInfo.Round, "QcNumOfSig", len(quorumCert.Signatures), "QcHash", quorumCert.ProposedBlockInfo.Hash, "QcNumber", quorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hot stuff rule to decide whether this node is eligible to vote for the received block
|
||||
func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, blockInfo *types.BlockInfo, quorumCert *types.QuorumCert) (bool, error) {
|
||||
// Make sure this node has not voted for this round.
|
||||
if x.currentRound <= x.highestVotedRound {
|
||||
log.Warn("Failed to pass the voting rule verification, currentRound is not large then highestVoteRound", "x.currentRound", x.currentRound, "x.highestVotedRound", x.highestVotedRound)
|
||||
return false, nil
|
||||
}
|
||||
/*
|
||||
HotStuff Voting rule:
|
||||
header's round == local current round, AND (one of the following two:)
|
||||
header's block extends lockQuorumCert's ProposedBlockInfo (we need a isExtending(block_a, block_b) function), OR
|
||||
header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round
|
||||
*/
|
||||
if blockInfo.Round != x.currentRound {
|
||||
log.Warn("Failed to pass the voting rule verification, blockRound is not equal currentRound", "x.currentRound", x.currentRound, "blockInfo.Round", blockInfo.Round)
|
||||
return false, nil
|
||||
}
|
||||
// XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule
|
||||
if x.lockQuorumCert == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if quorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, x.lockQuorumCert.ProposedBlockInfo)
|
||||
if err != nil {
|
||||
log.Error("Failed to pass the voting rule verification, error on isExtendingFromAncestor", "err", err, "blockInfo", blockInfo, "x.lockQuorumCert.ProposedBlockInfo", x.lockQuorumCert.ProposedBlockInfo)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !isExtended {
|
||||
log.Warn("Failed to pass the voting rule verification, block is not extended from ancestor", "blockInfo", blockInfo, "x.lockQuorumCert.ProposedBlockInfo", x.lockQuorumCert.ProposedBlockInfo)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) isExtendingFromAncestor(blockChainReader consensus.ChainReader, currentBlock *types.BlockInfo, ancestorBlock *types.BlockInfo) (bool, error) {
|
||||
blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64())
|
||||
|
||||
nextBlockHash := currentBlock.Hash
|
||||
for i := 0; i < blockNumDiff; i++ {
|
||||
parentBlock := blockChainReader.GetHeaderByHash(nextBlockHash)
|
||||
if parentBlock == nil {
|
||||
return false, fmt.Errorf("Could not find its parent block when checking whether currentBlock %v with hash %v is extending from the ancestorBlock %v", currentBlock.Number, currentBlock.Hash, ancestorBlock.Number)
|
||||
} else {
|
||||
nextBlockHash = parentBlock.ParentHash
|
||||
}
|
||||
log.Debug("[isExtendingFromAncestor] Found parent block", "CurrentBlockHash", currentBlock.Hash, "ParentHash", nextBlockHash)
|
||||
}
|
||||
|
||||
if nextBlockHash == ancestorBlock.Hash {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) hygieneVotePool() {
|
||||
x.lock.RLock()
|
||||
round := x.currentRound
|
||||
x.lock.RUnlock()
|
||||
votePoolKeys := x.votePool.PoolObjKeysList()
|
||||
|
||||
// Extract round number
|
||||
for _, k := range votePoolKeys {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
if err != nil {
|
||||
log.Error("[hygieneVotePool] Error while trying to get keyedRound inside pool", "Error", err)
|
||||
continue
|
||||
}
|
||||
// Clean up any votes round that is 10 rounds older
|
||||
if keyedRound < int64(round)-utils.PoolHygieneRound {
|
||||
log.Debug("[hygieneVotePool] Cleaned vote poll at round", "Round", keyedRound, "currentRound", round, "Key", k)
|
||||
x.votePool.ClearByPoolKey(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ var (
|
|||
NonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer
|
||||
NonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer.
|
||||
|
||||
UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
|
||||
UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
|
||||
InmemoryEpochs = 5 * EpochLength // Number of mapping from block to epoch switch infos to keep in memory
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -23,3 +24,8 @@ const (
|
|||
BlockSignersCacheLimit = 9000
|
||||
M2ByteLength = 4
|
||||
)
|
||||
|
||||
const (
|
||||
PeriodicJobPeriod = 60
|
||||
PoolHygieneRound = 10
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package utils
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
)
|
||||
|
||||
// Various error messages to mark blocks invalid. These should be private to
|
||||
// prevent engine specific errors from being referenced in the remainder of the
|
||||
|
|
@ -42,6 +47,9 @@ var (
|
|||
|
||||
ErrInvalidCheckpointPenalties = errors.New("invalid penalty list on checkpoint block")
|
||||
|
||||
ErrValidatorsNotLegit = errors.New("validators does not match what's stored in snapshot minutes its penalty")
|
||||
ErrPenaltiesNotLegit = errors.New("penalties does not match")
|
||||
|
||||
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
|
||||
ErrInvalidMixDigest = errors.New("non-zero mix digest")
|
||||
|
||||
|
|
@ -60,6 +68,9 @@ var (
|
|||
// be modified via out-of-range or non-contiguous headers.
|
||||
ErrInvalidVotingChain = errors.New("invalid voting chain")
|
||||
|
||||
ErrInvalidHeaderOrder = errors.New("invalid header order")
|
||||
ErrInvalidChild = errors.New("invalid header child")
|
||||
|
||||
// errUnauthorized is returned if a header is signed by a non-authorized entity.
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
|
||||
|
|
@ -71,4 +82,39 @@ var (
|
|||
ErrWaitTransactions = errors.New("waiting for transactions")
|
||||
|
||||
ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block")
|
||||
|
||||
ErrEmptyEpochSwitchValidators = errors.New("empty validators list on epoch switch block")
|
||||
|
||||
ErrInvalidV2Extra = errors.New("Invalid v2 extra in the block")
|
||||
ErrInvalidQC = errors.New("Invalid QC content")
|
||||
ErrInvalidTC = errors.New("Invalid TC content")
|
||||
ErrEmptyBlockInfoHash = errors.New("BlockInfo hash is empty")
|
||||
ErrInvalidFieldInNonEpochSwitch = errors.New("Invalid field exist in a non-epoch swtich block")
|
||||
ErrValidatorNotWithinMasternodes = errors.New("Validaotor address is not in the master node list")
|
||||
ErrCoinbaseAndValidatorMismatch = errors.New("Validaotor and coinbase address in header does not match")
|
||||
ErrNotItsTurn = errors.New("Not validator's turn to mine this block")
|
||||
|
||||
ErrRoundInvalid = errors.New("Invalid Round, it shall be bigger than QC round")
|
||||
|
||||
ErrAlreadyMined = errors.New("Already mined")
|
||||
)
|
||||
|
||||
type ErrIncomingMessageRoundNotEqualCurrentRound struct {
|
||||
Type string
|
||||
IncomingRound types.Round
|
||||
CurrentRound types.Round
|
||||
}
|
||||
|
||||
func (e *ErrIncomingMessageRoundNotEqualCurrentRound) Error() string {
|
||||
return fmt.Sprintf("%s message round number: %v does not match currentRound: %v", e.Type, e.IncomingRound, e.CurrentRound)
|
||||
}
|
||||
|
||||
type ErrIncomingMessageRoundTooFarFromCurrentRound struct {
|
||||
Type string
|
||||
IncomingRound types.Round
|
||||
CurrentRound types.Round
|
||||
}
|
||||
|
||||
func (e *ErrIncomingMessageRoundTooFarFromCurrentRound) Error() string {
|
||||
return fmt.Sprintf("%s message round number: %v is too far away from currentRound: %v", e.Type, e.IncomingRound, e.CurrentRound)
|
||||
}
|
||||
|
|
|
|||
93
consensus/XDPoS/utils/pool.go
Normal file
93
consensus/XDPoS/utils/pool.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
type PoolObj interface {
|
||||
Hash() common.Hash
|
||||
PoolKey() string
|
||||
}
|
||||
type Pool struct {
|
||||
objList map[string]map[common.Hash]PoolObj
|
||||
threshold int
|
||||
lock sync.RWMutex // Protects the pool fields
|
||||
}
|
||||
|
||||
func NewPool(threshold int) *Pool {
|
||||
return &Pool{
|
||||
objList: make(map[string]map[common.Hash]PoolObj),
|
||||
threshold: threshold,
|
||||
}
|
||||
}
|
||||
|
||||
// return true if it has reached threshold
|
||||
func (p *Pool) Add(obj PoolObj) (bool, int, map[common.Hash]PoolObj) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
poolKey := obj.PoolKey()
|
||||
objListKeyed, ok := p.objList[poolKey]
|
||||
if !ok {
|
||||
p.objList[poolKey] = make(map[common.Hash]PoolObj)
|
||||
objListKeyed = p.objList[poolKey]
|
||||
}
|
||||
objListKeyed[obj.Hash()] = obj
|
||||
numOfItems := len(objListKeyed)
|
||||
if numOfItems >= p.threshold {
|
||||
return true, numOfItems, objListKeyed
|
||||
}
|
||||
return false, numOfItems, objListKeyed
|
||||
}
|
||||
|
||||
func (p *Pool) Size(obj PoolObj) int {
|
||||
poolKey := obj.PoolKey()
|
||||
objListKeyed, ok := p.objList[poolKey]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return len(objListKeyed)
|
||||
}
|
||||
|
||||
func (p *Pool) PoolObjKeysList() []string {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
var keyList []string
|
||||
for key := range p.objList {
|
||||
keyList = append(keyList, key)
|
||||
}
|
||||
return keyList
|
||||
}
|
||||
|
||||
// Given the pool object, clear all object under the same pool key
|
||||
func (p *Pool) ClearPoolKeyByObj(obj PoolObj) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
poolKey := obj.PoolKey()
|
||||
delete(p.objList, poolKey)
|
||||
}
|
||||
|
||||
// Given the pool key, clean its content
|
||||
func (p *Pool) ClearByPoolKey(poolKey string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
delete(p.objList, poolKey)
|
||||
}
|
||||
|
||||
func (p *Pool) Clear() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.objList = make(map[string]map[common.Hash]PoolObj)
|
||||
}
|
||||
|
||||
func (p *Pool) SetThreshold(t int) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.threshold = t
|
||||
}
|
||||
64
consensus/XDPoS/utils/pool_test.go
Normal file
64
consensus/XDPoS/utils/pool_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPoolAdd(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pool := NewPool(2) // 2 is the cert threshold
|
||||
timeout1 := types.Timeout{Round: 1, Signature: []byte{1}}
|
||||
timeout2 := types.Timeout{Round: 1, Signature: []byte{2}}
|
||||
timeout3 := types.Timeout{Round: 1, Signature: []byte{3}}
|
||||
timeout4 := types.Timeout{Round: 1, Signature: []byte{4}}
|
||||
thresholdReached, numOfItems, pooledTimeouts := pool.Add(&timeout1)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(1, numOfItems)
|
||||
assert.False(thresholdReached)
|
||||
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout1)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.False(thresholdReached)
|
||||
// Duplicates should not be added
|
||||
assert.Equal(1, numOfItems)
|
||||
|
||||
// Should add the one that is not a duplicates
|
||||
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout2)
|
||||
assert.True(thresholdReached)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(2, numOfItems)
|
||||
|
||||
// Try to add one more to the same round, it should also trigger threshold
|
||||
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout3)
|
||||
assert.True(thresholdReached)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(3, numOfItems)
|
||||
|
||||
// Only after manually clearned the pool at its objKey, we shall not have any value for this particular key
|
||||
pool.ClearPoolKeyByObj(&timeout3)
|
||||
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout4)
|
||||
assert.False(thresholdReached)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(1, numOfItems)
|
||||
|
||||
pool = NewPool(3) // 3 is the cert size
|
||||
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout1)
|
||||
assert.False(thresholdReached)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(1, numOfItems)
|
||||
|
||||
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout2)
|
||||
assert.False(thresholdReached)
|
||||
assert.Equal(2, numOfItems)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
pool.Clear()
|
||||
|
||||
// Pool has been cleared. Start from 0 again
|
||||
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout3)
|
||||
assert.False(thresholdReached)
|
||||
assert.Equal(1, numOfItems)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
}
|
||||
|
|
@ -2,16 +2,14 @@ package utils
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
|
|
@ -52,100 +50,53 @@ func ExtractValidatorsFromBytes(byteValidators []byte) []int64 {
|
|||
return validators
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header.
|
||||
func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-ExtraVanity-ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
// Get m2 list from checkpoint block.
|
||||
func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
|
||||
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
|
||||
return nil, errors.New("This block is not checkpoint block epoc.")
|
||||
}
|
||||
// Get signers from this block.
|
||||
masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader)
|
||||
validators := ExtractValidatorsFromBytes(checkpointHeader.Validators)
|
||||
m1m2, _, err := GetM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
return map[common.Address]common.Address{}, err
|
||||
}
|
||||
return m1m2, nil
|
||||
}
|
||||
|
||||
func GetM1M2(masternodes []common.Address, validators []int64, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, uint64, error) {
|
||||
m1m2 := map[common.Address]common.Address{}
|
||||
maxMNs := len(masternodes)
|
||||
moveM2 := uint64(0)
|
||||
if len(validators) < maxMNs {
|
||||
return nil, moveM2, errors.New("len(m2) is less than len(m1)")
|
||||
}
|
||||
if maxMNs > 0 {
|
||||
isForked := config.IsTIPRandomize(currentHeader.Number)
|
||||
if isForked {
|
||||
moveM2 = ((currentHeader.Number.Uint64() % config.XDPoS.Epoch) / uint64(maxMNs)) % uint64(maxMNs)
|
||||
}
|
||||
for i, m1 := range masternodes {
|
||||
m2Index := uint64(validators[i] % int64(maxMNs))
|
||||
m2Index = (m2Index + moveM2) % uint64(maxMNs)
|
||||
m1m2[m1] = masternodes[m2Index]
|
||||
}
|
||||
}
|
||||
return m1m2, moveM2, nil
|
||||
}
|
||||
|
||||
// compare 2 signers lists
|
||||
// return true if they are same elements, otherwise return false
|
||||
func CompareSignersLists(list1 []common.Address, list2 []common.Address) bool {
|
||||
if len(list1) == 0 && len(list2) == 0 {
|
||||
l1 := make([]common.Address, len(list1))
|
||||
l2 := make([]common.Address, len(list2))
|
||||
|
||||
copy(l1, list1)
|
||||
copy(l2, list2)
|
||||
|
||||
if len(l1) == 0 && len(l2) == 0 {
|
||||
return true
|
||||
}
|
||||
sort.Slice(list1, func(i, j int) bool {
|
||||
return list1[i].String() <= list1[j].String()
|
||||
})
|
||||
sort.Slice(list2, func(i, j int) bool {
|
||||
return list2[i].String() <= list2[j].String()
|
||||
})
|
||||
return reflect.DeepEqual(list1, list2)
|
||||
}
|
||||
|
||||
// SignerFn is a signer callback function to request a hash to be signed by a
|
||||
// backing account.
|
||||
//type SignerFn func(accounts.Account, []byte) ([]byte, error)
|
||||
|
||||
// sigHash returns the hash which is used as input for the delegated-proof-of-stake
|
||||
// signing. It is the hash of the entire header apart from the 65 byte signature
|
||||
// contained at the end of the extra data.
|
||||
//
|
||||
// Note, the method requires the extra data to be at least 65 bytes, otherwise it
|
||||
// panics. This is done to avoid accidentally using both forms (signature present
|
||||
// or not), which could be abused to produce different hashes for the same header.
|
||||
func SigHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := sha3.NewKeccak256()
|
||||
|
||||
err := rlp.Encode(hasher, []interface{}{
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("Fail to encode", err)
|
||||
if len(l1) != len(l2) {
|
||||
return false
|
||||
}
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
|
||||
sort.Slice(l1, func(i, j int) bool {
|
||||
return l1[i].String() <= l1[j].String()
|
||||
})
|
||||
sort.Slice(l2, func(i, j int) bool {
|
||||
return l2[i].String() <= l2[j].String()
|
||||
})
|
||||
return reflect.DeepEqual(l1, l2)
|
||||
}
|
||||
|
||||
// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions)
|
||||
func DecodeBytesExtraFields(b []byte, val interface{}) error {
|
||||
if len(b) == 0 {
|
||||
return fmt.Errorf("extra field is 0 length")
|
||||
}
|
||||
switch b[0] {
|
||||
case 1:
|
||||
return fmt.Errorf("consensus version 1 is not applicable for decoding extra fields")
|
||||
case 2:
|
||||
return rlp.DecodeBytes(b[1:], val)
|
||||
default:
|
||||
return fmt.Errorf("consensus version %d is not defined", b[0])
|
||||
}
|
||||
}
|
||||
|
||||
func rlpHash(x interface{}) (h common.Hash) {
|
||||
hw := sha3.NewKeccak256()
|
||||
err := rlp.Encode(hw, x)
|
||||
if err != nil {
|
||||
log.Error("[rlpHash] Fail to hash item", "Error", err)
|
||||
}
|
||||
hw.Sum(h[:0])
|
||||
return h
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,11 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
)
|
||||
|
||||
func TestGetM1M2FromCheckpointHeader(t *testing.T) {
|
||||
masternodes := []common.Address{
|
||||
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
common.StringToAddress("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
|
||||
common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc"),
|
||||
}
|
||||
validators := []int64{
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
}
|
||||
epoch := uint64(900)
|
||||
config := ¶ms.ChainConfig{
|
||||
XDPoS: ¶ms.XDPoSConfig{
|
||||
Epoch: uint64(epoch),
|
||||
},
|
||||
}
|
||||
testMoveM2 := []uint64{0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2}
|
||||
//try from block 3410001 to 3410018
|
||||
for i := uint64(3464001); i <= 3464018; i++ {
|
||||
currentNumber := int64(i)
|
||||
currentHeader := &types.Header{
|
||||
Number: big.NewInt(currentNumber),
|
||||
}
|
||||
m1m2, moveM2, err := GetM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
t.Error("can't get m1m2", "err", err)
|
||||
}
|
||||
fmt.Printf("block: %v, moveM2: %v\n", currentHeader.Number.Int64(), moveM2)
|
||||
for _, k := range masternodes {
|
||||
fmt.Printf("m1: %v - m2: %v\n", k.Str(), m1m2[k].Str())
|
||||
}
|
||||
if moveM2 != testMoveM2[i-3464001] {
|
||||
t.Error("wrong moveM2", "currentNumber", currentNumber, "want", testMoveM2[i-3464001], "have", moveM2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareSignersLists(t *testing.T) {
|
||||
list1 := []common.Address{
|
||||
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
|
|
|
|||
|
|
@ -38,4 +38,10 @@ var (
|
|||
ErrFailValidatorSignature = errors.New("missing validator in header")
|
||||
|
||||
ErrNoValidatorSignature = errors.New("no validator in header")
|
||||
|
||||
ErrNotReadyToPropose = errors.New("not ready to propose, QC is not ready")
|
||||
|
||||
ErrNotReadyToMine = errors.New("Not ready to mine, it's not your turn")
|
||||
|
||||
ErrCoinbaseMismatch = errors.New("Block Coinbase address does not match its wallte address")
|
||||
)
|
||||
|
|
|
|||
75
consensus/tests/engine_v1_tests/authorised_test.go
Normal file
75
consensus/tests/engine_v1_tests/authorised_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsAuthorisedMNForConsensusV1(t *testing.T) {
|
||||
/*
|
||||
V1 consensus engine
|
||||
*/
|
||||
blockchain, _, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
|
||||
// Insert first Block 449
|
||||
t.Logf("Inserting block with propose at 449...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
|
||||
tx, err := voteTX(37117, 0, acc1Addr.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(449)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
assert.Nil(t, err)
|
||||
err = blockchain.InsertBlock(block449)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parentBlock = block449
|
||||
|
||||
// At block 449, we should not update signerList. we need to update it till block 450 gap block.
|
||||
// Acc3 is the default account that is on the signerList
|
||||
|
||||
engine := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
isAuthorisedMN := engine.IsAuthorisedAddress(blockchain, block449.Header(), acc3Addr)
|
||||
assert.True(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block449.Header(), acc1Addr)
|
||||
assert.False(t, isAuthorisedMN)
|
||||
|
||||
// Now, let's mine another block to trigger the GAP block signerList update
|
||||
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
|
||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(block450CoinbaseAddress),
|
||||
}
|
||||
block450, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450)
|
||||
assert.Nil(t, err)
|
||||
|
||||
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc3Addr)
|
||||
assert.False(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc1Addr)
|
||||
assert.True(t, isAuthorisedMN)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package consensus
|
||||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -6,13 +6,15 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Should NOT update signerList if not on the gap block
|
||||
func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
|
||||
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -26,11 +28,18 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
|||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
blockA, err := insertBlockTxs(blockchain, 401, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(401)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -50,14 +59,22 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
|||
|
||||
// Should call updateM1 at the gap block, and have the same snapshot values as the parent block if no SM transaction is involved
|
||||
func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
|
||||
blockchain, _, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, _, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Insert block 450
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block, err := insertBlock(blockchain, 450, blockCoinBase, parentBlock, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -78,7 +95,7 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
|
|||
//Should call updateM1 at gap block, and update the snapshot if there are SM transactions involved
|
||||
func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
||||
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
|
||||
// Insert first Block 449
|
||||
t.Logf("Inserting block with propose at 449...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
|
||||
|
|
@ -89,10 +106,18 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
|||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
block449, err := insertBlockTxs(blockchain, 449, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(449)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block449)
|
||||
assert.Nil(t, err)
|
||||
parentBlock = block449
|
||||
|
||||
signers, err := GetSnapshotSigner(blockchain, block449.Header())
|
||||
|
|
@ -113,11 +138,18 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
|||
// Now, let's mine another block to trigger the GAP block signerList update
|
||||
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
|
||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
block450, err := insertBlock(blockchain, 450, block450CoinbaseAddress, parentBlock, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(block450CoinbaseAddress),
|
||||
}
|
||||
block450, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(block450)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, block450.Header())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get signers")
|
||||
|
|
@ -136,7 +168,7 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
|||
//Should call updateM1 before gap block, and update the snapshot if there are SM transactions involved
|
||||
func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
||||
|
||||
blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Insert first Block 450 A
|
||||
t.Logf("Inserting block with propose at 450 A...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000450"
|
||||
|
|
@ -147,11 +179,18 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
|||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -165,7 +204,7 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
|||
// Should call updateM1 and update snapshot when a forked block(at gap block number) is inserted back into main chain (Edge case)
|
||||
func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
||||
|
||||
blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Check initial signer, by default, acc3 is in the signerList
|
||||
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
||||
if err != nil {
|
||||
|
|
@ -189,11 +228,18 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
}
|
||||
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, blockA.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -217,10 +263,18 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
}
|
||||
|
||||
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
|
||||
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
}
|
||||
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -240,8 +294,16 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
assert.Nil(t, err)
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -288,7 +350,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
|
||||
func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testing.T) {
|
||||
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
|
||||
state, err := blockchain.State()
|
||||
if err != nil {
|
||||
|
|
@ -316,10 +378,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
transferTransaction := transferTx(t, acc1Addr, 999)
|
||||
|
||||
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
state, err = blockchain.State()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get blockchain state")
|
||||
|
|
@ -351,10 +421,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
transferTransaction = transferTx(t, acc1Addr, 888)
|
||||
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
}
|
||||
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
state, err = blockchain.State()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get blockchain state")
|
||||
|
|
@ -378,11 +456,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||
if err != nil {
|
||||
|
|
@ -422,7 +507,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
}
|
||||
|
||||
func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Check initial signer, by default, acc3 is in the signerList
|
||||
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
||||
if err != nil {
|
||||
|
|
@ -440,10 +525,18 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
|||
// Insert normal blocks 450 A
|
||||
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block450A, err := insertBlock(blockchain, 450, blockCoinBase450A, parentBlock, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450A),
|
||||
}
|
||||
block450A, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450A)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Insert 451 A with vote
|
||||
blockCoinbase451A := "0xaaa0000000000000000000000000000000000451"
|
||||
|
|
@ -453,10 +546,18 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
|||
}
|
||||
|
||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
block451A, err := insertBlockTxs(blockchain, 451, blockCoinbase451A, block450A, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450A.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbase451A),
|
||||
}
|
||||
block451A, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451A)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// SignerList should be unchanged as the vote happen after GAP block
|
||||
signers, err = GetSnapshotSigner(blockchain, block451A.Header())
|
||||
|
|
@ -476,24 +577,48 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
|||
// Insert forked Block 450 B
|
||||
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
|
||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block450B, err := insertBlock(blockchain, 450, blockCoinBase450B, parentBlock, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
}
|
||||
block450B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
|
||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block452B, err := insertBlock(blockchain, 452, blockCoinBase452B, block451B, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(452)),
|
||||
ParentHash: block451B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase452B),
|
||||
}
|
||||
block452B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block452B)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, block452B.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -505,3 +630,63 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
|||
t.Fatalf("account 3 should sit in the signer list as previos block result")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
V2 Consensus
|
||||
*/
|
||||
/*
|
||||
// Pending for creating cross version blocks
|
||||
func TestV2UpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, int(config.XDPoS.Epoch)+GAP-2, config)
|
||||
// Insert first Block 1349
|
||||
t.Logf("Inserting block with propose at 1349...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000001349"
|
||||
tx, err := voteTX(37117, 0, acc1Addr.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(1349)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
block1349, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parentBlock = block1349
|
||||
|
||||
// Now, let's mine another block to trigger the GAP block signerList update
|
||||
block1350CoinbaseAddress := "0xaaa0000000000000000000000000000000001350"
|
||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(1350)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(block1350CoinbaseAddress),
|
||||
}
|
||||
block1350, err := insertBlock(blockchain, header)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
signers, err := GetSnapshotSigner(blockchain, block1350.Header())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get signers")
|
||||
}
|
||||
// Now, we voted acc 1 to be in the signerList, which will kick out acc3 because it has less funds
|
||||
if signers[acc3Addr.Hex()] == true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 3 should NOT sit in the signer list")
|
||||
}
|
||||
if signers[acc1Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 1 should sit in the signer list")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
package consensus
|
||||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Snapshot try to read before blockchain is written
|
||||
func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
||||
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
|
||||
state, err := blockchain.State()
|
||||
if err != nil {
|
||||
|
|
@ -39,10 +41,20 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
|||
transferTransaction := transferTx(t, acc1Addr, 999)
|
||||
|
||||
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
|
||||
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
state, err = blockchain.State()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get blockchain state")
|
||||
|
|
@ -74,10 +86,21 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
|||
transferTransaction = transferTx(t, acc1Addr, 888)
|
||||
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 2)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
Difficulty: big.NewInt(2),
|
||||
}
|
||||
|
||||
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
if blockchain.CurrentHeader().Hash() != block450B.Hash() {
|
||||
t.Fatalf("the block with higher difficulty should be current header")
|
||||
}
|
||||
|
|
@ -104,11 +127,19 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
|||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 3)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
Difficulty: big.NewInt(3),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||
if err != nil {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package consensus
|
||||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -6,14 +6,17 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
. "github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
@ -58,6 +61,16 @@ func debugMessage(backend *backends.SimulatedBackend, signers signersList, t *te
|
|||
}
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func RandStringBytes(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
|
||||
|
||||
// initial helper backend
|
||||
|
|
@ -223,23 +236,49 @@ func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testi
|
|||
return ms
|
||||
}
|
||||
|
||||
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block) {
|
||||
// V1 consensus engine
|
||||
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
// Preparation
|
||||
var err error
|
||||
// Authorise
|
||||
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
|
||||
backend := getCommonBackend(t, chainConfig)
|
||||
blockchain := backend.GetBlockChain()
|
||||
blockchain.Client = backend
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
|
||||
}
|
||||
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
|
||||
|
||||
currentBlock := blockchain.Genesis()
|
||||
|
||||
go func() {
|
||||
for range core.CheckpointCh {
|
||||
checkpointChanMsg := <-core.CheckpointCh
|
||||
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert initial blocks
|
||||
for i := 1; i <= numOfBlocks; i++ {
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(i)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
currentBlock = block
|
||||
}
|
||||
// Update Signer as there is no previous signer assigned
|
||||
|
|
@ -248,83 +287,96 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return blockchain, backend, currentBlock
|
||||
return blockchain, backend, currentBlock, signer, signFn
|
||||
}
|
||||
|
||||
// insert Block without transcation attached
|
||||
func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, difficulty int64) (*types.Block, error) {
|
||||
block, err := createXDPoSTestBlock(
|
||||
blockchain,
|
||||
parentBlock.Hash().Hex(),
|
||||
blockCoinBase, blockNum, nil,
|
||||
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
common.HexToHash(root),
|
||||
difficulty,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte) *types.Block {
|
||||
currentBlock := startingBlock
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(blockNumber)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
|
||||
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
|
||||
// reset extra
|
||||
header.Extra = []byte{}
|
||||
if len(header.Extra) < utils.ExtraVanity {
|
||||
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, utils.ExtraVanity-len(header.Extra))...)
|
||||
}
|
||||
header.Extra = header.Extra[:utils.ExtraVanity]
|
||||
var masternodes []common.Address
|
||||
// Place the test's signer address to the last
|
||||
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
|
||||
// masternodesFromV1LastEpoch = masternodes
|
||||
for _, masternode := range masternodes {
|
||||
header.Extra = append(header.Extra, masternode[:]...)
|
||||
}
|
||||
header.Extra = append(header.Extra, make([]byte, utils.ExtraSeal)...)
|
||||
|
||||
// Sign all the things for v1 block use v1 sigHash function
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, blockchain.Engine().(*XDPoS.XDPoS).SigHash(header).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error when sign last v1 block hash during test block creation"))
|
||||
}
|
||||
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
|
||||
}
|
||||
return block, nil
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Fail to create block in test helper, %v", err))
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
// insert Block with transcation attached
|
||||
func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, txs []*types.Transaction, root string, difficulty int64) (*types.Block, error) {
|
||||
block, err := createXDPoSTestBlock(
|
||||
blockchain,
|
||||
parentBlock.Hash().Hex(),
|
||||
blockCoinBase, blockNum, txs,
|
||||
"0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c",
|
||||
common.HexToHash(root),
|
||||
difficulty,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (*types.Block, error) {
|
||||
if customHeader.Extra == nil {
|
||||
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
|
||||
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
|
||||
}
|
||||
var difficulty *big.Int
|
||||
if customHeader.Difficulty == nil {
|
||||
difficulty = big.NewInt(1)
|
||||
} else {
|
||||
difficulty = customHeader.Difficulty
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// TODO: check if this is needed
|
||||
if len(txs) != 0 {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
|
||||
} else {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, difficulty int64) (*types.Block, error) {
|
||||
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
|
||||
//ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
||||
//Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc"
|
||||
extraByte, _ := hex.DecodeString(extraSubstring)
|
||||
header := types.Header{
|
||||
ParentHash: common.HexToHash(parentHash),
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
// ReceiptHash: types.EmptyRootHash,
|
||||
ReceiptHash: common.HexToHash(receiptHash),
|
||||
Root: root,
|
||||
Coinbase: common.HexToAddress(coinbase),
|
||||
Difficulty: big.NewInt(difficulty),
|
||||
Number: big.NewInt(int64(number)),
|
||||
ParentHash: customHeader.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
ReceiptHash: customHeader.ReceiptHash,
|
||||
Root: customHeader.Root,
|
||||
Coinbase: customHeader.Coinbase,
|
||||
Difficulty: difficulty,
|
||||
Number: customHeader.Number,
|
||||
GasLimit: 1200000000,
|
||||
Time: big.NewInt(int64(number * 10)),
|
||||
Extra: extraByte,
|
||||
Time: big.NewInt(time.Now().Unix()),
|
||||
Extra: customHeader.Extra,
|
||||
Validator: customHeader.Validator,
|
||||
Validators: customHeader.Validators,
|
||||
Penalties: customHeader.Penalties,
|
||||
}
|
||||
|
||||
var block *types.Block
|
||||
if len(txs) == 0 {
|
||||
block = types.NewBlockWithHeader(&header)
|
||||
} else {
|
||||
|
||||
// Prepare Receipt
|
||||
statedb, err := bc.StateAt(bc.GetBlockByNumber(uint64(number - 1)).Root()) //Get parent root
|
||||
statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v when get state", err)
|
||||
}
|
||||
gp := new(GasPool).AddGas(header.GasLimit)
|
||||
// usedGas := uint64(0)
|
||||
|
||||
var gasUsed = new(uint64)
|
||||
var receipts types.Receipts
|
||||
|
|
@ -338,7 +390,6 @@ func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number in
|
|||
}
|
||||
|
||||
header.GasUsed = *gasUsed
|
||||
|
||||
block = types.NewBlock(&header, txs, nil, receipts)
|
||||
}
|
||||
|
||||
262
consensus/tests/engine_v2_tests/adaptor_test.go
Normal file
262
consensus/tests/engine_v2_tests/adaptor_test.go
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
addressFromAdaptor, errorAdaptor := adaptor.Author(currentBlock.Header())
|
||||
if errorAdaptor != nil {
|
||||
t.Fatalf("Failed while trying to get Author from adaptor")
|
||||
}
|
||||
addressFromV1Engine, errV1 := adaptor.EngineV1.Author(currentBlock.Header())
|
||||
if errV1 != nil {
|
||||
t.Fatalf("Failed while trying to get Author from engine v1")
|
||||
}
|
||||
// Make sure the value is exactly the same as from V1 engine
|
||||
assert.Equal(t, addressFromAdaptor, addressFromV1Engine)
|
||||
|
||||
// Insert one more block to make it above 10, which means now we are on v2 of consensus engine
|
||||
// Insert block 901
|
||||
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(901)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: signer,
|
||||
}
|
||||
|
||||
header.Extra = generateV2Extra(1, currentBlock, signer, signFn, nil)
|
||||
|
||||
block901, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block901)
|
||||
assert.Nil(t, err)
|
||||
|
||||
addressFromAdaptor, errorAdaptor = adaptor.Author(block901.Header())
|
||||
if errorAdaptor != nil {
|
||||
t.Fatalf("Failed while trying to get Author from adaptor")
|
||||
}
|
||||
addressFromV2Engine, errV2 := adaptor.EngineV2.Author(block901.Header())
|
||||
if errV2 != nil {
|
||||
t.Fatalf("Failed while trying to get Author from engine v2")
|
||||
}
|
||||
// Make sure the value is exactly the same as from V2 engine
|
||||
assert.Equal(t, addressFromAdaptor, addressFromV2Engine)
|
||||
}
|
||||
|
||||
func TestAdaptorGetMasternodesFromCheckpointHeader(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
headerV1 := currentBlock.Header()
|
||||
headerV1.Extra = common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400")
|
||||
masternodesV1 := adaptor.GetMasternodesFromCheckpointHeader(headerV1)
|
||||
headerV2 := currentBlock.Header()
|
||||
headerV2.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(1))
|
||||
headerV2.Validators = common.Hex2Bytes("0278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758c")
|
||||
masternodesV2 := adaptor.GetMasternodesFromCheckpointHeader(headerV2)
|
||||
assert.True(t, reflect.DeepEqual(masternodesV1, masternodesV2), "GetMasternodesFromCheckpointHeader in adaptor for v1 v2 not equal", "v1", masternodesV1, "v2", masternodesV2)
|
||||
}
|
||||
func TestAdaptorIsEpochSwitch(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
header := currentBlock.Header()
|
||||
// v1
|
||||
header.Number.SetUint64(0)
|
||||
|
||||
isEpochSwitchBlock, epochNum, err := adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isEpochSwitchBlock, "header should be epoch switch", header)
|
||||
assert.Equal(t, uint64(0), epochNum)
|
||||
header.Number.SetUint64(1)
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isEpochSwitchBlock, "header should not be epoch switch", header)
|
||||
// v2
|
||||
parentBlockInfo := &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(0),
|
||||
Number: big.NewInt(0).Set(blockchain.Config().XDPoS.V2.SwitchBlock),
|
||||
}
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: 1,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err := extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(1))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isEpochSwitchBlock, "header should be epoch switch", header)
|
||||
parentBlockInfo = &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(1),
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(1)),
|
||||
}
|
||||
quorumCert = &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra = types.ExtraFields_v2{
|
||||
Round: 2,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(2))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isEpochSwitchBlock, "header should not be epoch switch", header)
|
||||
parentBlockInfo = &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) - 1,
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(100)),
|
||||
}
|
||||
quorumCert = &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra = types.ExtraFields_v2{
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) + 1,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(101))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isEpochSwitchBlock, "header should be epoch switch", header)
|
||||
parentBlockInfo = &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) + 1,
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(100)),
|
||||
}
|
||||
quorumCert = &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra = types.ExtraFields_v2{
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) + 2,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(101))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isEpochSwitchBlock, "header should not be epoch switch", header)
|
||||
}
|
||||
|
||||
func TestAdaptorGetMasternodesV2(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil, nil)
|
||||
|
||||
// block 901 is the first v2 block, and is treated as epoch switch block
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
masternodes1 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
|
||||
assert.Equal(t, 5, len(masternodes1))
|
||||
masternodes1ByNumber := adaptor.GetMasternodesByNumber(blockchain, currentBlock.NumberU64())
|
||||
assert.True(t, reflect.DeepEqual(masternodes1, masternodes1ByNumber), "at block number", blockNum)
|
||||
for blockNum = 902; blockNum < 915; blockNum++ {
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
masternodes2 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
|
||||
assert.True(t, reflect.DeepEqual(masternodes1, masternodes2), "at block number", blockNum)
|
||||
masternodes2ByNumber := adaptor.GetMasternodesByNumber(blockchain, currentBlock.NumberU64())
|
||||
assert.True(t, reflect.DeepEqual(masternodes2, masternodes2ByNumber), "at block number", blockNum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrentEpochSwitchBlock(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// V1
|
||||
currentCheckpointNumber, epochNum, err := adaptor.GetCurrentEpochSwitchBlock(blockchain, big.NewInt(900))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(900), currentCheckpointNumber)
|
||||
assert.Equal(t, uint64(1), epochNum)
|
||||
|
||||
// V2
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
currentCheckpointNumber, epochNum, err = adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(901), currentCheckpointNumber)
|
||||
assert.Equal(t, uint64(1), epochNum)
|
||||
|
||||
for blockNum = 902; blockNum < 915; blockNum++ {
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil)
|
||||
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
currentCheckpointNumber, epochNum, err := adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(901), currentCheckpointNumber)
|
||||
assert.Equal(t, uint64(1), epochNum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetParentBlock(t *testing.T) {
|
||||
blockchain, _, block900, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// V1
|
||||
block := adaptor.FindParentBlockToAssign(blockchain, block900)
|
||||
assert.Equal(t, block, block900)
|
||||
|
||||
// Initialise
|
||||
err := adaptor.EngineV2.Initial(blockchain, block.Header())
|
||||
assert.Nil(t, err)
|
||||
|
||||
// V2
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
block901 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block900, blockNum, 1, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block901)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// let's inject another one, but the highestedQC has not been updated, so it shall still point to 900
|
||||
blockNum = 902
|
||||
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block901, blockNum, 1, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block902)
|
||||
assert.Nil(t, err)
|
||||
block = adaptor.FindParentBlockToAssign(blockchain, block902)
|
||||
|
||||
assert.Equal(t, block900.Hash(), block.Hash())
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsAuthorisedMNForConsensusV2(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 902
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil, nil)
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
// As long as the address is in the master node list, they are all valid
|
||||
isAuthorisedMN := adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.True(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc71562b71999873DB5b286dF957af199Ec94617F7"))
|
||||
assert.True(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdcbanana"))
|
||||
assert.False(t, isAuthorisedMN)
|
||||
}
|
||||
|
||||
func TestIsYourTurnConsensusV2(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
minePeriod := params.TestXDPoSV2Config.MinePeriod
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil, nil)
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
// Less then Mine Period
|
||||
isYourTurn, err := adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
// The second address is valid as the round starting from 1
|
||||
isYourTurn, err = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isYourTurn)
|
||||
|
||||
// The first and third address are not valid
|
||||
isYourTurn, err = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc703c4b2bD70c169f5717101CaeE543299Fc946C7"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isYourTurn)
|
||||
isYourTurn, err = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc71562b71999873DB5b286dF957af199Ec94617F7"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
// We continue to grow the chain which will increase the round number
|
||||
blockNum = 902
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, 2, false)
|
||||
isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc71562b71999873DB5b286dF957af199Ec94617F7"))
|
||||
assert.True(t, isYourTurn)
|
||||
|
||||
isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc5F74529C0338546f82389402a01c31fB52c6f434"))
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
}
|
||||
313
consensus/tests/engine_v2_tests/forensics_test.go
Normal file
313
consensus/tests/engine_v2_tests/forensics_test.go
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessQcShallSetForensicsCommittedQc(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Assuming we are getting block 906 which have QC pointing at block 905
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
signedHash = SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create another vote which is signed by someone not from the master node list
|
||||
randomSigner, randomSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
randomlySignedHash, err := randomSignFn(accounts.Account{Address: randomSigner}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: randomlySignedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook and increment the round to 6
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
assert.Equal(t, 3, len(engineV2.GetForensicsFaker().HighestCommittedQCs))
|
||||
}
|
||||
|
||||
func TestSetCommittedQCsInOrder(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
var headers []types.Header
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the qc1 and qc2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(902)), *decodedExtraField.QuorumCert)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "headers shall be on the same chain and in the right order", err.Error())
|
||||
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(904)), *decodedExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(forensics.HighestCommittedQCs))
|
||||
|
||||
// Test previous blocks
|
||||
err = utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(904).Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(902), *blockchain.GetHeaderByNumber(903)), *decodedExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(forensics.HighestCommittedQCs))
|
||||
}
|
||||
|
||||
// Happty path
|
||||
func TestForensicsMonitoring(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil)
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
var decodedCurrentblockExtraField types.ExtraFields_v2
|
||||
// Decode the QC from latest block
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Header().Extra, &decodedCurrentblockExtraField)
|
||||
assert.Nil(t, err)
|
||||
incomingQC := decodedCurrentblockExtraField.QuorumCert
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 905, 906 and 907
|
||||
var headers []types.Header
|
||||
var decodedBlock905ExtraField types.ExtraFields_v2
|
||||
err = utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(905).Extra, &decodedBlock905ExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(904)), *decodedBlock905ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
var newIncomingQcHeaders []types.Header
|
||||
newIncomingQcHeaders = append(newIncomingQcHeaders, *blockchain.GetHeaderByNumber(913), *blockchain.GetHeaderByNumber(914))
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, newIncomingQcHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestForensicsMonitoringNotOnSameChainButHaveSameRoundQC(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 10
|
||||
var forkRoundDifference = new(int)
|
||||
*forkRoundDifference = 1
|
||||
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference})
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 913, 914 and 915
|
||||
var headers []types.Header
|
||||
var decodedBlock915ExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(915).Extra, &decodedBlock915ExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(913), *blockchain.GetHeaderByNumber(914)), *decodedBlock915ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the QC from forking chain
|
||||
err = utils.DecodeBytesExtraFields(currentForkBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
incomingQC := decodedExtraField.QuorumCert
|
||||
|
||||
var forkedHeaders []types.Header
|
||||
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
|
||||
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
|
||||
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
|
||||
|
||||
// Set up forensics events trigger
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Check SendForensicProof triggered
|
||||
for {
|
||||
select {
|
||||
case forensics := <-forensicsEventCh:
|
||||
assert.NotNil(t, forensics.ForensicsProof)
|
||||
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
|
||||
content := &types.ForensicsContent{}
|
||||
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
|
||||
assert.False(t, content.AcrossEpoch)
|
||||
assert.Equal(t, types.Round(13), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(913), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 9, len(content.SmallerRoundInfo.HashPath))
|
||||
assert.Equal(t, 4, len(content.SmallerRoundInfo.SignerAddresses))
|
||||
assert.Equal(t, types.Round(13), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(912), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 8, len(content.LargerRoundInfo.HashPath))
|
||||
assert.Equal(t, 4, len(content.LargerRoundInfo.SignerAddresses))
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 10
|
||||
var forkRoundDifference = new(int)
|
||||
*forkRoundDifference = 10
|
||||
var forkedChainSignersKey []*ecdsa.PrivateKey
|
||||
forkedChainSignersKey = append(forkedChainSignersKey, acc1Key)
|
||||
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference, signersKey: forkedChainSignersKey})
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 913, 914 and 915
|
||||
var headers []types.Header
|
||||
var decodedBlock915ExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(915).Extra, &decodedBlock915ExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(913), *blockchain.GetHeaderByNumber(914)), *decodedBlock915ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the QC from forking chain
|
||||
err = utils.DecodeBytesExtraFields(currentForkBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
incomingQC := decodedExtraField.QuorumCert
|
||||
var forkedHeaders []types.Header
|
||||
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
|
||||
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
|
||||
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
|
||||
|
||||
// Set up forensics events trigger
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
// Check SendForensicProof triggered
|
||||
for {
|
||||
select {
|
||||
case forensics := <-forensicsEventCh:
|
||||
assert.NotNil(t, forensics.ForensicsProof)
|
||||
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
|
||||
content := &types.ForensicsContent{}
|
||||
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
|
||||
|
||||
assert.False(t, content.AcrossEpoch)
|
||||
assert.Equal(t, types.Round(14), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(914), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 10, len(content.SmallerRoundInfo.HashPath))
|
||||
assert.Equal(t, 4, len(content.SmallerRoundInfo.SignerAddresses))
|
||||
assert.Equal(t, types.Round(16), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(906), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.HashPath))
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.SignerAddresses))
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "prone to attack" test where the "across epoch" field is true
|
||||
func TestForensicsAcrossEpoch(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 10
|
||||
var forkRoundDifference = new(int)
|
||||
*forkRoundDifference = 10
|
||||
var forkedChainSignersKey []*ecdsa.PrivateKey
|
||||
forkedChainSignersKey = append(forkedChainSignersKey, acc1Key)
|
||||
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 1801, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference, signersKey: forkedChainSignersKey})
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 1799, 1800 and 1801
|
||||
var headers []types.Header
|
||||
var decodedBlock1801ExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(1801).Extra, &decodedBlock1801ExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(1799), *blockchain.GetHeaderByNumber(1800)), *decodedBlock1801ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the QC from forking chain
|
||||
err = utils.DecodeBytesExtraFields(currentForkBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
incomingQC := decodedExtraField.QuorumCert
|
||||
var forkedHeaders []types.Header
|
||||
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
|
||||
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
|
||||
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
|
||||
|
||||
// Set up forensics events trigger
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
// Check SendForensicProof triggered
|
||||
for {
|
||||
select {
|
||||
case forensics := <-forensicsEventCh:
|
||||
assert.NotNil(t, forensics.ForensicsProof)
|
||||
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
|
||||
content := &types.ForensicsContent{}
|
||||
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
|
||||
|
||||
idToCompare := content.DivergingBlockHash + ":" + content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Hash.Hex() + ":" + content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Hash.Hex()
|
||||
assert.Equal(t, idToCompare, forensics.ForensicsProof.Id)
|
||||
assert.True(t, content.AcrossEpoch)
|
||||
assert.Equal(t, types.Round(900), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(1800), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 10, len(content.SmallerRoundInfo.HashPath))
|
||||
assert.Equal(t, 4, len(content.SmallerRoundInfo.SignerAddresses))
|
||||
assert.Equal(t, types.Round(902), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(1792), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.HashPath))
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.SignerAddresses))
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
685
consensus/tests/engine_v2_tests/helper.go
Normal file
685
consensus/tests/engine_v2_tests/helper.go
Normal file
|
|
@ -0,0 +1,685 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/contracts"
|
||||
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
. "github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
type masterNodes map[string]big.Int
|
||||
type signersList map[string]bool
|
||||
|
||||
const GAP = int(450)
|
||||
|
||||
var (
|
||||
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
acc3Key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
voterKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee04aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) //xdc703c4b2bD70c169f5717101CaeE543299Fc946C7
|
||||
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) //xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e
|
||||
acc3Addr = crypto.PubkeyToAddress(acc3Key.PublicKey) //xdc71562b71999873DB5b286dF957af199Ec94617F7
|
||||
voterAddr = crypto.PubkeyToAddress(voterKey.PublicKey) //xdc5F74529C0338546f82389402a01c31fB52c6f434
|
||||
chainID = int64(1337)
|
||||
)
|
||||
|
||||
func SignHashByPK(pk *ecdsa.PrivateKey, itemToSign []byte) []byte {
|
||||
signer, signFn, err := getSignerAndSignFn(pk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, itemToSign)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return signedHash
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func RandStringBytes(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getSignerAndSignFn(pk *ecdsa.PrivateKey) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) {
|
||||
veryLightScryptN := 2
|
||||
veryLightScryptP := 1
|
||||
dir, _ := ioutil.TempDir("", fmt.Sprintf("eth-getSignerAndSignFn-test-%v", RandStringBytes(5)))
|
||||
|
||||
new := func(kd string) *keystore.KeyStore {
|
||||
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
ks := new(dir)
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.ImportECDSA(pk, pass)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
if err := ks.Unlock(a1, ""); err != nil {
|
||||
return a1.Address, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return a1.Address, ks.SignHash, nil
|
||||
}
|
||||
|
||||
func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, error) {
|
||||
vote := "6dd7d8ea" // VoteMethod = "0x6dd7d8ea"
|
||||
action := fmt.Sprintf("%s%s%s", vote, "000000000000000000000000", addr[3:])
|
||||
data := common.Hex2Bytes(action)
|
||||
gasPrice := big.NewInt(int64(0))
|
||||
amountInt := new(big.Int)
|
||||
amount, ok := amountInt.SetString("60000", 10)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("big int init failed")
|
||||
}
|
||||
to := common.HexToAddress(common.MasternodeVotingSMC)
|
||||
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
|
||||
|
||||
signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return signedTX, nil
|
||||
}
|
||||
|
||||
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
|
||||
|
||||
// initial helper backend
|
||||
contractBackendForSC := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
|
||||
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
}, 10000000, chainConfig)
|
||||
|
||||
transactOpts := bind.NewKeyedTransactor(voterKey)
|
||||
|
||||
var candidates []common.Address
|
||||
var caps []*big.Int
|
||||
defalutCap := new(big.Int)
|
||||
defalutCap.SetString("1000000000", 10)
|
||||
|
||||
for i := 1; i <= 16; i++ {
|
||||
addr := fmt.Sprintf("%02d", i)
|
||||
candidates = append(candidates, common.StringToAddress(addr)) // StringToAddress does not exist
|
||||
caps = append(caps, defalutCap)
|
||||
}
|
||||
|
||||
acc1Cap, acc2Cap, acc3Cap, voterCap := new(big.Int), new(big.Int), new(big.Int), new(big.Int)
|
||||
|
||||
acc1Cap.SetString("10000001", 10)
|
||||
acc2Cap.SetString("10000002", 10)
|
||||
acc3Cap.SetString("10000003", 10)
|
||||
voterCap.SetString("1000000000", 10)
|
||||
|
||||
caps = append(caps, voterCap, acc1Cap, acc2Cap, acc3Cap)
|
||||
candidates = append(candidates, voterAddr, acc1Addr, acc2Addr, acc3Addr)
|
||||
// create validator smart contract
|
||||
validatorSCAddr, _, _, err := contractValidator.DeployXDCValidator(
|
||||
transactOpts,
|
||||
contractBackendForSC,
|
||||
candidates,
|
||||
caps,
|
||||
voterAddr, // first owner, not used
|
||||
big.NewInt(50000),
|
||||
big.NewInt(1),
|
||||
big.NewInt(99),
|
||||
big.NewInt(100),
|
||||
big.NewInt(100),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("can't deploy root registry: %v", err)
|
||||
}
|
||||
|
||||
contractBackendForSC.Commit() // Write into database(state)
|
||||
|
||||
// Prepare Code and Storage
|
||||
d := time.Now().Add(1000 * time.Millisecond)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
|
||||
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
|
||||
storage := make(map[common.Hash]common.Hash)
|
||||
f := func(key, val common.Hash) bool {
|
||||
decode := []byte{}
|
||||
trim := bytes.TrimLeft(val.Bytes(), "\x00")
|
||||
err := rlp.DecodeBytes(trim, &decode)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while decode byte")
|
||||
}
|
||||
storage[key] = common.BytesToHash(decode)
|
||||
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
|
||||
return true
|
||||
}
|
||||
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to read all keys from SC")
|
||||
}
|
||||
|
||||
// create test backend with smart contract in it
|
||||
contractBackend2 := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
|
||||
acc1Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
acc2Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
acc3Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
common.HexToAddress(common.MasternodeVotingSMC): {Balance: new(big.Int).SetUint64(1), Code: code, Storage: storage}, // Binding the MasternodeVotingSMC with newly created 'code' for SC execution
|
||||
}, 10000000, chainConfig)
|
||||
|
||||
return contractBackend2
|
||||
}
|
||||
|
||||
func signingTxWithKey(header *types.Header, nonce uint64, privateKey *ecdsa.PrivateKey) (*types.Transaction, error) {
|
||||
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
|
||||
s := types.NewEIP155Signer(big.NewInt(chainID))
|
||||
h := s.Hash(tx)
|
||||
sig, err := crypto.Sign(h[:], privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedTx, err := tx.WithSignature(s, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signedTx, nil
|
||||
}
|
||||
|
||||
func signingTxWithSignerFn(header *types.Header, nonce uint64, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) (*types.Transaction, error) {
|
||||
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
|
||||
s := types.NewEIP155Signer(big.NewInt(chainID))
|
||||
h := s.Hash(tx)
|
||||
sig, err := signFn(accounts.Account{Address: signer}, h[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedTx, err := tx.WithSignature(s, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signedTx, nil
|
||||
}
|
||||
|
||||
func UpdateSigner(bc *BlockChain) error {
|
||||
err := bc.UpdateM1()
|
||||
return err
|
||||
}
|
||||
|
||||
func GetSnapshotSigner(bc *BlockChain, header *types.Header) (signersList, error) {
|
||||
engine := bc.Engine().(*XDPoS.XDPoS)
|
||||
snap, err := engine.GetSnapshot(bc, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
ms := make(signersList)
|
||||
|
||||
for addr := range snap.Signers {
|
||||
ms[addr.Hex()] = true
|
||||
}
|
||||
return ms, nil
|
||||
|
||||
}
|
||||
|
||||
func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testing.T) masterNodes {
|
||||
addr := common.HexToAddress(common.MasternodeVotingSMC)
|
||||
validator, err := contractValidator.NewXDCValidator(addr, backend)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opts := new(bind.CallOpts)
|
||||
candidates, err := validator.GetCandidates(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ms := make(masterNodes)
|
||||
for _, candidate := range candidates {
|
||||
v, err := validator.GetCandidateCap(opts, candidate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ms[candidate.String()] = *v
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
type ForkedBlockOptions struct {
|
||||
numOfForkedBlocks *int
|
||||
forkedRoundDifference *int // Minimum is 1
|
||||
signersKey []*ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// V2 concensus engine
|
||||
func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig, forkedBlockOptions *ForkedBlockOptions) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error), *types.Block) {
|
||||
// Preparation
|
||||
var err error
|
||||
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
|
||||
}
|
||||
backend := getCommonBackend(t, chainConfig)
|
||||
blockchain := backend.GetBlockChain()
|
||||
blockchain.Client = backend
|
||||
|
||||
engine := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Authorise
|
||||
engine.Authorize(signer, signFn)
|
||||
|
||||
currentBlock := blockchain.Genesis()
|
||||
|
||||
var currentForkBlock *types.Block
|
||||
|
||||
go func() {
|
||||
for range core.CheckpointCh {
|
||||
checkpointChanMsg := <-core.CheckpointCh
|
||||
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert initial blocks
|
||||
for i := 1; i <= numOfBlocks; i++ {
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||
// for v2 blocks, fill in correct coinbase
|
||||
if int64(i) > chainConfig.XDPoS.V2.SwitchBlock.Int64() {
|
||||
blockCoinBase = signer.Hex()
|
||||
}
|
||||
roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()
|
||||
block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, nil, nil)
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Produce forked block for the last numOfForkedBlocks'th blocks
|
||||
if forkedBlockOptions != nil && forkedBlockOptions.numOfForkedBlocks != nil && i > numOfBlocks-*forkedBlockOptions.numOfForkedBlocks {
|
||||
if currentForkBlock == nil {
|
||||
currentForkBlock = currentBlock
|
||||
}
|
||||
forkedBlockCoinBase := fmt.Sprintf("0x222000000000000000000000000000000%03d", i)
|
||||
var forkedBlockRoundNumber int64
|
||||
if forkedBlockOptions.forkedRoundDifference != nil {
|
||||
if *forkedBlockOptions.forkedRoundDifference == 0 {
|
||||
t.Fatal("forkedRoundDifference minimum is 1")
|
||||
}
|
||||
forkedBlockRoundNumber = roundNumber + int64(*forkedBlockOptions.forkedRoundDifference)
|
||||
} else {
|
||||
forkedBlockRoundNumber = roundNumber + int64(*forkedBlockOptions.numOfForkedBlocks)
|
||||
}
|
||||
|
||||
forkedBlock := CreateBlock(blockchain, chainConfig, currentForkBlock, i, forkedBlockRoundNumber, forkedBlockCoinBase, signer, signFn, nil, forkedBlockOptions.signersKey)
|
||||
|
||||
err = blockchain.InsertBlock(forkedBlock)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
currentForkBlock = forkedBlock
|
||||
}
|
||||
|
||||
// First v2 block
|
||||
if (int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()) == 1 {
|
||||
lastv1BlockNumber := block.Header().Number.Uint64() - 1
|
||||
checkpointBlockNumber := lastv1BlockNumber - lastv1BlockNumber%chainConfig.XDPoS.Epoch
|
||||
checkpointHeader := blockchain.GetHeaderByNumber(checkpointBlockNumber)
|
||||
err := engine.EngineV2.Initial(blockchain, checkpointHeader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
currentBlock = block
|
||||
}
|
||||
|
||||
// Update Signer as there is no previous signer assigned
|
||||
err = UpdateSigner(blockchain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return blockchain, backend, currentBlock, signer, signFn, currentForkBlock
|
||||
}
|
||||
|
||||
// V2 concensus engine, compared to PrepareXDCTestBlockChainForV2Engine: (1) no forking (2) add penalty
|
||||
func PrepareXDCTestBlockChainWithPenaltyForV2Engine(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
// Preparation
|
||||
var err error
|
||||
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
if err != nil {
|
||||
t.Fatal("Error while creating simulated wallet for generating singer address and signer fn: ", err)
|
||||
}
|
||||
backend := getCommonBackend(t, chainConfig)
|
||||
blockchain := backend.GetBlockChain()
|
||||
blockchain.Client = backend
|
||||
|
||||
// Authorise
|
||||
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
|
||||
|
||||
currentBlock := blockchain.Genesis()
|
||||
|
||||
go func() {
|
||||
for range core.CheckpointCh {
|
||||
checkpointChanMsg := <-core.CheckpointCh
|
||||
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert initial blocks
|
||||
for i := 1; i <= numOfBlocks; i++ {
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||
// for v2 blocks, fill in correct coinbase
|
||||
if int64(i) > chainConfig.XDPoS.V2.SwitchBlock.Int64() {
|
||||
blockCoinBase = signer.Hex()
|
||||
}
|
||||
roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()
|
||||
// use signer itself as penalty
|
||||
block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, signer[:], nil)
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
currentBlock = block
|
||||
}
|
||||
|
||||
// Update Signer as there is no previous signer assigned
|
||||
err = UpdateSigner(blockchain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return blockchain, backend, currentBlock, signer, signFn
|
||||
}
|
||||
|
||||
func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte, signersKey []*ecdsa.PrivateKey) *types.Block {
|
||||
currentBlock := startingBlock
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
var header *types.Header
|
||||
|
||||
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 1 { // Build engine v2 compatible extra data field
|
||||
extraInBytes := generateV2Extra(roundNumber, currentBlock, signer, signFn, signersKey)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(blockNumber)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
Extra: extraInBytes,
|
||||
}
|
||||
if int64(blockNumber) == (chainConfig.XDPoS.V2.SwitchBlock.Int64() + 1) { // This is the first v2 block, we need to copy the last v1 epoch master node list and inject into v2 validators
|
||||
// Get last master node list from last v1 block
|
||||
lastv1Block := blockchain.GetBlockByNumber(chainConfig.XDPoS.V2.SwitchBlock.Uint64())
|
||||
masternodesFromV1LastEpoch := decodeMasternodesFromHeaderExtra(lastv1Block.Header())
|
||||
for _, v := range masternodesFromV1LastEpoch {
|
||||
header.Validators = append(header.Validators, v[:]...)
|
||||
}
|
||||
} else if roundNumber%int64(chainConfig.XDPoS.Epoch) == 0 {
|
||||
// epoch switch blocks, copy the master node list and inject into v2 validators
|
||||
// Get last master node list from last v1 block
|
||||
lastv1Block := blockchain.GetBlockByNumber(chainConfig.XDPoS.V2.SwitchBlock.Uint64())
|
||||
masternodesFromV1LastEpoch := decodeMasternodesFromHeaderExtra(lastv1Block.Header())
|
||||
for _, v := range masternodesFromV1LastEpoch {
|
||||
header.Validators = append(header.Validators, v[:]...)
|
||||
}
|
||||
if penalties != nil {
|
||||
header.Penalties = penalties
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// V1 block
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(blockNumber)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
|
||||
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
|
||||
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
|
||||
// reset extra
|
||||
header.Extra = []byte{}
|
||||
if len(header.Extra) < utils.ExtraVanity {
|
||||
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, utils.ExtraVanity-len(header.Extra))...)
|
||||
}
|
||||
header.Extra = header.Extra[:utils.ExtraVanity]
|
||||
var masternodes []common.Address
|
||||
// Place the test's signer address to the last
|
||||
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
|
||||
// masternodesFromV1LastEpoch = masternodes
|
||||
for _, masternode := range masternodes {
|
||||
header.Extra = append(header.Extra, masternode[:]...)
|
||||
}
|
||||
header.Extra = append(header.Extra, make([]byte, utils.ExtraSeal)...)
|
||||
|
||||
// Sign all the things for v1 block use v1 sigHash function
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, blockchain.Engine().(*XDPoS.XDPoS).SigHash(header).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error when sign last v1 block hash during test block creation"))
|
||||
}
|
||||
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
|
||||
}
|
||||
}
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Fail to create block in test helper, %v", err))
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (*types.Block, error) {
|
||||
if customHeader.Extra == nil {
|
||||
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
|
||||
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
|
||||
}
|
||||
var difficulty *big.Int
|
||||
if customHeader.Difficulty == nil {
|
||||
difficulty = big.NewInt(1)
|
||||
} else {
|
||||
difficulty = customHeader.Difficulty
|
||||
}
|
||||
|
||||
if len(txs) != 0 {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
|
||||
} else {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
}
|
||||
|
||||
header := types.Header{
|
||||
ParentHash: customHeader.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
ReceiptHash: customHeader.ReceiptHash,
|
||||
Root: customHeader.Root,
|
||||
Coinbase: customHeader.Coinbase,
|
||||
Difficulty: difficulty,
|
||||
Number: customHeader.Number,
|
||||
GasLimit: 1200000000,
|
||||
Time: big.NewInt(time.Now().Unix()),
|
||||
Extra: customHeader.Extra,
|
||||
Validator: customHeader.Validator,
|
||||
Validators: customHeader.Validators,
|
||||
Penalties: customHeader.Penalties,
|
||||
}
|
||||
var block *types.Block
|
||||
if len(txs) == 0 {
|
||||
// Sign all the things and seal it
|
||||
signerAddress, signerFunction := findSignerAndSignFn(bc, &header, signer, signFn, config)
|
||||
header.Coinbase = signerAddress
|
||||
sealHeader(bc, &header, signerAddress, signerFunction)
|
||||
|
||||
block = types.NewBlockWithHeader(&header)
|
||||
} else {
|
||||
// Prepare Receipt
|
||||
statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v when get state", err)
|
||||
}
|
||||
gp := new(GasPool).AddGas(header.GasLimit)
|
||||
|
||||
var gasUsed = new(uint64)
|
||||
var receipts types.Receipts
|
||||
for i, tx := range txs {
|
||||
statedb.Prepare(tx.Hash(), header.Hash(), i)
|
||||
receipt, _, err, _ := ApplyTransaction(bc.Config(), nil, bc, &header.Coinbase, gp, statedb, nil, &header, tx, gasUsed, vm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v when applying transaction", err)
|
||||
}
|
||||
receipts = append(receipts, receipt)
|
||||
}
|
||||
|
||||
header.GasUsed = *gasUsed
|
||||
|
||||
// Sign all the things and seal it
|
||||
signerAddress, signerFunction := findSignerAndSignFn(bc, &header, signer, signFn, config)
|
||||
header.Coinbase = signerAddress
|
||||
sealHeader(bc, &header, signerAddress, signerFunction)
|
||||
|
||||
block = types.NewBlock(&header, txs, nil, receipts)
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header. Only used for v1 last block
|
||||
func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
func findSignerAndSignFn(bc *BlockChain, header *types.Header, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
addressToSign := signer
|
||||
addressedSignFn := signFn
|
||||
|
||||
// If v2 block, we need to use extra data's round to find who is creating the block in order to verify the validator
|
||||
if header.Number.Cmp(config.XDPoS.V2.SwitchBlock) > 0 {
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("fail to seal header for v2 block"))
|
||||
}
|
||||
round := decodedExtraField.Round
|
||||
masterNodes := getMasternodesList(signer)
|
||||
|
||||
index := uint64(round) % config.XDPoS.Epoch % uint64(len(masterNodes))
|
||||
// index 0 to 2 are acc1Addr, acc2Addr, acc3Addr
|
||||
addressToSign = masterNodes[index]
|
||||
if index == 0 {
|
||||
_, signFn, err = getSignerAndSignFn(acc1Key)
|
||||
} else if index == 1 {
|
||||
_, signFn, err = getSignerAndSignFn(acc2Key)
|
||||
} else if index == 2 {
|
||||
_, signFn, err = getSignerAndSignFn(acc3Key)
|
||||
} else if index == 3 {
|
||||
// Skip signing anything for voterAddress to simulate penalty
|
||||
return signer, signFn
|
||||
}
|
||||
addressedSignFn = signFn
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error trying to use one of the pre-defined private key to sign"))
|
||||
}
|
||||
}
|
||||
|
||||
return addressToSign, addressedSignFn
|
||||
}
|
||||
|
||||
func sealHeader(bc *BlockChain, header *types.Header, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
// Sign all the things and seal it
|
||||
signedBlockHeader := bc.Engine().(*XDPoS.XDPoS).SigHash(header)
|
||||
|
||||
signature, err := signFn(accounts.Account{Address: signer}, signedBlockHeader.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
header.Validator = signature
|
||||
}
|
||||
|
||||
func getMasternodesList(signer common.Address) []common.Address {
|
||||
var masternodes []common.Address
|
||||
// Place the test's signer address to the last
|
||||
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
|
||||
return masternodes
|
||||
}
|
||||
|
||||
func generateV2Extra(roundNumber int64, currentBlock *types.Block, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), accKeys []*ecdsa.PrivateKey) []byte {
|
||||
var extraField types.ExtraFields_v2
|
||||
var round types.Round
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
round = types.Round(0)
|
||||
} else {
|
||||
round = extraField.Round
|
||||
}
|
||||
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: round,
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
gapNumber := currentBlock.Number().Uint64() - currentBlock.Number().Uint64()%params.TestXDPoSMockChainConfig.XDPoS.Epoch - params.TestXDPoSMockChainConfig.XDPoS.Gap
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err))
|
||||
}
|
||||
var signatures []types.Signature
|
||||
if len(accKeys) == 0 {
|
||||
// Sign from acc 1, 2, 3 by default
|
||||
accKeys = append(accKeys, acc1Key, acc2Key, acc3Key)
|
||||
}
|
||||
for _, acc := range accKeys {
|
||||
h := SignHashByPK(acc, types.VoteSigHash(voteForSign).Bytes())
|
||||
signatures = append(signatures, h)
|
||||
}
|
||||
signatures = append(signatures, signedHash)
|
||||
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signatures,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(roundNumber),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, err := extra.EncodeToBytes()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
|
||||
}
|
||||
return extraInBytes
|
||||
}
|
||||
126
consensus/tests/engine_v2_tests/initial_test.go
Normal file
126
consensus/tests/engine_v2_tests/initial_test.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInitialFirstV2Blcok(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
header := currentBlock.Header()
|
||||
|
||||
// snapshot should not be created before initial
|
||||
snap, _ := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
assert.Nil(t, snap)
|
||||
|
||||
err := adaptor.EngineV2.Initial(blockchain, header)
|
||||
assert.Nil(t, err)
|
||||
|
||||
round, _, highQC, _, _, _ := adaptor.EngineV2.GetPropertiesFaker()
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: header.Hash(),
|
||||
Round: types.Round(0),
|
||||
Number: header.Number,
|
||||
}
|
||||
expectedQuorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
assert.Equal(t, types.Round(1), round)
|
||||
assert.Equal(t, expectedQuorumCert, highQC)
|
||||
|
||||
// Test snapshot
|
||||
snap, err = adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(450), snap.Number)
|
||||
|
||||
// Test Running channels
|
||||
WaitPeriod := <-adaptor.WaitPeriodCh
|
||||
assert.Equal(t, params.TestXDPoSMockChainConfig.XDPoS.V2.WaitPeriod, WaitPeriod)
|
||||
|
||||
t.Logf("Waiting %d secs for timeout to happen", params.TestXDPoSMockChainConfig.XDPoS.V2.TimeoutPeriod)
|
||||
timeoutMsg := <-adaptor.EngineV2.BroadcastCh
|
||||
assert.NotNil(t, timeoutMsg)
|
||||
assert.Equal(t, types.Round(1), timeoutMsg.(*types.Timeout).Round)
|
||||
}
|
||||
|
||||
func TestInitialOtherV2Block(t *testing.T) {
|
||||
// insert new block with new extra fields
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
for blockNum := 901; blockNum <= 910; blockNum++ {
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil)
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// v2
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Header().Hash(),
|
||||
Round: types.Round(10),
|
||||
Number: big.NewInt(910),
|
||||
}
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signatures: nil, // after decode it got default value []utils.Signature{}
|
||||
GapNumber: 450,
|
||||
}
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: 11,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err := extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash("35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"),
|
||||
Number: big.NewInt(int64(911)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress("0x111000000000000000000000000000000123"),
|
||||
}
|
||||
header.Extra = extraBytes
|
||||
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
// Initialise
|
||||
err = adaptor.EngineV2.Initial(blockchain, block.Header())
|
||||
assert.Nil(t, err)
|
||||
|
||||
round, _, highQC, _, _, _ := adaptor.EngineV2.GetPropertiesFaker()
|
||||
expectedQuorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signatures: []types.Signature{},
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
assert.Equal(t, types.Round(11), round)
|
||||
assert.Equal(t, expectedQuorumCert, highQC)
|
||||
|
||||
// Test snapshot
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, block.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(450), snap.Number)
|
||||
}
|
||||
|
||||
func TestSnapshotShouldAlreadyCreatedByUpdateM1(t *testing.T) {
|
||||
// insert new block with new extra fields
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1800, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1350), snap.Number)
|
||||
}
|
||||
224
consensus/tests/engine_v2_tests/mine_test.go
Normal file
224
consensus/tests/engine_v2_tests/mine_test.go
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestYourTurnInitialV2(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, parentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)-1, config, nil)
|
||||
minePeriod := config.XDPoS.V2.MinePeriod
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Insert block 900
|
||||
t.Logf("Inserting block with propose at 900...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000900"
|
||||
//Get from block validator error message
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(900)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
Extra: common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400"),
|
||||
}
|
||||
block900, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block900)
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
|
||||
// YourTurn is called before mine first v2 block
|
||||
b, err := adaptor.YourTurn(blockchain, block900.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, b)
|
||||
b, err = adaptor.YourTurn(blockchain, block900.Header(), common.HexToAddress("xdc03d9e17Ae3fF2c6712E44e25B09Ac5ee91f6c9ff"))
|
||||
assert.Nil(t, err)
|
||||
// round=1, so masternode[1] has YourTurn = True
|
||||
assert.True(t, b)
|
||||
assert.Equal(t, adaptor.EngineV2.GetCurrentRoundFaker(), types.Round(1))
|
||||
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, block900.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, snap)
|
||||
masterNodes := adaptor.EngineV1.GetMasternodesFromCheckpointHeader(block900.Header())
|
||||
for i := 0; i < len(masterNodes); i++ {
|
||||
assert.Equal(t, masterNodes[i].Hex(), snap.NextEpochMasterNodes[i].Hex())
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldMineOncePerRound(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, block910, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
minePeriod := config.XDPoS.V2.MinePeriod
|
||||
|
||||
// Make sure we seal the parentBlock 910
|
||||
_, err := adaptor.Seal(blockchain, block910, nil)
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
b, err := adaptor.YourTurn(blockchain, block910.Header(), signer)
|
||||
assert.False(t, b)
|
||||
assert.Equal(t, utils.ErrAlreadyMined, err)
|
||||
}
|
||||
|
||||
func TestUpdateMasterNodes(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch+config.XDPoS.Gap)-1, config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
x := adaptor.EngineV2
|
||||
snap, err := x.GetSnapshot(blockchain, currentBlock.Header())
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 450, int(snap.Number))
|
||||
|
||||
// Insert block 1350
|
||||
t.Logf("Inserting block with propose at 1350...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000001350"
|
||||
// NOTE: voterAddr never exist in the Masternode list, but all acc1,2,3 already does
|
||||
tx, err := voteTX(37117, 0, voterAddr.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//Get from block validator error message
|
||||
merkleRoot := "ef9198eb14b003774a505033f6cdcea2d357cbf7a7e7b004d8034d4e2a9770ee"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(1350)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
|
||||
header.Extra = generateV2Extra(450, currentBlock, signer, signFn, nil)
|
||||
|
||||
parentBlock, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, config)
|
||||
assert.Nil(t, err)
|
||||
err = blockchain.InsertBlock(parentBlock)
|
||||
assert.Nil(t, err)
|
||||
// 1350 is a gap block, need to update the snapshot
|
||||
err = blockchain.UpdateM1()
|
||||
assert.Nil(t, err)
|
||||
t.Logf("Inserting block from 1351 to 1800...")
|
||||
for i := 1351; i <= 1800; i++ {
|
||||
blockCoinbase := fmt.Sprintf("0xaaa000000000000000000000000000000000%4d", i)
|
||||
//Get from block validator error message
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(i)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbase),
|
||||
}
|
||||
|
||||
header.Extra = generateV2Extra(int64(i), currentBlock, signer, signFn, nil)
|
||||
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
parentBlock = block
|
||||
}
|
||||
|
||||
snap, err = x.GetSnapshot(blockchain, parentBlock.Header())
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, snap.IsMasterNodes(voterAddr))
|
||||
assert.Equal(t, int(snap.Number), 1350)
|
||||
}
|
||||
|
||||
func TestPrepareFail(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, currentBlock, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch), config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
tstamp := time.Now().Unix()
|
||||
|
||||
notReadyToProposeHeader := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
Coinbase: signer,
|
||||
}
|
||||
|
||||
err := adaptor.Prepare(blockchain, notReadyToProposeHeader)
|
||||
assert.Equal(t, consensus.ErrNotReadyToPropose, err)
|
||||
|
||||
notReadyToMine := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
Coinbase: signer,
|
||||
}
|
||||
// trigger initial which will set the highestQC
|
||||
_, err = adaptor.YourTurn(blockchain, currentBlock.Header(), signer)
|
||||
assert.Nil(t, err)
|
||||
err = adaptor.Prepare(blockchain, notReadyToMine)
|
||||
assert.Equal(t, consensus.ErrNotReadyToMine, err)
|
||||
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(4), false)
|
||||
header901WithoutCoinbase := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
}
|
||||
|
||||
err = adaptor.Prepare(blockchain, header901WithoutCoinbase)
|
||||
assert.Equal(t, consensus.ErrCoinbaseMismatch, err)
|
||||
}
|
||||
|
||||
func TestPrepareHappyPath(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, currentBlock, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch), config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
// trigger initial
|
||||
_, err := adaptor.YourTurn(blockchain, currentBlock.Header(), signer)
|
||||
assert.Nil(t, err)
|
||||
|
||||
tstamp := time.Now().Unix()
|
||||
|
||||
header901 := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
Coinbase: signer,
|
||||
}
|
||||
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(4), false)
|
||||
err = adaptor.Prepare(blockchain, header901)
|
||||
assert.Nil(t, err)
|
||||
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validators := []byte{}
|
||||
for _, v := range snap.NextEpochMasterNodes {
|
||||
validators = append(validators, v[:]...)
|
||||
}
|
||||
assert.Equal(t, validators, header901.Validators)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err = utils.DecodeBytesExtraFields(header901.Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, types.Round(4), decodedExtraField.Round)
|
||||
assert.Equal(t, types.Round(0), decodedExtraField.QuorumCert.ProposedBlockInfo.Round)
|
||||
}
|
||||
115
consensus/tests/engine_v2_tests/penalty_test.go
Normal file
115
consensus/tests/engine_v2_tests/penalty_test.go
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/hooks"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHookPenaltyV2Mining(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*3, config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
|
||||
var extraField types.ExtraFields_v2
|
||||
// 901 is the first v2 block
|
||||
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
|
||||
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
|
||||
assert.Equal(t, 5, len(masternodes))
|
||||
header2100 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*3)), header2100.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
// when we prepare the chain, we include all 5 signers as coinbase except one signer
|
||||
// header2100 records 5 masternodes, so penalty contains 5-4=1 address
|
||||
assert.Equal(t, 1, len(penalty))
|
||||
contains := false
|
||||
for _, mn := range common.RemoveItemFromArray(masternodes, penalty) {
|
||||
if mn == header901.Coinbase {
|
||||
contains = true
|
||||
}
|
||||
}
|
||||
assert.True(t, contains)
|
||||
// set adaptor round/qc to that of 6299
|
||||
err = utils.DecodeBytesExtraFields(header2100.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
err = adaptor.EngineV2.ProcessQCFaker(blockchain, extraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
// coinbase is a faker signer
|
||||
headerMining := &types.Header{
|
||||
ParentHash: header2100.ParentHash,
|
||||
Number: header2100.Number,
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: header2100.Time,
|
||||
Coinbase: acc1Addr,
|
||||
}
|
||||
// Force to make the node to be at its round to mine, otherwise won't pass the yourturn masternodes check
|
||||
// We have 19 nodes in total (20 candidates in snapshot - 1 penalty) and the fake signer is always at the 18th(last) in the list. Hence int(config.XDPoS.Epoch)*3+18-900, the +18 means is to force to next 18 round and -900 is the relative round number to block number int(config.XDPoS.Epoch)*3
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(int(config.XDPoS.Epoch)*3+18-900), false)
|
||||
// The test default signer is not in the msaternodes, so we set the faker signer
|
||||
adaptor.EngineV2.AuthorizeFaker(acc1Addr)
|
||||
err = adaptor.Prepare(blockchain, headerMining)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(headerMining.Penalties)/common.AddressLength)
|
||||
// 20 candidates (set by PrepareXDCTestBlockChainForV2Engine) - 1 penalty = 19
|
||||
assert.Equal(t, 19, len(headerMining.Validators)/common.AddressLength)
|
||||
}
|
||||
|
||||
func TestHookPenaltyV2Comeback(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, _, signer, signFn := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*3, config)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
|
||||
var extraField types.ExtraFields_v2
|
||||
// 901 is the first v2 block
|
||||
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
|
||||
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
|
||||
assert.Equal(t, 5, len(masternodes))
|
||||
header2100 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*3)), header2100.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
// miner (coinbase) is in comeback. so all addresses are in penalty
|
||||
assert.Equal(t, 2, len(penalty))
|
||||
header2085 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - common.MergeSignRange)
|
||||
// forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 3
|
||||
tx, err := signingTxWithSignerFn(header2085, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header2085.Hash(), []*types.Transaction{tx})
|
||||
penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*3)), header2100.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(penalty))
|
||||
}
|
||||
|
||||
func TestHookPenaltyV2Jump(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
end := int(config.XDPoS.Epoch)*3 - common.MergeSignRange
|
||||
blockchain, _, _, _, _ := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*3, config)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
|
||||
var extraField types.ExtraFields_v2
|
||||
// 901 is the first v2 block
|
||||
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
|
||||
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
|
||||
assert.Equal(t, 5, len(masternodes))
|
||||
header2085 := blockchain.GetHeaderByNumber(uint64(end))
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(config.XDPoS.Epoch*3), false)
|
||||
// round 2085-2100 miss blocks, penalty should work as usual
|
||||
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, header2085.Number, header2085.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(penalty))
|
||||
}
|
||||
394
consensus/tests/engine_v2_tests/proposed_block_test.go
Normal file
394
consensus/tests/engine_v2_tests/proposed_block_test.go
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) {
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
poolSize := engineV2.GetVotePoolSizeFaker(voteMsg.(*types.Vote))
|
||||
|
||||
assert.Equal(t, poolSize, 1)
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(1), round)
|
||||
// Should not update the highestQC
|
||||
assert.Equal(t, types.Round(0), highestQC.ProposedBlockInfo.Round)
|
||||
|
||||
// Insert another Block, but it won't trigger commit
|
||||
blockNum := 902
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block902)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block902.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(2), round)
|
||||
assert.Equal(t, types.Round(1), highestQC.ProposedBlockInfo.Round)
|
||||
|
||||
// Insert one more Block, but still won't trigger commit
|
||||
blockNum = 903
|
||||
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block903 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block902, blockNum, 3, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block903)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block903.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// Shoud NOT trigger setNewRound as the new block parent QC is round 1 but the currentRound is already 2
|
||||
assert.Equal(t, types.Round(3), round)
|
||||
assert.Equal(t, types.Round(2), highestQC.ProposedBlockInfo.Round)
|
||||
assert.Nil(t, highestCommitBlock)
|
||||
|
||||
// Insert one more Block, this time will trigger commit
|
||||
blockNum = 904
|
||||
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block904 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block903, blockNum, 4, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block904)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block904.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker()
|
||||
|
||||
assert.Equal(t, types.Round(4), round)
|
||||
assert.Equal(t, types.Round(3), highestQC.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, currentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, currentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(1), highestCommitBlock.Round)
|
||||
}
|
||||
|
||||
func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) {
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, highestQC, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
|
||||
grandGrandParentBlock := blockchain.GetBlockByNumber(902)
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(5), round)
|
||||
assert.Equal(t, types.Round(4), highestQC.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, grandGrandParentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, grandGrandParentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(2), highestCommitBlock.Round)
|
||||
|
||||
// Injecting new block which have gaps in the round number (Round 7 instead of 6)
|
||||
blockNum := 906
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block906 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 7, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block906)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block906.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker()
|
||||
grandGrandParentBlock = blockchain.GetBlockByNumber(903)
|
||||
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, types.Round(5), highestQC.ProposedBlockInfo.Round)
|
||||
// It commit its grandgrandparent block
|
||||
assert.Equal(t, grandGrandParentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, grandGrandParentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(3), highestCommitBlock.Round)
|
||||
|
||||
blockNum = 907
|
||||
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block907 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block906, blockNum, 8, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block907)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block907.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker()
|
||||
|
||||
assert.Equal(t, types.Round(8), round)
|
||||
assert.Equal(t, types.Round(7), highestQC.ProposedBlockInfo.Round)
|
||||
// Should NOT commit, the `grandGrandParentBlock` is still on blockNum 903
|
||||
assert.Equal(t, grandGrandParentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, grandGrandParentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(3), highestCommitBlock.Round)
|
||||
|
||||
}
|
||||
|
||||
func TestProposedBlockMessageHandlerSuccessfullyGenerateVote(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, extraField.QuorumCert.Signatures, highestQC.Signatures)
|
||||
}
|
||||
|
||||
// Should not set new round if proposedBlockInfo round is less than currentRound.
|
||||
// NOTE: This shall not even happen because we have `verifyQC` before being passed into ProposedBlockHandler
|
||||
func TestShouldNotSetNewRound(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 6
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(6), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud not trigger setNewRound
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, extraField.QuorumCert.Signatures, highestQC.Signatures)
|
||||
}
|
||||
|
||||
func TestShouldNotSendVoteMessageIfAlreadyVoteForThisRound(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
err := engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, _, _, highestVotedRound, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, types.Round(6), highestVotedRound)
|
||||
|
||||
// Let's send again, this time, it shall not broadcast any vote message, because HigestVoteRound is same as currentRound
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler again", err)
|
||||
}
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(5 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, highestVotedRound, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, types.Round(6), highestVotedRound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotSendVoteMsgIfBlockInfoRoundNotEqualCurrentRound(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 8
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(8), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(5 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(8), round)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Block and round relationship diagram for this test
|
||||
... - 13(3) - 14(4) - 15(5) - 16(6)
|
||||
\ 14'(7)
|
||||
*/
|
||||
func TestShouldNotSendVoteMsgIfBlockNotExtendedFromAncestor(t *testing.T) {
|
||||
// Block number 905, 906 have forks and forkedBlock is the 906th
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 3
|
||||
blockchain, _, currentBlock, _, _, forkedBlock := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks})
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(forkedBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
assert.Equal(t, types.Round(9), extraField.Round)
|
||||
// Set the lockQC and other pre-requist properties by block 906
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Error while handling block 16", err)
|
||||
}
|
||||
vote := <-engineV2.BroadcastCh
|
||||
assert.Equal(t, types.Round(6), vote.(*types.Vote).ProposedBlockInfo.Round)
|
||||
|
||||
// Find the first forked block at block 14th
|
||||
firstForkedBlock := blockchain.GetBlockByHash(blockchain.GetBlockByHash(forkedBlock.ParentHash()).ParentHash())
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(7), false)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, firstForkedBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(5 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(7), round)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldSendVoteMsg(t *testing.T) {
|
||||
// Block number 15, 16 have forks and forkedBlock is the 16th
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 903, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Block 901 is first v2 block
|
||||
for i := 901; i < 904; i++ {
|
||||
blockHeader := blockchain.GetBlockByNumber(uint64(i)).Header()
|
||||
err := engineV2.ProposedBlockHandler(blockchain, blockHeader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(i-900), round)
|
||||
vote := <-engineV2.BroadcastCh
|
||||
assert.Equal(t, round, vote.(*types.Vote).ProposedBlockInfo.Round)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProposedBlockMessageHandlerNotGenerateVoteIfSignerNotInMNlist(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
differentSigner, differentSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
// Let's change the address
|
||||
engineV2.Authorize(differentSigner, differentSignFn)
|
||||
|
||||
// Set current round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err = utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(2 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
}
|
||||
}
|
||||
163
consensus/tests/engine_v2_tests/reward_test.go
Normal file
163
consensus/tests/engine_v2_tests/reward_test.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/hooks"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHookRewardV2(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// set switch to 1800, so that it covers 901-1799, 1800-2700 two epochs
|
||||
config.XDPoS.V2.SwitchBlock.SetUint64(1800)
|
||||
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*5, &config, nil)
|
||||
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookReward)
|
||||
// forcely insert signing tx into cache, to give rewards.
|
||||
header915 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 15)
|
||||
header916 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 16)
|
||||
header1799 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 1)
|
||||
header1801 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 1)
|
||||
tx, err := signingTxWithSignerFn(header915, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header916.Hash(), []*types.Transaction{tx})
|
||||
statedb, err := blockchain.StateAt(header1799.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState := statedb.Copy()
|
||||
reward, err := adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header1801)
|
||||
assert.Nil(t, err)
|
||||
assert.Zero(t, len(reward))
|
||||
header2699 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - 1)
|
||||
header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
statedb, err = blockchain.StateAt(header2699.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header2700)
|
||||
assert.Nil(t, err)
|
||||
owner := state.GetCandidateOwner(parentState, signer)
|
||||
result := reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 1, len(result))
|
||||
for _, x := range result {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
a, _ := big.NewInt(0).SetString("225000000000000000000", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("25000000000000000000", 10)
|
||||
assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
|
||||
}
|
||||
header2685 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 885)
|
||||
header2716 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 + 16)
|
||||
header3599 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*4 - 1)
|
||||
header3600 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 4)
|
||||
tx, err = signingTxWithSignerFn(header2685, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
// signed block hash and block contains tx are in different epoch, we should get same rewards
|
||||
adaptor.CacheSigningTxs(header2716.Hash(), []*types.Transaction{tx})
|
||||
statedb, err = blockchain.StateAt(header3599.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header3600)
|
||||
assert.Nil(t, err)
|
||||
result = reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 1, len(result))
|
||||
for _, x := range result {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
a, _ := big.NewInt(0).SetString("225000000000000000000", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("25000000000000000000", 10)
|
||||
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]))
|
||||
}
|
||||
// if no signing tx, then reward will be 0
|
||||
header4499 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*5 - 1)
|
||||
header4500 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 5)
|
||||
statedb, err = blockchain.StateAt(header4499.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header4500)
|
||||
assert.Nil(t, err)
|
||||
result = reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 0, len(result))
|
||||
}
|
||||
|
||||
func TestHookRewardV2SplitReward(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// set switch to 1800, so that it covers 901-1799, 1800-2700 two epochs
|
||||
config.XDPoS.V2.SwitchBlock.SetUint64(1800)
|
||||
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*3, &config, nil)
|
||||
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookReward)
|
||||
// forcely insert signing tx into cache, to give rewards.
|
||||
header915 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 15)
|
||||
header916 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 16)
|
||||
// header917 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 17)
|
||||
header1785 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 15)
|
||||
header1799 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 1)
|
||||
header1801 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 1)
|
||||
tx, err := signingTxWithSignerFn(header915, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header916.Hash(), []*types.Transaction{tx})
|
||||
tx2, err := signingTxWithKey(header915, 0, acc1Key)
|
||||
assert.Nil(t, err)
|
||||
tx3, err := signingTxWithKey(header1785, 0, acc1Key)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header1799.Hash(), []*types.Transaction{tx2, tx3})
|
||||
|
||||
statedb, err := blockchain.StateAt(header1799.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState := statedb.Copy()
|
||||
reward, err := adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header1801)
|
||||
assert.Nil(t, err)
|
||||
assert.Zero(t, len(reward))
|
||||
header2699 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - 1)
|
||||
header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
statedb, err = blockchain.StateAt(header2699.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header2700)
|
||||
assert.Nil(t, err)
|
||||
result := reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 2, len(result))
|
||||
// two signing account, 3 txs, reward is split by 1:2 (total reward is 250...000)
|
||||
for addr, x := range result {
|
||||
if addr == acc1Addr {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
owner := state.GetCandidateOwner(parentState, acc1Addr)
|
||||
a, _ := big.NewInt(0).SetString("149999999999999999999", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("16666666666666666666", 10)
|
||||
assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
|
||||
} else if addr == signer {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
owner := state.GetCandidateOwner(parentState, signer)
|
||||
a, _ := big.NewInt(0).SetString("74999999999999999999", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("8333333333333333333", 10)
|
||||
assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
|
||||
}
|
||||
}
|
||||
}
|
||||
102
consensus/tests/engine_v2_tests/sync_info_test.go
Normal file
102
consensus/tests/engine_v2_tests/sync_info_test.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSyncInfoShouldSuccessfullyUpdateByQC(t *testing.T) {
|
||||
// Block 901 is the first v2 block with starting round of 0
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
syncInfoMsg := &types.SyncInfo{
|
||||
HighestQuorumCert: extraField.QuorumCert,
|
||||
HighestTimeoutCert: &types.TimeoutCert{
|
||||
Round: types.Round(2),
|
||||
Signatures: []types.Signature{},
|
||||
},
|
||||
}
|
||||
|
||||
err = engineV2.SyncInfoHandler(blockchain, syncInfoMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
round, _, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// QC is parent block's qc, which is pointing at round 4, hence 4 + 1 = 5
|
||||
assert.Equal(t, types.Round(5), round)
|
||||
assert.Equal(t, extraField.QuorumCert, highestQuorumCert)
|
||||
assert.Equal(t, types.Round(2), highestCommitBlock.Round)
|
||||
assert.Equal(t, big.NewInt(902), highestCommitBlock.Number)
|
||||
}
|
||||
|
||||
func TestSyncInfoShouldSuccessfullyUpdateByTC(t *testing.T) {
|
||||
// Block 901 is the first v2 block with starting round of 0
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
highestTC := &types.TimeoutCert{
|
||||
Round: types.Round(6),
|
||||
Signatures: []types.Signature{},
|
||||
}
|
||||
|
||||
syncInfoMsg := &types.SyncInfo{
|
||||
HighestQuorumCert: extraField.QuorumCert,
|
||||
HighestTimeoutCert: highestTC,
|
||||
}
|
||||
|
||||
err = engineV2.SyncInfoHandler(blockchain, syncInfoMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
round, _, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(7), round)
|
||||
assert.Equal(t, extraField.QuorumCert, highestQuorumCert)
|
||||
}
|
||||
|
||||
func TestSkipVerifySyncInfoIfBothQcTcNotQualified(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Make the Highest QC in syncInfo point to an old block to simulate it's no longer qualified
|
||||
parentBlock := blockchain.GetBlockByNumber(903)
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(parentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
highestTC := &types.TimeoutCert{
|
||||
Round: types.Round(5),
|
||||
Signatures: []types.Signature{},
|
||||
}
|
||||
|
||||
syncInfoMsg := &types.SyncInfo{
|
||||
HighestQuorumCert: extraField.QuorumCert,
|
||||
HighestTimeoutCert: highestTC,
|
||||
}
|
||||
|
||||
engineV2.SetPropertiesFaker(syncInfoMsg.HighestQuorumCert, syncInfoMsg.HighestTimeoutCert)
|
||||
|
||||
verified, err := engineV2.VerifySyncInfoMessage(blockchain, syncInfoMsg)
|
||||
assert.False(t, verified)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
294
consensus/tests/engine_v2_tests/timeout_test.go
Normal file
294
consensus/tests/engine_v2_tests/timeout_test.go
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
timeoutMsg := <-engineV2.BroadcastCh
|
||||
poolSize := engineV2.GetTimeoutPoolSizeFaker(timeoutMsg.(*types.Timeout))
|
||||
assert.Equal(t, poolSize, 1)
|
||||
assert.NotNil(t, timeoutMsg)
|
||||
assert.Equal(t, uint64(450), timeoutMsg.(*types.Timeout).GapNumber)
|
||||
assert.Equal(t, types.Round(1), timeoutMsg.(*types.Timeout).Round)
|
||||
}
|
||||
|
||||
func TestCountdownTimeoutNotToSendTimeoutMessageIfNotInMasternodeList(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
differentSigner, differentSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
// Let's change the address
|
||||
engineV2.Authorize(differentSigner, differentSignFn)
|
||||
|
||||
engineV2.SetNewRoundFaker(blockchain, 1, true)
|
||||
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatalf("Not suppose to receive timeout msg")
|
||||
case <-time.After(15 * time.Second): //Countdown is only 1s wait, let's wait for 3s here
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncInfoAfterReachTimeoutSnycThreadhold(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
engineV2.SetNewRoundFaker(blockchain, 1, true)
|
||||
|
||||
// Because messages are sending async and on random order, so use this way to test
|
||||
var timeoutCounter, syncInfoCounter int
|
||||
for i := 0; i < 3; i++ {
|
||||
obj := <-engineV2.BroadcastCh
|
||||
switch v := obj.(type) {
|
||||
case *types.Timeout:
|
||||
timeoutCounter++
|
||||
case *types.SyncInfo:
|
||||
syncInfoCounter++
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 2, timeoutCounter)
|
||||
assert.Equal(t, 1, syncInfoCounter)
|
||||
|
||||
t.Log("waiting for another consecutive period")
|
||||
// another consecutive period
|
||||
for i := 0; i < 3; i++ {
|
||||
obj := <-engineV2.BroadcastCh
|
||||
switch v := obj.(type) {
|
||||
case *types.Timeout:
|
||||
timeoutCounter++
|
||||
case *types.SyncInfo:
|
||||
syncInfoCounter++
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 4, timeoutCounter)
|
||||
assert.Equal(t, 2, syncInfoCounter)
|
||||
}
|
||||
|
||||
// Timeout handler
|
||||
func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 1
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(1), false)
|
||||
// Create two timeout message which will not reach timeout pool threshold
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{1},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{2},
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
// Send a timeout with different gap number, it shall not trigger timeout pool hook
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{3},
|
||||
GapNumber: 1350,
|
||||
}
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
// Create a timeout message that should trigger timeout pool hook
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{4},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
syncInfoMsg := <-engineV2.BroadcastCh
|
||||
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
|
||||
assert.NotNil(t, syncInfoMsg)
|
||||
|
||||
// Shouldn't have QC, however, we did not inilise it, hence will show default empty value
|
||||
qc := syncInfoMsg.(*types.SyncInfo).HighestQuorumCert
|
||||
assert.Equal(t, types.Round(0), qc.ProposedBlockInfo.Round)
|
||||
|
||||
tc := syncInfoMsg.(*types.SyncInfo).HighestTimeoutCert
|
||||
assert.NotNil(t, tc)
|
||||
assert.Equal(t, tc.Round, types.Round(1))
|
||||
assert.Equal(t, uint64(450), tc.GapNumber)
|
||||
// The signatures shall not include the byte{3} from a different gap number
|
||||
sigatures := []types.Signature{[]byte{1}, []byte{2}, []byte{4}}
|
||||
assert.ElementsMatch(t, tc.Signatures, sigatures)
|
||||
assert.Equal(t, types.Round(2), currentRound)
|
||||
}
|
||||
|
||||
func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 3
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(3), false)
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(2),
|
||||
Signature: []byte{1},
|
||||
}
|
||||
|
||||
err := engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.NotNil(t, err)
|
||||
// Timeout msg round > currentRound
|
||||
assert.Equal(t, "timeout message round number: 2 does not match currentRound: 3", err.Error())
|
||||
|
||||
// Set round to 1
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(1), false)
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.NotNil(t, err)
|
||||
// Timeout msg round < currentRound
|
||||
assert.Equal(t, "timeout message round number: 2 does not match currentRound: 1", err.Error())
|
||||
}
|
||||
|
||||
func TestShouldVerifyTimeoutMessageForFirstV2Block(t *testing.T) {
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(1),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
assert.Nil(t, err)
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
|
||||
verified, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, verified)
|
||||
|
||||
signedHash, err = signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(2),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
assert.Nil(t, err)
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(2),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
|
||||
verified, err = engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, verified)
|
||||
}
|
||||
|
||||
func TestShouldVerifyTimeoutMessage(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 2251, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
signedHash := SignHashByPK(acc1Key, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(5000),
|
||||
GapNumber: 2250,
|
||||
}).Bytes())
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(5000),
|
||||
GapNumber: 2250,
|
||||
Signature: signedHash,
|
||||
}
|
||||
|
||||
verified, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, verified)
|
||||
}
|
||||
|
||||
func TestTimeoutPoolKeeyGoodHygiene(t *testing.T) {
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Inject the first timeout with round 5
|
||||
|
||||
signedHash, _ := signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(5),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(5),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
|
||||
// Inject a second timeout with round 16
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(16),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(16),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
// Set round to 16
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(16), false)
|
||||
engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
|
||||
// Inject a third timeout with round 17
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(17),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(17),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
// Set round to 16
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(17), false)
|
||||
engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
|
||||
// Let's keep good Hygiene
|
||||
engineV2.HygieneTimeoutPoolFaker()
|
||||
// Let's wait for 5 second for the goroutine
|
||||
<-time.After(5 * time.Second)
|
||||
keyList := engineV2.GetTimeoutPoolKeyListFaker()
|
||||
|
||||
assert.Equal(t, 2, len(keyList))
|
||||
for _, k := range keyList {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
assert.Nil(t, err)
|
||||
if keyedRound < 25-10 {
|
||||
assert.Fail(t, "Did not clean up the timeout pool")
|
||||
}
|
||||
}
|
||||
}
|
||||
64
consensus/tests/engine_v2_tests/verify_blockinfo_test.go
Normal file
64
consensus/tests/engine_v2_tests/verify_blockinfo_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShouldVerifyBlockInfo(t *testing.T) {
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(1),
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
err := engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Insert another Block, but it won't trigger commit
|
||||
blockNum := 902
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil, nil)
|
||||
err = blockchain.InsertBlock(block902)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: block902.Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: block902.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: block902.Hash(),
|
||||
Round: types.Round(3),
|
||||
Number: block902.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: block902.Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
316
consensus/tests/engine_v2_tests/verify_header_test.go
Normal file
316
consensus/tests/engine_v2_tests/verify_header_test.go
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShouldVerifyBlock(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Skip the mining time validation by set mine time to 0
|
||||
config.XDPoS.V2.MinePeriod = 0
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Happy path
|
||||
happyPathHeader := blockchain.GetBlockByNumber(901).Header()
|
||||
err = adaptor.VerifyHeader(blockchain, happyPathHeader, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Unhappy path
|
||||
|
||||
// Verify non-epoch switch block
|
||||
err = adaptor.VerifyHeader(blockchain, blockchain.GetBlockByNumber(902).Header(), true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
nonEpochSwitchWithValidators := blockchain.GetBlockByNumber(902).Header()
|
||||
nonEpochSwitchWithValidators.Validators = acc1Addr.Bytes()
|
||||
err = adaptor.VerifyHeader(blockchain, nonEpochSwitchWithValidators, true)
|
||||
assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err)
|
||||
|
||||
noValidatorBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
noValidatorBlock.Validator = []byte{}
|
||||
err = adaptor.VerifyHeader(blockchain, noValidatorBlock, true)
|
||||
assert.Equal(t, consensus.ErrNoValidatorSignature, err)
|
||||
|
||||
blockFromFuture := blockchain.GetBlockByNumber(902).Header()
|
||||
blockFromFuture.Time = big.NewInt(time.Now().Unix() + 10000)
|
||||
err = adaptor.VerifyHeader(blockchain, blockFromFuture, true)
|
||||
assert.Equal(t, consensus.ErrFutureBlock, err)
|
||||
|
||||
invalidQcBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidQcBlock.Extra = []byte{}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidQcBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidV2Extra, err)
|
||||
|
||||
// Epoch switch
|
||||
invalidAuthNonceBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
invalidAuthNonceBlock.Nonce = types.BlockNonce{123}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidAuthNonceBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidVote, err)
|
||||
|
||||
emptyValidatorsBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
emptyValidatorsBlock.Validators = []byte{}
|
||||
err = adaptor.VerifyHeader(blockchain, emptyValidatorsBlock, true)
|
||||
assert.Equal(t, utils.ErrEmptyEpochSwitchValidators, err)
|
||||
|
||||
invalidValidatorsSignerBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
invalidValidatorsSignerBlock.Validators = []byte{123}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidValidatorsSignerBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidCheckpointSigners, err)
|
||||
|
||||
// non-epoch switch
|
||||
invalidValidatorsExistBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidValidatorsExistBlock.Validators = []byte{123}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidValidatorsExistBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err)
|
||||
|
||||
invalidPenaltiesExistBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidPenaltiesExistBlock.Penalties = common.Hex2BytesFixed("123131231", 20)
|
||||
err = adaptor.VerifyHeader(blockchain, invalidPenaltiesExistBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err)
|
||||
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb123"
|
||||
parentNotExistBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
parentNotExistBlock.ParentHash = common.HexToHash(merkleRoot)
|
||||
err = adaptor.VerifyHeader(blockchain, parentNotExistBlock, true)
|
||||
assert.Equal(t, consensus.ErrUnknownAncestor, err)
|
||||
|
||||
tooFastMinedBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
tooFastMinedBlock.Time = big.NewInt(time.Now().Unix() - 10)
|
||||
err = adaptor.VerifyHeader(blockchain, tooFastMinedBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidTimestamp, err)
|
||||
|
||||
invalidDifficultyBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidDifficultyBlock.Difficulty = big.NewInt(2)
|
||||
err = adaptor.VerifyHeader(blockchain, invalidDifficultyBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidDifficulty, err)
|
||||
|
||||
// Creat an invalid QC round
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: blockchain.GetBlockByNumber(902).Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: blockchain.GetBlockByNumber(902).Number(),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
// Genrate QC
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err))
|
||||
}
|
||||
// Sign from acc 1, 2, 3
|
||||
acc1SignedHash := SignHashByPK(acc1Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc2SignedHash := SignHashByPK(acc2Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc3SignedHash := SignHashByPK(acc3Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
var signatures []types.Signature
|
||||
signatures = append(signatures, signedHash, acc1SignedHash, acc2SignedHash, acc3SignedHash)
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signatures,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(2),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, err := extra.EncodeToBytes()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
|
||||
}
|
||||
|
||||
invalidRoundBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidRoundBlock.Extra = extraInBytes
|
||||
err = adaptor.VerifyHeader(blockchain, invalidRoundBlock, true)
|
||||
assert.Equal(t, utils.ErrRoundInvalid, err)
|
||||
|
||||
// Not valid validator
|
||||
coinbaseValidatorMismatchBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
notQualifiedSigner, notQualifiedSignFn, err := getSignerAndSignFn(voterKey)
|
||||
assert.Nil(t, err)
|
||||
sealHeader(blockchain, coinbaseValidatorMismatchBlock, notQualifiedSigner, notQualifiedSignFn)
|
||||
err = adaptor.VerifyHeader(blockchain, coinbaseValidatorMismatchBlock, true)
|
||||
assert.Equal(t, utils.ErrCoinbaseAndValidatorMismatch, err)
|
||||
|
||||
// Make the validators not legit by adding something to the validator
|
||||
validatorsNotLegit := blockchain.GetBlockByNumber(901).Header()
|
||||
validatorsNotLegit.Validators = append(validatorsNotLegit.Validators, acc1Addr[:]...)
|
||||
err = adaptor.VerifyHeader(blockchain, validatorsNotLegit, true)
|
||||
assert.Equal(t, utils.ErrValidatorsNotLegit, err)
|
||||
|
||||
// Make the penalties not legit by adding something to the penalty
|
||||
penaltiesNotLegit := blockchain.GetBlockByNumber(901).Header()
|
||||
penaltiesNotLegit.Penalties = append(penaltiesNotLegit.Penalties, acc1Addr[:]...)
|
||||
err = adaptor.VerifyHeader(blockchain, penaltiesNotLegit, true)
|
||||
assert.Equal(t, utils.ErrPenaltiesNotLegit, err)
|
||||
}
|
||||
|
||||
func TestShouldFailIfNotEnoughQCSignatures(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Skip the mining time validation by set mine time to 0
|
||||
config.XDPoS.V2.MinePeriod = 0
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 902, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
parentBlock := blockchain.GetBlockByNumber(901)
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: parentBlock.Hash(),
|
||||
Round: types.Round(1),
|
||||
Number: parentBlock.Number(),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
assert.Nil(t, err)
|
||||
var signatures []types.Signature
|
||||
// Duplicate the signatures
|
||||
signatures = append(signatures, signedHash, signedHash, signedHash, signedHash, signedHash, signedHash)
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signatures,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(2),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, err := extra.EncodeToBytes()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
|
||||
}
|
||||
headerWithDuplicatedSignatures := currentBlock.Header()
|
||||
headerWithDuplicatedSignatures.Extra = extraInBytes
|
||||
// Happy path
|
||||
err = adaptor.VerifyHeader(blockchain, headerWithDuplicatedSignatures, true)
|
||||
assert.Equal(t, utils.ErrInvalidQC, err)
|
||||
|
||||
}
|
||||
|
||||
func TestShouldVerifyHeaders(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Skip the mining time validation by set mine time to 0
|
||||
config.XDPoS.V2.MinePeriod = 0
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Happy path
|
||||
var happyPathHeaders []*types.Header
|
||||
happyPathHeaders = append(happyPathHeaders, blockchain.GetBlockByNumber(899).Header(), blockchain.GetBlockByNumber(900).Header(), blockchain.GetBlockByNumber(901).Header(), blockchain.GetBlockByNumber(902).Header())
|
||||
// Randomly set full verify
|
||||
var fullVerifies []bool
|
||||
fullVerifies = append(fullVerifies, false, true, true, false)
|
||||
_, results := adaptor.VerifyHeaders(blockchain, happyPathHeaders, fullVerifies)
|
||||
var verified []bool
|
||||
for {
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
panic("Error received while verifying headers")
|
||||
}
|
||||
verified = append(verified, true)
|
||||
case <-time.After(time.Duration(5) * time.Second): // It should be very fast to verify headers
|
||||
if len(verified) == len(happyPathHeaders) {
|
||||
return
|
||||
} else {
|
||||
panic("Suppose to have verified 3 block headers")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldVerifyHeadersEvenIfParentsNotYetWrittenIntoDB(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Skip the mining time validation by set mine time to 0
|
||||
config.XDPoS.V2.MinePeriod = 0
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, block910, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
var headersTobeVerified []*types.Header
|
||||
|
||||
// Create block 911 but don't write into DB
|
||||
blockNumber := 911
|
||||
roundNumber := int64(blockNumber) - config.XDPoS.V2.SwitchBlock.Int64()
|
||||
block911 := CreateBlock(blockchain, &config, block910, blockNumber, roundNumber, signer.Hex(), signer, signFn, nil, nil)
|
||||
|
||||
// Create block 912 and not write into DB as well
|
||||
blockNumber = 912
|
||||
roundNumber = int64(blockNumber) - config.XDPoS.V2.SwitchBlock.Int64()
|
||||
block912 := CreateBlock(blockchain, &config, block911, blockNumber, roundNumber, signer.Hex(), signer, signFn, nil, nil)
|
||||
|
||||
headersTobeVerified = append(headersTobeVerified, block910.Header(), block911.Header(), block912.Header())
|
||||
// Randomly set full verify
|
||||
var fullVerifies []bool
|
||||
fullVerifies = append(fullVerifies, true, true, true)
|
||||
_, results := adaptor.VerifyHeaders(blockchain, headersTobeVerified, fullVerifies)
|
||||
|
||||
var verified []bool
|
||||
for {
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
panic("Error received while verifying headers")
|
||||
}
|
||||
verified = append(verified, true)
|
||||
case <-time.After(time.Duration(5) * time.Second): // It should be very fast to verify headers
|
||||
if len(verified) == len(headersTobeVerified) {
|
||||
return
|
||||
} else {
|
||||
panic("Suppose to have verified 3 block headers")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
676
consensus/tests/engine_v2_tests/vote_test.go
Normal file
676
consensus/tests/engine_v2_tests/vote_test.go
Normal file
|
|
@ -0,0 +1,676 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// VoteHandler
|
||||
func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQCForFistV2Round(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(1),
|
||||
Number: big.NewInt(901),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(1), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
signedHash = SignHashByPK(acc2Key, voteSigningHash.Bytes())
|
||||
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook and increment the round to 6
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(0), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
// Check round has now changed from 1 to 2
|
||||
assert.Equal(t, types.Round(2), currentRound)
|
||||
}
|
||||
|
||||
func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
signedHash = SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create another vote which is signed by someone not from the master node list
|
||||
randomSigner, randomSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
randomlySignedHash, err := randomSignFn(accounts.Account{Address: randomSigner}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: randomlySignedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook and increment the round to 6
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(4), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
// Check round has now changed from 5 to 6
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
// Should trigger ProcessQC and trying to commit from blockNum of 16's grandgrandparent which is blockNum 903 with round 3
|
||||
assert.Equal(t, types.Round(3), highestCommitBlock.Round)
|
||||
assert.Equal(t, big.NewInt(903), highestCommitBlock.Number)
|
||||
}
|
||||
|
||||
func TestThrowErrorIfVoteMsgRoundIsMoreThanOneRoundAwayFromCurrentRound(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: common.HexToHash("0x1"),
|
||||
Round: types.Round(6),
|
||||
Number: big.NewInt(999),
|
||||
}
|
||||
|
||||
// Set round to 7
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(7), false)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: []byte{1},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
// voteRound > currentRound
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 7", err.Error())
|
||||
|
||||
// Set round to 5, it's 1 round away, should not trigger failure
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(4), false)
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 4", err.Error())
|
||||
|
||||
}
|
||||
|
||||
func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
// Start with vote messages
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
// Create two vote message which will not reach vote pool threshold
|
||||
signedHash := SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc2Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc3Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
// Check round has now changed from 5 to 6
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(4), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
|
||||
// We shall have highestQuorumCert in engine now, let's do timeout msg to see if we can broadcast SyncInfo which contains both highestQuorumCert and HighestTimeoutCert
|
||||
|
||||
// First, all incoming old timeout msg shall not be processed
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(5),
|
||||
Signature: []byte{1},
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "timeout message round number: 5 does not match currentRound: 6", err.Error())
|
||||
|
||||
// Ok, let's do the timeout msg which is on the same round as the current round by creating two timeout message which will not reach timeout pool threshold
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(6),
|
||||
Signature: []byte{1},
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(6),
|
||||
Signature: []byte{2},
|
||||
}
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
|
||||
// Create a timeout message that should trigger timeout pool hook
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(6),
|
||||
Signature: []byte{3},
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
syncInfoMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, syncInfoMsg)
|
||||
|
||||
// Should have HighestQuorumCert from previous round votes
|
||||
qc := syncInfoMsg.(*types.SyncInfo).HighestQuorumCert
|
||||
assert.NotNil(t, qc)
|
||||
assert.Equal(t, types.Round(5), qc.ProposedBlockInfo.Round)
|
||||
|
||||
tc := syncInfoMsg.(*types.SyncInfo).HighestTimeoutCert
|
||||
assert.NotNil(t, tc)
|
||||
assert.Equal(t, types.Round(6), tc.Round)
|
||||
sigatures := []types.Signature{[]byte{1}, []byte{2}, []byte{3}}
|
||||
assert.ElementsMatch(t, tc.Signatures, sigatures)
|
||||
// Round shall be +1 now
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(7), currentRound)
|
||||
}
|
||||
|
||||
func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Create a new block but don't inject it into the chain yet
|
||||
blockNum := 906
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 6, blockCoinBase, signer, signFn, nil, nil)
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: block.Header().Hash(),
|
||||
Round: types.Round(6),
|
||||
Number: big.NewInt(906),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 6
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(6), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc1Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc2Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook, but it shall not produce any QC yet
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc3Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
|
||||
// Now, inject the block into the chain
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(voterKey, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(5), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
assert.Equal(t, types.Round(7), currentRound)
|
||||
// Should trigger ProcessQC and trying to commit from blockNum of 16's grandgrandparent which is blockNum 904 with round 4
|
||||
assert.Equal(t, types.Round(4), highestCommitBlock.Round)
|
||||
assert.Equal(t, big.NewInt(904), highestCommitBlock.Number)
|
||||
}
|
||||
|
||||
func TestProcessVoteMsgFailIfVerifyBlockInfoFail(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
// Start with vote messages
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.ParentHash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
// Create two vote message which will not reach vote pool threshold
|
||||
signedHash := SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc2Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc3Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
expectedError := fmt.Errorf("[VerifyBlockInfo] chain header number does not match for the received blockInfo at hash: %v", blockInfo.Hash.Hex())
|
||||
assert.Equal(t, expectedError, err)
|
||||
}
|
||||
|
||||
func TestVerifyVoteMsg(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(14),
|
||||
Number: big.NewInt(915),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
// Valid message but disqualified as the round does not match
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: []byte{1},
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(15), false)
|
||||
verified, err := engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.False(t, verified)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Invalid vote message with wrong signature
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(14), false)
|
||||
verified, err = engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.False(t, verified)
|
||||
assert.Equal(t, "Error while verifying message: invalid signature length", err.Error())
|
||||
|
||||
// Valid vote message from a master node
|
||||
signHash, _ := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
verified, err = engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.True(t, verified)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestVoteMsgMissingSnapshot(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(14),
|
||||
Number: big.NewInt(915),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
signHash, _ := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signHash,
|
||||
GapNumber: 1350, // missing 1350 snapshot
|
||||
}
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(14), false)
|
||||
verified, err := engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.False(t, verified)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestVoteMessageHandlerWrongGapNumber(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
signedHash = SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Create a vote message that has wrong gap number
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 451,
|
||||
}
|
||||
voteSigningHash = types.VoteSigHash(voteForSign)
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 451,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
// Shall not even trigger the vote threashold as vote pool key also contains the gapNumber
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestVotePoolKeepGoodHygiene(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Inject a second vote with round 16
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(16),
|
||||
Number: big.NewInt(906),
|
||||
}
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash = types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 16
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(16), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Inject a second vote with round 25, which is less than 10 rounds difference to the last vote round
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(25),
|
||||
Number: big.NewInt(907),
|
||||
}
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash = types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 25
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(25), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Let's keep good Hygiene
|
||||
engineV2.HygieneVotePoolFaker()
|
||||
// Let's wait for 5 second for the goroutine
|
||||
<-time.After(5 * time.Second)
|
||||
keyList := engineV2.GetVotePoolKeyListFaker()
|
||||
|
||||
assert.Equal(t, 2, len(keyList))
|
||||
for _, k := range keyList {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
assert.Nil(t, err)
|
||||
if keyedRound < 25-10 {
|
||||
assert.Fail(t, "Did not clean up the vote pool")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ const (
|
|||
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
||||
)
|
||||
|
||||
type rewardLog struct {
|
||||
type RewardLog struct {
|
||||
Sign uint64 `json:"sign"`
|
||||
Reward *big.Int `json:"reward"`
|
||||
}
|
||||
|
|
@ -319,13 +319,13 @@ func DecryptRandomizeFromSecretsAndOpening(secrets [][32]byte, opening [32]byte)
|
|||
}
|
||||
|
||||
// Calculate reward for reward checkpoint.
|
||||
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*rewardLog, error) {
|
||||
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*RewardLog, error) {
|
||||
// Not reward for singer of genesis block and only calculate reward at checkpoint block.
|
||||
number := header.Number.Uint64()
|
||||
prevCheckpoint := number - (rCheckpoint * 2)
|
||||
startBlockNumber := prevCheckpoint + 1
|
||||
endBlockNumber := startBlockNumber + rCheckpoint - 1
|
||||
signers := make(map[common.Address]*rewardLog)
|
||||
signers := make(map[common.Address]*RewardLog)
|
||||
mapBlkHash := map[uint64]common.Hash{}
|
||||
|
||||
data := make(map[common.Hash][]common.Address)
|
||||
|
|
@ -352,7 +352,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
|||
}
|
||||
}
|
||||
header = chain.GetHeader(header.ParentHash, prevCheckpoint)
|
||||
masternodes := utils.GetMasternodesFromCheckpointHeader(header)
|
||||
masternodes := c.GetMasternodesFromCheckpointHeader(header)
|
||||
|
||||
for i := startBlockNumber; i <= endBlockNumber; i++ {
|
||||
if i%common.MergeSignRange == 0 || !chain.Config().IsTIP2019(big.NewInt(int64(i))) {
|
||||
|
|
@ -376,7 +376,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
|||
if exist {
|
||||
signers[addr].Sign++
|
||||
} else {
|
||||
signers[addr] = &rewardLog{1, new(big.Int)}
|
||||
signers[addr] = &RewardLog{1, new(big.Int)}
|
||||
}
|
||||
*totalSigner++
|
||||
}
|
||||
|
|
@ -390,7 +390,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
|||
}
|
||||
|
||||
// Calculate reward for signers.
|
||||
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*rewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
|
||||
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*RewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
|
||||
resultSigners := make(map[common.Address]*big.Int)
|
||||
// Add reward for signers.
|
||||
if totalSigner > 0 {
|
||||
|
|
@ -404,12 +404,11 @@ func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*
|
|||
resultSigners[signer] = calcReward
|
||||
}
|
||||
}
|
||||
jsonSigners, err := json.Marshal(signers)
|
||||
if err != nil {
|
||||
log.Error("Fail to parse json signers", "error", err)
|
||||
return nil, err
|
||||
|
||||
log.Info("Signers data", "totalSigner", totalSigner, "totalReward", chainReward)
|
||||
for addr, signer := range signers {
|
||||
log.Info("Signer reward", "signer", addr, "sign", signer.Sign, "reward", signer.Reward)
|
||||
}
|
||||
log.Info("Signers data", "signers", string(jsonSigners), "totalSigner", totalSigner, "totalReward", chainReward)
|
||||
|
||||
return resultSigners, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||
|
|
|
|||
|
|
@ -352,6 +352,13 @@ func (bc *BlockChain) loadLastState() error {
|
|||
}
|
||||
bc.hc.SetCurrentHeader(currentHeader)
|
||||
|
||||
if engine, ok := bc.Engine().(*XDPoS.XDPoS); ok {
|
||||
err := engine.Initial(bc, currentHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the last known head fast block
|
||||
bc.currentFastBlock.Store(currentBlock)
|
||||
if head := GetHeadFastBlockHash(bc.db); head != (common.Hash{}) {
|
||||
|
|
@ -974,7 +981,20 @@ func (bc *BlockChain) procFutureBlocks() {
|
|||
|
||||
// Insert one by one as chain insertion needs contiguous ancestry between blocks
|
||||
for i := range blocks {
|
||||
bc.InsertChain(blocks[i : i+1])
|
||||
_, err := bc.InsertChain(blocks[i : i+1])
|
||||
// let consensus engine handle the last block (e.g. for voting)
|
||||
if i == len(blocks)-1 && err == nil {
|
||||
engine, ok := bc.Engine().(*XDPoS.XDPoS)
|
||||
if ok {
|
||||
go func() {
|
||||
header := blocks[i].Header()
|
||||
err = engine.HandleProposedBlock(bc, header)
|
||||
if err != nil {
|
||||
log.Info("[procFutureBlocks] handle proposed block has error", "err", err, "block hash", header.Hash(), "number", header.Number)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1590,8 +1610,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
|||
bc.reportBlock(block, nil, err)
|
||||
return i, events, coalescedLogs, err
|
||||
}
|
||||
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(block.NumberU64()/bc.chainConfig.XDPoS.Epoch, tradingState, statedb); err != nil {
|
||||
isEpochSwithBlock, epochNumber, err := engine.IsEpochSwitch(block.Header())
|
||||
if err != nil {
|
||||
log.Error("[insertChain] Error while checking if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
if isEpochSwithBlock {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(epochNumber, tradingState, statedb); err != nil {
|
||||
return i, events, coalescedLogs, err
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1708,9 +1733,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
|||
stats.report(chain, i, dirty)
|
||||
if bc.chainConfig.XDPoS != nil {
|
||||
// epoch block
|
||||
if (chain[i].NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
isEpochSwithBlock, _, err := engine.IsEpochSwitch(chain[i].Header())
|
||||
if err != nil {
|
||||
log.Error("[insertChain] Error while checking and notifying channel CheckpointCh if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
if isEpochSwithBlock {
|
||||
CheckpointCh <- 1
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1855,8 +1884,15 @@ func (bc *BlockChain) getResultBlock(block *types.Block, verifiedM2 bool) (*Resu
|
|||
bc.reportBlock(block, nil, err)
|
||||
return nil, err
|
||||
}
|
||||
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(block.NumberU64()/bc.chainConfig.XDPoS.Epoch, tradingState, statedb); err != nil {
|
||||
|
||||
isEpochSwithBlock, epochNumber, err := engine.IsEpochSwitch(block.Header())
|
||||
if err != nil {
|
||||
log.Error("[getResultBlock] Error while checking block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
|
||||
if isEpochSwithBlock {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(epochNumber, tradingState, statedb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
|
|
@ -2029,9 +2065,13 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L
|
|||
stats.report(types.Blocks{block}, 0, dirty)
|
||||
if bc.chainConfig.XDPoS != nil {
|
||||
// epoch block
|
||||
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
isEpochSwithBlock, _, err := bc.Engine().(*XDPoS.XDPoS).IsEpochSwitch(block.Header())
|
||||
if err != nil {
|
||||
log.Error("[insertBlock] Error while checking if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
if isEpochSwithBlock {
|
||||
CheckpointCh <- 1
|
||||
|
||||
}
|
||||
}
|
||||
// Append a single chain head event if we've progressed the chain
|
||||
|
|
@ -2459,16 +2499,12 @@ func (bc *BlockChain) UpdateM1() error {
|
|||
// if can't get anything, request from contracts
|
||||
stateDB, err := bc.State()
|
||||
if err != nil {
|
||||
|
||||
candidates, err = validator.GetCandidates(opts)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
candidates = state.GetCandidates(stateDB)
|
||||
|
||||
}
|
||||
|
||||
var ms []utils.Masternode
|
||||
|
|
@ -2500,7 +2536,7 @@ func (bc *BlockChain) UpdateM1() error {
|
|||
header := bc.CurrentHeader()
|
||||
var maxMasternodes int
|
||||
// check if block number is increase ms checkpoint
|
||||
if bc.chainConfig.IsTIPIncreaseMasternodes(header.Number) {
|
||||
if bc.chainConfig.IsTIPIncreaseMasternodes(header.Number) || (bc.chainConfig.XDPoS.V2.SwitchBlock != nil && header.Number.Cmp(bc.chainConfig.XDPoS.V2.SwitchBlock) == 1) {
|
||||
// using new masterndoes
|
||||
maxMasternodes = common.MaxMasternodesV2
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -188,10 +188,11 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
|||
}
|
||||
return newcfg, stored, err
|
||||
}
|
||||
// Special case: don't change the existing config of a non-mainnet chain if no new
|
||||
|
||||
// Special case: don't change the existing config of a non-xinfin chain if no new
|
||||
// config is supplied. These chains would get AllProtocolChanges (and a compat error)
|
||||
// if we just continued here.
|
||||
if genesis == nil && stored != params.MainnetGenesisHash {
|
||||
if genesis == nil && newcfg == params.AllEthashProtocolChanges {
|
||||
return storedcfg, stored, nil
|
||||
}
|
||||
|
||||
|
|
@ -211,12 +212,19 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
|||
func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||
switch {
|
||||
case g != nil:
|
||||
log.Info("[configOrDefault] load orignal config", "hash", ghash)
|
||||
return g.Config
|
||||
case ghash == params.MainnetGenesisHash:
|
||||
case ghash == params.XDCMainnetGenesisHash:
|
||||
log.Info("[configOrDefault] load mainnetconfig")
|
||||
return params.XDCMainnetChainConfig
|
||||
case ghash == params.TestnetGenesisHash:
|
||||
log.Info("[configOrDefault] load TestnetChainConfig")
|
||||
return params.TestnetChainConfig
|
||||
case ghash == params.DevnetGenesisHash:
|
||||
log.Info("[configOrDefault] load DevnetChainConfig")
|
||||
return params.DevnetChainConfig
|
||||
default:
|
||||
log.Info("[configOrDefault] load AllEthashProtocolChanges", "hash", ghash)
|
||||
return params.AllEthashProtocolChanges
|
||||
}
|
||||
}
|
||||
|
|
@ -312,26 +320,31 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big
|
|||
|
||||
// DefaultGenesisBlock returns the Ethereum main net genesis block.
|
||||
func DefaultGenesisBlock() *Genesis {
|
||||
config := params.XDCMainnetChainConfig
|
||||
config.XDPoS.V2 = nil
|
||||
return &Genesis{
|
||||
Config: params.XDCMainnetChainConfig,
|
||||
Config: config,
|
||||
Nonce: 0,
|
||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001b82c4bf317fcafe3d77e8b444c82715d216afe845b7bd987fa22c9bac89b71f0ded03f6e150ba31ad670b2b166684657ffff95f4810380ae7381e9bce41231d5dd8cdd7499e418b648c00af75d184a2f9aba09a6fa4a46fb1a6a3919b027d9cac5aa6890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
ExtraData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000000025c65b4b379ac37cf78357c4915f73677022eaffc7d49d0a2cf198deebd6ce581af465944ec8b2bbcfccdea1006a5cfa7d9484b5b293b46964c265c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
GasLimit: 4700000,
|
||||
Difficulty: big.NewInt(1),
|
||||
Alloc: DecodeMainnet(),
|
||||
Timestamp: 1544771829,
|
||||
Alloc: DecodeAllocJson(XDCAllocData),
|
||||
Timestamp: 1559211559,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultTestnetGenesisBlock returns the Ropsten network genesis block.
|
||||
func DefaultTestnetGenesisBlock() *Genesis {
|
||||
config := params.TestnetChainConfig
|
||||
config.XDPoS.V2 = nil
|
||||
return &Genesis{
|
||||
Config: params.TestnetChainConfig,
|
||||
Nonce: 66,
|
||||
ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"),
|
||||
GasLimit: 16777216,
|
||||
Difficulty: big.NewInt(1048576),
|
||||
Alloc: decodePrealloc(testnetAllocData),
|
||||
Nonce: 0,
|
||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000003ea0a3555f9b1de983572bff6444aeb1899ec58c4f7900282f3d371d585ab1361205b0940ab1789c942a5885a8844ee5587c8ac5e371fc39ffe618960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
GasLimit: 4700000,
|
||||
Difficulty: big.NewInt(1),
|
||||
Alloc: DecodeAllocJson(XDCTestAllocData),
|
||||
Timestamp: 1560417871,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,8 +399,8 @@ func decodePrealloc(data string) GenesisAlloc {
|
|||
return ga
|
||||
}
|
||||
|
||||
func DecodeMainnet() GenesisAlloc {
|
||||
mainnetAlloc := GenesisAlloc{}
|
||||
json.Unmarshal([]byte(XDCAllocData), &mainnetAlloc)
|
||||
return mainnetAlloc
|
||||
func DecodeAllocJson(s string) GenesisAlloc {
|
||||
alloc := GenesisAlloc{}
|
||||
json.Unmarshal([]byte(s), &alloc)
|
||||
return alloc
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -17,14 +17,15 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
|
|
|||
117
core/types/consensus_v2.go
Normal file
117
core/types/consensus_v2.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
// Round number type in XDPoS 2.0
|
||||
type Round uint64
|
||||
type Signature []byte
|
||||
|
||||
// Block Info struct in XDPoS 2.0, used for vote message, etc.
|
||||
type BlockInfo struct {
|
||||
Hash common.Hash
|
||||
Round Round
|
||||
Number *big.Int
|
||||
}
|
||||
|
||||
// Vote message in XDPoS 2.0
|
||||
type Vote struct {
|
||||
ProposedBlockInfo *BlockInfo
|
||||
Signature Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
// Timeout message in XDPoS 2.0
|
||||
type Timeout struct {
|
||||
Round Round
|
||||
Signature Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
// BFT Sync Info message in XDPoS 2.0
|
||||
type SyncInfo struct {
|
||||
HighestQuorumCert *QuorumCert
|
||||
HighestTimeoutCert *TimeoutCert
|
||||
}
|
||||
|
||||
// Quorum Certificate struct in XDPoS 2.0
|
||||
type QuorumCert struct {
|
||||
ProposedBlockInfo *BlockInfo
|
||||
Signatures []Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
// Timeout Certificate struct in XDPoS 2.0
|
||||
type TimeoutCert struct {
|
||||
Round Round
|
||||
Signatures []Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
// The parsed extra fields in block header in XDPoS 2.0 (excluding the version byte)
|
||||
// The version byte (consensus version) is the first byte in header's extra and it's only valid with value >= 2
|
||||
type ExtraFields_v2 struct {
|
||||
Round Round
|
||||
QuorumCert *QuorumCert
|
||||
}
|
||||
|
||||
type EpochSwitchInfo struct {
|
||||
Masternodes []common.Address
|
||||
EpochSwitchBlockInfo *BlockInfo
|
||||
EpochSwitchParentBlockInfo *BlockInfo
|
||||
}
|
||||
|
||||
// Encode XDPoS 2.0 extra fields into bytes
|
||||
func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) {
|
||||
bytes, err := rlp.EncodeToBytes(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionByte := []byte{2}
|
||||
return append(versionByte, bytes...), nil
|
||||
}
|
||||
|
||||
func (m *Vote) Hash() common.Hash {
|
||||
return rlpHash(m)
|
||||
}
|
||||
|
||||
func (m *Timeout) Hash() common.Hash {
|
||||
return rlpHash(m)
|
||||
}
|
||||
|
||||
func (m *SyncInfo) Hash() common.Hash {
|
||||
return rlpHash(m)
|
||||
}
|
||||
|
||||
type VoteForSign struct {
|
||||
ProposedBlockInfo *BlockInfo
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
func VoteSigHash(m *VoteForSign) common.Hash {
|
||||
return rlpHash(m)
|
||||
}
|
||||
|
||||
type TimeoutForSign struct {
|
||||
Round Round
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
func TimeoutSigHash(m *TimeoutForSign) common.Hash {
|
||||
return rlpHash(m)
|
||||
}
|
||||
|
||||
func (m *Vote) PoolKey() string {
|
||||
// return the voted block hash
|
||||
return fmt.Sprint(m.ProposedBlockInfo.Round, ":", m.GapNumber, ":", m.ProposedBlockInfo.Number, ":", m.ProposedBlockInfo.Hash.Hex())
|
||||
}
|
||||
|
||||
func (m *Timeout) PoolKey() string {
|
||||
// timeout pool key is round:gapNumber
|
||||
return fmt.Sprint(m.Round, ":", m.GapNumber)
|
||||
}
|
||||
114
core/types/consensus_v2_test.go
Normal file
114
core/types/consensus_v2_test.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions)
|
||||
func DecodeBytesExtraFields(b []byte, val interface{}) error {
|
||||
if len(b) == 0 {
|
||||
return fmt.Errorf("extra field is 0 length")
|
||||
}
|
||||
switch b[0] {
|
||||
case 1:
|
||||
return fmt.Errorf("consensus version 1 is not applicable for decoding extra fields")
|
||||
case 2:
|
||||
return rlp.DecodeBytes(b[1:], val)
|
||||
default:
|
||||
return fmt.Errorf("consensus version %d is not defined", b[0])
|
||||
}
|
||||
}
|
||||
|
||||
func toyExtraFields() *ExtraFields_v2 {
|
||||
round := Round(307)
|
||||
blockInfo := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(900)}
|
||||
signature := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
signatures := []Signature{signature}
|
||||
quorumCert := &QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures, GapNumber: 450}
|
||||
e := &ExtraFields_v2{Round: round, QuorumCert: quorumCert}
|
||||
return e
|
||||
}
|
||||
func TestExtraFieldsEncodeDecode(t *testing.T) {
|
||||
extraFields := toyExtraFields()
|
||||
encoded, err := extraFields.EncodeToBytes()
|
||||
if err != nil {
|
||||
t.Errorf("Error when encoding extra fields")
|
||||
}
|
||||
var decoded ExtraFields_v2
|
||||
err = DecodeBytesExtraFields(encoded, &decoded)
|
||||
if err != nil {
|
||||
t.Errorf("Error when decoding extra fields")
|
||||
}
|
||||
if !reflect.DeepEqual(*extraFields, decoded) {
|
||||
t.Fatalf("Decoded not equal to original extra field, original: %v; decoded: %v", extraFields, decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashAndSigHash(t *testing.T) {
|
||||
round := Round(307)
|
||||
gapNumer := uint64(450)
|
||||
blockInfo1 := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(900)}
|
||||
blockInfo2 := &BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: round - 1, Number: big.NewInt(900)}
|
||||
voteForSign1 := &VoteForSign{ProposedBlockInfo: blockInfo1, GapNumber: gapNumer}
|
||||
voteForSign2 := &VoteForSign{ProposedBlockInfo: blockInfo2, GapNumber: gapNumer}
|
||||
signature1 := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
signature2 := []byte{1, 2, 3, 4, 5, 6, 7, 7}
|
||||
signatures1 := []Signature{signature1}
|
||||
signatures2 := []Signature{signature2}
|
||||
quorumCert1 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures1, GapNumber: 450}
|
||||
quorumCert2 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures2, GapNumber: 450}
|
||||
vote1 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature1, GapNumber: gapNumer}
|
||||
vote2 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature2, GapNumber: gapNumer}
|
||||
if vote1.Hash() == vote2.Hash() {
|
||||
t.Fatalf("Hash of two votes shouldn't equal")
|
||||
}
|
||||
timeout1 := Timeout{Round: 10, Signature: signature1}
|
||||
timeout2 := Timeout{Round: 10, Signature: signature2}
|
||||
if timeout1.Hash() == timeout2.Hash() {
|
||||
t.Fatalf("Hash of two timeouts shouldn't equal")
|
||||
}
|
||||
syncInfo1 := SyncInfo{HighestQuorumCert: quorumCert1}
|
||||
syncInfo2 := SyncInfo{HighestQuorumCert: quorumCert2}
|
||||
if syncInfo1.Hash() == syncInfo2.Hash() {
|
||||
t.Fatalf("Hash of two sync info shouldn't equal")
|
||||
}
|
||||
if VoteSigHash(voteForSign1) == VoteSigHash(voteForSign2) {
|
||||
t.Fatalf("SigHash of two block info shouldn't equal")
|
||||
}
|
||||
round2 := Round(999)
|
||||
if TimeoutSigHash(&TimeoutForSign{
|
||||
Round: round,
|
||||
GapNumber: 450,
|
||||
}) == TimeoutSigHash(&TimeoutForSign{
|
||||
Round: round2,
|
||||
GapNumber: 450,
|
||||
}) {
|
||||
t.Fatalf("SigHash of two round shouldn't equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolKeyFormat(t *testing.T) {
|
||||
voteMsg := &Vote{
|
||||
ProposedBlockInfo: &BlockInfo{
|
||||
Hash: common.Hash{1},
|
||||
Round: 5,
|
||||
Number: big.NewInt(4),
|
||||
},
|
||||
Signature: []byte{},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
voteKey := strings.Split(voteMsg.PoolKey(), ":")
|
||||
assert.Equal(t, "5", voteKey[0])
|
||||
assert.Equal(t, "450", voteKey[1])
|
||||
assert.Equal(t, "4", voteKey[2])
|
||||
assert.Equal(t, common.Hash{1}.String(), voteKey[3])
|
||||
}
|
||||
25
core/types/forensics.go
Normal file
25
core/types/forensics.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package types
|
||||
|
||||
type ForensicsInfo struct {
|
||||
HashPath []string `json:"hashPath"`
|
||||
QuorumCert QuorumCert `json:"quorumCert"`
|
||||
SignerAddresses []string `json:"signerAddresses"`
|
||||
}
|
||||
|
||||
type ForensicsContent struct {
|
||||
DivergingBlockNumber uint64 `json:"divergingBlockNumber"`
|
||||
DivergingBlockHash string `json:"divergingBlockHash"`
|
||||
AcrossEpoch bool `json:"acrossEpoch"`
|
||||
SmallerRoundInfo *ForensicsInfo `json:"smallerRoundInfo"`
|
||||
LargerRoundInfo *ForensicsInfo `json:"largerRoundInfo"`
|
||||
}
|
||||
|
||||
type ForensicProof struct {
|
||||
Id string `json:"id"`
|
||||
ForensicsType string `json:"forensicsType"` // QC or VOTE
|
||||
Content string `json:"content"` // Json string of the forensics data
|
||||
}
|
||||
|
||||
type ForensicsEvent struct {
|
||||
ForensicsProof *ForensicProof
|
||||
}
|
||||
31610
coverage.txt
31610
coverage.txt
File diff suppressed because it is too large
Load diff
|
|
@ -189,6 +189,7 @@ func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
|
|||
|
||||
// SetEtherbase sets the etherbase of the miner
|
||||
func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
|
||||
log.Info("[PrivateMinerAPI] SetEtherbase", "addr", etherbase)
|
||||
api.e.SetEtherbase(etherbase)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,7 +303,17 @@ func (b *EthApiBackend) GetVotersRewards(masternodeAddr common.Address) map[comm
|
|||
number := block.Number().Uint64()
|
||||
engine := b.GetEngine().(*XDPoS.XDPoS)
|
||||
foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr
|
||||
lastCheckpointNumber := number - (number % b.ChainConfig().XDPoS.Epoch) - b.ChainConfig().XDPoS.Epoch // calculate for 2 epochs ago
|
||||
|
||||
// calculate for 2 epochs ago
|
||||
currentCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, block.Number())
|
||||
if err != nil {
|
||||
log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for current checkpoint block", "block", block)
|
||||
}
|
||||
lastCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, big.NewInt(int64(currentCheckpointNumber-1)))
|
||||
if err != nil {
|
||||
log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for last checkpoint block", "block", block)
|
||||
}
|
||||
|
||||
lastCheckpointBlock := chain.GetBlockByNumber(lastCheckpointNumber)
|
||||
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint
|
||||
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
|||
return block, false, err
|
||||
}
|
||||
header := block.Header()
|
||||
sighash, err := wallet.SignHash(accounts.Account{Address: eb}, XDPoS.SigHash(header).Bytes())
|
||||
sighash, err := wallet.SignHash(accounts.Account{Address: eb}, c.SigHash(header).Bytes())
|
||||
if err != nil || sighash == nil {
|
||||
log.Error("Can't get signature hash of m2", "sighash", sighash, "err", err)
|
||||
return block, false, err
|
||||
|
|
@ -285,6 +285,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
|||
XDPoS1.0 Specific hooks
|
||||
*/
|
||||
hooks.AttachConsensusV1Hooks(c, eth.blockchain, chainConfig)
|
||||
hooks.AttachConsensusV2Hooks(c, eth.blockchain, chainConfig)
|
||||
|
||||
eth.txPool.IsSigner = func(address common.Address) bool {
|
||||
currentHeader := eth.blockchain.CurrentHeader()
|
||||
|
|
@ -296,7 +297,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
|||
// not genesis block
|
||||
header = parentHeader
|
||||
}
|
||||
return c.IsAuthorisedAddress(header, eth.blockchain, address)
|
||||
return c.IsAuthorisedAddress(eth.blockchain, header, address)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -364,7 +365,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai
|
|||
// APIs returns the collection of RPC services the ethereum package offers.
|
||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||
func (s *Ethereum) APIs() []rpc.API {
|
||||
apis := ethapi.GetAPIs(s.ApiBackend)
|
||||
apis := ethapi.GetAPIs(s.ApiBackend, s.BlockChain())
|
||||
|
||||
// Append any APIs exposed explicitly by the consensus engine
|
||||
apis = append(apis, s.engine.APIs(s.BlockChain())...)
|
||||
|
|
@ -464,7 +465,7 @@ func (s *Ethereum) ValidateMasternode() (bool, error) {
|
|||
//check if miner's wallet is in set of validators
|
||||
c := s.engine.(*XDPoS.XDPoS)
|
||||
|
||||
authorized := c.IsAuthorisedAddress(s.blockchain.CurrentHeader(), s.blockchain, eb)
|
||||
authorized := c.IsAuthorisedAddress(s.blockchain, s.blockchain.CurrentHeader(), eb)
|
||||
if !authorized {
|
||||
//This miner doesn't belong to set of validators
|
||||
return false, nil
|
||||
|
|
@ -506,7 +507,7 @@ func (s *Ethereum) StartStaking(local bool) error {
|
|||
if XDPoS, ok := s.engine.(*XDPoS.XDPoS); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
log.Error("Etherbase account unavailable locally", "address", eb, "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
}
|
||||
XDPoS.Authorize(eb, wallet.SignHash)
|
||||
|
|
|
|||
162
eth/bft/bft_handler.go
Normal file
162
eth/bft/bft_handler.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
package bft
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
//Define Boradcast Group functions
|
||||
type broadcastVoteFn func(*types.Vote)
|
||||
type broadcastTimeoutFn func(*types.Timeout)
|
||||
type broadcastSyncInfoFn func(*types.SyncInfo)
|
||||
|
||||
type Bfter struct {
|
||||
blockChainReader consensus.ChainReader
|
||||
broadcastCh chan interface{}
|
||||
quit chan struct{}
|
||||
consensus ConsensusFns
|
||||
broadcast BroadcastFns
|
||||
}
|
||||
|
||||
type ConsensusFns struct {
|
||||
verifyVote func(consensus.ChainReader, *types.Vote) (bool, error)
|
||||
voteHandler func(consensus.ChainReader, *types.Vote) error
|
||||
|
||||
verifyTimeout func(consensus.ChainReader, *types.Timeout) (bool, error)
|
||||
timeoutHandler func(consensus.ChainReader, *types.Timeout) error
|
||||
|
||||
verifySyncInfo func(consensus.ChainReader, *types.SyncInfo) (bool, error)
|
||||
syncInfoHandler func(consensus.ChainReader, *types.SyncInfo) error
|
||||
}
|
||||
|
||||
type BroadcastFns struct {
|
||||
Vote broadcastVoteFn
|
||||
Timeout broadcastTimeoutFn
|
||||
SyncInfo broadcastSyncInfoFn
|
||||
}
|
||||
|
||||
func New(broadcasts BroadcastFns, blockChainReader *core.BlockChain) *Bfter {
|
||||
|
||||
return &Bfter{
|
||||
quit: make(chan struct{}),
|
||||
broadcastCh: make(chan interface{}),
|
||||
broadcast: broadcasts,
|
||||
blockChainReader: blockChainReader,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bfter) SetConsensusFuns(engine consensus.Engine) {
|
||||
e := engine.(*XDPoS.XDPoS)
|
||||
b.broadcastCh = e.EngineV2.BroadcastCh
|
||||
b.consensus = ConsensusFns{
|
||||
verifySyncInfo: e.EngineV2.VerifySyncInfoMessage,
|
||||
verifyVote: e.EngineV2.VerifyVoteMessage,
|
||||
verifyTimeout: e.EngineV2.VerifyTimeoutMessage,
|
||||
|
||||
voteHandler: e.EngineV2.VoteHandler,
|
||||
timeoutHandler: e.EngineV2.TimeoutHandler,
|
||||
syncInfoHandler: e.EngineV2.SyncInfoHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bfter) Vote(vote *types.Vote) error {
|
||||
log.Trace("Receive Vote", "hash", vote.Hash().Hex(), "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round)
|
||||
|
||||
verified, err := b.consensus.verifyVote(b.blockChainReader, vote)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Verify BFT Vote", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.broadcastCh <- vote
|
||||
|
||||
if verified {
|
||||
err = b.consensus.voteHandler(b.blockChainReader, vote)
|
||||
if err != nil {
|
||||
if _, ok := err.(*utils.ErrIncomingMessageRoundTooFarFromCurrentRound); ok {
|
||||
log.Debug("vote round not equal", "error", err, "vote", vote.Hash())
|
||||
return err
|
||||
}
|
||||
log.Error("handle BFT Vote", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (b *Bfter) Timeout(timeout *types.Timeout) error {
|
||||
log.Debug("Receive Timeout", "timeout", timeout)
|
||||
|
||||
verified, err := b.consensus.verifyTimeout(b.blockChainReader, timeout)
|
||||
if err != nil {
|
||||
log.Error("Verify BFT Timeout", "timeoutRound", timeout.Round, "timeoutGapNum", timeout.GapNumber, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.broadcastCh <- timeout
|
||||
if verified {
|
||||
err = b.consensus.timeoutHandler(b.blockChainReader, timeout)
|
||||
if err != nil {
|
||||
if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok {
|
||||
log.Debug("timeout round not equal", "error", err)
|
||||
return err
|
||||
}
|
||||
log.Error("handle BFT Timeout", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (b *Bfter) SyncInfo(syncInfo *types.SyncInfo) error {
|
||||
log.Debug("Receive SyncInfo", "syncInfo", syncInfo)
|
||||
|
||||
verified, err := b.consensus.verifySyncInfo(b.blockChainReader, syncInfo)
|
||||
if err != nil {
|
||||
log.Error("Verify BFT SyncInfo", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.broadcastCh <- syncInfo
|
||||
// Process only if verified and qualified
|
||||
if verified {
|
||||
err = b.consensus.syncInfoHandler(b.blockChainReader, syncInfo)
|
||||
if err != nil {
|
||||
log.Error("handle BFT SyncInfo", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start Bft receiver
|
||||
func (b *Bfter) Start() {
|
||||
go b.loop()
|
||||
}
|
||||
func (b *Bfter) Stop() {
|
||||
close(b.quit)
|
||||
}
|
||||
func (b *Bfter) loop() {
|
||||
for {
|
||||
select {
|
||||
case <-b.quit:
|
||||
return
|
||||
case obj := <-b.broadcastCh:
|
||||
switch v := obj.(type) {
|
||||
case *types.Vote:
|
||||
go b.broadcast.Vote(v)
|
||||
case *types.Timeout:
|
||||
go b.broadcast.Timeout(v)
|
||||
case *types.SyncInfo:
|
||||
go b.broadcast.SyncInfo(v)
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
256
eth/bft/bft_handler_test.go
Normal file
256
eth/bft/bft_handler_test.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package bft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// make different votes based on Signatures
|
||||
func makeVotes(n int) []types.Vote {
|
||||
var votes []types.Vote
|
||||
for i := 0; i < n; i++ {
|
||||
votes = append(votes, types.Vote{
|
||||
ProposedBlockInfo: &types.BlockInfo{},
|
||||
Signature: []byte{byte(i)},
|
||||
GapNumber: 0,
|
||||
})
|
||||
}
|
||||
return votes
|
||||
}
|
||||
|
||||
// bfterTester is a test simulator for mocking out bfter worker.
|
||||
type bfterTester struct {
|
||||
bfter *Bfter
|
||||
}
|
||||
|
||||
// newTester creates a new bft fetcher test mocker.
|
||||
func newTester() *bfterTester {
|
||||
testConsensus := &XDPoS.XDPoS{EngineV2: &engine_v2.XDPoS_v2{}}
|
||||
broadcasts := BroadcastFns{}
|
||||
blockChain := &core.BlockChain{}
|
||||
|
||||
tester := &bfterTester{}
|
||||
tester.bfter = New(broadcasts, blockChain)
|
||||
tester.bfter.SetConsensusFuns(testConsensus)
|
||||
tester.bfter.broadcastCh = make(chan interface{})
|
||||
tester.bfter.Start()
|
||||
|
||||
return tester
|
||||
}
|
||||
|
||||
// Tests that a bfter accepts vote and process verfiy and broadcast
|
||||
func TestSequentialVotes(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 10
|
||||
|
||||
tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *types.Vote) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *types.Vote) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Vote = func(*types.Vote) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
votes := makeVotes(targetVotes)
|
||||
for _, vote := range votes {
|
||||
err := tester.bfter.Vote(&vote)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if int(verifyCounter) != targetVotes || int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
|
||||
t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that avoid boardcast if there is bad vote
|
||||
func TestNotBoardcastInvalidVote(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 0
|
||||
|
||||
tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *types.Vote) (bool, error) {
|
||||
return false, fmt.Errorf("This is invalid vote")
|
||||
}
|
||||
|
||||
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *types.Vote) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.Vote = func(*types.Vote) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
vote := types.Vote{ProposedBlockInfo: &types.BlockInfo{}}
|
||||
tester.bfter.Vote(&vote)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardcastButNotProcessDisqualifiedVotes(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 0
|
||||
|
||||
tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *types.Vote) (bool, error) {
|
||||
return false, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *types.Vote) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.Vote = func(*types.Vote) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
vote := types.Vote{ProposedBlockInfo: &types.BlockInfo{}}
|
||||
tester.bfter.Vote(&vote)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetVotes || int(broadcastCounter) != 1 {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardcastButNotProcessDisqualifiedTimeout(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetTimeout := 0
|
||||
|
||||
tester.bfter.consensus.verifyTimeout = func(chain consensus.ChainReader, timeout *types.Timeout) (bool, error) {
|
||||
return false, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *types.Timeout) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.Timeout = func(*types.Timeout) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
timeout := types.Timeout{}
|
||||
tester.bfter.Timeout(&timeout)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetTimeout || int(broadcastCounter) != 1 {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardcastButNotProcessDisqualifiedSyncInfo(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetSyncInfo := 0
|
||||
|
||||
tester.bfter.consensus.verifySyncInfo = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) (bool, error) {
|
||||
return false, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.syncInfoHandler = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.SyncInfo = func(*types.SyncInfo) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
syncInfo := types.SyncInfo{}
|
||||
tester.bfter.SyncInfo(&syncInfo)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetSyncInfo || int(broadcastCounter) != 1 {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetSyncInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: SyncInfo and Timeout Test, should be same as Vote.
|
||||
// Once all test on vote covered, then duplicate to others
|
||||
|
||||
func TestTimeoutHandler(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 1
|
||||
|
||||
tester.bfter.consensus.verifyTimeout = func(consensus.ChainReader, *types.Timeout) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *types.Timeout) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Timeout = func(*types.Timeout) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
timeoutMsg := &types.Timeout{}
|
||||
|
||||
err := tester.bfter.Timeout(timeoutMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if int(verifyCounter) != targetVotes || int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
|
||||
t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutHandlerRoundNotEqual(t *testing.T) {
|
||||
tester := newTester()
|
||||
|
||||
tester.bfter.consensus.verifyTimeout = func(consensus.ChainReader, *types.Timeout) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *types.Timeout) error {
|
||||
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
|
||||
Type: "timeout",
|
||||
IncomingRound: types.Round(1),
|
||||
CurrentRound: types.Round(2),
|
||||
}
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Timeout = func(*types.Timeout) {}
|
||||
|
||||
timeoutMsg := &types.Timeout{}
|
||||
|
||||
err := tester.bfter.Timeout(timeoutMsg)
|
||||
assert.Equal(t, "timeout message round number: 1 does not match currentRound: 2", err.Error())
|
||||
}
|
||||
|
|
@ -36,6 +36,9 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
)
|
||||
|
||||
// proposeBlockHandlerFn is a callback type to handle a block by the consensus
|
||||
type proposeBlockHandlerFn func(header *types.Header) error
|
||||
|
||||
var (
|
||||
MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request
|
||||
MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request
|
||||
|
|
@ -114,7 +117,8 @@ type Downloader struct {
|
|||
blockchain BlockChain
|
||||
|
||||
// Callbacks
|
||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
|
||||
|
||||
// Status
|
||||
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
|
||||
|
|
@ -199,31 +203,32 @@ type BlockChain interface {
|
|||
}
|
||||
|
||||
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
||||
func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn) *Downloader {
|
||||
func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, handleProposedBlock proposeBlockHandlerFn) *Downloader {
|
||||
if lightchain == nil {
|
||||
lightchain = chain
|
||||
}
|
||||
|
||||
dl := &Downloader{
|
||||
mode: mode,
|
||||
stateDB: stateDb,
|
||||
mux: mux,
|
||||
queue: newQueue(),
|
||||
peers: newPeerSet(),
|
||||
rttEstimate: uint64(rttMaxEstimate),
|
||||
rttConfidence: uint64(1000000),
|
||||
blockchain: chain,
|
||||
lightchain: lightchain,
|
||||
dropPeer: dropPeer,
|
||||
headerCh: make(chan dataPack, 1),
|
||||
bodyCh: make(chan dataPack, 1),
|
||||
receiptCh: make(chan dataPack, 1),
|
||||
bodyWakeCh: make(chan bool, 1),
|
||||
receiptWakeCh: make(chan bool, 1),
|
||||
headerProcCh: make(chan []*types.Header, 1),
|
||||
quitCh: make(chan struct{}),
|
||||
stateCh: make(chan dataPack),
|
||||
stateSyncStart: make(chan *stateSync),
|
||||
mode: mode,
|
||||
stateDB: stateDb,
|
||||
mux: mux,
|
||||
queue: newQueue(),
|
||||
peers: newPeerSet(),
|
||||
rttEstimate: uint64(rttMaxEstimate),
|
||||
rttConfidence: uint64(1000000),
|
||||
blockchain: chain,
|
||||
lightchain: lightchain,
|
||||
dropPeer: dropPeer,
|
||||
handleProposedBlock: handleProposedBlock,
|
||||
headerCh: make(chan dataPack, 1),
|
||||
bodyCh: make(chan dataPack, 1),
|
||||
receiptCh: make(chan dataPack, 1),
|
||||
bodyWakeCh: make(chan bool, 1),
|
||||
receiptWakeCh: make(chan bool, 1),
|
||||
headerProcCh: make(chan []*types.Header, 1),
|
||||
quitCh: make(chan struct{}),
|
||||
stateCh: make(chan dataPack),
|
||||
stateSyncStart: make(chan *stateSync),
|
||||
syncStatsState: stateSyncStats{
|
||||
processed: core.GetTrieSyncProgress(stateDb),
|
||||
},
|
||||
|
|
@ -1393,7 +1398,13 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error {
|
|||
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
|
||||
return errInvalidChain
|
||||
}
|
||||
|
||||
if d.handleProposedBlock != nil {
|
||||
header := blocks[len(blocks)-1].Header()
|
||||
err := d.handleProposedBlock(header)
|
||||
if err != nil {
|
||||
log.Info("[downloader] handle proposed block has error", "err", err, "block hash", header.Hash(), "number", header.Number)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ package downloader
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
@ -97,7 +98,7 @@ func newTester() *downloadTester {
|
|||
tester.stateDb = rawdb.NewMemoryDatabase()
|
||||
tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00})
|
||||
|
||||
tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer)
|
||||
tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer, tester.handleProposedBlock)
|
||||
|
||||
return tester
|
||||
}
|
||||
|
|
@ -457,6 +458,11 @@ func (dl *downloadTester) dropPeer(id string) {
|
|||
dl.downloader.UnregisterPeer(id)
|
||||
}
|
||||
|
||||
// an empty handleProposedBlock function
|
||||
func (dl *downloadTester) handleProposedBlock(header *types.Header) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config retrieves the blockchain's chain configuration.
|
||||
func (dl *downloadTester) Config() *params.ChainConfig { return params.TestChainConfig }
|
||||
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
|
|||
defer p.lock.RUnlock()
|
||||
return p.headerThroughput
|
||||
}
|
||||
return ps.idlePeers(62, 64, idle, throughput)
|
||||
return ps.idlePeers(62, 101, idle, throughput)
|
||||
}
|
||||
|
||||
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
|
||||
|
|
@ -491,7 +491,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
|
|||
defer p.lock.RUnlock()
|
||||
return p.blockThroughput
|
||||
}
|
||||
return ps.idlePeers(62, 64, idle, throughput)
|
||||
return ps.idlePeers(62, 101, idle, throughput)
|
||||
}
|
||||
|
||||
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
|
||||
|
|
@ -505,7 +505,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
|
|||
defer p.lock.RUnlock()
|
||||
return p.receiptThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 64, idle, throughput)
|
||||
return ps.idlePeers(63, 101, idle, throughput)
|
||||
}
|
||||
|
||||
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
|
||||
|
|
@ -519,7 +519,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
|
|||
defer p.lock.RUnlock()
|
||||
return p.stateThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 64, idle, throughput)
|
||||
return ps.idlePeers(63, 101, idle, throughput)
|
||||
}
|
||||
|
||||
// idlePeers retrieves a flat list of all currently idle peers satisfying the
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ package fetcher
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
|
|
@ -56,6 +57,9 @@ type bodyRequesterFn func([]common.Hash) error
|
|||
// headerVerifierFn is a callback type to verify a block's header for fast propagation.
|
||||
type headerVerifierFn func(header *types.Header) error
|
||||
|
||||
// proposeBlockHandlerFn is a callback type to handle a block by the consensus
|
||||
type proposeBlockHandlerFn func(header *types.Header) error
|
||||
|
||||
// blockBroadcasterFn is a callback type for broadcasting a block to connected peers.
|
||||
type blockBroadcasterFn func(block *types.Block, propagate bool)
|
||||
|
||||
|
|
@ -133,13 +137,14 @@ type Fetcher struct {
|
|||
queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports)
|
||||
knowns *lru.ARCCache
|
||||
// Callbacks
|
||||
getBlock blockRetrievalFn // Retrieves a block from the local chain
|
||||
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
|
||||
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
|
||||
chainHeight chainHeightFn // Retrieves the current chain's height
|
||||
insertBlock blockInsertFn // Injects a batch of blocks into the chain
|
||||
prepareBlock blockPrepareFn
|
||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||
getBlock blockRetrievalFn // Retrieves a block from the local chain
|
||||
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
|
||||
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
|
||||
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
|
||||
chainHeight chainHeightFn // Retrieves the current chain's height
|
||||
insertBlock blockInsertFn // Injects a batch of blocks into the chain
|
||||
prepareBlock blockPrepareFn
|
||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||
|
||||
// Testing hooks
|
||||
announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the announce list
|
||||
|
|
@ -151,32 +156,33 @@ type Fetcher struct {
|
|||
}
|
||||
|
||||
// New creates a block fetcher to retrieve blocks based on hash announcements.
|
||||
func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher {
|
||||
func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, handleProposedBlock proposeBlockHandlerFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher {
|
||||
knownBlocks, _ := lru.NewARC(blockLimit)
|
||||
return &Fetcher{
|
||||
notify: make(chan *announce),
|
||||
inject: make(chan *inject),
|
||||
blockFilter: make(chan chan []*types.Block),
|
||||
headerFilter: make(chan chan *headerFilterTask),
|
||||
bodyFilter: make(chan chan *bodyFilterTask),
|
||||
done: make(chan common.Hash),
|
||||
quit: make(chan struct{}),
|
||||
announces: make(map[string]int),
|
||||
announced: make(map[common.Hash][]*announce),
|
||||
fetching: make(map[common.Hash]*announce),
|
||||
fetched: make(map[common.Hash][]*announce),
|
||||
completing: make(map[common.Hash]*announce),
|
||||
queue: prque.New(),
|
||||
queues: make(map[string]int),
|
||||
queued: make(map[common.Hash]*inject),
|
||||
knowns: knownBlocks,
|
||||
getBlock: getBlock,
|
||||
verifyHeader: verifyHeader,
|
||||
broadcastBlock: broadcastBlock,
|
||||
chainHeight: chainHeight,
|
||||
insertBlock: insertBlock,
|
||||
prepareBlock: prepareBlock,
|
||||
dropPeer: dropPeer,
|
||||
notify: make(chan *announce),
|
||||
inject: make(chan *inject),
|
||||
blockFilter: make(chan chan []*types.Block),
|
||||
headerFilter: make(chan chan *headerFilterTask),
|
||||
bodyFilter: make(chan chan *bodyFilterTask),
|
||||
done: make(chan common.Hash),
|
||||
quit: make(chan struct{}),
|
||||
announces: make(map[string]int),
|
||||
announced: make(map[common.Hash][]*announce),
|
||||
fetching: make(map[common.Hash]*announce),
|
||||
fetched: make(map[common.Hash][]*announce),
|
||||
completing: make(map[common.Hash]*announce),
|
||||
queue: prque.New(),
|
||||
queues: make(map[string]int),
|
||||
queued: make(map[common.Hash]*inject),
|
||||
knowns: knownBlocks,
|
||||
getBlock: getBlock,
|
||||
verifyHeader: verifyHeader,
|
||||
handleProposedBlock: handleProposedBlock,
|
||||
broadcastBlock: broadcastBlock,
|
||||
chainHeight: chainHeight,
|
||||
insertBlock: insertBlock,
|
||||
prepareBlock: prepareBlock,
|
||||
dropPeer: dropPeer,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -705,13 +711,13 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
|
|||
goto again
|
||||
default:
|
||||
// Something went very wrong, drop the peer
|
||||
log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
|
||||
log.Warn("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
|
||||
f.dropPeer(peer)
|
||||
return
|
||||
}
|
||||
// Run the actual import and log any issues
|
||||
if err := f.insertBlock(block); err != nil {
|
||||
log.Debug("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
|
||||
log.Warn("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -721,6 +727,10 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
|
|||
return
|
||||
}
|
||||
}
|
||||
err = f.handleProposedBlock(block.Header())
|
||||
if err != nil {
|
||||
log.Warn("[insert] Unable to handle new proposed block", "err", err, "number", block.Number(), "hash", block.Hash())
|
||||
}
|
||||
// If import succeeded, broadcast the block
|
||||
propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
|
||||
if !fastBroadCast {
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@ package fetcher
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
@ -92,7 +93,7 @@ func newTester() *fetcherTester {
|
|||
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
|
||||
drops: make(map[string]bool),
|
||||
}
|
||||
tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer)
|
||||
tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.handleProposedBlock, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer)
|
||||
tester.fetcher.Start()
|
||||
|
||||
return tester
|
||||
|
|
@ -111,6 +112,10 @@ func (f *fetcherTester) verifyHeader(header *types.Header) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *fetcherTester) handleProposedBlock(header *types.Header) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// broadcastBlock is a nop placeholder for the block broadcasting.
|
||||
func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) {
|
||||
}
|
||||
|
|
@ -296,6 +301,14 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) {
|
|||
}
|
||||
}
|
||||
|
||||
func verifyProposeBlockHandlerCalled(t *testing.T, proposedBlockChan chan *types.Header) {
|
||||
select {
|
||||
case <-proposedBlockChan:
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
t.Fatalf("did not call propose block handler")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that a fetcher accepts block announcements and initiates retrievals for
|
||||
// them, successfully importing into the local chain.
|
||||
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
|
||||
|
|
@ -317,12 +330,18 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
|
|||
imported <- block
|
||||
return nil
|
||||
}
|
||||
handleProposedBlockChan := make(chan *types.Header)
|
||||
tester.fetcher.handleProposedBlock = func(header *types.Header) error {
|
||||
go func() { handleProposedBlockChan <- header }()
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := len(hashes) - 2; i >= 0; i-- {
|
||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||
verifyImportEvent(t, imported, true)
|
||||
}
|
||||
verifyImportDone(t, imported)
|
||||
verifyProposeBlockHandlerCalled(t, handleProposedBlockChan)
|
||||
}
|
||||
|
||||
// Tests that if blocks are announced by multiple peers (or even the same buggy
|
||||
|
|
|
|||
153
eth/handler.go
153
eth/handler.go
|
|
@ -29,9 +29,11 @@ import (
|
|||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/bft"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/fetcher"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
|
|
@ -80,6 +82,7 @@ type ProtocolManager struct {
|
|||
downloader *downloader.Downloader
|
||||
fetcher *fetcher.Fetcher
|
||||
peers *peerSet
|
||||
bft *bft.Bfter
|
||||
|
||||
SubProtocols []p2p.Protocol
|
||||
|
||||
|
|
@ -104,6 +107,11 @@ type ProtocolManager struct {
|
|||
knownTxs *lru.Cache
|
||||
knowOrderTxs *lru.Cache
|
||||
knowLendingTxs *lru.Cache
|
||||
|
||||
// V2 messages
|
||||
knownVotes *lru.Cache
|
||||
knownSyncInfos *lru.Cache
|
||||
knownTimeouts *lru.Cache
|
||||
}
|
||||
|
||||
// NewProtocolManagerEx add order pool to protocol
|
||||
|
|
@ -123,6 +131,11 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
|
|||
knownTxs, _ := lru.New(maxKnownTxs)
|
||||
knowOrderTxs, _ := lru.New(maxKnownOrderTxs)
|
||||
knowLendingTxs, _ := lru.New(maxKnownLendingTxs)
|
||||
|
||||
knownVotes, _ := lru.New(maxKnownVote)
|
||||
knownSyncInfos, _ := lru.New(maxKnownSyncInfo)
|
||||
knownTimeouts, _ := lru.New(maxKnownTimeout)
|
||||
|
||||
// Create the protocol manager with the base fields
|
||||
manager := &ProtocolManager{
|
||||
networkId: networkID,
|
||||
|
|
@ -138,6 +151,9 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
|
|||
knownTxs: knownTxs,
|
||||
knowOrderTxs: knowOrderTxs,
|
||||
knowLendingTxs: knowLendingTxs,
|
||||
knownVotes: knownVotes,
|
||||
knownSyncInfos: knownSyncInfos,
|
||||
knownTimeouts: knownTimeouts,
|
||||
orderpool: nil,
|
||||
lendingpool: nil,
|
||||
orderTxSub: nil,
|
||||
|
|
@ -189,12 +205,25 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
|
|||
if len(manager.SubProtocols) == 0 {
|
||||
return nil, errIncompatibleConfig
|
||||
}
|
||||
|
||||
var handleProposedBlock func(header *types.Header) error
|
||||
if config.XDPoS != nil {
|
||||
handleProposedBlock = func(header *types.Header) error {
|
||||
return engine.(*XDPoS.XDPoS).HandleProposedBlock(blockchain, header)
|
||||
}
|
||||
} else {
|
||||
handleProposedBlock = func(header *types.Header) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the different synchronisation mechanisms
|
||||
manager.downloader = downloader.New(mode, chaindb, manager.eventMux, blockchain, nil, manager.removePeer)
|
||||
manager.downloader = downloader.New(mode, chaindb, manager.eventMux, blockchain, nil, manager.removePeer, handleProposedBlock)
|
||||
|
||||
validator := func(header *types.Header) error {
|
||||
return engine.VerifyHeader(blockchain, header, true)
|
||||
}
|
||||
|
||||
heighter := func() uint64 {
|
||||
return blockchain.CurrentBlock().NumberU64()
|
||||
}
|
||||
|
|
@ -217,7 +246,17 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
|
|||
atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
|
||||
return manager.blockchain.PrepareBlock(block)
|
||||
}
|
||||
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer)
|
||||
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, handleProposedBlock, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer)
|
||||
//Define bft function
|
||||
broadcasts := bft.BroadcastFns{
|
||||
Vote: manager.BroadcastVote,
|
||||
Timeout: manager.BroadcastTimeout,
|
||||
SyncInfo: manager.BroadcastSyncInfo,
|
||||
}
|
||||
manager.bft = bft.New(broadcasts, blockchain)
|
||||
if blockchain.Config().XDPoS != nil {
|
||||
manager.bft.SetConsensusFuns(engine)
|
||||
}
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
|
@ -253,7 +292,6 @@ func (pm *ProtocolManager) Start(maxPeers int) {
|
|||
// broadcast transactions
|
||||
pm.txCh = make(chan core.TxPreEvent, txChanSize)
|
||||
pm.txSub = pm.txpool.SubscribeTxPreEvent(pm.txCh)
|
||||
|
||||
pm.orderTxCh = make(chan core.OrderTxPreEvent, txChanSize)
|
||||
if pm.orderpool != nil {
|
||||
pm.orderTxSub = pm.orderpool.SubscribeTxPreEvent(pm.orderTxCh)
|
||||
|
|
@ -808,6 +846,63 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
|||
if pm.lendingpool != nil {
|
||||
pm.lendingpool.AddRemotes(txs)
|
||||
}
|
||||
case msg.Code == VoteMsg:
|
||||
// VoteMsg arrived, make sure we have a valid and fresh chain to handle them
|
||||
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
var vote types.Vote
|
||||
if err := msg.Decode(&vote); err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
p.MarkVote(vote.Hash())
|
||||
|
||||
exist, _ := pm.knownVotes.ContainsOrAdd(vote.Hash(), true)
|
||||
if !exist {
|
||||
go pm.bft.Vote(&vote)
|
||||
} else {
|
||||
log.Debug("Discarded vote, known vote", "vote hash", vote.Hash(), "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round)
|
||||
}
|
||||
|
||||
case msg.Code == TimeoutMsg:
|
||||
// TimeoutMsg arrived, make sure we have a valid and fresh chain to handle them
|
||||
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
var timeout types.Timeout
|
||||
if err := msg.Decode(&timeout); err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
p.MarkTimeout(timeout.Hash())
|
||||
|
||||
exist, _ := pm.knownTimeouts.ContainsOrAdd(timeout.Hash(), true)
|
||||
|
||||
if !exist {
|
||||
go pm.bft.Timeout(&timeout)
|
||||
} else {
|
||||
log.Trace("Discarded Timeout, known Timeout", "Signature", timeout.Signature, "hash", timeout.Hash(), "round", timeout.Round)
|
||||
}
|
||||
|
||||
case msg.Code == SyncInfoMsg:
|
||||
// SyncInfoMsg arrived, make sure we have a valid and fresh chain to handle them
|
||||
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
var syncInfo types.SyncInfo
|
||||
if err := msg.Decode(&syncInfo); err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
p.MarkSyncInfo(syncInfo.Hash())
|
||||
|
||||
exist, _ := pm.knownSyncInfos.ContainsOrAdd(syncInfo.Hash(), true)
|
||||
if !exist {
|
||||
go pm.bft.SyncInfo(&syncInfo)
|
||||
} else {
|
||||
log.Trace("Discarded SyncInfo, known SyncInfo", "hash", syncInfo.Hash())
|
||||
}
|
||||
|
||||
default:
|
||||
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
|
||||
|
|
@ -859,6 +954,58 @@ func (pm *ProtocolManager) BroadcastTx(hash common.Hash, tx *types.Transaction)
|
|||
log.Trace("Broadcast transaction", "hash", hash, "recipients", len(peers))
|
||||
}
|
||||
|
||||
// BroadcastVote will propagate a Vote to all peers which are not known to
|
||||
// already have the given vote.
|
||||
func (pm *ProtocolManager) BroadcastVote(vote *types.Vote) {
|
||||
hash := vote.Hash()
|
||||
peers := pm.peers.PeersWithoutVote(hash)
|
||||
if len(peers) > 0 {
|
||||
for _, peer := range peers {
|
||||
err := peer.SendVote(vote)
|
||||
if err != nil {
|
||||
log.Error("[BroadcastVote] Fail to broadcast vote message", "peerId", peer.id, "version", peer.version, "blockNum", vote.ProposedBlockInfo.Number, "err", err)
|
||||
pm.removePeer(peer.id)
|
||||
}
|
||||
}
|
||||
log.Trace("Propagated Vote", "vote hash", vote.Hash(), "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round, "recipients", len(peers))
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastTimeout will propagate a Timeout to all peers which are not known to
|
||||
// already have the given timeout.
|
||||
func (pm *ProtocolManager) BroadcastTimeout(timeout *types.Timeout) {
|
||||
hash := timeout.Hash()
|
||||
peers := pm.peers.PeersWithoutTimeout(hash)
|
||||
if len(peers) > 0 {
|
||||
for _, peer := range peers {
|
||||
err := peer.SendTimeout(timeout)
|
||||
if err != nil {
|
||||
log.Error("[BroadcastTimeout] Fail to broadcast timeout message, remove peer", "peerId", peer.id, "version", peer.version, "timeout", timeout, "err", err)
|
||||
pm.removePeer(peer.id)
|
||||
}
|
||||
}
|
||||
log.Trace("Propagated Timeout", "hash", hash, "recipients", len(peers))
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastSyncInfo will propagate a SyncInfo to all peers which are not known to
|
||||
// already have the given SyncInfo.
|
||||
func (pm *ProtocolManager) BroadcastSyncInfo(syncInfo *types.SyncInfo) {
|
||||
hash := syncInfo.Hash()
|
||||
peers := pm.peers.PeersWithoutSyncInfo(hash)
|
||||
if len(peers) > 0 {
|
||||
for _, peer := range peers {
|
||||
err := peer.SendSyncInfo(syncInfo)
|
||||
if err != nil {
|
||||
log.Error("[BroadcastSyncInfo] Fail to broadcast syncInfo message, remove peer", "peerId", peer.id, "version", peer.version, "syncInfo", syncInfo, "err", err)
|
||||
pm.removePeer(peer.id)
|
||||
}
|
||||
}
|
||||
log.Trace("Propagated SyncInfo", "hash", hash, "recipients", len(peers))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// OrderBroadcastTx will propagate a transaction to all peers which are not known to
|
||||
// already have the given transaction.
|
||||
func (pm *ProtocolManager) OrderBroadcastTx(hash common.Hash, tx *types.OrderTransaction) {
|
||||
|
|
|
|||
|
|
@ -17,13 +17,14 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
|
|||
|
|
@ -22,12 +22,13 @@ package eth
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
|
|||
299
eth/hooks/engine_v2_hooks.go
Normal file
299
eth/hooks/engine_v2_hooks.go
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
package hooks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/contracts"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/util"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
)
|
||||
|
||||
func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConfig *params.ChainConfig) {
|
||||
// Hook scans for bad masternodes and decide to penalty them
|
||||
adaptor.EngineV2.HookPenalty = func(chain consensus.ChainReader, number *big.Int, currentHash common.Hash, candidates []common.Address) ([]common.Address, error) {
|
||||
start := time.Now()
|
||||
listBlockHash := make([]common.Hash, chain.Config().XDPoS.Epoch)
|
||||
|
||||
// get list block hash & stats total created block
|
||||
statMiners := make(map[common.Address]int)
|
||||
listBlockHash[0] = currentHash
|
||||
parentNumber := number.Uint64() - 1
|
||||
parentHash := currentHash
|
||||
|
||||
// check and wait the latest block is already in the disk
|
||||
// sometimes blocks are yet inserted into block
|
||||
for timeout := 0; ; timeout++ {
|
||||
parentHeader := chain.GetHeader(parentHash, parentNumber)
|
||||
if parentHeader != nil { // found the latest block in the disk
|
||||
break
|
||||
}
|
||||
log.Info("[V2 Hook Penalty] parentHeader is nil, wait block to be writen in disk", "parentNumber", parentNumber)
|
||||
time.Sleep(200 * time.Millisecond) // 0.2s
|
||||
|
||||
if timeout > 50 { // wait over 10s
|
||||
log.Error("[V2 Hook Penalty] parentHeader is nil, wait too long not writen in to disk", "parentNumber", parentNumber)
|
||||
return []common.Address{}, fmt.Errorf("parentHeader is nil")
|
||||
}
|
||||
}
|
||||
|
||||
for i := uint64(1); ; i++ {
|
||||
parentHeader := chain.GetHeader(parentHash, parentNumber)
|
||||
isEpochSwitch, _, err := adaptor.EngineV2.IsEpochSwitch(parentHeader)
|
||||
if err != nil {
|
||||
log.Error("[HookPenalty] isEpochSwitch", "err", err)
|
||||
return []common.Address{}, err
|
||||
}
|
||||
if isEpochSwitch {
|
||||
break
|
||||
}
|
||||
miner := parentHeader.Coinbase // we can directly use coinbase, since it's verified (Verification is a TODO)
|
||||
_, exist := statMiners[miner]
|
||||
if exist {
|
||||
statMiners[miner]++
|
||||
} else {
|
||||
statMiners[miner] = 1
|
||||
}
|
||||
parentHash = parentHeader.ParentHash
|
||||
listBlockHash[i] = parentHash
|
||||
parentNumber--
|
||||
}
|
||||
|
||||
// add list not miner to penalties
|
||||
preMasternodes := adaptor.EngineV2.GetMasternodesByHash(chain, currentHash)
|
||||
penalties := []common.Address{}
|
||||
for miner, total := range statMiners {
|
||||
if total < common.MinimunMinerBlockPerEpoch {
|
||||
log.Info("[HookPenalty] Find a node does not create enough block", "addr", miner.Hex(), "total", total, "require", common.MinimunMinerBlockPerEpoch)
|
||||
penalties = append(penalties, miner)
|
||||
}
|
||||
}
|
||||
for _, addr := range preMasternodes {
|
||||
if _, exist := statMiners[addr]; !exist {
|
||||
log.Info("[HookPenalty] Find a node do not create any block", "addr", addr.Hex())
|
||||
penalties = append(penalties, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// get list check penalties signing block & list master nodes wil comeback
|
||||
// start to calc comeback at v2 block + limitPenaltyEpochV2 to avoid reading v1 blocks
|
||||
comebackHeight := (common.LimitPenaltyEpochV2+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64()
|
||||
penComebacks := []common.Address{}
|
||||
if number.Uint64() > comebackHeight {
|
||||
pens := adaptor.EngineV2.GetPreviousPenaltyByHash(chain, currentHash, common.LimitPenaltyEpochV2)
|
||||
for _, p := range pens {
|
||||
for _, addr := range candidates {
|
||||
if p == addr {
|
||||
log.Info("[HookPenalty] get previous penalty node and add into comeback list", "addr", addr)
|
||||
penComebacks = append(penComebacks, p)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop for each block to check missing sign. with comeback nodes
|
||||
mapBlockHash := map[common.Hash]bool{}
|
||||
startRange := common.RangeReturnSigner - 1
|
||||
// to prevent visiting outside index of listBlockHash
|
||||
if startRange >= len(listBlockHash) {
|
||||
startRange = len(listBlockHash) - 1
|
||||
}
|
||||
for i := startRange; i >= 0; i-- {
|
||||
if len(penComebacks) > 0 {
|
||||
blockNumber := number.Uint64() - uint64(i) - 1
|
||||
bhash := listBlockHash[i]
|
||||
if blockNumber%common.MergeSignRange == 0 {
|
||||
mapBlockHash[bhash] = true
|
||||
}
|
||||
signData, ok := adaptor.GetCachedSigningTxs(bhash)
|
||||
if !ok {
|
||||
block := chain.GetBlock(bhash, blockNumber)
|
||||
txs := block.Transactions()
|
||||
signData = adaptor.CacheSigningTxs(bhash, txs)
|
||||
}
|
||||
txs := signData.([]*types.Transaction)
|
||||
// Check signer signed?
|
||||
for _, tx := range txs {
|
||||
blkHash := common.BytesToHash(tx.Data()[len(tx.Data())-32:])
|
||||
from := *tx.From()
|
||||
if mapBlockHash[blkHash] {
|
||||
for j, addr := range penComebacks {
|
||||
if from == addr {
|
||||
// Remove it from dupSigners.
|
||||
penComebacks = append(penComebacks[:j], penComebacks[j+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, comeback := range penComebacks {
|
||||
ok := true
|
||||
for _, p := range penalties {
|
||||
if p == comeback {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
penalties = append(penalties, comeback)
|
||||
}
|
||||
}
|
||||
|
||||
for i, p := range penalties {
|
||||
log.Info("[HookPenalty] Final penalty list", "index", i, "addr", p)
|
||||
}
|
||||
log.Info("[HookPenalty] Time Calculated HookPenaltyV2 ", "block", number, "time", common.PrettyDuration(time.Since(start)))
|
||||
return penalties, nil
|
||||
}
|
||||
|
||||
// Hook calculates reward for masternodes
|
||||
adaptor.EngineV2.HookReward = func(chain consensus.ChainReader, stateBlock *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) {
|
||||
number := header.Number.Uint64()
|
||||
foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr
|
||||
if foundationWalletAddr == (common.Address{}) {
|
||||
log.Error("Foundation Wallet Address is empty", "error", foundationWalletAddr)
|
||||
return nil, errors.New("foundation wallet address is empty")
|
||||
}
|
||||
rewards := make(map[string]interface{})
|
||||
// skip hook reward if this is the first v2
|
||||
if number == chain.Config().XDPoS.V2.SwitchBlock.Uint64()+1 {
|
||||
return rewards, nil
|
||||
}
|
||||
start := time.Now()
|
||||
// Get reward inflation.
|
||||
chainReward := new(big.Int).Mul(new(big.Int).SetUint64(chain.Config().XDPoS.Reward), new(big.Int).SetUint64(params.Ether))
|
||||
chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear)
|
||||
|
||||
// Get signers/signing tx count
|
||||
totalSigner := new(uint64)
|
||||
signers, err := GetSigningTxCount(adaptor, chain, header, totalSigner)
|
||||
|
||||
log.Debug("Time Get Signers", "block", header.Number.Uint64(), "time", common.PrettyDuration(time.Since(start)))
|
||||
if err != nil {
|
||||
log.Error("[HookReward] Fail to get signers count for reward checkpoint", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
rewards["signers"] = signers
|
||||
rewardSigners, err := contracts.CalculateRewardForSigner(chainReward, signers, *totalSigner)
|
||||
if err != nil {
|
||||
log.Error("[HookReward] Fail to calculate reward for signers", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
// Add reward for coin holders.
|
||||
voterResults := make(map[common.Address]interface{})
|
||||
if len(signers) > 0 {
|
||||
for signer, calcReward := range rewardSigners {
|
||||
err, rewards := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number)
|
||||
if err != nil {
|
||||
log.Error("[HookReward] Fail to calculate reward for holders.", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(rewards) > 0 {
|
||||
for holder, reward := range rewards {
|
||||
stateBlock.AddBalance(holder, reward)
|
||||
}
|
||||
}
|
||||
voterResults[signer] = rewards
|
||||
}
|
||||
}
|
||||
rewards["rewards"] = voterResults
|
||||
log.Debug("Time Calculated HookReward ", "block", header.Number.Uint64(), "time", common.PrettyDuration(time.Since(start)))
|
||||
return rewards, nil
|
||||
}
|
||||
}
|
||||
|
||||
// get signing transaction sender count
|
||||
func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, totalSigner *uint64) (map[common.Address]*contracts.RewardLog, error) {
|
||||
// header should be a new epoch switch block
|
||||
number := header.Number.Uint64()
|
||||
rewardEpochCount := 2
|
||||
signEpochCount := 1
|
||||
signers := make(map[common.Address]*contracts.RewardLog)
|
||||
mapBlkHash := map[uint64]common.Hash{}
|
||||
|
||||
data := make(map[common.Hash][]common.Address)
|
||||
epochCount := 0
|
||||
var masternodes []common.Address
|
||||
var startBlockNumber, endBlockNumber uint64
|
||||
for i := number - 1; ; i-- {
|
||||
header = chain.GetHeader(header.ParentHash, i)
|
||||
isEpochSwitch, _, err := c.IsEpochSwitch(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isEpochSwitch && i != chain.Config().XDPoS.V2.SwitchBlock.Uint64()+1 {
|
||||
epochCount += 1
|
||||
if epochCount == signEpochCount {
|
||||
endBlockNumber = header.Number.Uint64() - 1
|
||||
}
|
||||
if epochCount == rewardEpochCount {
|
||||
startBlockNumber = header.Number.Uint64() + 1
|
||||
masternodes = c.GetMasternodesFromCheckpointHeader(header)
|
||||
break
|
||||
}
|
||||
}
|
||||
mapBlkHash[i] = header.Hash()
|
||||
signData, ok := c.GetCachedSigningTxs(header.Hash())
|
||||
if !ok {
|
||||
log.Debug("Failed get from cached", "hash", header.Hash().String(), "number", i)
|
||||
block := chain.GetBlock(header.Hash(), i)
|
||||
txs := block.Transactions()
|
||||
signData = c.CacheSigningTxs(header.Hash(), txs)
|
||||
}
|
||||
txs := signData.([]*types.Transaction)
|
||||
for _, tx := range txs {
|
||||
blkHash := common.BytesToHash(tx.Data()[len(tx.Data())-32:])
|
||||
from := *tx.From()
|
||||
data[blkHash] = append(data[blkHash], from)
|
||||
}
|
||||
}
|
||||
|
||||
for i := startBlockNumber; i <= endBlockNumber; i++ {
|
||||
if i%common.MergeSignRange == 0 {
|
||||
addrs := data[mapBlkHash[i]]
|
||||
// Filter duplicate address.
|
||||
if len(addrs) > 0 {
|
||||
addrSigners := make(map[common.Address]bool)
|
||||
for _, masternode := range masternodes {
|
||||
for _, addr := range addrs {
|
||||
if addr == masternode {
|
||||
if _, ok := addrSigners[addr]; !ok {
|
||||
addrSigners[addr] = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for addr := range addrSigners {
|
||||
_, exist := signers[addr]
|
||||
if exist {
|
||||
signers[addr].Sign++
|
||||
} else {
|
||||
signers[addr] = &contracts.RewardLog{Sign: 1, Reward: new(big.Int)}
|
||||
}
|
||||
*totalSigner++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Calculate reward at checkpoint", "startBlock", startBlockNumber, "endBlock", endBlockNumber)
|
||||
|
||||
return signers, nil
|
||||
}
|
||||
134
eth/peer.go
134
eth/peer.go
|
|
@ -41,6 +41,9 @@ const (
|
|||
maxKnownOrderTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
|
||||
maxKnownLendingTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
|
||||
maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS)
|
||||
maxKnownVote = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
|
||||
maxKnownTimeout = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
|
||||
maxKnownSyncInfo = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
|
||||
handshakeTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
|
|
@ -66,10 +69,15 @@ type peer struct {
|
|||
td *big.Int
|
||||
lock sync.RWMutex
|
||||
|
||||
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
|
||||
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
|
||||
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
|
||||
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
|
||||
|
||||
knownOrderTxs mapset.Set // Set of order transaction hashes known to be known by this peer
|
||||
knownLendingTxs mapset.Set // Set of lending transaction hashes known to be known by this peer
|
||||
|
||||
knownVote mapset.Set // Set of BFT Vote known to be known by this peer
|
||||
knownTimeout mapset.Set // Set of BFT timeout known to be known by this peer
|
||||
knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer`
|
||||
}
|
||||
|
||||
func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
||||
|
|
@ -84,6 +92,10 @@ func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
|||
knownBlocks: mapset.NewSet(),
|
||||
knownOrderTxs: mapset.NewSet(),
|
||||
knownLendingTxs: mapset.NewSet(),
|
||||
|
||||
knownVote: mapset.NewSet(),
|
||||
knownTimeout: mapset.NewSet(),
|
||||
knownSyncInfo: mapset.NewSet(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +169,36 @@ func (p *peer) MarkLendingTransaction(hash common.Hash) {
|
|||
p.knownLendingTxs.Add(hash)
|
||||
}
|
||||
|
||||
// MarkVote marks a vote as known for the peer, ensuring that it
|
||||
// will never be propagated to this particular peer.
|
||||
func (p *peer) MarkVote(hash common.Hash) {
|
||||
// If we reached the memory allowance, drop a previously known transaction hash
|
||||
for p.knownVote.Cardinality() >= maxKnownVote {
|
||||
p.knownVote.Pop()
|
||||
}
|
||||
p.knownVote.Add(hash)
|
||||
}
|
||||
|
||||
// MarkTimeout marks a timeout as known for the peer, ensuring that it
|
||||
// will never be propagated to this particular peer.
|
||||
func (p *peer) MarkTimeout(hash common.Hash) {
|
||||
// If we reached the memory allowance, drop a previously known transaction hash
|
||||
for p.knownTimeout.Cardinality() >= maxKnownTimeout {
|
||||
p.knownTimeout.Pop()
|
||||
}
|
||||
p.knownTimeout.Add(hash)
|
||||
}
|
||||
|
||||
// MarkSyncInfo marks a syncInfo as known for the peer, ensuring that it
|
||||
// will never be propagated to this particular peer.
|
||||
func (p *peer) MarkSyncInfo(hash common.Hash) {
|
||||
// If we reached the memory allowance, drop a previously known transaction hash
|
||||
for p.knownSyncInfo.Cardinality() >= maxKnownSyncInfo {
|
||||
p.knownSyncInfo.Pop()
|
||||
}
|
||||
p.knownSyncInfo.Add(hash)
|
||||
}
|
||||
|
||||
// SendTransactions sends transactions to the peer and includes the hashes
|
||||
// in its transaction hash set for future reference.
|
||||
func (p *peer) SendTransactions(txs types.Transactions) error {
|
||||
|
|
@ -256,6 +298,49 @@ func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *peer) SendVote(vote *types.Vote) error {
|
||||
p.knownVote.Add(vote.Hash())
|
||||
if p.pairRw != nil {
|
||||
return p2p.Send(p.pairRw, VoteMsg, vote)
|
||||
} else {
|
||||
return p2p.Send(p.rw, VoteMsg, vote)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func (p *peer) AsyncSendVote() {
|
||||
|
||||
}
|
||||
*/
|
||||
func (p *peer) SendTimeout(timeout *types.Timeout) error {
|
||||
p.knownTimeout.Add(timeout.Hash())
|
||||
if p.pairRw != nil {
|
||||
return p2p.Send(p.pairRw, TimeoutMsg, timeout)
|
||||
} else {
|
||||
return p2p.Send(p.rw, TimeoutMsg, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func (p *peer) AsyncSendTimeout() {
|
||||
|
||||
}
|
||||
*/
|
||||
func (p *peer) SendSyncInfo(syncInfo *types.SyncInfo) error {
|
||||
p.knownSyncInfo.Add(syncInfo.Hash())
|
||||
if p.pairRw != nil {
|
||||
return p2p.Send(p.pairRw, SyncInfoMsg, syncInfo)
|
||||
} else {
|
||||
return p2p.Send(p.rw, SyncInfoMsg, syncInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func (p *peer) AsyncSendSyncInfo() {
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// RequestOneHeader is a wrapper around the header query functions to fetch a
|
||||
// single header. It is used solely by the fetcher.
|
||||
func (p *peer) RequestOneHeader(hash common.Hash) error {
|
||||
|
|
@ -486,6 +571,51 @@ func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer {
|
|||
return list
|
||||
}
|
||||
|
||||
// PeersWithoutVote retrieves a list of peers that do not have a given block in
|
||||
// their set of known hashes.
|
||||
func (ps *peerSet) PeersWithoutVote(hash common.Hash) []*peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*peer, 0, len(ps.peers))
|
||||
for _, p := range ps.peers {
|
||||
if !p.knownVote.Contains(hash) {
|
||||
list = append(list, p)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// PeersWithoutTimeout retrieves a list of peers that do not have a given block in
|
||||
// their set of known hashes.
|
||||
func (ps *peerSet) PeersWithoutTimeout(hash common.Hash) []*peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*peer, 0, len(ps.peers))
|
||||
for _, p := range ps.peers {
|
||||
if !p.knownTimeout.Contains(hash) {
|
||||
list = append(list, p)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// PeersWithoutSyncInfo retrieves a list of peers that do not have a given block in
|
||||
// their set of known hashes.
|
||||
func (ps *peerSet) PeersWithoutSyncInfo(hash common.Hash) []*peer {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*peer, 0, len(ps.peers))
|
||||
for _, p := range ps.peers {
|
||||
if !p.knownSyncInfo.Contains(hash) {
|
||||
list = append(list, p)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// PeersWithoutTx retrieves a list of peers that do not have a given transaction
|
||||
// in their set of known hashes.
|
||||
func (ps *peerSet) OrderPeersWithoutTx(hash common.Hash) []*peer {
|
||||
|
|
|
|||
|
|
@ -30,18 +30,19 @@ import (
|
|||
|
||||
// Constants to match up protocol versions and messages
|
||||
const (
|
||||
eth62 = 62
|
||||
eth63 = 63
|
||||
eth62 = 62
|
||||
eth63 = 63
|
||||
xdpos2 = 100
|
||||
)
|
||||
|
||||
// Official short name of the protocol used during capability negotiation.
|
||||
var ProtocolName = "eth"
|
||||
|
||||
// Supported versions of the eth protocol (first is primary).
|
||||
var ProtocolVersions = []uint{eth63, eth62}
|
||||
var ProtocolVersions = []uint{xdpos2, eth63, eth62}
|
||||
|
||||
// Number of implemented message corresponding to different protocol versions.
|
||||
var ProtocolLengths = []uint64{17, 8}
|
||||
var ProtocolLengths = []uint64{227, 17, 8}
|
||||
|
||||
const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
|
||||
|
||||
|
|
@ -63,6 +64,11 @@ const (
|
|||
NodeDataMsg = 0x0e
|
||||
GetReceiptsMsg = 0x0f
|
||||
ReceiptsMsg = 0x10
|
||||
|
||||
// Protocol messages belonging to xdpos2/100
|
||||
VoteMsg = 0xe0
|
||||
TimeoutMsg = 0xe1
|
||||
SyncInfoMsg = 0xe2
|
||||
)
|
||||
|
||||
type errCode int
|
||||
|
|
|
|||
|
|
@ -134,7 +134,9 @@ func (pm *ProtocolManager) txsyncLoop() {
|
|||
func (pm *ProtocolManager) syncer() {
|
||||
// Start and ensure cleanup of sync mechanisms
|
||||
pm.fetcher.Start()
|
||||
pm.bft.Start()
|
||||
defer pm.fetcher.Stop()
|
||||
defer pm.bft.Stop()
|
||||
defer pm.downloader.Terminate()
|
||||
|
||||
// Wait for different events to fire synchronisation operations
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common/mclock"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth"
|
||||
|
|
@ -56,6 +57,12 @@ const (
|
|||
chainHeadChanSize = 10
|
||||
)
|
||||
|
||||
type consensusEngine interface {
|
||||
// SubscribeForensicsEvent should return an event subscription of
|
||||
// ForensicsEvent and send events to the given channel.
|
||||
SubscribeForensicsEvent(chan<- types.ForensicsEvent) event.Subscription
|
||||
}
|
||||
|
||||
type txPool interface {
|
||||
// SubscribeTxPreEvent should return an event subscription of
|
||||
// TxPreEvent and send events to the given channel.
|
||||
|
|
@ -140,9 +147,11 @@ func (s *Service) loop() {
|
|||
// Subscribe to chain events to execute updates on
|
||||
var blockchain blockChain
|
||||
var txpool txPool
|
||||
var engine consensusEngine
|
||||
if s.eth != nil {
|
||||
blockchain = s.eth.BlockChain()
|
||||
txpool = s.eth.TxPool()
|
||||
engine = s.eth.Engine().(*XDPoS.XDPoS)
|
||||
} else {
|
||||
blockchain = s.les.BlockChain()
|
||||
txpool = s.les.TxPool()
|
||||
|
|
@ -156,11 +165,19 @@ func (s *Service) loop() {
|
|||
txSub := txpool.SubscribeTxPreEvent(txEventCh)
|
||||
defer txSub.Unsubscribe()
|
||||
|
||||
// Forensics events
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
if engine != nil {
|
||||
forensicsSub := engine.SubscribeForensicsEvent(forensicsEventCh)
|
||||
defer forensicsSub.Unsubscribe()
|
||||
}
|
||||
|
||||
// Start a goroutine that exhausts the subsciptions to avoid events piling up
|
||||
var (
|
||||
quitCh = make(chan struct{})
|
||||
headCh = make(chan *types.Block, 1)
|
||||
txCh = make(chan struct{}, 1)
|
||||
quitCh = make(chan struct{})
|
||||
headCh = make(chan *types.Block, 1)
|
||||
txCh = make(chan struct{}, 1)
|
||||
forensicsCh = make(chan *types.ForensicProof, 1)
|
||||
)
|
||||
go func() {
|
||||
var lastTx mclock.AbsTime
|
||||
|
|
@ -168,6 +185,11 @@ func (s *Service) loop() {
|
|||
HandleLoop:
|
||||
for {
|
||||
select {
|
||||
case forensics := <-forensicsEventCh:
|
||||
select {
|
||||
case forensicsCh <- forensics.ForensicsProof:
|
||||
default:
|
||||
}
|
||||
// Notify of chain head events, but drop if too frequent
|
||||
case head := <-chainHeadCh:
|
||||
select {
|
||||
|
|
@ -268,6 +290,10 @@ func (s *Service) loop() {
|
|||
if err = s.reportPending(conn); err != nil {
|
||||
log.Warn("Transaction stats report failed", "err", err)
|
||||
}
|
||||
case forensicsReport := <-forensicsCh:
|
||||
if err = s.reportForensics(conn, forensicsReport); err != nil {
|
||||
log.Error("Forensics proof stats report failed", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Make sure the connection is closed
|
||||
|
|
@ -485,6 +511,13 @@ type blockStats struct {
|
|||
Uncles uncleStats `json:"uncles"`
|
||||
}
|
||||
|
||||
// blockStats is the information to report about individual blocks.
|
||||
type latestCommittedBlockStats struct {
|
||||
Number *big.Int `json:"number"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
Round uint64 `json:"round"`
|
||||
}
|
||||
|
||||
// txStats is the information to report about individual transactions.
|
||||
type txStats struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
|
|
@ -513,12 +546,37 @@ func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
|
|||
"id": s.node,
|
||||
"block": details,
|
||||
}
|
||||
|
||||
// Get the latest committed block information
|
||||
if (s.engine.(*XDPoS.XDPoS).EngineV2 != nil) && (s.engine.(*XDPoS.XDPoS).EngineV2.GetLatestCommittedBlockInfo() != nil) {
|
||||
latestCommittedBlockInfo := s.engine.(*XDPoS.XDPoS).EngineV2.GetLatestCommittedBlockInfo()
|
||||
stats["latestCommittedBlockInfo"] = &latestCommittedBlockStats{
|
||||
Number: latestCommittedBlockInfo.Number,
|
||||
Round: uint64(latestCommittedBlockInfo.Round),
|
||||
Hash: latestCommittedBlockInfo.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
report := map[string][]interface{}{
|
||||
"emit": {"block", stats},
|
||||
}
|
||||
return websocket.JSON.Send(conn, report)
|
||||
}
|
||||
|
||||
// reportForensics forward the forensics repors it to the stats server.
|
||||
func (s *Service) reportForensics(conn *websocket.Conn, forensicsProof *types.ForensicProof) error {
|
||||
log.Info("Sending Forensics report to ethstats", "ForensicsType", forensicsProof.ForensicsType)
|
||||
|
||||
stats := map[string]interface{}{
|
||||
"id": s.node,
|
||||
"forensicsProof": forensicsProof,
|
||||
}
|
||||
report := map[string][]interface{}{
|
||||
"emit": {"forensics", stats},
|
||||
}
|
||||
return websocket.JSON.Send(conn, report)
|
||||
}
|
||||
|
||||
// assembleBlockStats retrieves any required metadata to report a single block
|
||||
// and assembles the block stats. If block is nil, the current head is processed.
|
||||
func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
go.mod
3
go.mod
|
|
@ -11,7 +11,7 @@ require (
|
|||
github.com/btcsuite/winsvc v1.0.0 // indirect
|
||||
github.com/cespare/cp v1.1.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
|
||||
github.com/deckarep/golang-set v1.8.0
|
||||
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
|
||||
github.com/edsrzf/mmap-go v1.0.0
|
||||
github.com/elastic/gosigar v0.10.5
|
||||
|
|
@ -63,4 +63,5 @@ require (
|
|||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
|
||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
|
||||
gopkg.in/urfave/cli.v1 v1.20.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
)
|
||||
|
|
|
|||
5
go.sum
5
go.sum
|
|
@ -72,6 +72,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
|
||||
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M=
|
||||
|
|
@ -118,6 +120,7 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
|||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
|
|
@ -339,6 +342,7 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv
|
|||
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
||||
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw=
|
||||
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA=
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
|
||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA=
|
||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
|
||||
|
|
@ -353,5 +357,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
|||
|
|
@ -91,9 +91,9 @@ var Flags = []cli.Flag{
|
|||
//vmoduleFlag,
|
||||
//backtraceAtFlag,
|
||||
//debugFlag,
|
||||
//pprofFlag,
|
||||
//pprofAddrFlag,
|
||||
//pprofPortFlag,
|
||||
// pprofFlag,
|
||||
// pprofAddrFlag,
|
||||
// pprofPortFlag,
|
||||
//memprofilerateFlag,
|
||||
//blockprofilerateFlag,
|
||||
//cpuprofileFlag,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
|
||||
|
|
@ -497,12 +498,16 @@ func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args Sen
|
|||
// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
|
||||
// It offers only methods that operate on public data that is freely available to anyone.
|
||||
type PublicBlockChainAPI struct {
|
||||
b Backend
|
||||
b Backend
|
||||
chainReader consensus.ChainReader
|
||||
}
|
||||
|
||||
// NewPublicBlockChainAPI creates a new Ethereum blockchain API.
|
||||
func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI {
|
||||
return &PublicBlockChainAPI{b}
|
||||
func NewPublicBlockChainAPI(b Backend, chainReader consensus.ChainReader) *PublicBlockChainAPI {
|
||||
return &PublicBlockChainAPI{
|
||||
b,
|
||||
chainReader,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockNumber returns the block number of the chain head.
|
||||
|
|
@ -694,11 +699,7 @@ func (s *PublicBlockChainAPI) GetMasternodes(ctx context.Context, b *types.Block
|
|||
}
|
||||
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
||||
// Get block epoc latest.
|
||||
lastCheckpointNumber := prevBlockNumber - (prevBlockNumber % s.b.ChainConfig().XDPoS.Epoch)
|
||||
prevCheckpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(lastCheckpointNumber))
|
||||
if prevCheckpointBlock != nil {
|
||||
masternodes = engine.GetMasternodesFromCheckpointHeader(prevCheckpointBlock.Header(), curBlockNumber, s.b.ChainConfig().XDPoS.Epoch)
|
||||
}
|
||||
return engine.GetMasternodesByNumber(s.chainReader, prevBlockNumber), nil
|
||||
} else {
|
||||
log.Error("Undefined XDPoS consensus engine")
|
||||
}
|
||||
|
|
@ -791,7 +792,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd
|
|||
|
||||
// Second, Find candidates that have masternode status
|
||||
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
||||
masternodes = engine.GetMasternodesFromCheckpointHeader(header, block.Number().Uint64(), s.b.ChainConfig().XDPoS.Epoch)
|
||||
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
|
||||
if len(masternodes) == 0 {
|
||||
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
|
||||
result[fieldSuccess] = false
|
||||
|
|
@ -902,7 +903,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
|
|||
|
||||
// Second, Find candidates that have masternode status
|
||||
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
||||
masternodes = engine.GetMasternodesFromCheckpointHeader(header, block.Number().Uint64(), s.b.ChainConfig().XDPoS.Epoch)
|
||||
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
|
||||
if len(masternodes) == 0 {
|
||||
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
|
||||
result[fieldSuccess] = false
|
||||
|
|
@ -965,23 +966,28 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
|
|||
// GetPreviousCheckpointFromEpoch returns header of the previous checkpoint
|
||||
func (s *PublicBlockChainAPI) GetPreviousCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) {
|
||||
var checkpointNumber uint64
|
||||
epoch := s.b.ChainConfig().XDPoS.Epoch
|
||||
|
||||
if epochNum == rpc.LatestEpochNumber {
|
||||
blockNumer := s.b.CurrentBlock().Number().Uint64()
|
||||
diff := blockNumer % epoch
|
||||
// checkpoint number
|
||||
checkpointNumber = blockNumer - diff
|
||||
epochNum = rpc.EpochNumber(checkpointNumber / epoch)
|
||||
if diff > 0 {
|
||||
epochNum += 1
|
||||
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
||||
currentCheckpointNumber, epochNumber, err := engine.GetCurrentEpochSwitchBlock(s.chainReader, s.b.CurrentBlock().Number())
|
||||
if err != nil {
|
||||
log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get current epoch switch block information", "Block", s.b.CurrentBlock(), "Error", err)
|
||||
}
|
||||
} else if epochNum < 2 {
|
||||
checkpointNumber = 0
|
||||
if epochNum == rpc.LatestEpochNumber {
|
||||
checkpointNumber = currentCheckpointNumber
|
||||
epochNum = rpc.EpochNumber(epochNumber)
|
||||
} else if epochNum < 2 {
|
||||
checkpointNumber = 0
|
||||
} else {
|
||||
blockNumberBeforeCurrentEpochSwitch := currentCheckpointNumber - 1
|
||||
checkpointNumber, _, err = engine.GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(blockNumberBeforeCurrentEpochSwitch)))
|
||||
if err != nil {
|
||||
log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get last epoch switch block information", "Number", blockNumberBeforeCurrentEpochSwitch, "Error", err)
|
||||
}
|
||||
}
|
||||
return rpc.BlockNumber(checkpointNumber), epochNum
|
||||
} else {
|
||||
checkpointNumber = epoch * (uint64(epochNum) - 1)
|
||||
panic("[GetPreviousCheckpointFromEpoch] Error while trying to get XDPoS consensus engine")
|
||||
}
|
||||
return rpc.BlockNumber(checkpointNumber), epochNum
|
||||
}
|
||||
|
||||
// getCandidatesFromSmartContract returns all candidates with their capacities at the current time
|
||||
|
|
@ -1301,7 +1307,11 @@ func (s *PublicBlockChainAPI) findNearestSignedBlock(ctx context.Context, b *typ
|
|||
}
|
||||
|
||||
// Get block epoc latest
|
||||
checkpointNumber := signedBlockNumber - (signedBlockNumber % s.b.ChainConfig().XDPoS.Epoch)
|
||||
checkpointNumber, _, err := s.b.GetEngine().(*XDPoS.XDPoS).GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(signedBlockNumber)))
|
||||
if err != nil {
|
||||
log.Error("[findNearestSignedBlock] Error while trying to get current Epoch switch block", "Number", signedBlockNumber)
|
||||
}
|
||||
|
||||
checkpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber))
|
||||
|
||||
if checkpointBlock != nil {
|
||||
|
|
@ -1385,13 +1395,9 @@ func (s *PublicBlockChainAPI) getSigners(ctx context.Context, block *types.Block
|
|||
var err error
|
||||
var filterSigners []common.Address
|
||||
var signers []common.Address
|
||||
blockNumber := block.Number().Uint64()
|
||||
|
||||
// Get block epoc latest.
|
||||
checkpointNumber := blockNumber - (blockNumber % s.b.ChainConfig().XDPoS.Epoch)
|
||||
checkpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber))
|
||||
masternodes := engine.GetMasternodes(s.chainReader, block.Header())
|
||||
|
||||
masternodes := engine.GetMasternodesFromCheckpointHeader(checkpointBlock.Header(), blockNumber, s.b.ChainConfig().XDPoS.Epoch)
|
||||
signers, err = GetSignersFromBlocks(s.b, block.NumberU64(), block.Hash(), masternodes)
|
||||
if err != nil {
|
||||
log.Error("Fail to get signers from block signer SC.", "error", err)
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ type Backend interface {
|
|||
GetOrderNonce(address common.Hash) (uint64, error)
|
||||
}
|
||||
|
||||
func GetAPIs(apiBackend Backend) []rpc.API {
|
||||
func GetAPIs(apiBackend Backend, chainReader consensus.ChainReader) []rpc.API {
|
||||
nonceLock := new(AddrLocker)
|
||||
return []rpc.API{
|
||||
{
|
||||
|
|
@ -108,7 +108,7 @@ func GetAPIs(apiBackend Backend) []rpc.API {
|
|||
}, {
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: NewPublicBlockChainAPI(apiBackend),
|
||||
Service: NewPublicBlockChainAPI(apiBackend, chainReader),
|
||||
Public: true,
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ web3._extend({
|
|||
call: 'XDPoS_getSignersAtHash',
|
||||
params: 1
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'getLatestCommittedBlockInfo',
|
||||
call: 'XDPoS_getLatestCommittedBlockHeader'
|
||||
}),
|
||||
],
|
||||
properties: [
|
||||
new web3._extend.Property({
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ func (s *LightDummyAPI) Mining() bool {
|
|||
// APIs returns the collection of RPC services the ethereum package offers.
|
||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||
func (s *LightEthereum) APIs() []rpc.API {
|
||||
return append(ethapi.GetAPIs(s.ApiBackend), []rpc.API{
|
||||
return append(ethapi.GetAPIs(s.ApiBackend, nil), []rpc.API{
|
||||
{
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
|
|
|
|||
|
|
@ -21,12 +21,13 @@ import (
|
|||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
@ -205,7 +206,7 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protoco
|
|||
}
|
||||
|
||||
if lightSync {
|
||||
manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, nil, blockchain, removePeer)
|
||||
manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, nil, blockchain, removePeer, manager.handleProposedBlock)
|
||||
manager.peers.notify((*downloaderPeerNotify)(manager))
|
||||
manager.fetcher = newLightFetcher(manager)
|
||||
}
|
||||
|
|
@ -218,6 +219,11 @@ func (pm *ProtocolManager) removePeer(id string) {
|
|||
pm.peers.Unregister(id)
|
||||
}
|
||||
|
||||
// an empty handleProposedBlock function
|
||||
func (pm *ProtocolManager) handleProposedBlock(header *types.Header) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProtocolManager) Start(maxPeers int) {
|
||||
pm.maxPeers = maxPeers
|
||||
|
||||
|
|
|
|||
33
metrics/config.go
Normal file
33
metrics/config.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package metrics
|
||||
|
||||
// Config contains the configuration for the metric collection.
|
||||
type Config struct {
|
||||
Enabled bool `toml:",omitempty"`
|
||||
EnabledExpensive bool `toml:",omitempty"`
|
||||
HTTP string `toml:",omitempty"`
|
||||
Port int `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// DefaultConfig is the default config for metrics used in go-ethereum.
|
||||
var DefaultConfig = Config{
|
||||
Enabled: false,
|
||||
EnabledExpensive: false,
|
||||
HTTP: "127.0.0.1",
|
||||
Port: 6060,
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||
)
|
||||
|
||||
|
|
@ -50,6 +51,19 @@ func ExpHandler(r metrics.Registry) http.Handler {
|
|||
return http.HandlerFunc(e.expHandler)
|
||||
}
|
||||
|
||||
// Setup starts a dedicated metrics server at the given address.
|
||||
// This function enables metrics reporting separate from pprof.
|
||||
func Setup(address string) {
|
||||
m := http.NewServeMux()
|
||||
m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry))
|
||||
log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address))
|
||||
go func() {
|
||||
if err := http.ListenAndServe(address, m); err != nil {
|
||||
log.Error("Failure in running metrics server", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (exp *exp) getInt(name string) *expvar.Int {
|
||||
var v *expvar.Int
|
||||
exp.expvarLock.Lock()
|
||||
|
|
@ -111,7 +125,7 @@ func (exp *exp) publishMeter(name string, metric metrics.Meter) {
|
|||
exp.getInt(name + ".count").Set(m.Count())
|
||||
exp.getFloat(name + ".one-minute").Set(m.Rate1())
|
||||
exp.getFloat(name + ".five-minute").Set(m.Rate5())
|
||||
exp.getFloat(name + ".fifteen-minute").Set((m.Rate15()))
|
||||
exp.getFloat(name + ".fifteen-minute").Set(m.Rate15())
|
||||
exp.getFloat(name + ".mean").Set(m.RateMean())
|
||||
}
|
||||
|
||||
|
|
@ -134,21 +148,34 @@ func (exp *exp) publishTimer(name string, metric metrics.Timer) {
|
|||
exp.getFloat(name + ".mean-rate").Set(t.RateMean())
|
||||
}
|
||||
|
||||
func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer) {
|
||||
t := metric.Snapshot()
|
||||
ps := t.Percentiles([]float64{50, 75, 95, 99})
|
||||
exp.getInt(name + ".count").Set(int64(len(t.Values())))
|
||||
exp.getFloat(name + ".mean").Set(t.Mean())
|
||||
exp.getInt(name + ".50-percentile").Set(ps[0])
|
||||
exp.getInt(name + ".75-percentile").Set(ps[1])
|
||||
exp.getInt(name + ".95-percentile").Set(ps[2])
|
||||
exp.getInt(name + ".99-percentile").Set(ps[3])
|
||||
}
|
||||
|
||||
func (exp *exp) syncToExpvar() {
|
||||
exp.registry.Each(func(name string, i interface{}) {
|
||||
switch i.(type) {
|
||||
switch i := i.(type) {
|
||||
case metrics.Counter:
|
||||
exp.publishCounter(name, i.(metrics.Counter))
|
||||
exp.publishCounter(name, i)
|
||||
case metrics.Gauge:
|
||||
exp.publishGauge(name, i.(metrics.Gauge))
|
||||
exp.publishGauge(name, i)
|
||||
case metrics.GaugeFloat64:
|
||||
exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64))
|
||||
exp.publishGaugeFloat64(name, i)
|
||||
case metrics.Histogram:
|
||||
exp.publishHistogram(name, i.(metrics.Histogram))
|
||||
exp.publishHistogram(name, i)
|
||||
case metrics.Meter:
|
||||
exp.publishMeter(name, i.(metrics.Meter))
|
||||
exp.publishMeter(name, i)
|
||||
case metrics.Timer:
|
||||
exp.publishTimer(name, i.(metrics.Timer))
|
||||
exp.publishTimer(name, i)
|
||||
case metrics.ResettingTimer:
|
||||
exp.publishResettingTimer(name, i)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@ package miner
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
|
|
|
|||
132
miner/worker.go
132
miner/worker.go
|
|
@ -34,7 +34,6 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
||||
"github.com/XinFinOrg/XDPoSChain/contracts"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
@ -59,10 +58,6 @@ const (
|
|||
chainHeadChanSize = 10
|
||||
// chainSideChanSize is the size of channel listening to ChainSideEvent.
|
||||
chainSideChanSize = 10
|
||||
// timeout waiting for M1
|
||||
waitPeriod = 10
|
||||
// timeout for checkpoint.
|
||||
waitPeriodCheckpoint = 20
|
||||
|
||||
txMatchGasLimit = 40000000
|
||||
)
|
||||
|
|
@ -270,7 +265,14 @@ func (self *worker) update() {
|
|||
}
|
||||
defer self.chainHeadSub.Unsubscribe()
|
||||
defer self.chainSideSub.Unsubscribe()
|
||||
timeout := time.NewTimer(waitPeriod * time.Second)
|
||||
|
||||
// timeout waiting for v1 inital value
|
||||
// TODO: Read value from config after we decide where is the config
|
||||
waitPeriod := 10
|
||||
WaitPeriodCh := self.engine.(*XDPoS.XDPoS).WaitPeriodCh
|
||||
defer close(WaitPeriodCh)
|
||||
|
||||
timeout := time.NewTimer(time.Duration(waitPeriod) * time.Second)
|
||||
c := make(chan struct{})
|
||||
finish := make(chan struct{})
|
||||
defer close(finish)
|
||||
|
|
@ -289,24 +291,31 @@ func (self *worker) update() {
|
|||
for {
|
||||
// A real event arrived, process interesting content
|
||||
select {
|
||||
case v := <-WaitPeriodCh:
|
||||
log.Info("[worker] update mine period", "period", v)
|
||||
waitPeriod = v
|
||||
timeout.Reset(time.Duration(waitPeriod) * time.Second)
|
||||
|
||||
case <-c:
|
||||
if atomic.LoadInt32(&self.mining) == 1 {
|
||||
self.commitNewWork()
|
||||
}
|
||||
timeout.Reset(waitPeriod * time.Second)
|
||||
// Handle ChainHeadEvent
|
||||
timeout.Reset(time.Duration(waitPeriod) * time.Second)
|
||||
|
||||
// Handle ChainHeadEvent
|
||||
case <-self.chainHeadCh:
|
||||
self.commitNewWork()
|
||||
timeout.Reset(waitPeriod * time.Second)
|
||||
timeout.Reset(time.Duration(waitPeriod) * time.Second)
|
||||
|
||||
// Handle ChainSideEvent
|
||||
// Handle ChainSideEvent
|
||||
case ev := <-self.chainSideCh:
|
||||
if self.config.XDPoS == nil {
|
||||
self.uncleMu.Lock()
|
||||
self.possibleUncles[ev.Block.Hash()] = ev.Block
|
||||
self.uncleMu.Unlock()
|
||||
}
|
||||
// Handle TxPreEvent
|
||||
|
||||
// Handle TxPreEvent
|
||||
case ev := <-self.txCh:
|
||||
// Apply transaction to the pending state if we're not mining
|
||||
if atomic.LoadInt32(&self.mining) == 0 {
|
||||
|
|
@ -323,8 +332,10 @@ func (self *worker) update() {
|
|||
self.commitNewWork()
|
||||
}
|
||||
}
|
||||
|
||||
case <-self.chainHeadSub.Err():
|
||||
return
|
||||
|
||||
case <-self.chainSideSub.Err():
|
||||
return
|
||||
}
|
||||
|
|
@ -381,7 +392,11 @@ func (self *worker) wait() {
|
|||
}
|
||||
if work.config.XDPoS != nil {
|
||||
// epoch block
|
||||
if (block.NumberU64() % work.config.XDPoS.Epoch) == 0 {
|
||||
isEpochSwitchBlock, _, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(block.Header())
|
||||
if err != nil {
|
||||
log.Error("[wait] fail to check if block is epoch switch block when worker waiting", "BlockNum", block.Number(), "Hash", block.Hash())
|
||||
}
|
||||
if isEpochSwitchBlock {
|
||||
core.CheckpointCh <- 1
|
||||
}
|
||||
}
|
||||
|
|
@ -397,8 +412,12 @@ func (self *worker) wait() {
|
|||
|
||||
if self.config.XDPoS != nil {
|
||||
c := self.engine.(*XDPoS.XDPoS)
|
||||
err = c.HandleProposedBlock(self.chain, block.Header())
|
||||
if err != nil {
|
||||
log.Warn("[wait] Unable to handle new proposed block", "err", err, "number", block.Number(), "hash", block.Hash())
|
||||
}
|
||||
|
||||
authorized := c.IsAuthorisedAddress(block.Header(), self.chain, self.coinbase)
|
||||
authorized := c.IsAuthorisedAddress(self.chain, block.Header(), self.coinbase)
|
||||
if !authorized {
|
||||
valid := false
|
||||
masternodes := c.GetMasternodes(self.chain, block.Header())
|
||||
|
|
@ -416,7 +435,7 @@ func (self *worker) wait() {
|
|||
// Send tx sign to smart contract blockSigners.
|
||||
if block.NumberU64()%common.MergeSignRange == 0 || !self.config.IsTIP2019(block.Number()) {
|
||||
if err := contracts.CreateTransactionSign(self.config, self.eth.TxPool(), self.eth.AccountManager(), block, self.chainDb, self.coinbase); err != nil {
|
||||
log.Error("Fail to create tx sign for signer", "error", "err")
|
||||
log.Error("Fail to create tx sign for signer", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -508,7 +527,15 @@ func (self *worker) commitNewWork() {
|
|||
defer self.currentMu.Unlock()
|
||||
|
||||
tstart := time.Now()
|
||||
parent := self.chain.CurrentBlock()
|
||||
|
||||
c := self.engine.(*XDPoS.XDPoS)
|
||||
var parent *types.Block
|
||||
if c != nil {
|
||||
parent = c.FindParentBlockToAssign(self.chain, self.chain.CurrentBlock())
|
||||
} else {
|
||||
parent = self.chain.CurrentBlock()
|
||||
}
|
||||
|
||||
var signers map[common.Address]struct{}
|
||||
if parent.Hash().Hex() == self.lastParentBlockCommit {
|
||||
return
|
||||
|
|
@ -520,39 +547,15 @@ func (self *worker) commitNewWork() {
|
|||
// Only try to commit new work if we are mining
|
||||
if atomic.LoadInt32(&self.mining) == 1 {
|
||||
// check if we are right after parent's coinbase in the list
|
||||
// only go with XDPoS
|
||||
if self.config.XDPoS != nil {
|
||||
// get masternodes set from latest checkpoint
|
||||
c := self.engine.(*XDPoS.XDPoS)
|
||||
len, preIndex, curIndex, ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
|
||||
ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
|
||||
if err != nil {
|
||||
log.Warn("Failed when trying to commit new work", "err", err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
log.Info("Not my turn to commit block. Waiting...")
|
||||
// in case some nodes are down
|
||||
if preIndex == -1 {
|
||||
// first block
|
||||
return
|
||||
}
|
||||
if curIndex == -1 {
|
||||
// you're not allowed to create this block
|
||||
return
|
||||
}
|
||||
h := utils.Hop(len, preIndex, curIndex)
|
||||
gap := waitPeriod * int64(h)
|
||||
// Check nearest checkpoint block in hop range.
|
||||
nearest := self.config.XDPoS.Epoch - (parent.Header().Number.Uint64() % self.config.XDPoS.Epoch)
|
||||
if uint64(h) >= nearest {
|
||||
gap = waitPeriodCheckpoint * int64(h)
|
||||
}
|
||||
log.Info("Distance from the parent block", "seconds", gap, "hops", h)
|
||||
waitedTime := time.Now().Unix() - parent.Header().Time.Int64()
|
||||
if gap > waitedTime {
|
||||
return
|
||||
}
|
||||
log.Info("Wait enough. It's my turn", "waited seconds", waitedTime)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -581,6 +584,10 @@ func (self *worker) commitNewWork() {
|
|||
}
|
||||
|
||||
if err := self.engine.Prepare(self.chain, header); err != nil {
|
||||
if err == consensus.ErrNotReadyToPropose {
|
||||
log.Info("Waiting...", "err", err)
|
||||
return
|
||||
}
|
||||
log.Error("Failed to prepare header for new block", "err", err)
|
||||
return
|
||||
}
|
||||
|
|
@ -626,13 +633,19 @@ func (self *worker) commitNewWork() {
|
|||
lendingFinalizedTradeTransaction *types.Transaction
|
||||
)
|
||||
feeCapacity := state.GetTRC21FeeCapacityFromStateWithCache(parent.Root(), work.state)
|
||||
if self.config.XDPoS != nil && header.Number.Uint64()%self.config.XDPoS.Epoch != 0 {
|
||||
pending, err := self.eth.TxPool().Pending()
|
||||
if self.config.XDPoS != nil {
|
||||
isEpochSwitchBlock, _, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(header)
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch pending transactions", "err", err)
|
||||
return
|
||||
log.Error("[commitNewWork] fail to check if block is epoch switch block when fetching pending transactions", "BlockNum", header.Number, "Hash", header.Hash())
|
||||
}
|
||||
if !isEpochSwitchBlock {
|
||||
pending, err := self.eth.TxPool().Pending()
|
||||
if err != nil {
|
||||
log.Error("Failed to fetch pending transactions", "err", err)
|
||||
return
|
||||
}
|
||||
txs, specialTxs = types.NewTransactionsByPriceAndNonce(self.current.signer, pending, signers, feeCapacity)
|
||||
}
|
||||
txs, specialTxs = types.NewTransactionsByPriceAndNonce(self.current.signer, pending, signers, feeCapacity)
|
||||
}
|
||||
if atomic.LoadInt32(&self.mining) == 1 {
|
||||
wallet, err := self.eth.AccountManager().Find(accounts.Account{Address: self.coinbase})
|
||||
|
|
@ -644,16 +657,20 @@ func (self *worker) commitNewWork() {
|
|||
XDCX := self.eth.GetXDCX()
|
||||
XDCXLending := self.eth.GetXDCXLending()
|
||||
if XDCX != nil && header.Number.Uint64() > self.config.XDPoS.Epoch {
|
||||
if header.Number.Uint64()%self.config.XDPoS.Epoch == 0 {
|
||||
err := XDCX.UpdateMediumPriceBeforeEpoch(header.Number.Uint64()/self.config.XDPoS.Epoch, work.tradingState, work.state)
|
||||
isEpochSwitchBlock, epochNumber, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(header)
|
||||
if err != nil {
|
||||
log.Error("[commitNewWork] fail to check if block is epoch switch block when performing XDCX and XDCXLending operations", "BlockNum", header.Number, "Hash", header.Hash())
|
||||
}
|
||||
|
||||
if isEpochSwitchBlock {
|
||||
err := XDCX.UpdateMediumPriceBeforeEpoch(epochNumber, work.tradingState, work.state)
|
||||
if err != nil {
|
||||
log.Error("Fail when update medium price last epoch", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// won't grasp tx at checkpoint
|
||||
//https://github.com/XinFinOrg/XDPoSChain-v1/pull/416
|
||||
if header.Number.Uint64()%self.config.XDPoS.Epoch != 0 {
|
||||
} else {
|
||||
// won't grasp tx at checkpoint
|
||||
//https://github.com/XinFinOrg/XDPoSChain-v1/pull/416
|
||||
log.Debug("Start processing order pending")
|
||||
tradingOrderPending, _ := self.eth.OrderPool().Pending()
|
||||
log.Debug("Start processing order pending", "len", len(tradingOrderPending))
|
||||
|
|
@ -671,6 +688,7 @@ func (self *worker) commitNewWork() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(tradingTxMatches) > 0 {
|
||||
txMatchBatch := &tradingstate.TxMatchBatch{
|
||||
Data: tradingTxMatches,
|
||||
|
|
@ -1053,10 +1071,16 @@ func (env *Work) commitTransactions(mux *event.TypeMux, balanceFee map[common.Ad
|
|||
}
|
||||
go func(logs []*types.Log, tcount int) {
|
||||
if len(logs) > 0 {
|
||||
mux.Post(core.PendingLogsEvent{Logs: logs})
|
||||
err := mux.Post(core.PendingLogsEvent{Logs: logs})
|
||||
if err != nil {
|
||||
log.Warn("[commitTransactions] Error when sending PendingLogsEvent", "LogLength", len(logs))
|
||||
}
|
||||
}
|
||||
if tcount > 0 {
|
||||
mux.Post(core.PendingStateEvent{})
|
||||
err := mux.Post(core.PendingStateEvent{})
|
||||
if err != nil {
|
||||
log.Warn("[commitTransactions] Error when sending PendingStateEvent", "tcount", tcount)
|
||||
}
|
||||
}
|
||||
}(cpy, env.tcount)
|
||||
}
|
||||
|
|
|
|||
122
params/config.go
122
params/config.go
|
|
@ -29,15 +29,42 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
XDCMainnetGenesisHash = common.HexToHash("9326145f8a2c8c00bbe13afc7d7f3d9c868b5ef39d89f2f4e9390e9720298624") // XDC Mainnet genesis hash to enforce below configs on
|
||||
XDCMainnetGenesisHash = common.HexToHash("4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1") // XDC Mainnet genesis hash to enforce below configs on
|
||||
MainnetGenesisHash = common.HexToHash("8d13370621558f4ed0da587934473c0404729f28b0ff1d50e5fdd840457a2f17") // Mainnet genesis hash to enforce below configs on
|
||||
TestnetGenesisHash = common.HexToHash("dffc8ae3b45965404b4fd73ce7f0e13e822ac0fc23ce7e95b42bc5f1e57023a5") // Testnet genesis hash to enforce below configs on
|
||||
TestnetGenesisHash = common.HexToHash("bdea512b4f12ff1135ec92c00dc047ffb93890c2ea1aa0eefe9b013d80640075") // Testnet genesis hash to enforce below configs on
|
||||
DevnetGenesisHash = common.HexToHash("ab6fd3cb7d1a489e03250c7d14c2d6d819a6a528d6380b31e8410951964ef423") // Devnet genesis hash to enforce below configs on
|
||||
)
|
||||
|
||||
var (
|
||||
XDPoSV2Config = &V2{
|
||||
SwitchBlock: big.NewInt(9999999999),
|
||||
CertThreshold: common.MaxMasternodesV2*2/3 + 1,
|
||||
TimeoutSyncThreshold: 3,
|
||||
TimeoutPeriod: 60,
|
||||
WaitPeriod: 10,
|
||||
MinePeriod: 10,
|
||||
}
|
||||
TestXDPoSV2Config = &V2{
|
||||
SwitchBlock: big.NewInt(900),
|
||||
CertThreshold: 3,
|
||||
TimeoutSyncThreshold: 2,
|
||||
TimeoutPeriod: 10,
|
||||
WaitPeriod: 1,
|
||||
MinePeriod: 2,
|
||||
SkipV2Validation: true,
|
||||
}
|
||||
DevnetXDPoSV2Config = &V2{
|
||||
SwitchBlock: big.NewInt(7218000),
|
||||
CertThreshold: 6,
|
||||
TimeoutSyncThreshold: 5,
|
||||
TimeoutPeriod: 10,
|
||||
WaitPeriod: 2,
|
||||
MinePeriod: 2,
|
||||
}
|
||||
|
||||
// XDPoSChain mainnet config
|
||||
XDCMainnetChainConfig = &ChainConfig{
|
||||
ChainId: big.NewInt(88),
|
||||
ChainId: big.NewInt(50),
|
||||
HomesteadBlock: big.NewInt(1),
|
||||
EIP150Block: big.NewInt(2),
|
||||
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
|
|
@ -47,10 +74,11 @@ var (
|
|||
XDPoS: &XDPoSConfig{
|
||||
Period: 2,
|
||||
Epoch: 900,
|
||||
Reward: 250,
|
||||
Reward: 5000,
|
||||
RewardCheckpoint: 900,
|
||||
Gap: 5,
|
||||
FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068"),
|
||||
Gap: 450,
|
||||
FoudationWalletAddr: common.HexToAddress("xdc92a289fe95a85c53b8d0d113cbaef0c1ec98ac65"),
|
||||
V2: XDPoSV2Config,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -71,17 +99,45 @@ var (
|
|||
|
||||
// TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network.
|
||||
TestnetChainConfig = &ChainConfig{
|
||||
ChainId: big.NewInt(3),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
ChainId: big.NewInt(51),
|
||||
HomesteadBlock: big.NewInt(1),
|
||||
DAOForkBlock: nil,
|
||||
DAOForkSupport: true,
|
||||
EIP150Block: big.NewInt(0),
|
||||
EIP150Hash: common.HexToHash("0x62e0fde86e34c263e250fbcd5ca4598ba8ca10a1d166c8526bb127e10b313311"),
|
||||
EIP155Block: big.NewInt(10),
|
||||
EIP158Block: big.NewInt(10),
|
||||
ByzantiumBlock: big.NewInt(1700000),
|
||||
DAOForkSupport: false,
|
||||
EIP150Block: big.NewInt(2),
|
||||
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
EIP155Block: big.NewInt(3),
|
||||
EIP158Block: big.NewInt(3),
|
||||
ByzantiumBlock: big.NewInt(4),
|
||||
ConstantinopleBlock: nil,
|
||||
Ethash: new(EthashConfig),
|
||||
XDPoS: &XDPoSConfig{
|
||||
Period: 2,
|
||||
Epoch: 900,
|
||||
Reward: 5000,
|
||||
RewardCheckpoint: 900,
|
||||
Gap: 450,
|
||||
FoudationWalletAddr: common.HexToAddress("xdc746249c61f5832c5eed53172776b460491bdcd5c"),
|
||||
V2: TestXDPoSV2Config,
|
||||
},
|
||||
}
|
||||
|
||||
// TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network.
|
||||
DevnetChainConfig = &ChainConfig{
|
||||
ChainId: big.NewInt(551),
|
||||
HomesteadBlock: big.NewInt(1),
|
||||
EIP150Block: big.NewInt(2),
|
||||
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
EIP155Block: big.NewInt(3),
|
||||
EIP158Block: big.NewInt(3),
|
||||
ByzantiumBlock: big.NewInt(4),
|
||||
XDPoS: &XDPoSConfig{
|
||||
Period: 2,
|
||||
Epoch: 900,
|
||||
Reward: 5000,
|
||||
RewardCheckpoint: 900,
|
||||
Gap: 450,
|
||||
FoudationWalletAddr: common.HexToAddress("0x746249c61f5832c5eed53172776b460491bdcd5c"),
|
||||
V2: DevnetXDPoSV2Config,
|
||||
},
|
||||
}
|
||||
|
||||
// RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network.
|
||||
|
|
@ -99,6 +155,7 @@ var (
|
|||
XDPoS: &XDPoSConfig{
|
||||
Period: 15,
|
||||
Epoch: 30000,
|
||||
V2: XDPoSV2Config,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -117,11 +174,8 @@ var (
|
|||
AllXDPoSProtocolChanges = &ChainConfig{big.NewInt(89), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &XDPoSConfig{Period: 0, Epoch: 30000}}
|
||||
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
|
||||
|
||||
TestXDPoSChanConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &XDPoSConfig{Period: 2, Epoch: 900, Reward: 250, RewardCheckpoint: 900, Gap: 890, FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068")}}
|
||||
// XDPoS config in use for v1 engine only
|
||||
TestXDPoSMockChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true}}
|
||||
// XDPoS config with v2 engine after block 10
|
||||
TestXDPoSMockChainConfigWithV2Engine = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true, V2ConsensusBlockNumber: big.NewInt(10)}}
|
||||
// XDPoS config with v2 engine after block 901
|
||||
TestXDPoSMockChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipV1Validation: true, V2: TestXDPoSV2Config, FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068"), Reward: 250}}
|
||||
|
||||
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil}
|
||||
TestRules = TestChainConfig.Rules(new(big.Int))
|
||||
|
|
@ -177,14 +231,24 @@ func (c *CliqueConfig) String() string {
|
|||
|
||||
// XDPoSConfig is the consensus engine configs for delegated-proof-of-stake based sealing.
|
||||
type XDPoSConfig struct {
|
||||
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
||||
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
|
||||
Reward uint64 `json:"reward"` // Block reward - unit Ether
|
||||
RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards.
|
||||
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
|
||||
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
|
||||
SkipValidation bool //Skip Block Validation for testing purpose
|
||||
V2ConsensusBlockNumber *big.Int
|
||||
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
||||
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
|
||||
Reward uint64 `json:"reward"` // Block reward - unit Ether
|
||||
RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards.
|
||||
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
|
||||
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
|
||||
SkipV1Validation bool //Skip Block Validation for testing purpose, V1 consensus only
|
||||
V2 *V2 `json:"v2"`
|
||||
}
|
||||
|
||||
type V2 struct {
|
||||
WaitPeriod int `json:"waitPeriod"` // Miner wait period to check mine event
|
||||
MinePeriod int `json:"minePeriod"` // Miner mine period to mine a block
|
||||
SwitchBlock *big.Int `json:"switchBlock"` // v1 to v2 switch block number
|
||||
TimeoutSyncThreshold int `json:"timeoutSyncThreshold"` // send syncInfo after number of timeout
|
||||
TimeoutPeriod int `json:"timeoutPeriod"` // Duration in ms
|
||||
CertThreshold int `json:"certificateThreshold"` // Necessary number of messages from master nodes to form a certificate
|
||||
SkipV2Validation bool //Skip Block Validation for testing purpose, V2 consensus only
|
||||
}
|
||||
|
||||
// String implements the stringer interface, returning the consensus engine details.
|
||||
|
|
@ -197,7 +261,7 @@ ConsensusVersion will return the consensus version to use for the provided block
|
|||
TODO: It's a dummy value for now until the 2.0 consensus engine is fully implemented.
|
||||
*/
|
||||
func (c *XDPoSConfig) BlockConsensusVersion(num *big.Int) string {
|
||||
if c.V2ConsensusBlockNumber != nil && num.Cmp(c.V2ConsensusBlockNumber) > 0 {
|
||||
if c.V2 != nil && c.V2.SwitchBlock != nil && num.Cmp(c.V2.SwitchBlock) > 0 {
|
||||
return ConsensusEngineVersion2
|
||||
}
|
||||
return ConsensusEngineVersion1
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
VersionMajor = 1 // Major version component of the current release
|
||||
VersionMinor = 4 // Minor version component of the current release
|
||||
VersionPatch = 4 // Patch version component of the current release
|
||||
VersionMeta = "stable" // Version metadata to append to the version string
|
||||
VersionMajor = 2 // Major version component of the current release
|
||||
VersionMinor = 0 // Minor version component of the current release
|
||||
VersionPatch = 0 // Patch version component of the current release
|
||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
// Version holds the textual version string.
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
package consensus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
|
||||
blockchain, _, currentBlock := PrepareXDCTestBlockChain(t, 10, params.TestXDPoSMockChainConfigWithV2Engine)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
addressFromAdaptor, errorAdaptor := adaptor.Author(currentBlock.Header())
|
||||
if errorAdaptor != nil {
|
||||
t.Fatalf("Failed while trying to get Author from adaptor")
|
||||
}
|
||||
addressFromV1Engine, errV1 := adaptor.EngineV1.Author(currentBlock.Header())
|
||||
if errV1 != nil {
|
||||
t.Fatalf("Failed while trying to get Author from engine v1")
|
||||
}
|
||||
// Make sure the value is exactly the same as from V1 engine
|
||||
assert.Equal(t, addressFromAdaptor, addressFromV1Engine)
|
||||
|
||||
// Insert one more block to make it above 10, which means now we are on v2 of consensus engine
|
||||
// Insert block 11
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 11)
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block11, err := insertBlock(blockchain, 11, blockCoinBase, currentBlock, merkleRoot, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
addressFromAdaptor, errorAdaptor = adaptor.Author(block11.Header())
|
||||
if errorAdaptor != nil {
|
||||
t.Fatalf("Failed while trying to get Author from adaptor")
|
||||
}
|
||||
addressFromV2Engine, errV2 := adaptor.EngineV2.Author(block11.Header())
|
||||
if errV2 != nil {
|
||||
t.Fatalf("Failed while trying to get Author from engine v2")
|
||||
}
|
||||
// Make sure the value is exactly the same as from V2 engine
|
||||
assert.Equal(t, addressFromAdaptor, addressFromV2Engine)
|
||||
}
|
||||
Loading…
Reference in a new issue