Merge pull request #169 from XinFinOrg/v2-beta

- V2 consensus engine
This commit is contained in:
Jerome 2022-08-06 20:36:23 +08:00 committed by GitHub
commit 22479f11ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 10111 additions and 32486 deletions

View file

@ -1 +1,51 @@
# XDPoSChain
## XinFin Hybrid Blockchain
XinFin Hybrid Blockchain is an Enterprise ready Blockchain for global trade and finance
Visit: [XinFin.org](https://xinfin.org)
Contribute: [Developer Docs](https://docs.xinfin.org)
## XinFin Network XDPoS is community driven project to achieve the following
- XinFin DPOS (XDPoS) consensus that selects 108 set of Masternodes to achieve a high throughput Energy efficient consensus with instant block finality
- KYC Enforcement on Masternodes for Enterprise Adoption and compliance
- Ability to port/relay limited set of data and transactions from privacy channels to public channel
- Interoperability between applications hosted on Private Blockchains like Corda, Hyperledger, Quorum(JP Morgan) using relayers to XinFin Network
- Customer Centric and consortium driven Governance to equally benefit the validators as well as providing comfort for large scale enterprise applications to be hosted on the Network. This achieves
- Rapid Upgradability
- DApps Standardisation for rapid commercialisation
- Compliance with major global jurisdictions.
### KYC for masternodes
#### OVERVIEW
To add a layer of KYC for masternodes in the current system and a sense of ownership amongst the masternodes hence tying such a cluster of masternodes to physical entity which can held accountable for its actions.
#### Design
We established a bidirectional connection between a candidate and its owner inorder to retrieve a candidate belonging to a specific owner & vice versa.
All the masternodes are recognized by the KYC of their owners and hence are considered as a single verified entity ( for eg. while voting for invalid KYC, only one vote is considered per such cluster )
The contract is very strict in handing out penalty for invalid KYC, it results loss of all funds invested in all of its candidates.
For eg. say A proposes condidates B,C,D by paying for its proposal cost.
If at a later stage if some predecided amount of owners ( investors ) vote that a KYC for a A is invalid then A & all of its candidates (B,C,D) will lose their position & all their funds will be lost ( will remain with contract wallet ).
### For developers
### To contribute
Simple create a pull request along with proper reasoning, we'll get back to you.
Our Channels : [Telegram Developer Group](https://t.me/XinFinDevelopers) or [Public Slack Group](https://launchpass.com/xinfin-public)

View file

@ -20,7 +20,9 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"math/big"
"os"
"sync"
"time"
@ -29,7 +31,9 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
@ -69,6 +73,28 @@ type SimulatedBackend struct {
config *params.ChainConfig
}
func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) {
veryLightScryptN := 2
veryLightScryptP := 1
dir, _ := ioutil.TempDir("", "eth-SimulateWalletAddressAndSignFn-test")
new := func(kd string) *keystore.KeyStore {
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
}
defer os.RemoveAll(dir)
ks := new(dir)
pass := "" // not used but required by API
a1, err := ks.NewAccount(pass)
if err != nil {
return common.Address{}, nil, fmt.Errorf(err.Error())
}
if err := ks.Unlock(a1, ""); err != nil {
return a1.Address, nil, fmt.Errorf(err.Error())
}
return a1.Address, ks.SignHash, nil
}
// XDC simulated backend for testing purpose.
func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
// database := ethdb.NewMemDatabase()

View file

@ -19,13 +19,15 @@ package main
import (
"encoding/json"
"fmt"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"os"
"runtime"
"strconv"
"sync/atomic"
"time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/metrics"
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/console"
@ -207,6 +209,12 @@ func importChain(ctx *cli.Context) error {
if len(ctx.Args()) < 1 {
utils.Fatalf("This command requires an argument.")
}
// Start metrics export if enabled
utils.SetupMetrics(ctx)
// Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second)
stack, _ := makeFullNode(ctx)
chain, chainDb := utils.MakeChain(ctx, stack)
defer chainDb.Close()
@ -368,7 +376,7 @@ func copyDb(ctx *cli.Context) error {
chain, chainDb := utils.MakeChain(ctx, stack)
syncmode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode)
dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil)
dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil, nil)
// Create a source peer to satisfy downloader requests from
db, err := rawdb.NewLevelDBDatabase(ctx.Args().First(), ctx.GlobalInt(utils.CacheFlag.Name), 256, "")

View file

@ -52,7 +52,7 @@ func TestConsoleWelcome(t *testing.T) {
XDC.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
XDC.SetTemplateFunc("gover", runtime.Version)
XDC.SetTemplateFunc("XDCver", func() string { return params.Version })
XDC.SetTemplateFunc("niltime", func() string { return time.Unix(1544771829, 0).Format(time.RFC1123) })
XDC.SetTemplateFunc("niltime", func() string { return time.Unix(1559211559, 0).Format(time.RFC1123) })
XDC.SetTemplateFunc("apis", func() string { return ipcAPIs })
// Verify the actual welcome message to the required template
@ -137,7 +137,7 @@ func testAttachWelcome(t *testing.T, XDC *testXDC, endpoint, apis string) {
attach.SetTemplateFunc("gover", runtime.Version)
attach.SetTemplateFunc("XDCver", func() string { return params.Version })
attach.SetTemplateFunc("etherbase", func() string { return XDC.Etherbase })
attach.SetTemplateFunc("niltime", func() string { return time.Unix(1544771829, 0).Format(time.RFC1123) })
attach.SetTemplateFunc("niltime", func() string { return time.Unix(1559211559, 0).Format(time.RFC1123) })
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
attach.SetTemplateFunc("datadir", func() string { return XDC.Datadir })
attach.SetTemplateFunc("apis", func() string { return apis })

View file

@ -119,6 +119,8 @@ var (
utils.RPCVirtualHostsFlag,
utils.EthStatsURLFlag,
utils.MetricsEnabledFlag,
utils.MetricsHTTPFlag,
utils.MetricsPortFlag,
//utils.FakePoWFlag,
//utils.NoCompactionFlag,
//utils.GpoBlocksFlag,
@ -290,6 +292,11 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
utils.Fatalf("Light clients do not support staking")
}
// Start metrics export if enabled
utils.SetupMetrics(ctx)
// Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second)
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil {
utils.Fatalf("Ethereum service not running: %v", err)

View file

@ -45,6 +45,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics"
"github.com/XinFinOrg/XDPoSChain/metrics/exp"
"github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
@ -355,6 +356,20 @@ var (
Name: "ethstats",
Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)",
}
// MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint.
// Since the pprof service enables sensitive/vulnerable behavior, this allows a user
// to enable a public-OK metrics endpoint without having to worry about ALSO exposing
// other profiling behavior or information.
MetricsHTTPFlag = cli.StringFlag{
Name: "metrics.addr",
Usage: "Enable stand-alone metrics HTTP server listening interface",
Value: metrics.DefaultConfig.HTTP,
}
MetricsPortFlag = cli.IntFlag{
Name: "metrics.port",
Usage: "Metrics HTTP server listening port",
Value: metrics.DefaultConfig.Port,
}
MetricsEnabledFlag = cli.BoolFlag{
Name: metrics.MetricsEnabledFlag,
Usage: "Enable metrics collection and reporting",
@ -1330,3 +1345,15 @@ func WalkMatch(root, pattern string) ([]string, error) {
}
return matches, nil
}
func SetupMetrics(ctx *cli.Context) {
if metrics.Enabled {
log.Info("Enabling metrics collection")
if ctx.GlobalIsSet(MetricsHTTPFlag.Name) {
address := fmt.Sprintf("%s:%d", ctx.GlobalString(MetricsHTTPFlag.Name), ctx.GlobalInt(MetricsPortFlag.Name))
log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address)
exp.Setup(address)
}
}
}

View file

@ -17,6 +17,7 @@ const (
MaxMasternodes = 18
MaxMasternodesV2 = 108
LimitPenaltyEpoch = 4
LimitPenaltyEpochV2 = 0
BlocksPerYearTest = uint64(200000)
BlocksPerYear = uint64(15768000)
LimitThresholdNonceInQueue = 10

View 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
}

View 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
}

View file

@ -279,19 +279,23 @@ func (a UnprefixedAddress) MarshalText() ([]byte, error) {
// Extract validators from byte array.
func RemoveItemFromArray(array []Address, items []Address) []Address {
// Create newArray to stop append change array value
newArray := make([]Address, len(array))
copy(newArray, array)
if len(items) == 0 {
return array
return newArray
}
for _, item := range items {
for i := len(array) - 1; i >= 0; i-- {
if array[i] == item {
array = append(array[:i], array[i+1:]...)
for i := len(newArray) - 1; i >= 0; i-- {
if newArray[i] == item {
newArray = append(newArray[:i], newArray[i+1:]...)
}
}
}
return array
return newArray
}
// Extract validators from byte array.

View file

@ -158,8 +158,12 @@ func BenchmarkAddressHex(b *testing.B) {
func TestRemoveItemInArray(t *testing.T) {
array := []Address{HexToAddress("0x0000003"), HexToAddress("0x0000001"), HexToAddress("0x0000002"), HexToAddress("0x0000003")}
remove := []Address{HexToAddress("0x0000002"), HexToAddress("0x0000004"), HexToAddress("0x0000003")}
array = RemoveItemFromArray(array, remove)
if len(array) != 1 {
newArray := RemoveItemFromArray(array, remove)
if array[0] != HexToAddress("0x0000003") || array[2] != HexToAddress("0x0000002") {
t.Error("should keep the original item from array address")
}
if len(newArray) != 1 {
t.Error("fail remove item from array address")
}
}

View file

@ -24,6 +24,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/consensus/clique"
"github.com/XinFinOrg/XDPoSChain/core/state"
@ -35,8 +36,13 @@ import (
lru "github.com/hashicorp/golang-lru"
)
func SigHash(header *types.Header) (hash common.Hash) {
return utils.SigHash(header)
func (x *XDPoS) SigHash(header *types.Header) (hash common.Hash) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.SignHash(header)
default: // Default "v1"
return x.EngineV1.SigHash(header)
}
}
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
@ -48,34 +54,53 @@ type XDPoS struct {
// Transaction cache, only make sense for adaptor level
signingTxsCache *lru.Cache
// Share Channel
WaitPeriodCh chan int // Miner wait Period Channel
// Trading and lending service
GetXDCXService func() utils.TradingService
GetLendingService func() utils.LendingService
// The exact consensus engine with different versions
EngineV1 engine_v1.XDPoS_v1
EngineV2 engine_v2.XDPoS_v2
EngineV1 *engine_v1.XDPoS_v1
EngineV2 *engine_v2.XDPoS_v2
}
// Subscribe to consensus engines forensics events. Currently only exist for engine v2
func (x *XDPoS) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription {
return x.EngineV2.ForensicsProcessor.SubscribeForensicsEvent(ch)
}
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
// signers set to the ones provided by the user.
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS {
log.Info("[New] initial conensus engines")
// Set any missing consensus parameters to their defaults
conf := *config
if conf.Epoch == 0 {
conf.Epoch = utils.EpochLength
if config.Epoch == 0 {
config.Epoch = utils.EpochLength
}
// For testing and testing project, default to mainnet config
if config.V2 == nil {
config.V2 = params.XDPoSV2Config
}
log.Info("xdc config loading", "config", config)
waitPeriodCh := make(chan int)
// Allocate the snapshot caches and create the engine
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
return &XDPoS{
config: &conf,
config: config,
db: db,
WaitPeriodCh: waitPeriodCh,
signingTxsCache: signingTxsCache,
EngineV1: *engine_v1.New(&conf, db),
EngineV2: *engine_v2.New(&conf, db),
EngineV1: engine_v1.New(config, db),
EngineV2: engine_v2.New(config, db, waitPeriodCh),
}
}
@ -89,6 +114,8 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
conf = chainConfig.XDPoS
}
waitPeriodCh := make(chan int)
// Allocate the snapshot caches and create the engine
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
@ -96,13 +123,27 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
config: conf,
db: db,
WaitPeriodCh: waitPeriodCh,
GetXDCXService: func() utils.TradingService { return nil },
GetLendingService: func() utils.LendingService { return nil },
signingTxsCache: signingTxsCache,
EngineV1: *engine_v1.NewFaker(db, conf),
EngineV2: *engine_v2.NewFaker(db, conf),
EngineV1: engine_v1.NewFaker(db, conf),
EngineV2: engine_v2.New(conf, db, waitPeriodCh),
}
return fakeEngine
}
func (x *XDPoS) Initial(chain consensus.ChainReader, header *types.Header) error {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.Initial(chain, header)
default: // Default "v1"
return nil
}
}
/*
Eth Consensus engine interface implementation
*/
@ -131,6 +172,8 @@ func (x *XDPoS) Author(header *types.Header) (common.Address, error) {
// VerifyHeader checks whether a header conforms to the consensus rules.
func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.VerifyHeader(chain, header, fullVerify)
default: // Default "v1"
return x.EngineV1.VerifyHeader(chain, header, fullVerify)
}
@ -140,14 +183,38 @@ func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header,
// method returns a quit channel to abort the operations and a results channel to
// retrieve the async verifications (the order is that of the input slice).
func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) {
// TODO: (Hashlab) This funciton is a special case
return x.EngineV1.VerifyHeaders(chain, headers, fullVerifies)
abort := make(chan struct{})
results := make(chan error, len(headers))
// Split the headers list into v1 and v2 buckets
var v1headers []*types.Header
var v2headers []*types.Header
for _, header := range headers {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
v2headers = append(v2headers, header)
default: // Default "v1"
v1headers = append(v1headers, header)
}
}
if v1headers != nil {
x.EngineV1.VerifyHeaders(chain, v1headers, fullVerifies, abort, results)
}
if v2headers != nil {
x.EngineV2.VerifyHeaders(chain, v2headers, fullVerifies, abort, results)
}
return abort, results
}
// VerifyUncles implements consensus.Engine, always returning an error for any
// uncles as this consensus mechanism doesn't permit uncles.
func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
switch x.config.BlockConsensusVersion(block.Number()) {
case params.ConsensusEngineVersion2:
return nil
default: // Default "v1"
return x.EngineV1.VerifyUncles(chain, block)
}
@ -157,6 +224,8 @@ func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) er
// in the header satisfies the consensus protocol requirements.
func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return nil
default: // Default "v1"
return x.EngineV1.VerifySeal(chain, header)
}
@ -166,6 +235,8 @@ func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) er
// header for running the transactions on top.
func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.Prepare(chain, header)
default: // Default "v1"
return x.EngineV1.Prepare(chain, header)
}
@ -175,6 +246,8 @@ func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error
// rewards given, and returns the final block.
func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts)
default: // Default "v1"
return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts)
}
@ -184,6 +257,8 @@ func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, stat
// the local signing credentials.
func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
switch x.config.BlockConsensusVersion(block.Number()) {
case params.ConsensusEngineVersion2:
return x.EngineV2.Seal(chain, block, stop)
default: // Default "v1"
return x.EngineV1.Seal(chain, block, stop)
}
@ -194,11 +269,22 @@ func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha
// current signer.
func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
switch x.config.BlockConsensusVersion(parent.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.CalcDifficulty(chain, time, parent)
default: // Default "v1"
return x.EngineV1.CalcDifficulty(chain, time, parent)
}
}
func (x *XDPoS) HandleProposedBlock(chain consensus.ChainReader, header *types.Header) error {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.ProposedBlockHandler(chain, header)
default: // Default "v1"
return nil
}
}
/*
XDC specific methods
*/
@ -208,28 +294,49 @@ func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent
func (x *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) {
// Authorize each consensus individually
x.EngineV1.Authorize(signer, signFn)
x.EngineV2.Authorize(signer, signFn)
}
func (x *XDPoS) GetPeriod() uint64 {
return x.config.Period
}
func (x *XDPoS) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool {
func (x *XDPoS) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.IsAuthorisedAddress(chain, header, address)
default: // Default "v1"
return x.EngineV1.IsAuthorisedAddress(header, chain, address)
return x.EngineV1.IsAuthorisedAddress(chain, header, address)
}
}
func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.GetMasternodes(chain, header)
default: // Default "v1"
return x.EngineV1.GetMasternodes(chain, header)
}
}
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
switch x.config.BlockConsensusVersion(parent.Number) {
func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber uint64) []common.Address {
blockHeader := chain.GetHeaderByNumber(blockNumber)
if blockHeader == nil {
log.Error("[GetMasternodesByNumber] Unable to find block", "Num", blockNumber)
return []common.Address{}
}
switch x.config.BlockConsensusVersion(big.NewInt(int64(blockNumber))) {
case params.ConsensusEngineVersion2:
return x.EngineV2.GetMasternodes(chain, blockHeader)
default: // Default "v1"
return x.EngineV1.GetMasternodes(chain, blockHeader)
}
}
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
switch x.config.BlockConsensusVersion(big.NewInt(parent.Number.Int64() + 1)) {
case params.ConsensusEngineVersion2:
return x.EngineV2.YourTurn(chain, parent, signer)
default: // Default "v1"
return x.EngineV1.YourTurn(chain, parent, signer)
}
@ -237,13 +344,16 @@ func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, sign
func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) {
switch x.config.BlockConsensusVersion(header.Number) {
default: // Default "v1"
default: // Default "v1", v2 does not need this function
return x.EngineV1.GetValidator(creator, chain, header)
}
}
func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
// fmt.Println("UpdateMasternodes")
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.UpdateMasternodes(chain, header, ms)
default: // Default "v1"
return x.EngineV1.UpdateMasternodes(chain, header, ms)
}
@ -251,6 +361,8 @@ func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Hea
func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return common.Address{}, nil
default: // Default "v1"
return x.EngineV1.RecoverSigner(header)
}
@ -258,16 +370,39 @@ func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) {
func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return common.Address{}, nil
default: // Default "v1"
return x.EngineV1.RecoverValidator(header)
}
}
// Get master nodes over extra data of previous checkpoint block.
func (x *XDPoS) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
switch x.config.BlockConsensusVersion(preCheckpointHeader.Number) {
func (x *XDPoS) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
switch x.config.BlockConsensusVersion(checkpointHeader.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.GetMasternodesFromEpochSwitchHeader(checkpointHeader)
default: // Default "v1"
return x.EngineV1.GetMasternodesFromCheckpointHeader(preCheckpointHeader, n, e)
return x.EngineV1.GetMasternodesFromCheckpointHeader(checkpointHeader)
}
}
// Check is epoch switch (checkpoint) block
func (x *XDPoS) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.IsEpochSwitch(header)
default: // Default "v1"
return x.EngineV1.IsEpochSwitch(header)
}
}
func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNumber *big.Int) (uint64, uint64, error) {
switch x.config.BlockConsensusVersion(blockNumber) {
case params.ConsensusEngineVersion2:
return x.EngineV2.GetCurrentEpochSwitchBlock(chain, blockNumber)
default: // Default "v1"
return x.EngineV1.GetCurrentEpochSwitchBlock(blockNumber)
}
}
@ -278,6 +413,13 @@ func (x *XDPoS) GetDb() ethdb.Database {
func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
sp, err := x.EngineV2.GetSnapshot(chain, header)
return &utils.PublicApiSnapshot{
Number: sp.Number,
Hash: sp.Hash,
Signers: sp.GetMappedMasterNodes(),
}, err
default: // Default "v1"
sp, err := x.EngineV1.GetSnapshot(chain, header)
// Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API
@ -294,11 +436,26 @@ func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (
func (x *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return []common.Address{}, nil
default: // Default "v1"
return x.EngineV1.GetAuthorisedSignersFromSnapshot(chain, header)
}
}
func (x *XDPoS) FindParentBlockToAssign(chain consensus.ChainReader, currentBlock *types.Block) *types.Block {
switch x.config.BlockConsensusVersion(currentBlock.Number()) {
case params.ConsensusEngineVersion2:
block := x.EngineV2.FindParentBlockToAssign(chain)
if block == nil {
return currentBlock
}
return block
default: // Default "v1"
return currentBlock
}
}
/**
Caching
*/

View file

@ -91,6 +91,11 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
return api.XDPoS.GetAuthorisedSignersFromSnapshot(api.chain, header)
}
// Get the latest v2 committed block information. Note: This only applies to v2 engine. it doesn't make sense for v1
func (api *API) GetLatestCommittedBlockHeader() *types.BlockInfo {
return api.XDPoS.EngineV2.GetLatestCommittedBlockInfo()
}
func (api *API) NetworkInformation() NetworkInformation {
info := NetworkInformation{}
info.NetworkId = api.chain.Config().ChainId

View file

@ -28,30 +28,12 @@ import (
lru "github.com/hashicorp/golang-lru"
)
// ecrecover extracts the Ethereum account address from a signed header.
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
// If the signature's already cached, return that
hash := header.Hash()
if address, known := sigcache.Get(hash); known {
return address.(common.Address), nil
}
// Retrieve the signature from the header extra-data
if len(header.Extra) < utils.ExtraSeal {
return common.Address{}, utils.ErrMissingSignature
}
signature := header.Extra[len(header.Extra)-utils.ExtraSeal:]
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), signature)
if err != nil {
return common.Address{}, err
}
var signer common.Address
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
sigcache.Add(hash, signer)
return signer, nil
}
const (
// timeout waiting for M1
waitPeriod = 10
// timeout for checkpoint.
waitPeriodCheckpoint = 20
)
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
// Ethereum testnet following the Ropsten attacks.
@ -78,6 +60,23 @@ type XDPoS_v1 struct {
HookGetSignersFromContract func(blockHash common.Hash) ([]common.Address, error)
}
/* V1 Block
SignerFn is a signer callback function to request a hash to be signed by a
backing account.
type SignerFn func(accounts.Account, []byte) ([]byte, error)
sigHash returns the hash which is used as input for the delegated-proof-of-stake
signing. It is the hash of the entire header apart from the 65 byte signature
contained at the end of the extra data.
Note, the method requires the extra data to be at least 65 bytes, otherwise it
panics. This is done to avoid accidentally using both forms (signature present
or not), which could be abused to produce different hashes for the same header.
*/
func (x *XDPoS_v1) SigHash(header *types.Header) (hash common.Hash) {
return sigHash(header)
}
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
// signers set to the ones provided by the user.
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 {
@ -117,10 +116,7 @@ func (x *XDPoS_v1) VerifyHeader(chain consensus.ChainReader, header *types.Heade
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
// method returns a quit channel to abort the operations and a results channel to
// retrieve the async verifications (the order is that of the input slice).
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) {
abort := make(chan struct{})
results := make(chan error, len(headers))
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool, abort <-chan struct{}, results chan<- error) {
go func() {
for i, header := range headers {
err := x.verifyHeaderWithCache(chain, header, headers[:i], fullVerifies[i])
@ -132,7 +128,6 @@ func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.H
}
}
}()
return abort, results
}
func (x *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
@ -153,7 +148,7 @@ func (x *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *ty
// a batch of new headers.
func (x *XDPoS_v1) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
// If we're running a engine faking, accept any block as valid
if x.config.SkipValidation {
if x.config.SkipV1Validation {
return nil
}
if common.IsTestnet {
@ -321,7 +316,7 @@ func (x *XDPoS_v1) checkSignersOnCheckpoint(chain consensus.ChainReader, header
return nil
}
func (x *XDPoS_v1) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool {
func (x *XDPoS_v1) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool {
snap, err := x.GetSnapshot(chain, header)
if err != nil {
log.Error("[IsAuthorisedAddress] Can't get snapshot with at ", "number", header.Number, "hash", header.Hash().Hex(), "err", err)
@ -355,32 +350,33 @@ func (x *XDPoS_v1) StoreSnapshot(snap *SnapshotV1) error {
return snap.store(x.db)
}
func position(list []common.Address, x common.Address) int {
for i, item := range list {
if item == x {
return i
}
}
return -1
}
func (x *XDPoS_v1) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
n := header.Number.Uint64()
e := x.config.Epoch
switch {
case n%e == 0:
return x.GetMasternodesFromCheckpointHeader(header, n, e)
return x.GetMasternodesFromCheckpointHeader(header)
case n%e != 0:
h := chain.GetHeaderByNumber(n - (n % e))
return x.GetMasternodesFromCheckpointHeader(h, n, e)
if h == nil {
log.Warn("[GetMasternodes v1] epoch switch block header nil", "BlockNum", n)
}
return x.GetMasternodesFromCheckpointHeader(h)
default:
return []common.Address{}
}
}
func (x *XDPoS_v1) GetCurrentEpochSwitchBlock(blockNumber *big.Int) (uint64, uint64, error) {
currentBlockNum := blockNumber.Uint64()
currentCheckpointNumber := currentBlockNum - currentBlockNum%x.config.Epoch
epochNumber := currentBlockNum / x.config.Epoch
return currentCheckpointNumber, epochNumber, nil
}
func (x *XDPoS_v1) GetPeriod() uint64 { return x.config.Period }
func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error) {
func (x *XDPoS_v1) whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error) {
if header.Number.Uint64() == 0 {
return common.Address{}, errors.New("Don't take block 0")
}
@ -390,8 +386,41 @@ func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error
}
return m, nil
}
func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
len, preIndex, curIndex, ok, err := x.yourTurn(chain, parent, signer)
func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
if err != nil {
log.Warn("Failed when trying to commit new work", "err", err)
return false, err
}
if !ok {
// in case some nodes are down
if preIndex == -1 {
// first block
return false, nil
}
if curIndex == -1 {
// you're not allowed to create this block
return false, nil
}
h := utils.Hop(len, preIndex, curIndex)
gap := waitPeriod * int64(h)
// Check nearest checkpoint block in hop range.
nearest := x.config.Epoch - (parent.Number.Uint64() % x.config.Epoch)
if uint64(h) >= nearest {
gap = waitPeriodCheckpoint * int64(h)
}
log.Info("Distance from the parent block", "seconds", gap, "hops", h)
waitedTime := time.Now().Unix() - parent.Time.Int64()
if gap > waitedTime {
return false, nil
}
log.Info("Wait enough. It's my turn", "waited seconds", waitedTime)
}
return true, nil
}
func (x *XDPoS_v1) yourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
masternodes := x.GetMasternodes(chain, parent)
// if common.IsTestnet {
@ -415,13 +444,13 @@ func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, s
// masternode[0] has chance to create block 1
preIndex := -1
if parent.Number.Uint64() != 0 {
pre, err = whoIsCreator(snap, parent)
pre, err = x.whoIsCreator(snap, parent)
if err != nil {
return 0, 0, 0, false, err
}
preIndex = position(masternodes, pre)
preIndex = utils.Position(masternodes, pre)
}
curIndex := position(masternodes, signer)
curIndex := utils.Position(masternodes, signer)
if signer == x.signer {
log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex)
}
@ -643,7 +672,7 @@ func (x *XDPoS_v1) GetValidator(creator common.Address, chain consensus.ChainRea
return common.Address{}, fmt.Errorf("couldn't find checkpoint header")
}
}
m, err := GetM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
m, err := getM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
if err != nil {
return common.Address{}, err
}
@ -876,7 +905,7 @@ func (x *XDPoS_v1) Seal(chain consensus.ChainReader, block *types.Block, stop <-
default:
}
// Sign all the things!
sighash, err := signFn(accounts.Account{Address: signer}, utils.SigHash(header).Bytes())
sighash, err := signFn(accounts.Account{Address: signer}, x.SigHash(header).Bytes())
if err != nil {
return nil, err
}
@ -900,10 +929,10 @@ func (x *XDPoS_v1) CalcDifficulty(chain consensus.ChainReader, time uint64, pare
func (x *XDPoS_v1) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int {
// If we're running a engine faking, skip calculation
if x.config.SkipValidation {
if x.config.SkipV1Validation {
return big.NewInt(1)
}
len, preIndex, curIndex, _, err := x.YourTurn(chain, parent, signer)
len, preIndex, curIndex, _, err := x.yourTurn(chain, parent, signer)
if err != nil {
return big.NewInt(int64(len + curIndex - preIndex))
}
@ -926,7 +955,7 @@ func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error
return common.Address{}, consensus.ErrFailValidatorSignature
}
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), header.Validator)
pubkey, err := crypto.Ecrecover(x.SigHash(header).Bytes(), header.Validator)
if err != nil {
return common.Address{}, err
}
@ -937,18 +966,13 @@ func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error
return signer, nil
}
// Get master nodes over extra data of previous checkpoint block.
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
if preCheckpointHeader == nil {
log.Info("Previous checkpoint's header is empty", "block number", n, "epoch", e)
// Get master nodes over extra data of checkpoint block.
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
if checkpointHeader == nil {
log.Warn("Checkpoint's header is empty", "Header", checkpointHeader)
return []common.Address{}
}
masternodes := make([]common.Address, (len(preCheckpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
for i := 0; i < len(masternodes); i++ {
copy(masternodes[i][:], preCheckpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
}
return masternodes
return decodeMasternodesFromHeaderExtra(checkpointHeader)
}
func (x *XDPoS_v1) GetDb() ethdb.Database {
@ -970,30 +994,6 @@ func removePenaltiesFromBlock(chain consensus.ChainReader, masternodes []common.
return masternodes
}
// Get masternodes address from checkpoint Header.
func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
for i := 0; i < len(masternodes); i++ {
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
}
return masternodes
}
// Get m2 list from checkpoint block.
func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
return nil, errors.New("This block is not checkpoint block epoc.")
}
// Get signers from this block.
masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader)
validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators)
m1m2, _, err := utils.GetM1M2(masternodes, validators, currentHeader, config)
if err != nil {
return map[common.Address]common.Address{}, err
}
return m1m2, nil
}
func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) {
startGapBlockHeader := checkpointHeader
number := checkpointHeader.Number.Uint64()
@ -1028,3 +1028,10 @@ func NewFaker(db ethdb.Database, config *params.XDPoSConfig) *XDPoS_v1 {
}
return fakeEngine
}
// Epoch Switch is also known as checkpoint in v1
func (x *XDPoS_v1) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
epochNumber := header.Number.Uint64() / x.config.Epoch
blockNumInEpoch := header.Number.Uint64() % x.config.Epoch
return blockNumInEpoch == 0, epochNumber, nil
}

View 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
}

View 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 := &params.ChainConfig{
XDPoS: &params.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)
}
}
}

View 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

View 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
}

View 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
}

View 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

View 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
}

View file

@ -1 +1,102 @@
package engine_v2
import (
"encoding/json"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
)
// Snapshot is the state of the smart contract validator list
// The validator list is used on next epoch master nodes
// If we don't have the snapshot, then we have to trace back the gap block smart contract state which is very costly
type SnapshotV2 struct {
Number uint64 `json:"number"` // Block number where the snapshot was created
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
// MasterNodes will get assigned on updateM1
NextEpochMasterNodes []common.Address `json:"masterNodes"` // Set of authorized master nodes at this moment for next epoch
}
// create new snapshot for next epoch to use
func newSnapshot(number uint64, hash common.Hash, masternodes []common.Address) *SnapshotV2 {
snap := &SnapshotV2{
Number: number,
Hash: hash,
NextEpochMasterNodes: masternodes,
}
return snap
}
// loadSnapshot loads an existing snapshot from the database.
func loadSnapshot(db ethdb.Database, hash common.Hash) (*SnapshotV2, error) {
blob, err := db.Get(append([]byte("XDPoS-V2-"), hash[:]...))
if err != nil {
return nil, err
}
snap := new(SnapshotV2)
if err := json.Unmarshal(blob, snap); err != nil {
return nil, err
}
return snap, nil
}
// store inserts the SnapshotV2 into the database.
func storeSnapshot(s *SnapshotV2, db ethdb.Database) error {
blob, err := json.Marshal(s)
if err != nil {
return err
}
return db.Put(append([]byte("XDPoS-V2-"), s.Hash[:]...), blob)
}
// retrieves master nodes list in map type
func (s *SnapshotV2) GetMappedMasterNodes() map[common.Address]struct{} {
ms := make(map[common.Address]struct{})
for _, n := range s.NextEpochMasterNodes {
ms[n] = struct{}{}
}
return ms
}
func (s *SnapshotV2) IsMasterNodes(address common.Address) bool {
for _, n := range s.NextEpochMasterNodes {
if n.String() == address.String() {
return true
}
}
return false
}
// snapshot retrieves the authorization snapshot at a given point in time.
func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64, isGapNumber bool) (*SnapshotV2, error) {
var gapBlockNum uint64
if isGapNumber {
gapBlockNum = number
} else {
gapBlockNum = number - number%x.config.Epoch - x.config.Gap
}
gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash()
log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex())
// If an in-memory SnapshotV2 was found, use that
if s, ok := x.snapshots.Get(gapBlockHash); ok {
snap := s.(*SnapshotV2)
log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash)
return snap, nil
}
// If an on-disk checkpoint snapshot can be found, use that
snap, err := loadSnapshot(x.db, gapBlockHash)
if err != nil {
log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash)
return nil, err
}
log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash)
x.snapshots.Add(snap.Hash, snap)
return snap, nil
}

View 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)
}
}

View 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
}

View 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)
}
}
}

View 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
}

View 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
}

View 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)
}
}
}

View file

@ -15,7 +15,8 @@ var (
NonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer
NonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer.
UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
InmemoryEpochs = 5 * EpochLength // Number of mapping from block to epoch switch infos to keep in memory
)
const (
@ -23,3 +24,8 @@ const (
BlockSignersCacheLimit = 9000
M2ByteLength = 4
)
const (
PeriodicJobPeriod = 60
PoolHygieneRound = 10
)

View file

@ -1,6 +1,11 @@
package utils
import "errors"
import (
"errors"
"fmt"
"github.com/XinFinOrg/XDPoSChain/core/types"
)
// Various error messages to mark blocks invalid. These should be private to
// prevent engine specific errors from being referenced in the remainder of the
@ -42,6 +47,9 @@ var (
ErrInvalidCheckpointPenalties = errors.New("invalid penalty list on checkpoint block")
ErrValidatorsNotLegit = errors.New("validators does not match what's stored in snapshot minutes its penalty")
ErrPenaltiesNotLegit = errors.New("penalties does not match")
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
ErrInvalidMixDigest = errors.New("non-zero mix digest")
@ -60,6 +68,9 @@ var (
// be modified via out-of-range or non-contiguous headers.
ErrInvalidVotingChain = errors.New("invalid voting chain")
ErrInvalidHeaderOrder = errors.New("invalid header order")
ErrInvalidChild = errors.New("invalid header child")
// errUnauthorized is returned if a header is signed by a non-authorized entity.
ErrUnauthorized = errors.New("unauthorized")
@ -71,4 +82,39 @@ var (
ErrWaitTransactions = errors.New("waiting for transactions")
ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block")
ErrEmptyEpochSwitchValidators = errors.New("empty validators list on epoch switch block")
ErrInvalidV2Extra = errors.New("Invalid v2 extra in the block")
ErrInvalidQC = errors.New("Invalid QC content")
ErrInvalidTC = errors.New("Invalid TC content")
ErrEmptyBlockInfoHash = errors.New("BlockInfo hash is empty")
ErrInvalidFieldInNonEpochSwitch = errors.New("Invalid field exist in a non-epoch swtich block")
ErrValidatorNotWithinMasternodes = errors.New("Validaotor address is not in the master node list")
ErrCoinbaseAndValidatorMismatch = errors.New("Validaotor and coinbase address in header does not match")
ErrNotItsTurn = errors.New("Not validator's turn to mine this block")
ErrRoundInvalid = errors.New("Invalid Round, it shall be bigger than QC round")
ErrAlreadyMined = errors.New("Already mined")
)
type ErrIncomingMessageRoundNotEqualCurrentRound struct {
Type string
IncomingRound types.Round
CurrentRound types.Round
}
func (e *ErrIncomingMessageRoundNotEqualCurrentRound) Error() string {
return fmt.Sprintf("%s message round number: %v does not match currentRound: %v", e.Type, e.IncomingRound, e.CurrentRound)
}
type ErrIncomingMessageRoundTooFarFromCurrentRound struct {
Type string
IncomingRound types.Round
CurrentRound types.Round
}
func (e *ErrIncomingMessageRoundTooFarFromCurrentRound) Error() string {
return fmt.Sprintf("%s message round number: %v is too far away from currentRound: %v", e.Type, e.IncomingRound, e.CurrentRound)
}

View 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
}

View 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)
}

View file

@ -2,16 +2,14 @@ package utils
import (
"bytes"
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
@ -52,100 +50,53 @@ func ExtractValidatorsFromBytes(byteValidators []byte) []int64 {
return validators
}
// Get masternodes address from checkpoint Header.
func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-ExtraVanity-ExtraSeal)/common.AddressLength)
for i := 0; i < len(masternodes); i++ {
copy(masternodes[i][:], checkpointHeader.Extra[ExtraVanity+i*common.AddressLength:])
}
return masternodes
}
// Get m2 list from checkpoint block.
func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
return nil, errors.New("This block is not checkpoint block epoc.")
}
// Get signers from this block.
masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader)
validators := ExtractValidatorsFromBytes(checkpointHeader.Validators)
m1m2, _, err := GetM1M2(masternodes, validators, currentHeader, config)
if err != nil {
return map[common.Address]common.Address{}, err
}
return m1m2, nil
}
func GetM1M2(masternodes []common.Address, validators []int64, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, uint64, error) {
m1m2 := map[common.Address]common.Address{}
maxMNs := len(masternodes)
moveM2 := uint64(0)
if len(validators) < maxMNs {
return nil, moveM2, errors.New("len(m2) is less than len(m1)")
}
if maxMNs > 0 {
isForked := config.IsTIPRandomize(currentHeader.Number)
if isForked {
moveM2 = ((currentHeader.Number.Uint64() % config.XDPoS.Epoch) / uint64(maxMNs)) % uint64(maxMNs)
}
for i, m1 := range masternodes {
m2Index := uint64(validators[i] % int64(maxMNs))
m2Index = (m2Index + moveM2) % uint64(maxMNs)
m1m2[m1] = masternodes[m2Index]
}
}
return m1m2, moveM2, nil
}
// compare 2 signers lists
// return true if they are same elements, otherwise return false
func CompareSignersLists(list1 []common.Address, list2 []common.Address) bool {
if len(list1) == 0 && len(list2) == 0 {
l1 := make([]common.Address, len(list1))
l2 := make([]common.Address, len(list2))
copy(l1, list1)
copy(l2, list2)
if len(l1) == 0 && len(l2) == 0 {
return true
}
sort.Slice(list1, func(i, j int) bool {
return list1[i].String() <= list1[j].String()
})
sort.Slice(list2, func(i, j int) bool {
return list2[i].String() <= list2[j].String()
})
return reflect.DeepEqual(list1, list2)
}
// SignerFn is a signer callback function to request a hash to be signed by a
// backing account.
//type SignerFn func(accounts.Account, []byte) ([]byte, error)
// sigHash returns the hash which is used as input for the delegated-proof-of-stake
// signing. It is the hash of the entire header apart from the 65 byte signature
// contained at the end of the extra data.
//
// Note, the method requires the extra data to be at least 65 bytes, otherwise it
// panics. This is done to avoid accidentally using both forms (signature present
// or not), which could be abused to produce different hashes for the same header.
func SigHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewKeccak256()
err := rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
})
if err != nil {
log.Debug("Fail to encode", err)
if len(l1) != len(l2) {
return false
}
hasher.Sum(hash[:0])
return hash
sort.Slice(l1, func(i, j int) bool {
return l1[i].String() <= l1[j].String()
})
sort.Slice(l2, func(i, j int) bool {
return l2[i].String() <= l2[j].String()
})
return reflect.DeepEqual(l1, l2)
}
// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions)
func DecodeBytesExtraFields(b []byte, val interface{}) error {
if len(b) == 0 {
return fmt.Errorf("extra field is 0 length")
}
switch b[0] {
case 1:
return fmt.Errorf("consensus version 1 is not applicable for decoding extra fields")
case 2:
return rlp.DecodeBytes(b[1:], val)
default:
return fmt.Errorf("consensus version %d is not defined", b[0])
}
}
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewKeccak256()
err := rlp.Encode(hw, x)
if err != nil {
log.Error("[rlpHash] Fail to hash item", "Error", err)
}
hw.Sum(h[:0])
return h
}

View file

@ -1,53 +1,11 @@
package utils
import (
"fmt"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
)
func TestGetM1M2FromCheckpointHeader(t *testing.T) {
masternodes := []common.Address{
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
common.StringToAddress("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc"),
}
validators := []int64{
2,
1,
0,
}
epoch := uint64(900)
config := &params.ChainConfig{
XDPoS: &params.XDPoSConfig{
Epoch: uint64(epoch),
},
}
testMoveM2 := []uint64{0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2}
//try from block 3410001 to 3410018
for i := uint64(3464001); i <= 3464018; i++ {
currentNumber := int64(i)
currentHeader := &types.Header{
Number: big.NewInt(currentNumber),
}
m1m2, moveM2, err := GetM1M2(masternodes, validators, currentHeader, config)
if err != nil {
t.Error("can't get m1m2", "err", err)
}
fmt.Printf("block: %v, moveM2: %v\n", currentHeader.Number.Int64(), moveM2)
for _, k := range masternodes {
fmt.Printf("m1: %v - m2: %v\n", k.Str(), m1m2[k].Str())
}
if moveM2 != testMoveM2[i-3464001] {
t.Error("wrong moveM2", "currentNumber", currentNumber, "want", testMoveM2[i-3464001], "have", moveM2)
}
}
}
func TestCompareSignersLists(t *testing.T) {
list1 := []common.Address{
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),

View file

@ -38,4 +38,10 @@ var (
ErrFailValidatorSignature = errors.New("missing validator in header")
ErrNoValidatorSignature = errors.New("no validator in header")
ErrNotReadyToPropose = errors.New("not ready to propose, QC is not ready")
ErrNotReadyToMine = errors.New("Not ready to mine, it's not your turn")
ErrCoinbaseMismatch = errors.New("Block Coinbase address does not match its wallte address")
)

View 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)
}

View file

@ -1,4 +1,4 @@
package consensus
package engine_v1_tests
import (
"fmt"
@ -6,13 +6,15 @@ import (
"reflect"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
// Should NOT update signerList if not on the gap block
func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
if err != nil {
t.Fatal(err)
@ -26,11 +28,18 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
blockA, err := insertBlockTxs(blockchain, 401, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(401)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
if err != nil {
t.Fatal(err)
@ -50,14 +59,22 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
// Should call updateM1 at the gap block, and have the same snapshot values as the parent block if no SM transaction is involved
func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
blockchain, _, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, _, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Insert block 450
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block, err := insertBlock(blockchain, 450, blockCoinBase, parentBlock, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block)
assert.Nil(t, err)
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
if err != nil {
t.Fatal(err)
@ -78,7 +95,7 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
//Should call updateM1 at gap block, and update the snapshot if there are SM transactions involved
func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
// Insert first Block 449
t.Logf("Inserting block with propose at 449...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
@ -89,10 +106,18 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
block449, err := insertBlockTxs(blockchain, 449, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(449)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block449)
assert.Nil(t, err)
parentBlock = block449
signers, err := GetSnapshotSigner(blockchain, block449.Header())
@ -113,11 +138,18 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
// Now, let's mine another block to trigger the GAP block signerList update
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
block450, err := insertBlock(blockchain, 450, block450CoinbaseAddress, parentBlock, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(block450CoinbaseAddress),
}
block450, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block450)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450.Header())
if err != nil {
t.Fatalf("Failed while trying to get signers")
@ -136,7 +168,7 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
//Should call updateM1 before gap block, and update the snapshot if there are SM transactions involved
func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Insert first Block 450 A
t.Logf("Inserting block with propose at 450 A...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000450"
@ -147,11 +179,18 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
if err != nil {
t.Fatal(err)
@ -165,7 +204,7 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
// Should call updateM1 and update snapshot when a forked block(at gap block number) is inserted back into main chain (Edge case)
func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Check initial signer, by default, acc3 is in the signerList
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
if err != nil {
@ -189,11 +228,18 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
}
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, blockA.Header())
if err != nil {
t.Fatal(err)
@ -217,10 +263,18 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
}
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
if err != nil {
t.Fatal(err)
@ -240,8 +294,16 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
assert.Nil(t, err)
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
if err != nil {
t.Fatal(err)
}
@ -288,7 +350,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testing.T) {
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
state, err := blockchain.State()
if err != nil {
@ -316,10 +378,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
transferTransaction := transferTx(t, acc1Addr, 999)
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
state, err = blockchain.State()
if err != nil {
t.Fatalf("Failed while trying to get blockchain state")
@ -351,10 +421,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
transferTransaction = transferTx(t, acc1Addr, 888)
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
state, err = blockchain.State()
if err != nil {
t.Fatalf("Failed while trying to get blockchain state")
@ -378,11 +456,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
if err != nil {
@ -422,7 +507,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
}
func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Check initial signer, by default, acc3 is in the signerList
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
if err != nil {
@ -440,10 +525,18 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert normal blocks 450 A
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block450A, err := insertBlock(blockchain, 450, blockCoinBase450A, parentBlock, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450A),
}
block450A, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block450A)
assert.Nil(t, err)
// Insert 451 A with vote
blockCoinbase451A := "0xaaa0000000000000000000000000000000000451"
@ -453,10 +546,18 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
}
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
block451A, err := insertBlockTxs(blockchain, 451, blockCoinbase451A, block450A, []*types.Transaction{tx}, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450A.Hash(),
Coinbase: common.HexToAddress(blockCoinbase451A),
}
block451A, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block451A)
assert.Nil(t, err)
// SignerList should be unchanged as the vote happen after GAP block
signers, err = GetSnapshotSigner(blockchain, block451A.Header())
@ -476,24 +577,48 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert forked Block 450 B
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block450B, err := insertBlock(blockchain, 450, blockCoinBase450B, parentBlock, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block452B, err := insertBlock(blockchain, 452, blockCoinBase452B, block451B, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(452)),
ParentHash: block451B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase452B),
}
block452B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block452B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block452B.Header())
if err != nil {
t.Fatal(err)
@ -505,3 +630,63 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
t.Fatalf("account 3 should sit in the signer list as previos block result")
}
}
/*
V2 Consensus
*/
/*
// Pending for creating cross version blocks
func TestV2UpdateSignerListIfVotedBeforeGap(t *testing.T) {
config := params.TestXDPoSMockChainConfig
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, int(config.XDPoS.Epoch)+GAP-2, config)
// Insert first Block 1349
t.Logf("Inserting block with propose at 1349...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000001349"
tx, err := voteTX(37117, 0, acc1Addr.String())
if err != nil {
t.Fatal(err)
}
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(1349)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block1349, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
parentBlock = block1349
// Now, let's mine another block to trigger the GAP block signerList update
block1350CoinbaseAddress := "0xaaa0000000000000000000000000000000001350"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(1350)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(block1350CoinbaseAddress),
}
block1350, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
signers, err := GetSnapshotSigner(blockchain, block1350.Header())
if err != nil {
t.Fatalf("Failed while trying to get signers")
}
// Now, we voted acc 1 to be in the signerList, which will kick out acc3 because it has less funds
if signers[acc3Addr.Hex()] == true {
debugMessage(backend, signers, t)
t.Fatalf("account 3 should NOT sit in the signer list")
}
if signers[acc1Addr.Hex()] != true {
debugMessage(backend, signers, t)
t.Fatalf("account 1 should sit in the signer list")
}
}
*/

View file

@ -1,17 +1,19 @@
package consensus
package engine_v1_tests
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
// Snapshot try to read before blockchain is written
func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
state, err := blockchain.State()
if err != nil {
@ -39,10 +41,20 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
transferTransaction := transferTx(t, acc1Addr, 999)
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
state, err = blockchain.State()
if err != nil {
t.Fatalf("Failed while trying to get blockchain state")
@ -74,10 +86,21 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
transferTransaction = transferTx(t, acc1Addr, 888)
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 2)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
Difficulty: big.NewInt(2),
}
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
if blockchain.CurrentHeader().Hash() != block450B.Hash() {
t.Fatalf("the block with higher difficulty should be current header")
}
@ -104,11 +127,19 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 3)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
Difficulty: big.NewInt(3),
}
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
if err != nil {

View file

@ -1,4 +1,4 @@
package consensus
package engine_v1_tests
import (
"bytes"
@ -6,14 +6,17 @@ import (
"encoding/hex"
"fmt"
"math/big"
"math/rand"
"strings"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
"github.com/XinFinOrg/XDPoSChain/core"
. "github.com/XinFinOrg/XDPoSChain/core"
@ -58,6 +61,16 @@ func debugMessage(backend *backends.SimulatedBackend, signers signersList, t *te
}
}
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
// initial helper backend
@ -223,23 +236,49 @@ func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testi
return ms
}
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block) {
// V1 consensus engine
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
// Preparation
var err error
// Authorise
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
backend := getCommonBackend(t, chainConfig)
blockchain := backend.GetBlockChain()
blockchain.Client = backend
if err != nil {
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
}
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
currentBlock := blockchain.Genesis()
go func() {
for range core.CheckpointCh {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}
}()
// Insert initial blocks
for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(i)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block)
if err != nil {
panic(err)
}
currentBlock = block
}
// Update Signer as there is no previous signer assigned
@ -248,83 +287,96 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
t.Fatal(err)
}
return blockchain, backend, currentBlock
return blockchain, backend, currentBlock, signer, signFn
}
// insert Block without transcation attached
func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, difficulty int64) (*types.Block, error) {
block, err := createXDPoSTestBlock(
blockchain,
parentBlock.Hash().Hex(),
blockCoinBase, blockNum, nil,
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
common.HexToHash(root),
difficulty,
)
if err != nil {
return nil, err
func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte) *types.Block {
currentBlock := startingBlock
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(blockNumber)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
err = blockchain.InsertBlock(block)
if err != nil {
return nil, err
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
// reset extra
header.Extra = []byte{}
if len(header.Extra) < utils.ExtraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, utils.ExtraVanity-len(header.Extra))...)
}
header.Extra = header.Extra[:utils.ExtraVanity]
var masternodes []common.Address
// Place the test's signer address to the last
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
// masternodesFromV1LastEpoch = masternodes
for _, masternode := range masternodes {
header.Extra = append(header.Extra, masternode[:]...)
}
header.Extra = append(header.Extra, make([]byte, utils.ExtraSeal)...)
// Sign all the things for v1 block use v1 sigHash function
sighash, err := signFn(accounts.Account{Address: signer}, blockchain.Engine().(*XDPoS.XDPoS).SigHash(header).Bytes())
if err != nil {
panic(fmt.Errorf("Error when sign last v1 block hash during test block creation"))
}
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
}
return block, nil
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
if err != nil {
panic(fmt.Errorf("Fail to create block in test helper, %v", err))
}
return block
}
// insert Block with transcation attached
func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, txs []*types.Transaction, root string, difficulty int64) (*types.Block, error) {
block, err := createXDPoSTestBlock(
blockchain,
parentBlock.Hash().Hex(),
blockCoinBase, blockNum, txs,
"0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c",
common.HexToHash(root),
difficulty,
)
if err != nil {
return nil, err
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (*types.Block, error) {
if customHeader.Extra == nil {
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
}
var difficulty *big.Int
if customHeader.Difficulty == nil {
difficulty = big.NewInt(1)
} else {
difficulty = customHeader.Difficulty
}
err = blockchain.InsertBlock(block)
if err != nil {
return nil, err
// TODO: check if this is needed
if len(txs) != 0 {
customHeader.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
} else {
customHeader.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
}
return block, nil
}
func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, difficulty int64) (*types.Block, error) {
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
//ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
//Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc"
extraByte, _ := hex.DecodeString(extraSubstring)
header := types.Header{
ParentHash: common.HexToHash(parentHash),
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
// ReceiptHash: types.EmptyRootHash,
ReceiptHash: common.HexToHash(receiptHash),
Root: root,
Coinbase: common.HexToAddress(coinbase),
Difficulty: big.NewInt(difficulty),
Number: big.NewInt(int64(number)),
ParentHash: customHeader.ParentHash,
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
ReceiptHash: customHeader.ReceiptHash,
Root: customHeader.Root,
Coinbase: customHeader.Coinbase,
Difficulty: difficulty,
Number: customHeader.Number,
GasLimit: 1200000000,
Time: big.NewInt(int64(number * 10)),
Extra: extraByte,
Time: big.NewInt(time.Now().Unix()),
Extra: customHeader.Extra,
Validator: customHeader.Validator,
Validators: customHeader.Validators,
Penalties: customHeader.Penalties,
}
var block *types.Block
if len(txs) == 0 {
block = types.NewBlockWithHeader(&header)
} else {
// Prepare Receipt
statedb, err := bc.StateAt(bc.GetBlockByNumber(uint64(number - 1)).Root()) //Get parent root
statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root
if err != nil {
return nil, fmt.Errorf("%v when get state", err)
}
gp := new(GasPool).AddGas(header.GasLimit)
// usedGas := uint64(0)
var gasUsed = new(uint64)
var receipts types.Receipts
@ -338,7 +390,6 @@ func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number in
}
header.GasUsed = *gasUsed
block = types.NewBlock(&header, txs, nil, receipts)
}

View 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())
}

View file

@ -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)
}

View 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()
}
}
}

View 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
}

View 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)
}

View 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)
}

View 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))
}

View 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)
}
}

View 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")]))
}
}
}

View 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)
}

View 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")
}
}
}

View 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)
}

View 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")
}
}
}
}

View 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")
}
}
}

View file

@ -54,7 +54,7 @@ const (
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
)
type rewardLog struct {
type RewardLog struct {
Sign uint64 `json:"sign"`
Reward *big.Int `json:"reward"`
}
@ -319,13 +319,13 @@ func DecryptRandomizeFromSecretsAndOpening(secrets [][32]byte, opening [32]byte)
}
// Calculate reward for reward checkpoint.
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*rewardLog, error) {
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*RewardLog, error) {
// Not reward for singer of genesis block and only calculate reward at checkpoint block.
number := header.Number.Uint64()
prevCheckpoint := number - (rCheckpoint * 2)
startBlockNumber := prevCheckpoint + 1
endBlockNumber := startBlockNumber + rCheckpoint - 1
signers := make(map[common.Address]*rewardLog)
signers := make(map[common.Address]*RewardLog)
mapBlkHash := map[uint64]common.Hash{}
data := make(map[common.Hash][]common.Address)
@ -352,7 +352,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
}
}
header = chain.GetHeader(header.ParentHash, prevCheckpoint)
masternodes := utils.GetMasternodesFromCheckpointHeader(header)
masternodes := c.GetMasternodesFromCheckpointHeader(header)
for i := startBlockNumber; i <= endBlockNumber; i++ {
if i%common.MergeSignRange == 0 || !chain.Config().IsTIP2019(big.NewInt(int64(i))) {
@ -376,7 +376,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
if exist {
signers[addr].Sign++
} else {
signers[addr] = &rewardLog{1, new(big.Int)}
signers[addr] = &RewardLog{1, new(big.Int)}
}
*totalSigner++
}
@ -390,7 +390,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
}
// Calculate reward for signers.
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*rewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*RewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
resultSigners := make(map[common.Address]*big.Int)
// Add reward for signers.
if totalSigner > 0 {
@ -404,12 +404,11 @@ func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*
resultSigners[signer] = calcReward
}
}
jsonSigners, err := json.Marshal(signers)
if err != nil {
log.Error("Fail to parse json signers", "error", err)
return nil, err
log.Info("Signers data", "totalSigner", totalSigner, "totalReward", chainReward)
for addr, signer := range signers {
log.Info("Signer reward", "signer", addr, "sign", signer.Sign, "reward", signer.Reward)
}
log.Info("Signers data", "signers", string(jsonSigners), "totalSigner", totalSigner, "totalReward", chainReward)
return resultSigners, nil
}

View file

@ -17,11 +17,12 @@
package core
import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"runtime"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"

View file

@ -352,6 +352,13 @@ func (bc *BlockChain) loadLastState() error {
}
bc.hc.SetCurrentHeader(currentHeader)
if engine, ok := bc.Engine().(*XDPoS.XDPoS); ok {
err := engine.Initial(bc, currentHeader)
if err != nil {
return err
}
}
// Restore the last known head fast block
bc.currentFastBlock.Store(currentBlock)
if head := GetHeadFastBlockHash(bc.db); head != (common.Hash{}) {
@ -974,7 +981,20 @@ func (bc *BlockChain) procFutureBlocks() {
// Insert one by one as chain insertion needs contiguous ancestry between blocks
for i := range blocks {
bc.InsertChain(blocks[i : i+1])
_, err := bc.InsertChain(blocks[i : i+1])
// let consensus engine handle the last block (e.g. for voting)
if i == len(blocks)-1 && err == nil {
engine, ok := bc.Engine().(*XDPoS.XDPoS)
if ok {
go func() {
header := blocks[i].Header()
err = engine.HandleProposedBlock(bc, header)
if err != nil {
log.Info("[procFutureBlocks] handle proposed block has error", "err", err, "block hash", header.Hash(), "number", header.Number)
}
}()
}
}
}
}
}
@ -1590,8 +1610,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
bc.reportBlock(block, nil, err)
return i, events, coalescedLogs, err
}
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
if err := tradingService.UpdateMediumPriceBeforeEpoch(block.NumberU64()/bc.chainConfig.XDPoS.Epoch, tradingState, statedb); err != nil {
isEpochSwithBlock, epochNumber, err := engine.IsEpochSwitch(block.Header())
if err != nil {
log.Error("[insertChain] Error while checking if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
bc.reportBlock(block, nil, err)
}
if isEpochSwithBlock {
if err := tradingService.UpdateMediumPriceBeforeEpoch(epochNumber, tradingState, statedb); err != nil {
return i, events, coalescedLogs, err
}
} else {
@ -1708,9 +1733,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
stats.report(chain, i, dirty)
if bc.chainConfig.XDPoS != nil {
// epoch block
if (chain[i].NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
isEpochSwithBlock, _, err := engine.IsEpochSwitch(chain[i].Header())
if err != nil {
log.Error("[insertChain] Error while checking and notifying channel CheckpointCh if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
bc.reportBlock(block, nil, err)
}
if isEpochSwithBlock {
CheckpointCh <- 1
}
}
}
@ -1855,8 +1884,15 @@ func (bc *BlockChain) getResultBlock(block *types.Block, verifiedM2 bool) (*Resu
bc.reportBlock(block, nil, err)
return nil, err
}
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
if err := tradingService.UpdateMediumPriceBeforeEpoch(block.NumberU64()/bc.chainConfig.XDPoS.Epoch, tradingState, statedb); err != nil {
isEpochSwithBlock, epochNumber, err := engine.IsEpochSwitch(block.Header())
if err != nil {
log.Error("[getResultBlock] Error while checking block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
bc.reportBlock(block, nil, err)
}
if isEpochSwithBlock {
if err := tradingService.UpdateMediumPriceBeforeEpoch(epochNumber, tradingState, statedb); err != nil {
return nil, err
}
} else {
@ -2029,9 +2065,13 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L
stats.report(types.Blocks{block}, 0, dirty)
if bc.chainConfig.XDPoS != nil {
// epoch block
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
isEpochSwithBlock, _, err := bc.Engine().(*XDPoS.XDPoS).IsEpochSwitch(block.Header())
if err != nil {
log.Error("[insertBlock] Error while checking if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
bc.reportBlock(block, nil, err)
}
if isEpochSwithBlock {
CheckpointCh <- 1
}
}
// Append a single chain head event if we've progressed the chain
@ -2459,16 +2499,12 @@ func (bc *BlockChain) UpdateM1() error {
// if can't get anything, request from contracts
stateDB, err := bc.State()
if err != nil {
candidates, err = validator.GetCandidates(opts)
if err != nil {
return err
}
} else {
candidates = state.GetCandidates(stateDB)
}
var ms []utils.Masternode
@ -2500,7 +2536,7 @@ func (bc *BlockChain) UpdateM1() error {
header := bc.CurrentHeader()
var maxMasternodes int
// check if block number is increase ms checkpoint
if bc.chainConfig.IsTIPIncreaseMasternodes(header.Number) {
if bc.chainConfig.IsTIPIncreaseMasternodes(header.Number) || (bc.chainConfig.XDPoS.V2.SwitchBlock != nil && header.Number.Cmp(bc.chainConfig.XDPoS.V2.SwitchBlock) == 1) {
// using new masterndoes
maxMasternodes = common.MaxMasternodesV2
} else {

View file

@ -188,10 +188,11 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
}
return newcfg, stored, err
}
// Special case: don't change the existing config of a non-mainnet chain if no new
// Special case: don't change the existing config of a non-xinfin chain if no new
// config is supplied. These chains would get AllProtocolChanges (and a compat error)
// if we just continued here.
if genesis == nil && stored != params.MainnetGenesisHash {
if genesis == nil && newcfg == params.AllEthashProtocolChanges {
return storedcfg, stored, nil
}
@ -211,12 +212,19 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
switch {
case g != nil:
log.Info("[configOrDefault] load orignal config", "hash", ghash)
return g.Config
case ghash == params.MainnetGenesisHash:
case ghash == params.XDCMainnetGenesisHash:
log.Info("[configOrDefault] load mainnetconfig")
return params.XDCMainnetChainConfig
case ghash == params.TestnetGenesisHash:
log.Info("[configOrDefault] load TestnetChainConfig")
return params.TestnetChainConfig
case ghash == params.DevnetGenesisHash:
log.Info("[configOrDefault] load DevnetChainConfig")
return params.DevnetChainConfig
default:
log.Info("[configOrDefault] load AllEthashProtocolChanges", "hash", ghash)
return params.AllEthashProtocolChanges
}
}
@ -312,26 +320,31 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big
// DefaultGenesisBlock returns the Ethereum main net genesis block.
func DefaultGenesisBlock() *Genesis {
config := params.XDCMainnetChainConfig
config.XDPoS.V2 = nil
return &Genesis{
Config: params.XDCMainnetChainConfig,
Config: config,
Nonce: 0,
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001b82c4bf317fcafe3d77e8b444c82715d216afe845b7bd987fa22c9bac89b71f0ded03f6e150ba31ad670b2b166684657ffff95f4810380ae7381e9bce41231d5dd8cdd7499e418b648c00af75d184a2f9aba09a6fa4a46fb1a6a3919b027d9cac5aa6890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
ExtraData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000000025c65b4b379ac37cf78357c4915f73677022eaffc7d49d0a2cf198deebd6ce581af465944ec8b2bbcfccdea1006a5cfa7d9484b5b293b46964c265c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
GasLimit: 4700000,
Difficulty: big.NewInt(1),
Alloc: DecodeMainnet(),
Timestamp: 1544771829,
Alloc: DecodeAllocJson(XDCAllocData),
Timestamp: 1559211559,
}
}
// DefaultTestnetGenesisBlock returns the Ropsten network genesis block.
func DefaultTestnetGenesisBlock() *Genesis {
config := params.TestnetChainConfig
config.XDPoS.V2 = nil
return &Genesis{
Config: params.TestnetChainConfig,
Nonce: 66,
ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"),
GasLimit: 16777216,
Difficulty: big.NewInt(1048576),
Alloc: decodePrealloc(testnetAllocData),
Nonce: 0,
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000003ea0a3555f9b1de983572bff6444aeb1899ec58c4f7900282f3d371d585ab1361205b0940ab1789c942a5885a8844ee5587c8ac5e371fc39ffe618960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
GasLimit: 4700000,
Difficulty: big.NewInt(1),
Alloc: DecodeAllocJson(XDCTestAllocData),
Timestamp: 1560417871,
}
}
@ -386,8 +399,8 @@ func decodePrealloc(data string) GenesisAlloc {
return ga
}
func DecodeMainnet() GenesisAlloc {
mainnetAlloc := GenesisAlloc{}
json.Unmarshal([]byte(XDCAllocData), &mainnetAlloc)
return mainnetAlloc
func DecodeAllocJson(s string) GenesisAlloc {
alloc := GenesisAlloc{}
json.Unmarshal([]byte(s), &alloc)
return alloc
}

File diff suppressed because one or more lines are too long

View file

@ -17,14 +17,15 @@
package core
import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"reflect"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/davecgh/go-spew/spew"

117
core/types/consensus_v2.go Normal file
View 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)
}

View 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
View 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

File diff suppressed because it is too large Load diff

View file

@ -189,6 +189,7 @@ func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
// SetEtherbase sets the etherbase of the miner
func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
log.Info("[PrivateMinerAPI] SetEtherbase", "addr", etherbase)
api.e.SetEtherbase(etherbase)
return true
}

View file

@ -303,7 +303,17 @@ func (b *EthApiBackend) GetVotersRewards(masternodeAddr common.Address) map[comm
number := block.Number().Uint64()
engine := b.GetEngine().(*XDPoS.XDPoS)
foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr
lastCheckpointNumber := number - (number % b.ChainConfig().XDPoS.Epoch) - b.ChainConfig().XDPoS.Epoch // calculate for 2 epochs ago
// calculate for 2 epochs ago
currentCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, block.Number())
if err != nil {
log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for current checkpoint block", "block", block)
}
lastCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, big.NewInt(int64(currentCheckpointNumber-1)))
if err != nil {
log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for last checkpoint block", "block", block)
}
lastCheckpointBlock := chain.GetBlockByNumber(lastCheckpointNumber)
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint

View file

@ -267,7 +267,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
return block, false, err
}
header := block.Header()
sighash, err := wallet.SignHash(accounts.Account{Address: eb}, XDPoS.SigHash(header).Bytes())
sighash, err := wallet.SignHash(accounts.Account{Address: eb}, c.SigHash(header).Bytes())
if err != nil || sighash == nil {
log.Error("Can't get signature hash of m2", "sighash", sighash, "err", err)
return block, false, err
@ -285,6 +285,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
XDPoS1.0 Specific hooks
*/
hooks.AttachConsensusV1Hooks(c, eth.blockchain, chainConfig)
hooks.AttachConsensusV2Hooks(c, eth.blockchain, chainConfig)
eth.txPool.IsSigner = func(address common.Address) bool {
currentHeader := eth.blockchain.CurrentHeader()
@ -296,7 +297,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
// not genesis block
header = parentHeader
}
return c.IsAuthorisedAddress(header, eth.blockchain, address)
return c.IsAuthorisedAddress(eth.blockchain, header, address)
}
}
@ -364,7 +365,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai
// APIs returns the collection of RPC services the ethereum package offers.
// NOTE, some of these services probably need to be moved to somewhere else.
func (s *Ethereum) APIs() []rpc.API {
apis := ethapi.GetAPIs(s.ApiBackend)
apis := ethapi.GetAPIs(s.ApiBackend, s.BlockChain())
// Append any APIs exposed explicitly by the consensus engine
apis = append(apis, s.engine.APIs(s.BlockChain())...)
@ -464,7 +465,7 @@ func (s *Ethereum) ValidateMasternode() (bool, error) {
//check if miner's wallet is in set of validators
c := s.engine.(*XDPoS.XDPoS)
authorized := c.IsAuthorisedAddress(s.blockchain.CurrentHeader(), s.blockchain, eb)
authorized := c.IsAuthorisedAddress(s.blockchain, s.blockchain.CurrentHeader(), eb)
if !authorized {
//This miner doesn't belong to set of validators
return false, nil
@ -506,7 +507,7 @@ func (s *Ethereum) StartStaking(local bool) error {
if XDPoS, ok := s.engine.(*XDPoS.XDPoS); ok {
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
if wallet == nil || err != nil {
log.Error("Etherbase account unavailable locally", "err", err)
log.Error("Etherbase account unavailable locally", "address", eb, "err", err)
return fmt.Errorf("signer missing: %v", err)
}
XDPoS.Authorize(eb, wallet.SignHash)

162
eth/bft/bft_handler.go Normal file
View 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
View 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())
}

View file

@ -36,6 +36,9 @@ import (
"github.com/XinFinOrg/XDPoSChain/params"
)
// proposeBlockHandlerFn is a callback type to handle a block by the consensus
type proposeBlockHandlerFn func(header *types.Header) error
var (
MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request
MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request
@ -114,7 +117,8 @@ type Downloader struct {
blockchain BlockChain
// Callbacks
dropPeer peerDropFn // Drops a peer for misbehaving
dropPeer peerDropFn // Drops a peer for misbehaving
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
// Status
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
@ -199,31 +203,32 @@ type BlockChain interface {
}
// New creates a new downloader to fetch hashes and blocks from remote peers.
func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn) *Downloader {
func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, handleProposedBlock proposeBlockHandlerFn) *Downloader {
if lightchain == nil {
lightchain = chain
}
dl := &Downloader{
mode: mode,
stateDB: stateDb,
mux: mux,
queue: newQueue(),
peers: newPeerSet(),
rttEstimate: uint64(rttMaxEstimate),
rttConfidence: uint64(1000000),
blockchain: chain,
lightchain: lightchain,
dropPeer: dropPeer,
headerCh: make(chan dataPack, 1),
bodyCh: make(chan dataPack, 1),
receiptCh: make(chan dataPack, 1),
bodyWakeCh: make(chan bool, 1),
receiptWakeCh: make(chan bool, 1),
headerProcCh: make(chan []*types.Header, 1),
quitCh: make(chan struct{}),
stateCh: make(chan dataPack),
stateSyncStart: make(chan *stateSync),
mode: mode,
stateDB: stateDb,
mux: mux,
queue: newQueue(),
peers: newPeerSet(),
rttEstimate: uint64(rttMaxEstimate),
rttConfidence: uint64(1000000),
blockchain: chain,
lightchain: lightchain,
dropPeer: dropPeer,
handleProposedBlock: handleProposedBlock,
headerCh: make(chan dataPack, 1),
bodyCh: make(chan dataPack, 1),
receiptCh: make(chan dataPack, 1),
bodyWakeCh: make(chan bool, 1),
receiptWakeCh: make(chan bool, 1),
headerProcCh: make(chan []*types.Header, 1),
quitCh: make(chan struct{}),
stateCh: make(chan dataPack),
stateSyncStart: make(chan *stateSync),
syncStatsState: stateSyncStats{
processed: core.GetTrieSyncProgress(stateDb),
},
@ -1393,7 +1398,13 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error {
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
return errInvalidChain
}
if d.handleProposedBlock != nil {
header := blocks[len(blocks)-1].Header()
err := d.handleProposedBlock(header)
if err != nil {
log.Info("[downloader] handle proposed block has error", "err", err, "block hash", header.Hash(), "number", header.Number)
}
}
return nil
}

View file

@ -19,13 +19,14 @@ package downloader
import (
"errors"
"fmt"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
@ -97,7 +98,7 @@ func newTester() *downloadTester {
tester.stateDb = rawdb.NewMemoryDatabase()
tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00})
tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer)
tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer, tester.handleProposedBlock)
return tester
}
@ -457,6 +458,11 @@ func (dl *downloadTester) dropPeer(id string) {
dl.downloader.UnregisterPeer(id)
}
// an empty handleProposedBlock function
func (dl *downloadTester) handleProposedBlock(header *types.Header) error {
return nil
}
// Config retrieves the blockchain's chain configuration.
func (dl *downloadTester) Config() *params.ChainConfig { return params.TestChainConfig }

View file

@ -477,7 +477,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock()
return p.headerThroughput
}
return ps.idlePeers(62, 64, idle, throughput)
return ps.idlePeers(62, 101, idle, throughput)
}
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
@ -491,7 +491,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock()
return p.blockThroughput
}
return ps.idlePeers(62, 64, idle, throughput)
return ps.idlePeers(62, 101, idle, throughput)
}
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
@ -505,7 +505,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock()
return p.receiptThroughput
}
return ps.idlePeers(63, 64, idle, throughput)
return ps.idlePeers(63, 101, idle, throughput)
}
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
@ -519,7 +519,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
defer p.lock.RUnlock()
return p.stateThroughput
}
return ps.idlePeers(63, 64, idle, throughput)
return ps.idlePeers(63, 101, idle, throughput)
}
// idlePeers retrieves a flat list of all currently idle peers satisfying the

View file

@ -19,10 +19,11 @@ package fetcher
import (
"errors"
"github.com/hashicorp/golang-lru"
"math/rand"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/types"
@ -56,6 +57,9 @@ type bodyRequesterFn func([]common.Hash) error
// headerVerifierFn is a callback type to verify a block's header for fast propagation.
type headerVerifierFn func(header *types.Header) error
// proposeBlockHandlerFn is a callback type to handle a block by the consensus
type proposeBlockHandlerFn func(header *types.Header) error
// blockBroadcasterFn is a callback type for broadcasting a block to connected peers.
type blockBroadcasterFn func(block *types.Block, propagate bool)
@ -133,13 +137,14 @@ type Fetcher struct {
queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports)
knowns *lru.ARCCache
// Callbacks
getBlock blockRetrievalFn // Retrieves a block from the local chain
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
chainHeight chainHeightFn // Retrieves the current chain's height
insertBlock blockInsertFn // Injects a batch of blocks into the chain
prepareBlock blockPrepareFn
dropPeer peerDropFn // Drops a peer for misbehaving
getBlock blockRetrievalFn // Retrieves a block from the local chain
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
chainHeight chainHeightFn // Retrieves the current chain's height
insertBlock blockInsertFn // Injects a batch of blocks into the chain
prepareBlock blockPrepareFn
dropPeer peerDropFn // Drops a peer for misbehaving
// Testing hooks
announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the announce list
@ -151,32 +156,33 @@ type Fetcher struct {
}
// New creates a block fetcher to retrieve blocks based on hash announcements.
func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher {
func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, handleProposedBlock proposeBlockHandlerFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher {
knownBlocks, _ := lru.NewARC(blockLimit)
return &Fetcher{
notify: make(chan *announce),
inject: make(chan *inject),
blockFilter: make(chan chan []*types.Block),
headerFilter: make(chan chan *headerFilterTask),
bodyFilter: make(chan chan *bodyFilterTask),
done: make(chan common.Hash),
quit: make(chan struct{}),
announces: make(map[string]int),
announced: make(map[common.Hash][]*announce),
fetching: make(map[common.Hash]*announce),
fetched: make(map[common.Hash][]*announce),
completing: make(map[common.Hash]*announce),
queue: prque.New(),
queues: make(map[string]int),
queued: make(map[common.Hash]*inject),
knowns: knownBlocks,
getBlock: getBlock,
verifyHeader: verifyHeader,
broadcastBlock: broadcastBlock,
chainHeight: chainHeight,
insertBlock: insertBlock,
prepareBlock: prepareBlock,
dropPeer: dropPeer,
notify: make(chan *announce),
inject: make(chan *inject),
blockFilter: make(chan chan []*types.Block),
headerFilter: make(chan chan *headerFilterTask),
bodyFilter: make(chan chan *bodyFilterTask),
done: make(chan common.Hash),
quit: make(chan struct{}),
announces: make(map[string]int),
announced: make(map[common.Hash][]*announce),
fetching: make(map[common.Hash]*announce),
fetched: make(map[common.Hash][]*announce),
completing: make(map[common.Hash]*announce),
queue: prque.New(),
queues: make(map[string]int),
queued: make(map[common.Hash]*inject),
knowns: knownBlocks,
getBlock: getBlock,
verifyHeader: verifyHeader,
handleProposedBlock: handleProposedBlock,
broadcastBlock: broadcastBlock,
chainHeight: chainHeight,
insertBlock: insertBlock,
prepareBlock: prepareBlock,
dropPeer: dropPeer,
}
}
@ -705,13 +711,13 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
goto again
default:
// Something went very wrong, drop the peer
log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
log.Warn("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
f.dropPeer(peer)
return
}
// Run the actual import and log any issues
if err := f.insertBlock(block); err != nil {
log.Debug("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
log.Warn("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
return
}
@ -721,6 +727,10 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
return
}
}
err = f.handleProposedBlock(block.Header())
if err != nil {
log.Warn("[insert] Unable to handle new proposed block", "err", err, "number", block.Number(), "hash", block.Hash())
}
// If import succeeded, broadcast the block
propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
if !fastBroadCast {

View file

@ -18,13 +18,14 @@ package fetcher
import (
"errors"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
@ -92,7 +93,7 @@ func newTester() *fetcherTester {
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
drops: make(map[string]bool),
}
tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer)
tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.handleProposedBlock, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer)
tester.fetcher.Start()
return tester
@ -111,6 +112,10 @@ func (f *fetcherTester) verifyHeader(header *types.Header) error {
return nil
}
func (f *fetcherTester) handleProposedBlock(header *types.Header) error {
return nil
}
// broadcastBlock is a nop placeholder for the block broadcasting.
func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) {
}
@ -296,6 +301,14 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) {
}
}
func verifyProposeBlockHandlerCalled(t *testing.T, proposedBlockChan chan *types.Header) {
select {
case <-proposedBlockChan:
case <-time.After(50 * time.Millisecond):
t.Fatalf("did not call propose block handler")
}
}
// Tests that a fetcher accepts block announcements and initiates retrievals for
// them, successfully importing into the local chain.
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
@ -317,12 +330,18 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
imported <- block
return nil
}
handleProposedBlockChan := make(chan *types.Header)
tester.fetcher.handleProposedBlock = func(header *types.Header) error {
go func() { handleProposedBlockChan <- header }()
return nil
}
for i := len(hashes) - 2; i >= 0; i-- {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
verifyImportEvent(t, imported, true)
}
verifyImportDone(t, imported)
verifyProposeBlockHandlerCalled(t, handleProposedBlockChan)
}
// Tests that if blocks are announced by multiple peers (or even the same buggy

View file

@ -29,9 +29,11 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/bft"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/fetcher"
"github.com/XinFinOrg/XDPoSChain/ethdb"
@ -80,6 +82,7 @@ type ProtocolManager struct {
downloader *downloader.Downloader
fetcher *fetcher.Fetcher
peers *peerSet
bft *bft.Bfter
SubProtocols []p2p.Protocol
@ -104,6 +107,11 @@ type ProtocolManager struct {
knownTxs *lru.Cache
knowOrderTxs *lru.Cache
knowLendingTxs *lru.Cache
// V2 messages
knownVotes *lru.Cache
knownSyncInfos *lru.Cache
knownTimeouts *lru.Cache
}
// NewProtocolManagerEx add order pool to protocol
@ -123,6 +131,11 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
knownTxs, _ := lru.New(maxKnownTxs)
knowOrderTxs, _ := lru.New(maxKnownOrderTxs)
knowLendingTxs, _ := lru.New(maxKnownLendingTxs)
knownVotes, _ := lru.New(maxKnownVote)
knownSyncInfos, _ := lru.New(maxKnownSyncInfo)
knownTimeouts, _ := lru.New(maxKnownTimeout)
// Create the protocol manager with the base fields
manager := &ProtocolManager{
networkId: networkID,
@ -138,6 +151,9 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
knownTxs: knownTxs,
knowOrderTxs: knowOrderTxs,
knowLendingTxs: knowLendingTxs,
knownVotes: knownVotes,
knownSyncInfos: knownSyncInfos,
knownTimeouts: knownTimeouts,
orderpool: nil,
lendingpool: nil,
orderTxSub: nil,
@ -189,12 +205,25 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
if len(manager.SubProtocols) == 0 {
return nil, errIncompatibleConfig
}
var handleProposedBlock func(header *types.Header) error
if config.XDPoS != nil {
handleProposedBlock = func(header *types.Header) error {
return engine.(*XDPoS.XDPoS).HandleProposedBlock(blockchain, header)
}
} else {
handleProposedBlock = func(header *types.Header) error {
return nil
}
}
// Construct the different synchronisation mechanisms
manager.downloader = downloader.New(mode, chaindb, manager.eventMux, blockchain, nil, manager.removePeer)
manager.downloader = downloader.New(mode, chaindb, manager.eventMux, blockchain, nil, manager.removePeer, handleProposedBlock)
validator := func(header *types.Header) error {
return engine.VerifyHeader(blockchain, header, true)
}
heighter := func() uint64 {
return blockchain.CurrentBlock().NumberU64()
}
@ -217,7 +246,17 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
return manager.blockchain.PrepareBlock(block)
}
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer)
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, handleProposedBlock, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer)
//Define bft function
broadcasts := bft.BroadcastFns{
Vote: manager.BroadcastVote,
Timeout: manager.BroadcastTimeout,
SyncInfo: manager.BroadcastSyncInfo,
}
manager.bft = bft.New(broadcasts, blockchain)
if blockchain.Config().XDPoS != nil {
manager.bft.SetConsensusFuns(engine)
}
return manager, nil
}
@ -253,7 +292,6 @@ func (pm *ProtocolManager) Start(maxPeers int) {
// broadcast transactions
pm.txCh = make(chan core.TxPreEvent, txChanSize)
pm.txSub = pm.txpool.SubscribeTxPreEvent(pm.txCh)
pm.orderTxCh = make(chan core.OrderTxPreEvent, txChanSize)
if pm.orderpool != nil {
pm.orderTxSub = pm.orderpool.SubscribeTxPreEvent(pm.orderTxCh)
@ -808,6 +846,63 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if pm.lendingpool != nil {
pm.lendingpool.AddRemotes(txs)
}
case msg.Code == VoteMsg:
// VoteMsg arrived, make sure we have a valid and fresh chain to handle them
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
break
}
var vote types.Vote
if err := msg.Decode(&vote); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.MarkVote(vote.Hash())
exist, _ := pm.knownVotes.ContainsOrAdd(vote.Hash(), true)
if !exist {
go pm.bft.Vote(&vote)
} else {
log.Debug("Discarded vote, known vote", "vote hash", vote.Hash(), "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round)
}
case msg.Code == TimeoutMsg:
// TimeoutMsg arrived, make sure we have a valid and fresh chain to handle them
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
break
}
var timeout types.Timeout
if err := msg.Decode(&timeout); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.MarkTimeout(timeout.Hash())
exist, _ := pm.knownTimeouts.ContainsOrAdd(timeout.Hash(), true)
if !exist {
go pm.bft.Timeout(&timeout)
} else {
log.Trace("Discarded Timeout, known Timeout", "Signature", timeout.Signature, "hash", timeout.Hash(), "round", timeout.Round)
}
case msg.Code == SyncInfoMsg:
// SyncInfoMsg arrived, make sure we have a valid and fresh chain to handle them
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
break
}
var syncInfo types.SyncInfo
if err := msg.Decode(&syncInfo); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.MarkSyncInfo(syncInfo.Hash())
exist, _ := pm.knownSyncInfos.ContainsOrAdd(syncInfo.Hash(), true)
if !exist {
go pm.bft.SyncInfo(&syncInfo)
} else {
log.Trace("Discarded SyncInfo, known SyncInfo", "hash", syncInfo.Hash())
}
default:
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
@ -859,6 +954,58 @@ func (pm *ProtocolManager) BroadcastTx(hash common.Hash, tx *types.Transaction)
log.Trace("Broadcast transaction", "hash", hash, "recipients", len(peers))
}
// BroadcastVote will propagate a Vote to all peers which are not known to
// already have the given vote.
func (pm *ProtocolManager) BroadcastVote(vote *types.Vote) {
hash := vote.Hash()
peers := pm.peers.PeersWithoutVote(hash)
if len(peers) > 0 {
for _, peer := range peers {
err := peer.SendVote(vote)
if err != nil {
log.Error("[BroadcastVote] Fail to broadcast vote message", "peerId", peer.id, "version", peer.version, "blockNum", vote.ProposedBlockInfo.Number, "err", err)
pm.removePeer(peer.id)
}
}
log.Trace("Propagated Vote", "vote hash", vote.Hash(), "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round, "recipients", len(peers))
}
}
// BroadcastTimeout will propagate a Timeout to all peers which are not known to
// already have the given timeout.
func (pm *ProtocolManager) BroadcastTimeout(timeout *types.Timeout) {
hash := timeout.Hash()
peers := pm.peers.PeersWithoutTimeout(hash)
if len(peers) > 0 {
for _, peer := range peers {
err := peer.SendTimeout(timeout)
if err != nil {
log.Error("[BroadcastTimeout] Fail to broadcast timeout message, remove peer", "peerId", peer.id, "version", peer.version, "timeout", timeout, "err", err)
pm.removePeer(peer.id)
}
}
log.Trace("Propagated Timeout", "hash", hash, "recipients", len(peers))
}
}
// BroadcastSyncInfo will propagate a SyncInfo to all peers which are not known to
// already have the given SyncInfo.
func (pm *ProtocolManager) BroadcastSyncInfo(syncInfo *types.SyncInfo) {
hash := syncInfo.Hash()
peers := pm.peers.PeersWithoutSyncInfo(hash)
if len(peers) > 0 {
for _, peer := range peers {
err := peer.SendSyncInfo(syncInfo)
if err != nil {
log.Error("[BroadcastSyncInfo] Fail to broadcast syncInfo message, remove peer", "peerId", peer.id, "version", peer.version, "syncInfo", syncInfo, "err", err)
pm.removePeer(peer.id)
}
}
log.Trace("Propagated SyncInfo", "hash", hash, "recipients", len(peers))
}
}
// OrderBroadcastTx will propagate a transaction to all peers which are not known to
// already have the given transaction.
func (pm *ProtocolManager) OrderBroadcastTx(hash common.Hash, tx *types.OrderTransaction) {

View file

@ -17,13 +17,14 @@
package eth
import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math"
"math/big"
"math/rand"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"

View file

@ -22,12 +22,13 @@ package eth
import (
"crypto/ecdsa"
"crypto/rand"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"sort"
"sync"
"testing"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"

View 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
}

View file

@ -41,6 +41,9 @@ const (
maxKnownOrderTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
maxKnownLendingTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS)
maxKnownVote = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
maxKnownTimeout = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
maxKnownSyncInfo = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS)
handshakeTimeout = 5 * time.Second
)
@ -66,10 +69,15 @@ type peer struct {
td *big.Int
lock sync.RWMutex
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
knownOrderTxs mapset.Set // Set of order transaction hashes known to be known by this peer
knownLendingTxs mapset.Set // Set of lending transaction hashes known to be known by this peer
knownVote mapset.Set // Set of BFT Vote known to be known by this peer
knownTimeout mapset.Set // Set of BFT timeout known to be known by this peer
knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer`
}
func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
@ -84,6 +92,10 @@ func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
knownBlocks: mapset.NewSet(),
knownOrderTxs: mapset.NewSet(),
knownLendingTxs: mapset.NewSet(),
knownVote: mapset.NewSet(),
knownTimeout: mapset.NewSet(),
knownSyncInfo: mapset.NewSet(),
}
}
@ -157,6 +169,36 @@ func (p *peer) MarkLendingTransaction(hash common.Hash) {
p.knownLendingTxs.Add(hash)
}
// MarkVote marks a vote as known for the peer, ensuring that it
// will never be propagated to this particular peer.
func (p *peer) MarkVote(hash common.Hash) {
// If we reached the memory allowance, drop a previously known transaction hash
for p.knownVote.Cardinality() >= maxKnownVote {
p.knownVote.Pop()
}
p.knownVote.Add(hash)
}
// MarkTimeout marks a timeout as known for the peer, ensuring that it
// will never be propagated to this particular peer.
func (p *peer) MarkTimeout(hash common.Hash) {
// If we reached the memory allowance, drop a previously known transaction hash
for p.knownTimeout.Cardinality() >= maxKnownTimeout {
p.knownTimeout.Pop()
}
p.knownTimeout.Add(hash)
}
// MarkSyncInfo marks a syncInfo as known for the peer, ensuring that it
// will never be propagated to this particular peer.
func (p *peer) MarkSyncInfo(hash common.Hash) {
// If we reached the memory allowance, drop a previously known transaction hash
for p.knownSyncInfo.Cardinality() >= maxKnownSyncInfo {
p.knownSyncInfo.Pop()
}
p.knownSyncInfo.Add(hash)
}
// SendTransactions sends transactions to the peer and includes the hashes
// in its transaction hash set for future reference.
func (p *peer) SendTransactions(txs types.Transactions) error {
@ -256,6 +298,49 @@ func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
}
}
func (p *peer) SendVote(vote *types.Vote) error {
p.knownVote.Add(vote.Hash())
if p.pairRw != nil {
return p2p.Send(p.pairRw, VoteMsg, vote)
} else {
return p2p.Send(p.rw, VoteMsg, vote)
}
}
/*
func (p *peer) AsyncSendVote() {
}
*/
func (p *peer) SendTimeout(timeout *types.Timeout) error {
p.knownTimeout.Add(timeout.Hash())
if p.pairRw != nil {
return p2p.Send(p.pairRw, TimeoutMsg, timeout)
} else {
return p2p.Send(p.rw, TimeoutMsg, timeout)
}
}
/*
func (p *peer) AsyncSendTimeout() {
}
*/
func (p *peer) SendSyncInfo(syncInfo *types.SyncInfo) error {
p.knownSyncInfo.Add(syncInfo.Hash())
if p.pairRw != nil {
return p2p.Send(p.pairRw, SyncInfoMsg, syncInfo)
} else {
return p2p.Send(p.rw, SyncInfoMsg, syncInfo)
}
}
/*
func (p *peer) AsyncSendSyncInfo() {
}
*/
// RequestOneHeader is a wrapper around the header query functions to fetch a
// single header. It is used solely by the fetcher.
func (p *peer) RequestOneHeader(hash common.Hash) error {
@ -486,6 +571,51 @@ func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer {
return list
}
// PeersWithoutVote retrieves a list of peers that do not have a given block in
// their set of known hashes.
func (ps *peerSet) PeersWithoutVote(hash common.Hash) []*peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
list := make([]*peer, 0, len(ps.peers))
for _, p := range ps.peers {
if !p.knownVote.Contains(hash) {
list = append(list, p)
}
}
return list
}
// PeersWithoutTimeout retrieves a list of peers that do not have a given block in
// their set of known hashes.
func (ps *peerSet) PeersWithoutTimeout(hash common.Hash) []*peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
list := make([]*peer, 0, len(ps.peers))
for _, p := range ps.peers {
if !p.knownTimeout.Contains(hash) {
list = append(list, p)
}
}
return list
}
// PeersWithoutSyncInfo retrieves a list of peers that do not have a given block in
// their set of known hashes.
func (ps *peerSet) PeersWithoutSyncInfo(hash common.Hash) []*peer {
ps.lock.RLock()
defer ps.lock.RUnlock()
list := make([]*peer, 0, len(ps.peers))
for _, p := range ps.peers {
if !p.knownSyncInfo.Contains(hash) {
list = append(list, p)
}
}
return list
}
// PeersWithoutTx retrieves a list of peers that do not have a given transaction
// in their set of known hashes.
func (ps *peerSet) OrderPeersWithoutTx(hash common.Hash) []*peer {

View file

@ -30,18 +30,19 @@ import (
// Constants to match up protocol versions and messages
const (
eth62 = 62
eth63 = 63
eth62 = 62
eth63 = 63
xdpos2 = 100
)
// Official short name of the protocol used during capability negotiation.
var ProtocolName = "eth"
// Supported versions of the eth protocol (first is primary).
var ProtocolVersions = []uint{eth63, eth62}
var ProtocolVersions = []uint{xdpos2, eth63, eth62}
// Number of implemented message corresponding to different protocol versions.
var ProtocolLengths = []uint64{17, 8}
var ProtocolLengths = []uint64{227, 17, 8}
const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
@ -63,6 +64,11 @@ const (
NodeDataMsg = 0x0e
GetReceiptsMsg = 0x0f
ReceiptsMsg = 0x10
// Protocol messages belonging to xdpos2/100
VoteMsg = 0xe0
TimeoutMsg = 0xe1
SyncInfoMsg = 0xe2
)
type errCode int

View file

@ -134,7 +134,9 @@ func (pm *ProtocolManager) txsyncLoop() {
func (pm *ProtocolManager) syncer() {
// Start and ensure cleanup of sync mechanisms
pm.fetcher.Start()
pm.bft.Start()
defer pm.fetcher.Stop()
defer pm.bft.Stop()
defer pm.downloader.Terminate()
// Wait for different events to fire synchronisation operations

View file

@ -33,6 +33,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/mclock"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth"
@ -56,6 +57,12 @@ const (
chainHeadChanSize = 10
)
type consensusEngine interface {
// SubscribeForensicsEvent should return an event subscription of
// ForensicsEvent and send events to the given channel.
SubscribeForensicsEvent(chan<- types.ForensicsEvent) event.Subscription
}
type txPool interface {
// SubscribeTxPreEvent should return an event subscription of
// TxPreEvent and send events to the given channel.
@ -140,9 +147,11 @@ func (s *Service) loop() {
// Subscribe to chain events to execute updates on
var blockchain blockChain
var txpool txPool
var engine consensusEngine
if s.eth != nil {
blockchain = s.eth.BlockChain()
txpool = s.eth.TxPool()
engine = s.eth.Engine().(*XDPoS.XDPoS)
} else {
blockchain = s.les.BlockChain()
txpool = s.les.TxPool()
@ -156,11 +165,19 @@ func (s *Service) loop() {
txSub := txpool.SubscribeTxPreEvent(txEventCh)
defer txSub.Unsubscribe()
// Forensics events
forensicsEventCh := make(chan types.ForensicsEvent)
if engine != nil {
forensicsSub := engine.SubscribeForensicsEvent(forensicsEventCh)
defer forensicsSub.Unsubscribe()
}
// Start a goroutine that exhausts the subsciptions to avoid events piling up
var (
quitCh = make(chan struct{})
headCh = make(chan *types.Block, 1)
txCh = make(chan struct{}, 1)
quitCh = make(chan struct{})
headCh = make(chan *types.Block, 1)
txCh = make(chan struct{}, 1)
forensicsCh = make(chan *types.ForensicProof, 1)
)
go func() {
var lastTx mclock.AbsTime
@ -168,6 +185,11 @@ func (s *Service) loop() {
HandleLoop:
for {
select {
case forensics := <-forensicsEventCh:
select {
case forensicsCh <- forensics.ForensicsProof:
default:
}
// Notify of chain head events, but drop if too frequent
case head := <-chainHeadCh:
select {
@ -268,6 +290,10 @@ func (s *Service) loop() {
if err = s.reportPending(conn); err != nil {
log.Warn("Transaction stats report failed", "err", err)
}
case forensicsReport := <-forensicsCh:
if err = s.reportForensics(conn, forensicsReport); err != nil {
log.Error("Forensics proof stats report failed", "err", err)
}
}
}
// Make sure the connection is closed
@ -485,6 +511,13 @@ type blockStats struct {
Uncles uncleStats `json:"uncles"`
}
// blockStats is the information to report about individual blocks.
type latestCommittedBlockStats struct {
Number *big.Int `json:"number"`
Hash common.Hash `json:"hash"`
Round uint64 `json:"round"`
}
// txStats is the information to report about individual transactions.
type txStats struct {
Hash common.Hash `json:"hash"`
@ -513,12 +546,37 @@ func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
"id": s.node,
"block": details,
}
// Get the latest committed block information
if (s.engine.(*XDPoS.XDPoS).EngineV2 != nil) && (s.engine.(*XDPoS.XDPoS).EngineV2.GetLatestCommittedBlockInfo() != nil) {
latestCommittedBlockInfo := s.engine.(*XDPoS.XDPoS).EngineV2.GetLatestCommittedBlockInfo()
stats["latestCommittedBlockInfo"] = &latestCommittedBlockStats{
Number: latestCommittedBlockInfo.Number,
Round: uint64(latestCommittedBlockInfo.Round),
Hash: latestCommittedBlockInfo.Hash,
}
}
report := map[string][]interface{}{
"emit": {"block", stats},
}
return websocket.JSON.Send(conn, report)
}
// reportForensics forward the forensics repors it to the stats server.
func (s *Service) reportForensics(conn *websocket.Conn, forensicsProof *types.ForensicProof) error {
log.Info("Sending Forensics report to ethstats", "ForensicsType", forensicsProof.ForensicsType)
stats := map[string]interface{}{
"id": s.node,
"forensicsProof": forensicsProof,
}
report := map[string][]interface{}{
"emit": {"forensics", stats},
}
return websocket.JSON.Send(conn, report)
}
// assembleBlockStats retrieves any required metadata to report a single block
// and assembles the block stats. If block is nil, the current head is processed.
func (s *Service) assembleBlockStats(block *types.Block) *blockStats {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
go.mod
View file

@ -11,7 +11,7 @@ require (
github.com/btcsuite/winsvc v1.0.0 // indirect
github.com/cespare/cp v1.1.1
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
github.com/deckarep/golang-set v1.8.0
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
github.com/edsrzf/mmap-go v1.0.0
github.com/elastic/gosigar v0.10.5
@ -63,4 +63,5 @@ require (
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
gopkg.in/urfave/cli.v1 v1.20.0
gotest.tools v2.2.0+incompatible
)

5
go.sum
View file

@ -72,6 +72,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M=
@ -118,6 +120,7 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
@ -339,6 +342,7 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw=
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
@ -353,5 +357,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -91,9 +91,9 @@ var Flags = []cli.Flag{
//vmoduleFlag,
//backtraceAtFlag,
//debugFlag,
//pprofFlag,
//pprofAddrFlag,
//pprofPortFlag,
// pprofFlag,
// pprofAddrFlag,
// pprofPortFlag,
//memprofilerateFlag,
//blockprofilerateFlag,
//cpuprofileFlag,

View file

@ -27,6 +27,7 @@ import (
"time"
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
@ -497,12 +498,16 @@ func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args Sen
// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicBlockChainAPI struct {
b Backend
b Backend
chainReader consensus.ChainReader
}
// NewPublicBlockChainAPI creates a new Ethereum blockchain API.
func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI {
return &PublicBlockChainAPI{b}
func NewPublicBlockChainAPI(b Backend, chainReader consensus.ChainReader) *PublicBlockChainAPI {
return &PublicBlockChainAPI{
b,
chainReader,
}
}
// BlockNumber returns the block number of the chain head.
@ -694,11 +699,7 @@ func (s *PublicBlockChainAPI) GetMasternodes(ctx context.Context, b *types.Block
}
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
// Get block epoc latest.
lastCheckpointNumber := prevBlockNumber - (prevBlockNumber % s.b.ChainConfig().XDPoS.Epoch)
prevCheckpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(lastCheckpointNumber))
if prevCheckpointBlock != nil {
masternodes = engine.GetMasternodesFromCheckpointHeader(prevCheckpointBlock.Header(), curBlockNumber, s.b.ChainConfig().XDPoS.Epoch)
}
return engine.GetMasternodesByNumber(s.chainReader, prevBlockNumber), nil
} else {
log.Error("Undefined XDPoS consensus engine")
}
@ -791,7 +792,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd
// Second, Find candidates that have masternode status
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
masternodes = engine.GetMasternodesFromCheckpointHeader(header, block.Number().Uint64(), s.b.ChainConfig().XDPoS.Epoch)
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
result[fieldSuccess] = false
@ -902,7 +903,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
// Second, Find candidates that have masternode status
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
masternodes = engine.GetMasternodesFromCheckpointHeader(header, block.Number().Uint64(), s.b.ChainConfig().XDPoS.Epoch)
masternodes = engine.GetMasternodesFromCheckpointHeader(header)
if len(masternodes) == 0 {
log.Error("Failed to get masternodes", "err", err, "len(masternodes)", len(masternodes), "blockNum", header.Number.Uint64())
result[fieldSuccess] = false
@ -965,23 +966,28 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch
// GetPreviousCheckpointFromEpoch returns header of the previous checkpoint
func (s *PublicBlockChainAPI) GetPreviousCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) {
var checkpointNumber uint64
epoch := s.b.ChainConfig().XDPoS.Epoch
if epochNum == rpc.LatestEpochNumber {
blockNumer := s.b.CurrentBlock().Number().Uint64()
diff := blockNumer % epoch
// checkpoint number
checkpointNumber = blockNumer - diff
epochNum = rpc.EpochNumber(checkpointNumber / epoch)
if diff > 0 {
epochNum += 1
if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok {
currentCheckpointNumber, epochNumber, err := engine.GetCurrentEpochSwitchBlock(s.chainReader, s.b.CurrentBlock().Number())
if err != nil {
log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get current epoch switch block information", "Block", s.b.CurrentBlock(), "Error", err)
}
} else if epochNum < 2 {
checkpointNumber = 0
if epochNum == rpc.LatestEpochNumber {
checkpointNumber = currentCheckpointNumber
epochNum = rpc.EpochNumber(epochNumber)
} else if epochNum < 2 {
checkpointNumber = 0
} else {
blockNumberBeforeCurrentEpochSwitch := currentCheckpointNumber - 1
checkpointNumber, _, err = engine.GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(blockNumberBeforeCurrentEpochSwitch)))
if err != nil {
log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get last epoch switch block information", "Number", blockNumberBeforeCurrentEpochSwitch, "Error", err)
}
}
return rpc.BlockNumber(checkpointNumber), epochNum
} else {
checkpointNumber = epoch * (uint64(epochNum) - 1)
panic("[GetPreviousCheckpointFromEpoch] Error while trying to get XDPoS consensus engine")
}
return rpc.BlockNumber(checkpointNumber), epochNum
}
// getCandidatesFromSmartContract returns all candidates with their capacities at the current time
@ -1301,7 +1307,11 @@ func (s *PublicBlockChainAPI) findNearestSignedBlock(ctx context.Context, b *typ
}
// Get block epoc latest
checkpointNumber := signedBlockNumber - (signedBlockNumber % s.b.ChainConfig().XDPoS.Epoch)
checkpointNumber, _, err := s.b.GetEngine().(*XDPoS.XDPoS).GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(signedBlockNumber)))
if err != nil {
log.Error("[findNearestSignedBlock] Error while trying to get current Epoch switch block", "Number", signedBlockNumber)
}
checkpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber))
if checkpointBlock != nil {
@ -1385,13 +1395,9 @@ func (s *PublicBlockChainAPI) getSigners(ctx context.Context, block *types.Block
var err error
var filterSigners []common.Address
var signers []common.Address
blockNumber := block.Number().Uint64()
// Get block epoc latest.
checkpointNumber := blockNumber - (blockNumber % s.b.ChainConfig().XDPoS.Epoch)
checkpointBlock, _ := s.b.BlockByNumber(ctx, rpc.BlockNumber(checkpointNumber))
masternodes := engine.GetMasternodes(s.chainReader, block.Header())
masternodes := engine.GetMasternodesFromCheckpointHeader(checkpointBlock.Header(), blockNumber, s.b.ChainConfig().XDPoS.Epoch)
signers, err = GetSignersFromBlocks(s.b, block.NumberU64(), block.Hash(), masternodes)
if err != nil {
log.Error("Fail to get signers from block signer SC.", "error", err)

View file

@ -97,7 +97,7 @@ type Backend interface {
GetOrderNonce(address common.Hash) (uint64, error)
}
func GetAPIs(apiBackend Backend) []rpc.API {
func GetAPIs(apiBackend Backend, chainReader consensus.ChainReader) []rpc.API {
nonceLock := new(AddrLocker)
return []rpc.API{
{
@ -108,7 +108,7 @@ func GetAPIs(apiBackend Backend) []rpc.API {
}, {
Namespace: "eth",
Version: "1.0",
Service: NewPublicBlockChainAPI(apiBackend),
Service: NewPublicBlockChainAPI(apiBackend, chainReader),
Public: true,
}, {
Namespace: "eth",

View file

@ -138,6 +138,10 @@ web3._extend({
call: 'XDPoS_getSignersAtHash',
params: 1
}),
new web3._extend.Method({
name: 'getLatestCommittedBlockInfo',
call: 'XDPoS_getLatestCommittedBlockHeader'
}),
],
properties: [
new web3._extend.Property({

View file

@ -176,7 +176,7 @@ func (s *LightDummyAPI) Mining() bool {
// APIs returns the collection of RPC services the ethereum package offers.
// NOTE, some of these services probably need to be moved to somewhere else.
func (s *LightEthereum) APIs() []rpc.API {
return append(ethapi.GetAPIs(s.ApiBackend), []rpc.API{
return append(ethapi.GetAPIs(s.ApiBackend, nil), []rpc.API{
{
Namespace: "eth",
Version: "1.0",

View file

@ -21,12 +21,13 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"net"
"sync"
"time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core"
@ -205,7 +206,7 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protoco
}
if lightSync {
manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, nil, blockchain, removePeer)
manager.downloader = downloader.New(downloader.LightSync, chainDb, manager.eventMux, nil, blockchain, removePeer, manager.handleProposedBlock)
manager.peers.notify((*downloaderPeerNotify)(manager))
manager.fetcher = newLightFetcher(manager)
}
@ -218,6 +219,11 @@ func (pm *ProtocolManager) removePeer(id string) {
pm.peers.Unregister(id)
}
// an empty handleProposedBlock function
func (pm *ProtocolManager) handleProposedBlock(header *types.Header) error {
return nil
}
func (pm *ProtocolManager) Start(maxPeers int) {
pm.maxPeers = maxPeers

33
metrics/config.go Normal file
View 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,
}

View file

@ -8,6 +8,7 @@ import (
"net/http"
"sync"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/metrics"
)
@ -50,6 +51,19 @@ func ExpHandler(r metrics.Registry) http.Handler {
return http.HandlerFunc(e.expHandler)
}
// Setup starts a dedicated metrics server at the given address.
// This function enables metrics reporting separate from pprof.
func Setup(address string) {
m := http.NewServeMux()
m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry))
log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address))
go func() {
if err := http.ListenAndServe(address, m); err != nil {
log.Error("Failure in running metrics server", "err", err)
}
}()
}
func (exp *exp) getInt(name string) *expvar.Int {
var v *expvar.Int
exp.expvarLock.Lock()
@ -111,7 +125,7 @@ func (exp *exp) publishMeter(name string, metric metrics.Meter) {
exp.getInt(name + ".count").Set(m.Count())
exp.getFloat(name + ".one-minute").Set(m.Rate1())
exp.getFloat(name + ".five-minute").Set(m.Rate5())
exp.getFloat(name + ".fifteen-minute").Set((m.Rate15()))
exp.getFloat(name + ".fifteen-minute").Set(m.Rate15())
exp.getFloat(name + ".mean").Set(m.RateMean())
}
@ -134,21 +148,34 @@ func (exp *exp) publishTimer(name string, metric metrics.Timer) {
exp.getFloat(name + ".mean-rate").Set(t.RateMean())
}
func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer) {
t := metric.Snapshot()
ps := t.Percentiles([]float64{50, 75, 95, 99})
exp.getInt(name + ".count").Set(int64(len(t.Values())))
exp.getFloat(name + ".mean").Set(t.Mean())
exp.getInt(name + ".50-percentile").Set(ps[0])
exp.getInt(name + ".75-percentile").Set(ps[1])
exp.getInt(name + ".95-percentile").Set(ps[2])
exp.getInt(name + ".99-percentile").Set(ps[3])
}
func (exp *exp) syncToExpvar() {
exp.registry.Each(func(name string, i interface{}) {
switch i.(type) {
switch i := i.(type) {
case metrics.Counter:
exp.publishCounter(name, i.(metrics.Counter))
exp.publishCounter(name, i)
case metrics.Gauge:
exp.publishGauge(name, i.(metrics.Gauge))
exp.publishGauge(name, i)
case metrics.GaugeFloat64:
exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64))
exp.publishGaugeFloat64(name, i)
case metrics.Histogram:
exp.publishHistogram(name, i.(metrics.Histogram))
exp.publishHistogram(name, i)
case metrics.Meter:
exp.publishMeter(name, i.(metrics.Meter))
exp.publishMeter(name, i)
case metrics.Timer:
exp.publishTimer(name, i.(metrics.Timer))
exp.publishTimer(name, i)
case metrics.ResettingTimer:
exp.publishResettingTimer(name, i)
default:
panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
}

View file

@ -19,9 +19,10 @@ package miner
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
"sync/atomic"
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
"github.com/XinFinOrg/XDPoSChain/XDCx"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"

View file

@ -34,7 +34,6 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/contracts"
"github.com/XinFinOrg/XDPoSChain/core"
@ -59,10 +58,6 @@ const (
chainHeadChanSize = 10
// chainSideChanSize is the size of channel listening to ChainSideEvent.
chainSideChanSize = 10
// timeout waiting for M1
waitPeriod = 10
// timeout for checkpoint.
waitPeriodCheckpoint = 20
txMatchGasLimit = 40000000
)
@ -270,7 +265,14 @@ func (self *worker) update() {
}
defer self.chainHeadSub.Unsubscribe()
defer self.chainSideSub.Unsubscribe()
timeout := time.NewTimer(waitPeriod * time.Second)
// timeout waiting for v1 inital value
// TODO: Read value from config after we decide where is the config
waitPeriod := 10
WaitPeriodCh := self.engine.(*XDPoS.XDPoS).WaitPeriodCh
defer close(WaitPeriodCh)
timeout := time.NewTimer(time.Duration(waitPeriod) * time.Second)
c := make(chan struct{})
finish := make(chan struct{})
defer close(finish)
@ -289,24 +291,31 @@ func (self *worker) update() {
for {
// A real event arrived, process interesting content
select {
case v := <-WaitPeriodCh:
log.Info("[worker] update mine period", "period", v)
waitPeriod = v
timeout.Reset(time.Duration(waitPeriod) * time.Second)
case <-c:
if atomic.LoadInt32(&self.mining) == 1 {
self.commitNewWork()
}
timeout.Reset(waitPeriod * time.Second)
// Handle ChainHeadEvent
timeout.Reset(time.Duration(waitPeriod) * time.Second)
// Handle ChainHeadEvent
case <-self.chainHeadCh:
self.commitNewWork()
timeout.Reset(waitPeriod * time.Second)
timeout.Reset(time.Duration(waitPeriod) * time.Second)
// Handle ChainSideEvent
// Handle ChainSideEvent
case ev := <-self.chainSideCh:
if self.config.XDPoS == nil {
self.uncleMu.Lock()
self.possibleUncles[ev.Block.Hash()] = ev.Block
self.uncleMu.Unlock()
}
// Handle TxPreEvent
// Handle TxPreEvent
case ev := <-self.txCh:
// Apply transaction to the pending state if we're not mining
if atomic.LoadInt32(&self.mining) == 0 {
@ -323,8 +332,10 @@ func (self *worker) update() {
self.commitNewWork()
}
}
case <-self.chainHeadSub.Err():
return
case <-self.chainSideSub.Err():
return
}
@ -381,7 +392,11 @@ func (self *worker) wait() {
}
if work.config.XDPoS != nil {
// epoch block
if (block.NumberU64() % work.config.XDPoS.Epoch) == 0 {
isEpochSwitchBlock, _, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(block.Header())
if err != nil {
log.Error("[wait] fail to check if block is epoch switch block when worker waiting", "BlockNum", block.Number(), "Hash", block.Hash())
}
if isEpochSwitchBlock {
core.CheckpointCh <- 1
}
}
@ -397,8 +412,12 @@ func (self *worker) wait() {
if self.config.XDPoS != nil {
c := self.engine.(*XDPoS.XDPoS)
err = c.HandleProposedBlock(self.chain, block.Header())
if err != nil {
log.Warn("[wait] Unable to handle new proposed block", "err", err, "number", block.Number(), "hash", block.Hash())
}
authorized := c.IsAuthorisedAddress(block.Header(), self.chain, self.coinbase)
authorized := c.IsAuthorisedAddress(self.chain, block.Header(), self.coinbase)
if !authorized {
valid := false
masternodes := c.GetMasternodes(self.chain, block.Header())
@ -416,7 +435,7 @@ func (self *worker) wait() {
// Send tx sign to smart contract blockSigners.
if block.NumberU64()%common.MergeSignRange == 0 || !self.config.IsTIP2019(block.Number()) {
if err := contracts.CreateTransactionSign(self.config, self.eth.TxPool(), self.eth.AccountManager(), block, self.chainDb, self.coinbase); err != nil {
log.Error("Fail to create tx sign for signer", "error", "err")
log.Error("Fail to create tx sign for signer", "error", err)
}
}
}
@ -508,7 +527,15 @@ func (self *worker) commitNewWork() {
defer self.currentMu.Unlock()
tstart := time.Now()
parent := self.chain.CurrentBlock()
c := self.engine.(*XDPoS.XDPoS)
var parent *types.Block
if c != nil {
parent = c.FindParentBlockToAssign(self.chain, self.chain.CurrentBlock())
} else {
parent = self.chain.CurrentBlock()
}
var signers map[common.Address]struct{}
if parent.Hash().Hex() == self.lastParentBlockCommit {
return
@ -520,39 +547,15 @@ func (self *worker) commitNewWork() {
// Only try to commit new work if we are mining
if atomic.LoadInt32(&self.mining) == 1 {
// check if we are right after parent's coinbase in the list
// only go with XDPoS
if self.config.XDPoS != nil {
// get masternodes set from latest checkpoint
c := self.engine.(*XDPoS.XDPoS)
len, preIndex, curIndex, ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
if err != nil {
log.Warn("Failed when trying to commit new work", "err", err)
return
}
if !ok {
log.Info("Not my turn to commit block. Waiting...")
// in case some nodes are down
if preIndex == -1 {
// first block
return
}
if curIndex == -1 {
// you're not allowed to create this block
return
}
h := utils.Hop(len, preIndex, curIndex)
gap := waitPeriod * int64(h)
// Check nearest checkpoint block in hop range.
nearest := self.config.XDPoS.Epoch - (parent.Header().Number.Uint64() % self.config.XDPoS.Epoch)
if uint64(h) >= nearest {
gap = waitPeriodCheckpoint * int64(h)
}
log.Info("Distance from the parent block", "seconds", gap, "hops", h)
waitedTime := time.Now().Unix() - parent.Header().Time.Int64()
if gap > waitedTime {
return
}
log.Info("Wait enough. It's my turn", "waited seconds", waitedTime)
return
}
}
}
@ -581,6 +584,10 @@ func (self *worker) commitNewWork() {
}
if err := self.engine.Prepare(self.chain, header); err != nil {
if err == consensus.ErrNotReadyToPropose {
log.Info("Waiting...", "err", err)
return
}
log.Error("Failed to prepare header for new block", "err", err)
return
}
@ -626,13 +633,19 @@ func (self *worker) commitNewWork() {
lendingFinalizedTradeTransaction *types.Transaction
)
feeCapacity := state.GetTRC21FeeCapacityFromStateWithCache(parent.Root(), work.state)
if self.config.XDPoS != nil && header.Number.Uint64()%self.config.XDPoS.Epoch != 0 {
pending, err := self.eth.TxPool().Pending()
if self.config.XDPoS != nil {
isEpochSwitchBlock, _, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(header)
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
return
log.Error("[commitNewWork] fail to check if block is epoch switch block when fetching pending transactions", "BlockNum", header.Number, "Hash", header.Hash())
}
if !isEpochSwitchBlock {
pending, err := self.eth.TxPool().Pending()
if err != nil {
log.Error("Failed to fetch pending transactions", "err", err)
return
}
txs, specialTxs = types.NewTransactionsByPriceAndNonce(self.current.signer, pending, signers, feeCapacity)
}
txs, specialTxs = types.NewTransactionsByPriceAndNonce(self.current.signer, pending, signers, feeCapacity)
}
if atomic.LoadInt32(&self.mining) == 1 {
wallet, err := self.eth.AccountManager().Find(accounts.Account{Address: self.coinbase})
@ -644,16 +657,20 @@ func (self *worker) commitNewWork() {
XDCX := self.eth.GetXDCX()
XDCXLending := self.eth.GetXDCXLending()
if XDCX != nil && header.Number.Uint64() > self.config.XDPoS.Epoch {
if header.Number.Uint64()%self.config.XDPoS.Epoch == 0 {
err := XDCX.UpdateMediumPriceBeforeEpoch(header.Number.Uint64()/self.config.XDPoS.Epoch, work.tradingState, work.state)
isEpochSwitchBlock, epochNumber, err := self.engine.(*XDPoS.XDPoS).IsEpochSwitch(header)
if err != nil {
log.Error("[commitNewWork] fail to check if block is epoch switch block when performing XDCX and XDCXLending operations", "BlockNum", header.Number, "Hash", header.Hash())
}
if isEpochSwitchBlock {
err := XDCX.UpdateMediumPriceBeforeEpoch(epochNumber, work.tradingState, work.state)
if err != nil {
log.Error("Fail when update medium price last epoch", "error", err)
return
}
}
// won't grasp tx at checkpoint
//https://github.com/XinFinOrg/XDPoSChain-v1/pull/416
if header.Number.Uint64()%self.config.XDPoS.Epoch != 0 {
} else {
// won't grasp tx at checkpoint
//https://github.com/XinFinOrg/XDPoSChain-v1/pull/416
log.Debug("Start processing order pending")
tradingOrderPending, _ := self.eth.OrderPool().Pending()
log.Debug("Start processing order pending", "len", len(tradingOrderPending))
@ -671,6 +688,7 @@ func (self *worker) commitNewWork() {
}
}
}
if len(tradingTxMatches) > 0 {
txMatchBatch := &tradingstate.TxMatchBatch{
Data: tradingTxMatches,
@ -1053,10 +1071,16 @@ func (env *Work) commitTransactions(mux *event.TypeMux, balanceFee map[common.Ad
}
go func(logs []*types.Log, tcount int) {
if len(logs) > 0 {
mux.Post(core.PendingLogsEvent{Logs: logs})
err := mux.Post(core.PendingLogsEvent{Logs: logs})
if err != nil {
log.Warn("[commitTransactions] Error when sending PendingLogsEvent", "LogLength", len(logs))
}
}
if tcount > 0 {
mux.Post(core.PendingStateEvent{})
err := mux.Post(core.PendingStateEvent{})
if err != nil {
log.Warn("[commitTransactions] Error when sending PendingStateEvent", "tcount", tcount)
}
}
}(cpy, env.tcount)
}

View file

@ -29,15 +29,42 @@ const (
)
var (
XDCMainnetGenesisHash = common.HexToHash("9326145f8a2c8c00bbe13afc7d7f3d9c868b5ef39d89f2f4e9390e9720298624") // XDC Mainnet genesis hash to enforce below configs on
XDCMainnetGenesisHash = common.HexToHash("4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1") // XDC Mainnet genesis hash to enforce below configs on
MainnetGenesisHash = common.HexToHash("8d13370621558f4ed0da587934473c0404729f28b0ff1d50e5fdd840457a2f17") // Mainnet genesis hash to enforce below configs on
TestnetGenesisHash = common.HexToHash("dffc8ae3b45965404b4fd73ce7f0e13e822ac0fc23ce7e95b42bc5f1e57023a5") // Testnet genesis hash to enforce below configs on
TestnetGenesisHash = common.HexToHash("bdea512b4f12ff1135ec92c00dc047ffb93890c2ea1aa0eefe9b013d80640075") // Testnet genesis hash to enforce below configs on
DevnetGenesisHash = common.HexToHash("ab6fd3cb7d1a489e03250c7d14c2d6d819a6a528d6380b31e8410951964ef423") // Devnet genesis hash to enforce below configs on
)
var (
XDPoSV2Config = &V2{
SwitchBlock: big.NewInt(9999999999),
CertThreshold: common.MaxMasternodesV2*2/3 + 1,
TimeoutSyncThreshold: 3,
TimeoutPeriod: 60,
WaitPeriod: 10,
MinePeriod: 10,
}
TestXDPoSV2Config = &V2{
SwitchBlock: big.NewInt(900),
CertThreshold: 3,
TimeoutSyncThreshold: 2,
TimeoutPeriod: 10,
WaitPeriod: 1,
MinePeriod: 2,
SkipV2Validation: true,
}
DevnetXDPoSV2Config = &V2{
SwitchBlock: big.NewInt(7218000),
CertThreshold: 6,
TimeoutSyncThreshold: 5,
TimeoutPeriod: 10,
WaitPeriod: 2,
MinePeriod: 2,
}
// XDPoSChain mainnet config
XDCMainnetChainConfig = &ChainConfig{
ChainId: big.NewInt(88),
ChainId: big.NewInt(50),
HomesteadBlock: big.NewInt(1),
EIP150Block: big.NewInt(2),
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
@ -47,10 +74,11 @@ var (
XDPoS: &XDPoSConfig{
Period: 2,
Epoch: 900,
Reward: 250,
Reward: 5000,
RewardCheckpoint: 900,
Gap: 5,
FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068"),
Gap: 450,
FoudationWalletAddr: common.HexToAddress("xdc92a289fe95a85c53b8d0d113cbaef0c1ec98ac65"),
V2: XDPoSV2Config,
},
}
@ -71,17 +99,45 @@ var (
// TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network.
TestnetChainConfig = &ChainConfig{
ChainId: big.NewInt(3),
HomesteadBlock: big.NewInt(0),
ChainId: big.NewInt(51),
HomesteadBlock: big.NewInt(1),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x62e0fde86e34c263e250fbcd5ca4598ba8ca10a1d166c8526bb127e10b313311"),
EIP155Block: big.NewInt(10),
EIP158Block: big.NewInt(10),
ByzantiumBlock: big.NewInt(1700000),
DAOForkSupport: false,
EIP150Block: big.NewInt(2),
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
EIP155Block: big.NewInt(3),
EIP158Block: big.NewInt(3),
ByzantiumBlock: big.NewInt(4),
ConstantinopleBlock: nil,
Ethash: new(EthashConfig),
XDPoS: &XDPoSConfig{
Period: 2,
Epoch: 900,
Reward: 5000,
RewardCheckpoint: 900,
Gap: 450,
FoudationWalletAddr: common.HexToAddress("xdc746249c61f5832c5eed53172776b460491bdcd5c"),
V2: TestXDPoSV2Config,
},
}
// TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network.
DevnetChainConfig = &ChainConfig{
ChainId: big.NewInt(551),
HomesteadBlock: big.NewInt(1),
EIP150Block: big.NewInt(2),
EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
EIP155Block: big.NewInt(3),
EIP158Block: big.NewInt(3),
ByzantiumBlock: big.NewInt(4),
XDPoS: &XDPoSConfig{
Period: 2,
Epoch: 900,
Reward: 5000,
RewardCheckpoint: 900,
Gap: 450,
FoudationWalletAddr: common.HexToAddress("0x746249c61f5832c5eed53172776b460491bdcd5c"),
V2: DevnetXDPoSV2Config,
},
}
// RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network.
@ -99,6 +155,7 @@ var (
XDPoS: &XDPoSConfig{
Period: 15,
Epoch: 30000,
V2: XDPoSV2Config,
},
}
@ -117,11 +174,8 @@ var (
AllXDPoSProtocolChanges = &ChainConfig{big.NewInt(89), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &XDPoSConfig{Period: 0, Epoch: 30000}}
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
TestXDPoSChanConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &XDPoSConfig{Period: 2, Epoch: 900, Reward: 250, RewardCheckpoint: 900, Gap: 890, FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068")}}
// XDPoS config in use for v1 engine only
TestXDPoSMockChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true}}
// XDPoS config with v2 engine after block 10
TestXDPoSMockChainConfigWithV2Engine = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true, V2ConsensusBlockNumber: big.NewInt(10)}}
// XDPoS config with v2 engine after block 901
TestXDPoSMockChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipV1Validation: true, V2: TestXDPoSV2Config, FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068"), Reward: 250}}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil}
TestRules = TestChainConfig.Rules(new(big.Int))
@ -177,14 +231,24 @@ func (c *CliqueConfig) String() string {
// XDPoSConfig is the consensus engine configs for delegated-proof-of-stake based sealing.
type XDPoSConfig struct {
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
Reward uint64 `json:"reward"` // Block reward - unit Ether
RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards.
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
SkipValidation bool //Skip Block Validation for testing purpose
V2ConsensusBlockNumber *big.Int
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
Reward uint64 `json:"reward"` // Block reward - unit Ether
RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards.
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
SkipV1Validation bool //Skip Block Validation for testing purpose, V1 consensus only
V2 *V2 `json:"v2"`
}
type V2 struct {
WaitPeriod int `json:"waitPeriod"` // Miner wait period to check mine event
MinePeriod int `json:"minePeriod"` // Miner mine period to mine a block
SwitchBlock *big.Int `json:"switchBlock"` // v1 to v2 switch block number
TimeoutSyncThreshold int `json:"timeoutSyncThreshold"` // send syncInfo after number of timeout
TimeoutPeriod int `json:"timeoutPeriod"` // Duration in ms
CertThreshold int `json:"certificateThreshold"` // Necessary number of messages from master nodes to form a certificate
SkipV2Validation bool //Skip Block Validation for testing purpose, V2 consensus only
}
// String implements the stringer interface, returning the consensus engine details.
@ -197,7 +261,7 @@ ConsensusVersion will return the consensus version to use for the provided block
TODO: It's a dummy value for now until the 2.0 consensus engine is fully implemented.
*/
func (c *XDPoSConfig) BlockConsensusVersion(num *big.Int) string {
if c.V2ConsensusBlockNumber != nil && num.Cmp(c.V2ConsensusBlockNumber) > 0 {
if c.V2 != nil && c.V2.SwitchBlock != nil && num.Cmp(c.V2.SwitchBlock) > 0 {
return ConsensusEngineVersion2
}
return ConsensusEngineVersion1

View file

@ -21,10 +21,10 @@ import (
)
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 4 // Minor version component of the current release
VersionPatch = 4 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string
VersionMajor = 2 // Major version component of the current release
VersionMinor = 0 // Minor version component of the current release
VersionPatch = 0 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string
)
// Version holds the textual version string.

View file

@ -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)
}