mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 18:30:45 +00:00
commit
22479f11ff
99 changed files with 10111 additions and 32486 deletions
50
README.md
50
README.md
|
|
@ -1 +1,51 @@
|
||||||
# XDPoSChain
|
# 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"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -29,7 +31,9 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain"
|
"github.com/XinFinOrg/XDPoSChain"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||||
|
|
@ -69,6 +73,28 @@ type SimulatedBackend struct {
|
||||||
config *params.ChainConfig
|
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.
|
// XDC simulated backend for testing purpose.
|
||||||
func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
|
func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
|
||||||
// database := ethdb.NewMemDatabase()
|
// database := ethdb.NewMemDatabase()
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,15 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
|
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/console"
|
"github.com/XinFinOrg/XDPoSChain/console"
|
||||||
|
|
@ -207,6 +209,12 @@ func importChain(ctx *cli.Context) error {
|
||||||
if len(ctx.Args()) < 1 {
|
if len(ctx.Args()) < 1 {
|
||||||
utils.Fatalf("This command requires an argument.")
|
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)
|
stack, _ := makeFullNode(ctx)
|
||||||
chain, chainDb := utils.MakeChain(ctx, stack)
|
chain, chainDb := utils.MakeChain(ctx, stack)
|
||||||
defer chainDb.Close()
|
defer chainDb.Close()
|
||||||
|
|
@ -368,7 +376,7 @@ func copyDb(ctx *cli.Context) error {
|
||||||
chain, chainDb := utils.MakeChain(ctx, stack)
|
chain, chainDb := utils.MakeChain(ctx, stack)
|
||||||
|
|
||||||
syncmode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode)
|
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
|
// Create a source peer to satisfy downloader requests from
|
||||||
db, err := rawdb.NewLevelDBDatabase(ctx.Args().First(), ctx.GlobalInt(utils.CacheFlag.Name), 256, "")
|
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("goarch", func() string { return runtime.GOARCH })
|
||||||
XDC.SetTemplateFunc("gover", runtime.Version)
|
XDC.SetTemplateFunc("gover", runtime.Version)
|
||||||
XDC.SetTemplateFunc("XDCver", func() string { return params.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 })
|
XDC.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||||
|
|
||||||
// Verify the actual welcome message to the required template
|
// 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("gover", runtime.Version)
|
||||||
attach.SetTemplateFunc("XDCver", func() string { return params.Version })
|
attach.SetTemplateFunc("XDCver", func() string { return params.Version })
|
||||||
attach.SetTemplateFunc("etherbase", func() string { return XDC.Etherbase })
|
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("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||||
attach.SetTemplateFunc("datadir", func() string { return XDC.Datadir })
|
attach.SetTemplateFunc("datadir", func() string { return XDC.Datadir })
|
||||||
attach.SetTemplateFunc("apis", func() string { return apis })
|
attach.SetTemplateFunc("apis", func() string { return apis })
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,8 @@ var (
|
||||||
utils.RPCVirtualHostsFlag,
|
utils.RPCVirtualHostsFlag,
|
||||||
utils.EthStatsURLFlag,
|
utils.EthStatsURLFlag,
|
||||||
utils.MetricsEnabledFlag,
|
utils.MetricsEnabledFlag,
|
||||||
|
utils.MetricsHTTPFlag,
|
||||||
|
utils.MetricsPortFlag,
|
||||||
//utils.FakePoWFlag,
|
//utils.FakePoWFlag,
|
||||||
//utils.NoCompactionFlag,
|
//utils.NoCompactionFlag,
|
||||||
//utils.GpoBlocksFlag,
|
//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" {
|
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
||||||
utils.Fatalf("Light clients do not support staking")
|
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
|
var ethereum *eth.Ethereum
|
||||||
if err := stack.Service(ðereum); err != nil {
|
if err := stack.Service(ðereum); err != nil {
|
||||||
utils.Fatalf("Ethereum service not running: %v", err)
|
utils.Fatalf("Ethereum service not running: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
"github.com/XinFinOrg/XDPoSChain/metrics"
|
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/metrics/exp"
|
||||||
"github.com/XinFinOrg/XDPoSChain/node"
|
"github.com/XinFinOrg/XDPoSChain/node"
|
||||||
"github.com/XinFinOrg/XDPoSChain/p2p"
|
"github.com/XinFinOrg/XDPoSChain/p2p"
|
||||||
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
|
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
|
||||||
|
|
@ -355,6 +356,20 @@ var (
|
||||||
Name: "ethstats",
|
Name: "ethstats",
|
||||||
Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)",
|
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{
|
MetricsEnabledFlag = cli.BoolFlag{
|
||||||
Name: metrics.MetricsEnabledFlag,
|
Name: metrics.MetricsEnabledFlag,
|
||||||
Usage: "Enable metrics collection and reporting",
|
Usage: "Enable metrics collection and reporting",
|
||||||
|
|
@ -1330,3 +1345,15 @@ func WalkMatch(root, pattern string) ([]string, error) {
|
||||||
}
|
}
|
||||||
return matches, nil
|
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
|
MaxMasternodes = 18
|
||||||
MaxMasternodesV2 = 108
|
MaxMasternodesV2 = 108
|
||||||
LimitPenaltyEpoch = 4
|
LimitPenaltyEpoch = 4
|
||||||
|
LimitPenaltyEpochV2 = 0
|
||||||
BlocksPerYearTest = uint64(200000)
|
BlocksPerYearTest = uint64(200000)
|
||||||
BlocksPerYear = uint64(15768000)
|
BlocksPerYear = uint64(15768000)
|
||||||
LimitThresholdNonceInQueue = 10
|
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.
|
// Extract validators from byte array.
|
||||||
func RemoveItemFromArray(array []Address, items []Address) []Address {
|
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 {
|
if len(items) == 0 {
|
||||||
return array
|
return newArray
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
for i := len(array) - 1; i >= 0; i-- {
|
for i := len(newArray) - 1; i >= 0; i-- {
|
||||||
if array[i] == item {
|
if newArray[i] == item {
|
||||||
array = append(array[:i], array[i+1:]...)
|
newArray = append(newArray[:i], newArray[i+1:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array
|
return newArray
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract validators from byte array.
|
// Extract validators from byte array.
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,12 @@ func BenchmarkAddressHex(b *testing.B) {
|
||||||
func TestRemoveItemInArray(t *testing.T) {
|
func TestRemoveItemInArray(t *testing.T) {
|
||||||
array := []Address{HexToAddress("0x0000003"), HexToAddress("0x0000001"), HexToAddress("0x0000002"), HexToAddress("0x0000003")}
|
array := []Address{HexToAddress("0x0000003"), HexToAddress("0x0000001"), HexToAddress("0x0000002"), HexToAddress("0x0000003")}
|
||||||
remove := []Address{HexToAddress("0x0000002"), HexToAddress("0x0000004"), HexToAddress("0x0000003")}
|
remove := []Address{HexToAddress("0x0000002"), HexToAddress("0x0000004"), HexToAddress("0x0000003")}
|
||||||
array = RemoveItemFromArray(array, remove)
|
newArray := RemoveItemFromArray(array, remove)
|
||||||
if len(array) != 1 {
|
|
||||||
|
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")
|
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_v1"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/event"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/clique"
|
"github.com/XinFinOrg/XDPoSChain/consensus/clique"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||||
|
|
@ -35,8 +36,13 @@ import (
|
||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SigHash(header *types.Header) (hash common.Hash) {
|
func (x *XDPoS) SigHash(header *types.Header) (hash common.Hash) {
|
||||||
return utils.SigHash(header)
|
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
|
// 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
|
// Transaction cache, only make sense for adaptor level
|
||||||
signingTxsCache *lru.Cache
|
signingTxsCache *lru.Cache
|
||||||
|
|
||||||
|
// Share Channel
|
||||||
|
WaitPeriodCh chan int // Miner wait Period Channel
|
||||||
|
|
||||||
// Trading and lending service
|
// Trading and lending service
|
||||||
GetXDCXService func() utils.TradingService
|
GetXDCXService func() utils.TradingService
|
||||||
GetLendingService func() utils.LendingService
|
GetLendingService func() utils.LendingService
|
||||||
|
|
||||||
// The exact consensus engine with different versions
|
// The exact consensus engine with different versions
|
||||||
EngineV1 engine_v1.XDPoS_v1
|
EngineV1 *engine_v1.XDPoS_v1
|
||||||
EngineV2 engine_v2.XDPoS_v2
|
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
|
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
|
||||||
// signers set to the ones provided by the user.
|
// signers set to the ones provided by the user.
|
||||||
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS {
|
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS {
|
||||||
|
log.Info("[New] initial conensus engines")
|
||||||
// Set any missing consensus parameters to their defaults
|
// Set any missing consensus parameters to their defaults
|
||||||
conf := *config
|
if config.Epoch == 0 {
|
||||||
if conf.Epoch == 0 {
|
config.Epoch = utils.EpochLength
|
||||||
conf.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
|
// Allocate the snapshot caches and create the engine
|
||||||
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
||||||
|
|
||||||
return &XDPoS{
|
return &XDPoS{
|
||||||
config: &conf,
|
config: config,
|
||||||
db: db,
|
db: db,
|
||||||
|
|
||||||
|
WaitPeriodCh: waitPeriodCh,
|
||||||
|
|
||||||
signingTxsCache: signingTxsCache,
|
signingTxsCache: signingTxsCache,
|
||||||
EngineV1: *engine_v1.New(&conf, db),
|
EngineV1: engine_v1.New(config, db),
|
||||||
EngineV2: *engine_v2.New(&conf, db),
|
EngineV2: engine_v2.New(config, db, waitPeriodCh),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,6 +114,8 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
|
||||||
conf = chainConfig.XDPoS
|
conf = chainConfig.XDPoS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
waitPeriodCh := make(chan int)
|
||||||
|
|
||||||
// Allocate the snapshot caches and create the engine
|
// Allocate the snapshot caches and create the engine
|
||||||
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
||||||
|
|
||||||
|
|
@ -96,13 +123,27 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
|
||||||
config: conf,
|
config: conf,
|
||||||
db: db,
|
db: db,
|
||||||
|
|
||||||
|
WaitPeriodCh: waitPeriodCh,
|
||||||
|
|
||||||
|
GetXDCXService: func() utils.TradingService { return nil },
|
||||||
|
GetLendingService: func() utils.LendingService { return nil },
|
||||||
|
|
||||||
signingTxsCache: signingTxsCache,
|
signingTxsCache: signingTxsCache,
|
||||||
EngineV1: *engine_v1.NewFaker(db, conf),
|
EngineV1: engine_v1.NewFaker(db, conf),
|
||||||
EngineV2: *engine_v2.NewFaker(db, conf),
|
EngineV2: engine_v2.New(conf, db, waitPeriodCh),
|
||||||
}
|
}
|
||||||
return fakeEngine
|
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
|
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.
|
// VerifyHeader checks whether a header conforms to the consensus rules.
|
||||||
func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
|
func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.VerifyHeader(chain, header, fullVerify)
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.VerifyHeader(chain, header, fullVerify)
|
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
|
// 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).
|
// 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) {
|
func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) {
|
||||||
// TODO: (Hashlab) This funciton is a special case
|
abort := make(chan struct{})
|
||||||
return x.EngineV1.VerifyHeaders(chain, headers, fullVerifies)
|
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
|
// VerifyUncles implements consensus.Engine, always returning an error for any
|
||||||
// uncles as this consensus mechanism doesn't permit uncles.
|
// uncles as this consensus mechanism doesn't permit uncles.
|
||||||
func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
||||||
switch x.config.BlockConsensusVersion(block.Number()) {
|
switch x.config.BlockConsensusVersion(block.Number()) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return nil
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.VerifyUncles(chain, block)
|
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.
|
// in the header satisfies the consensus protocol requirements.
|
||||||
func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return nil
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.VerifySeal(chain, header)
|
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.
|
// header for running the transactions on top.
|
||||||
func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.Prepare(chain, header)
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.Prepare(chain, header)
|
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.
|
// 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) {
|
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) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts)
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts)
|
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.
|
// the local signing credentials.
|
||||||
func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
|
func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
|
||||||
switch x.config.BlockConsensusVersion(block.Number()) {
|
switch x.config.BlockConsensusVersion(block.Number()) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.Seal(chain, block, stop)
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.Seal(chain, block, stop)
|
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.
|
// current signer.
|
||||||
func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||||
switch x.config.BlockConsensusVersion(parent.Number) {
|
switch x.config.BlockConsensusVersion(parent.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.CalcDifficulty(chain, time, parent)
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.CalcDifficulty(chain, time, parent)
|
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
|
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) {
|
func (x *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) {
|
||||||
// Authorize each consensus individually
|
// Authorize each consensus individually
|
||||||
x.EngineV1.Authorize(signer, signFn)
|
x.EngineV1.Authorize(signer, signFn)
|
||||||
|
x.EngineV2.Authorize(signer, signFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XDPoS) GetPeriod() uint64 {
|
func (x *XDPoS) GetPeriod() uint64 {
|
||||||
return x.config.Period
|
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) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.IsAuthorisedAddress(chain, header, address)
|
||||||
default: // Default "v1"
|
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 {
|
func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.GetMasternodes(chain, header)
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.GetMasternodes(chain, header)
|
return x.EngineV1.GetMasternodes(chain, header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber uint64) []common.Address {
|
||||||
switch x.config.BlockConsensusVersion(parent.Number) {
|
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"
|
default: // Default "v1"
|
||||||
return x.EngineV1.YourTurn(chain, parent, signer)
|
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) {
|
func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
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)
|
return x.EngineV1.GetValidator(creator, chain, header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
|
func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
|
||||||
|
// fmt.Println("UpdateMasternodes")
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.UpdateMasternodes(chain, header, ms)
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.UpdateMasternodes(chain, header, ms)
|
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) {
|
func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return common.Address{}, nil
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.RecoverSigner(header)
|
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) {
|
func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return common.Address{}, nil
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.RecoverValidator(header)
|
return x.EngineV1.RecoverValidator(header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get master nodes over extra data of previous checkpoint block.
|
// Get master nodes over extra data of previous checkpoint block.
|
||||||
func (x *XDPoS) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
|
func (x *XDPoS) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||||
switch x.config.BlockConsensusVersion(preCheckpointHeader.Number) {
|
switch x.config.BlockConsensusVersion(checkpointHeader.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return x.EngineV2.GetMasternodesFromEpochSwitchHeader(checkpointHeader)
|
||||||
default: // Default "v1"
|
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) {
|
func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
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"
|
default: // Default "v1"
|
||||||
sp, err := x.EngineV1.GetSnapshot(chain, header)
|
sp, err := x.EngineV1.GetSnapshot(chain, header)
|
||||||
// Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API
|
// 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) {
|
func (x *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) {
|
||||||
switch x.config.BlockConsensusVersion(header.Number) {
|
switch x.config.BlockConsensusVersion(header.Number) {
|
||||||
|
case params.ConsensusEngineVersion2:
|
||||||
|
return []common.Address{}, nil
|
||||||
default: // Default "v1"
|
default: // Default "v1"
|
||||||
return x.EngineV1.GetAuthorisedSignersFromSnapshot(chain, header)
|
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
|
Caching
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,11 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
|
||||||
return api.XDPoS.GetAuthorisedSignersFromSnapshot(api.chain, header)
|
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 {
|
func (api *API) NetworkInformation() NetworkInformation {
|
||||||
info := NetworkInformation{}
|
info := NetworkInformation{}
|
||||||
info.NetworkId = api.chain.Config().ChainId
|
info.NetworkId = api.chain.Config().ChainId
|
||||||
|
|
|
||||||
|
|
@ -28,30 +28,12 @@ import (
|
||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ecrecover extracts the Ethereum account address from a signed header.
|
const (
|
||||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
// timeout waiting for M1
|
||||||
// If the signature's already cached, return that
|
waitPeriod = 10
|
||||||
hash := header.Hash()
|
// timeout for checkpoint.
|
||||||
if address, known := sigcache.Get(hash); known {
|
waitPeriodCheckpoint = 20
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
|
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
|
||||||
// Ethereum testnet following the Ropsten attacks.
|
// Ethereum testnet following the Ropsten attacks.
|
||||||
|
|
@ -78,6 +60,23 @@ type XDPoS_v1 struct {
|
||||||
HookGetSignersFromContract func(blockHash common.Hash) ([]common.Address, error)
|
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
|
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
|
||||||
// signers set to the ones provided by the user.
|
// signers set to the ones provided by the user.
|
||||||
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 {
|
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
|
// 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
|
// 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).
|
// 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) {
|
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool, abort <-chan struct{}, results chan<- error) {
|
||||||
abort := make(chan struct{})
|
|
||||||
results := make(chan error, len(headers))
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for i, header := range headers {
|
for i, header := range headers {
|
||||||
err := x.verifyHeaderWithCache(chain, header, headers[:i], fullVerifies[i])
|
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 {
|
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.
|
// a batch of new headers.
|
||||||
func (x *XDPoS_v1) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
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 we're running a engine faking, accept any block as valid
|
||||||
if x.config.SkipValidation {
|
if x.config.SkipV1Validation {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if common.IsTestnet {
|
if common.IsTestnet {
|
||||||
|
|
@ -321,7 +316,7 @@ func (x *XDPoS_v1) checkSignersOnCheckpoint(chain consensus.ChainReader, header
|
||||||
return nil
|
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)
|
snap, err := x.GetSnapshot(chain, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("[IsAuthorisedAddress] Can't get snapshot with at ", "number", header.Number, "hash", header.Hash().Hex(), "err", err)
|
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)
|
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 {
|
func (x *XDPoS_v1) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||||
n := header.Number.Uint64()
|
n := header.Number.Uint64()
|
||||||
e := x.config.Epoch
|
e := x.config.Epoch
|
||||||
switch {
|
switch {
|
||||||
case n%e == 0:
|
case n%e == 0:
|
||||||
return x.GetMasternodesFromCheckpointHeader(header, n, e)
|
return x.GetMasternodesFromCheckpointHeader(header)
|
||||||
case n%e != 0:
|
case n%e != 0:
|
||||||
h := chain.GetHeaderByNumber(n - (n % e))
|
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:
|
default:
|
||||||
return []common.Address{}
|
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 (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 {
|
if header.Number.Uint64() == 0 {
|
||||||
return common.Address{}, errors.New("Don't take block 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
|
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)
|
masternodes := x.GetMasternodes(chain, parent)
|
||||||
|
|
||||||
// if common.IsTestnet {
|
// 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
|
// masternode[0] has chance to create block 1
|
||||||
preIndex := -1
|
preIndex := -1
|
||||||
if parent.Number.Uint64() != 0 {
|
if parent.Number.Uint64() != 0 {
|
||||||
pre, err = whoIsCreator(snap, parent)
|
pre, err = x.whoIsCreator(snap, parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, false, err
|
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 {
|
if signer == x.signer {
|
||||||
log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex)
|
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")
|
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 {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -876,7 +905,7 @@ func (x *XDPoS_v1) Seal(chain consensus.ChainReader, block *types.Block, stop <-
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
// Sign all the things!
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
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 we're running a engine faking, skip calculation
|
||||||
if x.config.SkipValidation {
|
if x.config.SkipV1Validation {
|
||||||
return big.NewInt(1)
|
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 {
|
if err != nil {
|
||||||
return big.NewInt(int64(len + curIndex - preIndex))
|
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
|
return common.Address{}, consensus.ErrFailValidatorSignature
|
||||||
}
|
}
|
||||||
// Recover the public key and the Ethereum address
|
// 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 {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -937,18 +966,13 @@ func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error
|
||||||
return signer, nil
|
return signer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get master nodes over extra data of previous checkpoint block.
|
// Get master nodes over extra data of checkpoint block.
|
||||||
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
|
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||||
if preCheckpointHeader == nil {
|
if checkpointHeader == nil {
|
||||||
log.Info("Previous checkpoint's header is empty", "block number", n, "epoch", e)
|
log.Warn("Checkpoint's header is empty", "Header", checkpointHeader)
|
||||||
return []common.Address{}
|
return []common.Address{}
|
||||||
}
|
}
|
||||||
masternodes := make([]common.Address, (len(preCheckpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
return decodeMasternodesFromHeaderExtra(checkpointHeader)
|
||||||
for i := 0; i < len(masternodes); i++ {
|
|
||||||
copy(masternodes[i][:], preCheckpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return masternodes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XDPoS_v1) GetDb() ethdb.Database {
|
func (x *XDPoS_v1) GetDb() ethdb.Database {
|
||||||
|
|
@ -970,30 +994,6 @@ func removePenaltiesFromBlock(chain consensus.ChainReader, masternodes []common.
|
||||||
return masternodes
|
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) {
|
func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) {
|
||||||
startGapBlockHeader := checkpointHeader
|
startGapBlockHeader := checkpointHeader
|
||||||
number := checkpointHeader.Number.Uint64()
|
number := checkpointHeader.Number.Uint64()
|
||||||
|
|
@ -1028,3 +1028,10 @@ func NewFaker(db ethdb.Database, config *params.XDPoSConfig) *XDPoS_v1 {
|
||||||
}
|
}
|
||||||
return fakeEngine
|
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
|
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
|
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.
|
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 (
|
const (
|
||||||
|
|
@ -23,3 +24,8 @@ const (
|
||||||
BlockSignersCacheLimit = 9000
|
BlockSignersCacheLimit = 9000
|
||||||
M2ByteLength = 4
|
M2ByteLength = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PeriodicJobPeriod = 60
|
||||||
|
PoolHygieneRound = 10
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package utils
|
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
|
// Various error messages to mark blocks invalid. These should be private to
|
||||||
// prevent engine specific errors from being referenced in the remainder of the
|
// 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")
|
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 is returned if a block's mix digest is non-zero.
|
||||||
ErrInvalidMixDigest = errors.New("non-zero mix digest")
|
ErrInvalidMixDigest = errors.New("non-zero mix digest")
|
||||||
|
|
||||||
|
|
@ -60,6 +68,9 @@ var (
|
||||||
// be modified via out-of-range or non-contiguous headers.
|
// be modified via out-of-range or non-contiguous headers.
|
||||||
ErrInvalidVotingChain = errors.New("invalid voting chain")
|
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 is returned if a header is signed by a non-authorized entity.
|
||||||
ErrUnauthorized = errors.New("unauthorized")
|
ErrUnauthorized = errors.New("unauthorized")
|
||||||
|
|
||||||
|
|
@ -71,4 +82,39 @@ var (
|
||||||
ErrWaitTransactions = errors.New("waiting for transactions")
|
ErrWaitTransactions = errors.New("waiting for transactions")
|
||||||
|
|
||||||
ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block")
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -52,100 +50,53 @@ func ExtractValidatorsFromBytes(byteValidators []byte) []int64 {
|
||||||
return validators
|
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
|
// compare 2 signers lists
|
||||||
// return true if they are same elements, otherwise return false
|
// return true if they are same elements, otherwise return false
|
||||||
func CompareSignersLists(list1 []common.Address, list2 []common.Address) bool {
|
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
|
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
|
if len(l1) != len(l2) {
|
||||||
// backing account.
|
return false
|
||||||
//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)
|
|
||||||
}
|
}
|
||||||
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
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"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) {
|
func TestCompareSignersLists(t *testing.T) {
|
||||||
list1 := []common.Address{
|
list1 := []common.Address{
|
||||||
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,10 @@ var (
|
||||||
ErrFailValidatorSignature = errors.New("missing validator in header")
|
ErrFailValidatorSignature = errors.New("missing validator in header")
|
||||||
|
|
||||||
ErrNoValidatorSignature = errors.New("no 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -6,13 +6,15 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
"github.com/XinFinOrg/XDPoSChain/params"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Should NOT update signerList if not on the gap block
|
// Should NOT update signerList if not on the gap block
|
||||||
func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
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())
|
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -26,11 +28,18 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
||||||
|
|
||||||
//Get from block validator error message
|
//Get from block validator error message
|
||||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(blockA)
|
||||||
|
assert.Nil(t, err)
|
||||||
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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
|
// 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) {
|
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
|
// Insert block 450
|
||||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
|
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
|
||||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block)
|
||||||
|
assert.Nil(t, err)
|
||||||
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
|
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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
|
//Should call updateM1 at gap block, and update the snapshot if there are SM transactions involved
|
||||||
func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
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
|
// Insert first Block 449
|
||||||
t.Logf("Inserting block with propose at 449...")
|
t.Logf("Inserting block with propose at 449...")
|
||||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
|
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
|
||||||
|
|
@ -89,10 +106,18 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
||||||
|
|
||||||
//Get from block validator error message
|
//Get from block validator error message
|
||||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block449)
|
||||||
|
assert.Nil(t, err)
|
||||||
parentBlock = block449
|
parentBlock = block449
|
||||||
|
|
||||||
signers, err := GetSnapshotSigner(blockchain, block449.Header())
|
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
|
// Now, let's mine another block to trigger the GAP block signerList update
|
||||||
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
|
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
|
||||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block450)
|
||||||
|
assert.Nil(t, err)
|
||||||
signers, err = GetSnapshotSigner(blockchain, block450.Header())
|
signers, err = GetSnapshotSigner(blockchain, block450.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed while trying to get signers")
|
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
|
//Should call updateM1 before gap block, and update the snapshot if there are SM transactions involved
|
||||||
func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
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
|
// Insert first Block 450 A
|
||||||
t.Logf("Inserting block with propose at 450 A...")
|
t.Logf("Inserting block with propose at 450 A...")
|
||||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000450"
|
blockCoinbaseA := "0xaaa0000000000000000000000000000000000450"
|
||||||
|
|
@ -147,11 +179,18 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
||||||
|
|
||||||
//Get from block validator error message
|
//Get from block validator error message
|
||||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(blockA)
|
||||||
|
assert.Nil(t, err)
|
||||||
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
// 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) {
|
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
|
// Check initial signer, by default, acc3 is in the signerList
|
||||||
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -189,11 +228,18 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(blockA)
|
||||||
|
assert.Nil(t, err)
|
||||||
signers, err = GetSnapshotSigner(blockchain, blockA.Header())
|
signers, err = GetSnapshotSigner(blockchain, blockA.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -217,10 +263,18 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block450B)
|
||||||
|
assert.Nil(t, err)
|
||||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -240,8 +294,16 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
||||||
|
|
||||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||||
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -288,7 +350,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
||||||
|
|
||||||
func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(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()
|
state, err := blockchain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -316,10 +378,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
||||||
transferTransaction := transferTx(t, acc1Addr, 999)
|
transferTransaction := transferTx(t, acc1Addr, 999)
|
||||||
|
|
||||||
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(blockA)
|
||||||
|
assert.Nil(t, err)
|
||||||
state, err = blockchain.State()
|
state, err = blockchain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed while trying to get blockchain state")
|
t.Fatalf("Failed while trying to get blockchain state")
|
||||||
|
|
@ -351,10 +421,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
||||||
transferTransaction = transferTx(t, acc1Addr, 888)
|
transferTransaction = transferTx(t, acc1Addr, 888)
|
||||||
|
|
||||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block450B)
|
||||||
|
assert.Nil(t, err)
|
||||||
state, err = blockchain.State()
|
state, err = blockchain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed while trying to get blockchain state")
|
t.Fatalf("Failed while trying to get blockchain state")
|
||||||
|
|
@ -378,11 +456,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
||||||
|
|
||||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block451B)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -422,7 +507,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
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
|
// Check initial signer, by default, acc3 is in the signerList
|
||||||
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -440,10 +525,18 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
||||||
// Insert normal blocks 450 A
|
// Insert normal blocks 450 A
|
||||||
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
|
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
|
||||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block450A)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
// Insert 451 A with vote
|
// Insert 451 A with vote
|
||||||
blockCoinbase451A := "0xaaa0000000000000000000000000000000000451"
|
blockCoinbase451A := "0xaaa0000000000000000000000000000000000451"
|
||||||
|
|
@ -453,10 +546,18 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block451A)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
// SignerList should be unchanged as the vote happen after GAP block
|
// SignerList should be unchanged as the vote happen after GAP block
|
||||||
signers, err = GetSnapshotSigner(blockchain, block451A.Header())
|
signers, err = GetSnapshotSigner(blockchain, block451A.Header())
|
||||||
|
|
@ -476,24 +577,48 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
||||||
// Insert forked Block 450 B
|
// Insert forked Block 450 B
|
||||||
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
|
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
|
||||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block450B)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block451B)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
|
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
|
||||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block452B)
|
||||||
|
assert.Nil(t, err)
|
||||||
signers, err = GetSnapshotSigner(blockchain, block452B.Header())
|
signers, err = GetSnapshotSigner(blockchain, block452B.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
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 (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
"github.com/XinFinOrg/XDPoSChain/params"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Snapshot try to read before blockchain is written
|
// Snapshot try to read before blockchain is written
|
||||||
func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
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()
|
state, err := blockchain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -39,10 +41,20 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
||||||
transferTransaction := transferTx(t, acc1Addr, 999)
|
transferTransaction := transferTx(t, acc1Addr, 999)
|
||||||
|
|
||||||
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(blockA)
|
||||||
|
assert.Nil(t, err)
|
||||||
state, err = blockchain.State()
|
state, err = blockchain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed while trying to get blockchain state")
|
t.Fatalf("Failed while trying to get blockchain state")
|
||||||
|
|
@ -74,10 +86,21 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
||||||
transferTransaction = transferTx(t, acc1Addr, 888)
|
transferTransaction = transferTx(t, acc1Addr, 888)
|
||||||
|
|
||||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block450B)
|
||||||
|
assert.Nil(t, err)
|
||||||
if blockchain.CurrentHeader().Hash() != block450B.Hash() {
|
if blockchain.CurrentHeader().Hash() != block450B.Hash() {
|
||||||
t.Fatalf("the block with higher difficulty should be current header")
|
t.Fatalf("the block with higher difficulty should be current header")
|
||||||
}
|
}
|
||||||
|
|
@ -104,11 +127,19 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
||||||
|
|
||||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block451B)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package consensus
|
package engine_v1_tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -6,14 +6,17 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||||
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
|
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
. "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 {
|
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
|
||||||
|
|
||||||
// initial helper backend
|
// initial helper backend
|
||||||
|
|
@ -223,23 +236,49 @@ func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testi
|
||||||
return ms
|
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
|
// Preparation
|
||||||
var err error
|
var err error
|
||||||
|
// Authorise
|
||||||
|
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||||
|
|
||||||
backend := getCommonBackend(t, chainConfig)
|
backend := getCommonBackend(t, chainConfig)
|
||||||
blockchain := backend.GetBlockChain()
|
blockchain := backend.GetBlockChain()
|
||||||
blockchain.Client = backend
|
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()
|
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
|
// Insert initial blocks
|
||||||
for i := 1; i <= numOfBlocks; i++ {
|
for i := 1; i <= numOfBlocks; i++ {
|
||||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
err = blockchain.InsertBlock(block)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
currentBlock = block
|
currentBlock = block
|
||||||
}
|
}
|
||||||
// Update Signer as there is no previous signer assigned
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return blockchain, backend, currentBlock
|
return blockchain, backend, currentBlock, signer, signFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert Block without transcation attached
|
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 {
|
||||||
func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, difficulty int64) (*types.Block, error) {
|
currentBlock := startingBlock
|
||||||
block, err := createXDPoSTestBlock(
|
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||||
blockchain,
|
|
||||||
parentBlock.Hash().Hex(),
|
header := &types.Header{
|
||||||
blockCoinBase, blockNum, nil,
|
Root: common.HexToHash(merkleRoot),
|
||||||
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
Number: big.NewInt(int64(blockNumber)),
|
||||||
common.HexToHash(root),
|
ParentHash: currentBlock.Hash(),
|
||||||
difficulty,
|
Coinbase: common.HexToAddress(blockCoinBase),
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = blockchain.InsertBlock(block)
|
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
|
||||||
if err != nil {
|
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
|
||||||
return nil, err
|
// 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 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) {
|
||||||
func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, txs []*types.Transaction, root string, difficulty int64) (*types.Block, error) {
|
if customHeader.Extra == nil {
|
||||||
block, err := createXDPoSTestBlock(
|
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
|
||||||
blockchain,
|
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
|
||||||
parentBlock.Hash().Hex(),
|
}
|
||||||
blockCoinBase, blockNum, txs,
|
var difficulty *big.Int
|
||||||
"0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c",
|
if customHeader.Difficulty == nil {
|
||||||
common.HexToHash(root),
|
difficulty = big.NewInt(1)
|
||||||
difficulty,
|
} else {
|
||||||
)
|
difficulty = customHeader.Difficulty
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = blockchain.InsertBlock(block)
|
// TODO: check if this is needed
|
||||||
if err != nil {
|
if len(txs) != 0 {
|
||||||
return nil, err
|
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{
|
header := types.Header{
|
||||||
ParentHash: common.HexToHash(parentHash),
|
ParentHash: customHeader.ParentHash,
|
||||||
UncleHash: types.EmptyUncleHash,
|
UncleHash: types.EmptyUncleHash,
|
||||||
TxHash: types.EmptyRootHash,
|
TxHash: types.EmptyRootHash,
|
||||||
// ReceiptHash: types.EmptyRootHash,
|
ReceiptHash: customHeader.ReceiptHash,
|
||||||
ReceiptHash: common.HexToHash(receiptHash),
|
Root: customHeader.Root,
|
||||||
Root: root,
|
Coinbase: customHeader.Coinbase,
|
||||||
Coinbase: common.HexToAddress(coinbase),
|
Difficulty: difficulty,
|
||||||
Difficulty: big.NewInt(difficulty),
|
Number: customHeader.Number,
|
||||||
Number: big.NewInt(int64(number)),
|
|
||||||
GasLimit: 1200000000,
|
GasLimit: 1200000000,
|
||||||
Time: big.NewInt(int64(number * 10)),
|
Time: big.NewInt(time.Now().Unix()),
|
||||||
Extra: extraByte,
|
Extra: customHeader.Extra,
|
||||||
|
Validator: customHeader.Validator,
|
||||||
|
Validators: customHeader.Validators,
|
||||||
|
Penalties: customHeader.Penalties,
|
||||||
}
|
}
|
||||||
|
|
||||||
var block *types.Block
|
var block *types.Block
|
||||||
if len(txs) == 0 {
|
if len(txs) == 0 {
|
||||||
block = types.NewBlockWithHeader(&header)
|
block = types.NewBlockWithHeader(&header)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Prepare Receipt
|
// 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%v when get state", err)
|
return nil, fmt.Errorf("%v when get state", err)
|
||||||
}
|
}
|
||||||
gp := new(GasPool).AddGas(header.GasLimit)
|
gp := new(GasPool).AddGas(header.GasLimit)
|
||||||
// usedGas := uint64(0)
|
|
||||||
|
|
||||||
var gasUsed = new(uint64)
|
var gasUsed = new(uint64)
|
||||||
var receipts types.Receipts
|
var receipts types.Receipts
|
||||||
|
|
@ -338,7 +390,6 @@ func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number in
|
||||||
}
|
}
|
||||||
|
|
||||||
header.GasUsed = *gasUsed
|
header.GasUsed = *gasUsed
|
||||||
|
|
||||||
block = types.NewBlock(&header, txs, nil, receipts)
|
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
|
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
||||||
)
|
)
|
||||||
|
|
||||||
type rewardLog struct {
|
type RewardLog struct {
|
||||||
Sign uint64 `json:"sign"`
|
Sign uint64 `json:"sign"`
|
||||||
Reward *big.Int `json:"reward"`
|
Reward *big.Int `json:"reward"`
|
||||||
}
|
}
|
||||||
|
|
@ -319,13 +319,13 @@ func DecryptRandomizeFromSecretsAndOpening(secrets [][32]byte, opening [32]byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate reward for reward checkpoint.
|
// 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.
|
// Not reward for singer of genesis block and only calculate reward at checkpoint block.
|
||||||
number := header.Number.Uint64()
|
number := header.Number.Uint64()
|
||||||
prevCheckpoint := number - (rCheckpoint * 2)
|
prevCheckpoint := number - (rCheckpoint * 2)
|
||||||
startBlockNumber := prevCheckpoint + 1
|
startBlockNumber := prevCheckpoint + 1
|
||||||
endBlockNumber := startBlockNumber + rCheckpoint - 1
|
endBlockNumber := startBlockNumber + rCheckpoint - 1
|
||||||
signers := make(map[common.Address]*rewardLog)
|
signers := make(map[common.Address]*RewardLog)
|
||||||
mapBlkHash := map[uint64]common.Hash{}
|
mapBlkHash := map[uint64]common.Hash{}
|
||||||
|
|
||||||
data := make(map[common.Hash][]common.Address)
|
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)
|
header = chain.GetHeader(header.ParentHash, prevCheckpoint)
|
||||||
masternodes := utils.GetMasternodesFromCheckpointHeader(header)
|
masternodes := c.GetMasternodesFromCheckpointHeader(header)
|
||||||
|
|
||||||
for i := startBlockNumber; i <= endBlockNumber; i++ {
|
for i := startBlockNumber; i <= endBlockNumber; i++ {
|
||||||
if i%common.MergeSignRange == 0 || !chain.Config().IsTIP2019(big.NewInt(int64(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 {
|
if exist {
|
||||||
signers[addr].Sign++
|
signers[addr].Sign++
|
||||||
} else {
|
} else {
|
||||||
signers[addr] = &rewardLog{1, new(big.Int)}
|
signers[addr] = &RewardLog{1, new(big.Int)}
|
||||||
}
|
}
|
||||||
*totalSigner++
|
*totalSigner++
|
||||||
}
|
}
|
||||||
|
|
@ -390,7 +390,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate reward for signers.
|
// 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)
|
resultSigners := make(map[common.Address]*big.Int)
|
||||||
// Add reward for signers.
|
// Add reward for signers.
|
||||||
if totalSigner > 0 {
|
if totalSigner > 0 {
|
||||||
|
|
@ -404,12 +404,11 @@ func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*
|
||||||
resultSigners[signer] = calcReward
|
resultSigners[signer] = calcReward
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonSigners, err := json.Marshal(signers)
|
|
||||||
if err != nil {
|
log.Info("Signers data", "totalSigner", totalSigner, "totalReward", chainReward)
|
||||||
log.Error("Fail to parse json signers", "error", err)
|
for addr, signer := range signers {
|
||||||
return nil, err
|
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
|
return resultSigners, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,12 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||||
|
|
|
||||||
|
|
@ -352,6 +352,13 @@ func (bc *BlockChain) loadLastState() error {
|
||||||
}
|
}
|
||||||
bc.hc.SetCurrentHeader(currentHeader)
|
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
|
// Restore the last known head fast block
|
||||||
bc.currentFastBlock.Store(currentBlock)
|
bc.currentFastBlock.Store(currentBlock)
|
||||||
if head := GetHeadFastBlockHash(bc.db); head != (common.Hash{}) {
|
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
|
// Insert one by one as chain insertion needs contiguous ancestry between blocks
|
||||||
for i := range 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)
|
bc.reportBlock(block, nil, err)
|
||||||
return i, events, coalescedLogs, err
|
return i, events, coalescedLogs, err
|
||||||
}
|
}
|
||||||
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
isEpochSwithBlock, epochNumber, err := engine.IsEpochSwitch(block.Header())
|
||||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(block.NumberU64()/bc.chainConfig.XDPoS.Epoch, tradingState, statedb); err != nil {
|
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
|
return i, events, coalescedLogs, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1708,9 +1733,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
||||||
stats.report(chain, i, dirty)
|
stats.report(chain, i, dirty)
|
||||||
if bc.chainConfig.XDPoS != nil {
|
if bc.chainConfig.XDPoS != nil {
|
||||||
// epoch block
|
// 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
|
CheckpointCh <- 1
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1855,8 +1884,15 @@ func (bc *BlockChain) getResultBlock(block *types.Block, verifiedM2 bool) (*Resu
|
||||||
bc.reportBlock(block, nil, err)
|
bc.reportBlock(block, nil, err)
|
||||||
return 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
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2029,9 +2065,13 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L
|
||||||
stats.report(types.Blocks{block}, 0, dirty)
|
stats.report(types.Blocks{block}, 0, dirty)
|
||||||
if bc.chainConfig.XDPoS != nil {
|
if bc.chainConfig.XDPoS != nil {
|
||||||
// epoch block
|
// 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
|
CheckpointCh <- 1
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Append a single chain head event if we've progressed the chain
|
// 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
|
// if can't get anything, request from contracts
|
||||||
stateDB, err := bc.State()
|
stateDB, err := bc.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
candidates, err = validator.GetCandidates(opts)
|
candidates, err = validator.GetCandidates(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
candidates = state.GetCandidates(stateDB)
|
candidates = state.GetCandidates(stateDB)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ms []utils.Masternode
|
var ms []utils.Masternode
|
||||||
|
|
@ -2500,7 +2536,7 @@ func (bc *BlockChain) UpdateM1() error {
|
||||||
header := bc.CurrentHeader()
|
header := bc.CurrentHeader()
|
||||||
var maxMasternodes int
|
var maxMasternodes int
|
||||||
// check if block number is increase ms checkpoint
|
// 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
|
// using new masterndoes
|
||||||
maxMasternodes = common.MaxMasternodesV2
|
maxMasternodes = common.MaxMasternodesV2
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -188,10 +188,11 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
||||||
}
|
}
|
||||||
return newcfg, stored, err
|
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)
|
// config is supplied. These chains would get AllProtocolChanges (and a compat error)
|
||||||
// if we just continued here.
|
// if we just continued here.
|
||||||
if genesis == nil && stored != params.MainnetGenesisHash {
|
if genesis == nil && newcfg == params.AllEthashProtocolChanges {
|
||||||
return storedcfg, stored, nil
|
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 {
|
func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||||
switch {
|
switch {
|
||||||
case g != nil:
|
case g != nil:
|
||||||
|
log.Info("[configOrDefault] load orignal config", "hash", ghash)
|
||||||
return g.Config
|
return g.Config
|
||||||
case ghash == params.MainnetGenesisHash:
|
case ghash == params.XDCMainnetGenesisHash:
|
||||||
|
log.Info("[configOrDefault] load mainnetconfig")
|
||||||
return params.XDCMainnetChainConfig
|
return params.XDCMainnetChainConfig
|
||||||
case ghash == params.TestnetGenesisHash:
|
case ghash == params.TestnetGenesisHash:
|
||||||
|
log.Info("[configOrDefault] load TestnetChainConfig")
|
||||||
return params.TestnetChainConfig
|
return params.TestnetChainConfig
|
||||||
|
case ghash == params.DevnetGenesisHash:
|
||||||
|
log.Info("[configOrDefault] load DevnetChainConfig")
|
||||||
|
return params.DevnetChainConfig
|
||||||
default:
|
default:
|
||||||
|
log.Info("[configOrDefault] load AllEthashProtocolChanges", "hash", ghash)
|
||||||
return params.AllEthashProtocolChanges
|
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.
|
// DefaultGenesisBlock returns the Ethereum main net genesis block.
|
||||||
func DefaultGenesisBlock() *Genesis {
|
func DefaultGenesisBlock() *Genesis {
|
||||||
|
config := params.XDCMainnetChainConfig
|
||||||
|
config.XDPoS.V2 = nil
|
||||||
return &Genesis{
|
return &Genesis{
|
||||||
Config: params.XDCMainnetChainConfig,
|
Config: config,
|
||||||
Nonce: 0,
|
Nonce: 0,
|
||||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001b82c4bf317fcafe3d77e8b444c82715d216afe845b7bd987fa22c9bac89b71f0ded03f6e150ba31ad670b2b166684657ffff95f4810380ae7381e9bce41231d5dd8cdd7499e418b648c00af75d184a2f9aba09a6fa4a46fb1a6a3919b027d9cac5aa6890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
ExtraData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000000025c65b4b379ac37cf78357c4915f73677022eaffc7d49d0a2cf198deebd6ce581af465944ec8b2bbcfccdea1006a5cfa7d9484b5b293b46964c265c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
GasLimit: 4700000,
|
GasLimit: 4700000,
|
||||||
Difficulty: big.NewInt(1),
|
Difficulty: big.NewInt(1),
|
||||||
Alloc: DecodeMainnet(),
|
Alloc: DecodeAllocJson(XDCAllocData),
|
||||||
Timestamp: 1544771829,
|
Timestamp: 1559211559,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultTestnetGenesisBlock returns the Ropsten network genesis block.
|
// DefaultTestnetGenesisBlock returns the Ropsten network genesis block.
|
||||||
func DefaultTestnetGenesisBlock() *Genesis {
|
func DefaultTestnetGenesisBlock() *Genesis {
|
||||||
|
config := params.TestnetChainConfig
|
||||||
|
config.XDPoS.V2 = nil
|
||||||
return &Genesis{
|
return &Genesis{
|
||||||
Config: params.TestnetChainConfig,
|
Config: params.TestnetChainConfig,
|
||||||
Nonce: 66,
|
Nonce: 0,
|
||||||
ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"),
|
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000003ea0a3555f9b1de983572bff6444aeb1899ec58c4f7900282f3d371d585ab1361205b0940ab1789c942a5885a8844ee5587c8ac5e371fc39ffe618960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
GasLimit: 16777216,
|
GasLimit: 4700000,
|
||||||
Difficulty: big.NewInt(1048576),
|
Difficulty: big.NewInt(1),
|
||||||
Alloc: decodePrealloc(testnetAllocData),
|
Alloc: DecodeAllocJson(XDCTestAllocData),
|
||||||
|
Timestamp: 1560417871,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,8 +399,8 @@ func decodePrealloc(data string) GenesisAlloc {
|
||||||
return ga
|
return ga
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeMainnet() GenesisAlloc {
|
func DecodeAllocJson(s string) GenesisAlloc {
|
||||||
mainnetAlloc := GenesisAlloc{}
|
alloc := GenesisAlloc{}
|
||||||
json.Unmarshal([]byte(XDCAllocData), &mainnetAlloc)
|
json.Unmarshal([]byte(s), &alloc)
|
||||||
return mainnetAlloc
|
return alloc
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -17,14 +17,15 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
"github.com/XinFinOrg/XDPoSChain/params"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"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
|
// SetEtherbase sets the etherbase of the miner
|
||||||
func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
|
func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
|
||||||
|
log.Info("[PrivateMinerAPI] SetEtherbase", "addr", etherbase)
|
||||||
api.e.SetEtherbase(etherbase)
|
api.e.SetEtherbase(etherbase)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,17 @@ func (b *EthApiBackend) GetVotersRewards(masternodeAddr common.Address) map[comm
|
||||||
number := block.Number().Uint64()
|
number := block.Number().Uint64()
|
||||||
engine := b.GetEngine().(*XDPoS.XDPoS)
|
engine := b.GetEngine().(*XDPoS.XDPoS)
|
||||||
foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr
|
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)
|
lastCheckpointBlock := chain.GetBlockByNumber(lastCheckpointNumber)
|
||||||
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint
|
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
||||||
return block, false, err
|
return block, false, err
|
||||||
}
|
}
|
||||||
header := block.Header()
|
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 {
|
if err != nil || sighash == nil {
|
||||||
log.Error("Can't get signature hash of m2", "sighash", sighash, "err", err)
|
log.Error("Can't get signature hash of m2", "sighash", sighash, "err", err)
|
||||||
return block, false, err
|
return block, false, err
|
||||||
|
|
@ -285,6 +285,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
||||||
XDPoS1.0 Specific hooks
|
XDPoS1.0 Specific hooks
|
||||||
*/
|
*/
|
||||||
hooks.AttachConsensusV1Hooks(c, eth.blockchain, chainConfig)
|
hooks.AttachConsensusV1Hooks(c, eth.blockchain, chainConfig)
|
||||||
|
hooks.AttachConsensusV2Hooks(c, eth.blockchain, chainConfig)
|
||||||
|
|
||||||
eth.txPool.IsSigner = func(address common.Address) bool {
|
eth.txPool.IsSigner = func(address common.Address) bool {
|
||||||
currentHeader := eth.blockchain.CurrentHeader()
|
currentHeader := eth.blockchain.CurrentHeader()
|
||||||
|
|
@ -296,7 +297,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
||||||
// not genesis block
|
// not genesis block
|
||||||
header = parentHeader
|
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.
|
// APIs returns the collection of RPC services the ethereum package offers.
|
||||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||||
func (s *Ethereum) APIs() []rpc.API {
|
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
|
// Append any APIs exposed explicitly by the consensus engine
|
||||||
apis = append(apis, s.engine.APIs(s.BlockChain())...)
|
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
|
//check if miner's wallet is in set of validators
|
||||||
c := s.engine.(*XDPoS.XDPoS)
|
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 {
|
if !authorized {
|
||||||
//This miner doesn't belong to set of validators
|
//This miner doesn't belong to set of validators
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
@ -506,7 +507,7 @@ func (s *Ethereum) StartStaking(local bool) error {
|
||||||
if XDPoS, ok := s.engine.(*XDPoS.XDPoS); ok {
|
if XDPoS, ok := s.engine.(*XDPoS.XDPoS); ok {
|
||||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||||
if wallet == nil || err != nil {
|
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)
|
return fmt.Errorf("signer missing: %v", err)
|
||||||
}
|
}
|
||||||
XDPoS.Authorize(eb, wallet.SignHash)
|
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"
|
"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 (
|
var (
|
||||||
MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request
|
MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request
|
||||||
MaxBlockFetch = 128 // Amount of blocks 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
|
blockchain BlockChain
|
||||||
|
|
||||||
// Callbacks
|
// 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
|
// Status
|
||||||
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
|
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.
|
// 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 {
|
if lightchain == nil {
|
||||||
lightchain = chain
|
lightchain = chain
|
||||||
}
|
}
|
||||||
|
|
||||||
dl := &Downloader{
|
dl := &Downloader{
|
||||||
mode: mode,
|
mode: mode,
|
||||||
stateDB: stateDb,
|
stateDB: stateDb,
|
||||||
mux: mux,
|
mux: mux,
|
||||||
queue: newQueue(),
|
queue: newQueue(),
|
||||||
peers: newPeerSet(),
|
peers: newPeerSet(),
|
||||||
rttEstimate: uint64(rttMaxEstimate),
|
rttEstimate: uint64(rttMaxEstimate),
|
||||||
rttConfidence: uint64(1000000),
|
rttConfidence: uint64(1000000),
|
||||||
blockchain: chain,
|
blockchain: chain,
|
||||||
lightchain: lightchain,
|
lightchain: lightchain,
|
||||||
dropPeer: dropPeer,
|
dropPeer: dropPeer,
|
||||||
headerCh: make(chan dataPack, 1),
|
handleProposedBlock: handleProposedBlock,
|
||||||
bodyCh: make(chan dataPack, 1),
|
headerCh: make(chan dataPack, 1),
|
||||||
receiptCh: make(chan dataPack, 1),
|
bodyCh: make(chan dataPack, 1),
|
||||||
bodyWakeCh: make(chan bool, 1),
|
receiptCh: make(chan dataPack, 1),
|
||||||
receiptWakeCh: make(chan bool, 1),
|
bodyWakeCh: make(chan bool, 1),
|
||||||
headerProcCh: make(chan []*types.Header, 1),
|
receiptWakeCh: make(chan bool, 1),
|
||||||
quitCh: make(chan struct{}),
|
headerProcCh: make(chan []*types.Header, 1),
|
||||||
stateCh: make(chan dataPack),
|
quitCh: make(chan struct{}),
|
||||||
stateSyncStart: make(chan *stateSync),
|
stateCh: make(chan dataPack),
|
||||||
|
stateSyncStart: make(chan *stateSync),
|
||||||
syncStatsState: stateSyncStats{
|
syncStatsState: stateSyncStats{
|
||||||
processed: core.GetTrieSyncProgress(stateDb),
|
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)
|
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
|
||||||
return errInvalidChain
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,14 @@ package downloader
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
|
|
@ -97,7 +98,7 @@ func newTester() *downloadTester {
|
||||||
tester.stateDb = rawdb.NewMemoryDatabase()
|
tester.stateDb = rawdb.NewMemoryDatabase()
|
||||||
tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00})
|
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
|
return tester
|
||||||
}
|
}
|
||||||
|
|
@ -457,6 +458,11 @@ func (dl *downloadTester) dropPeer(id string) {
|
||||||
dl.downloader.UnregisterPeer(id)
|
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.
|
// Config retrieves the blockchain's chain configuration.
|
||||||
func (dl *downloadTester) Config() *params.ChainConfig { return params.TestChainConfig }
|
func (dl *downloadTester) Config() *params.ChainConfig { return params.TestChainConfig }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
|
||||||
defer p.lock.RUnlock()
|
defer p.lock.RUnlock()
|
||||||
return p.headerThroughput
|
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
|
// 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()
|
defer p.lock.RUnlock()
|
||||||
return p.blockThroughput
|
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
|
// 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()
|
defer p.lock.RUnlock()
|
||||||
return p.receiptThroughput
|
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
|
// 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()
|
defer p.lock.RUnlock()
|
||||||
return p.stateThroughput
|
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
|
// idlePeers retrieves a flat list of all currently idle peers satisfying the
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@ package fetcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/hashicorp/golang-lru"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"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.
|
// headerVerifierFn is a callback type to verify a block's header for fast propagation.
|
||||||
type headerVerifierFn func(header *types.Header) error
|
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.
|
// blockBroadcasterFn is a callback type for broadcasting a block to connected peers.
|
||||||
type blockBroadcasterFn func(block *types.Block, propagate bool)
|
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)
|
queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports)
|
||||||
knowns *lru.ARCCache
|
knowns *lru.ARCCache
|
||||||
// Callbacks
|
// Callbacks
|
||||||
getBlock blockRetrievalFn // Retrieves a block from the local chain
|
getBlock blockRetrievalFn // Retrieves a block from the local chain
|
||||||
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
|
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
|
||||||
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
|
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
|
||||||
chainHeight chainHeightFn // Retrieves the current chain's height
|
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
|
||||||
insertBlock blockInsertFn // Injects a batch of blocks into the chain
|
chainHeight chainHeightFn // Retrieves the current chain's height
|
||||||
prepareBlock blockPrepareFn
|
insertBlock blockInsertFn // Injects a batch of blocks into the chain
|
||||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
prepareBlock blockPrepareFn
|
||||||
|
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||||
|
|
||||||
// Testing hooks
|
// Testing hooks
|
||||||
announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the announce list
|
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.
|
// 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)
|
knownBlocks, _ := lru.NewARC(blockLimit)
|
||||||
return &Fetcher{
|
return &Fetcher{
|
||||||
notify: make(chan *announce),
|
notify: make(chan *announce),
|
||||||
inject: make(chan *inject),
|
inject: make(chan *inject),
|
||||||
blockFilter: make(chan chan []*types.Block),
|
blockFilter: make(chan chan []*types.Block),
|
||||||
headerFilter: make(chan chan *headerFilterTask),
|
headerFilter: make(chan chan *headerFilterTask),
|
||||||
bodyFilter: make(chan chan *bodyFilterTask),
|
bodyFilter: make(chan chan *bodyFilterTask),
|
||||||
done: make(chan common.Hash),
|
done: make(chan common.Hash),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
announces: make(map[string]int),
|
announces: make(map[string]int),
|
||||||
announced: make(map[common.Hash][]*announce),
|
announced: make(map[common.Hash][]*announce),
|
||||||
fetching: make(map[common.Hash]*announce),
|
fetching: make(map[common.Hash]*announce),
|
||||||
fetched: make(map[common.Hash][]*announce),
|
fetched: make(map[common.Hash][]*announce),
|
||||||
completing: make(map[common.Hash]*announce),
|
completing: make(map[common.Hash]*announce),
|
||||||
queue: prque.New(),
|
queue: prque.New(),
|
||||||
queues: make(map[string]int),
|
queues: make(map[string]int),
|
||||||
queued: make(map[common.Hash]*inject),
|
queued: make(map[common.Hash]*inject),
|
||||||
knowns: knownBlocks,
|
knowns: knownBlocks,
|
||||||
getBlock: getBlock,
|
getBlock: getBlock,
|
||||||
verifyHeader: verifyHeader,
|
verifyHeader: verifyHeader,
|
||||||
broadcastBlock: broadcastBlock,
|
handleProposedBlock: handleProposedBlock,
|
||||||
chainHeight: chainHeight,
|
broadcastBlock: broadcastBlock,
|
||||||
insertBlock: insertBlock,
|
chainHeight: chainHeight,
|
||||||
prepareBlock: prepareBlock,
|
insertBlock: insertBlock,
|
||||||
dropPeer: dropPeer,
|
prepareBlock: prepareBlock,
|
||||||
|
dropPeer: dropPeer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -705,13 +711,13 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
|
||||||
goto again
|
goto again
|
||||||
default:
|
default:
|
||||||
// Something went very wrong, drop the peer
|
// 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)
|
f.dropPeer(peer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Run the actual import and log any issues
|
// Run the actual import and log any issues
|
||||||
if err := f.insertBlock(block); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -721,6 +727,10 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
|
||||||
return
|
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
|
// If import succeeded, broadcast the block
|
||||||
propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
|
propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
|
||||||
if !fastBroadCast {
|
if !fastBroadCast {
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@ package fetcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
|
|
@ -92,7 +93,7 @@ func newTester() *fetcherTester {
|
||||||
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
|
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
|
||||||
drops: make(map[string]bool),
|
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()
|
tester.fetcher.Start()
|
||||||
|
|
||||||
return tester
|
return tester
|
||||||
|
|
@ -111,6 +112,10 @@ func (f *fetcherTester) verifyHeader(header *types.Header) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fetcherTester) handleProposedBlock(header *types.Header) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// broadcastBlock is a nop placeholder for the block broadcasting.
|
// broadcastBlock is a nop placeholder for the block broadcasting.
|
||||||
func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) {
|
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
|
// Tests that a fetcher accepts block announcements and initiates retrievals for
|
||||||
// them, successfully importing into the local chain.
|
// them, successfully importing into the local chain.
|
||||||
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
|
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
|
||||||
|
|
@ -317,12 +330,18 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
|
||||||
imported <- block
|
imported <- block
|
||||||
return nil
|
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-- {
|
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)
|
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
||||||
verifyImportEvent(t, imported, true)
|
verifyImportEvent(t, imported, true)
|
||||||
}
|
}
|
||||||
verifyImportDone(t, imported)
|
verifyImportDone(t, imported)
|
||||||
|
verifyProposeBlockHandlerCalled(t, handleProposedBlockChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that if blocks are announced by multiple peers (or even the same buggy
|
// 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/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/eth/bft"
|
||||||
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
|
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
|
||||||
"github.com/XinFinOrg/XDPoSChain/eth/fetcher"
|
"github.com/XinFinOrg/XDPoSChain/eth/fetcher"
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||||
|
|
@ -80,6 +82,7 @@ type ProtocolManager struct {
|
||||||
downloader *downloader.Downloader
|
downloader *downloader.Downloader
|
||||||
fetcher *fetcher.Fetcher
|
fetcher *fetcher.Fetcher
|
||||||
peers *peerSet
|
peers *peerSet
|
||||||
|
bft *bft.Bfter
|
||||||
|
|
||||||
SubProtocols []p2p.Protocol
|
SubProtocols []p2p.Protocol
|
||||||
|
|
||||||
|
|
@ -104,6 +107,11 @@ type ProtocolManager struct {
|
||||||
knownTxs *lru.Cache
|
knownTxs *lru.Cache
|
||||||
knowOrderTxs *lru.Cache
|
knowOrderTxs *lru.Cache
|
||||||
knowLendingTxs *lru.Cache
|
knowLendingTxs *lru.Cache
|
||||||
|
|
||||||
|
// V2 messages
|
||||||
|
knownVotes *lru.Cache
|
||||||
|
knownSyncInfos *lru.Cache
|
||||||
|
knownTimeouts *lru.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProtocolManagerEx add order pool to protocol
|
// NewProtocolManagerEx add order pool to protocol
|
||||||
|
|
@ -123,6 +131,11 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
|
||||||
knownTxs, _ := lru.New(maxKnownTxs)
|
knownTxs, _ := lru.New(maxKnownTxs)
|
||||||
knowOrderTxs, _ := lru.New(maxKnownOrderTxs)
|
knowOrderTxs, _ := lru.New(maxKnownOrderTxs)
|
||||||
knowLendingTxs, _ := lru.New(maxKnownLendingTxs)
|
knowLendingTxs, _ := lru.New(maxKnownLendingTxs)
|
||||||
|
|
||||||
|
knownVotes, _ := lru.New(maxKnownVote)
|
||||||
|
knownSyncInfos, _ := lru.New(maxKnownSyncInfo)
|
||||||
|
knownTimeouts, _ := lru.New(maxKnownTimeout)
|
||||||
|
|
||||||
// Create the protocol manager with the base fields
|
// Create the protocol manager with the base fields
|
||||||
manager := &ProtocolManager{
|
manager := &ProtocolManager{
|
||||||
networkId: networkID,
|
networkId: networkID,
|
||||||
|
|
@ -138,6 +151,9 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
|
||||||
knownTxs: knownTxs,
|
knownTxs: knownTxs,
|
||||||
knowOrderTxs: knowOrderTxs,
|
knowOrderTxs: knowOrderTxs,
|
||||||
knowLendingTxs: knowLendingTxs,
|
knowLendingTxs: knowLendingTxs,
|
||||||
|
knownVotes: knownVotes,
|
||||||
|
knownSyncInfos: knownSyncInfos,
|
||||||
|
knownTimeouts: knownTimeouts,
|
||||||
orderpool: nil,
|
orderpool: nil,
|
||||||
lendingpool: nil,
|
lendingpool: nil,
|
||||||
orderTxSub: nil,
|
orderTxSub: nil,
|
||||||
|
|
@ -189,12 +205,25 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
|
||||||
if len(manager.SubProtocols) == 0 {
|
if len(manager.SubProtocols) == 0 {
|
||||||
return nil, errIncompatibleConfig
|
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
|
// 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 {
|
validator := func(header *types.Header) error {
|
||||||
return engine.VerifyHeader(blockchain, header, true)
|
return engine.VerifyHeader(blockchain, header, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
heighter := func() uint64 {
|
heighter := func() uint64 {
|
||||||
return blockchain.CurrentBlock().NumberU64()
|
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
|
atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
|
||||||
return manager.blockchain.PrepareBlock(block)
|
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
|
return manager, nil
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +292,6 @@ func (pm *ProtocolManager) Start(maxPeers int) {
|
||||||
// broadcast transactions
|
// broadcast transactions
|
||||||
pm.txCh = make(chan core.TxPreEvent, txChanSize)
|
pm.txCh = make(chan core.TxPreEvent, txChanSize)
|
||||||
pm.txSub = pm.txpool.SubscribeTxPreEvent(pm.txCh)
|
pm.txSub = pm.txpool.SubscribeTxPreEvent(pm.txCh)
|
||||||
|
|
||||||
pm.orderTxCh = make(chan core.OrderTxPreEvent, txChanSize)
|
pm.orderTxCh = make(chan core.OrderTxPreEvent, txChanSize)
|
||||||
if pm.orderpool != nil {
|
if pm.orderpool != nil {
|
||||||
pm.orderTxSub = pm.orderpool.SubscribeTxPreEvent(pm.orderTxCh)
|
pm.orderTxSub = pm.orderpool.SubscribeTxPreEvent(pm.orderTxCh)
|
||||||
|
|
@ -808,6 +846,63 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if pm.lendingpool != nil {
|
if pm.lendingpool != nil {
|
||||||
pm.lendingpool.AddRemotes(txs)
|
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:
|
default:
|
||||||
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
|
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))
|
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
|
// OrderBroadcastTx will propagate a transaction to all peers which are not known to
|
||||||
// already have the given transaction.
|
// already have the given transaction.
|
||||||
func (pm *ProtocolManager) OrderBroadcastTx(hash common.Hash, tx *types.OrderTransaction) {
|
func (pm *ProtocolManager) OrderBroadcastTx(hash common.Hash, tx *types.OrderTransaction) {
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,14 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,13 @@ package eth
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"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)
|
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)
|
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)
|
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
|
handshakeTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -66,10 +69,15 @@ type peer struct {
|
||||||
td *big.Int
|
td *big.Int
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
||||||
knownTxs mapset.Set // Set of transaction 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
|
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
|
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
|
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 {
|
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(),
|
knownBlocks: mapset.NewSet(),
|
||||||
knownOrderTxs: mapset.NewSet(),
|
knownOrderTxs: mapset.NewSet(),
|
||||||
knownLendingTxs: 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)
|
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
|
// SendTransactions sends transactions to the peer and includes the hashes
|
||||||
// in its transaction hash set for future reference.
|
// in its transaction hash set for future reference.
|
||||||
func (p *peer) SendTransactions(txs types.Transactions) error {
|
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
|
// RequestOneHeader is a wrapper around the header query functions to fetch a
|
||||||
// single header. It is used solely by the fetcher.
|
// single header. It is used solely by the fetcher.
|
||||||
func (p *peer) RequestOneHeader(hash common.Hash) error {
|
func (p *peer) RequestOneHeader(hash common.Hash) error {
|
||||||
|
|
@ -486,6 +571,51 @@ func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer {
|
||||||
return list
|
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
|
// PeersWithoutTx retrieves a list of peers that do not have a given transaction
|
||||||
// in their set of known hashes.
|
// in their set of known hashes.
|
||||||
func (ps *peerSet) OrderPeersWithoutTx(hash common.Hash) []*peer {
|
func (ps *peerSet) OrderPeersWithoutTx(hash common.Hash) []*peer {
|
||||||
|
|
|
||||||
|
|
@ -30,18 +30,19 @@ import (
|
||||||
|
|
||||||
// Constants to match up protocol versions and messages
|
// Constants to match up protocol versions and messages
|
||||||
const (
|
const (
|
||||||
eth62 = 62
|
eth62 = 62
|
||||||
eth63 = 63
|
eth63 = 63
|
||||||
|
xdpos2 = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
// Official short name of the protocol used during capability negotiation.
|
// Official short name of the protocol used during capability negotiation.
|
||||||
var ProtocolName = "eth"
|
var ProtocolName = "eth"
|
||||||
|
|
||||||
// Supported versions of the eth protocol (first is primary).
|
// 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.
|
// 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
|
const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
|
||||||
|
|
||||||
|
|
@ -63,6 +64,11 @@ const (
|
||||||
NodeDataMsg = 0x0e
|
NodeDataMsg = 0x0e
|
||||||
GetReceiptsMsg = 0x0f
|
GetReceiptsMsg = 0x0f
|
||||||
ReceiptsMsg = 0x10
|
ReceiptsMsg = 0x10
|
||||||
|
|
||||||
|
// Protocol messages belonging to xdpos2/100
|
||||||
|
VoteMsg = 0xe0
|
||||||
|
TimeoutMsg = 0xe1
|
||||||
|
SyncInfoMsg = 0xe2
|
||||||
)
|
)
|
||||||
|
|
||||||
type errCode int
|
type errCode int
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,9 @@ func (pm *ProtocolManager) txsyncLoop() {
|
||||||
func (pm *ProtocolManager) syncer() {
|
func (pm *ProtocolManager) syncer() {
|
||||||
// Start and ensure cleanup of sync mechanisms
|
// Start and ensure cleanup of sync mechanisms
|
||||||
pm.fetcher.Start()
|
pm.fetcher.Start()
|
||||||
|
pm.bft.Start()
|
||||||
defer pm.fetcher.Stop()
|
defer pm.fetcher.Stop()
|
||||||
|
defer pm.bft.Stop()
|
||||||
defer pm.downloader.Terminate()
|
defer pm.downloader.Terminate()
|
||||||
|
|
||||||
// Wait for different events to fire synchronisation operations
|
// Wait for different events to fire synchronisation operations
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/mclock"
|
"github.com/XinFinOrg/XDPoSChain/common/mclock"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/eth"
|
"github.com/XinFinOrg/XDPoSChain/eth"
|
||||||
|
|
@ -56,6 +57,12 @@ const (
|
||||||
chainHeadChanSize = 10
|
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 {
|
type txPool interface {
|
||||||
// SubscribeTxPreEvent should return an event subscription of
|
// SubscribeTxPreEvent should return an event subscription of
|
||||||
// TxPreEvent and send events to the given channel.
|
// TxPreEvent and send events to the given channel.
|
||||||
|
|
@ -140,9 +147,11 @@ func (s *Service) loop() {
|
||||||
// Subscribe to chain events to execute updates on
|
// Subscribe to chain events to execute updates on
|
||||||
var blockchain blockChain
|
var blockchain blockChain
|
||||||
var txpool txPool
|
var txpool txPool
|
||||||
|
var engine consensusEngine
|
||||||
if s.eth != nil {
|
if s.eth != nil {
|
||||||
blockchain = s.eth.BlockChain()
|
blockchain = s.eth.BlockChain()
|
||||||
txpool = s.eth.TxPool()
|
txpool = s.eth.TxPool()
|
||||||
|
engine = s.eth.Engine().(*XDPoS.XDPoS)
|
||||||
} else {
|
} else {
|
||||||
blockchain = s.les.BlockChain()
|
blockchain = s.les.BlockChain()
|
||||||
txpool = s.les.TxPool()
|
txpool = s.les.TxPool()
|
||||||
|
|
@ -156,11 +165,19 @@ func (s *Service) loop() {
|
||||||
txSub := txpool.SubscribeTxPreEvent(txEventCh)
|
txSub := txpool.SubscribeTxPreEvent(txEventCh)
|
||||||
defer txSub.Unsubscribe()
|
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
|
// Start a goroutine that exhausts the subsciptions to avoid events piling up
|
||||||
var (
|
var (
|
||||||
quitCh = make(chan struct{})
|
quitCh = make(chan struct{})
|
||||||
headCh = make(chan *types.Block, 1)
|
headCh = make(chan *types.Block, 1)
|
||||||
txCh = make(chan struct{}, 1)
|
txCh = make(chan struct{}, 1)
|
||||||
|
forensicsCh = make(chan *types.ForensicProof, 1)
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
var lastTx mclock.AbsTime
|
var lastTx mclock.AbsTime
|
||||||
|
|
@ -168,6 +185,11 @@ func (s *Service) loop() {
|
||||||
HandleLoop:
|
HandleLoop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case forensics := <-forensicsEventCh:
|
||||||
|
select {
|
||||||
|
case forensicsCh <- forensics.ForensicsProof:
|
||||||
|
default:
|
||||||
|
}
|
||||||
// Notify of chain head events, but drop if too frequent
|
// Notify of chain head events, but drop if too frequent
|
||||||
case head := <-chainHeadCh:
|
case head := <-chainHeadCh:
|
||||||
select {
|
select {
|
||||||
|
|
@ -268,6 +290,10 @@ func (s *Service) loop() {
|
||||||
if err = s.reportPending(conn); err != nil {
|
if err = s.reportPending(conn); err != nil {
|
||||||
log.Warn("Transaction stats report failed", "err", err)
|
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
|
// Make sure the connection is closed
|
||||||
|
|
@ -485,6 +511,13 @@ type blockStats struct {
|
||||||
Uncles uncleStats `json:"uncles"`
|
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.
|
// txStats is the information to report about individual transactions.
|
||||||
type txStats struct {
|
type txStats struct {
|
||||||
Hash common.Hash `json:"hash"`
|
Hash common.Hash `json:"hash"`
|
||||||
|
|
@ -513,12 +546,37 @@ func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
|
||||||
"id": s.node,
|
"id": s.node,
|
||||||
"block": details,
|
"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{}{
|
report := map[string][]interface{}{
|
||||||
"emit": {"block", stats},
|
"emit": {"block", stats},
|
||||||
}
|
}
|
||||||
return websocket.JSON.Send(conn, report)
|
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
|
// assembleBlockStats retrieves any required metadata to report a single block
|
||||||
// and assembles the block stats. If block is nil, the current head is processed.
|
// and assembles the block stats. If block is nil, the current head is processed.
|
||||||
func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
|
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/btcsuite/winsvc v1.0.0 // indirect
|
||||||
github.com/cespare/cp v1.1.1
|
github.com/cespare/cp v1.1.1
|
||||||
github.com/davecgh/go-spew 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/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
|
||||||
github.com/edsrzf/mmap-go v1.0.0
|
github.com/edsrzf/mmap-go v1.0.0
|
||||||
github.com/elastic/gosigar v0.10.5
|
github.com/elastic/gosigar v0.10.5
|
||||||
|
|
@ -63,4 +63,5 @@ require (
|
||||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
|
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
|
||||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
|
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
|
||||||
gopkg.in/urfave/cli.v1 v1.20.0
|
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/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 h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
|
||||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
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/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/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=
|
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/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.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.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/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
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/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 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw=
|
||||||
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA=
|
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/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 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA=
|
||||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
|
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.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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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=
|
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=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,9 @@ var Flags = []cli.Flag{
|
||||||
//vmoduleFlag,
|
//vmoduleFlag,
|
||||||
//backtraceAtFlag,
|
//backtraceAtFlag,
|
||||||
//debugFlag,
|
//debugFlag,
|
||||||
//pprofFlag,
|
// pprofFlag,
|
||||||
//pprofAddrFlag,
|
// pprofAddrFlag,
|
||||||
//pprofPortFlag,
|
// pprofPortFlag,
|
||||||
//memprofilerateFlag,
|
//memprofilerateFlag,
|
||||||
//blockprofilerateFlag,
|
//blockprofilerateFlag,
|
||||||
//cpuprofileFlag,
|
//cpuprofileFlag,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
|
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
"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.
|
// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
|
||||||
// It offers only methods that operate on public data that is freely available to anyone.
|
// It offers only methods that operate on public data that is freely available to anyone.
|
||||||
type PublicBlockChainAPI struct {
|
type PublicBlockChainAPI struct {
|
||||||
b Backend
|
b Backend
|
||||||
|
chainReader consensus.ChainReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicBlockChainAPI creates a new Ethereum blockchain API.
|
// NewPublicBlockChainAPI creates a new Ethereum blockchain API.
|
||||||
func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI {
|
func NewPublicBlockChainAPI(b Backend, chainReader consensus.ChainReader) *PublicBlockChainAPI {
|
||||||
return &PublicBlockChainAPI{b}
|
return &PublicBlockChainAPI{
|
||||||
|
b,
|
||||||
|
chainReader,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockNumber returns the block number of the chain head.
|
// 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 {
|
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
||||||
// Get block epoc latest.
|
// Get block epoc latest.
|
||||||
lastCheckpointNumber := prevBlockNumber - (prevBlockNumber % s.b.ChainConfig().XDPoS.Epoch)
|
return engine.GetMasternodesByNumber(s.chainReader, prevBlockNumber), nil
|
||||||
prevCheckpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(lastCheckpointNumber))
|
|
||||||
if prevCheckpointBlock != nil {
|
|
||||||
masternodes = engine.GetMasternodesFromCheckpointHeader(prevCheckpointBlock.Header(), curBlockNumber, s.b.ChainConfig().XDPoS.Epoch)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.Error("Undefined XDPoS consensus engine")
|
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
|
// Second, Find candidates that have masternode status
|
||||||
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
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 {
|
if len(masternodes) == 0 {
|
||||||
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
|
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
|
||||||
result[fieldSuccess] = false
|
result[fieldSuccess] = false
|
||||||
|
|
@ -902,7 +903,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
|
||||||
|
|
||||||
// Second, Find candidates that have masternode status
|
// Second, Find candidates that have masternode status
|
||||||
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
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 {
|
if len(masternodes) == 0 {
|
||||||
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
|
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
|
||||||
result[fieldSuccess] = false
|
result[fieldSuccess] = false
|
||||||
|
|
@ -965,23 +966,28 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
|
||||||
// GetPreviousCheckpointFromEpoch returns header of the previous checkpoint
|
// GetPreviousCheckpointFromEpoch returns header of the previous checkpoint
|
||||||
func (s *PublicBlockChainAPI) GetPreviousCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) {
|
func (s *PublicBlockChainAPI) GetPreviousCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) {
|
||||||
var checkpointNumber uint64
|
var checkpointNumber uint64
|
||||||
epoch := s.b.ChainConfig().XDPoS.Epoch
|
|
||||||
|
|
||||||
if epochNum == rpc.LatestEpochNumber {
|
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
|
||||||
blockNumer := s.b.CurrentBlock().Number().Uint64()
|
currentCheckpointNumber, epochNumber, err := engine.GetCurrentEpochSwitchBlock(s.chainReader, s.b.CurrentBlock().Number())
|
||||||
diff := blockNumer % epoch
|
if err != nil {
|
||||||
// checkpoint number
|
log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get current epoch switch block information", "Block", s.b.CurrentBlock(), "Error", err)
|
||||||
checkpointNumber = blockNumer - diff
|
|
||||||
epochNum = rpc.EpochNumber(checkpointNumber / epoch)
|
|
||||||
if diff > 0 {
|
|
||||||
epochNum += 1
|
|
||||||
}
|
}
|
||||||
} else if epochNum < 2 {
|
if epochNum == rpc.LatestEpochNumber {
|
||||||
checkpointNumber = 0
|
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 {
|
} 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
|
// 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
|
// 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))
|
checkpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber))
|
||||||
|
|
||||||
if checkpointBlock != nil {
|
if checkpointBlock != nil {
|
||||||
|
|
@ -1385,13 +1395,9 @@ func (s *PublicBlockChainAPI) getSigners(ctx context.Context, block *types.Block
|
||||||
var err error
|
var err error
|
||||||
var filterSigners []common.Address
|
var filterSigners []common.Address
|
||||||
var signers []common.Address
|
var signers []common.Address
|
||||||
blockNumber := block.Number().Uint64()
|
|
||||||
|
|
||||||
// Get block epoc latest.
|
masternodes := engine.GetMasternodes(s.chainReader, block.Header())
|
||||||
checkpointNumber := blockNumber - (blockNumber % s.b.ChainConfig().XDPoS.Epoch)
|
|
||||||
checkpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber))
|
|
||||||
|
|
||||||
masternodes := engine.GetMasternodesFromCheckpointHeader(checkpointBlock.Header(), blockNumber, s.b.ChainConfig().XDPoS.Epoch)
|
|
||||||
signers, err = GetSignersFromBlocks(s.b, block.NumberU64(), block.Hash(), masternodes)
|
signers, err = GetSignersFromBlocks(s.b, block.NumberU64(), block.Hash(), masternodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Fail to get signers from block signer SC.", "error", err)
|
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)
|
GetOrderNonce(address common.Hash) (uint64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAPIs(apiBackend Backend) []rpc.API {
|
func GetAPIs(apiBackend Backend, chainReader consensus.ChainReader) []rpc.API {
|
||||||
nonceLock := new(AddrLocker)
|
nonceLock := new(AddrLocker)
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
|
|
@ -108,7 +108,7 @@ func GetAPIs(apiBackend Backend) []rpc.API {
|
||||||
}, {
|
}, {
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: NewPublicBlockChainAPI(apiBackend),
|
Service: NewPublicBlockChainAPI(apiBackend, chainReader),
|
||||||
Public: true,
|
Public: true,
|
||||||
}, {
|
}, {
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,10 @@ web3._extend({
|
||||||
call: 'XDPoS_getSignersAtHash',
|
call: 'XDPoS_getSignersAtHash',
|
||||||
params: 1
|
params: 1
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getLatestCommittedBlockInfo',
|
||||||
|
call: 'XDPoS_getLatestCommittedBlockHeader'
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
properties: [
|
properties: [
|
||||||
new web3._extend.Property({
|
new web3._extend.Property({
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ func (s *LightDummyAPI) Mining() bool {
|
||||||
// APIs returns the collection of RPC services the ethereum package offers.
|
// APIs returns the collection of RPC services the ethereum package offers.
|
||||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||||
func (s *LightEthereum) APIs() []rpc.API {
|
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",
|
Namespace: "eth",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,13 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
|
|
@ -205,7 +206,7 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protoco
|
||||||
}
|
}
|
||||||
|
|
||||||
if lightSync {
|
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.peers.notify((*downloaderPeerNotify)(manager))
|
||||||
manager.fetcher = newLightFetcher(manager)
|
manager.fetcher = newLightFetcher(manager)
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +219,11 @@ func (pm *ProtocolManager) removePeer(id string) {
|
||||||
pm.peers.Unregister(id)
|
pm.peers.Unregister(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// an empty handleProposedBlock function
|
||||||
|
func (pm *ProtocolManager) handleProposedBlock(header *types.Header) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (pm *ProtocolManager) Start(maxPeers int) {
|
func (pm *ProtocolManager) Start(maxPeers int) {
|
||||||
pm.maxPeers = maxPeers
|
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"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
"github.com/XinFinOrg/XDPoSChain/metrics"
|
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -50,6 +51,19 @@ func ExpHandler(r metrics.Registry) http.Handler {
|
||||||
return http.HandlerFunc(e.expHandler)
|
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 {
|
func (exp *exp) getInt(name string) *expvar.Int {
|
||||||
var v *expvar.Int
|
var v *expvar.Int
|
||||||
exp.expvarLock.Lock()
|
exp.expvarLock.Lock()
|
||||||
|
|
@ -111,7 +125,7 @@ func (exp *exp) publishMeter(name string, metric metrics.Meter) {
|
||||||
exp.getInt(name + ".count").Set(m.Count())
|
exp.getInt(name + ".count").Set(m.Count())
|
||||||
exp.getFloat(name + ".one-minute").Set(m.Rate1())
|
exp.getFloat(name + ".one-minute").Set(m.Rate1())
|
||||||
exp.getFloat(name + ".five-minute").Set(m.Rate5())
|
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())
|
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())
|
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() {
|
func (exp *exp) syncToExpvar() {
|
||||||
exp.registry.Each(func(name string, i interface{}) {
|
exp.registry.Each(func(name string, i interface{}) {
|
||||||
switch i.(type) {
|
switch i := i.(type) {
|
||||||
case metrics.Counter:
|
case metrics.Counter:
|
||||||
exp.publishCounter(name, i.(metrics.Counter))
|
exp.publishCounter(name, i)
|
||||||
case metrics.Gauge:
|
case metrics.Gauge:
|
||||||
exp.publishGauge(name, i.(metrics.Gauge))
|
exp.publishGauge(name, i)
|
||||||
case metrics.GaugeFloat64:
|
case metrics.GaugeFloat64:
|
||||||
exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64))
|
exp.publishGaugeFloat64(name, i)
|
||||||
case metrics.Histogram:
|
case metrics.Histogram:
|
||||||
exp.publishHistogram(name, i.(metrics.Histogram))
|
exp.publishHistogram(name, i)
|
||||||
case metrics.Meter:
|
case metrics.Meter:
|
||||||
exp.publishMeter(name, i.(metrics.Meter))
|
exp.publishMeter(name, i)
|
||||||
case metrics.Timer:
|
case metrics.Timer:
|
||||||
exp.publishTimer(name, i.(metrics.Timer))
|
exp.publishTimer(name, i)
|
||||||
|
case metrics.ResettingTimer:
|
||||||
|
exp.publishResettingTimer(name, i)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
|
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,10 @@ package miner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"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/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
||||||
"github.com/XinFinOrg/XDPoSChain/contracts"
|
"github.com/XinFinOrg/XDPoSChain/contracts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
|
|
@ -59,10 +58,6 @@ const (
|
||||||
chainHeadChanSize = 10
|
chainHeadChanSize = 10
|
||||||
// chainSideChanSize is the size of channel listening to ChainSideEvent.
|
// chainSideChanSize is the size of channel listening to ChainSideEvent.
|
||||||
chainSideChanSize = 10
|
chainSideChanSize = 10
|
||||||
// timeout waiting for M1
|
|
||||||
waitPeriod = 10
|
|
||||||
// timeout for checkpoint.
|
|
||||||
waitPeriodCheckpoint = 20
|
|
||||||
|
|
||||||
txMatchGasLimit = 40000000
|
txMatchGasLimit = 40000000
|
||||||
)
|
)
|
||||||
|
|
@ -270,7 +265,14 @@ func (self *worker) update() {
|
||||||
}
|
}
|
||||||
defer self.chainHeadSub.Unsubscribe()
|
defer self.chainHeadSub.Unsubscribe()
|
||||||
defer self.chainSideSub.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{})
|
c := make(chan struct{})
|
||||||
finish := make(chan struct{})
|
finish := make(chan struct{})
|
||||||
defer close(finish)
|
defer close(finish)
|
||||||
|
|
@ -289,24 +291,31 @@ func (self *worker) update() {
|
||||||
for {
|
for {
|
||||||
// A real event arrived, process interesting content
|
// A real event arrived, process interesting content
|
||||||
select {
|
select {
|
||||||
|
case v := <-WaitPeriodCh:
|
||||||
|
log.Info("[worker] update mine period", "period", v)
|
||||||
|
waitPeriod = v
|
||||||
|
timeout.Reset(time.Duration(waitPeriod) * time.Second)
|
||||||
|
|
||||||
case <-c:
|
case <-c:
|
||||||
if atomic.LoadInt32(&self.mining) == 1 {
|
if atomic.LoadInt32(&self.mining) == 1 {
|
||||||
self.commitNewWork()
|
self.commitNewWork()
|
||||||
}
|
}
|
||||||
timeout.Reset(waitPeriod * time.Second)
|
timeout.Reset(time.Duration(waitPeriod) * time.Second)
|
||||||
// Handle ChainHeadEvent
|
|
||||||
|
// Handle ChainHeadEvent
|
||||||
case <-self.chainHeadCh:
|
case <-self.chainHeadCh:
|
||||||
self.commitNewWork()
|
self.commitNewWork()
|
||||||
timeout.Reset(waitPeriod * time.Second)
|
timeout.Reset(time.Duration(waitPeriod) * time.Second)
|
||||||
|
|
||||||
// Handle ChainSideEvent
|
// Handle ChainSideEvent
|
||||||
case ev := <-self.chainSideCh:
|
case ev := <-self.chainSideCh:
|
||||||
if self.config.XDPoS == nil {
|
if self.config.XDPoS == nil {
|
||||||
self.uncleMu.Lock()
|
self.uncleMu.Lock()
|
||||||
self.possibleUncles[ev.Block.Hash()] = ev.Block
|
self.possibleUncles[ev.Block.Hash()] = ev.Block
|
||||||
self.uncleMu.Unlock()
|
self.uncleMu.Unlock()
|
||||||
}
|
}
|
||||||
// Handle TxPreEvent
|
|
||||||
|
// Handle TxPreEvent
|
||||||
case ev := <-self.txCh:
|
case ev := <-self.txCh:
|
||||||
// Apply transaction to the pending state if we're not mining
|
// Apply transaction to the pending state if we're not mining
|
||||||
if atomic.LoadInt32(&self.mining) == 0 {
|
if atomic.LoadInt32(&self.mining) == 0 {
|
||||||
|
|
@ -323,8 +332,10 @@ func (self *worker) update() {
|
||||||
self.commitNewWork()
|
self.commitNewWork()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-self.chainHeadSub.Err():
|
case <-self.chainHeadSub.Err():
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-self.chainSideSub.Err():
|
case <-self.chainSideSub.Err():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -381,7 +392,11 @@ func (self *worker) wait() {
|
||||||
}
|
}
|
||||||
if work.config.XDPoS != nil {
|
if work.config.XDPoS != nil {
|
||||||
// epoch block
|
// 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
|
core.CheckpointCh <- 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -397,8 +412,12 @@ func (self *worker) wait() {
|
||||||
|
|
||||||
if self.config.XDPoS != nil {
|
if self.config.XDPoS != nil {
|
||||||
c := self.engine.(*XDPoS.XDPoS)
|
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 {
|
if !authorized {
|
||||||
valid := false
|
valid := false
|
||||||
masternodes := c.GetMasternodes(self.chain, block.Header())
|
masternodes := c.GetMasternodes(self.chain, block.Header())
|
||||||
|
|
@ -416,7 +435,7 @@ func (self *worker) wait() {
|
||||||
// Send tx sign to smart contract blockSigners.
|
// Send tx sign to smart contract blockSigners.
|
||||||
if block.NumberU64()%common.MergeSignRange == 0 || !self.config.IsTIP2019(block.Number()) {
|
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 {
|
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()
|
defer self.currentMu.Unlock()
|
||||||
|
|
||||||
tstart := time.Now()
|
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{}
|
var signers map[common.Address]struct{}
|
||||||
if parent.Hash().Hex() == self.lastParentBlockCommit {
|
if parent.Hash().Hex() == self.lastParentBlockCommit {
|
||||||
return
|
return
|
||||||
|
|
@ -520,39 +547,15 @@ func (self *worker) commitNewWork() {
|
||||||
// Only try to commit new work if we are mining
|
// Only try to commit new work if we are mining
|
||||||
if atomic.LoadInt32(&self.mining) == 1 {
|
if atomic.LoadInt32(&self.mining) == 1 {
|
||||||
// check if we are right after parent's coinbase in the list
|
// check if we are right after parent's coinbase in the list
|
||||||
// only go with XDPoS
|
|
||||||
if self.config.XDPoS != nil {
|
if self.config.XDPoS != nil {
|
||||||
// get masternodes set from latest checkpoint
|
ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
|
||||||
c := self.engine.(*XDPoS.XDPoS)
|
|
||||||
len, preIndex, curIndex, ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed when trying to commit new work", "err", err)
|
log.Warn("Failed when trying to commit new work", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Info("Not my turn to commit block. Waiting...")
|
log.Info("Not my turn to commit block. Waiting...")
|
||||||
// in case some nodes are down
|
return
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -581,6 +584,10 @@ func (self *worker) commitNewWork() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.engine.Prepare(self.chain, header); err != nil {
|
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)
|
log.Error("Failed to prepare header for new block", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -626,13 +633,19 @@ func (self *worker) commitNewWork() {
|
||||||
lendingFinalizedTradeTransaction *types.Transaction
|
lendingFinalizedTradeTransaction *types.Transaction
|
||||||
)
|
)
|
||||||
feeCapacity := state.GetTRC21FeeCapacityFromStateWithCache(parent.Root(), work.state)
|
feeCapacity := state.GetTRC21FeeCapacityFromStateWithCache(parent.Root(), work.state)
|
||||||
if self.config.XDPoS != nil && header.Number.Uint64()%self.config.XDPoS.Epoch != 0 {
|
if self.config.XDPoS != nil {
|
||||||
pending, err := self.eth.TxPool().Pending()
|
isEpochSwitchBlock, _, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to fetch pending transactions", "err", err)
|
log.Error("[commitNewWork] fail to check if block is epoch switch block when fetching pending transactions", "BlockNum", header.Number, "Hash", header.Hash())
|
||||||
return
|
}
|
||||||
|
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 {
|
if atomic.LoadInt32(&self.mining) == 1 {
|
||||||
wallet, err := self.eth.AccountManager().Find(accounts.Account{Address: self.coinbase})
|
wallet, err := self.eth.AccountManager().Find(accounts.Account{Address: self.coinbase})
|
||||||
|
|
@ -644,16 +657,20 @@ func (self *worker) commitNewWork() {
|
||||||
XDCX := self.eth.GetXDCX()
|
XDCX := self.eth.GetXDCX()
|
||||||
XDCXLending := self.eth.GetXDCXLending()
|
XDCXLending := self.eth.GetXDCXLending()
|
||||||
if XDCX != nil && header.Number.Uint64() > self.config.XDPoS.Epoch {
|
if XDCX != nil && header.Number.Uint64() > self.config.XDPoS.Epoch {
|
||||||
if header.Number.Uint64()%self.config.XDPoS.Epoch == 0 {
|
isEpochSwitchBlock, epochNumber, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(header)
|
||||||
err := XDCX.UpdateMediumPriceBeforeEpoch(header.Number.Uint64()/self.config.XDPoS.Epoch, work.tradingState, work.state)
|
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 {
|
if err != nil {
|
||||||
log.Error("Fail when update medium price last epoch", "error", err)
|
log.Error("Fail when update medium price last epoch", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
// won't grasp tx at checkpoint
|
// won't grasp tx at checkpoint
|
||||||
//https://github.com/XinFinOrg/XDPoSChain-v1/pull/416
|
//https://github.com/XinFinOrg/XDPoSChain-v1/pull/416
|
||||||
if header.Number.Uint64()%self.config.XDPoS.Epoch != 0 {
|
|
||||||
log.Debug("Start processing order pending")
|
log.Debug("Start processing order pending")
|
||||||
tradingOrderPending, _ := self.eth.OrderPool().Pending()
|
tradingOrderPending, _ := self.eth.OrderPool().Pending()
|
||||||
log.Debug("Start processing order pending", "len", len(tradingOrderPending))
|
log.Debug("Start processing order pending", "len", len(tradingOrderPending))
|
||||||
|
|
@ -671,6 +688,7 @@ func (self *worker) commitNewWork() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tradingTxMatches) > 0 {
|
if len(tradingTxMatches) > 0 {
|
||||||
txMatchBatch := &tradingstate.TxMatchBatch{
|
txMatchBatch := &tradingstate.TxMatchBatch{
|
||||||
Data: tradingTxMatches,
|
Data: tradingTxMatches,
|
||||||
|
|
@ -1053,10 +1071,16 @@ func (env *Work) commitTransactions(mux *event.TypeMux, balanceFee map[common.Ad
|
||||||
}
|
}
|
||||||
go func(logs []*types.Log, tcount int) {
|
go func(logs []*types.Log, tcount int) {
|
||||||
if len(logs) > 0 {
|
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 {
|
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)
|
}(cpy, env.tcount)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
122
params/config.go
122
params/config.go
|
|
@ -29,15 +29,42 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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
|
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 (
|
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
|
// XDPoSChain mainnet config
|
||||||
XDCMainnetChainConfig = &ChainConfig{
|
XDCMainnetChainConfig = &ChainConfig{
|
||||||
ChainId: big.NewInt(88),
|
ChainId: big.NewInt(50),
|
||||||
HomesteadBlock: big.NewInt(1),
|
HomesteadBlock: big.NewInt(1),
|
||||||
EIP150Block: big.NewInt(2),
|
EIP150Block: big.NewInt(2),
|
||||||
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
|
@ -47,10 +74,11 @@ var (
|
||||||
XDPoS: &XDPoSConfig{
|
XDPoS: &XDPoSConfig{
|
||||||
Period: 2,
|
Period: 2,
|
||||||
Epoch: 900,
|
Epoch: 900,
|
||||||
Reward: 250,
|
Reward: 5000,
|
||||||
RewardCheckpoint: 900,
|
RewardCheckpoint: 900,
|
||||||
Gap: 5,
|
Gap: 450,
|
||||||
FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068"),
|
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 contains the chain parameters to run a node on the Ropsten test network.
|
||||||
TestnetChainConfig = &ChainConfig{
|
TestnetChainConfig = &ChainConfig{
|
||||||
ChainId: big.NewInt(3),
|
ChainId: big.NewInt(51),
|
||||||
HomesteadBlock: big.NewInt(0),
|
HomesteadBlock: big.NewInt(1),
|
||||||
DAOForkBlock: nil,
|
DAOForkBlock: nil,
|
||||||
DAOForkSupport: true,
|
DAOForkSupport: false,
|
||||||
EIP150Block: big.NewInt(0),
|
EIP150Block: big.NewInt(2),
|
||||||
EIP150Hash: common.HexToHash("0x62e0fde86e34c263e250fbcd5ca4598ba8ca10a1d166c8526bb127e10b313311"),
|
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
EIP155Block: big.NewInt(10),
|
EIP155Block: big.NewInt(3),
|
||||||
EIP158Block: big.NewInt(10),
|
EIP158Block: big.NewInt(3),
|
||||||
ByzantiumBlock: big.NewInt(1700000),
|
ByzantiumBlock: big.NewInt(4),
|
||||||
ConstantinopleBlock: nil,
|
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.
|
// RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network.
|
||||||
|
|
@ -99,6 +155,7 @@ var (
|
||||||
XDPoS: &XDPoSConfig{
|
XDPoS: &XDPoSConfig{
|
||||||
Period: 15,
|
Period: 15,
|
||||||
Epoch: 30000,
|
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}}
|
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}
|
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 with v2 engine after block 901
|
||||||
// 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, SkipV1Validation: true, V2: TestXDPoSV2Config, FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068"), Reward: 250}}
|
||||||
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)}}
|
|
||||||
|
|
||||||
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}
|
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))
|
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.
|
// XDPoSConfig is the consensus engine configs for delegated-proof-of-stake based sealing.
|
||||||
type XDPoSConfig struct {
|
type XDPoSConfig struct {
|
||||||
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
||||||
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
|
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
|
||||||
Reward uint64 `json:"reward"` // Block reward - unit Ether
|
Reward uint64 `json:"reward"` // Block reward - unit Ether
|
||||||
RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards.
|
RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards.
|
||||||
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
|
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
|
||||||
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
|
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
|
||||||
SkipValidation bool //Skip Block Validation for testing purpose
|
SkipV1Validation bool //Skip Block Validation for testing purpose, V1 consensus only
|
||||||
V2ConsensusBlockNumber *big.Int
|
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.
|
// 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.
|
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 {
|
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 ConsensusEngineVersion2
|
||||||
}
|
}
|
||||||
return ConsensusEngineVersion1
|
return ConsensusEngineVersion1
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 2 // Major version component of the current release
|
||||||
VersionMinor = 4 // Minor version component of the current release
|
VersionMinor = 0 // Minor version component of the current release
|
||||||
VersionPatch = 4 // Patch version component of the current release
|
VersionPatch = 0 // Patch version component of the current release
|
||||||
VersionMeta = "stable" // Version metadata to append to the version string
|
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version holds the textual 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