mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
Merge pull request #386 from maticnetwork/rfc35/common-ancestor-approach-units
RFC35/Common Ancestor Approach
This commit is contained in:
commit
ed2d9ae58f
20 changed files with 880 additions and 154 deletions
3
Makefile
3
Makefile
|
|
@ -64,6 +64,9 @@ devtools:
|
|||
env GOBIN= go install github.com/fjl/gencodec@latest
|
||||
env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest
|
||||
env GOBIN= go install ./cmd/abigen
|
||||
PATH=$(GOBIN):$(PATH) go generate ./common
|
||||
PATH=$(GOBIN):$(PATH) go generate ./core/types
|
||||
PATH=$(GOBIN):$(PATH) go generate ./consensus/bor
|
||||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
|
|
@ -23,10 +25,22 @@ type ResponseWithHeight struct {
|
|||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
|
||||
// Checkpoint defines a response object type of bor checkpoint
|
||||
type Checkpoint struct {
|
||||
Proposer common.Address `json:"proposer"`
|
||||
StartBlock *big.Int `json:"start_block"`
|
||||
EndBlock *big.Int `json:"end_block"`
|
||||
RootHash common.Hash `json:"root_hash"`
|
||||
BorChainID string `json:"bor_chain_id"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
//go:generate mockgen -destination=../../tests/bor/mocks/IHeimdallClient.go -package=mocks . IHeimdallClient
|
||||
type IHeimdallClient interface {
|
||||
Fetch(path string, query string) (*ResponseWithHeight, error)
|
||||
FetchWithRetry(path string, query string) (*ResponseWithHeight, error)
|
||||
FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error)
|
||||
FetchLatestCheckpoint() (*Checkpoint, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +90,21 @@ func (h *HeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*Event
|
|||
return eventRecords, nil
|
||||
}
|
||||
|
||||
// FetchLatestCheckpoint fetches the latest bor submitted checkpoint from heimdall
|
||||
func (h *HeimdallClient) FetchLatestCheckpoint() (*Checkpoint, error) {
|
||||
checkpoint := &Checkpoint{}
|
||||
|
||||
response, err := h.Fetch("/checkpoints/latest", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(response.Result, checkpoint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return checkpoint, nil
|
||||
}
|
||||
|
||||
// Fetch fetches response from heimdall
|
||||
func (h *HeimdallClient) Fetch(rawPath string, rawQuery string) (*ResponseWithHeight, error) {
|
||||
u, err := url.Parse(h.urlString)
|
||||
|
|
|
|||
|
|
@ -359,3 +359,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
|
|||
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
|
||||
return b.eth.stateAtTransaction(block, txIndex, reexec)
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) GetCheckpointWhitelist() map[uint64]common.Hash {
|
||||
return b.eth.Downloader().ChainValidator.GetCheckpointWhitelist()
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) PurgeCheckpointWhitelist() {
|
||||
b.eth.Downloader().ChainValidator.PurgeCheckpointWhitelist()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ type Ethereum struct {
|
|||
|
||||
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
|
||||
|
||||
closeCh chan struct{} // Channel to signal the background processes to exit
|
||||
|
||||
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +163,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
|
||||
p2pServer: stack.Server(),
|
||||
closeCh: make(chan struct{}),
|
||||
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +184,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
// END: Bor changes
|
||||
|
||||
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
||||
var dbVer = "<nil>"
|
||||
dbVer := "<nil>"
|
||||
if bcVersion != nil {
|
||||
dbVer = fmt.Sprintf("%d", *bcVersion)
|
||||
}
|
||||
|
|
@ -252,6 +255,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
BloomCache: uint64(cacheLimit),
|
||||
EventMux: eth.eventMux,
|
||||
Checkpoint: checkpoint,
|
||||
EthAPI: ethAPI,
|
||||
PeerRequiredBlocks: config.PeerRequiredBlocks,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -582,8 +586,77 @@ func (s *Ethereum) Start() error {
|
|||
}
|
||||
maxPeers -= s.config.LightPeers
|
||||
}
|
||||
|
||||
// Start the networking layer and the light server if requested
|
||||
s.handler.Start(maxPeers)
|
||||
|
||||
go s.startCheckpointWhitelistService()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartCheckpointWhitelistService starts the goroutine to fetch checkpoints and update the
|
||||
// checkpoint whitelist map.
|
||||
func (s *Ethereum) startCheckpointWhitelistService() {
|
||||
// a shortcut helps with tests and early exit
|
||||
select {
|
||||
case <-s.closeCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// first run the checkpoint whitelist
|
||||
err := s.handleWhitelistCheckpoint()
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrBorConsensusWithoutHeimdall) || errors.Is(err, ErrNotBorConsensus) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Warn("unable to whitelist checkpoint - first run", "err", err)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(100 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
err := s.handleWhitelistCheckpoint()
|
||||
if err != nil {
|
||||
log.Warn("unable to whitelist checkpoint", "err", err)
|
||||
}
|
||||
case <-s.closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotBorConsensus = errors.New("not bor consensus was given")
|
||||
ErrBorConsensusWithoutHeimdall = errors.New("bor consensus without heimdall")
|
||||
)
|
||||
|
||||
// handleWhitelistCheckpoint handles the checkpoint whitelist mechanism.
|
||||
func (s *Ethereum) handleWhitelistCheckpoint() error {
|
||||
ethHandler := (*ethHandler)(s.handler)
|
||||
|
||||
bor, ok := ethHandler.chain.Engine().(*bor.Bor)
|
||||
if !ok {
|
||||
return ErrNotBorConsensus
|
||||
}
|
||||
|
||||
if bor.WithoutHeimdall {
|
||||
return ErrBorConsensusWithoutHeimdall
|
||||
}
|
||||
|
||||
endBlockNum, endBlockHash, err := ethHandler.fetchWhitelistCheckpoint(bor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the checkpoint whitelist map.
|
||||
ethHandler.downloader.ProcessCheckpoint(endBlockNum, endBlockHash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -599,6 +672,9 @@ func (s *Ethereum) Stop() error {
|
|||
s.bloomIndexer.Close()
|
||||
close(s.closeBloomHandler)
|
||||
|
||||
// Close all bg processes
|
||||
close(s.closeCh)
|
||||
|
||||
// closing consensus engine first, as miner has deps on it
|
||||
s.engine.Close()
|
||||
s.txPool.Stop()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader/whitelist"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
|
|
@ -143,6 +144,8 @@ type Downloader struct {
|
|||
quitCh chan struct{} // Quit channel to signal termination
|
||||
quitLock sync.Mutex // Lock to prevent double closes
|
||||
|
||||
ChainValidator
|
||||
|
||||
// Testing hooks
|
||||
syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run
|
||||
bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch
|
||||
|
|
@ -150,6 +153,14 @@ type Downloader struct {
|
|||
chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations)
|
||||
}
|
||||
|
||||
// interface for whitelist service
|
||||
type ChainValidator interface {
|
||||
IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error)
|
||||
ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash)
|
||||
GetCheckpointWhitelist() map[uint64]common.Hash
|
||||
PurgeCheckpointWhitelist()
|
||||
}
|
||||
|
||||
// LightChain encapsulates functions required to synchronise a light chain.
|
||||
type LightChain interface {
|
||||
// HasHeader verifies a header's presence in the local chain.
|
||||
|
|
@ -204,7 +215,7 @@ type BlockChain interface {
|
|||
}
|
||||
|
||||
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
||||
func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||
func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func(), whitelistService ChainValidator) *Downloader {
|
||||
if lightchain == nil {
|
||||
lightchain = chain
|
||||
}
|
||||
|
|
@ -221,6 +232,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain Bl
|
|||
quitCh: make(chan struct{}),
|
||||
SnapSyncer: snap.NewSyncer(stateDb),
|
||||
stateSyncStart: make(chan *stateSync),
|
||||
ChainValidator: whitelistService,
|
||||
}
|
||||
dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success))
|
||||
|
||||
|
|
@ -332,9 +344,11 @@ func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, m
|
|||
case nil, errBusy, errCanceled:
|
||||
return err
|
||||
}
|
||||
|
||||
if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) ||
|
||||
errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) ||
|
||||
errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) {
|
||||
errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) ||
|
||||
errors.Is(err, whitelist.ErrCheckpointMismatch) {
|
||||
log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err)
|
||||
if d.dropPeer == nil {
|
||||
// The dropPeer method is nil when `--copydb` is used for a local copy.
|
||||
|
|
@ -345,10 +359,17 @@ func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, m
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrMergeTransition) {
|
||||
return err // This is an expected fault, don't keep printing it in a spin-loop
|
||||
}
|
||||
log.Warn("Synchronisation failed, retrying", "err", err)
|
||||
|
||||
if errors.Is(err, whitelist.ErrNoRemoteCheckoint) {
|
||||
log.Warn("Doesn't have remote checkpoint yet", "peer", id, "err", err)
|
||||
}
|
||||
|
||||
log.Warn("Synchronisation failed, retrying", "peer", id, "err", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -764,12 +785,24 @@ func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, ui
|
|||
return int64(from), count, span - 1, uint64(max)
|
||||
}
|
||||
|
||||
// curried fetchHeadersByNumber
|
||||
func (d *Downloader) getFetchHeadersByNumber(p *peerConnection) func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
|
||||
return func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
|
||||
return d.fetchHeadersByNumber(p, number, amount, skip, reverse)
|
||||
}
|
||||
}
|
||||
|
||||
// findAncestor tries to locate the common ancestor link of the local chain and
|
||||
// a remote peers blockchain. In the general case when our node was in sync and
|
||||
// on the correct chain, checking the top N links should already get us a match.
|
||||
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
|
||||
// the head links match), we do a binary search to find the common ancestor.
|
||||
func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) {
|
||||
// Check the validity of chain to be downloaded
|
||||
if _, err := d.IsValidChain(remoteHeader, d.getFetchHeadersByNumber(p)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Figure out the valid ancestor range to prevent rewrite attacks
|
||||
var (
|
||||
floor = int64(-1)
|
||||
|
|
@ -1402,6 +1435,13 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
|
|||
// Merge threshold reached, stop importing, but don't roll back
|
||||
rollback = 0
|
||||
|
||||
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd)
|
||||
return ErrMergeTransition
|
||||
}
|
||||
if len(rejected) != 0 {
|
||||
// Merge threshold reached, stop importing, but don't roll back
|
||||
rollback = 0
|
||||
|
||||
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd)
|
||||
return ErrMergeTransition
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader/whitelist"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
|
|
@ -42,6 +43,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// downloadTester is a test simulator for mocking out local block chain.
|
||||
|
|
@ -75,10 +77,15 @@ func newTester() *downloadTester {
|
|||
chain: chain,
|
||||
peers: make(map[string]*downloadTesterPeer),
|
||||
}
|
||||
tester.downloader = New(0, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, nil)
|
||||
|
||||
tester.downloader = New(0, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, nil, whitelist.NewService(10))
|
||||
return tester
|
||||
}
|
||||
|
||||
func (dl *downloadTester) setWhitelist(w ChainValidator) {
|
||||
dl.downloader.ChainValidator = w
|
||||
}
|
||||
|
||||
// terminate aborts any operations on the embedded downloader and releases all
|
||||
// held resources.
|
||||
func (dl *downloadTester) terminate() {
|
||||
|
|
@ -155,7 +162,7 @@ func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) {
|
|||
}
|
||||
|
||||
func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header {
|
||||
var headers = make([]*types.Header, len(rlpdata))
|
||||
headers := make([]*types.Header, len(rlpdata))
|
||||
for i, data := range rlpdata {
|
||||
var h types.Header
|
||||
if err := rlp.DecodeBytes(data, &h); err != nil {
|
||||
|
|
@ -620,6 +627,7 @@ func TestBoundedHeavyForkedSync66Full(t *testing.T) {
|
|||
func TestBoundedHeavyForkedSync66Snap(t *testing.T) {
|
||||
testBoundedHeavyForkedSync(t, eth.ETH66, SnapSync)
|
||||
}
|
||||
|
||||
func TestBoundedHeavyForkedSync66Light(t *testing.T) {
|
||||
testBoundedHeavyForkedSync(t, eth.ETH66, LightSync)
|
||||
}
|
||||
|
|
@ -711,7 +719,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
|||
|
||||
// Create peers of every type
|
||||
tester.newPeer("peer 66", eth.ETH66, chain.blocks[1:])
|
||||
//tester.newPeer("peer 65", eth.ETH67, chain.blocks[1:)
|
||||
// tester.newPeer("peer 65", eth.ETH67, chain.blocks[1:)
|
||||
|
||||
// Synchronise with the requested peer and make sure all blocks were retrieved
|
||||
if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil {
|
||||
|
|
@ -916,6 +924,7 @@ func TestHighTDStarvationAttack66Full(t *testing.T) {
|
|||
func TestHighTDStarvationAttack66Snap(t *testing.T) {
|
||||
testHighTDStarvationAttack(t, eth.ETH66, SnapSync)
|
||||
}
|
||||
|
||||
func TestHighTDStarvationAttack66Light(t *testing.T) {
|
||||
testHighTDStarvationAttack(t, eth.ETH66, LightSync)
|
||||
}
|
||||
|
|
@ -1268,36 +1277,45 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
|
|||
expected []int
|
||||
}{
|
||||
// Remote is way higher. We should ask for the remote head and go backwards
|
||||
{1500, 1000,
|
||||
{
|
||||
1500, 1000,
|
||||
[]int{1323, 1339, 1355, 1371, 1387, 1403, 1419, 1435, 1451, 1467, 1483, 1499},
|
||||
},
|
||||
{15000, 13006,
|
||||
{
|
||||
15000, 13006,
|
||||
[]int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999},
|
||||
},
|
||||
// Remote is pretty close to us. We don't have to fetch as many
|
||||
{1200, 1150,
|
||||
{
|
||||
1200, 1150,
|
||||
[]int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199},
|
||||
},
|
||||
// Remote is equal to us (so on a fork with higher td)
|
||||
// We should get the closest couple of ancestors
|
||||
{1500, 1500,
|
||||
{
|
||||
1500, 1500,
|
||||
[]int{1497, 1499},
|
||||
},
|
||||
// We're higher than the remote! Odd
|
||||
{1000, 1500,
|
||||
{
|
||||
1000, 1500,
|
||||
[]int{997, 999},
|
||||
},
|
||||
// Check some weird edgecases that it behaves somewhat rationally
|
||||
{0, 1500,
|
||||
{
|
||||
0, 1500,
|
||||
[]int{0, 2},
|
||||
},
|
||||
{6000000, 0,
|
||||
{
|
||||
6000000, 0,
|
||||
[]int{5999823, 5999839, 5999855, 5999871, 5999887, 5999903, 5999919, 5999935, 5999951, 5999967, 5999983, 5999999},
|
||||
},
|
||||
{0, 0,
|
||||
{
|
||||
0, 0,
|
||||
[]int{0, 2},
|
||||
},
|
||||
}
|
||||
|
||||
reqs := func(from, count, span int) []int {
|
||||
var r []int
|
||||
num := from
|
||||
|
|
@ -1307,32 +1325,38 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
|
|||
}
|
||||
return r
|
||||
}
|
||||
for i, tt := range testCases {
|
||||
from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight)
|
||||
data := reqs(int(from), count, span)
|
||||
|
||||
if max != uint64(data[len(data)-1]) {
|
||||
t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max)
|
||||
}
|
||||
failed := false
|
||||
if len(data) != len(tt.expected) {
|
||||
failed = true
|
||||
t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data))
|
||||
} else {
|
||||
for j, n := range data {
|
||||
if n != tt.expected[j] {
|
||||
failed = true
|
||||
break
|
||||
for i, tt := range testCases {
|
||||
i := i
|
||||
tt := tt
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight)
|
||||
data := reqs(int(from), count, span)
|
||||
|
||||
if max != uint64(data[len(data)-1]) {
|
||||
t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max)
|
||||
}
|
||||
failed := false
|
||||
if len(data) != len(tt.expected) {
|
||||
failed = true
|
||||
t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data))
|
||||
} else {
|
||||
for j, n := range data {
|
||||
if n != tt.expected[j] {
|
||||
failed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
res := strings.Replace(fmt.Sprint(data), " ", ",", -1)
|
||||
exp := strings.Replace(fmt.Sprint(tt.expected), " ", ",", -1)
|
||||
t.Logf("got: %v\n", res)
|
||||
t.Logf("exp: %v\n", exp)
|
||||
t.Errorf("test %d: wrong values", i)
|
||||
}
|
||||
if failed {
|
||||
res := strings.Replace(fmt.Sprint(data), " ", ",", -1)
|
||||
exp := strings.Replace(fmt.Sprint(tt.expected), " ", ",", -1)
|
||||
t.Logf("got: %v\n", res)
|
||||
t.Logf("exp: %v\n", exp)
|
||||
t.Errorf("test %d: wrong values", i)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1368,3 +1392,114 @@ func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) {
|
|||
assertOwnChain(t, tester, len(chain.blocks))
|
||||
}
|
||||
}
|
||||
|
||||
// whitelistFake is a mock for the chain validator service
|
||||
type whitelistFake struct {
|
||||
// count denotes the number of times the validate function was called
|
||||
count int
|
||||
|
||||
// validate is the dynamic function to be called while syncing
|
||||
validate func(count int) (bool, error)
|
||||
}
|
||||
|
||||
// newWhitelistFake returns a new mock whitelist
|
||||
func newWhitelistFake(validate func(count int) (bool, error)) *whitelistFake {
|
||||
return &whitelistFake{0, validate}
|
||||
}
|
||||
|
||||
// IsValidChain is the mock function which the downloader will use to validate the chain
|
||||
// to be received from a peer.
|
||||
func (w *whitelistFake) IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) {
|
||||
defer func() {
|
||||
w.count++
|
||||
}()
|
||||
return w.validate(w.count)
|
||||
}
|
||||
|
||||
func (w *whitelistFake) ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) {}
|
||||
|
||||
func (w *whitelistFake) GetCheckpointWhitelist() map[uint64]common.Hash {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *whitelistFake) PurgeCheckpointWhitelist() {}
|
||||
|
||||
// TestFakedSyncProgress66WhitelistMismatch tests if in case of whitelisted
|
||||
// checkpoint mismatch with opposite peer, the sync should fail.
|
||||
func TestFakedSyncProgress66WhitelistMismatch(t *testing.T) {
|
||||
protocol := uint(eth.ETH66)
|
||||
mode := FullSync
|
||||
|
||||
tester := newTester()
|
||||
validate := func(count int) (bool, error) {
|
||||
return false, whitelist.ErrCheckpointMismatch
|
||||
}
|
||||
tester.downloader.ChainValidator = newWhitelistFake(validate)
|
||||
|
||||
defer tester.terminate()
|
||||
|
||||
chainA := testChainForkLightA.blocks
|
||||
tester.newPeer("light", protocol, chainA[1:])
|
||||
|
||||
// Synchronise with the peer and make sure all blocks were retrieved
|
||||
if err := tester.sync("light", nil, mode); err == nil {
|
||||
t.Fatal("succeeded attacker synchronisation")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFakedSyncProgress66WhitelistMatch tests if in case of whitelisted
|
||||
// checkpoint match with opposite peer, the sync should succeed.
|
||||
func TestFakedSyncProgress66WhitelistMatch(t *testing.T) {
|
||||
protocol := uint(eth.ETH66)
|
||||
mode := FullSync
|
||||
|
||||
tester := newTester()
|
||||
validate := func(count int) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tester.downloader.ChainValidator = newWhitelistFake(validate)
|
||||
|
||||
defer tester.terminate()
|
||||
|
||||
chainA := testChainForkLightA.blocks
|
||||
tester.newPeer("light", protocol, chainA[1:])
|
||||
|
||||
// Synchronise with the peer and make sure all blocks were retrieved
|
||||
if err := tester.sync("light", nil, mode); err != nil {
|
||||
t.Fatal("succeeded attacker synchronisation")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFakedSyncProgress66NoRemoteCheckpoint tests if in case of missing/invalid
|
||||
// checkpointed blocks with opposite peer, the sync should fail initially but
|
||||
// with the retry mechanism, it should succeed eventually.
|
||||
func TestFakedSyncProgress66NoRemoteCheckpoint(t *testing.T) {
|
||||
protocol := uint(eth.ETH66)
|
||||
mode := FullSync
|
||||
|
||||
tester := newTester()
|
||||
validate := func(count int) (bool, error) {
|
||||
// only return the `ErrNoRemoteCheckoint` error for the first call
|
||||
if count == 0 {
|
||||
return false, whitelist.ErrNoRemoteCheckoint
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
tester.downloader.ChainValidator = newWhitelistFake(validate)
|
||||
|
||||
defer tester.terminate()
|
||||
|
||||
chainA := testChainForkLightA.blocks
|
||||
tester.newPeer("light", protocol, chainA[1:])
|
||||
|
||||
// Synchronise with the peer and make sure all blocks were retrieved
|
||||
// Should fail in first attempt
|
||||
if err := tester.sync("light", nil, mode); err != nil {
|
||||
assert.Equal(t, whitelist.ErrNoRemoteCheckoint, err, "failed synchronisation")
|
||||
}
|
||||
|
||||
// Try syncing again, should succeed
|
||||
if err := tester.sync("light", nil, mode); err != nil {
|
||||
t.Fatal("succeeded attacker synchronisation")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,8 +64,11 @@ type chainData struct {
|
|||
offset int
|
||||
}
|
||||
|
||||
var chain *chainData
|
||||
var emptyChain *chainData
|
||||
var (
|
||||
chain *chainData
|
||||
chainLongerFork *chainData
|
||||
emptyChain *chainData
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Create a chain of blocks to import
|
||||
|
|
@ -75,6 +78,9 @@ func init() {
|
|||
|
||||
blocks, _ = makeChain(targetBlocks, 0, genesis, true)
|
||||
emptyChain = &chainData{blocks, 0}
|
||||
|
||||
chainLongerForkBlocks, _ := makeChain(1024, 0, blocks[len(blocks)-1], false)
|
||||
chainLongerFork = &chainData{chainLongerForkBlocks, 0}
|
||||
}
|
||||
|
||||
func (chain *chainData) headers() []*types.Header {
|
||||
|
|
|
|||
126
eth/downloader/whitelist/service.go
Normal file
126
eth/downloader/whitelist/service.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package whitelist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Checkpoint whitelist
|
||||
type Service struct {
|
||||
m sync.Mutex
|
||||
checkpointWhitelist map[uint64]common.Hash // Checkpoint whitelist, populated by reaching out to heimdall
|
||||
checkpointOrder []uint64 // Checkpoint order, populated by reaching out to heimdall
|
||||
maxCapacity uint
|
||||
}
|
||||
|
||||
func NewService(maxCapacity uint) *Service {
|
||||
return &Service{
|
||||
checkpointWhitelist: make(map[uint64]common.Hash),
|
||||
checkpointOrder: []uint64{},
|
||||
maxCapacity: maxCapacity,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrCheckpointMismatch = errors.New("checkpoint mismatch")
|
||||
ErrNoRemoteCheckoint = errors.New("remote peer doesn't have a checkoint")
|
||||
)
|
||||
|
||||
// IsValidChain checks if the chain we're about to receive from this peer is valid or not
|
||||
// in terms of reorgs. We won't reorg beyond the last bor checkpoint submitted to mainchain.
|
||||
func (w *Service) IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) {
|
||||
// We want to validate the chain by comparing the last checkpointed block
|
||||
// we're storing in `checkpointWhitelist` with the peer's block.
|
||||
|
||||
// Check for availaibility of the last checkpointed block.
|
||||
// This can be also be empty if our heimdall is not responding
|
||||
// or we're running without it.
|
||||
if len(w.checkpointWhitelist) == 0 {
|
||||
// worst case, we don't have the checkpoints in memory
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Fetch the last checkpoint entry
|
||||
lastCheckpointBlockNum := w.checkpointOrder[len(w.checkpointOrder)-1]
|
||||
lastCheckpointBlockHash := w.checkpointWhitelist[lastCheckpointBlockNum]
|
||||
|
||||
// todo: we can extract this as an interface and mock as well or just test IsValidChain in isolation from downloader passing fake fetchHeadersByNumber functions
|
||||
headers, hashes, err := fetchHeadersByNumber(lastCheckpointBlockNum, 1, 0, false)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: last checkpoint %d, err %v", ErrNoRemoteCheckoint, lastCheckpointBlockNum, err)
|
||||
}
|
||||
|
||||
if len(headers) == 0 {
|
||||
return false, fmt.Errorf("%w: last checkpoint %d", ErrNoRemoteCheckoint, lastCheckpointBlockNum)
|
||||
}
|
||||
|
||||
reqBlockNum := headers[0].Number.Uint64()
|
||||
reqBlockHash := hashes[0]
|
||||
|
||||
// Check against the checkpointed blocks
|
||||
if reqBlockNum == lastCheckpointBlockNum && reqBlockHash == lastCheckpointBlockHash {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, ErrCheckpointMismatch
|
||||
}
|
||||
|
||||
func (w *Service) ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) {
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
|
||||
w.enqueueCheckpointWhitelist(endBlockNum, endBlockHash)
|
||||
// If size of checkpoint whitelist map is greater than 10, remove the oldest entry.
|
||||
|
||||
if w.length() > int(w.maxCapacity) {
|
||||
w.dequeueCheckpointWhitelist()
|
||||
}
|
||||
}
|
||||
|
||||
// GetCheckpointWhitelist returns the existing whitelisted
|
||||
// entries of checkpoint of the form block number -> block hash.
|
||||
func (w *Service) GetCheckpointWhitelist() map[uint64]common.Hash {
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
|
||||
return w.checkpointWhitelist
|
||||
}
|
||||
|
||||
// PurgeCheckpointWhitelist purges data from checkpoint whitelist map
|
||||
func (w *Service) PurgeCheckpointWhitelist() {
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
|
||||
w.checkpointWhitelist = make(map[uint64]common.Hash)
|
||||
w.checkpointOrder = make([]uint64, 0)
|
||||
}
|
||||
|
||||
// EnqueueWhitelistBlock enqueues blockNumber, blockHash to the checkpoint whitelist map
|
||||
func (w *Service) enqueueCheckpointWhitelist(key uint64, val common.Hash) {
|
||||
if _, ok := w.checkpointWhitelist[key]; !ok {
|
||||
log.Debug("Enqueing new checkpoint whitelist", "block number", key, "block hash", val)
|
||||
|
||||
w.checkpointWhitelist[key] = val
|
||||
w.checkpointOrder = append(w.checkpointOrder, key)
|
||||
}
|
||||
}
|
||||
|
||||
// DequeueWhitelistBlock dequeues block, blockhash from the checkpoint whitelist map
|
||||
func (w *Service) dequeueCheckpointWhitelist() {
|
||||
if len(w.checkpointOrder) > 0 {
|
||||
log.Debug("Dequeing checkpoint whitelist", "block number", w.checkpointOrder[0], "block hash", w.checkpointWhitelist[w.checkpointOrder[0]])
|
||||
|
||||
delete(w.checkpointWhitelist, w.checkpointOrder[0])
|
||||
w.checkpointOrder = w.checkpointOrder[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// length returns the len of the whitelist.
|
||||
func (w *Service) length() int {
|
||||
return len(w.checkpointWhitelist)
|
||||
}
|
||||
106
eth/downloader/whitelist/service_test.go
Normal file
106
eth/downloader/whitelist/service_test.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package whitelist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
// NewMockService creates a new mock whitelist service
|
||||
func NewMockService(maxCapacity uint) *Service {
|
||||
return &Service{
|
||||
checkpointWhitelist: make(map[uint64]common.Hash),
|
||||
checkpointOrder: []uint64{},
|
||||
maxCapacity: maxCapacity,
|
||||
}
|
||||
}
|
||||
|
||||
// TestWhitelistCheckpoint checks the checkpoint whitelist map queue mechanism
|
||||
func TestWhitelistCheckpoint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := NewMockService(10)
|
||||
for i := 0; i < 10; i++ {
|
||||
s.enqueueCheckpointWhitelist(uint64(i), common.Hash{})
|
||||
}
|
||||
assert.Equal(t, s.length(), 10, "expected 10 items in whitelist")
|
||||
|
||||
s.enqueueCheckpointWhitelist(11, common.Hash{})
|
||||
s.dequeueCheckpointWhitelist()
|
||||
assert.Equal(t, s.length(), 10, "expected 10 items in whitelist")
|
||||
}
|
||||
|
||||
// TestIsValidChain checks che IsValidChain function in isolation
|
||||
// for different cases by providing a mock fetchHeadersByNumber function
|
||||
func TestIsValidChain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := NewMockService(10)
|
||||
|
||||
// case1: no checkpoint whitelist, should consider the chain as valid
|
||||
res, err := s.IsValidChain(nil, nil)
|
||||
assert.NilError(t, err, "expected no error")
|
||||
assert.Equal(t, res, true, "expected chain to be valid")
|
||||
|
||||
// add checkpoint entries and mock fetchHeadersByNumber function
|
||||
s.ProcessCheckpoint(uint64(0), common.Hash{})
|
||||
s.ProcessCheckpoint(uint64(1), common.Hash{})
|
||||
|
||||
assert.Equal(t, s.length(), 2, "expected 2 items in whitelist")
|
||||
|
||||
// create a false function, returning absolutely nothing
|
||||
falseFetchHeadersByNumber := func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// case2: false fetchHeadersByNumber function provided, should consider the chain as invalid
|
||||
// and throw `ErrNoRemoteCheckoint` error
|
||||
res, err = s.IsValidChain(nil, falseFetchHeadersByNumber)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, ErrNoRemoteCheckoint) {
|
||||
t.Fatalf("expected error ErrNoRemoteCheckoint, got %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, res, false, "expected chain to be invalid")
|
||||
|
||||
// case3: correct fetchHeadersByNumber function provided, should consider the chain as valid
|
||||
// create a mock function, returning a the required header
|
||||
fetchHeadersByNumber := func(number uint64, _ int, _ int, _ bool) ([]*types.Header, []common.Hash, error) {
|
||||
hash := common.Hash{}
|
||||
header := types.Header{Number: big.NewInt(0)}
|
||||
|
||||
switch number {
|
||||
case 0:
|
||||
return []*types.Header{&header}, []common.Hash{hash}, nil
|
||||
case 1:
|
||||
header.Number = big.NewInt(1)
|
||||
return []*types.Header{&header}, []common.Hash{hash}, nil
|
||||
case 2:
|
||||
header.Number = big.NewInt(1) // sending wrong header for misamatch
|
||||
return []*types.Header{&header}, []common.Hash{hash}, nil
|
||||
default:
|
||||
return nil, nil, errors.New("invalid number")
|
||||
}
|
||||
}
|
||||
|
||||
res, err = s.IsValidChain(nil, fetchHeadersByNumber)
|
||||
assert.NilError(t, err, "expected no error")
|
||||
assert.Equal(t, res, true, "expected chain to be valid")
|
||||
|
||||
// add one more checkpoint whitelist entry
|
||||
s.ProcessCheckpoint(uint64(2), common.Hash{})
|
||||
assert.Equal(t, s.length(), 3, "expected 3 items in whitelist")
|
||||
|
||||
// case4: correct fetchHeadersByNumber function provided with wrong header
|
||||
// for block number 2. Should consider the chain as invalid and throw an error
|
||||
res, err = s.IsValidChain(nil, fetchHeadersByNumber)
|
||||
assert.Equal(t, err, ErrCheckpointMismatch, "expected checkpoint mismatch error")
|
||||
assert.Equal(t, res, false, "expected chain to be invalid")
|
||||
}
|
||||
|
|
@ -31,11 +31,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader/whitelist"
|
||||
"github.com/ethereum/go-ethereum/eth/fetcher"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -77,15 +79,16 @@ type txPool interface {
|
|||
// handlerConfig is the collection of initialization parameters to create a full
|
||||
// node network handler.
|
||||
type handlerConfig struct {
|
||||
Database ethdb.Database // Database for direct sync insertions
|
||||
Chain *core.BlockChain // Blockchain to serve data from
|
||||
TxPool txPool // Transaction pool to propagate from
|
||||
Merger *consensus.Merger // The manager for eth1/2 transition
|
||||
Network uint64 // Network identifier to adfvertise
|
||||
Sync downloader.SyncMode // Whether to snap or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
|
||||
Database ethdb.Database // Database for direct sync insertions
|
||||
Chain *core.BlockChain // Blockchain to serve data from
|
||||
TxPool txPool // Transaction pool to propagate from
|
||||
Merger *consensus.Merger // The manager for eth1/2 transition
|
||||
Network uint64 // Network identifier to adfvertise
|
||||
Sync downloader.SyncMode // Whether to snap or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
|
||||
EthAPI *ethapi.PublicBlockChainAPI // EthAPI to interact
|
||||
|
||||
PeerRequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
||||
}
|
||||
|
|
@ -111,6 +114,8 @@ type handler struct {
|
|||
peers *peerSet
|
||||
merger *consensus.Merger
|
||||
|
||||
ethAPI *ethapi.PublicBlockChainAPI // EthAPI to interact
|
||||
|
||||
eventMux *event.TypeMux
|
||||
txsCh chan core.NewTxsEvent
|
||||
txsSub event.Subscription
|
||||
|
|
@ -141,6 +146,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
chain: config.Chain,
|
||||
peers: newPeerSet(),
|
||||
merger: config.Merger,
|
||||
ethAPI: config.EthAPI,
|
||||
peerRequiredBlocks: config.PeerRequiredBlocks,
|
||||
quitSync: make(chan struct{}),
|
||||
}
|
||||
|
|
@ -195,7 +201,8 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
// Construct the downloader (long sync) and its backing state bloom if snap
|
||||
// sync is requested. The downloader is responsible for deallocating the state
|
||||
// bloom when it's done.
|
||||
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success)
|
||||
// todo: it'd better to extract maxCapacity into config
|
||||
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success, whitelist.NewService(10))
|
||||
|
||||
// Construct the fetcher (short sync)
|
||||
validator := func(header *types.Header) error {
|
||||
|
|
|
|||
75
eth/handler_bor.go
Normal file
75
eth/handler_bor.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus/bor"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// errCheckpoint is returned when we are unable to fetch the
|
||||
// latest checkpoint from the local heimdall.
|
||||
errCheckpoint = errors.New("failed to fetch latest checkpoint")
|
||||
|
||||
// errMissingCheckpoint is returned when we don't have the
|
||||
// checkpoint blocks locally, yet.
|
||||
errMissingCheckpoint = errors.New("missing checkpoint blocks")
|
||||
|
||||
// errRootHash is returned when we aren't able to calculate the root hash
|
||||
// locally for a range of blocks.
|
||||
errRootHash = errors.New("failed to get local root hash")
|
||||
|
||||
// errCheckpointRootHashMismatch is returned when the local root hash
|
||||
// doesn't match with the root hash in checkpoint.
|
||||
errCheckpointRootHashMismatch = errors.New("checkpoint roothash mismatch")
|
||||
|
||||
// errEndBlock is returned when we're unable to fetch a block locally.
|
||||
errEndBlock = errors.New("failed to get end block")
|
||||
)
|
||||
|
||||
// fetchWhitelistCheckpoint fetched the latest checkpoint from it's local heimdall
|
||||
// and verifies the data against bor data.
|
||||
func (h *ethHandler) fetchWhitelistCheckpoint(bor *bor.Bor) (uint64, common.Hash, error) {
|
||||
// check for checkpoint whitelisting: bor
|
||||
checkpoint, err := bor.HeimdallClient.FetchLatestCheckpoint()
|
||||
if err != nil {
|
||||
log.Debug("Failed to fetch latest checkpoint for whitelisting")
|
||||
return 0, common.Hash{}, errCheckpoint
|
||||
}
|
||||
|
||||
// check if we have the checkpoint blocks
|
||||
head := h.ethAPI.BlockNumber()
|
||||
if head < hexutil.Uint64(checkpoint.EndBlock.Uint64()) {
|
||||
log.Debug("Head block behind checkpoint block", "head", head, "checkpoint end block", checkpoint.EndBlock)
|
||||
return 0, common.Hash{}, errMissingCheckpoint
|
||||
}
|
||||
|
||||
// verify the root hash of checkpoint
|
||||
roothash, err := h.ethAPI.GetRootHash(context.Background(), checkpoint.StartBlock.Uint64(), checkpoint.EndBlock.Uint64())
|
||||
if err != nil {
|
||||
log.Debug("Failed to get root hash of checkpoint while whitelisting")
|
||||
return 0, common.Hash{}, errRootHash
|
||||
}
|
||||
|
||||
if roothash != checkpoint.RootHash.String()[2:] {
|
||||
log.Warn("Checkpoint root hash mismatch while whitelisting", "expected", checkpoint.RootHash.String()[2:], "got", roothash)
|
||||
return 0, common.Hash{}, errCheckpointRootHashMismatch
|
||||
}
|
||||
|
||||
// fetch the end checkpoint block hash
|
||||
block, err := h.ethAPI.GetBlockByNumber(context.Background(), rpc.BlockNumber(checkpoint.EndBlock.Uint64()), false)
|
||||
if err != nil {
|
||||
log.Debug("Failed to get end block hash of checkpoint while whitelisting")
|
||||
return 0, common.Hash{}, errEndBlock
|
||||
}
|
||||
|
||||
hash := fmt.Sprintf("%v", block["hash"])
|
||||
|
||||
return checkpoint.EndBlock.Uint64(), common.HexToHash(hash), nil
|
||||
}
|
||||
3
go.mod
3
go.mod
|
|
@ -28,6 +28,7 @@ require (
|
|||
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||
github.com/go-stack/stack v1.8.0
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0
|
||||
github.com/golang/mock v1.3.1
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/golang/snappy v0.0.4
|
||||
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa
|
||||
|
|
@ -83,5 +84,5 @@ require (
|
|||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
|
||||
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
|
||||
gopkg.in/urfave/cli.v1 v1.20.0
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
gotest.tools v2.2.0+incompatible
|
||||
)
|
||||
|
|
|
|||
1
go.sum
1
go.sum
|
|
@ -194,6 +194,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er
|
|||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
|
|
|||
|
|
@ -2156,6 +2156,17 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) {
|
|||
api.b.SetHead(uint64(number))
|
||||
}
|
||||
|
||||
// GetCheckpointWhitelist retrieves the current checkpoint whitelist
|
||||
// entries (of the form block number -> block hash)
|
||||
func (api *PrivateDebugAPI) GetCheckpointWhitelist() map[uint64]common.Hash {
|
||||
return api.b.GetCheckpointWhitelist()
|
||||
}
|
||||
|
||||
// PurgeCheckpointWhitelist purges the current checkpoint whitelist entries
|
||||
func (api *PrivateDebugAPI) PurgeCheckpointWhitelist() {
|
||||
api.b.PurgeCheckpointWhitelist()
|
||||
}
|
||||
|
||||
// PublicNetAPI offers network related RPC methods
|
||||
type PublicNetAPI struct {
|
||||
net *p2p.Server
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ type Backend interface {
|
|||
GetBorBlockTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
|
||||
GetBorBlockTransactionWithBlockHash(ctx context.Context, txHash common.Hash, blockHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
|
||||
SubscribeChain2HeadEvent(ch chan<- core.Chain2HeadEvent) event.Subscription
|
||||
GetCheckpointWhitelist() map[uint64]common.Hash
|
||||
PurgeCheckpointWhitelist()
|
||||
|
||||
ChainConfig() *params.ChainConfig
|
||||
Engine() consensus.Engine
|
||||
|
|
|
|||
|
|
@ -474,6 +474,16 @@ web3._extend({
|
|||
params: 2,
|
||||
inputFormatter:[web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter],
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'getCheckpointWhitelist',
|
||||
call: 'debug_getCheckpointWhitelist',
|
||||
params: 0,
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'purgeCheckpointWhitelist',
|
||||
call: 'debug_purgeCheckpointWhitelist',
|
||||
params: 0,
|
||||
}),
|
||||
],
|
||||
properties: []
|
||||
});
|
||||
|
|
|
|||
|
|
@ -337,17 +337,24 @@ func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Blo
|
|||
//
|
||||
|
||||
func (b *LesApiBackend) GetBorBlockReceipt(ctx context.Context, hash common.Hash) (*types.Receipt, error) {
|
||||
return nil, errors.New("Not implemented")
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) GetBorBlockLogs(ctx context.Context, hash common.Hash) ([]*types.Log, error) {
|
||||
return nil, errors.New("Not implemented")
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) GetBorBlockTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
|
||||
return nil, common.Hash{}, 0, 0, errors.New("Not implemented")
|
||||
return nil, common.Hash{}, 0, 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) GetBorBlockTransactionWithBlockHash(ctx context.Context, txHash common.Hash, blockHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
|
||||
return nil, common.Hash{}, 0, 0, errors.New("Not implemented")
|
||||
return nil, common.Hash{}, 0, 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) GetCheckpointWhitelist() map[uint64]common.Hash {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *LesApiBackend) PurgeCheckpointWhitelist() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/bor"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
|
|
@ -19,9 +23,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/tests/bor/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -35,7 +36,21 @@ func TestInsertingSpanSizeBlocks(t *testing.T) {
|
|||
chain := init.ethereum.BlockChain()
|
||||
engine := init.ethereum.Engine()
|
||||
_bor := engine.(*bor.Bor)
|
||||
h, heimdallSpan := getMockedHeimdallClient(t)
|
||||
|
||||
defer engine.Close()
|
||||
|
||||
h, heimdallSpan, ctrl := getMockedHeimdallClient(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
_, span := loadSpanFromFile(t)
|
||||
|
||||
h.EXPECT().Close().AnyTimes()
|
||||
h.EXPECT().FetchLatestCheckpoint().Return(&bor.Checkpoint{
|
||||
Proposer: span.SelectedProducers[0].Address,
|
||||
StartBlock: big.NewInt(0),
|
||||
EndBlock: big.NewInt(int64(spanSize)),
|
||||
}, nil).AnyTimes()
|
||||
|
||||
_bor.SetHeimdallClient(h)
|
||||
|
||||
db := init.ethereum.ChainDb()
|
||||
|
|
@ -48,7 +63,6 @@ func TestInsertingSpanSizeBlocks(t *testing.T) {
|
|||
insertNewBlock(t, chain, block)
|
||||
}
|
||||
|
||||
assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, ""))
|
||||
validators, err := _bor.GetCurrentValidators(block.Hash(), spanSize) // check validator set at the first block of new span
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
|
|
@ -67,6 +81,8 @@ func TestFetchStateSyncEvents(t *testing.T) {
|
|||
engine := init.ethereum.Engine()
|
||||
_bor := engine.(*bor.Bor)
|
||||
|
||||
defer engine.Close()
|
||||
|
||||
// A. Insert blocks for 0th sprint
|
||||
db := init.ethereum.ChainDb()
|
||||
block := init.genesis.ToBlock(db)
|
||||
|
|
@ -79,8 +95,12 @@ func TestFetchStateSyncEvents(t *testing.T) {
|
|||
// B. Before inserting 1st block of the next sprint, mock heimdall deps
|
||||
// B.1 Mock /bor/span/1
|
||||
res, _ := loadSpanFromFile(t)
|
||||
h := &mocks.IHeimdallClient{}
|
||||
h.On("FetchWithRetry", spanPath, "").Return(res, nil)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
h := mocks.NewMockIHeimdallClient(ctrl)
|
||||
h.EXPECT().Close().AnyTimes()
|
||||
|
||||
// B.2 Mock State Sync events
|
||||
fromID := uint64(1)
|
||||
|
|
@ -91,14 +111,14 @@ func TestFetchStateSyncEvents(t *testing.T) {
|
|||
sample := getSampleEventRecord(t)
|
||||
sample.Time = time.Unix(to-int64(eventCount+1), 0) // last event.Time will be just < to
|
||||
eventRecords := generateFakeStateSyncEvents(sample, eventCount)
|
||||
h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil)
|
||||
|
||||
// Mock
|
||||
h.EXPECT().FetchWithRetry(spanPath, "").Return(res, nil).AnyTimes()
|
||||
h.EXPECT().FetchStateSyncEvents(fromID, to).Return(eventRecords, nil).AnyTimes()
|
||||
_bor.SetHeimdallClient(h)
|
||||
|
||||
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
|
||||
insertNewBlock(t, chain, block)
|
||||
|
||||
assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, ""))
|
||||
assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to))
|
||||
}
|
||||
|
||||
func TestFetchStateSyncEvents_2(t *testing.T) {
|
||||
|
|
@ -107,10 +127,17 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
|
|||
engine := init.ethereum.Engine()
|
||||
_bor := engine.(*bor.Bor)
|
||||
|
||||
defer _bor.Close()
|
||||
|
||||
// Mock /bor/span/1
|
||||
res, _ := loadSpanFromFile(t)
|
||||
h := &mocks.IHeimdallClient{}
|
||||
h.On("FetchWithRetry", spanPath, "").Return(res, nil)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
h := mocks.NewMockIHeimdallClient(ctrl)
|
||||
h.EXPECT().Close().AnyTimes()
|
||||
h.EXPECT().FetchWithRetry(spanPath, "").Return(res, nil).AnyTimes()
|
||||
|
||||
// Mock State Sync events
|
||||
// at # sprintSize, events are fetched for [fromID, (block-sprint).Time)
|
||||
|
|
@ -128,7 +155,8 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
|
|||
buildStateEvent(sample, 4, 5), // id = 4, time = 5
|
||||
buildStateEvent(sample, 6, 4), // id = 6, time = 4
|
||||
}
|
||||
h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil)
|
||||
|
||||
h.EXPECT().FetchStateSyncEvents(fromID, to).Return(eventRecords, nil).AnyTimes()
|
||||
_bor.SetHeimdallClient(h)
|
||||
|
||||
// Insert blocks for 0th sprint
|
||||
|
|
@ -138,9 +166,9 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
|
|||
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
|
||||
insertNewBlock(t, chain, block)
|
||||
}
|
||||
assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, ""))
|
||||
assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to))
|
||||
|
||||
lastStateID, _ := _bor.GenesisContractsClient.LastStateId(sprintSize)
|
||||
|
||||
// state 6 was not written
|
||||
assert.Equal(t, uint64(4), lastStateID.Uint64())
|
||||
|
||||
|
|
@ -151,12 +179,14 @@ func TestFetchStateSyncEvents_2(t *testing.T) {
|
|||
buildStateEvent(sample, 5, 7),
|
||||
buildStateEvent(sample, 6, 4),
|
||||
}
|
||||
h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil)
|
||||
|
||||
h.EXPECT().FetchStateSyncEvents(fromID, to).Return(eventRecords, nil).AnyTimes()
|
||||
|
||||
for i := sprintSize + 1; i <= spanSize; i++ {
|
||||
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
|
||||
insertNewBlock(t, chain, block)
|
||||
}
|
||||
assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to))
|
||||
|
||||
lastStateID, _ = _bor.GenesisContractsClient.LastStateId(spanSize)
|
||||
assert.Equal(t, uint64(6), lastStateID.Uint64())
|
||||
}
|
||||
|
|
@ -166,7 +196,13 @@ func TestOutOfTurnSigning(t *testing.T) {
|
|||
chain := init.ethereum.BlockChain()
|
||||
engine := init.ethereum.Engine()
|
||||
_bor := engine.(*bor.Bor)
|
||||
h, _ := getMockedHeimdallClient(t)
|
||||
|
||||
defer _bor.Close()
|
||||
|
||||
h, _, ctrl := getMockedHeimdallClient(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
h.EXPECT().Close().AnyTimes()
|
||||
_bor.SetHeimdallClient(h)
|
||||
|
||||
db := init.ethereum.ChainDb()
|
||||
|
|
@ -197,6 +233,7 @@ func TestOutOfTurnSigning(t *testing.T) {
|
|||
bor.CalcProducerDelay(header.Number.Uint64(), 0, init.genesis.Config.Bor))
|
||||
sign(t, header, signerKey, init.genesis.Config.Bor)
|
||||
block = types.NewBlockWithHeader(header)
|
||||
|
||||
_, err = chain.InsertChain([]*types.Block{block})
|
||||
assert.Equal(t,
|
||||
*err.(*bor.WrongDifficultyError),
|
||||
|
|
@ -205,6 +242,7 @@ func TestOutOfTurnSigning(t *testing.T) {
|
|||
header.Difficulty = new(big.Int).SetUint64(expectedDifficulty)
|
||||
sign(t, header, signerKey, init.genesis.Config.Bor)
|
||||
block = types.NewBlockWithHeader(header)
|
||||
|
||||
_, err = chain.InsertChain([]*types.Block{block})
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
|
@ -214,7 +252,14 @@ func TestSignerNotFound(t *testing.T) {
|
|||
chain := init.ethereum.BlockChain()
|
||||
engine := init.ethereum.Engine()
|
||||
_bor := engine.(*bor.Bor)
|
||||
h, _ := getMockedHeimdallClient(t)
|
||||
|
||||
defer _bor.Close()
|
||||
|
||||
h, _, ctrl := getMockedHeimdallClient(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
h.EXPECT().Close().AnyTimes()
|
||||
|
||||
_bor.SetHeimdallClient(h)
|
||||
|
||||
db := init.ethereum.ChainDb()
|
||||
|
|
@ -233,15 +278,16 @@ func TestSignerNotFound(t *testing.T) {
|
|||
bor.UnauthorizedSignerError{Number: 0, Signer: addr.Bytes()})
|
||||
}
|
||||
|
||||
func getMockedHeimdallClient(t *testing.T) (*mocks.IHeimdallClient, *bor.HeimdallSpan) {
|
||||
func getMockedHeimdallClient(t *testing.T) (*mocks.MockIHeimdallClient, *bor.HeimdallSpan, *gomock.Controller) {
|
||||
ctrl := gomock.NewController(t)
|
||||
h := mocks.NewMockIHeimdallClient(ctrl)
|
||||
|
||||
res, heimdallSpan := loadSpanFromFile(t)
|
||||
h := &mocks.IHeimdallClient{}
|
||||
h.On("FetchWithRetry", "bor/span/1", "").Return(res, nil)
|
||||
h.On(
|
||||
"FetchStateSyncEvents",
|
||||
mock.AnythingOfType("uint64"),
|
||||
mock.AnythingOfType("int64")).Return([]*bor.EventRecordWithTime{getSampleEventRecord(t)}, nil)
|
||||
return h, heimdallSpan
|
||||
|
||||
h.EXPECT().FetchWithRetry(spanPath, "").Return(res, nil).AnyTimes()
|
||||
h.EXPECT().FetchStateSyncEvents(gomock.Any(), gomock.Any()).Return(getEventRecords(t), nil).AnyTimes()
|
||||
|
||||
return h, heimdallSpan, ctrl
|
||||
}
|
||||
|
||||
func generateFakeStateSyncEvents(sample *bor.EventRecordWithTime, count int) []*bor.EventRecordWithTime {
|
||||
|
|
@ -276,6 +322,16 @@ func getSampleEventRecord(t *testing.T) *bor.EventRecordWithTime {
|
|||
return _eventRecords[0]
|
||||
}
|
||||
|
||||
func getEventRecords(t *testing.T) []*bor.EventRecordWithTime {
|
||||
res := stateSyncEventsPayload(t)
|
||||
var _eventRecords []*bor.EventRecordWithTime
|
||||
if err := json.Unmarshal(res.Result, &_eventRecords); err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
return _eventRecords
|
||||
}
|
||||
|
||||
// TestEIP1559Transition tests the following:
|
||||
//
|
||||
// 1. A transaction whose gasFeeCap is greater than the baseFee is valid.
|
||||
|
|
|
|||
|
|
@ -1,87 +1,107 @@
|
|||
// Code generated by mockery v2.10.0. DO NOT EDIT.
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: IHeimdallClient)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
bor "github.com/ethereum/go-ethereum/consensus/bor"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// IHeimdallClient is an autogenerated mock type for the IHeimdallClient type
|
||||
type IHeimdallClient struct {
|
||||
mock.Mock
|
||||
// MockIHeimdallClient is a mock of IHeimdallClient interface.
|
||||
type MockIHeimdallClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockIHeimdallClientMockRecorder
|
||||
}
|
||||
|
||||
// Close provides a mock function with given fields:
|
||||
func (_m *IHeimdallClient) Close() {
|
||||
_m.Called()
|
||||
// MockIHeimdallClientMockRecorder is the mock recorder for MockIHeimdallClient.
|
||||
type MockIHeimdallClientMockRecorder struct {
|
||||
mock *MockIHeimdallClient
|
||||
}
|
||||
|
||||
// Fetch provides a mock function with given fields: path, query
|
||||
func (_m *IHeimdallClient) Fetch(path string, query string) (*bor.ResponseWithHeight, error) {
|
||||
ret := _m.Called(path, query)
|
||||
|
||||
var r0 *bor.ResponseWithHeight
|
||||
if rf, ok := ret.Get(0).(func(string, string) *bor.ResponseWithHeight); ok {
|
||||
r0 = rf(path, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*bor.ResponseWithHeight)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(path, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
// NewMockIHeimdallClient creates a new mock instance.
|
||||
func NewMockIHeimdallClient(ctrl *gomock.Controller) *MockIHeimdallClient {
|
||||
mock := &MockIHeimdallClient{ctrl: ctrl}
|
||||
mock.recorder = &MockIHeimdallClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// FetchStateSyncEvents provides a mock function with given fields: fromID, to
|
||||
func (_m *IHeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*bor.EventRecordWithTime, error) {
|
||||
ret := _m.Called(fromID, to)
|
||||
|
||||
var r0 []*bor.EventRecordWithTime
|
||||
if rf, ok := ret.Get(0).(func(uint64, int64) []*bor.EventRecordWithTime); ok {
|
||||
r0 = rf(fromID, to)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*bor.EventRecordWithTime)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(uint64, int64) error); ok {
|
||||
r1 = rf(fromID, to)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockIHeimdallClient) EXPECT() *MockIHeimdallClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// FetchWithRetry provides a mock function with given fields: path, query
|
||||
func (_m *IHeimdallClient) FetchWithRetry(path string, query string) (*bor.ResponseWithHeight, error) {
|
||||
ret := _m.Called(path, query)
|
||||
|
||||
var r0 *bor.ResponseWithHeight
|
||||
if rf, ok := ret.Get(0).(func(string, string) *bor.ResponseWithHeight); ok {
|
||||
r0 = rf(path, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*bor.ResponseWithHeight)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(path, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
// Close mocks base method.
|
||||
func (m *MockIHeimdallClient) Close() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Close")
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockIHeimdallClientMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockIHeimdallClient)(nil).Close))
|
||||
}
|
||||
|
||||
// Fetch mocks base method.
|
||||
func (m *MockIHeimdallClient) Fetch(arg0, arg1 string) (*bor.ResponseWithHeight, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Fetch", arg0, arg1)
|
||||
ret0, _ := ret[0].(*bor.ResponseWithHeight)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Fetch indicates an expected call of Fetch.
|
||||
func (mr *MockIHeimdallClientMockRecorder) Fetch(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockIHeimdallClient)(nil).Fetch), arg0, arg1)
|
||||
}
|
||||
|
||||
// FetchLatestCheckpoint mocks base method.
|
||||
func (m *MockIHeimdallClient) FetchLatestCheckpoint() (*bor.Checkpoint, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FetchLatestCheckpoint")
|
||||
ret0, _ := ret[0].(*bor.Checkpoint)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchLatestCheckpoint indicates an expected call of FetchLatestCheckpoint.
|
||||
func (mr *MockIHeimdallClientMockRecorder) FetchLatestCheckpoint() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchLatestCheckpoint", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchLatestCheckpoint))
|
||||
}
|
||||
|
||||
// FetchStateSyncEvents mocks base method.
|
||||
func (m *MockIHeimdallClient) FetchStateSyncEvents(arg0 uint64, arg1 int64) ([]*bor.EventRecordWithTime, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FetchStateSyncEvents", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*bor.EventRecordWithTime)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchStateSyncEvents indicates an expected call of FetchStateSyncEvents.
|
||||
func (mr *MockIHeimdallClientMockRecorder) FetchStateSyncEvents(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchStateSyncEvents", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchStateSyncEvents), arg0, arg1)
|
||||
}
|
||||
|
||||
// FetchWithRetry mocks base method.
|
||||
func (m *MockIHeimdallClient) FetchWithRetry(arg0, arg1 string) (*bor.ResponseWithHeight, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FetchWithRetry", arg0, arg1)
|
||||
ret0, _ := ret[0].(*bor.ResponseWithHeight)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchWithRetry indicates an expected call of FetchWithRetry.
|
||||
func (mr *MockIHeimdallClientMockRecorder) FetchWithRetry(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchWithRetry", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchWithRetry), arg0, arg1)
|
||||
}
|
||||
|
|
|
|||
7
tests/deps/fake.go
Normal file
7
tests/deps/fake.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package deps
|
||||
|
||||
// it is a fake file to lock deps
|
||||
//nolint:typecheck
|
||||
import (
|
||||
_ "github.com/golang/mock/mockgen/model"
|
||||
)
|
||||
Loading…
Reference in a new issue