mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-10 22:13:47 +00:00
Merge branch 'master' into portal1
This commit is contained in:
commit
69ed1c63e2
136 changed files with 5035 additions and 5940 deletions
|
|
@ -195,7 +195,7 @@ func TextHash(data []byte) []byte {
|
||||||
//
|
//
|
||||||
// This gives context to the signed message and prevents signing of transactions.
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
func TextAndHash(data []byte) ([]byte, string) {
|
func TextAndHash(data []byte) ([]byte, string) {
|
||||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
|
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
hasher.Write([]byte(msg))
|
hasher.Write([]byte(msg))
|
||||||
return hasher.Sum(nil), msg
|
return hasher.Sum(nil), msg
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||||
func TestWatchNewFile(t *testing.T) {
|
func TestWatchNewFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dir, ks := tmpKeyStore(t, false)
|
dir, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Ensure the watcher is started before adding any files.
|
// Ensure the watcher is started before adding any files.
|
||||||
ks.Accounts()
|
ks.Accounts()
|
||||||
|
|
|
||||||
|
|
@ -87,15 +87,6 @@ func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
||||||
return ks
|
return ks
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlaintextKeyStore creates a keystore for the given directory.
|
|
||||||
// Deprecated: Use NewKeyStore.
|
|
||||||
func NewPlaintextKeyStore(keydir string) *KeyStore {
|
|
||||||
keydir, _ = filepath.Abs(keydir)
|
|
||||||
ks := &KeyStore{storage: &keyStorePlain{keydir}}
|
|
||||||
ks.init(keydir)
|
|
||||||
return ks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ks *KeyStore) init(keydir string) {
|
func (ks *KeyStore) init(keydir string) {
|
||||||
// Lock the mutex since the account cache might call back with events
|
// Lock the mutex since the account cache might call back with events
|
||||||
ks.mu.Lock()
|
ks.mu.Lock()
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ var testSigData = make([]byte, 32)
|
||||||
|
|
||||||
func TestKeyStore(t *testing.T) {
|
func TestKeyStore(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dir, ks := tmpKeyStore(t, true)
|
dir, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
a, err := ks.NewAccount("foo")
|
a, err := ks.NewAccount("foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -72,7 +72,7 @@ func TestKeyStore(t *testing.T) {
|
||||||
|
|
||||||
func TestSign(t *testing.T) {
|
func TestSign(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
|
|
@ -89,7 +89,7 @@ func TestSign(t *testing.T) {
|
||||||
|
|
||||||
func TestSignWithPassphrase(t *testing.T) {
|
func TestSignWithPassphrase(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "passwd"
|
pass := "passwd"
|
||||||
acc, err := ks.NewAccount(pass)
|
acc, err := ks.NewAccount(pass)
|
||||||
|
|
@ -117,7 +117,7 @@ func TestSignWithPassphrase(t *testing.T) {
|
||||||
|
|
||||||
func TestTimedUnlock(t *testing.T) {
|
func TestTimedUnlock(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
|
|
@ -152,7 +152,7 @@ func TestTimedUnlock(t *testing.T) {
|
||||||
|
|
||||||
func TestOverrideUnlock(t *testing.T) {
|
func TestOverrideUnlock(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
|
|
@ -193,7 +193,7 @@ func TestOverrideUnlock(t *testing.T) {
|
||||||
// This test should fail under -race if signing races the expiration goroutine.
|
// This test should fail under -race if signing races the expiration goroutine.
|
||||||
func TestSignRace(t *testing.T) {
|
func TestSignRace(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Create a test account.
|
// Create a test account.
|
||||||
a1, err := ks.NewAccount("")
|
a1, err := ks.NewAccount("")
|
||||||
|
|
@ -238,7 +238,7 @@ func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time
|
||||||
func TestWalletNotifierLifecycle(t *testing.T) {
|
func TestWalletNotifierLifecycle(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Create a temporary keystore to test with
|
// Create a temporary keystore to test with
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Ensure that the notification updater is not running yet
|
// Ensure that the notification updater is not running yet
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
|
@ -284,7 +284,7 @@ type walletEvent struct {
|
||||||
// or deleted from the keystore.
|
// or deleted from the keystore.
|
||||||
func TestWalletNotifications(t *testing.T) {
|
func TestWalletNotifications(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Subscribe to the wallet feed and collect events.
|
// Subscribe to the wallet feed and collect events.
|
||||||
var (
|
var (
|
||||||
|
|
@ -346,7 +346,7 @@ func TestWalletNotifications(t *testing.T) {
|
||||||
// TestImportExport tests the import functionality of a keystore.
|
// TestImportExport tests the import functionality of a keystore.
|
||||||
func TestImportECDSA(t *testing.T) {
|
func TestImportECDSA(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t)
|
||||||
key, err := crypto.GenerateKey()
|
key, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generate key: %v", key)
|
t.Fatalf("failed to generate key: %v", key)
|
||||||
|
|
@ -365,7 +365,7 @@ func TestImportECDSA(t *testing.T) {
|
||||||
// TestImportECDSA tests the import and export functionality of a keystore.
|
// TestImportECDSA tests the import and export functionality of a keystore.
|
||||||
func TestImportExport(t *testing.T) {
|
func TestImportExport(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t)
|
||||||
acc, err := ks.NewAccount("old")
|
acc, err := ks.NewAccount("old")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create account: %v", acc)
|
t.Fatalf("failed to create account: %v", acc)
|
||||||
|
|
@ -374,7 +374,7 @@ func TestImportExport(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to export account: %v", acc)
|
t.Fatalf("failed to export account: %v", acc)
|
||||||
}
|
}
|
||||||
_, ks2 := tmpKeyStore(t, true)
|
_, ks2 := tmpKeyStore(t)
|
||||||
if _, err = ks2.Import(json, "old", "old"); err == nil {
|
if _, err = ks2.Import(json, "old", "old"); err == nil {
|
||||||
t.Errorf("importing with invalid password succeeded")
|
t.Errorf("importing with invalid password succeeded")
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +394,7 @@ func TestImportExport(t *testing.T) {
|
||||||
// This test should fail under -race if importing races.
|
// This test should fail under -race if importing races.
|
||||||
func TestImportRace(t *testing.T) {
|
func TestImportRace(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t)
|
||||||
acc, err := ks.NewAccount("old")
|
acc, err := ks.NewAccount("old")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create account: %v", acc)
|
t.Fatalf("failed to create account: %v", acc)
|
||||||
|
|
@ -403,7 +403,7 @@ func TestImportRace(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to export account: %v", acc)
|
t.Fatalf("failed to export account: %v", acc)
|
||||||
}
|
}
|
||||||
_, ks2 := tmpKeyStore(t, true)
|
_, ks2 := tmpKeyStore(t)
|
||||||
var atom atomic.Uint32
|
var atom atomic.Uint32
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
|
|
@ -457,11 +457,7 @@ func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
|
func tmpKeyStore(t *testing.T) (string, *KeyStore) {
|
||||||
d := t.TempDir()
|
d := t.TempDir()
|
||||||
newKs := NewPlaintextKeyStore
|
return d, NewKeyStore(d, veryLightScryptN, veryLightScryptP)
|
||||||
if encrypted {
|
|
||||||
newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
|
|
||||||
}
|
|
||||||
return d, newKs(d)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/usb"
|
"github.com/karalabe/hid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
|
|
@ -109,7 +109,7 @@ func NewTrezorHubWithWebUSB() (*Hub, error) {
|
||||||
|
|
||||||
// newHub creates a new hardware wallet manager for generic USB devices.
|
// newHub creates a new hardware wallet manager for generic USB devices.
|
||||||
func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
|
func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
|
||||||
if !usb.Supported() {
|
if !hid.Supported() {
|
||||||
return nil, errors.New("unsupported platform")
|
return nil, errors.New("unsupported platform")
|
||||||
}
|
}
|
||||||
hub := &Hub{
|
hub := &Hub{
|
||||||
|
|
@ -155,7 +155,7 @@ func (hub *Hub) refreshWallets() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Retrieve the current list of USB wallet devices
|
// Retrieve the current list of USB wallet devices
|
||||||
var devices []usb.DeviceInfo
|
var devices []hid.DeviceInfo
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
||||||
|
|
@ -170,7 +170,7 @@ func (hub *Hub) refreshWallets() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
infos, err := usb.Enumerate(hub.vendorID, 0)
|
infos, err := hid.Enumerate(hub.vendorID, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failcount := hub.enumFails.Add(1)
|
failcount := hub.enumFails.Add(1)
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/usb"
|
"github.com/karalabe/hid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Maximum time between wallet health checks to detect USB unplugs.
|
// Maximum time between wallet health checks to detect USB unplugs.
|
||||||
|
|
@ -79,8 +79,8 @@ type wallet struct {
|
||||||
driver driver // Hardware implementation of the low level device operations
|
driver driver // Hardware implementation of the low level device operations
|
||||||
url *accounts.URL // Textual URL uniquely identifying this wallet
|
url *accounts.URL // Textual URL uniquely identifying this wallet
|
||||||
|
|
||||||
info usb.DeviceInfo // Known USB device infos about the wallet
|
info hid.DeviceInfo // Known USB device infos about the wallet
|
||||||
device usb.Device // USB device advertising itself as a hardware wallet
|
device hid.Device // USB device advertising itself as a hardware wallet
|
||||||
|
|
||||||
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
||||||
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
||||||
|
|
|
||||||
203
beacon/blsync/block_sync.go
Executable file
203
beacon/blsync/block_sync.go
Executable file
|
|
@ -0,0 +1,203 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
ctypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/capella"
|
||||||
|
"github.com/protolambda/zrnt/eth2/configs"
|
||||||
|
"github.com/protolambda/ztyp/tree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// beaconBlockSync implements request.Module; it fetches the beacon blocks belonging
|
||||||
|
// to the validated and prefetch heads.
|
||||||
|
type beaconBlockSync struct {
|
||||||
|
recentBlocks *lru.Cache[common.Hash, *capella.BeaconBlock]
|
||||||
|
locked map[common.Hash]request.ServerAndID
|
||||||
|
serverHeads map[request.Server]common.Hash
|
||||||
|
headTracker headTracker
|
||||||
|
|
||||||
|
lastHeadInfo types.HeadInfo
|
||||||
|
chainHeadFeed *event.Feed
|
||||||
|
}
|
||||||
|
|
||||||
|
type headTracker interface {
|
||||||
|
PrefetchHead() types.HeadInfo
|
||||||
|
ValidatedHead() (types.SignedHeader, bool)
|
||||||
|
ValidatedFinality() (types.FinalityUpdate, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBeaconBlockSync returns a new beaconBlockSync.
|
||||||
|
func newBeaconBlockSync(headTracker headTracker, chainHeadFeed *event.Feed) *beaconBlockSync {
|
||||||
|
return &beaconBlockSync{
|
||||||
|
headTracker: headTracker,
|
||||||
|
chainHeadFeed: chainHeadFeed,
|
||||||
|
recentBlocks: lru.NewCache[common.Hash, *capella.BeaconBlock](10),
|
||||||
|
locked: make(map[common.Hash]request.ServerAndID),
|
||||||
|
serverHeads: make(map[request.Server]common.Hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *beaconBlockSync) Process(requester request.Requester, events []request.Event) {
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||||
|
sid, req, resp := event.RequestInfo()
|
||||||
|
blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
|
||||||
|
if resp != nil {
|
||||||
|
s.recentBlocks.Add(blockRoot, resp.(*capella.BeaconBlock))
|
||||||
|
}
|
||||||
|
if s.locked[blockRoot] == sid {
|
||||||
|
delete(s.locked, blockRoot)
|
||||||
|
}
|
||||||
|
case sync.EvNewHead:
|
||||||
|
s.serverHeads[event.Server] = event.Data.(types.HeadInfo).BlockRoot
|
||||||
|
case request.EvUnregistered:
|
||||||
|
delete(s.serverHeads, event.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.updateEventFeed()
|
||||||
|
// request validated head block if unavailable and not yet requested
|
||||||
|
if vh, ok := s.headTracker.ValidatedHead(); ok {
|
||||||
|
s.tryRequestBlock(requester, vh.Header.Hash(), false)
|
||||||
|
}
|
||||||
|
// request prefetch head if the given server has announced it
|
||||||
|
if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) {
|
||||||
|
s.tryRequestBlock(requester, prefetchHead, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *beaconBlockSync) tryRequestBlock(requester request.Requester, blockRoot common.Hash, needSameHead bool) {
|
||||||
|
if _, ok := s.recentBlocks.Get(blockRoot); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := s.locked[blockRoot]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, server := range requester.CanSendTo() {
|
||||||
|
if needSameHead && (s.serverHeads[server] != blockRoot) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id := requester.Send(server, sync.ReqBeaconBlock(blockRoot))
|
||||||
|
s.locked[blockRoot] = request.ServerAndID{Server: server, ID: id}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockHeadInfo(block *capella.BeaconBlock) types.HeadInfo {
|
||||||
|
if block == nil {
|
||||||
|
return types.HeadInfo{}
|
||||||
|
}
|
||||||
|
return types.HeadInfo{Slot: uint64(block.Slot), BlockRoot: beaconBlockHash(block)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// beaconBlockHash calculates the hash of a beacon block.
|
||||||
|
func beaconBlockHash(beaconBlock *capella.BeaconBlock) common.Hash {
|
||||||
|
return common.Hash(beaconBlock.HashTreeRoot(configs.Mainnet, tree.GetHashFn()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getExecBlock extracts the execution block from the beacon block's payload.
|
||||||
|
func getExecBlock(beaconBlock *capella.BeaconBlock) (*ctypes.Block, error) {
|
||||||
|
payload := &beaconBlock.Body.ExecutionPayload
|
||||||
|
txs := make([]*ctypes.Transaction, len(payload.Transactions))
|
||||||
|
for i, opaqueTx := range payload.Transactions {
|
||||||
|
var tx ctypes.Transaction
|
||||||
|
if err := tx.UnmarshalBinary(opaqueTx); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tx %d: %v", i, err)
|
||||||
|
}
|
||||||
|
txs[i] = &tx
|
||||||
|
}
|
||||||
|
withdrawals := make([]*ctypes.Withdrawal, len(payload.Withdrawals))
|
||||||
|
for i, w := range payload.Withdrawals {
|
||||||
|
withdrawals[i] = &ctypes.Withdrawal{
|
||||||
|
Index: uint64(w.Index),
|
||||||
|
Validator: uint64(w.ValidatorIndex),
|
||||||
|
Address: common.Address(w.Address),
|
||||||
|
Amount: uint64(w.Amount),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wroot := ctypes.DeriveSha(ctypes.Withdrawals(withdrawals), trie.NewStackTrie(nil))
|
||||||
|
execHeader := &ctypes.Header{
|
||||||
|
ParentHash: common.Hash(payload.ParentHash),
|
||||||
|
UncleHash: ctypes.EmptyUncleHash,
|
||||||
|
Coinbase: common.Address(payload.FeeRecipient),
|
||||||
|
Root: common.Hash(payload.StateRoot),
|
||||||
|
TxHash: ctypes.DeriveSha(ctypes.Transactions(txs), trie.NewStackTrie(nil)),
|
||||||
|
ReceiptHash: common.Hash(payload.ReceiptsRoot),
|
||||||
|
Bloom: ctypes.Bloom(payload.LogsBloom),
|
||||||
|
Difficulty: common.Big0,
|
||||||
|
Number: new(big.Int).SetUint64(uint64(payload.BlockNumber)),
|
||||||
|
GasLimit: uint64(payload.GasLimit),
|
||||||
|
GasUsed: uint64(payload.GasUsed),
|
||||||
|
Time: uint64(payload.Timestamp),
|
||||||
|
Extra: []byte(payload.ExtraData),
|
||||||
|
MixDigest: common.Hash(payload.PrevRandao), // reused in merge
|
||||||
|
Nonce: ctypes.BlockNonce{}, // zero
|
||||||
|
BaseFee: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(),
|
||||||
|
WithdrawalsHash: &wroot,
|
||||||
|
}
|
||||||
|
execBlock := ctypes.NewBlockWithHeader(execHeader).WithBody(txs, nil).WithWithdrawals(withdrawals)
|
||||||
|
if execBlockHash := execBlock.Hash(); execBlockHash != common.Hash(payload.BlockHash) {
|
||||||
|
return execBlock, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", common.Hash(payload.BlockHash), execBlockHash)
|
||||||
|
}
|
||||||
|
return execBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *beaconBlockSync) updateEventFeed() {
|
||||||
|
head, ok := s.headTracker.ValidatedHead()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
finality, ok := s.headTracker.ValidatedFinality() //TODO fetch directly if subscription does not deliver
|
||||||
|
if !ok || head.Header.Epoch() != finality.Attested.Header.Epoch() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
validatedHead := head.Header.Hash()
|
||||||
|
headBlock, ok := s.recentBlocks.Get(validatedHead)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
headInfo := blockHeadInfo(headBlock)
|
||||||
|
if headInfo == s.lastHeadInfo {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.lastHeadInfo = headInfo
|
||||||
|
// new head block and finality info available; extract executable data and send event to feed
|
||||||
|
execBlock, err := getExecBlock(headBlock)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error extracting execution block from validated beacon block", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.chainHeadFeed.Send(types.ChainHeadEvent{
|
||||||
|
HeadBlock: engine.BlockToExecutableData(execBlock, nil, nil).ExecutionPayload,
|
||||||
|
Finalized: common.Hash(finality.Finalized.PayloadHeader.BlockHash),
|
||||||
|
})
|
||||||
|
}
|
||||||
160
beacon/blsync/block_sync_test.go
Normal file
160
beacon/blsync/block_sync_test.go
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/capella"
|
||||||
|
"github.com/protolambda/zrnt/eth2/configs"
|
||||||
|
"github.com/protolambda/ztyp/tree"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testServer1 = "testServer1"
|
||||||
|
testServer2 = "testServer2"
|
||||||
|
|
||||||
|
testBlock1 = &capella.BeaconBlock{
|
||||||
|
Slot: 123,
|
||||||
|
Body: capella.BeaconBlockBody{
|
||||||
|
ExecutionPayload: capella.ExecutionPayload{BlockNumber: 456},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testBlock2 = &capella.BeaconBlock{
|
||||||
|
Slot: 124,
|
||||||
|
Body: capella.BeaconBlockBody{
|
||||||
|
ExecutionPayload: capella.ExecutionPayload{BlockNumber: 457},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
eb1, _ := getExecBlock(testBlock1)
|
||||||
|
testBlock1.Body.ExecutionPayload.BlockHash = tree.Root(eb1.Hash())
|
||||||
|
eb2, _ := getExecBlock(testBlock2)
|
||||||
|
testBlock2.Body.ExecutionPayload.BlockHash = tree.Root(eb2.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockSync(t *testing.T) {
|
||||||
|
ht := &testHeadTracker{}
|
||||||
|
eventFeed := new(event.Feed)
|
||||||
|
blockSync := newBeaconBlockSync(ht, eventFeed)
|
||||||
|
headCh := make(chan types.ChainHeadEvent, 16)
|
||||||
|
eventFeed.Subscribe(headCh)
|
||||||
|
ts := sync.NewTestScheduler(t, blockSync)
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
|
||||||
|
expHeadBlock := func(tci int, expHead *capella.BeaconBlock) {
|
||||||
|
var expNumber, headNumber uint64
|
||||||
|
if expHead != nil {
|
||||||
|
expNumber = uint64(expHead.Body.ExecutionPayload.BlockNumber)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case event := <-headCh:
|
||||||
|
headNumber = event.HeadBlock.Number
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if headNumber != expNumber {
|
||||||
|
t.Errorf("Wrong head block in test case #%d (expected block number %d, got %d)", tci, expNumber, headNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no block requests expected until head tracker knows about a head
|
||||||
|
ts.Run(1)
|
||||||
|
expHeadBlock(1, nil)
|
||||||
|
|
||||||
|
// set block 1 as prefetch head, announced by server 2
|
||||||
|
head1 := blockHeadInfo(testBlock1)
|
||||||
|
ht.prefetch = head1
|
||||||
|
ts.ServerEvent(sync.EvNewHead, testServer2, head1)
|
||||||
|
// expect request to server 2 which has announced the head
|
||||||
|
ts.Run(2, testServer2, sync.ReqBeaconBlock(head1.BlockRoot))
|
||||||
|
|
||||||
|
// valid response
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testBlock1)
|
||||||
|
ts.AddAllowance(testServer2, 1)
|
||||||
|
ts.Run(3)
|
||||||
|
// head block still not expected as the fetched block is not the validated head yet
|
||||||
|
expHeadBlock(3, nil)
|
||||||
|
|
||||||
|
// set as validated head, expect no further requests but block 1 set as head block
|
||||||
|
ht.validated.Header = blockHeader(testBlock1)
|
||||||
|
ts.Run(4)
|
||||||
|
expHeadBlock(4, testBlock1)
|
||||||
|
|
||||||
|
// set block 2 as prefetch head, announced by server 1
|
||||||
|
head2 := blockHeadInfo(testBlock2)
|
||||||
|
ht.prefetch = head2
|
||||||
|
ts.ServerEvent(sync.EvNewHead, testServer1, head2)
|
||||||
|
// expect request to server 1
|
||||||
|
ts.Run(5, testServer1, sync.ReqBeaconBlock(head2.BlockRoot))
|
||||||
|
|
||||||
|
// req2 fails, no further requests expected because server 2 has not announced it
|
||||||
|
ts.RequestEvent(request.EvFail, ts.Request(5, 1), nil)
|
||||||
|
ts.Run(6)
|
||||||
|
|
||||||
|
// set as validated head before retrieving block; now it's assumed to be available from server 2 too
|
||||||
|
ht.validated.Header = blockHeader(testBlock2)
|
||||||
|
// expect req2 retry to server 2
|
||||||
|
ts.Run(7, testServer2, sync.ReqBeaconBlock(head2.BlockRoot))
|
||||||
|
// now head block should be unavailable again
|
||||||
|
expHeadBlock(4, nil)
|
||||||
|
|
||||||
|
// valid response, now head block should be block 2 immediately as it is already validated
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testBlock2)
|
||||||
|
ts.Run(8)
|
||||||
|
expHeadBlock(5, testBlock2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockHeader(block *capella.BeaconBlock) types.Header {
|
||||||
|
return types.Header{
|
||||||
|
Slot: uint64(block.Slot),
|
||||||
|
ProposerIndex: uint64(block.ProposerIndex),
|
||||||
|
ParentRoot: common.Hash(block.ParentRoot),
|
||||||
|
StateRoot: common.Hash(block.StateRoot),
|
||||||
|
BodyRoot: common.Hash(block.Body.HashTreeRoot(configs.Mainnet, tree.GetHashFn())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testHeadTracker struct {
|
||||||
|
prefetch types.HeadInfo
|
||||||
|
validated types.SignedHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *testHeadTracker) PrefetchHead() types.HeadInfo {
|
||||||
|
return h.prefetch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *testHeadTracker) ValidatedHead() (types.SignedHeader, bool) {
|
||||||
|
return h.validated, h.validated.Header != (types.Header{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add test case for finality
|
||||||
|
func (h *testHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
|
return types.FinalityUpdate{
|
||||||
|
Attested: types.HeaderWithExecProof{Header: h.validated.Header},
|
||||||
|
Finalized: types.HeaderWithExecProof{PayloadHeader: &capella.ExecutionPayloadHeader{}},
|
||||||
|
Signature: h.validated.Signature,
|
||||||
|
SignatureSlot: h.validated.SignatureSlot,
|
||||||
|
}, h.validated.Header != (types.Header{})
|
||||||
|
}
|
||||||
103
beacon/blsync/client.go
Normal file
103
beacon/blsync/client.go
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/api"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
scheduler *request.Scheduler
|
||||||
|
chainHeadFeed *event.Feed
|
||||||
|
urls []string
|
||||||
|
customHeader map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ctx *cli.Context) *Client {
|
||||||
|
if !ctx.IsSet(utils.BeaconApiFlag.Name) {
|
||||||
|
utils.Fatalf("Beacon node light client API URL not specified")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
chainConfig = makeChainConfig(ctx)
|
||||||
|
customHeader = make(map[string]string)
|
||||||
|
)
|
||||||
|
for _, s := range ctx.StringSlice(utils.BeaconApiHeaderFlag.Name) {
|
||||||
|
kv := strings.Split(s, ":")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
utils.Fatalf("Invalid custom API header entry: %s", s)
|
||||||
|
}
|
||||||
|
customHeader[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
|
||||||
|
}
|
||||||
|
// create data structures
|
||||||
|
var (
|
||||||
|
db = memorydb.New()
|
||||||
|
threshold = ctx.Int(utils.BeaconThresholdFlag.Name)
|
||||||
|
committeeChain = light.NewCommitteeChain(db, chainConfig.ChainConfig, threshold, !ctx.Bool(utils.BeaconNoFilterFlag.Name))
|
||||||
|
headTracker = light.NewHeadTracker(committeeChain, threshold)
|
||||||
|
)
|
||||||
|
headSync := sync.NewHeadSync(headTracker, committeeChain)
|
||||||
|
|
||||||
|
// set up scheduler and sync modules
|
||||||
|
chainHeadFeed := new(event.Feed)
|
||||||
|
scheduler := request.NewScheduler()
|
||||||
|
checkpointInit := sync.NewCheckpointInit(committeeChain, chainConfig.Checkpoint)
|
||||||
|
forwardSync := sync.NewForwardUpdateSync(committeeChain)
|
||||||
|
beaconBlockSync := newBeaconBlockSync(headTracker, chainHeadFeed)
|
||||||
|
scheduler.RegisterTarget(headTracker)
|
||||||
|
scheduler.RegisterTarget(committeeChain)
|
||||||
|
scheduler.RegisterModule(checkpointInit, "checkpointInit")
|
||||||
|
scheduler.RegisterModule(forwardSync, "forwardSync")
|
||||||
|
scheduler.RegisterModule(headSync, "headSync")
|
||||||
|
scheduler.RegisterModule(beaconBlockSync, "beaconBlockSync")
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
scheduler: scheduler,
|
||||||
|
urls: ctx.StringSlice(utils.BeaconApiFlag.Name),
|
||||||
|
customHeader: customHeader,
|
||||||
|
chainHeadFeed: chainHeadFeed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeChainHeadEvent allows callers to subscribe a provided channel to new
|
||||||
|
// head updates.
|
||||||
|
func (c *Client) SubscribeChainHeadEvent(ch chan<- types.ChainHeadEvent) event.Subscription {
|
||||||
|
return c.chainHeadFeed.Subscribe(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Start() {
|
||||||
|
c.scheduler.Start()
|
||||||
|
// register server(s)
|
||||||
|
for _, url := range c.urls {
|
||||||
|
beaconApi := api.NewBeaconLightApi(url, c.customHeader)
|
||||||
|
c.scheduler.RegisterServer(request.NewServer(api.NewApiServer(beaconApi), &mclock.System{}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Stop() {
|
||||||
|
c.scheduler.Stop()
|
||||||
|
}
|
||||||
113
beacon/blsync/config.go
Normal file
113
beacon/blsync/config.go
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lightClientConfig contains beacon light client configuration
|
||||||
|
type lightClientConfig struct {
|
||||||
|
*types.ChainConfig
|
||||||
|
Checkpoint common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
MainnetConfig = lightClientConfig{
|
||||||
|
ChainConfig: (&types.ChainConfig{
|
||||||
|
GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"),
|
||||||
|
GenesisTime: 1606824023,
|
||||||
|
}).
|
||||||
|
AddFork("GENESIS", 0, []byte{0, 0, 0, 0}).
|
||||||
|
AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}).
|
||||||
|
AddFork("BELLATRIX", 144896, []byte{2, 0, 0, 0}).
|
||||||
|
AddFork("CAPELLA", 194048, []byte{3, 0, 0, 0}),
|
||||||
|
Checkpoint: common.HexToHash("0x388be41594ec7d6a6894f18c73f3469f07e2c19a803de4755d335817ed8e2e5a"),
|
||||||
|
}
|
||||||
|
|
||||||
|
SepoliaConfig = lightClientConfig{
|
||||||
|
ChainConfig: (&types.ChainConfig{
|
||||||
|
GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"),
|
||||||
|
GenesisTime: 1655733600,
|
||||||
|
}).
|
||||||
|
AddFork("GENESIS", 0, []byte{144, 0, 0, 105}).
|
||||||
|
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
|
||||||
|
AddFork("BELLATRIX", 100, []byte{144, 0, 0, 113}).
|
||||||
|
AddFork("CAPELLA", 56832, []byte{144, 0, 0, 114}),
|
||||||
|
Checkpoint: common.HexToHash("0x1005a6d9175e96bfbce4d35b80f468e9bff0b674e1e861d16e09e10005a58e81"),
|
||||||
|
}
|
||||||
|
|
||||||
|
GoerliConfig = lightClientConfig{
|
||||||
|
ChainConfig: (&types.ChainConfig{
|
||||||
|
GenesisValidatorsRoot: common.HexToHash("0x043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb"),
|
||||||
|
GenesisTime: 1614588812,
|
||||||
|
}).
|
||||||
|
AddFork("GENESIS", 0, []byte{0, 0, 16, 32}).
|
||||||
|
AddFork("ALTAIR", 36660, []byte{1, 0, 16, 32}).
|
||||||
|
AddFork("BELLATRIX", 112260, []byte{2, 0, 16, 32}).
|
||||||
|
AddFork("CAPELLA", 162304, []byte{3, 0, 16, 32}),
|
||||||
|
Checkpoint: common.HexToHash("0x53a0f4f0a378e2c4ae0a9ee97407eb69d0d737d8d8cd0a5fb1093f42f7b81c49"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeChainConfig(ctx *cli.Context) lightClientConfig {
|
||||||
|
utils.CheckExclusive(ctx, utils.MainnetFlag, utils.GoerliFlag, utils.SepoliaFlag)
|
||||||
|
customConfig := ctx.IsSet(utils.BeaconConfigFlag.Name) || ctx.IsSet(utils.BeaconGenesisRootFlag.Name) || ctx.IsSet(utils.BeaconGenesisTimeFlag.Name)
|
||||||
|
var config lightClientConfig
|
||||||
|
switch {
|
||||||
|
case ctx.Bool(utils.MainnetFlag.Name):
|
||||||
|
config = MainnetConfig
|
||||||
|
case ctx.Bool(utils.SepoliaFlag.Name):
|
||||||
|
config = SepoliaConfig
|
||||||
|
case ctx.Bool(utils.GoerliFlag.Name):
|
||||||
|
config = GoerliConfig
|
||||||
|
default:
|
||||||
|
if !customConfig {
|
||||||
|
config = MainnetConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if customConfig && config.Forks != nil {
|
||||||
|
utils.Fatalf("Cannot use custom beacon chain config flags in combination with pre-defined network config")
|
||||||
|
}
|
||||||
|
if ctx.IsSet(utils.BeaconGenesisRootFlag.Name) {
|
||||||
|
if c, err := hexutil.Decode(ctx.String(utils.BeaconGenesisRootFlag.Name)); err == nil && len(c) <= 32 {
|
||||||
|
copy(config.GenesisValidatorsRoot[:len(c)], c)
|
||||||
|
} else {
|
||||||
|
utils.Fatalf("Invalid hex string", "beacon.genesis.gvroot", ctx.String(utils.BeaconGenesisRootFlag.Name), "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.IsSet(utils.BeaconGenesisTimeFlag.Name) {
|
||||||
|
config.GenesisTime = ctx.Uint64(utils.BeaconGenesisTimeFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.IsSet(utils.BeaconConfigFlag.Name) {
|
||||||
|
if err := config.ChainConfig.LoadForks(ctx.String(utils.BeaconConfigFlag.Name)); err != nil {
|
||||||
|
utils.Fatalf("Could not load beacon chain config file", "file name", ctx.String(utils.BeaconConfigFlag.Name), "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.IsSet(utils.BeaconCheckpointFlag.Name) {
|
||||||
|
if c, err := hexutil.Decode(ctx.String(utils.BeaconCheckpointFlag.Name)); err == nil && len(c) <= 32 {
|
||||||
|
copy(config.Checkpoint[:len(c)], c)
|
||||||
|
} else {
|
||||||
|
utils.Fatalf("Invalid hex string", "beacon.checkpoint", ctx.String(utils.BeaconCheckpointFlag.Name), "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
103
beacon/light/api/api_server.go
Executable file
103
beacon/light/api/api_server.go
Executable file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApiServer is a wrapper around BeaconLightApi that implements request.requestServer.
|
||||||
|
type ApiServer struct {
|
||||||
|
api *BeaconLightApi
|
||||||
|
eventCallback func(event request.Event)
|
||||||
|
unsubscribe func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApiServer creates a new ApiServer.
|
||||||
|
func NewApiServer(api *BeaconLightApi) *ApiServer {
|
||||||
|
return &ApiServer{api: api}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe implements request.requestServer.
|
||||||
|
func (s *ApiServer) Subscribe(eventCallback func(event request.Event)) {
|
||||||
|
s.eventCallback = eventCallback
|
||||||
|
listener := HeadEventListener{
|
||||||
|
OnNewHead: func(slot uint64, blockRoot common.Hash) {
|
||||||
|
log.Debug("New head received", "slot", slot, "blockRoot", blockRoot)
|
||||||
|
eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}})
|
||||||
|
},
|
||||||
|
OnSignedHead: func(head types.SignedHeader) {
|
||||||
|
log.Debug("New signed head received", "slot", head.Header.Slot, "blockRoot", head.Header.Hash(), "signerCount", head.Signature.SignerCount())
|
||||||
|
eventCallback(request.Event{Type: sync.EvNewSignedHead, Data: head})
|
||||||
|
},
|
||||||
|
OnFinality: func(head types.FinalityUpdate) {
|
||||||
|
log.Debug("New finality update received", "slot", head.Attested.Slot, "blockRoot", head.Attested.Hash(), "signerCount", head.Signature.SignerCount())
|
||||||
|
eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: head})
|
||||||
|
},
|
||||||
|
OnError: func(err error) {
|
||||||
|
log.Warn("Head event stream error", "err", err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s.unsubscribe = s.api.StartHeadListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRequest implements request.requestServer.
|
||||||
|
func (s *ApiServer) SendRequest(id request.ID, req request.Request) {
|
||||||
|
go func() {
|
||||||
|
var resp request.Response
|
||||||
|
var err error
|
||||||
|
switch data := req.(type) {
|
||||||
|
case sync.ReqUpdates:
|
||||||
|
log.Debug("Beacon API: requesting light client update", "reqid", id, "period", data.FirstPeriod, "count", data.Count)
|
||||||
|
var r sync.RespUpdates
|
||||||
|
r.Updates, r.Committees, err = s.api.GetBestUpdatesAndCommittees(data.FirstPeriod, data.Count)
|
||||||
|
resp = r
|
||||||
|
case sync.ReqHeader:
|
||||||
|
log.Debug("Beacon API: requesting header", "reqid", id, "hash", common.Hash(data))
|
||||||
|
resp, err = s.api.GetHeader(common.Hash(data))
|
||||||
|
case sync.ReqCheckpointData:
|
||||||
|
log.Debug("Beacon API: requesting checkpoint data", "reqid", id, "hash", common.Hash(data))
|
||||||
|
resp, err = s.api.GetCheckpointData(common.Hash(data))
|
||||||
|
case sync.ReqBeaconBlock:
|
||||||
|
log.Debug("Beacon API: requesting block", "reqid", id, "hash", common.Hash(data))
|
||||||
|
resp, err = s.api.GetBeaconBlock(common.Hash(data))
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Beacon API request failed", "type", reflect.TypeOf(req), "reqid", id, "err", err)
|
||||||
|
s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}})
|
||||||
|
} else {
|
||||||
|
s.eventCallback(request.Event{Type: request.EvResponse, Data: request.RequestResponse{ID: id, Request: req, Response: resp}})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe implements request.requestServer.
|
||||||
|
// Note: Unsubscribe should not be called concurrently with Subscribe.
|
||||||
|
func (s *ApiServer) Unsubscribe() {
|
||||||
|
if s.unsubscribe != nil {
|
||||||
|
s.unsubscribe()
|
||||||
|
s.unsubscribe = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
496
beacon/light/api/light_api.go
Executable file
496
beacon/light/api/light_api.go
Executable file
|
|
@ -0,0 +1,496 @@
|
||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 detaiapi.
|
||||||
|
//
|
||||||
|
// 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 api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/donovanhide/eventsource"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/capella"
|
||||||
|
"github.com/protolambda/zrnt/eth2/configs"
|
||||||
|
"github.com/protolambda/ztyp/tree"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("404 Not Found")
|
||||||
|
ErrInternal = errors.New("500 Internal Server Error")
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommitteeUpdate struct {
|
||||||
|
Version string
|
||||||
|
Update types.LightClientUpdate
|
||||||
|
NextSyncCommittee types.SerializedSyncCommittee
|
||||||
|
}
|
||||||
|
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate
|
||||||
|
type committeeUpdateJson struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Data committeeUpdateData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type committeeUpdateData struct {
|
||||||
|
Header jsonBeaconHeader `json:"attested_header"`
|
||||||
|
NextSyncCommittee types.SerializedSyncCommittee `json:"next_sync_committee"`
|
||||||
|
NextSyncCommitteeBranch merkle.Values `json:"next_sync_committee_branch"`
|
||||||
|
FinalizedHeader *jsonBeaconHeader `json:"finalized_header,omitempty"`
|
||||||
|
FinalityBranch merkle.Values `json:"finality_branch,omitempty"`
|
||||||
|
SyncAggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
|
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonBeaconHeader struct {
|
||||||
|
Beacon types.Header `json:"beacon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonHeaderWithExecProof struct {
|
||||||
|
Beacon types.Header `json:"beacon"`
|
||||||
|
Execution *capella.ExecutionPayloadHeader `json:"execution"`
|
||||||
|
ExecutionBranch merkle.Values `json:"execution_branch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (u *CommitteeUpdate) UnmarshalJSON(input []byte) error {
|
||||||
|
var dec committeeUpdateJson
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.Version = dec.Version
|
||||||
|
u.NextSyncCommittee = dec.Data.NextSyncCommittee
|
||||||
|
u.Update = types.LightClientUpdate{
|
||||||
|
AttestedHeader: types.SignedHeader{
|
||||||
|
Header: dec.Data.Header.Beacon,
|
||||||
|
Signature: dec.Data.SyncAggregate,
|
||||||
|
SignatureSlot: uint64(dec.Data.SignatureSlot),
|
||||||
|
},
|
||||||
|
NextSyncCommitteeRoot: u.NextSyncCommittee.Root(),
|
||||||
|
NextSyncCommitteeBranch: dec.Data.NextSyncCommitteeBranch,
|
||||||
|
FinalityBranch: dec.Data.FinalityBranch,
|
||||||
|
}
|
||||||
|
if dec.Data.FinalizedHeader != nil {
|
||||||
|
u.Update.FinalizedHeader = &dec.Data.FinalizedHeader.Beacon
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetcher is an interface useful for debug-harnessing the http api.
|
||||||
|
type fetcher interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeaconLightApi requests light client information from a beacon node REST API.
|
||||||
|
// Note: all required API endpoints are currently only implemented by Lodestar.
|
||||||
|
type BeaconLightApi struct {
|
||||||
|
url string
|
||||||
|
client fetcher
|
||||||
|
customHeaders map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBeaconLightApi(url string, customHeaders map[string]string) *BeaconLightApi {
|
||||||
|
return &BeaconLightApi{
|
||||||
|
url: url,
|
||||||
|
client: &http.Client{
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
},
|
||||||
|
customHeaders: customHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BeaconLightApi) httpGet(path string) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest("GET", api.url+path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range api.customHeaders {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
resp, err := api.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case 200:
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
case 404:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
case 500:
|
||||||
|
return nil, ErrInternal
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected error from API endpoint \"%s\": status code %d", path, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BeaconLightApi) httpGetf(format string, params ...any) ([]byte, error) {
|
||||||
|
return api.httpGet(fmt.Sprintf(format, params...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBestUpdateAndCommittee fetches and validates LightClientUpdate for given
|
||||||
|
// period and full serialized committee for the next period (committee root hash
|
||||||
|
// equals update.NextSyncCommitteeRoot).
|
||||||
|
// Note that the results are validated but the update signature should be verified
|
||||||
|
// by the caller as its validity depends on the update chain.
|
||||||
|
func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64) ([]*types.LightClientUpdate, []*types.SerializedSyncCommittee, error) {
|
||||||
|
resp, err := api.httpGetf("/eth/v1/beacon/light_client/updates?start_period=%d&count=%d", firstPeriod, count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []CommitteeUpdate
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(data) != int(count) {
|
||||||
|
return nil, nil, errors.New("invalid number of committee updates")
|
||||||
|
}
|
||||||
|
updates := make([]*types.LightClientUpdate, int(count))
|
||||||
|
committees := make([]*types.SerializedSyncCommittee, int(count))
|
||||||
|
for i, d := range data {
|
||||||
|
if d.Update.AttestedHeader.Header.SyncPeriod() != firstPeriod+uint64(i) {
|
||||||
|
return nil, nil, errors.New("wrong committee update header period")
|
||||||
|
}
|
||||||
|
if err := d.Update.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if d.NextSyncCommittee.Root() != d.Update.NextSyncCommitteeRoot {
|
||||||
|
return nil, nil, errors.New("wrong sync committee root")
|
||||||
|
}
|
||||||
|
updates[i], committees[i] = new(types.LightClientUpdate), new(types.SerializedSyncCommittee)
|
||||||
|
*updates[i], *committees[i] = d.Update, d.NextSyncCommittee
|
||||||
|
}
|
||||||
|
return updates, committees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOptimisticHeadUpdate fetches a signed header based on the latest available
|
||||||
|
// optimistic update. Note that the signature should be verified by the caller
|
||||||
|
// as its validity depends on the update chain.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
||||||
|
func (api *BeaconLightApi) GetOptimisticHeadUpdate() (types.SignedHeader, error) {
|
||||||
|
resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update")
|
||||||
|
if err != nil {
|
||||||
|
return types.SignedHeader{}, err
|
||||||
|
}
|
||||||
|
return decodeOptimisticHeadUpdate(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeOptimisticHeadUpdate(enc []byte) (types.SignedHeader, error) {
|
||||||
|
var data struct {
|
||||||
|
Data struct {
|
||||||
|
Header jsonBeaconHeader `json:"attested_header"`
|
||||||
|
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
|
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(enc, &data); err != nil {
|
||||||
|
return types.SignedHeader{}, err
|
||||||
|
}
|
||||||
|
if data.Data.Header.Beacon.StateRoot == (common.Hash{}) {
|
||||||
|
// workaround for different event encoding format in Lodestar
|
||||||
|
if err := json.Unmarshal(enc, &data.Data); err != nil {
|
||||||
|
return types.SignedHeader{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize {
|
||||||
|
return types.SignedHeader{}, errors.New("invalid sync_committee_bits length")
|
||||||
|
}
|
||||||
|
if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize {
|
||||||
|
return types.SignedHeader{}, errors.New("invalid sync_committee_signature length")
|
||||||
|
}
|
||||||
|
return types.SignedHeader{
|
||||||
|
Header: data.Data.Header.Beacon,
|
||||||
|
Signature: data.Data.Aggregate,
|
||||||
|
SignatureSlot: uint64(data.Data.SignatureSlot),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFinalityUpdate fetches the latest available finality update.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
|
||||||
|
func (api *BeaconLightApi) GetFinalityUpdate() (types.FinalityUpdate, error) {
|
||||||
|
resp, err := api.httpGet("/eth/v1/beacon/light_client/finality_update")
|
||||||
|
if err != nil {
|
||||||
|
return types.FinalityUpdate{}, err
|
||||||
|
}
|
||||||
|
return decodeFinalityUpdate(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
|
||||||
|
var data struct {
|
||||||
|
Data struct {
|
||||||
|
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
||||||
|
Finalized jsonHeaderWithExecProof `json:"finalized_header"`
|
||||||
|
FinalityBranch merkle.Values `json:"finality_branch"`
|
||||||
|
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
|
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(enc, &data); err != nil {
|
||||||
|
return types.FinalityUpdate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize {
|
||||||
|
return types.FinalityUpdate{}, errors.New("invalid sync_committee_bits length")
|
||||||
|
}
|
||||||
|
if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize {
|
||||||
|
return types.FinalityUpdate{}, errors.New("invalid sync_committee_signature length")
|
||||||
|
}
|
||||||
|
return types.FinalityUpdate{
|
||||||
|
Attested: types.HeaderWithExecProof{
|
||||||
|
Header: data.Data.Attested.Beacon,
|
||||||
|
PayloadHeader: data.Data.Attested.Execution,
|
||||||
|
PayloadBranch: data.Data.Attested.ExecutionBranch,
|
||||||
|
},
|
||||||
|
Finalized: types.HeaderWithExecProof{
|
||||||
|
Header: data.Data.Finalized.Beacon,
|
||||||
|
PayloadHeader: data.Data.Finalized.Execution,
|
||||||
|
PayloadBranch: data.Data.Finalized.ExecutionBranch,
|
||||||
|
},
|
||||||
|
FinalityBranch: data.Data.FinalityBranch,
|
||||||
|
Signature: data.Data.Aggregate,
|
||||||
|
SignatureSlot: uint64(data.Data.SignatureSlot),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHead fetches and validates the beacon header with the given blockRoot.
|
||||||
|
// If blockRoot is null hash then the latest head header is fetched.
|
||||||
|
func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, error) {
|
||||||
|
var blockId string
|
||||||
|
if blockRoot == (common.Hash{}) {
|
||||||
|
blockId = "head"
|
||||||
|
} else {
|
||||||
|
blockId = blockRoot.Hex()
|
||||||
|
}
|
||||||
|
resp, err := api.httpGetf("/eth/v1/beacon/headers/%s", blockId)
|
||||||
|
if err != nil {
|
||||||
|
return types.Header{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Data struct {
|
||||||
|
Root common.Hash `json:"root"`
|
||||||
|
Canonical bool `json:"canonical"`
|
||||||
|
Header struct {
|
||||||
|
Message types.Header `json:"message"`
|
||||||
|
Signature hexutil.Bytes `json:"signature"`
|
||||||
|
} `json:"header"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return types.Header{}, err
|
||||||
|
}
|
||||||
|
header := data.Data.Header.Message
|
||||||
|
if blockRoot == (common.Hash{}) {
|
||||||
|
blockRoot = data.Data.Root
|
||||||
|
}
|
||||||
|
if header.Hash() != blockRoot {
|
||||||
|
return types.Header{}, errors.New("retrieved beacon header root does not match")
|
||||||
|
}
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint.
|
||||||
|
func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types.BootstrapData, error) {
|
||||||
|
resp, err := api.httpGetf("/eth/v1/beacon/light_client/bootstrap/0x%x", checkpointHash[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientbootstrap
|
||||||
|
type bootstrapData struct {
|
||||||
|
Data struct {
|
||||||
|
Header jsonBeaconHeader `json:"header"`
|
||||||
|
Committee *types.SerializedSyncCommittee `json:"current_sync_committee"`
|
||||||
|
CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var data bootstrapData
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if data.Data.Committee == nil {
|
||||||
|
return nil, errors.New("sync committee is missing")
|
||||||
|
}
|
||||||
|
header := data.Data.Header.Beacon
|
||||||
|
if header.Hash() != checkpointHash {
|
||||||
|
return nil, fmt.Errorf("invalid checkpoint block header, have %v want %v", header.Hash(), checkpointHash)
|
||||||
|
}
|
||||||
|
checkpoint := &types.BootstrapData{
|
||||||
|
Header: header,
|
||||||
|
CommitteeBranch: data.Data.CommitteeBranch,
|
||||||
|
CommitteeRoot: data.Data.Committee.Root(),
|
||||||
|
Committee: data.Data.Committee,
|
||||||
|
}
|
||||||
|
if err := checkpoint.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid checkpoint: %w", err)
|
||||||
|
}
|
||||||
|
if checkpoint.Header.Hash() != checkpointHash {
|
||||||
|
return nil, errors.New("wrong checkpoint hash")
|
||||||
|
}
|
||||||
|
return checkpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BeaconLightApi) GetBeaconBlock(blockRoot common.Hash) (*capella.BeaconBlock, error) {
|
||||||
|
resp, err := api.httpGetf("/eth/v2/beacon/blocks/0x%x", blockRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var beaconBlockMessage struct {
|
||||||
|
Data struct {
|
||||||
|
Message capella.BeaconBlock `json:"message"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resp, &beaconBlockMessage); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid block json data: %v", err)
|
||||||
|
}
|
||||||
|
beaconBlock := new(capella.BeaconBlock)
|
||||||
|
*beaconBlock = beaconBlockMessage.Data.Message
|
||||||
|
root := common.Hash(beaconBlock.HashTreeRoot(configs.Mainnet, tree.GetHashFn()))
|
||||||
|
if root != blockRoot {
|
||||||
|
return nil, fmt.Errorf("Beacon block root hash mismatch (expected: %x, got: %x)", blockRoot, root)
|
||||||
|
}
|
||||||
|
return beaconBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHeadEvent(enc []byte) (uint64, common.Hash, error) {
|
||||||
|
var data struct {
|
||||||
|
Slot common.Decimal `json:"slot"`
|
||||||
|
Block common.Hash `json:"block"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(enc, &data); err != nil {
|
||||||
|
return 0, common.Hash{}, err
|
||||||
|
}
|
||||||
|
return uint64(data.Slot), data.Block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeadEventListener struct {
|
||||||
|
OnNewHead func(slot uint64, blockRoot common.Hash)
|
||||||
|
OnSignedHead func(head types.SignedHeader)
|
||||||
|
OnFinality func(head types.FinalityUpdate)
|
||||||
|
OnError func(err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartHeadListener creates an event subscription for heads and signed (optimistic)
|
||||||
|
// head updates and calls the specified callback functions when they are received.
|
||||||
|
// The callbacks are also called for the current head and optimistic head at startup.
|
||||||
|
// They are never called concurrently.
|
||||||
|
func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func() {
|
||||||
|
closeCh := make(chan struct{}) // initiate closing the stream
|
||||||
|
closedCh := make(chan struct{}) // stream closed (or failed to create)
|
||||||
|
stoppedCh := make(chan struct{}) // sync loop stopped
|
||||||
|
streamCh := make(chan *eventsource.Stream, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(closedCh)
|
||||||
|
// when connected to a Lodestar node the subscription blocks until the
|
||||||
|
// first actual event arrives; therefore we create the subscription in
|
||||||
|
// a separate goroutine while letting the main goroutine sync up to the
|
||||||
|
// current head
|
||||||
|
req, err := http.NewRequest("GET", api.url+
|
||||||
|
"/eth/v1/events?topics=head&topics=light_client_optimistic_update&topics=light_client_finality_update", nil)
|
||||||
|
if err != nil {
|
||||||
|
listener.OnError(fmt.Errorf("error creating event subscription request: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range api.customHeaders {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
stream, err := eventsource.SubscribeWithRequest("", req)
|
||||||
|
if err != nil {
|
||||||
|
listener.OnError(fmt.Errorf("error creating event subscription: %v", err))
|
||||||
|
close(streamCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
streamCh <- stream
|
||||||
|
<-closeCh
|
||||||
|
stream.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(stoppedCh)
|
||||||
|
|
||||||
|
if head, err := api.GetHeader(common.Hash{}); err == nil {
|
||||||
|
listener.OnNewHead(head.Slot, head.Hash())
|
||||||
|
}
|
||||||
|
if signedHead, err := api.GetOptimisticHeadUpdate(); err == nil {
|
||||||
|
listener.OnSignedHead(signedHead)
|
||||||
|
}
|
||||||
|
if finalityUpdate, err := api.GetFinalityUpdate(); err == nil {
|
||||||
|
listener.OnFinality(finalityUpdate)
|
||||||
|
}
|
||||||
|
stream := <-streamCh
|
||||||
|
if stream == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-stream.Events:
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch event.Event() {
|
||||||
|
case "head":
|
||||||
|
if slot, blockRoot, err := decodeHeadEvent([]byte(event.Data())); err == nil {
|
||||||
|
listener.OnNewHead(slot, blockRoot)
|
||||||
|
} else {
|
||||||
|
listener.OnError(fmt.Errorf("error decoding head event: %v", err))
|
||||||
|
}
|
||||||
|
case "light_client_optimistic_update":
|
||||||
|
if signedHead, err := decodeOptimisticHeadUpdate([]byte(event.Data())); err == nil {
|
||||||
|
listener.OnSignedHead(signedHead)
|
||||||
|
} else {
|
||||||
|
listener.OnError(fmt.Errorf("error decoding optimistic update event: %v", err))
|
||||||
|
}
|
||||||
|
case "light_client_finality_update":
|
||||||
|
if finalityUpdate, err := decodeFinalityUpdate([]byte(event.Data())); err == nil {
|
||||||
|
listener.OnFinality(finalityUpdate)
|
||||||
|
} else {
|
||||||
|
listener.OnError(fmt.Errorf("error decoding finality update event: %v", err))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
listener.OnError(fmt.Errorf("unexpected event: %s", event.Event()))
|
||||||
|
}
|
||||||
|
case err, ok := <-stream.Errors:
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
listener.OnError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
close(closeCh)
|
||||||
|
<-closedCh
|
||||||
|
<-stoppedCh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -70,6 +70,7 @@ type CommitteeChain struct {
|
||||||
committees *canonicalStore[*types.SerializedSyncCommittee]
|
committees *canonicalStore[*types.SerializedSyncCommittee]
|
||||||
fixedCommitteeRoots *canonicalStore[common.Hash]
|
fixedCommitteeRoots *canonicalStore[common.Hash]
|
||||||
committeeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees
|
committeeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees
|
||||||
|
changeCounter uint64
|
||||||
|
|
||||||
clock mclock.Clock // monotonic clock (simulated clock in tests)
|
clock mclock.Clock // monotonic clock (simulated clock in tests)
|
||||||
unixNano func() int64 // system clock (simulated clock in tests)
|
unixNano func() int64 // system clock (simulated clock in tests)
|
||||||
|
|
@ -86,6 +87,11 @@ func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signer
|
||||||
return newCommitteeChain(db, config, signerThreshold, enforceTime, blsVerifier{}, &mclock.System{}, func() int64 { return time.Now().UnixNano() })
|
return newCommitteeChain(db, config, signerThreshold, enforceTime, blsVerifier{}, &mclock.System{}, func() int64 { return time.Now().UnixNano() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTestCommitteeChain creates a new CommitteeChain for testing.
|
||||||
|
func NewTestCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, clock *mclock.Simulated) *CommitteeChain {
|
||||||
|
return newCommitteeChain(db, config, signerThreshold, enforceTime, dummyVerifier{}, clock, func() int64 { return int64(clock.Now()) })
|
||||||
|
}
|
||||||
|
|
||||||
// newCommitteeChain creates a new CommitteeChain with the option of replacing the
|
// newCommitteeChain creates a new CommitteeChain with the option of replacing the
|
||||||
// clock source and signature verification for testing purposes.
|
// clock source and signature verification for testing purposes.
|
||||||
func newCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain {
|
func newCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain {
|
||||||
|
|
@ -181,20 +187,20 @@ func (s *CommitteeChain) Reset() {
|
||||||
if err := s.rollback(0); err != nil {
|
if err := s.rollback(0); err != nil {
|
||||||
log.Error("Error writing batch into chain database", "error", err)
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
}
|
}
|
||||||
|
s.changeCounter++
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckpointInit initializes a CommitteeChain based on the checkpoint.
|
// CheckpointInit initializes a CommitteeChain based on a checkpoint.
|
||||||
// Note: if the chain is already initialized and the committees proven by the
|
// Note: if the chain is already initialized and the committees proven by the
|
||||||
// checkpoint do match the existing chain then the chain is retained and the
|
// checkpoint do match the existing chain then the chain is retained and the
|
||||||
// new checkpoint becomes fixed.
|
// new checkpoint becomes fixed.
|
||||||
func (s *CommitteeChain) CheckpointInit(bootstrap *types.BootstrapData) error {
|
func (s *CommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
|
||||||
s.chainmu.Lock()
|
s.chainmu.Lock()
|
||||||
defer s.chainmu.Unlock()
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
if err := bootstrap.Validate(); err != nil {
|
if err := bootstrap.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
period := bootstrap.Header.SyncPeriod()
|
period := bootstrap.Header.SyncPeriod()
|
||||||
if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil {
|
if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil {
|
||||||
s.Reset()
|
s.Reset()
|
||||||
|
|
@ -215,6 +221,7 @@ func (s *CommitteeChain) CheckpointInit(bootstrap *types.BootstrapData) error {
|
||||||
s.Reset()
|
s.Reset()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
s.changeCounter++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,6 +374,7 @@ func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommi
|
||||||
return ErrWrongCommitteeRoot
|
return ErrWrongCommitteeRoot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.changeCounter++
|
||||||
if reorg {
|
if reorg {
|
||||||
if err := s.rollback(period + 1); err != nil {
|
if err := s.rollback(period + 1); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -405,6 +413,13 @@ func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) {
|
||||||
return s.committees.periods.End - 1, true
|
return s.committees.periods.End - 1, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CommitteeChain) ChangeCounter() uint64 {
|
||||||
|
s.chainmu.RLock()
|
||||||
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
|
return s.changeCounter
|
||||||
|
}
|
||||||
|
|
||||||
// rollback removes all committees and fixed roots from the given period and updates
|
// rollback removes all committees and fixed roots from the given period and updates
|
||||||
// starting from the previous period.
|
// starting from the previous period.
|
||||||
func (s *CommitteeChain) rollback(period uint64) error {
|
func (s *CommitteeChain) rollback(period uint64) error {
|
||||||
|
|
@ -452,12 +467,12 @@ func (s *CommitteeChain) getSyncCommittee(period uint64) (syncCommittee, error)
|
||||||
if sc, ok := s.committees.get(s.db, period); ok {
|
if sc, ok := s.committees.get(s.db, period); ok {
|
||||||
c, err := s.sigVerifier.deserializeSyncCommittee(sc)
|
c, err := s.sigVerifier.deserializeSyncCommittee(sc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Sync committee #%d deserialization error: %v", period, err)
|
return nil, fmt.Errorf("sync committee #%d deserialization error: %v", period, err)
|
||||||
}
|
}
|
||||||
s.committeeCache.Add(period, c)
|
s.committeeCache.Add(period, c)
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Missing serialized sync committee #%d", period)
|
return nil, fmt.Errorf("missing serialized sync committee #%d", period)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifySignedHeader returns true if the given signed header has a valid signature
|
// VerifySignedHeader returns true if the given signed header has a valid signature
|
||||||
|
|
|
||||||
|
|
@ -241,12 +241,12 @@ func newCommitteeChainTest(t *testing.T, config types.ChainConfig, signerThresho
|
||||||
signerThreshold: signerThreshold,
|
signerThreshold: signerThreshold,
|
||||||
enforceTime: enforceTime,
|
enforceTime: enforceTime,
|
||||||
}
|
}
|
||||||
c.chain = newCommitteeChain(c.db, &config, signerThreshold, enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) })
|
c.chain = NewTestCommitteeChain(c.db, &config, signerThreshold, enforceTime, c.clock)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *committeeChainTest) reloadChain() {
|
func (c *committeeChainTest) reloadChain() {
|
||||||
c.chain = newCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) })
|
c.chain = NewTestCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, c.clock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *committeeChainTest) setClockPeriod(period float64) {
|
func (c *committeeChainTest) setClockPeriod(period float64) {
|
||||||
|
|
|
||||||
150
beacon/light/head_tracker.go
Normal file
150
beacon/light/head_tracker.go
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeadTracker keeps track of the latest validated head and the "prefetch" head
|
||||||
|
// which is the (not necessarily validated) head announced by the majority of
|
||||||
|
// servers.
|
||||||
|
type HeadTracker struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
committeeChain *CommitteeChain
|
||||||
|
minSignerCount int
|
||||||
|
signedHead types.SignedHeader
|
||||||
|
hasSignedHead bool
|
||||||
|
finalityUpdate types.FinalityUpdate
|
||||||
|
hasFinalityUpdate bool
|
||||||
|
prefetchHead types.HeadInfo
|
||||||
|
changeCounter uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHeadTracker creates a new HeadTracker.
|
||||||
|
func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTracker {
|
||||||
|
return &HeadTracker{
|
||||||
|
committeeChain: committeeChain,
|
||||||
|
minSignerCount: minSignerCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatedHead returns the latest validated head.
|
||||||
|
func (h *HeadTracker) ValidatedHead() (types.SignedHeader, bool) {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.signedHead, h.hasSignedHead
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatedHead returns the latest validated head.
|
||||||
|
func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.finalityUpdate, h.hasFinalityUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the given signed head. If the head is successfully validated
|
||||||
|
// and it is better than the old validated head (higher slot or same slot and more
|
||||||
|
// signers) then ValidatedHead is updated. The boolean return flag signals if
|
||||||
|
// ValidatedHead has been changed.
|
||||||
|
func (h *HeadTracker) ValidateHead(head types.SignedHeader) (bool, error) {
|
||||||
|
h.lock.Lock()
|
||||||
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
replace, err := h.validate(head, h.signedHead)
|
||||||
|
if replace {
|
||||||
|
h.signedHead, h.hasSignedHead = head, true
|
||||||
|
h.changeCounter++
|
||||||
|
}
|
||||||
|
return replace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
||||||
|
h.lock.Lock()
|
||||||
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
|
||||||
|
if replace {
|
||||||
|
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
||||||
|
h.changeCounter++
|
||||||
|
}
|
||||||
|
return replace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HeadTracker) validate(head, oldHead types.SignedHeader) (bool, error) {
|
||||||
|
signerCount := head.Signature.SignerCount()
|
||||||
|
if signerCount < h.minSignerCount {
|
||||||
|
return false, errors.New("low signer count")
|
||||||
|
}
|
||||||
|
if head.Header.Slot < oldHead.Header.Slot || (head.Header.Slot == oldHead.Header.Slot && signerCount <= oldHead.Signature.SignerCount()) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
sigOk, age, err := h.committeeChain.VerifySignedHeader(head)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if age < 0 {
|
||||||
|
log.Warn("Future signed head received", "age", age)
|
||||||
|
}
|
||||||
|
if age > time.Minute*2 {
|
||||||
|
log.Warn("Old signed head received", "age", age)
|
||||||
|
}
|
||||||
|
if !sigOk {
|
||||||
|
return false, errors.New("invalid header signature")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrefetchHead returns the latest known prefetch head's head info.
|
||||||
|
// This head can be used to start fetching related data hoping that it will be
|
||||||
|
// validated soon.
|
||||||
|
// Note that the prefetch head cannot be validated cryptographically so it should
|
||||||
|
// only be used as a performance optimization hint.
|
||||||
|
func (h *HeadTracker) PrefetchHead() types.HeadInfo {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.prefetchHead
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrefetchHead sets the prefetch head info.
|
||||||
|
// Note that HeadTracker does not verify the prefetch head, just acts as a thread
|
||||||
|
// safe bulletin board.
|
||||||
|
func (h *HeadTracker) SetPrefetchHead(head types.HeadInfo) {
|
||||||
|
h.lock.Lock()
|
||||||
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
if head == h.prefetchHead {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.prefetchHead = head
|
||||||
|
h.changeCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HeadTracker) ChangeCounter() uint64 {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.changeCounter
|
||||||
|
}
|
||||||
401
beacon/light/request/scheduler.go
Normal file
401
beacon/light/request/scheduler.go
Normal file
|
|
@ -0,0 +1,401 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Module represents a mechanism which is typically responsible for downloading
|
||||||
|
// and updating a passive data structure. It does not directly interact with the
|
||||||
|
// servers. It can start requests using the Requester interface, maintain its
|
||||||
|
// internal state by receiving and processing Events and update its target data
|
||||||
|
// structure based on the obtained data.
|
||||||
|
// It is the Scheduler's responsibility to feed events to the modules, call
|
||||||
|
// Process as long as there might be something to process and then generate request
|
||||||
|
// candidates using MakeRequest and start the best possible requests.
|
||||||
|
// Modules are called by Scheduler whenever a global trigger is fired. All events
|
||||||
|
// fire the trigger. Changing a target data structure also triggers a next
|
||||||
|
// processing round as it could make further actions possible either by the same
|
||||||
|
// or another Module.
|
||||||
|
type Module interface {
|
||||||
|
// Process is a non-blocking function responsible for starting requests,
|
||||||
|
// processing events and updating the target data structures(s) and the
|
||||||
|
// internal state of the module. Module state typically consists of information
|
||||||
|
// about pending requests and registered servers.
|
||||||
|
// Process is always called after an event is received or after a target data
|
||||||
|
// structure has been changed.
|
||||||
|
//
|
||||||
|
// Note: Process functions of different modules are never called concurrently;
|
||||||
|
// they are called by Scheduler in the same order of priority as they were
|
||||||
|
// registered in.
|
||||||
|
Process(Requester, []Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requester allows Modules to obtain the list of momentarily available servers,
|
||||||
|
// start new requests and report server failure when a response has been proven
|
||||||
|
// to be invalid in the processing phase.
|
||||||
|
// Note that all Requester functions should be safe to call from Module.Process.
|
||||||
|
type Requester interface {
|
||||||
|
CanSendTo() []Server
|
||||||
|
Send(Server, Request) ID
|
||||||
|
Fail(Server, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheduler is a modular network data retrieval framework that coordinates multiple
|
||||||
|
// servers and retrieval mechanisms (modules). It implements a trigger mechanism
|
||||||
|
// that calls the Process function of registered modules whenever either the state
|
||||||
|
// of existing data structures or events coming from registered servers could
|
||||||
|
// allow new operations.
|
||||||
|
type Scheduler struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
modules []Module // first has highest priority
|
||||||
|
names map[Module]string
|
||||||
|
servers map[server]struct{}
|
||||||
|
targets map[targetData]uint64
|
||||||
|
|
||||||
|
requesterLock sync.RWMutex
|
||||||
|
serverOrder []server
|
||||||
|
pending map[ServerAndID]pendingRequest
|
||||||
|
|
||||||
|
// eventLock guards access to the events list. Note that eventLock can be
|
||||||
|
// locked either while lock is locked or unlocked but lock cannot be locked
|
||||||
|
// while eventLock is locked.
|
||||||
|
eventLock sync.Mutex
|
||||||
|
events []Event
|
||||||
|
stopCh chan chan struct{}
|
||||||
|
|
||||||
|
triggerCh chan struct{} // restarts waiting sync loop
|
||||||
|
// if trigger has already been fired then send to testWaitCh blocks until
|
||||||
|
// the triggered processing round is finished
|
||||||
|
testWaitCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Server identifies a server without allowing any direct interaction.
|
||||||
|
// Note: server interface is used by Scheduler and Tracker but not used by
|
||||||
|
// the modules that do not interact with them directly.
|
||||||
|
// In order to make module testing easier, Server interface is used in
|
||||||
|
// events and modules.
|
||||||
|
Server any
|
||||||
|
Request any
|
||||||
|
Response any
|
||||||
|
ID uint64
|
||||||
|
ServerAndID struct {
|
||||||
|
Server Server
|
||||||
|
ID ID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// targetData represents a registered target data structure that increases its
|
||||||
|
// ChangeCounter whenever it has been changed.
|
||||||
|
type targetData interface {
|
||||||
|
ChangeCounter() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// pendingRequest keeps track of sent and not yet finalized requests and their
|
||||||
|
// sender modules.
|
||||||
|
type pendingRequest struct {
|
||||||
|
request Request
|
||||||
|
module Module
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScheduler creates a new Scheduler.
|
||||||
|
func NewScheduler() *Scheduler {
|
||||||
|
s := &Scheduler{
|
||||||
|
servers: make(map[server]struct{}),
|
||||||
|
names: make(map[Module]string),
|
||||||
|
pending: make(map[ServerAndID]pendingRequest),
|
||||||
|
targets: make(map[targetData]uint64),
|
||||||
|
stopCh: make(chan chan struct{}),
|
||||||
|
// Note: testWaitCh should not have capacity in order to ensure
|
||||||
|
// that after a trigger happens testWaitCh will block until the resulting
|
||||||
|
// processing round has been finished
|
||||||
|
triggerCh: make(chan struct{}, 1),
|
||||||
|
testWaitCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTarget registers a target data structure, ensuring that any changes
|
||||||
|
// made to it trigger a new round of Module.Process calls, giving a chance to
|
||||||
|
// modules to react to the changes.
|
||||||
|
func (s *Scheduler) RegisterTarget(t targetData) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.targets[t] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterModule registers a module. Should be called before starting the scheduler.
|
||||||
|
// In each processing round the order of module processing depends on the order of
|
||||||
|
// registration.
|
||||||
|
func (s *Scheduler) RegisterModule(m Module, name string) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.modules = append(s.modules, m)
|
||||||
|
s.names[m] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterServer registers a new server.
|
||||||
|
func (s *Scheduler) RegisterServer(server server) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.addEvent(Event{Type: EvRegistered, Server: server})
|
||||||
|
server.subscribe(func(event Event) {
|
||||||
|
event.Server = server
|
||||||
|
s.addEvent(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterServer removes a registered server.
|
||||||
|
func (s *Scheduler) UnregisterServer(server server) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
server.unsubscribe()
|
||||||
|
s.addEvent(Event{Type: EvUnregistered, Server: server})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the scheduler. It should be called after registering all modules
|
||||||
|
// and before registering any servers.
|
||||||
|
func (s *Scheduler) Start() {
|
||||||
|
go s.syncLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the scheduler.
|
||||||
|
func (s *Scheduler) Stop() {
|
||||||
|
stop := make(chan struct{})
|
||||||
|
s.stopCh <- stop
|
||||||
|
<-stop
|
||||||
|
s.lock.Lock()
|
||||||
|
for server := range s.servers {
|
||||||
|
server.unsubscribe()
|
||||||
|
}
|
||||||
|
s.servers = nil
|
||||||
|
s.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncLoop is the main event loop responsible for event/data processing and
|
||||||
|
// sending new requests.
|
||||||
|
// A round of processing starts whenever the global trigger is fired. Triggers
|
||||||
|
// fired during a processing round ensure that there is going to be a next round.
|
||||||
|
func (s *Scheduler) syncLoop() {
|
||||||
|
for {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.processRound()
|
||||||
|
s.lock.Unlock()
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case stop := <-s.stopCh:
|
||||||
|
close(stop)
|
||||||
|
return
|
||||||
|
case <-s.triggerCh:
|
||||||
|
break loop
|
||||||
|
case <-s.testWaitCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// targetChanged returns true if a registered target data structure has been
|
||||||
|
// changed since the last call to this function.
|
||||||
|
func (s *Scheduler) targetChanged() (changed bool) {
|
||||||
|
for target, counter := range s.targets {
|
||||||
|
if newCounter := target.ChangeCounter(); newCounter != counter {
|
||||||
|
s.targets[target] = newCounter
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// processRound runs an entire processing round. It calls the Process functions
|
||||||
|
// of all modules, passing all relevant events and repeating Process calls as
|
||||||
|
// long as any changes have been made to the registered target data structures.
|
||||||
|
// Once all events have been processed and a stable state has been achieved,
|
||||||
|
// requests are generated and sent if necessary and possible.
|
||||||
|
func (s *Scheduler) processRound() {
|
||||||
|
for {
|
||||||
|
log.Trace("Processing modules")
|
||||||
|
filteredEvents := s.filterEvents()
|
||||||
|
for _, module := range s.modules {
|
||||||
|
log.Trace("Processing module", "name", s.names[module], "events", len(filteredEvents[module]))
|
||||||
|
module.Process(requester{s, module}, filteredEvents[module])
|
||||||
|
}
|
||||||
|
if !s.targetChanged() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger starts a new processing round. If fired during processing, it ensures
|
||||||
|
// another full round of processing all modules.
|
||||||
|
func (s *Scheduler) Trigger() {
|
||||||
|
select {
|
||||||
|
case s.triggerCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addEvent adds an event to be processed in the next round. Note that it can be
|
||||||
|
// called regardless of the state of the lock mutex, making it safe for use in
|
||||||
|
// the server event callback.
|
||||||
|
func (s *Scheduler) addEvent(event Event) {
|
||||||
|
s.eventLock.Lock()
|
||||||
|
s.events = append(s.events, event)
|
||||||
|
s.eventLock.Unlock()
|
||||||
|
s.Trigger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterEvent sorts each Event either as a request event or a server event,
|
||||||
|
// depending on its type. Request events are also sorted in a map based on the
|
||||||
|
// module that originally initiated the request. It also ensures that no events
|
||||||
|
// related to a server are returned before EvRegistered or after EvUnregistered.
|
||||||
|
// In case of an EvUnregistered server event it also closes all pending requests
|
||||||
|
// to the given server by adding a failed request event (EvFail), ensuring that
|
||||||
|
// all requests get finalized and thereby allowing the module logic to be safe
|
||||||
|
// and simple.
|
||||||
|
func (s *Scheduler) filterEvents() map[Module][]Event {
|
||||||
|
s.eventLock.Lock()
|
||||||
|
events := s.events
|
||||||
|
s.events = nil
|
||||||
|
s.eventLock.Unlock()
|
||||||
|
|
||||||
|
s.requesterLock.Lock()
|
||||||
|
defer s.requesterLock.Unlock()
|
||||||
|
|
||||||
|
filteredEvents := make(map[Module][]Event)
|
||||||
|
for _, event := range events {
|
||||||
|
server := event.Server.(server)
|
||||||
|
if _, ok := s.servers[server]; !ok && event.Type != EvRegistered {
|
||||||
|
continue // before EvRegister or after EvUnregister, discard
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.IsRequestEvent() {
|
||||||
|
sid, _, _ := event.RequestInfo()
|
||||||
|
pending, ok := s.pending[sid]
|
||||||
|
if !ok {
|
||||||
|
continue // request already closed, ignore further events
|
||||||
|
}
|
||||||
|
if event.Type == EvResponse || event.Type == EvFail {
|
||||||
|
delete(s.pending, sid) // final event, close pending request
|
||||||
|
}
|
||||||
|
filteredEvents[pending.module] = append(filteredEvents[pending.module], event)
|
||||||
|
} else {
|
||||||
|
switch event.Type {
|
||||||
|
case EvRegistered:
|
||||||
|
s.servers[server] = struct{}{}
|
||||||
|
s.serverOrder = append(s.serverOrder, nil)
|
||||||
|
copy(s.serverOrder[1:], s.serverOrder[:len(s.serverOrder)-1])
|
||||||
|
s.serverOrder[0] = server
|
||||||
|
case EvUnregistered:
|
||||||
|
s.closePending(event.Server, filteredEvents)
|
||||||
|
delete(s.servers, server)
|
||||||
|
for i, srv := range s.serverOrder {
|
||||||
|
if srv == server {
|
||||||
|
copy(s.serverOrder[i:len(s.serverOrder)-1], s.serverOrder[i+1:])
|
||||||
|
s.serverOrder = s.serverOrder[:len(s.serverOrder)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, module := range s.modules {
|
||||||
|
filteredEvents[module] = append(filteredEvents[module], event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
// closePending closes all pending requests to the given server and adds an EvFail
|
||||||
|
// event to properly finalize them
|
||||||
|
func (s *Scheduler) closePending(server Server, filteredEvents map[Module][]Event) {
|
||||||
|
for sid, pending := range s.pending {
|
||||||
|
if sid.Server == server {
|
||||||
|
filteredEvents[pending.module] = append(filteredEvents[pending.module], Event{
|
||||||
|
Type: EvFail,
|
||||||
|
Server: server,
|
||||||
|
Data: RequestResponse{
|
||||||
|
ID: sid.ID,
|
||||||
|
Request: pending.request,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
delete(s.pending, sid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requester implements Requester. Note that while requester basically wraps
|
||||||
|
// Scheduler (with the added information of the currently processed Module), all
|
||||||
|
// functions are safe to call from Module.Process which is running while
|
||||||
|
// the Scheduler.lock mutex is held.
|
||||||
|
type requester struct {
|
||||||
|
*Scheduler
|
||||||
|
module Module
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanSendTo returns the list of currently available servers. It also returns
|
||||||
|
// them in an order of least to most recently used, ensuring a round-robin usage
|
||||||
|
// of suitable servers if the module always chooses the first suitable one.
|
||||||
|
func (s requester) CanSendTo() []Server {
|
||||||
|
s.requesterLock.RLock()
|
||||||
|
defer s.requesterLock.RUnlock()
|
||||||
|
|
||||||
|
list := make([]Server, 0, len(s.serverOrder))
|
||||||
|
for _, server := range s.serverOrder {
|
||||||
|
if server.canRequestNow() {
|
||||||
|
list = append(list, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends a request and adds an entry to Scheduler.pending map, ensuring that
|
||||||
|
// related request events will be delivered to the sender Module.
|
||||||
|
func (s requester) Send(srv Server, req Request) ID {
|
||||||
|
s.requesterLock.Lock()
|
||||||
|
defer s.requesterLock.Unlock()
|
||||||
|
|
||||||
|
server := srv.(server)
|
||||||
|
id := server.sendRequest(req)
|
||||||
|
sid := ServerAndID{Server: srv, ID: id}
|
||||||
|
s.pending[sid] = pendingRequest{request: req, module: s.module}
|
||||||
|
for i, ss := range s.serverOrder {
|
||||||
|
if ss == server {
|
||||||
|
copy(s.serverOrder[i:len(s.serverOrder)-1], s.serverOrder[i+1:])
|
||||||
|
s.serverOrder[len(s.serverOrder)-1] = server
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Error("Target server not found in ordered list of registered servers")
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail should be called when a server delivers invalid or useless information.
|
||||||
|
// Calling Fail disables the given server for a period that is initially short
|
||||||
|
// but is exponentially growing if it happens frequently. This results in a
|
||||||
|
// somewhat fault tolerant operation that avoids hammering servers with requests
|
||||||
|
// that they cannot serve but still gives them a chance periodically.
|
||||||
|
func (s requester) Fail(srv Server, desc string) {
|
||||||
|
srv.(server).fail(desc)
|
||||||
|
}
|
||||||
122
beacon/light/request/scheduler_test.go
Normal file
122
beacon/light/request/scheduler_test.go
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEventFilter(t *testing.T) {
|
||||||
|
s := NewScheduler()
|
||||||
|
module1 := &testModule{name: "module1"}
|
||||||
|
module2 := &testModule{name: "module2"}
|
||||||
|
s.RegisterModule(module1, "module1")
|
||||||
|
s.RegisterModule(module2, "module2")
|
||||||
|
s.Start()
|
||||||
|
// startup process round without events
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
srv := &testServer{}
|
||||||
|
// register server; both modules should receive server event
|
||||||
|
s.RegisterServer(srv)
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
{Type: EvRegistered, Server: srv},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, []Event{
|
||||||
|
{Type: EvRegistered, Server: srv},
|
||||||
|
})
|
||||||
|
// let module1 send a request
|
||||||
|
srv.canRequest = 1
|
||||||
|
module1.sendReq = testRequest
|
||||||
|
s.Trigger()
|
||||||
|
// in first triggered round module1 sends the request, no events yet
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// server emits EvTimeout; only module1 should receive it
|
||||||
|
srv.eventCb(Event{Type: EvTimeout, Data: RequestResponse{ID: 1, Request: testRequest}})
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
{Type: EvTimeout, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// unregister server; both modules should receive server event
|
||||||
|
s.UnregisterServer(srv)
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
// module1 should also receive EvFail on its pending request
|
||||||
|
{Type: EvFail, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}},
|
||||||
|
{Type: EvUnregistered, Server: srv},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, []Event{
|
||||||
|
{Type: EvUnregistered, Server: srv},
|
||||||
|
})
|
||||||
|
// response after server unregistered; should be discarded
|
||||||
|
srv.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// no more process rounds expected; shut down
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expNoMoreProcess(t)
|
||||||
|
module2.expNoMoreProcess(t)
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type testServer struct {
|
||||||
|
eventCb func(Event)
|
||||||
|
lastID ID
|
||||||
|
canRequest int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) subscribe(eventCb func(Event)) {
|
||||||
|
s.eventCb = eventCb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) canRequestNow() bool {
|
||||||
|
return s.canRequest > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) sendRequest(req Request) ID {
|
||||||
|
s.canRequest--
|
||||||
|
s.lastID++
|
||||||
|
return s.lastID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) fail(string) {}
|
||||||
|
func (s *testServer) unsubscribe() {}
|
||||||
|
|
||||||
|
type testModule struct {
|
||||||
|
name string
|
||||||
|
processed [][]Event
|
||||||
|
sendReq Request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) Process(requester Requester, events []Event) {
|
||||||
|
m.processed = append(m.processed, events)
|
||||||
|
if m.sendReq != nil {
|
||||||
|
if cs := requester.CanSendTo(); len(cs) > 0 {
|
||||||
|
requester.Send(cs[0], m.sendReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) expProcess(t *testing.T, expEvents []Event) {
|
||||||
|
if len(m.processed) == 0 {
|
||||||
|
t.Errorf("Missing call to %s.Process", m.name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
events := m.processed[0]
|
||||||
|
m.processed = m.processed[1:]
|
||||||
|
if !reflect.DeepEqual(events, expEvents) {
|
||||||
|
t.Errorf("Call to %s.Process with wrong events (expected %v, got %v)", m.name, expEvents, events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) expNoMoreProcess(t *testing.T) {
|
||||||
|
for len(m.processed) > 0 {
|
||||||
|
t.Errorf("Unexpected call to %s.Process with events %v", m.name, m.processed[0])
|
||||||
|
m.processed = m.processed[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
439
beacon/light/request/server.go
Normal file
439
beacon/light/request/server.go
Normal file
|
|
@ -0,0 +1,439 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// request events
|
||||||
|
EvResponse = &EventType{Name: "response", requestEvent: true} // data: RequestResponse; sent by requestServer
|
||||||
|
EvFail = &EventType{Name: "fail", requestEvent: true} // data: RequestResponse; sent by requestServer
|
||||||
|
EvTimeout = &EventType{Name: "timeout", requestEvent: true} // data: RequestResponse; sent by serverWithTimeout
|
||||||
|
// server events
|
||||||
|
EvRegistered = &EventType{Name: "registered"} // data: nil; sent by Scheduler
|
||||||
|
EvUnregistered = &EventType{Name: "unregistered"} // data: nil; sent by Scheduler
|
||||||
|
EvCanRequestAgain = &EventType{Name: "canRequestAgain"} // data: nil; sent by serverWithLimits
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
softRequestTimeout = time.Second // allow resending request to a different server but do not cancel yet
|
||||||
|
hardRequestTimeout = time.Second * 10 // cancel request
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// serverWithLimits parameters
|
||||||
|
parallelAdjustUp = 0.1 // adjust parallelLimit up in case of success under full load
|
||||||
|
parallelAdjustDown = 1 // adjust parallelLimit down in case of timeout/failure
|
||||||
|
minParallelLimit = 1 // parallelLimit lower bound
|
||||||
|
defaultParallelLimit = 3 // parallelLimit initial value
|
||||||
|
minFailureDelay = time.Millisecond * 100 // minimum disable time in case of request failure
|
||||||
|
maxFailureDelay = time.Minute // maximum disable time in case of request failure
|
||||||
|
maxServerEventBuffer = 5 // server event allowance buffer limit
|
||||||
|
maxServerEventRate = time.Second // server event allowance buffer recharge rate
|
||||||
|
)
|
||||||
|
|
||||||
|
// requestServer can send requests in a non-blocking way and feed back events
|
||||||
|
// through the event callback. After each request it should send back either
|
||||||
|
// EvResponse or EvFail. Additionally, it may also send application-defined
|
||||||
|
// events that the Modules can interpret.
|
||||||
|
type requestServer interface {
|
||||||
|
Subscribe(eventCallback func(Event))
|
||||||
|
SendRequest(ID, Request)
|
||||||
|
Unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// server is implemented by a requestServer wrapped into serverWithTimeout and
|
||||||
|
// serverWithLimits and is used by Scheduler.
|
||||||
|
// In addition to requestServer functionality, server can also handle timeouts,
|
||||||
|
// limit the number of parallel in-flight requests and temporarily disable
|
||||||
|
// new requests based on timeouts and response failures.
|
||||||
|
type server interface {
|
||||||
|
subscribe(eventCallback func(Event))
|
||||||
|
canRequestNow() bool
|
||||||
|
sendRequest(Request) ID
|
||||||
|
fail(string)
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer wraps a requestServer and returns a server
|
||||||
|
func NewServer(rs requestServer, clock mclock.Clock) server {
|
||||||
|
s := &serverWithLimits{}
|
||||||
|
s.parent = rs
|
||||||
|
s.serverWithTimeout.init(clock)
|
||||||
|
s.init()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventType identifies an event type, either related to a request or the server
|
||||||
|
// in general. Server events can also be externally defined.
|
||||||
|
type EventType struct {
|
||||||
|
Name string
|
||||||
|
requestEvent bool // all request events are pre-defined in request package
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event describes an event where the type of Data depends on Type.
|
||||||
|
// Server field is not required when sent through the event callback; it is filled
|
||||||
|
// out when processed by the Scheduler. Note that the Scheduler can also create
|
||||||
|
// and send events (EvRegistered, EvUnregistered) directly.
|
||||||
|
type Event struct {
|
||||||
|
Type *EventType
|
||||||
|
Server Server // filled by Scheduler
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequestEvent returns true if the event is a request event
|
||||||
|
func (e *Event) IsRequestEvent() bool {
|
||||||
|
return e.Type.requestEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestInfo assumes that the event is a request event and returns its contents
|
||||||
|
// in a convenient form.
|
||||||
|
func (e *Event) RequestInfo() (ServerAndID, Request, Response) {
|
||||||
|
data := e.Data.(RequestResponse)
|
||||||
|
return ServerAndID{Server: e.Server, ID: data.ID}, data.Request, data.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestResponse is the Data type of request events.
|
||||||
|
type RequestResponse struct {
|
||||||
|
ID ID
|
||||||
|
Request Request
|
||||||
|
Response Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverWithTimeout wraps a requestServer and introduces timeouts.
|
||||||
|
// The request's lifecycle is concluded if EvResponse or EvFail emitted by the
|
||||||
|
// parent requestServer. If this does not happen until softRequestTimeout then
|
||||||
|
// EvTimeout is emitted, after which the final EvResponse or EvFail is still
|
||||||
|
// guaranteed to follow.
|
||||||
|
// If the parent fails to send this final event for hardRequestTimeout then
|
||||||
|
// serverWithTimeout emits EvFail and discards any further events from the
|
||||||
|
// parent related to the given request.
|
||||||
|
type serverWithTimeout struct {
|
||||||
|
parent requestServer
|
||||||
|
lock sync.Mutex
|
||||||
|
clock mclock.Clock
|
||||||
|
childEventCb func(event Event)
|
||||||
|
timeouts map[ID]mclock.Timer
|
||||||
|
lastID ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes serverWithTimeout
|
||||||
|
func (s *serverWithTimeout) init(clock mclock.Clock) {
|
||||||
|
s.clock = clock
|
||||||
|
s.timeouts = make(map[ID]mclock.Timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribe subscribes to events which include parent (requestServer) events
|
||||||
|
// plus EvTimeout.
|
||||||
|
func (s *serverWithTimeout) subscribe(eventCallback func(event Event)) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.childEventCb = eventCallback
|
||||||
|
s.parent.Subscribe(s.eventCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRequest generated a new request ID, emits EvRequest, sets up the timeout
|
||||||
|
// timer, then sends the request through the parent (requestServer).
|
||||||
|
func (s *serverWithTimeout) sendRequest(request Request) (reqId ID) {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.lastID++
|
||||||
|
id := s.lastID
|
||||||
|
s.startTimeout(RequestResponse{ID: id, Request: request})
|
||||||
|
s.lock.Unlock()
|
||||||
|
s.parent.SendRequest(id, request)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventCallback is called by parent (requestServer) event subscription.
|
||||||
|
func (s *serverWithTimeout) eventCallback(event Event) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
switch event.Type {
|
||||||
|
case EvResponse, EvFail:
|
||||||
|
id := event.Data.(RequestResponse).ID
|
||||||
|
if timer, ok := s.timeouts[id]; ok {
|
||||||
|
// Note: if stopping the timer is unsuccessful then the resulting AfterFunc
|
||||||
|
// call will just do nothing
|
||||||
|
timer.Stop()
|
||||||
|
delete(s.timeouts, id)
|
||||||
|
s.childEventCb(event)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s.childEventCb(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startTimeout starts a timeout timer for the given request.
|
||||||
|
func (s *serverWithTimeout) startTimeout(reqData RequestResponse) {
|
||||||
|
id := reqData.ID
|
||||||
|
s.timeouts[id] = s.clock.AfterFunc(softRequestTimeout, func() {
|
||||||
|
s.lock.Lock()
|
||||||
|
if _, ok := s.timeouts[id]; !ok {
|
||||||
|
s.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.timeouts[id] = s.clock.AfterFunc(hardRequestTimeout-softRequestTimeout, func() {
|
||||||
|
s.lock.Lock()
|
||||||
|
if _, ok := s.timeouts[id]; !ok {
|
||||||
|
s.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(s.timeouts, id)
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
childEventCb(Event{Type: EvFail, Data: reqData})
|
||||||
|
})
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
childEventCb(Event{Type: EvTimeout, Data: reqData})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops all goroutines associated with the server.
|
||||||
|
func (s *serverWithTimeout) unsubscribe() {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
for _, timer := range s.timeouts {
|
||||||
|
if timer != nil {
|
||||||
|
timer.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.childEventCb = nil
|
||||||
|
s.parent.Unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverWithLimits wraps serverWithTimeout and implements server. It limits the
|
||||||
|
// number of parallel in-flight requests and prevents sending new requests when a
|
||||||
|
// pending one has already timed out. Server events are also rate limited.
|
||||||
|
// It also implements a failure delay mechanism that adds an exponentially growing
|
||||||
|
// delay each time a request fails (wrong answer or hard timeout). This makes the
|
||||||
|
// syncing mechanism less brittle as temporary failures of the server might happen
|
||||||
|
// sometimes, but still avoids hammering a non-functional server with requests.
|
||||||
|
type serverWithLimits struct {
|
||||||
|
serverWithTimeout
|
||||||
|
lock sync.Mutex
|
||||||
|
childEventCb func(event Event)
|
||||||
|
softTimeouts map[ID]struct{}
|
||||||
|
pendingCount, timeoutCount int
|
||||||
|
parallelLimit float32
|
||||||
|
sendEvent bool
|
||||||
|
delayTimer mclock.Timer
|
||||||
|
delayCounter int
|
||||||
|
failureDelayEnd mclock.AbsTime
|
||||||
|
failureDelay float64
|
||||||
|
serverEventBuffer int
|
||||||
|
eventBufferUpdated mclock.AbsTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes serverWithLimits
|
||||||
|
func (s *serverWithLimits) init() {
|
||||||
|
s.softTimeouts = make(map[ID]struct{})
|
||||||
|
s.parallelLimit = defaultParallelLimit
|
||||||
|
s.serverEventBuffer = maxServerEventBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribe subscribes to events which include parent (serverWithTimeout) events
|
||||||
|
// plus EvCanRequstAgain.
|
||||||
|
func (s *serverWithLimits) subscribe(eventCallback func(event Event)) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.childEventCb = eventCallback
|
||||||
|
s.serverWithTimeout.subscribe(s.eventCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventCallback is called by parent (serverWithTimeout) event subscription.
|
||||||
|
func (s *serverWithLimits) eventCallback(event Event) {
|
||||||
|
s.lock.Lock()
|
||||||
|
var sendCanRequestAgain bool
|
||||||
|
passEvent := true
|
||||||
|
switch event.Type {
|
||||||
|
case EvTimeout:
|
||||||
|
id := event.Data.(RequestResponse).ID
|
||||||
|
s.softTimeouts[id] = struct{}{}
|
||||||
|
s.timeoutCount++
|
||||||
|
s.parallelLimit -= parallelAdjustDown
|
||||||
|
if s.parallelLimit < minParallelLimit {
|
||||||
|
s.parallelLimit = minParallelLimit
|
||||||
|
}
|
||||||
|
log.Debug("Server timeout", "count", s.timeoutCount, "parallelLimit", s.parallelLimit)
|
||||||
|
case EvResponse, EvFail:
|
||||||
|
id := event.Data.(RequestResponse).ID
|
||||||
|
if _, ok := s.softTimeouts[id]; ok {
|
||||||
|
delete(s.softTimeouts, id)
|
||||||
|
s.timeoutCount--
|
||||||
|
log.Debug("Server timeout finalized", "count", s.timeoutCount, "parallelLimit", s.parallelLimit)
|
||||||
|
}
|
||||||
|
if event.Type == EvResponse && s.pendingCount >= int(s.parallelLimit) {
|
||||||
|
s.parallelLimit += parallelAdjustUp
|
||||||
|
}
|
||||||
|
s.pendingCount--
|
||||||
|
if s.canRequest() {
|
||||||
|
sendCanRequestAgain = s.sendEvent
|
||||||
|
s.sendEvent = false
|
||||||
|
}
|
||||||
|
if event.Type == EvFail {
|
||||||
|
s.failLocked("failed request")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// server event; check rate limit
|
||||||
|
if s.serverEventBuffer < maxServerEventBuffer {
|
||||||
|
now := s.clock.Now()
|
||||||
|
sinceUpdate := time.Duration(now - s.eventBufferUpdated)
|
||||||
|
if sinceUpdate >= maxServerEventRate*time.Duration(maxServerEventBuffer-s.serverEventBuffer) {
|
||||||
|
s.serverEventBuffer = maxServerEventBuffer
|
||||||
|
s.eventBufferUpdated = now
|
||||||
|
} else {
|
||||||
|
addBuffer := int(sinceUpdate / maxServerEventRate)
|
||||||
|
s.serverEventBuffer += addBuffer
|
||||||
|
s.eventBufferUpdated += mclock.AbsTime(maxServerEventRate * time.Duration(addBuffer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.serverEventBuffer > 0 {
|
||||||
|
s.serverEventBuffer--
|
||||||
|
} else {
|
||||||
|
passEvent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if passEvent {
|
||||||
|
childEventCb(event)
|
||||||
|
}
|
||||||
|
if sendCanRequestAgain {
|
||||||
|
childEventCb(Event{Type: EvCanRequestAgain})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRequest sends a request through the parent (serverWithTimeout).
|
||||||
|
func (s *serverWithLimits) sendRequest(request Request) (reqId ID) {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.pendingCount++
|
||||||
|
s.lock.Unlock()
|
||||||
|
return s.serverWithTimeout.sendRequest(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops all goroutines associated with the server.
|
||||||
|
func (s *serverWithLimits) unsubscribe() {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
if s.delayTimer != nil {
|
||||||
|
s.delayTimer.Stop()
|
||||||
|
s.delayTimer = nil
|
||||||
|
}
|
||||||
|
s.childEventCb = nil
|
||||||
|
s.serverWithTimeout.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// canRequest checks whether a new request can be started.
|
||||||
|
func (s *serverWithLimits) canRequest() bool {
|
||||||
|
if s.delayTimer != nil || s.pendingCount >= int(s.parallelLimit) || s.timeoutCount > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s.parallelLimit < minParallelLimit {
|
||||||
|
s.parallelLimit = minParallelLimit
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// canRequestNow checks whether a new request can be started, according to the
|
||||||
|
// current in-flight request count and parallelLimit, and also the failure delay
|
||||||
|
// timer.
|
||||||
|
// If it returns false then it is guaranteed that an EvCanRequestAgain will be
|
||||||
|
// sent whenever the server becomes available for requesting again.
|
||||||
|
func (s *serverWithLimits) canRequestNow() bool {
|
||||||
|
var sendCanRequestAgain bool
|
||||||
|
s.lock.Lock()
|
||||||
|
canRequest := s.canRequest()
|
||||||
|
if canRequest {
|
||||||
|
sendCanRequestAgain = s.sendEvent
|
||||||
|
s.sendEvent = false
|
||||||
|
}
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if sendCanRequestAgain {
|
||||||
|
childEventCb(Event{Type: EvCanRequestAgain})
|
||||||
|
}
|
||||||
|
return canRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// delay sets the delay timer to the given duration, disabling new requests for
|
||||||
|
// the given period.
|
||||||
|
func (s *serverWithLimits) delay(delay time.Duration) {
|
||||||
|
if s.delayTimer != nil {
|
||||||
|
// Note: if stopping the timer is unsuccessful then the resulting AfterFunc
|
||||||
|
// call will just do nothing
|
||||||
|
s.delayTimer.Stop()
|
||||||
|
s.delayTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.delayCounter++
|
||||||
|
delayCounter := s.delayCounter
|
||||||
|
log.Debug("Server delay started", "length", delay)
|
||||||
|
s.delayTimer = s.clock.AfterFunc(delay, func() {
|
||||||
|
log.Debug("Server delay ended", "length", delay)
|
||||||
|
var sendCanRequestAgain bool
|
||||||
|
s.lock.Lock()
|
||||||
|
if s.delayTimer != nil && s.delayCounter == delayCounter { // do nothing if there is a new timer now
|
||||||
|
s.delayTimer = nil
|
||||||
|
if s.canRequest() {
|
||||||
|
sendCanRequestAgain = s.sendEvent
|
||||||
|
s.sendEvent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if sendCanRequestAgain {
|
||||||
|
childEventCb(Event{Type: EvCanRequestAgain})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail reports that a response from the server was found invalid by the processing
|
||||||
|
// Module, disabling new requests for a dynamically adjused time period.
|
||||||
|
func (s *serverWithLimits) fail(desc string) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.failLocked(desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// failLocked calculates the dynamic failure delay and applies it.
|
||||||
|
func (s *serverWithLimits) failLocked(desc string) {
|
||||||
|
log.Debug("Server error", "description", desc)
|
||||||
|
s.failureDelay *= 2
|
||||||
|
now := s.clock.Now()
|
||||||
|
if now > s.failureDelayEnd {
|
||||||
|
s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay))
|
||||||
|
}
|
||||||
|
if s.failureDelay < float64(minFailureDelay) {
|
||||||
|
s.failureDelay = float64(minFailureDelay)
|
||||||
|
}
|
||||||
|
s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay)
|
||||||
|
s.delay(time.Duration(s.failureDelay))
|
||||||
|
}
|
||||||
158
beacon/light/request/server_test.go
Normal file
158
beacon/light/request/server_test.go
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testRequest = "Life, the Universe, and Everything"
|
||||||
|
testResponse = 42
|
||||||
|
)
|
||||||
|
|
||||||
|
var testEventType = &EventType{Name: "testEvent"}
|
||||||
|
|
||||||
|
func TestServerEvents(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
srv := NewServer(rs, clock)
|
||||||
|
var lastEventType *EventType
|
||||||
|
srv.subscribe(func(event Event) { lastEventType = event.Type })
|
||||||
|
evTypeName := func(evType *EventType) string {
|
||||||
|
if evType == nil {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
return evType.Name
|
||||||
|
}
|
||||||
|
expEvent := func(expType *EventType) {
|
||||||
|
if lastEventType != expType {
|
||||||
|
t.Errorf("Wrong event type (expected %s, got %s)", evTypeName(expType), evTypeName(lastEventType))
|
||||||
|
}
|
||||||
|
lastEventType = nil
|
||||||
|
}
|
||||||
|
// user events should simply be passed through
|
||||||
|
rs.eventCb(Event{Type: testEventType})
|
||||||
|
expEvent(testEventType)
|
||||||
|
// send request, soft timeout, then valid response
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
clock.Run(softRequestTimeout)
|
||||||
|
expEvent(EvTimeout)
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expEvent(EvResponse)
|
||||||
|
// send request, hard timeout (response after hard timeout should be ignored)
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
clock.Run(softRequestTimeout)
|
||||||
|
expEvent(EvTimeout)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
clock.Run(hardRequestTimeout)
|
||||||
|
expEvent(EvFail)
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expEvent(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerParallel(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
srv := NewServer(rs, &mclock.Simulated{})
|
||||||
|
srv.subscribe(func(event Event) {})
|
||||||
|
|
||||||
|
expSend := func(expSent int) {
|
||||||
|
var sent int
|
||||||
|
for sent <= expSent {
|
||||||
|
if !srv.canRequestNow() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sent++
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
}
|
||||||
|
if sent != expSent {
|
||||||
|
t.Errorf("Wrong number of parallel requests accepted (expected %d, got %d)", expSent, sent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// max out parallel allowance
|
||||||
|
expSend(defaultParallelLimit)
|
||||||
|
// 1 answered, should accept 1 more
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expSend(1)
|
||||||
|
// 2 answered, should accept 2 more
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 2, Request: testRequest, Response: testResponse}})
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 3, Request: testRequest, Response: testResponse}})
|
||||||
|
expSend(2)
|
||||||
|
// failed request, should decrease allowance and not accept more
|
||||||
|
rs.eventCb(Event{Type: EvFail, Data: RequestResponse{ID: 4, Request: testRequest}})
|
||||||
|
expSend(0)
|
||||||
|
srv.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerFail(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
srv := NewServer(rs, clock)
|
||||||
|
srv.subscribe(func(event Event) {})
|
||||||
|
expCanRequest := func(expCanRequest bool) {
|
||||||
|
if canRequest := srv.canRequestNow(); canRequest != expCanRequest {
|
||||||
|
t.Errorf("Wrong result for canRequestNow (expected %v, got %v)", expCanRequest, canRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// timed out request
|
||||||
|
expCanRequest(true)
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
expCanRequest(true)
|
||||||
|
clock.Run(softRequestTimeout)
|
||||||
|
expCanRequest(false) // cannot request when there is a timed out request
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expCanRequest(true)
|
||||||
|
// explicit server.Fail
|
||||||
|
srv.fail("")
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
expCanRequest(false) // cannot request for a while after a failure
|
||||||
|
clock.Run(minFailureDelay)
|
||||||
|
expCanRequest(true)
|
||||||
|
// request returned with EvFail
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
rs.eventCb(Event{Type: EvFail, Data: RequestResponse{ID: 2, Request: testRequest}})
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
expCanRequest(false) // EvFail should also start failure delay
|
||||||
|
clock.Run(minFailureDelay)
|
||||||
|
expCanRequest(false) // second failure delay is longer, should still be disabled
|
||||||
|
clock.Run(minFailureDelay)
|
||||||
|
expCanRequest(true)
|
||||||
|
srv.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerEventRateLimit(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
srv := NewServer(rs, clock)
|
||||||
|
var eventCount int
|
||||||
|
srv.subscribe(func(event Event) {
|
||||||
|
if !event.IsRequestEvent() {
|
||||||
|
eventCount++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expEvents := func(send, expAllowed int) {
|
||||||
|
eventCount = 0
|
||||||
|
for sent := 0; sent < send; sent++ {
|
||||||
|
rs.eventCb(Event{Type: testEventType})
|
||||||
|
}
|
||||||
|
if eventCount != expAllowed {
|
||||||
|
t.Errorf("Wrong number of server events passing rate limitation (sent %d, expected %d, got %d)", send, expAllowed, eventCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expEvents(maxServerEventBuffer+5, maxServerEventBuffer)
|
||||||
|
clock.Run(maxServerEventRate)
|
||||||
|
expEvents(5, 1)
|
||||||
|
clock.Run(maxServerEventRate * maxServerEventBuffer * 2)
|
||||||
|
expEvents(maxServerEventBuffer+5, maxServerEventBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testRequestServer struct {
|
||||||
|
eventCb func(Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *testRequestServer) Subscribe(eventCb func(Event)) { rs.eventCb = eventCb }
|
||||||
|
func (rs *testRequestServer) SendRequest(ID, Request) {}
|
||||||
|
func (rs *testRequestServer) Unsubscribe() {}
|
||||||
176
beacon/light/sync/head_sync.go
Normal file
176
beacon/light/sync/head_sync.go
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headTracker interface {
|
||||||
|
ValidateHead(head types.SignedHeader) (bool, error)
|
||||||
|
ValidateFinality(head types.FinalityUpdate) (bool, error)
|
||||||
|
SetPrefetchHead(head types.HeadInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadSync implements request.Module; it updates the validated and prefetch
|
||||||
|
// heads of HeadTracker based on the EvHead and EvSignedHead events coming from
|
||||||
|
// registered servers.
|
||||||
|
// It can also postpone the validation of the latest announced signed head
|
||||||
|
// until the committee chain is synced up to at least the required period.
|
||||||
|
type HeadSync struct {
|
||||||
|
headTracker headTracker
|
||||||
|
chain committeeChain
|
||||||
|
nextSyncPeriod uint64
|
||||||
|
chainInit bool
|
||||||
|
unvalidatedHeads map[request.Server]types.SignedHeader
|
||||||
|
unvalidatedFinality map[request.Server]types.FinalityUpdate
|
||||||
|
serverHeads map[request.Server]types.HeadInfo
|
||||||
|
headServerCount map[types.HeadInfo]headServerCount
|
||||||
|
headCounter uint64
|
||||||
|
prefetchHead types.HeadInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// headServerCount is associated with most recently seen head infos; it counts
|
||||||
|
// the number of servers currently having the given head info as their announced
|
||||||
|
// head and a counter signaling how recent that head is.
|
||||||
|
// This data is used for selecting the prefetch head.
|
||||||
|
type headServerCount struct {
|
||||||
|
serverCount int
|
||||||
|
headCounter uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHeadSync creates a new HeadSync.
|
||||||
|
func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync {
|
||||||
|
s := &HeadSync{
|
||||||
|
headTracker: headTracker,
|
||||||
|
chain: chain,
|
||||||
|
unvalidatedHeads: make(map[request.Server]types.SignedHeader),
|
||||||
|
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
|
||||||
|
serverHeads: make(map[request.Server]types.HeadInfo),
|
||||||
|
headServerCount: make(map[types.HeadInfo]headServerCount),
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *HeadSync) Process(requester request.Requester, events []request.Event) {
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case EvNewHead:
|
||||||
|
s.setServerHead(event.Server, event.Data.(types.HeadInfo))
|
||||||
|
case EvNewSignedHead:
|
||||||
|
s.newSignedHead(event.Server, event.Data.(types.SignedHeader))
|
||||||
|
case EvNewFinalityUpdate:
|
||||||
|
s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate))
|
||||||
|
case request.EvUnregistered:
|
||||||
|
s.setServerHead(event.Server, types.HeadInfo{})
|
||||||
|
delete(s.serverHeads, event.Server)
|
||||||
|
delete(s.unvalidatedHeads, event.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPeriod, chainInit := s.chain.NextSyncPeriod()
|
||||||
|
if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit {
|
||||||
|
s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit
|
||||||
|
s.processUnvalidated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSignedHead handles received signed head; either validates it if the chain
|
||||||
|
// is properly synced or stores it for further validation.
|
||||||
|
func (s *HeadSync) newSignedHead(server request.Server, signedHead types.SignedHeader) {
|
||||||
|
if !s.chainInit || types.SyncPeriod(signedHead.SignatureSlot) > s.nextSyncPeriod {
|
||||||
|
s.unvalidatedHeads[server] = signedHead
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.headTracker.ValidateHead(signedHead)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSignedHead handles received signed head; either validates it if the chain
|
||||||
|
// is properly synced or stores it for further validation.
|
||||||
|
func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) {
|
||||||
|
if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod {
|
||||||
|
s.unvalidatedFinality[server] = finalityUpdate
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.headTracker.ValidateFinality(finalityUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// processUnvalidatedHeads iterates the list of unvalidated heads and validates
|
||||||
|
// those which can be validated.
|
||||||
|
func (s *HeadSync) processUnvalidated() {
|
||||||
|
if !s.chainInit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for server, signedHead := range s.unvalidatedHeads {
|
||||||
|
if types.SyncPeriod(signedHead.SignatureSlot) <= s.nextSyncPeriod {
|
||||||
|
s.headTracker.ValidateHead(signedHead)
|
||||||
|
delete(s.unvalidatedHeads, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for server, finalityUpdate := range s.unvalidatedFinality {
|
||||||
|
if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
||||||
|
s.headTracker.ValidateFinality(finalityUpdate)
|
||||||
|
delete(s.unvalidatedFinality, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setServerHead processes non-validated server head announcements and updates
|
||||||
|
// the prefetch head if necessary.
|
||||||
|
func (s *HeadSync) setServerHead(server request.Server, head types.HeadInfo) bool {
|
||||||
|
if oldHead, ok := s.serverHeads[server]; ok {
|
||||||
|
if head == oldHead {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h := s.headServerCount[oldHead]
|
||||||
|
if h.serverCount--; h.serverCount > 0 {
|
||||||
|
s.headServerCount[oldHead] = h
|
||||||
|
} else {
|
||||||
|
delete(s.headServerCount, oldHead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if head != (types.HeadInfo{}) {
|
||||||
|
h, ok := s.headServerCount[head]
|
||||||
|
if !ok {
|
||||||
|
s.headCounter++
|
||||||
|
h.headCounter = s.headCounter
|
||||||
|
}
|
||||||
|
h.serverCount++
|
||||||
|
s.headServerCount[head] = h
|
||||||
|
s.serverHeads[server] = head
|
||||||
|
} else {
|
||||||
|
delete(s.serverHeads, server)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
bestHead types.HeadInfo
|
||||||
|
bestHeadInfo headServerCount
|
||||||
|
)
|
||||||
|
for head, headServerCount := range s.headServerCount {
|
||||||
|
if headServerCount.serverCount > bestHeadInfo.serverCount ||
|
||||||
|
(headServerCount.serverCount == bestHeadInfo.serverCount && headServerCount.headCounter > bestHeadInfo.headCounter) {
|
||||||
|
bestHead, bestHeadInfo = head, headServerCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bestHead == s.prefetchHead {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.prefetchHead = bestHead
|
||||||
|
s.headTracker.SetPrefetchHead(bestHead)
|
||||||
|
return true
|
||||||
|
}
|
||||||
151
beacon/light/sync/head_sync_test.go
Normal file
151
beacon/light/sync/head_sync_test.go
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testServer1 = "testServer1"
|
||||||
|
testServer2 = "testServer2"
|
||||||
|
testServer3 = "testServer3"
|
||||||
|
testServer4 = "testServer4"
|
||||||
|
|
||||||
|
testHead0 = types.HeadInfo{}
|
||||||
|
testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}}
|
||||||
|
testHead2 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{2}}
|
||||||
|
testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}}
|
||||||
|
testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}}
|
||||||
|
|
||||||
|
testSHead1 = types.SignedHeader{SignatureSlot: 0x0124, Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}
|
||||||
|
testSHead2 = types.SignedHeader{SignatureSlot: 0x2010, Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}
|
||||||
|
// testSHead3 is at the end of period 1 but signed in period 2
|
||||||
|
testSHead3 = types.SignedHeader{SignatureSlot: 0x4000, Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}}
|
||||||
|
testSHead4 = types.SignedHeader{SignatureSlot: 0x6444, Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidatedHead(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
ht := &TestHeadTracker{}
|
||||||
|
headSync := NewHeadSync(ht, chain)
|
||||||
|
ts := NewTestScheduler(t, headSync)
|
||||||
|
|
||||||
|
ht.ExpValidated(t, 0, nil)
|
||||||
|
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead1)
|
||||||
|
ts.Run(1)
|
||||||
|
// announced head should be queued because of uninitialized chain
|
||||||
|
ht.ExpValidated(t, 1, nil)
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(0) // initialize chain
|
||||||
|
ts.Run(2)
|
||||||
|
// expect previously queued head to be validated
|
||||||
|
ht.ExpValidated(t, 2, []types.SignedHeader{testSHead1})
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead2)
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead2)
|
||||||
|
ts.Run(3)
|
||||||
|
// expect both head announcements to be validated instantly
|
||||||
|
ht.ExpValidated(t, 3, []types.SignedHeader{testSHead2, testSHead2})
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead3)
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer3, testSHead4)
|
||||||
|
ts.Run(4)
|
||||||
|
// future period annonced heads should be queued
|
||||||
|
ht.ExpValidated(t, 4, nil)
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(2)
|
||||||
|
ts.Run(5)
|
||||||
|
// testSHead3 can be validated now but not testSHead4
|
||||||
|
ht.ExpValidated(t, 5, []types.SignedHeader{testSHead3})
|
||||||
|
|
||||||
|
// server 3 disconnected without proving period 3, its announced head should be dropped
|
||||||
|
ts.RemoveServer(testServer3)
|
||||||
|
ts.Run(6)
|
||||||
|
ht.ExpValidated(t, 6, nil)
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(3)
|
||||||
|
ts.Run(7)
|
||||||
|
// testSHead4 could be validated now but it's not queued by any registered server
|
||||||
|
ht.ExpValidated(t, 7, nil)
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead4)
|
||||||
|
ts.Run(8)
|
||||||
|
// now testSHead4 should be validated
|
||||||
|
ht.ExpValidated(t, 8, []types.SignedHeader{testSHead4})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrefetchHead(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
ht := &TestHeadTracker{}
|
||||||
|
headSync := NewHeadSync(ht, chain)
|
||||||
|
ts := NewTestScheduler(t, headSync)
|
||||||
|
|
||||||
|
ht.ExpPrefetch(t, 0, testHead0) // no servers registered
|
||||||
|
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer1, testHead1)
|
||||||
|
ts.Run(1)
|
||||||
|
ht.ExpPrefetch(t, 1, testHead1) // s1: h1
|
||||||
|
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer2, testHead2)
|
||||||
|
ts.Run(2)
|
||||||
|
ht.ExpPrefetch(t, 2, testHead2) // s1: h1, s2: h2
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewHead, testServer1, testHead2)
|
||||||
|
ts.Run(3)
|
||||||
|
ht.ExpPrefetch(t, 3, testHead2) // s1: h2, s2: h2
|
||||||
|
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer3, testHead3)
|
||||||
|
ts.Run(4)
|
||||||
|
ht.ExpPrefetch(t, 4, testHead2) // s1: h2, s2: h2, s3: h3
|
||||||
|
|
||||||
|
ts.AddServer(testServer4, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer4, testHead4)
|
||||||
|
ts.Run(5)
|
||||||
|
ht.ExpPrefetch(t, 5, testHead2) // s1: h2, s2: h2, s3: h3, s4: h4
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewHead, testServer2, testHead3)
|
||||||
|
ts.Run(6)
|
||||||
|
ht.ExpPrefetch(t, 6, testHead3) // s1: h2, s2: h3, s3: h3, s4: h4
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer3)
|
||||||
|
ts.Run(7)
|
||||||
|
ht.ExpPrefetch(t, 7, testHead4) // s1: h2, s2: h3, s4: h4
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer1)
|
||||||
|
ts.Run(8)
|
||||||
|
ht.ExpPrefetch(t, 8, testHead4) // s2: h3, s4: h4
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer4)
|
||||||
|
ts.Run(9)
|
||||||
|
ht.ExpPrefetch(t, 9, testHead3) // s2: h3
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer2)
|
||||||
|
ts.Run(10)
|
||||||
|
ht.ExpPrefetch(t, 10, testHead0) // no servers registered
|
||||||
|
}
|
||||||
254
beacon/light/sync/test_helpers.go
Normal file
254
beacon/light/sync/test_helpers.go
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type requestWithID struct {
|
||||||
|
sid request.ServerAndID
|
||||||
|
request request.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestScheduler struct {
|
||||||
|
t *testing.T
|
||||||
|
module request.Module
|
||||||
|
events []request.Event
|
||||||
|
servers []request.Server
|
||||||
|
allowance map[request.Server]int
|
||||||
|
sent map[int][]requestWithID
|
||||||
|
testIndex int
|
||||||
|
expFail map[request.Server]int // expected Server.Fail calls during next Run
|
||||||
|
lastId request.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestScheduler(t *testing.T, module request.Module) *TestScheduler {
|
||||||
|
return &TestScheduler{
|
||||||
|
t: t,
|
||||||
|
module: module,
|
||||||
|
allowance: make(map[request.Server]int),
|
||||||
|
expFail: make(map[request.Server]int),
|
||||||
|
sent: make(map[int][]requestWithID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Run(testIndex int, exp ...any) {
|
||||||
|
expReqs := make([]requestWithID, len(exp)/2)
|
||||||
|
id := ts.lastId
|
||||||
|
for i := range expReqs {
|
||||||
|
id++
|
||||||
|
expReqs[i] = requestWithID{
|
||||||
|
sid: request.ServerAndID{Server: exp[i*2].(request.Server), ID: id},
|
||||||
|
request: exp[i*2+1].(request.Request),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(expReqs) == 0 {
|
||||||
|
expReqs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.testIndex = testIndex
|
||||||
|
ts.module.Process(ts, ts.events)
|
||||||
|
ts.events = nil
|
||||||
|
|
||||||
|
for server, count := range ts.expFail {
|
||||||
|
delete(ts.expFail, server)
|
||||||
|
if count == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ts.t.Errorf("Missing %d Server.Fail(s) from server %s in test case #%d", count, server.(string), testIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(ts.sent[testIndex], expReqs) {
|
||||||
|
ts.t.Errorf("Wrong sent requests in test case #%d (expected %v, got %v)", testIndex, expReqs, ts.sent[testIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) CanSendTo() (cs []request.Server) {
|
||||||
|
for _, server := range ts.servers {
|
||||||
|
if ts.allowance[server] > 0 {
|
||||||
|
cs = append(cs, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Send(server request.Server, req request.Request) request.ID {
|
||||||
|
ts.lastId++
|
||||||
|
ts.sent[ts.testIndex] = append(ts.sent[ts.testIndex], requestWithID{
|
||||||
|
sid: request.ServerAndID{Server: server, ID: ts.lastId},
|
||||||
|
request: req,
|
||||||
|
})
|
||||||
|
ts.allowance[server]--
|
||||||
|
return ts.lastId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Fail(server request.Server, desc string) {
|
||||||
|
if ts.expFail[server] == 0 {
|
||||||
|
ts.t.Errorf("Unexpected Fail from server %s in test case #%d: %s", server.(string), ts.testIndex, desc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ts.expFail[server]--
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Request(testIndex, reqIndex int) requestWithID {
|
||||||
|
if len(ts.sent[testIndex]) < reqIndex {
|
||||||
|
ts.t.Errorf("Missing request from test case %d index %d", testIndex, reqIndex)
|
||||||
|
return requestWithID{}
|
||||||
|
}
|
||||||
|
return ts.sent[testIndex][reqIndex-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) ServerEvent(evType *request.EventType, server request.Server, data any) {
|
||||||
|
ts.events = append(ts.events, request.Event{
|
||||||
|
Type: evType,
|
||||||
|
Server: server,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) RequestEvent(evType *request.EventType, req requestWithID, resp request.Response) {
|
||||||
|
if req.request == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ts.events = append(ts.events, request.Event{
|
||||||
|
Type: evType,
|
||||||
|
Server: req.sid.Server,
|
||||||
|
Data: request.RequestResponse{
|
||||||
|
ID: req.sid.ID,
|
||||||
|
Request: req.request,
|
||||||
|
Response: resp,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) AddServer(server request.Server, allowance int) {
|
||||||
|
ts.servers = append(ts.servers, server)
|
||||||
|
ts.allowance[server] = allowance
|
||||||
|
ts.ServerEvent(request.EvRegistered, server, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) RemoveServer(server request.Server) {
|
||||||
|
ts.servers = append(ts.servers, server)
|
||||||
|
for i, s := range ts.servers {
|
||||||
|
if s == server {
|
||||||
|
copy(ts.servers[i:len(ts.servers)-1], ts.servers[i+1:])
|
||||||
|
ts.servers = ts.servers[:len(ts.servers)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(ts.allowance, server)
|
||||||
|
ts.ServerEvent(request.EvUnregistered, server, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) AddAllowance(server request.Server, allowance int) {
|
||||||
|
ts.allowance[server] += allowance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) ExpFail(server request.Server) {
|
||||||
|
ts.expFail[server]++
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCommitteeChain struct {
|
||||||
|
fsp, nsp uint64
|
||||||
|
init bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestCommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
|
||||||
|
t.fsp, t.nsp, t.init = bootstrap.Header.SyncPeriod(), bootstrap.Header.SyncPeriod()+2, true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestCommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
|
||||||
|
period := update.AttestedHeader.Header.SyncPeriod()
|
||||||
|
if period < t.fsp || period > t.nsp || !t.init {
|
||||||
|
return light.ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
if period == t.nsp {
|
||||||
|
t.nsp++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestCommitteeChain) NextSyncPeriod() (uint64, bool) {
|
||||||
|
return t.nsp, t.init
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) ExpInit(t *testing.T, ExpInit bool) {
|
||||||
|
if tc.init != ExpInit {
|
||||||
|
t.Errorf("Incorrect init flag (expected %v, got %v)", ExpInit, tc.init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestCommitteeChain) SetNextSyncPeriod(nsp uint64) {
|
||||||
|
t.init, t.nsp = true, nsp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) ExpNextSyncPeriod(t *testing.T, expNsp uint64) {
|
||||||
|
tc.ExpInit(t, true)
|
||||||
|
if tc.nsp != expNsp {
|
||||||
|
t.Errorf("Incorrect NextSyncPeriod (expected %d, got %d)", expNsp, tc.nsp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestHeadTracker struct {
|
||||||
|
phead types.HeadInfo
|
||||||
|
validated []types.SignedHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ValidateHead(head types.SignedHeader) (bool, error) {
|
||||||
|
ht.validated = append(ht.validated, head)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add test case for finality
|
||||||
|
func (ht *TestHeadTracker) ValidateFinality(head types.FinalityUpdate) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ExpValidated(t *testing.T, tci int, expHeads []types.SignedHeader) {
|
||||||
|
for i, expHead := range expHeads {
|
||||||
|
if i >= len(ht.validated) {
|
||||||
|
t.Errorf("Missing validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got none)", tci, i, expHead.Header.Slot, expHead.Header.Hash())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ht.validated[i] != expHead {
|
||||||
|
vhead := ht.validated[i].Header
|
||||||
|
t.Errorf("Wrong validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, i, expHead.Header.Slot, expHead.Header.Hash(), vhead.Slot, vhead.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := len(expHeads); i < len(ht.validated); i++ {
|
||||||
|
vhead := ht.validated[i].Header
|
||||||
|
t.Errorf("Unexpected validated head in test case #%d index #%d (expected none, got {slot %d blockRoot %x})", tci, i, vhead.Slot, vhead.Hash())
|
||||||
|
}
|
||||||
|
ht.validated = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) SetPrefetchHead(head types.HeadInfo) {
|
||||||
|
ht.phead = head
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ExpPrefetch(t *testing.T, tci int, exp types.HeadInfo) {
|
||||||
|
if ht.phead != exp {
|
||||||
|
t.Errorf("Wrong prefetch head in test case #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, exp.Slot, exp.BlockRoot, ht.phead.Slot, ht.phead.BlockRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
beacon/light/sync/types.go
Normal file
42
beacon/light/sync/types.go
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo
|
||||||
|
EvNewSignedHead = &request.EventType{Name: "newSignedHead"} // data: types.SignedHeader
|
||||||
|
EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ReqUpdates struct {
|
||||||
|
FirstPeriod, Count uint64
|
||||||
|
}
|
||||||
|
RespUpdates struct {
|
||||||
|
Updates []*types.LightClientUpdate
|
||||||
|
Committees []*types.SerializedSyncCommittee
|
||||||
|
}
|
||||||
|
ReqHeader common.Hash
|
||||||
|
ReqCheckpointData common.Hash
|
||||||
|
ReqBeaconBlock common.Hash
|
||||||
|
)
|
||||||
299
beacon/light/sync/update_sync.go
Normal file
299
beacon/light/sync/update_sync.go
Normal file
|
|
@ -0,0 +1,299 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxUpdateRequest = 8 // maximum number of updates requested in a single request
|
||||||
|
|
||||||
|
type committeeChain interface {
|
||||||
|
CheckpointInit(bootstrap types.BootstrapData) error
|
||||||
|
InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error
|
||||||
|
NextSyncPeriod() (uint64, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckpointInit implements request.Module; it fetches the light client bootstrap
|
||||||
|
// data belonging to the given checkpoint hash and initializes the committee chain
|
||||||
|
// if successful.
|
||||||
|
type CheckpointInit struct {
|
||||||
|
chain committeeChain
|
||||||
|
checkpointHash common.Hash
|
||||||
|
locked request.ServerAndID
|
||||||
|
initialized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCheckpointInit creates a new CheckpointInit.
|
||||||
|
func NewCheckpointInit(chain committeeChain, checkpointHash common.Hash) *CheckpointInit {
|
||||||
|
return &CheckpointInit{
|
||||||
|
chain: chain,
|
||||||
|
checkpointHash: checkpointHash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *CheckpointInit) Process(requester request.Requester, events []request.Event) {
|
||||||
|
for _, event := range events {
|
||||||
|
if !event.IsRequestEvent() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sid, req, resp := event.RequestInfo()
|
||||||
|
if s.locked == sid {
|
||||||
|
s.locked = request.ServerAndID{}
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) {
|
||||||
|
s.chain.CheckpointInit(*checkpoint)
|
||||||
|
s.initialized = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requester.Fail(event.Server, "invalid checkpoint data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// start a request if possible
|
||||||
|
if s.initialized || s.locked != (request.ServerAndID{}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cs := requester.CanSendTo()
|
||||||
|
if len(cs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server := cs[0]
|
||||||
|
id := requester.Send(server, ReqCheckpointData(s.checkpointHash))
|
||||||
|
s.locked = request.ServerAndID{Server: server, ID: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardUpdateSync implements request.Module; it fetches updates between the
|
||||||
|
// committee chain head and each server's announced head. Updates are fetched
|
||||||
|
// in batches and multiple batches can also be requested in parallel.
|
||||||
|
// Out of order responses are also handled; if a batch of updates cannot be added
|
||||||
|
// to the chain immediately because of a gap then the future updates are
|
||||||
|
// remembered until they can be processed.
|
||||||
|
type ForwardUpdateSync struct {
|
||||||
|
chain committeeChain
|
||||||
|
rangeLock rangeLock
|
||||||
|
lockedIDs map[request.ServerAndID]struct{}
|
||||||
|
processQueue []updateResponse
|
||||||
|
nextSyncPeriod map[request.Server]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForwardUpdateSync creates a new ForwardUpdateSync.
|
||||||
|
func NewForwardUpdateSync(chain committeeChain) *ForwardUpdateSync {
|
||||||
|
return &ForwardUpdateSync{
|
||||||
|
chain: chain,
|
||||||
|
rangeLock: make(rangeLock),
|
||||||
|
lockedIDs: make(map[request.ServerAndID]struct{}),
|
||||||
|
nextSyncPeriod: make(map[request.Server]uint64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rangeLock allows locking sections of an integer space, preventing the syncing
|
||||||
|
// mechanism from making requests again for sections where a not timed out request
|
||||||
|
// is already pending or where already fetched and unprocessed data is available.
|
||||||
|
type rangeLock map[uint64]int
|
||||||
|
|
||||||
|
// lock locks or unlocks the given section, depending on the sign of the add parameter.
|
||||||
|
func (r rangeLock) lock(first, count uint64, add int) {
|
||||||
|
for i := first; i < first+count; i++ {
|
||||||
|
if v := r[i] + add; v > 0 {
|
||||||
|
r[i] = v
|
||||||
|
} else {
|
||||||
|
delete(r, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstUnlocked returns the first unlocked section starting at or after start
|
||||||
|
// and not longer than maxCount.
|
||||||
|
func (r rangeLock) firstUnlocked(start, maxCount uint64) (first, count uint64) {
|
||||||
|
first = start
|
||||||
|
for {
|
||||||
|
if _, ok := r[first]; !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
first++
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
count++
|
||||||
|
if count == maxCount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := r[first+count]; ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// lockRange locks the range belonging to the given update request, unless the
|
||||||
|
// same request has already been locked
|
||||||
|
func (s *ForwardUpdateSync) lockRange(sid request.ServerAndID, req ReqUpdates) {
|
||||||
|
if _, ok := s.lockedIDs[sid]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.lockedIDs[sid] = struct{}{}
|
||||||
|
s.rangeLock.lock(req.FirstPeriod, req.Count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockRange unlocks the range belonging to the given update request, unless
|
||||||
|
// same request has already been unlocked
|
||||||
|
func (s *ForwardUpdateSync) unlockRange(sid request.ServerAndID, req ReqUpdates) {
|
||||||
|
if _, ok := s.lockedIDs[sid]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(s.lockedIDs, sid)
|
||||||
|
s.rangeLock.lock(req.FirstPeriod, req.Count, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyRange returns true if the number of updates and the individual update
|
||||||
|
// periods in the response match the requested section.
|
||||||
|
func (s *ForwardUpdateSync) verifyRange(request ReqUpdates, response RespUpdates) bool {
|
||||||
|
if uint64(len(response.Updates)) != request.Count || uint64(len(response.Committees)) != request.Count {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, update := range response.Updates {
|
||||||
|
if update.AttestedHeader.Header.SyncPeriod() != request.FirstPeriod+uint64(i) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateResponse is a response that has passed initial verification and has been
|
||||||
|
// queued for processing. Note that an update response cannot be processed until
|
||||||
|
// the previous updates have also been added to the chain.
|
||||||
|
type updateResponse struct {
|
||||||
|
sid request.ServerAndID
|
||||||
|
request ReqUpdates
|
||||||
|
response RespUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateResponseList implements sort.Sort and sorts update request/response events by FirstPeriod.
|
||||||
|
type updateResponseList []updateResponse
|
||||||
|
|
||||||
|
func (u updateResponseList) Len() int { return len(u) }
|
||||||
|
func (u updateResponseList) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
|
||||||
|
func (u updateResponseList) Less(i, j int) bool {
|
||||||
|
return u[i].request.FirstPeriod < u[j].request.FirstPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *ForwardUpdateSync) Process(requester request.Requester, events []request.Event) {
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||||
|
sid, rq, rs := event.RequestInfo()
|
||||||
|
req := rq.(ReqUpdates)
|
||||||
|
var queued bool
|
||||||
|
if event.Type == request.EvResponse {
|
||||||
|
resp := rs.(RespUpdates)
|
||||||
|
if s.verifyRange(req, resp) {
|
||||||
|
// there is a response with a valid format; put it in the process queue
|
||||||
|
s.processQueue = append(s.processQueue, updateResponse{sid: sid, request: req, response: resp})
|
||||||
|
s.lockRange(sid, req)
|
||||||
|
queued = true
|
||||||
|
} else {
|
||||||
|
requester.Fail(event.Server, "invalid update range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !queued {
|
||||||
|
s.unlockRange(sid, req)
|
||||||
|
}
|
||||||
|
case EvNewSignedHead:
|
||||||
|
signedHead := event.Data.(types.SignedHeader)
|
||||||
|
s.nextSyncPeriod[event.Server] = types.SyncPeriod(signedHead.SignatureSlot + 256)
|
||||||
|
case request.EvUnregistered:
|
||||||
|
delete(s.nextSyncPeriod, event.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try processing ordered list of available responses
|
||||||
|
sort.Sort(updateResponseList(s.processQueue))
|
||||||
|
for s.processQueue != nil {
|
||||||
|
u := s.processQueue[0]
|
||||||
|
if !s.processResponse(requester, u) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s.unlockRange(u.sid, u.request)
|
||||||
|
s.processQueue = s.processQueue[1:]
|
||||||
|
if len(s.processQueue) == 0 {
|
||||||
|
s.processQueue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start new requests if possible
|
||||||
|
startPeriod, chainInit := s.chain.NextSyncPeriod()
|
||||||
|
if !chainInit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
firstPeriod, maxCount := s.rangeLock.firstUnlocked(startPeriod, maxUpdateRequest)
|
||||||
|
var (
|
||||||
|
sendTo request.Server
|
||||||
|
bestCount uint64
|
||||||
|
)
|
||||||
|
for _, server := range requester.CanSendTo() {
|
||||||
|
nextPeriod := s.nextSyncPeriod[server]
|
||||||
|
if nextPeriod <= firstPeriod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
count := maxCount
|
||||||
|
if nextPeriod < firstPeriod+maxCount {
|
||||||
|
count = nextPeriod - firstPeriod
|
||||||
|
}
|
||||||
|
if count > bestCount {
|
||||||
|
sendTo, bestCount = server, count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sendTo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req := ReqUpdates{FirstPeriod: firstPeriod, Count: bestCount}
|
||||||
|
id := requester.Send(sendTo, req)
|
||||||
|
s.lockRange(request.ServerAndID{Server: sendTo, ID: id}, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processResponse adds the fetched updates and committees to the committee chain.
|
||||||
|
// Returns true in case of full or partial success.
|
||||||
|
func (s *ForwardUpdateSync) processResponse(requester request.Requester, u updateResponse) (success bool) {
|
||||||
|
for i, update := range u.response.Updates {
|
||||||
|
if err := s.chain.InsertUpdate(update, u.response.Committees[i]); err != nil {
|
||||||
|
if err == light.ErrInvalidPeriod {
|
||||||
|
// there is a gap in the update periods; stop processing without
|
||||||
|
// failing and try again next time
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == light.ErrInvalidUpdate || err == light.ErrWrongCommitteeRoot || err == light.ErrCannotReorg {
|
||||||
|
requester.Fail(u.sid.Server, "invalid update received")
|
||||||
|
} else {
|
||||||
|
log.Error("Unexpected InsertUpdate error", "error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
219
beacon/light/sync/update_sync_test.go
Normal file
219
beacon/light/sync/update_sync_test.go
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckpointInit(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
checkpoint := &types.BootstrapData{Header: types.Header{Slot: 0x2000*4 + 0x1000}} // period 4
|
||||||
|
checkpointHash := checkpoint.Header.Hash()
|
||||||
|
chkInit := NewCheckpointInit(chain, checkpointHash)
|
||||||
|
ts := NewTestScheduler(t, chkInit)
|
||||||
|
// add 2 servers
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
|
||||||
|
// expect bootstrap request to server 1
|
||||||
|
ts.Run(1, testServer1, ReqCheckpointData(checkpointHash))
|
||||||
|
|
||||||
|
// server 1 times out; expect request to server 2
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 1), nil)
|
||||||
|
ts.Run(2, testServer2, ReqCheckpointData(checkpointHash))
|
||||||
|
|
||||||
|
// invalid response from server 2; expect init state to still be false
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), &types.BootstrapData{Header: types.Header{Slot: 123456}})
|
||||||
|
ts.ExpFail(testServer2)
|
||||||
|
ts.Run(3)
|
||||||
|
chain.ExpInit(t, false)
|
||||||
|
|
||||||
|
// server 1 fails (hard timeout)
|
||||||
|
ts.RequestEvent(request.EvFail, ts.Request(1, 1), nil)
|
||||||
|
ts.Run(4)
|
||||||
|
chain.ExpInit(t, false)
|
||||||
|
|
||||||
|
// server 3 is registered; expect bootstrap request to server 3
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.Run(5, testServer3, ReqCheckpointData(checkpointHash))
|
||||||
|
|
||||||
|
// valid response from server 3; expect chain to be initialized
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 1), checkpoint)
|
||||||
|
ts.Run(6)
|
||||||
|
chain.ExpInit(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSyncParallel(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
chain.SetNextSyncPeriod(0)
|
||||||
|
updateSync := NewForwardUpdateSync(chain)
|
||||||
|
ts := NewTestScheduler(t, updateSync)
|
||||||
|
// add 2 servers, head at period 100; allow 3-3 parallel requests for each
|
||||||
|
ts.AddServer(testServer1, 3)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer1, types.SignedHeader{SignatureSlot: 0x2000*100 + 0x1000})
|
||||||
|
ts.AddServer(testServer2, 3)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer2, types.SignedHeader{SignatureSlot: 0x2000*100 + 0x1000})
|
||||||
|
|
||||||
|
// expect 6 requests to be sent
|
||||||
|
ts.Run(1,
|
||||||
|
testServer1, ReqUpdates{FirstPeriod: 0, Count: 8},
|
||||||
|
testServer1, ReqUpdates{FirstPeriod: 8, Count: 8},
|
||||||
|
testServer1, ReqUpdates{FirstPeriod: 16, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 24, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 32, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 40, Count: 8})
|
||||||
|
|
||||||
|
// valid response to request 1; expect 8 periods synced and a new request started
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 1), testRespUpdate(ts.Request(1, 1)))
|
||||||
|
ts.AddAllowance(testServer1, 1)
|
||||||
|
ts.Run(2, testServer1, ReqUpdates{FirstPeriod: 48, Count: 8})
|
||||||
|
chain.ExpNextSyncPeriod(t, 8)
|
||||||
|
|
||||||
|
// valid response to requests 4 and 5
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 4), testRespUpdate(ts.Request(1, 4)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 5), testRespUpdate(ts.Request(1, 5)))
|
||||||
|
ts.AddAllowance(testServer2, 2)
|
||||||
|
// expect 2 more requests but no sync progress (responses 4 and 5 cannot be added before 2 and 3)
|
||||||
|
ts.Run(3,
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 56, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 64, Count: 8})
|
||||||
|
chain.ExpNextSyncPeriod(t, 8)
|
||||||
|
|
||||||
|
// soft timeout for requests 2 and 3 (server 1 is overloaded)
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 2), nil)
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 3), nil)
|
||||||
|
// no allowance, no more requests
|
||||||
|
ts.Run(4)
|
||||||
|
|
||||||
|
// valid response to requests 6 and 8 and 9
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 6), testRespUpdate(ts.Request(1, 6)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(3, 1), testRespUpdate(ts.Request(3, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(3, 2), testRespUpdate(ts.Request(3, 2)))
|
||||||
|
ts.AddAllowance(testServer2, 3)
|
||||||
|
// server 2 can now resend requests 2 and 3 (timed out by server 1) and also send a new one
|
||||||
|
ts.Run(5,
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 8, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 16, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 72, Count: 8})
|
||||||
|
|
||||||
|
// server 1 finally answers timed out request 2
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 2), testRespUpdate(ts.Request(1, 2)))
|
||||||
|
ts.AddAllowance(testServer1, 1)
|
||||||
|
// expect sync progress and one new request
|
||||||
|
ts.Run(6, testServer1, ReqUpdates{FirstPeriod: 80, Count: 8})
|
||||||
|
chain.ExpNextSyncPeriod(t, 16)
|
||||||
|
|
||||||
|
// server 2 answers requests 11 and 12 (resends of requests 2 and 3)
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 1), testRespUpdate(ts.Request(5, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 2), testRespUpdate(ts.Request(5, 2)))
|
||||||
|
ts.AddAllowance(testServer2, 2)
|
||||||
|
ts.Run(7,
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 88, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 96, Count: 4})
|
||||||
|
// finally the gap is filled, update can process responses up to req6
|
||||||
|
chain.ExpNextSyncPeriod(t, 48)
|
||||||
|
|
||||||
|
// all remaining requests are answered
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 3), testRespUpdate(ts.Request(1, 3)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testRespUpdate(ts.Request(2, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 3), testRespUpdate(ts.Request(5, 3)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(6, 1), testRespUpdate(ts.Request(6, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testRespUpdate(ts.Request(7, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 2), testRespUpdate(ts.Request(7, 2)))
|
||||||
|
ts.Run(8)
|
||||||
|
// expect chain to be fully synced
|
||||||
|
chain.ExpNextSyncPeriod(t, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSyncDifferentHeads(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
chain.SetNextSyncPeriod(10)
|
||||||
|
updateSync := NewForwardUpdateSync(chain)
|
||||||
|
ts := NewTestScheduler(t, updateSync)
|
||||||
|
// add 3 servers with different announced head periods
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer1, types.SignedHeader{SignatureSlot: 0x2000*15 + 0x1000})
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer2, types.SignedHeader{SignatureSlot: 0x2000*16 + 0x1000})
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer3, types.SignedHeader{SignatureSlot: 0x2000*17 + 0x1000})
|
||||||
|
|
||||||
|
// expect request to the best announced head
|
||||||
|
ts.Run(1, testServer3, ReqUpdates{FirstPeriod: 10, Count: 7})
|
||||||
|
|
||||||
|
// request times out, expect request to the next best head
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 1), nil)
|
||||||
|
ts.Run(2, testServer2, ReqUpdates{FirstPeriod: 10, Count: 6})
|
||||||
|
|
||||||
|
// request times out, expect request to the last available server
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(2, 1), nil)
|
||||||
|
ts.Run(3, testServer1, ReqUpdates{FirstPeriod: 10, Count: 5})
|
||||||
|
|
||||||
|
// valid response to request 3, expect chain synced to period 15
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(3, 1), testRespUpdate(ts.Request(3, 1)))
|
||||||
|
ts.AddAllowance(testServer1, 1)
|
||||||
|
ts.Run(4)
|
||||||
|
chain.ExpNextSyncPeriod(t, 15)
|
||||||
|
|
||||||
|
// invalid response to request 1, server can only deliver updates up to period 15 despite announced head
|
||||||
|
truncated := ts.Request(1, 1)
|
||||||
|
truncated.request = ReqUpdates{FirstPeriod: 10, Count: 5}
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 1), testRespUpdate(truncated))
|
||||||
|
ts.ExpFail(testServer3)
|
||||||
|
ts.Run(5)
|
||||||
|
// expect no progress of chain head
|
||||||
|
chain.ExpNextSyncPeriod(t, 15)
|
||||||
|
|
||||||
|
// valid response to request 2, expect chain synced to period 16
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testRespUpdate(ts.Request(2, 1)))
|
||||||
|
ts.AddAllowance(testServer2, 1)
|
||||||
|
ts.Run(6)
|
||||||
|
chain.ExpNextSyncPeriod(t, 16)
|
||||||
|
|
||||||
|
// a new server is registered with announced head period 17
|
||||||
|
ts.AddServer(testServer4, 1)
|
||||||
|
ts.ServerEvent(EvNewSignedHead, testServer4, types.SignedHeader{SignatureSlot: 0x2000*17 + 0x1000})
|
||||||
|
// expect request to sync one more period
|
||||||
|
ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1})
|
||||||
|
|
||||||
|
// valid response, expect chain synced to period 17
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testRespUpdate(ts.Request(7, 1)))
|
||||||
|
ts.AddAllowance(testServer4, 1)
|
||||||
|
ts.Run(8)
|
||||||
|
chain.ExpNextSyncPeriod(t, 17)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRespUpdate(request requestWithID) request.Response {
|
||||||
|
var resp RespUpdates
|
||||||
|
if request.request == nil {
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
req := request.request.(ReqUpdates)
|
||||||
|
resp.Updates = make([]*types.LightClientUpdate, int(req.Count))
|
||||||
|
resp.Committees = make([]*types.SerializedSyncCommittee, int(req.Count))
|
||||||
|
period := req.FirstPeriod
|
||||||
|
for i := range resp.Updates {
|
||||||
|
resp.Updates[i] = &types.LightClientUpdate{AttestedHeader: types.SignedHeader{Header: types.Header{Slot: 0x2000*period + 0x1000}}}
|
||||||
|
resp.Committees[i] = new(types.SerializedSyncCommittee)
|
||||||
|
period++
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
@ -41,4 +41,6 @@ const (
|
||||||
StateIndexNextSyncCommittee = 55
|
StateIndexNextSyncCommittee = 55
|
||||||
StateIndexExecPayload = 56
|
StateIndexExecPayload = 56
|
||||||
StateIndexExecHead = 908
|
StateIndexExecHead = 908
|
||||||
|
|
||||||
|
BodyIndexExecPayload = 25
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,20 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
"github.com/ethereum/go-ethereum/beacon/params"
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/capella"
|
||||||
|
"github.com/protolambda/ztyp/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HeadInfo represents an unvalidated new head announcement.
|
||||||
|
type HeadInfo struct {
|
||||||
|
Slot uint64
|
||||||
|
BlockRoot common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
// BootstrapData contains a sync committee where light sync can be started,
|
// BootstrapData contains a sync committee where light sync can be started,
|
||||||
// together with a proof through a beacon header and corresponding state.
|
// together with a proof through a beacon header and corresponding state.
|
||||||
// Note: BootstrapData is fetched from a server based on a known checkpoint hash.
|
// Note: BootstrapData is fetched from a server based on a known checkpoint hash.
|
||||||
|
|
@ -134,3 +143,50 @@ func (u UpdateScore) BetterThan(w UpdateScore) bool {
|
||||||
}
|
}
|
||||||
return u.SignerCount > w.SignerCount
|
return u.SignerCount > w.SignerCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HeaderWithExecProof struct {
|
||||||
|
Header
|
||||||
|
PayloadHeader *capella.ExecutionPayloadHeader
|
||||||
|
PayloadBranch merkle.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HeaderWithExecProof) Validate() error {
|
||||||
|
payloadRoot := merkle.Value(h.PayloadHeader.HashTreeRoot(tree.GetHashFn()))
|
||||||
|
return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, payloadRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FinalityUpdate struct {
|
||||||
|
Attested, Finalized HeaderWithExecProof
|
||||||
|
FinalityBranch merkle.Values
|
||||||
|
// Sync committee BLS signature aggregate
|
||||||
|
Signature SyncAggregate
|
||||||
|
// Slot in which the signature has been created (newer than Header.Slot,
|
||||||
|
// determines the signing sync committee)
|
||||||
|
SignatureSlot uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *FinalityUpdate) SignedHeader() SignedHeader {
|
||||||
|
return SignedHeader{
|
||||||
|
Header: u.Attested.Header,
|
||||||
|
Signature: u.Signature,
|
||||||
|
SignatureSlot: u.SignatureSlot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *FinalityUpdate) Validate() error {
|
||||||
|
if err := u.Attested.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := u.Finalized.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return merkle.VerifyProof(u.Attested.StateRoot, params.StateIndexFinalBlock, u.FinalityBranch, merkle.Value(u.Finalized.Hash()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainHeadEvent returns an authenticated execution payload associated with the
|
||||||
|
// latest accepted head of the beacon chain, along with the hash of the latest
|
||||||
|
// finalized execution block.
|
||||||
|
type ChainHeadEvent struct {
|
||||||
|
HeadBlock *engine.ExecutableData
|
||||||
|
Finalized common.Hash
|
||||||
|
}
|
||||||
|
|
|
||||||
69
cmd/blsync/engine_api.go
Normal file
69
cmd/blsync/engine_api.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateEngineApi(client *rpc.Client, headCh chan types.ChainHeadEvent) {
|
||||||
|
for event := range headCh {
|
||||||
|
if client == nil { // dry run, no engine API specified
|
||||||
|
log.Info("New execution block retrieved", "block number", event.HeadBlock.Number, "block hash", event.HeadBlock.BlockHash, "finalized block hash", event.Finalized)
|
||||||
|
} else {
|
||||||
|
if status, err := callNewPayloadV2(client, event.HeadBlock); err == nil {
|
||||||
|
log.Info("Successful NewPayload", "block number", event.HeadBlock.Number, "block hash", event.HeadBlock.BlockHash, "status", status)
|
||||||
|
} else {
|
||||||
|
log.Error("Failed NewPayload", "block number", event.HeadBlock.Number, "block hash", event.HeadBlock.BlockHash, "error", err)
|
||||||
|
}
|
||||||
|
if status, err := callForkchoiceUpdatedV1(client, event.HeadBlock.BlockHash, event.Finalized); err == nil {
|
||||||
|
log.Info("Successful ForkchoiceUpdated", "head", event.HeadBlock.BlockHash, "status", status)
|
||||||
|
} else {
|
||||||
|
log.Error("Failed ForkchoiceUpdated", "head", event.HeadBlock.BlockHash, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func callNewPayloadV2(client *rpc.Client, execData *engine.ExecutableData) (string, error) {
|
||||||
|
var resp engine.PayloadStatusV1
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
err := client.CallContext(ctx, &resp, "engine_newPayloadV2", execData)
|
||||||
|
cancel()
|
||||||
|
return resp.Status, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func callForkchoiceUpdatedV1(client *rpc.Client, headHash, finalizedHash common.Hash) (string, error) {
|
||||||
|
var resp engine.ForkChoiceResponse
|
||||||
|
update := engine.ForkchoiceStateV1{
|
||||||
|
HeadBlockHash: headHash,
|
||||||
|
SafeBlockHash: finalizedHash,
|
||||||
|
FinalizedBlockHash: finalizedHash,
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
err := client.CallContext(ctx, &resp, "engine_forkchoiceUpdatedV1", update, nil)
|
||||||
|
cancel()
|
||||||
|
return resp.PayloadStatus.Status, err
|
||||||
|
}
|
||||||
125
cmd/blsync/main.go
Normal file
125
cmd/blsync/main.go
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/blsync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
verbosityFlag = &cli.IntFlag{
|
||||||
|
Name: "verbosity",
|
||||||
|
Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail",
|
||||||
|
Value: 3,
|
||||||
|
Category: flags.LoggingCategory,
|
||||||
|
}
|
||||||
|
vmoduleFlag = &cli.StringFlag{
|
||||||
|
Name: "vmodule",
|
||||||
|
Usage: "Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. eth/*=5,p2p=4)",
|
||||||
|
Value: "",
|
||||||
|
Hidden: true,
|
||||||
|
Category: flags.LoggingCategory,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := flags.NewApp("beacon light syncer tool")
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
utils.BeaconApiFlag,
|
||||||
|
utils.BeaconApiHeaderFlag,
|
||||||
|
utils.BeaconThresholdFlag,
|
||||||
|
utils.BeaconNoFilterFlag,
|
||||||
|
utils.BeaconConfigFlag,
|
||||||
|
utils.BeaconGenesisRootFlag,
|
||||||
|
utils.BeaconGenesisTimeFlag,
|
||||||
|
utils.BeaconCheckpointFlag,
|
||||||
|
//TODO datadir for optional permanent database
|
||||||
|
utils.MainnetFlag,
|
||||||
|
utils.SepoliaFlag,
|
||||||
|
utils.GoerliFlag,
|
||||||
|
utils.BlsyncApiFlag,
|
||||||
|
utils.BlsyncJWTSecretFlag,
|
||||||
|
verbosityFlag,
|
||||||
|
vmoduleFlag,
|
||||||
|
}
|
||||||
|
app.Action = sync
|
||||||
|
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sync(ctx *cli.Context) error {
|
||||||
|
usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
|
||||||
|
output := io.Writer(os.Stderr)
|
||||||
|
if usecolor {
|
||||||
|
output = colorable.NewColorable(os.Stderr)
|
||||||
|
}
|
||||||
|
verbosity := log.FromLegacyLevel(ctx.Int(verbosityFlag.Name))
|
||||||
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(output, verbosity, usecolor)))
|
||||||
|
|
||||||
|
headCh := make(chan types.ChainHeadEvent, 16)
|
||||||
|
client := blsync.NewClient(ctx)
|
||||||
|
sub := client.SubscribeChainHeadEvent(headCh)
|
||||||
|
go updateEngineApi(makeRPCClient(ctx), headCh)
|
||||||
|
client.Start()
|
||||||
|
// run until stopped
|
||||||
|
<-ctx.Done()
|
||||||
|
client.Stop()
|
||||||
|
sub.Unsubscribe()
|
||||||
|
close(headCh)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRPCClient(ctx *cli.Context) *rpc.Client {
|
||||||
|
if !ctx.IsSet(utils.BlsyncApiFlag.Name) {
|
||||||
|
log.Warn("No engine API target specified, performing a dry run")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !ctx.IsSet(utils.BlsyncJWTSecretFlag.Name) {
|
||||||
|
utils.Fatalf("JWT secret parameter missing") //TODO use default if datadir is specified
|
||||||
|
}
|
||||||
|
|
||||||
|
engineApiUrl, jwtFileName := ctx.String(utils.BlsyncApiFlag.Name), ctx.String(utils.BlsyncJWTSecretFlag.Name)
|
||||||
|
var jwtSecret [32]byte
|
||||||
|
if jwt, err := node.ObtainJWTSecret(jwtFileName); err == nil {
|
||||||
|
copy(jwtSecret[:], jwt)
|
||||||
|
} else {
|
||||||
|
utils.Fatalf("Error loading or generating JWT secret: %v", err)
|
||||||
|
}
|
||||||
|
auth := node.NewJWTAuth(jwtSecret)
|
||||||
|
cl, err := rpc.DialOptions(context.Background(), engineApiUrl, rpc.WithHTTPAuth(auth))
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Could not create RPC client: %v", err)
|
||||||
|
}
|
||||||
|
return cl
|
||||||
|
}
|
||||||
|
|
@ -242,7 +242,7 @@ func readInput(ctx *cli.Context) (*bbInput, error) {
|
||||||
if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector {
|
if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector {
|
||||||
decoder := json.NewDecoder(os.Stdin)
|
decoder := json.NewDecoder(os.Stdin)
|
||||||
if err := decoder.Decode(inputData); err != nil {
|
if err := decoder.Decode(inputData); err != nil {
|
||||||
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
|
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cliqueStr != stdinSelector && cliqueStr != "" {
|
if cliqueStr != stdinSelector && cliqueStr != "" {
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func Transaction(ctx *cli.Context) error {
|
||||||
if txStr == stdinSelector {
|
if txStr == stdinSelector {
|
||||||
decoder := json.NewDecoder(os.Stdin)
|
decoder := json.NewDecoder(os.Stdin)
|
||||||
if err := decoder.Decode(inputData); err != nil {
|
if err := decoder.Decode(inputData); err != nil {
|
||||||
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
|
return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err))
|
||||||
}
|
}
|
||||||
// Decode the body of already signed transactions
|
// Decode the body of already signed transactions
|
||||||
body = common.FromHex(inputData.TxRlp)
|
body = common.FromHex(inputData.TxRlp)
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ func Transition(ctx *cli.Context) error {
|
||||||
if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
|
if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
|
||||||
decoder := json.NewDecoder(os.Stdin)
|
decoder := json.NewDecoder(os.Stdin)
|
||||||
if err := decoder.Decode(inputData); err != nil {
|
if err := decoder.Decode(inputData); err != nil {
|
||||||
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
|
return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allocStr != stdinSelector {
|
if allocStr != stdinSelector {
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ func loadTransactions(txStr string, inputData *input, env stEnv, chainConfig *pa
|
||||||
return newRlpTxIterator(body), nil
|
return newRlpTxIterator(body), nil
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &txsWithKeys); err != nil {
|
if err := json.Unmarshal(data, &txsWithKeys); err != nil {
|
||||||
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err))
|
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshalling txs-file: %v", err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(inputData.TxRlp) > 0 {
|
if len(inputData.TxRlp) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func readFile(path, desc string, dest interface{}) error {
|
||||||
defer inFile.Close()
|
defer inFile.Close()
|
||||||
decoder := json.NewDecoder(inFile)
|
decoder := json.NewDecoder(inFile)
|
||||||
if err := decoder.Decode(dest); err != nil {
|
if err := decoder.Decode(dest); err != nil {
|
||||||
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling %s file: %v", desc, err))
|
return NewError(ErrorJson, fmt.Errorf("failed unmarshalling %s file: %v", desc, err))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
||||||
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/blsync"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
|
@ -221,6 +222,8 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
|
||||||
}
|
}
|
||||||
catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon)
|
catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon)
|
||||||
stack.RegisterLifecycle(simBeacon)
|
stack.RegisterLifecycle(simBeacon)
|
||||||
|
} else if ctx.IsSet(utils.BeaconApiFlag.Name) {
|
||||||
|
stack.RegisterLifecycle(catalyst.NewBlsync(blsync.NewClient(ctx), eth))
|
||||||
} else {
|
} else {
|
||||||
err := catalyst.Register(stack, eth)
|
err := catalyst.Register(stack, eth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,6 @@ func TestConsoleWelcome(t *testing.T) {
|
||||||
Welcome to the Geth JavaScript console!
|
Welcome to the Geth JavaScript console!
|
||||||
|
|
||||||
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
||||||
coinbase: {{.Etherbase}}
|
|
||||||
at block: 0 ({{niltime}})
|
at block: 0 ({{niltime}})
|
||||||
datadir: {{.Datadir}}
|
datadir: {{.Datadir}}
|
||||||
modules: {{apis}}
|
modules: {{apis}}
|
||||||
|
|
@ -131,7 +130,6 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||||
attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
attach.SetTemplateFunc("gover", runtime.Version)
|
attach.SetTemplateFunc("gover", runtime.Version)
|
||||||
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
||||||
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
|
||||||
attach.SetTemplateFunc("niltime", func() string {
|
attach.SetTemplateFunc("niltime", func() string {
|
||||||
return time.Unix(1548854791, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
return time.Unix(1548854791, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
||||||
})
|
})
|
||||||
|
|
@ -144,7 +142,6 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||||
Welcome to the Geth JavaScript console!
|
Welcome to the Geth JavaScript console!
|
||||||
|
|
||||||
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
||||||
coinbase: {{etherbase}}
|
|
||||||
at block: 0 ({{niltime}}){{if ipc}}
|
at block: 0 ({{niltime}}){{if ipc}}
|
||||||
datadir: {{datadir}}{{end}}
|
datadir: {{datadir}}{{end}}
|
||||||
modules: {{apis}}
|
modules: {{apis}}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/console/prompt"
|
"github.com/ethereum/go-ethereum/console/prompt"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
|
|
@ -116,13 +115,14 @@ var (
|
||||||
utils.DiscoveryPortFlag,
|
utils.DiscoveryPortFlag,
|
||||||
utils.MaxPeersFlag,
|
utils.MaxPeersFlag,
|
||||||
utils.MaxPendingPeersFlag,
|
utils.MaxPendingPeersFlag,
|
||||||
utils.MiningEnabledFlag,
|
utils.MiningEnabledFlag, // deprecated
|
||||||
utils.MinerGasLimitFlag,
|
utils.MinerGasLimitFlag,
|
||||||
utils.MinerGasPriceFlag,
|
utils.MinerGasPriceFlag,
|
||||||
utils.MinerEtherbaseFlag,
|
utils.MinerEtherbaseFlag, // deprecated
|
||||||
utils.MinerExtraDataFlag,
|
utils.MinerExtraDataFlag,
|
||||||
utils.MinerRecommitIntervalFlag,
|
utils.MinerRecommitIntervalFlag,
|
||||||
utils.MinerNewPayloadTimeout,
|
utils.MinerPendingFeeRecipientFlag,
|
||||||
|
utils.MinerNewPayloadTimeoutFlag, // deprecated
|
||||||
utils.NATFlag,
|
utils.NATFlag,
|
||||||
utils.NoDiscoverFlag,
|
utils.NoDiscoverFlag,
|
||||||
utils.DiscoveryV4Flag,
|
utils.DiscoveryV4Flag,
|
||||||
|
|
@ -146,6 +146,14 @@ var (
|
||||||
configFileFlag,
|
configFileFlag,
|
||||||
utils.LogDebugFlag,
|
utils.LogDebugFlag,
|
||||||
utils.LogBacktraceAtFlag,
|
utils.LogBacktraceAtFlag,
|
||||||
|
utils.BeaconApiFlag,
|
||||||
|
utils.BeaconApiHeaderFlag,
|
||||||
|
utils.BeaconThresholdFlag,
|
||||||
|
utils.BeaconNoFilterFlag,
|
||||||
|
utils.BeaconConfigFlag,
|
||||||
|
utils.BeaconGenesisRootFlag,
|
||||||
|
utils.BeaconGenesisTimeFlag,
|
||||||
|
utils.BeaconCheckpointFlag,
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
rpcFlags = []cli.Flag{
|
||||||
|
|
@ -421,24 +429,6 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start auxiliary services if enabled
|
|
||||||
if ctx.Bool(utils.MiningEnabledFlag.Name) {
|
|
||||||
// Mining only makes sense if a full Ethereum node is running
|
|
||||||
if ctx.String(utils.SyncModeFlag.Name) == "light" {
|
|
||||||
utils.Fatalf("Light clients do not support mining")
|
|
||||||
}
|
|
||||||
ethBackend, ok := backend.(*eth.EthAPIBackend)
|
|
||||||
if !ok {
|
|
||||||
utils.Fatalf("Ethereum service not running")
|
|
||||||
}
|
|
||||||
// Set the gas price to the limits from the CLI and start mining
|
|
||||||
gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
|
|
||||||
ethBackend.TxPool().SetGasTip(gasprice)
|
|
||||||
if err := ethBackend.StartMining(); err != nil {
|
|
||||||
utils.Fatalf("Failed to start mining: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlockAccounts unlocks any account specifically requested.
|
// unlockAccounts unlocks any account specifically requested.
|
||||||
|
|
|
||||||
1
cmd/geth/testdata/clique.json
vendored
1
cmd/geth/testdata/clique.json
vendored
|
|
@ -8,6 +8,7 @@
|
||||||
"byzantiumBlock": 0,
|
"byzantiumBlock": 0,
|
||||||
"constantinopleBlock": 0,
|
"constantinopleBlock": 0,
|
||||||
"petersburgBlock": 0,
|
"petersburgBlock": 0,
|
||||||
|
"terminalTotalDifficultyPassed": true,
|
||||||
"clique": {
|
"clique": {
|
||||||
"period": 5,
|
"period": 5,
|
||||||
"epoch": 30000
|
"epoch": 30000
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
bparams "github.com/ethereum/go-ethereum/beacon/params"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -281,6 +282,58 @@ var (
|
||||||
Value: ethconfig.Defaults.TransactionHistory,
|
Value: ethconfig.Defaults.TransactionHistory,
|
||||||
Category: flags.StateCategory,
|
Category: flags.StateCategory,
|
||||||
}
|
}
|
||||||
|
// Beacon client light sync settings
|
||||||
|
BeaconApiFlag = &cli.StringSliceFlag{
|
||||||
|
Name: "beacon.api",
|
||||||
|
Usage: "Beacon node (CL) light client API URL. This flag can be given multiple times.",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BeaconApiHeaderFlag = &cli.StringSliceFlag{
|
||||||
|
Name: "beacon.api.header",
|
||||||
|
Usage: "Pass custom HTTP header fields to the emote beacon node API in \"key:value\" format. This flag can be given multiple times.",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BeaconThresholdFlag = &cli.IntFlag{
|
||||||
|
Name: "beacon.threshold",
|
||||||
|
Usage: "Beacon sync committee participation threshold",
|
||||||
|
Value: bparams.SyncCommitteeSupermajority,
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BeaconNoFilterFlag = &cli.BoolFlag{
|
||||||
|
Name: "beacon.nofilter",
|
||||||
|
Usage: "Disable future slot signature filter",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BeaconConfigFlag = &cli.StringFlag{
|
||||||
|
Name: "beacon.config",
|
||||||
|
Usage: "Beacon chain config YAML file",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BeaconGenesisRootFlag = &cli.StringFlag{
|
||||||
|
Name: "beacon.genesis.gvroot",
|
||||||
|
Usage: "Beacon chain genesis validators root",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BeaconGenesisTimeFlag = &cli.Uint64Flag{
|
||||||
|
Name: "beacon.genesis.time",
|
||||||
|
Usage: "Beacon chain genesis time",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BeaconCheckpointFlag = &cli.StringFlag{
|
||||||
|
Name: "beacon.checkpoint",
|
||||||
|
Usage: "Beacon chain weak subjectivity checkpoint block hash",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BlsyncApiFlag = &cli.StringFlag{
|
||||||
|
Name: "blsync.engine.api",
|
||||||
|
Usage: "Target EL engine API URL",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
|
BlsyncJWTSecretFlag = &cli.StringFlag{
|
||||||
|
Name: "blsync.jwtsecret",
|
||||||
|
Usage: "Path to a JWT secret to use for target engine API endpoint",
|
||||||
|
Category: flags.BeaconCategory,
|
||||||
|
}
|
||||||
// Transaction pool settings
|
// Transaction pool settings
|
||||||
TxPoolLocalsFlag = &cli.StringFlag{
|
TxPoolLocalsFlag = &cli.StringFlag{
|
||||||
Name: "txpool.locals",
|
Name: "txpool.locals",
|
||||||
|
|
@ -425,11 +478,6 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Miner settings
|
// Miner settings
|
||||||
MiningEnabledFlag = &cli.BoolFlag{
|
|
||||||
Name: "mine",
|
|
||||||
Usage: "Enable mining",
|
|
||||||
Category: flags.MinerCategory,
|
|
||||||
}
|
|
||||||
MinerGasLimitFlag = &cli.Uint64Flag{
|
MinerGasLimitFlag = &cli.Uint64Flag{
|
||||||
Name: "miner.gaslimit",
|
Name: "miner.gaslimit",
|
||||||
Usage: "Target gas ceiling for mined blocks",
|
Usage: "Target gas ceiling for mined blocks",
|
||||||
|
|
@ -442,11 +490,6 @@ var (
|
||||||
Value: ethconfig.Defaults.Miner.GasPrice,
|
Value: ethconfig.Defaults.Miner.GasPrice,
|
||||||
Category: flags.MinerCategory,
|
Category: flags.MinerCategory,
|
||||||
}
|
}
|
||||||
MinerEtherbaseFlag = &cli.StringFlag{
|
|
||||||
Name: "miner.etherbase",
|
|
||||||
Usage: "0x prefixed public address for block mining rewards",
|
|
||||||
Category: flags.MinerCategory,
|
|
||||||
}
|
|
||||||
MinerExtraDataFlag = &cli.StringFlag{
|
MinerExtraDataFlag = &cli.StringFlag{
|
||||||
Name: "miner.extradata",
|
Name: "miner.extradata",
|
||||||
Usage: "Block extra data set by the miner (default = client version)",
|
Usage: "Block extra data set by the miner (default = client version)",
|
||||||
|
|
@ -458,10 +501,9 @@ var (
|
||||||
Value: ethconfig.Defaults.Miner.Recommit,
|
Value: ethconfig.Defaults.Miner.Recommit,
|
||||||
Category: flags.MinerCategory,
|
Category: flags.MinerCategory,
|
||||||
}
|
}
|
||||||
MinerNewPayloadTimeout = &cli.DurationFlag{
|
MinerPendingFeeRecipientFlag = &cli.StringFlag{
|
||||||
Name: "miner.newpayload-timeout",
|
Name: "miner.pending.feeRecipient",
|
||||||
Usage: "Specify the maximum time allowance for creating a new payload",
|
Usage: "0x prefixed public address for the pending block producer (not used for actual block production)",
|
||||||
Value: ethconfig.Defaults.Miner.NewPayloadTimeout,
|
|
||||||
Category: flags.MinerCategory,
|
Category: flags.MinerCategory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1268,19 +1310,23 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
|
||||||
|
|
||||||
// setEtherbase retrieves the etherbase from the directly specified command line flags.
|
// setEtherbase retrieves the etherbase from the directly specified command line flags.
|
||||||
func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) {
|
func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) {
|
||||||
if !ctx.IsSet(MinerEtherbaseFlag.Name) {
|
if ctx.IsSet(MinerEtherbaseFlag.Name) {
|
||||||
|
log.Warn("Option --miner.etherbase is deprecated as the etherbase is set by the consensus client post-merge")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
addr := ctx.String(MinerEtherbaseFlag.Name)
|
if !ctx.IsSet(MinerPendingFeeRecipientFlag.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr := ctx.String(MinerPendingFeeRecipientFlag.Name)
|
||||||
if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") {
|
if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") {
|
||||||
addr = addr[2:]
|
addr = addr[2:]
|
||||||
}
|
}
|
||||||
b, err := hex.DecodeString(addr)
|
b, err := hex.DecodeString(addr)
|
||||||
if err != nil || len(b) != common.AddressLength {
|
if err != nil || len(b) != common.AddressLength {
|
||||||
Fatalf("-%s: invalid etherbase address %q", MinerEtherbaseFlag.Name, addr)
|
Fatalf("-%s: invalid pending block producer address %q", MinerPendingFeeRecipientFlag.Name, addr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.Miner.Etherbase = common.BytesToAddress(b)
|
cfg.Miner.PendingFeeRecipient = common.BytesToAddress(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakePasswordList reads password lines from the file specified by the global --password flag.
|
// MakePasswordList reads password lines from the file specified by the global --password flag.
|
||||||
|
|
@ -1496,6 +1542,9 @@ func setTxPool(ctx *cli.Context, cfg *legacypool.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMiner(ctx *cli.Context, cfg *miner.Config) {
|
func setMiner(ctx *cli.Context, cfg *miner.Config) {
|
||||||
|
if ctx.Bool(MiningEnabledFlag.Name) {
|
||||||
|
log.Warn("The flag --mine is deprecated and will be removed")
|
||||||
|
}
|
||||||
if ctx.IsSet(MinerExtraDataFlag.Name) {
|
if ctx.IsSet(MinerExtraDataFlag.Name) {
|
||||||
cfg.ExtraData = []byte(ctx.String(MinerExtraDataFlag.Name))
|
cfg.ExtraData = []byte(ctx.String(MinerExtraDataFlag.Name))
|
||||||
}
|
}
|
||||||
|
|
@ -1508,8 +1557,9 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
|
||||||
if ctx.IsSet(MinerRecommitIntervalFlag.Name) {
|
if ctx.IsSet(MinerRecommitIntervalFlag.Name) {
|
||||||
cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name)
|
cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name)
|
||||||
}
|
}
|
||||||
if ctx.IsSet(MinerNewPayloadTimeout.Name) {
|
if ctx.IsSet(MinerNewPayloadTimeoutFlag.Name) {
|
||||||
cfg.NewPayloadTimeout = ctx.Duration(MinerNewPayloadTimeout.Name)
|
log.Warn("The flag --miner.newpayload-timeout is deprecated and will be removed, please use --miner.recommit")
|
||||||
|
cfg.Recommit = ctx.Duration(MinerNewPayloadTimeoutFlag.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1786,8 +1836,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
|
|
||||||
// Figure out the dev account address.
|
// Figure out the dev account address.
|
||||||
// setEtherbase has been called above, configuring the miner address from command line flags.
|
// setEtherbase has been called above, configuring the miner address from command line flags.
|
||||||
if cfg.Miner.Etherbase != (common.Address{}) {
|
if cfg.Miner.PendingFeeRecipient != (common.Address{}) {
|
||||||
developer = accounts.Account{Address: cfg.Miner.Etherbase}
|
developer = accounts.Account{Address: cfg.Miner.PendingFeeRecipient}
|
||||||
} else if accs := ks.Accounts(); len(accs) > 0 {
|
} else if accs := ks.Accounts(); len(accs) > 0 {
|
||||||
developer = ks.Accounts()[0]
|
developer = ks.Accounts()[0]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1798,7 +1848,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
}
|
}
|
||||||
// Make sure the address is configured as fee recipient, otherwise
|
// Make sure the address is configured as fee recipient, otherwise
|
||||||
// the miner will fail to start.
|
// the miner will fail to start.
|
||||||
cfg.Miner.Etherbase = developer.Address
|
cfg.Miner.PendingFeeRecipient = developer.Address
|
||||||
|
|
||||||
if err := ks.Unlock(developer, passphrase); err != nil {
|
if err := ks.Unlock(developer, passphrase); err != nil {
|
||||||
Fatalf("Failed to unlock developer account: %v", err)
|
Fatalf("Failed to unlock developer account: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ var DeprecatedFlags = []cli.Flag{
|
||||||
LightNoSyncServeFlag,
|
LightNoSyncServeFlag,
|
||||||
LogBacktraceAtFlag,
|
LogBacktraceAtFlag,
|
||||||
LogDebugFlag,
|
LogDebugFlag,
|
||||||
|
MinerNewPayloadTimeoutFlag,
|
||||||
|
MinerEtherbaseFlag,
|
||||||
|
MiningEnabledFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -132,6 +135,23 @@ var (
|
||||||
Usage: "Prepends log messages with call-site location (deprecated)",
|
Usage: "Prepends log messages with call-site location (deprecated)",
|
||||||
Category: flags.DeprecatedCategory,
|
Category: flags.DeprecatedCategory,
|
||||||
}
|
}
|
||||||
|
// Deprecated February 2024
|
||||||
|
MinerNewPayloadTimeoutFlag = &cli.DurationFlag{
|
||||||
|
Name: "miner.newpayload-timeout",
|
||||||
|
Usage: "Specify the maximum time allowance for creating a new payload",
|
||||||
|
Value: ethconfig.Defaults.Miner.Recommit,
|
||||||
|
Category: flags.MinerCategory,
|
||||||
|
}
|
||||||
|
MinerEtherbaseFlag = &cli.StringFlag{
|
||||||
|
Name: "miner.etherbase",
|
||||||
|
Usage: "0x prefixed public address for block mining rewards",
|
||||||
|
Category: flags.MinerCategory,
|
||||||
|
}
|
||||||
|
MiningEnabledFlag = &cli.BoolFlag{
|
||||||
|
Name: "mine",
|
||||||
|
Usage: "Enable mining",
|
||||||
|
Category: flags.MinerCategory,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
|
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
|
||||||
|
|
|
||||||
|
|
@ -119,11 +119,3 @@ type Engine interface {
|
||||||
// Close terminates any background threads maintained by the consensus engine.
|
// Close terminates any background threads maintained by the consensus engine.
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PoW is a consensus engine based on proof-of-work.
|
|
||||||
type PoW interface {
|
|
||||||
Engine
|
|
||||||
|
|
||||||
// Hashrate returns the current mining hashrate of a PoW consensus engine.
|
|
||||||
Hashrate() float64
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
// Copyright 2021 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// 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 consensus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// transitionStatus describes the status of eth1/2 transition. This switch
|
|
||||||
// between modes is a one-way action which is triggered by corresponding
|
|
||||||
// consensus-layer message.
|
|
||||||
type transitionStatus struct {
|
|
||||||
LeftPoW bool // The flag is set when the first NewHead message received
|
|
||||||
EnteredPoS bool // The flag is set when the first FinalisedBlock message received
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merger is an internal help structure used to track the eth1/2 transition status.
|
|
||||||
// It's a common structure can be used in both full node and light client.
|
|
||||||
type Merger struct {
|
|
||||||
db ethdb.KeyValueStore
|
|
||||||
status transitionStatus
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMerger creates a new Merger which stores its transition status in the provided db.
|
|
||||||
func NewMerger(db ethdb.KeyValueStore) *Merger {
|
|
||||||
var status transitionStatus
|
|
||||||
blob := rawdb.ReadTransitionStatus(db)
|
|
||||||
if len(blob) != 0 {
|
|
||||||
if err := rlp.DecodeBytes(blob, &status); err != nil {
|
|
||||||
log.Crit("Failed to decode the transition status", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Merger{
|
|
||||||
db: db,
|
|
||||||
status: status,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReachTTD is called whenever the first NewHead message received
|
|
||||||
// from the consensus-layer.
|
|
||||||
func (m *Merger) ReachTTD() {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.status.LeftPoW {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.status = transitionStatus{LeftPoW: true}
|
|
||||||
blob, err := rlp.EncodeToBytes(m.status)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed to encode the transition status: %v", err))
|
|
||||||
}
|
|
||||||
rawdb.WriteTransitionStatus(m.db, blob)
|
|
||||||
log.Info("Left PoW stage")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FinalizePoS is called whenever the first FinalisedBlock message received
|
|
||||||
// from the consensus-layer.
|
|
||||||
func (m *Merger) FinalizePoS() {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.status.EnteredPoS {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.status = transitionStatus{LeftPoW: true, EnteredPoS: true}
|
|
||||||
blob, err := rlp.EncodeToBytes(m.status)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed to encode the transition status: %v", err))
|
|
||||||
}
|
|
||||||
rawdb.WriteTransitionStatus(m.db, blob)
|
|
||||||
log.Info("Entered PoS stage")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TDDReached reports whether the chain has left the PoW stage.
|
|
||||||
func (m *Merger) TDDReached() bool {
|
|
||||||
m.mu.RLock()
|
|
||||||
defer m.mu.RUnlock()
|
|
||||||
|
|
||||||
return m.status.LeftPoW
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoSFinalized reports whether the chain has entered the PoS stage.
|
|
||||||
func (m *Merger) PoSFinalized() bool {
|
|
||||||
m.mu.RLock()
|
|
||||||
defer m.mu.RUnlock()
|
|
||||||
|
|
||||||
return m.status.EnteredPoS
|
|
||||||
}
|
|
||||||
|
|
@ -325,9 +325,6 @@ func (c *Console) Welcome() {
|
||||||
// Print some generic Geth metadata
|
// Print some generic Geth metadata
|
||||||
if res, err := c.jsre.Run(`
|
if res, err := c.jsre.Run(`
|
||||||
var message = "instance: " + web3.version.node + "\n";
|
var message = "instance: " + web3.version.node + "\n";
|
||||||
try {
|
|
||||||
message += "coinbase: " + eth.coinbase + "\n";
|
|
||||||
} catch (err) {}
|
|
||||||
message += "at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")\n";
|
message += "at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")\n";
|
||||||
try {
|
try {
|
||||||
message += " datadir: " + admin.datadir + "\n";
|
message += " datadir: " + admin.datadir + "\n";
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
|
||||||
ethConf := ðconfig.Config{
|
ethConf := ðconfig.Config{
|
||||||
Genesis: core.DeveloperGenesisBlock(11_500_000, nil),
|
Genesis: core.DeveloperGenesisBlock(11_500_000, nil),
|
||||||
Miner: miner.Config{
|
Miner: miner.Config{
|
||||||
Etherbase: common.HexToAddress(testAddress),
|
PendingFeeRecipient: common.HexToAddress(testAddress),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if confOverride != nil {
|
if confOverride != nil {
|
||||||
|
|
@ -167,9 +167,6 @@ func TestWelcome(t *testing.T) {
|
||||||
if want := fmt.Sprintf("instance: %s", testInstance); !strings.Contains(output, want) {
|
if want := fmt.Sprintf("instance: %s", testInstance); !strings.Contains(output, want) {
|
||||||
t.Fatalf("console output missing instance: have\n%s\nwant also %s", output, want)
|
t.Fatalf("console output missing instance: have\n%s\nwant also %s", output, want)
|
||||||
}
|
}
|
||||||
if want := fmt.Sprintf("coinbase: %s", testAddress); !strings.Contains(output, want) {
|
|
||||||
t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want)
|
|
||||||
}
|
|
||||||
if want := "at block: 0"; !strings.Contains(output, want) {
|
if want := "at block: 0"; !strings.Contains(output, want) {
|
||||||
t.Fatalf("console output missing sync status: have\n%s\nwant also %s", output, want)
|
t.Fatalf("console output missing sync status: have\n%s\nwant also %s", output, want)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,6 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||||
preBlocks []*types.Block
|
preBlocks []*types.Block
|
||||||
postBlocks []*types.Block
|
postBlocks []*types.Block
|
||||||
engine consensus.Engine
|
engine consensus.Engine
|
||||||
merger = consensus.NewMerger(rawdb.NewMemoryDatabase())
|
|
||||||
)
|
)
|
||||||
if isClique {
|
if isClique {
|
||||||
var (
|
var (
|
||||||
|
|
@ -186,11 +185,6 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||||
}
|
}
|
||||||
chain.InsertChain(preBlocks[i : i+1])
|
chain.InsertChain(preBlocks[i : i+1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the transition
|
|
||||||
merger.ReachTTD()
|
|
||||||
merger.FinalizePoS()
|
|
||||||
|
|
||||||
// Verify the blocks after the merging
|
// Verify the blocks after the merging
|
||||||
for i := 0; i < len(postBlocks); i++ {
|
for i := 0; i < len(postBlocks); i++ {
|
||||||
_, results := engine.VerifyHeaders(chain, []*types.Header{postHeaders[i]})
|
_, results := engine.VerifyHeaders(chain, []*types.Header{postHeaders[i]})
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/hashdb"
|
"github.com/ethereum/go-ethereum/triedb/hashdb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -97,13 +96,11 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bodyCacheLimit = 256
|
bodyCacheLimit = 256
|
||||||
blockCacheLimit = 256
|
blockCacheLimit = 256
|
||||||
receiptsCacheLimit = 32
|
receiptsCacheLimit = 32
|
||||||
txLookupCacheLimit = 1024
|
txLookupCacheLimit = 1024
|
||||||
maxFutureBlocks = 256
|
TriesInMemory = 128
|
||||||
maxTimeFutureBlocks = 30
|
|
||||||
TriesInMemory = 128
|
|
||||||
|
|
||||||
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
|
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
|
||||||
//
|
//
|
||||||
|
|
@ -245,9 +242,6 @@ type BlockChain struct {
|
||||||
blockCache *lru.Cache[common.Hash, *types.Block]
|
blockCache *lru.Cache[common.Hash, *types.Block]
|
||||||
txLookupCache *lru.Cache[common.Hash, txLookup]
|
txLookupCache *lru.Cache[common.Hash, txLookup]
|
||||||
|
|
||||||
// future blocks are blocks added for later processing
|
|
||||||
futureBlocks *lru.Cache[common.Hash, *types.Block]
|
|
||||||
|
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
quit chan struct{} // shutdown signal, closed in Stop.
|
quit chan struct{} // shutdown signal, closed in Stop.
|
||||||
stopping atomic.Bool // false if chain is running, true when stopped
|
stopping atomic.Bool // false if chain is running, true when stopped
|
||||||
|
|
@ -299,7 +293,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
||||||
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
|
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
|
||||||
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
|
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
|
||||||
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
|
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
|
||||||
futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
|
|
||||||
engine: engine,
|
engine: engine,
|
||||||
vmConfig: vmConfig,
|
vmConfig: vmConfig,
|
||||||
}
|
}
|
||||||
|
|
@ -449,11 +442,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
||||||
}
|
}
|
||||||
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
|
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start future block processor.
|
|
||||||
bc.wg.Add(1)
|
|
||||||
go bc.updateFutureBlocks()
|
|
||||||
|
|
||||||
// Rewind the chain in case of an incompatible config upgrade.
|
// Rewind the chain in case of an incompatible config upgrade.
|
||||||
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
||||||
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
|
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
|
||||||
|
|
@ -794,7 +782,6 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
||||||
bc.receiptsCache.Purge()
|
bc.receiptsCache.Purge()
|
||||||
bc.blockCache.Purge()
|
bc.blockCache.Purge()
|
||||||
bc.txLookupCache.Purge()
|
bc.txLookupCache.Purge()
|
||||||
bc.futureBlocks.Purge()
|
|
||||||
|
|
||||||
// Clear safe block, finalized block if needed
|
// Clear safe block, finalized block if needed
|
||||||
if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() {
|
if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() {
|
||||||
|
|
@ -1048,24 +1035,6 @@ func (bc *BlockChain) insertStopped() bool {
|
||||||
return bc.procInterrupt.Load()
|
return bc.procInterrupt.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *BlockChain) procFutureBlocks() {
|
|
||||||
blocks := make([]*types.Block, 0, bc.futureBlocks.Len())
|
|
||||||
for _, hash := range bc.futureBlocks.Keys() {
|
|
||||||
if block, exist := bc.futureBlocks.Peek(hash); exist {
|
|
||||||
blocks = append(blocks, block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(blocks) > 0 {
|
|
||||||
slices.SortFunc(blocks, func(a, b *types.Block) int {
|
|
||||||
return a.Number().Cmp(b.Number())
|
|
||||||
})
|
|
||||||
// Insert one by one as chain insertion needs contiguous ancestry between blocks
|
|
||||||
for i := range blocks {
|
|
||||||
bc.InsertChain(blocks[i : i+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteStatus status of write
|
// WriteStatus status of write
|
||||||
type WriteStatus byte
|
type WriteStatus byte
|
||||||
|
|
||||||
|
|
@ -1466,8 +1435,6 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
|
||||||
if status == CanonStatTy {
|
if status == CanonStatTy {
|
||||||
bc.writeHeadBlock(block)
|
bc.writeHeadBlock(block)
|
||||||
}
|
}
|
||||||
bc.futureBlocks.Remove(block.Hash())
|
|
||||||
|
|
||||||
if status == CanonStatTy {
|
if status == CanonStatTy {
|
||||||
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
|
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
|
||||||
if len(logs) > 0 {
|
if len(logs) > 0 {
|
||||||
|
|
@ -1487,25 +1454,6 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
|
||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFutureBlock checks if the block is within the max allowed window to get
|
|
||||||
// accepted for future processing, and returns an error if the block is too far
|
|
||||||
// ahead and was not added.
|
|
||||||
//
|
|
||||||
// TODO after the transition, the future block shouldn't be kept. Because
|
|
||||||
// it's not checked in the Geth side anymore.
|
|
||||||
func (bc *BlockChain) addFutureBlock(block *types.Block) error {
|
|
||||||
max := uint64(time.Now().Unix() + maxTimeFutureBlocks)
|
|
||||||
if block.Time() > max {
|
|
||||||
return fmt.Errorf("future block timestamp %v > allowed %v", block.Time(), max)
|
|
||||||
}
|
|
||||||
if block.Difficulty().Cmp(common.Big0) == 0 {
|
|
||||||
// Never add PoS blocks into the future queue
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
bc.futureBlocks.Add(block.Hash(), block)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertChain attempts to insert the given batch of blocks in to the canonical
|
// InsertChain attempts to insert the given batch of blocks in to the canonical
|
||||||
// chain or, otherwise, create a fork. If an error is returned it will return
|
// chain or, otherwise, create a fork. If an error is returned it will return
|
||||||
// the index number of the failing block as well an error describing what went
|
// the index number of the failing block as well an error describing what went
|
||||||
|
|
@ -1643,26 +1591,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
_, err := bc.recoverAncestors(block)
|
_, err := bc.recoverAncestors(block)
|
||||||
return it.index, err
|
return it.index, err
|
||||||
}
|
}
|
||||||
// First block is future, shove it (and all children) to the future queue (unknown ancestor)
|
|
||||||
case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())):
|
|
||||||
for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) {
|
|
||||||
log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash())
|
|
||||||
if err := bc.addFutureBlock(block); err != nil {
|
|
||||||
return it.index, err
|
|
||||||
}
|
|
||||||
block, err = it.next()
|
|
||||||
}
|
|
||||||
stats.queued += it.processed()
|
|
||||||
stats.ignored += it.remaining()
|
|
||||||
|
|
||||||
// If there are any still remaining, mark as ignored
|
|
||||||
return it.index, err
|
|
||||||
|
|
||||||
// Some other error(except ErrKnownBlock) occurred, abort.
|
// Some other error(except ErrKnownBlock) occurred, abort.
|
||||||
// ErrKnownBlock is allowed here since some known blocks
|
// ErrKnownBlock is allowed here since some known blocks
|
||||||
// still need re-execution to generate snapshots that are missing
|
// still need re-execution to generate snapshots that are missing
|
||||||
case err != nil && !errors.Is(err, ErrKnownBlock):
|
case err != nil && !errors.Is(err, ErrKnownBlock):
|
||||||
bc.futureBlocks.Remove(block.Hash())
|
|
||||||
stats.ignored += len(it.chain)
|
stats.ignored += len(it.chain)
|
||||||
bc.reportBlock(block, nil, err)
|
bc.reportBlock(block, nil, err)
|
||||||
return it.index, err
|
return it.index, err
|
||||||
|
|
@ -1867,23 +1799,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
"root", block.Root())
|
"root", block.Root())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any blocks remaining here? The only ones we care about are the future ones
|
|
||||||
if block != nil && errors.Is(err, consensus.ErrFutureBlock) {
|
|
||||||
if err := bc.addFutureBlock(block); err != nil {
|
|
||||||
return it.index, err
|
|
||||||
}
|
|
||||||
block, err = it.next()
|
|
||||||
|
|
||||||
for ; block != nil && errors.Is(err, consensus.ErrUnknownAncestor); block, err = it.next() {
|
|
||||||
if err := bc.addFutureBlock(block); err != nil {
|
|
||||||
return it.index, err
|
|
||||||
}
|
|
||||||
stats.queued++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stats.ignored += it.remaining()
|
stats.ignored += it.remaining()
|
||||||
|
|
||||||
return it.index, err
|
return it.index, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2334,20 +2250,6 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
|
||||||
return head.Hash(), nil
|
return head.Hash(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *BlockChain) updateFutureBlocks() {
|
|
||||||
futureTimer := time.NewTicker(5 * time.Second)
|
|
||||||
defer futureTimer.Stop()
|
|
||||||
defer bc.wg.Done()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-futureTimer.C:
|
|
||||||
bc.procFutureBlocks()
|
|
||||||
case <-bc.quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipBlock returns 'true', if the block being imported can be skipped over, meaning
|
// skipBlock returns 'true', if the block being imported can be skipped over, meaning
|
||||||
// that the block does not need to be processed but can be considered already fully 'done'.
|
// that the block does not need to be processed but can be considered already fully 'done'.
|
||||||
func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
|
func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
|
||||||
|
|
|
||||||
|
|
@ -179,8 +179,3 @@ func (it *insertIterator) first() *types.Block {
|
||||||
func (it *insertIterator) remaining() int {
|
func (it *insertIterator) remaining() int {
|
||||||
return len(it.chain) - it.index
|
return len(it.chain) - it.index
|
||||||
}
|
}
|
||||||
|
|
||||||
// processed returns the number of processed blocks.
|
|
||||||
func (it *insertIterator) processed() int {
|
|
||||||
return it.index + 1
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2129,7 +2129,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
|
||||||
// Generate a canonical chain to act as the main dataset
|
// Generate a canonical chain to act as the main dataset
|
||||||
chainConfig := *params.TestChainConfig
|
chainConfig := *params.TestChainConfig
|
||||||
var (
|
var (
|
||||||
merger = consensus.NewMerger(rawdb.NewMemoryDatabase())
|
|
||||||
engine = beacon.New(ethash.NewFaker())
|
engine = beacon.New(ethash.NewFaker())
|
||||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
@ -2153,8 +2152,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
|
||||||
// Activate the transition since genesis if required
|
// Activate the transition since genesis if required
|
||||||
if mergePoint == 0 {
|
if mergePoint == 0 {
|
||||||
mergeBlock = 0
|
mergeBlock = 0
|
||||||
merger.ReachTTD()
|
|
||||||
merger.FinalizePoS()
|
|
||||||
|
|
||||||
// Set the terminal total difficulty in the config
|
// Set the terminal total difficulty in the config
|
||||||
gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
|
gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
|
||||||
|
|
@ -2189,8 +2186,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
|
||||||
|
|
||||||
// Activate the transition in the middle of the chain
|
// Activate the transition in the middle of the chain
|
||||||
if mergePoint == 1 {
|
if mergePoint == 1 {
|
||||||
merger.ReachTTD()
|
|
||||||
merger.FinalizePoS()
|
|
||||||
// Set the terminal total difficulty in the config
|
// Set the terminal total difficulty in the config
|
||||||
ttd := big.NewInt(int64(len(blocks)))
|
ttd := big.NewInt(int64(len(blocks)))
|
||||||
ttd.Mul(ttd, params.GenesisDifficulty)
|
ttd.Mul(ttd, params.GenesisDifficulty)
|
||||||
|
|
|
||||||
|
|
@ -33,5 +33,4 @@ var (
|
||||||
slotDeletionTimer = metrics.NewRegisteredResettingTimer("state/delete/storage/timer", nil)
|
slotDeletionTimer = metrics.NewRegisteredResettingTimer("state/delete/storage/timer", nil)
|
||||||
slotDeletionCount = metrics.NewRegisteredMeter("state/delete/storage/slot", nil)
|
slotDeletionCount = metrics.NewRegisteredMeter("state/delete/storage/slot", nil)
|
||||||
slotDeletionSize = metrics.NewRegisteredMeter("state/delete/storage/size", nil)
|
slotDeletionSize = metrics.NewRegisteredMeter("state/delete/storage/size", nil)
|
||||||
slotDeletionSkip = metrics.NewRegisteredGauge("state/delete/storage/skip", nil)
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,6 @@ import (
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// storageDeleteLimit denotes the highest permissible memory allocation
|
|
||||||
// employed for contract storage deletion.
|
|
||||||
storageDeleteLimit = 512 * 1024 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
type revision struct {
|
type revision struct {
|
||||||
id int
|
id int
|
||||||
journalIndex int
|
journalIndex int
|
||||||
|
|
@ -949,10 +943,10 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||||
// of a specific account. It leverages the associated state snapshot for fast
|
// of a specific account. It leverages the associated state snapshot for fast
|
||||||
// storage iteration and constructs trie node deletion markers by creating
|
// storage iteration and constructs trie node deletion markers by creating
|
||||||
// stack trie with iterated slots.
|
// stack trie with iterated slots.
|
||||||
func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||||
iter, err := s.snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{})
|
iter, err := s.snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 0, nil, nil, err
|
return 0, nil, nil, err
|
||||||
}
|
}
|
||||||
defer iter.Release()
|
defer iter.Release()
|
||||||
|
|
||||||
|
|
@ -968,40 +962,37 @@ func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (boo
|
||||||
})
|
})
|
||||||
stack := trie.NewStackTrie(options)
|
stack := trie.NewStackTrie(options)
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
if size > storageDeleteLimit {
|
|
||||||
return true, size, nil, nil, nil
|
|
||||||
}
|
|
||||||
slot := common.CopyBytes(iter.Slot())
|
slot := common.CopyBytes(iter.Slot())
|
||||||
if err := iter.Error(); err != nil { // error might occur after Slot function
|
if err := iter.Error(); err != nil { // error might occur after Slot function
|
||||||
return false, 0, nil, nil, err
|
return 0, nil, nil, err
|
||||||
}
|
}
|
||||||
size += common.StorageSize(common.HashLength + len(slot))
|
size += common.StorageSize(common.HashLength + len(slot))
|
||||||
slots[iter.Hash()] = slot
|
slots[iter.Hash()] = slot
|
||||||
|
|
||||||
if err := stack.Update(iter.Hash().Bytes(), slot); err != nil {
|
if err := stack.Update(iter.Hash().Bytes(), slot); err != nil {
|
||||||
return false, 0, nil, nil, err
|
return 0, nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := iter.Error(); err != nil { // error might occur during iteration
|
if err := iter.Error(); err != nil { // error might occur during iteration
|
||||||
return false, 0, nil, nil, err
|
return 0, nil, nil, err
|
||||||
}
|
}
|
||||||
if stack.Hash() != root {
|
if stack.Hash() != root {
|
||||||
return false, 0, nil, nil, fmt.Errorf("snapshot is not matched, exp %x, got %x", root, stack.Hash())
|
return 0, nil, nil, fmt.Errorf("snapshot is not matched, exp %x, got %x", root, stack.Hash())
|
||||||
}
|
}
|
||||||
return false, size, slots, nodes, nil
|
return size, slots, nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage,"
|
// slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage,"
|
||||||
// employed when the associated state snapshot is not available. It iterates the
|
// employed when the associated state snapshot is not available. It iterates the
|
||||||
// storage slots along with all internal trie nodes via trie directly.
|
// storage slots along with all internal trie nodes via trie directly.
|
||||||
func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||||
tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie)
|
tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
|
return 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
|
||||||
}
|
}
|
||||||
it, err := tr.NodeIterator(nil)
|
it, err := tr.NodeIterator(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, 0, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err)
|
return 0, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
size common.StorageSize
|
size common.StorageSize
|
||||||
|
|
@ -1009,9 +1000,6 @@ func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, r
|
||||||
slots = make(map[common.Hash][]byte)
|
slots = make(map[common.Hash][]byte)
|
||||||
)
|
)
|
||||||
for it.Next(true) {
|
for it.Next(true) {
|
||||||
if size > storageDeleteLimit {
|
|
||||||
return true, size, nil, nil, nil
|
|
||||||
}
|
|
||||||
if it.Leaf() {
|
if it.Leaf() {
|
||||||
slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob())
|
slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob())
|
||||||
size += common.StorageSize(common.HashLength + len(it.LeafBlob()))
|
size += common.StorageSize(common.HashLength + len(it.LeafBlob()))
|
||||||
|
|
@ -1024,9 +1012,9 @@ func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, r
|
||||||
nodes.AddNode(it.Path(), trienode.NewDeleted())
|
nodes.AddNode(it.Path(), trienode.NewDeleted())
|
||||||
}
|
}
|
||||||
if err := it.Error(); err != nil {
|
if err := it.Error(); err != nil {
|
||||||
return false, 0, nil, nil, err
|
return 0, nil, nil, err
|
||||||
}
|
}
|
||||||
return false, size, slots, nodes, nil
|
return size, slots, nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteStorage is designed to delete the storage trie of a designated account.
|
// deleteStorage is designed to delete the storage trie of a designated account.
|
||||||
|
|
@ -1034,31 +1022,27 @@ func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, r
|
||||||
// potentially leading to an out-of-memory panic. The function will make an attempt
|
// potentially leading to an out-of-memory panic. The function will make an attempt
|
||||||
// to utilize an efficient strategy if the associated state snapshot is reachable;
|
// to utilize an efficient strategy if the associated state snapshot is reachable;
|
||||||
// otherwise, it will resort to a less-efficient approach.
|
// otherwise, it will resort to a less-efficient approach.
|
||||||
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (bool, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
err error
|
err error
|
||||||
aborted bool
|
size common.StorageSize
|
||||||
size common.StorageSize
|
slots map[common.Hash][]byte
|
||||||
slots map[common.Hash][]byte
|
nodes *trienode.NodeSet
|
||||||
nodes *trienode.NodeSet
|
|
||||||
)
|
)
|
||||||
// The fast approach can be failed if the snapshot is not fully
|
// The fast approach can be failed if the snapshot is not fully
|
||||||
// generated, or it's internally corrupted. Fallback to the slow
|
// generated, or it's internally corrupted. Fallback to the slow
|
||||||
// one just in case.
|
// one just in case.
|
||||||
if s.snap != nil {
|
if s.snap != nil {
|
||||||
aborted, size, slots, nodes, err = s.fastDeleteStorage(addrHash, root)
|
size, slots, nodes, err = s.fastDeleteStorage(addrHash, root)
|
||||||
}
|
}
|
||||||
if s.snap == nil || err != nil {
|
if s.snap == nil || err != nil {
|
||||||
aborted, size, slots, nodes, err = s.slowDeleteStorage(addr, addrHash, root)
|
size, slots, nodes, err = s.slowDeleteStorage(addr, addrHash, root)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if metrics.EnabledExpensive {
|
if metrics.EnabledExpensive {
|
||||||
if aborted {
|
|
||||||
slotDeletionSkip.Inc(1)
|
|
||||||
}
|
|
||||||
n := int64(len(slots))
|
n := int64(len(slots))
|
||||||
|
|
||||||
slotDeletionMaxCount.UpdateIfGt(int64(len(slots)))
|
slotDeletionMaxCount.UpdateIfGt(int64(len(slots)))
|
||||||
|
|
@ -1068,7 +1052,7 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
|
||||||
slotDeletionCount.Mark(n)
|
slotDeletionCount.Mark(n)
|
||||||
slotDeletionSize.Mark(int64(size))
|
slotDeletionSize.Mark(int64(size))
|
||||||
}
|
}
|
||||||
return aborted, slots, nodes, nil
|
return slots, nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDestruction processes all destruction markers and deletes the account
|
// handleDestruction processes all destruction markers and deletes the account
|
||||||
|
|
@ -1095,13 +1079,12 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
|
||||||
//
|
//
|
||||||
// In case (d), **original** account along with its storages should be deleted,
|
// In case (d), **original** account along with its storages should be deleted,
|
||||||
// with their values be tracked as original value.
|
// with their values be tracked as original value.
|
||||||
func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.Address]struct{}, error) {
|
func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) error {
|
||||||
// Short circuit if geth is running with hash mode. This procedure can consume
|
// Short circuit if geth is running with hash mode. This procedure can consume
|
||||||
// considerable time and storage deletion isn't supported in hash mode, thus
|
// considerable time and storage deletion isn't supported in hash mode, thus
|
||||||
// preemptively avoiding unnecessary expenses.
|
// preemptively avoiding unnecessary expenses.
|
||||||
incomplete := make(map[common.Address]struct{})
|
|
||||||
if s.db.TrieDB().Scheme() == rawdb.HashScheme {
|
if s.db.TrieDB().Scheme() == rawdb.HashScheme {
|
||||||
return incomplete, nil
|
return nil
|
||||||
}
|
}
|
||||||
for addr, prev := range s.stateObjectsDestruct {
|
for addr, prev := range s.stateObjectsDestruct {
|
||||||
// The original account was non-existing, and it's marked as destructed
|
// The original account was non-existing, and it's marked as destructed
|
||||||
|
|
@ -1124,18 +1107,9 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Remove storage slots belong to the account.
|
// Remove storage slots belong to the account.
|
||||||
aborted, slots, set, err := s.deleteStorage(addr, addrHash, prev.Root)
|
slots, set, err := s.deleteStorage(addr, addrHash, prev.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
return fmt.Errorf("failed to delete storage, err: %w", err)
|
||||||
}
|
|
||||||
// The storage is too huge to handle, skip it but mark as incomplete.
|
|
||||||
// For case (d), the account is resurrected might with a few slots
|
|
||||||
// created. In this case, wipe the entire storage state diff because
|
|
||||||
// of aborted deletion.
|
|
||||||
if aborted {
|
|
||||||
incomplete[addr] = struct{}{}
|
|
||||||
delete(s.storagesOrigin, addr)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if s.storagesOrigin[addr] == nil {
|
if s.storagesOrigin[addr] == nil {
|
||||||
s.storagesOrigin[addr] = slots
|
s.storagesOrigin[addr] = slots
|
||||||
|
|
@ -1147,10 +1121,10 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := nodes.Merge(set); err != nil {
|
if err := nodes.Merge(set); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return incomplete, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit writes the state to the underlying in-memory trie database.
|
// Commit writes the state to the underlying in-memory trie database.
|
||||||
|
|
@ -1179,8 +1153,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
codeWriter = s.db.DiskDB().NewBatch()
|
codeWriter = s.db.DiskDB().NewBatch()
|
||||||
)
|
)
|
||||||
// Handle all state deletions first
|
// Handle all state deletions first
|
||||||
incomplete, err := s.handleDestruction(nodes)
|
if err := s.handleDestruction(nodes); err != nil {
|
||||||
if err != nil {
|
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
// Handle all state updates afterwards
|
// Handle all state updates afterwards
|
||||||
|
|
@ -1276,7 +1249,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
}
|
}
|
||||||
if root != origin {
|
if root != origin {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
set := triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)
|
set := triestate.New(s.accountsOrigin, s.storagesOrigin)
|
||||||
if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
|
if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1161,12 +1161,12 @@ func TestDeleteStorage(t *testing.T) {
|
||||||
obj := fastState.getOrNewStateObject(addr)
|
obj := fastState.getOrNewStateObject(addr)
|
||||||
storageRoot := obj.data.Root
|
storageRoot := obj.data.Root
|
||||||
|
|
||||||
_, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
_, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
_, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ func (st *StateTransition) to() common.Address {
|
||||||
|
|
||||||
func (st *StateTransition) buyGas() error {
|
func (st *StateTransition) buyGas() error {
|
||||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||||
mgval = mgval.Mul(mgval, st.msg.GasPrice)
|
mgval.Mul(mgval, st.msg.GasPrice)
|
||||||
balanceCheck := new(big.Int).Set(mgval)
|
balanceCheck := new(big.Int).Set(mgval)
|
||||||
if st.msg.GasFeeCap != nil {
|
if st.msg.GasFeeCap != nil {
|
||||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
balanceCheck.SetUint64(st.msg.GasLimit)
|
||||||
|
|
@ -263,7 +263,7 @@ func (st *StateTransition) buyGas() error {
|
||||||
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
st.gasRemaining += st.msg.GasLimit
|
st.gasRemaining = st.msg.GasLimit
|
||||||
|
|
||||||
st.initialGas = st.msg.GasLimit
|
st.initialGas = st.msg.GasLimit
|
||||||
mgvalU256, _ := uint256.FromBig(mgval)
|
mgvalU256, _ := uint256.FromBig(mgval)
|
||||||
|
|
@ -477,7 +477,7 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
|
||||||
|
|
||||||
// Return ETH for remaining gas, exchanged at the original rate.
|
// Return ETH for remaining gas, exchanged at the original rate.
|
||||||
remaining := uint256.NewInt(st.gasRemaining)
|
remaining := uint256.NewInt(st.gasRemaining)
|
||||||
remaining = remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||||
st.state.AddBalance(st.msg.From, remaining)
|
st.state.AddBalance(st.msg.From, remaining)
|
||||||
|
|
||||||
// Also return remaining gas to the block gas counter so it is
|
// Also return remaining gas to the block gas counter so it is
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ type accountMarshaling struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// storageJSON represents a 256 bit byte array, but allows less than 256 bits when
|
// storageJSON represents a 256 bit byte array, but allows less than 256 bits when
|
||||||
// unmarshaling from hex.
|
// unmarshalling from hex.
|
||||||
type storageJSON common.Hash
|
type storageJSON common.Hash
|
||||||
|
|
||||||
func (h *storageJSON) UnmarshalText(text []byte) error {
|
func (h *storageJSON) UnmarshalText(text []byte) error {
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ func TestReceiptJSON(t *testing.T) {
|
||||||
r := Receipt{}
|
r := Receipt{}
|
||||||
err = r.UnmarshalJSON(b)
|
err = r.UnmarshalJSON(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("error unmarshaling receipt from json:", err)
|
t.Fatal("error unmarshalling receipt from json:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -360,7 +360,7 @@ func TestEffectiveGasPriceNotRequired(t *testing.T) {
|
||||||
r2 := Receipt{}
|
r2 := Receipt{}
|
||||||
err = r2.UnmarshalJSON(b)
|
err = r2.UnmarshalJSON(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("error unmarshaling receipt from json:", err)
|
t.Fatal("error unmarshalling receipt from json:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,13 +107,7 @@ func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, err
|
||||||
|
|
||||||
// SignNewTx creates a transaction and signs it.
|
// SignNewTx creates a transaction and signs it.
|
||||||
func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) {
|
func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) {
|
||||||
tx := NewTx(txdata)
|
return SignTx(NewTx(txdata), s, prv)
|
||||||
h := s.Hash(tx)
|
|
||||||
sig, err := crypto.Sign(h[:], prv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tx.WithSignature(s, sig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustSignNewTx creates a transaction and signs it.
|
// MustSignNewTx creates a transaction and signs it.
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ type Claim [32]byte
|
||||||
var useCKZG atomic.Bool
|
var useCKZG atomic.Bool
|
||||||
|
|
||||||
// UseCKZG can be called to switch the default Go implementation of KZG to the C
|
// UseCKZG can be called to switch the default Go implementation of KZG to the C
|
||||||
// library if fo some reason the user wishes to do so (e.g. consensus bug in one
|
// library if for some reason the user wishes to do so (e.g. consensus bug in one
|
||||||
// or the other).
|
// or the other).
|
||||||
func UseCKZG(use bool) error {
|
func UseCKZG(use bool) error {
|
||||||
if use && !ckzgAvailable {
|
if use && !ckzgAvailable {
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,7 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact(
|
||||||
/** Verify an ECDSA signature.
|
/** Verify an ECDSA signature.
|
||||||
*
|
*
|
||||||
* Returns: 1: correct signature
|
* Returns: 1: correct signature
|
||||||
* 0: incorrect or unparseable signature
|
* 0: incorrect or unparsable signature
|
||||||
* Args: ctx: a secp256k1 context object, initialized for verification.
|
* Args: ctx: a secp256k1 context object, initialized for verification.
|
||||||
* In: sig: the signature being verified (cannot be NULL)
|
* In: sig: the signature being verified (cannot be NULL)
|
||||||
* msg32: the 32-byte message hash being verified (cannot be NULL)
|
* msg32: the 32-byte message hash being verified (cannot be NULL)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
# - A constraint describing the requirements of the law, called "require"
|
# - A constraint describing the requirements of the law, called "require"
|
||||||
# * Implementations are transliterated into functions that operate as well on
|
# * Implementations are transliterated into functions that operate as well on
|
||||||
# algebraic input points, and are called once per combination of branches
|
# algebraic input points, and are called once per combination of branches
|
||||||
# exectured. Each execution returns:
|
# executed. Each execution returns:
|
||||||
# - A constraint describing the assumptions this implementation requires
|
# - A constraint describing the assumptions this implementation requires
|
||||||
# (such as Z1=1), called "assumeFormula"
|
# (such as Z1=1), called "assumeFormula"
|
||||||
# - A constraint describing the assumptions this specific branch requires,
|
# - A constraint describing the assumptions this specific branch requires,
|
||||||
|
|
|
||||||
52
eth/api.go
52
eth/api.go
|
|
@ -1,52 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// 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 eth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EthereumAPI provides an API to access Ethereum full node-related information.
|
|
||||||
type EthereumAPI struct {
|
|
||||||
e *Ethereum
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEthereumAPI creates a new Ethereum protocol API for full nodes.
|
|
||||||
func NewEthereumAPI(e *Ethereum) *EthereumAPI {
|
|
||||||
return &EthereumAPI{e}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Etherbase is the address that mining rewards will be sent to.
|
|
||||||
func (api *EthereumAPI) Etherbase() (common.Address, error) {
|
|
||||||
return api.e.Etherbase()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coinbase is the address that mining rewards will be sent to (alias for Etherbase).
|
|
||||||
func (api *EthereumAPI) Coinbase() (common.Address, error) {
|
|
||||||
return api.Etherbase()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hashrate returns the POW hashrate.
|
|
||||||
func (api *EthereumAPI) Hashrate() hexutil.Uint64 {
|
|
||||||
return hexutil.Uint64(api.e.Miner().Hashrate())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mining returns an indication if this node is currently mining.
|
|
||||||
func (api *EthereumAPI) Mining() bool {
|
|
||||||
return api.e.IsMining()
|
|
||||||
}
|
|
||||||
|
|
@ -37,7 +37,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
@ -67,7 +66,7 @@ func (b *EthAPIBackend) SetHead(number uint64) {
|
||||||
func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
||||||
// Pending block is only known by the miner
|
// Pending block is only known by the miner
|
||||||
if number == rpc.PendingBlockNumber {
|
if number == rpc.PendingBlockNumber {
|
||||||
block := b.eth.miner.PendingBlock()
|
block, _, _ := b.eth.miner.Pending()
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return nil, errors.New("pending block is not available")
|
return nil, errors.New("pending block is not available")
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +117,7 @@ func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*ty
|
||||||
func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
|
func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
|
||||||
// Pending block is only known by the miner
|
// Pending block is only known by the miner
|
||||||
if number == rpc.PendingBlockNumber {
|
if number == rpc.PendingBlockNumber {
|
||||||
block := b.eth.miner.PendingBlock()
|
block, _, _ := b.eth.miner.Pending()
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return nil, errors.New("pending block is not available")
|
return nil, errors.New("pending block is not available")
|
||||||
}
|
}
|
||||||
|
|
@ -182,14 +181,14 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
|
||||||
return nil, errors.New("invalid arguments; neither block nor hash specified")
|
return nil, errors.New("invalid arguments; neither block nor hash specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
|
func (b *EthAPIBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) {
|
||||||
return b.eth.miner.PendingBlockAndReceipts()
|
return b.eth.miner.Pending()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
|
func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
|
||||||
// Pending state is only known by the miner
|
// Pending state is only known by the miner
|
||||||
if number == rpc.PendingBlockNumber {
|
if number == rpc.PendingBlockNumber {
|
||||||
block, state := b.eth.miner.Pending()
|
block, _, state := b.eth.miner.Pending()
|
||||||
if block == nil || state == nil {
|
if block == nil || state == nil {
|
||||||
return nil, nil, errors.New("pending state is not available")
|
return nil, nil, errors.New("pending state is not available")
|
||||||
}
|
}
|
||||||
|
|
@ -267,10 +266,6 @@ func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEven
|
||||||
return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch)
|
return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return b.eth.miner.SubscribePendingLogs(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||||||
return b.eth.BlockChain().SubscribeChainEvent(ch)
|
return b.eth.BlockChain().SubscribeChainEvent(ch)
|
||||||
}
|
}
|
||||||
|
|
@ -421,14 +416,6 @@ func (b *EthAPIBackend) CurrentHeader() *types.Header {
|
||||||
return b.eth.blockchain.CurrentHeader()
|
return b.eth.blockchain.CurrentHeader()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) Miner() *miner.Miner {
|
|
||||||
return b.eth.Miner()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *EthAPIBackend) StartMining() error {
|
|
||||||
return b.eth.StartMining()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
|
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
|
||||||
return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
|
return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) {
|
||||||
// If we're dumping the pending state, we need to request
|
// If we're dumping the pending state, we need to request
|
||||||
// both the pending block as well as the pending state from
|
// both the pending block as well as the pending state from
|
||||||
// the miner and operate on those
|
// the miner and operate on those
|
||||||
_, stateDb := api.eth.miner.Pending()
|
_, _, stateDb := api.eth.miner.Pending()
|
||||||
if stateDb == nil {
|
if stateDb == nil {
|
||||||
return state.Dump{}, errors.New("pending state is not available")
|
return state.Dump{}, errors.New("pending state is not available")
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +142,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
|
||||||
// If we're dumping the pending state, we need to request
|
// If we're dumping the pending state, we need to request
|
||||||
// both the pending block as well as the pending state from
|
// both the pending block as well as the pending state from
|
||||||
// the miner and operate on those
|
// the miner and operate on those
|
||||||
_, stateDb = api.eth.miner.Pending()
|
_, _, stateDb = api.eth.miner.Pending()
|
||||||
if stateDb == nil {
|
if stateDb == nil {
|
||||||
return state.Dump{}, errors.New("pending state is not available")
|
return state.Dump{}, errors.New("pending state is not available")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,21 +32,6 @@ func NewMinerAPI(e *Ethereum) *MinerAPI {
|
||||||
return &MinerAPI{e}
|
return &MinerAPI{e}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the miner with the given number of threads. If threads is nil,
|
|
||||||
// the number of workers started is equal to the number of logical CPUs that are
|
|
||||||
// usable by this process. If mining is already running, this method adjust the
|
|
||||||
// number of threads allowed to use and updates the minimum price required by the
|
|
||||||
// transaction pool.
|
|
||||||
func (api *MinerAPI) Start() error {
|
|
||||||
return api.e.StartMining()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop terminates the miner, both at the consensus engine level as well as at
|
|
||||||
// the block creation level.
|
|
||||||
func (api *MinerAPI) Stop() {
|
|
||||||
api.e.StopMining()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetExtra sets the extra data string that is included when this miner mines a block.
|
// SetExtra sets the extra data string that is included when this miner mines a block.
|
||||||
func (api *MinerAPI) SetExtra(extra string) (bool, error) {
|
func (api *MinerAPI) SetExtra(extra string) (bool, error) {
|
||||||
if err := api.e.Miner().SetExtra([]byte(extra)); err != nil {
|
if err := api.e.Miner().SetExtra([]byte(extra)); err != nil {
|
||||||
|
|
@ -73,14 +56,3 @@ func (api *MinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool {
|
||||||
api.e.Miner().SetGasCeil(uint64(gasLimit))
|
api.e.Miner().SetGasCeil(uint64(gasLimit))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEtherbase sets the etherbase of the miner.
|
|
||||||
func (api *MinerAPI) SetEtherbase(etherbase common.Address) bool {
|
|
||||||
api.e.SetEtherbase(etherbase)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRecommitInterval updates the interval for miner sealing work recommitting.
|
|
||||||
func (api *MinerAPI) SetRecommitInterval(interval int) {
|
|
||||||
api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
186
eth/backend.go
186
eth/backend.go
|
|
@ -28,8 +28,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -74,7 +72,6 @@ type Ethereum struct {
|
||||||
handler *handler
|
handler *handler
|
||||||
ethDialCandidates enode.Iterator
|
ethDialCandidates enode.Iterator
|
||||||
snapDialCandidates enode.Iterator
|
snapDialCandidates enode.Iterator
|
||||||
merger *consensus.Merger
|
|
||||||
|
|
||||||
// DB interfaces
|
// DB interfaces
|
||||||
chainDb ethdb.Database // Block chain database
|
chainDb ethdb.Database // Block chain database
|
||||||
|
|
@ -89,9 +86,8 @@ type Ethereum struct {
|
||||||
|
|
||||||
APIBackend *EthAPIBackend
|
APIBackend *EthAPIBackend
|
||||||
|
|
||||||
miner *miner.Miner
|
miner *miner.Miner
|
||||||
gasPrice *big.Int
|
gasPrice *big.Int
|
||||||
etherbase common.Address
|
|
||||||
|
|
||||||
networkID uint64
|
networkID uint64
|
||||||
netRPCService *ethapi.NetAPI
|
netRPCService *ethapi.NetAPI
|
||||||
|
|
@ -158,7 +154,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
}
|
}
|
||||||
eth := &Ethereum{
|
eth := &Ethereum{
|
||||||
config: config,
|
config: config,
|
||||||
merger: consensus.NewMerger(chainDb),
|
|
||||||
chainDb: chainDb,
|
chainDb: chainDb,
|
||||||
eventMux: stack.EventMux(),
|
eventMux: stack.EventMux(),
|
||||||
accountManager: stack.AccountManager(),
|
accountManager: stack.AccountManager(),
|
||||||
|
|
@ -166,7 +161,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
closeBloomHandler: make(chan struct{}),
|
closeBloomHandler: make(chan struct{}),
|
||||||
networkID: networkID,
|
networkID: networkID,
|
||||||
gasPrice: config.Miner.GasPrice,
|
gasPrice: config.Miner.GasPrice,
|
||||||
etherbase: config.Miner.Etherbase,
|
|
||||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||||
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
|
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
|
||||||
p2pServer: stack.Server(),
|
p2pServer: stack.Server(),
|
||||||
|
|
@ -213,7 +207,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
if config.OverrideVerkle != nil {
|
if config.OverrideVerkle != nil {
|
||||||
overrides.OverrideVerkle = config.OverrideVerkle
|
overrides.OverrideVerkle = config.OverrideVerkle
|
||||||
}
|
}
|
||||||
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory)
|
// TODO (MariusVanDerWijden) get rid of shouldPreserve in a follow-up PR
|
||||||
|
shouldPreserve := func(header *types.Header) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, shouldPreserve, &config.TransactionHistory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +238,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
Database: chainDb,
|
Database: chainDb,
|
||||||
Chain: eth.blockchain,
|
Chain: eth.blockchain,
|
||||||
TxPool: eth.txPool,
|
TxPool: eth.txPool,
|
||||||
Merger: eth.merger,
|
|
||||||
Network: networkID,
|
Network: networkID,
|
||||||
Sync: config.SyncMode,
|
Sync: config.SyncMode,
|
||||||
BloomCache: uint64(cacheLimit),
|
BloomCache: uint64(cacheLimit),
|
||||||
|
|
@ -250,7 +247,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
eth.miner = miner.New(eth, &config.Miner, eth.blockchain.Config(), eth.EventMux(), eth.engine, eth.isLocalBlock)
|
eth.miner = miner.New(eth, config.Miner, eth.engine)
|
||||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||||
|
|
||||||
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
||||||
|
|
@ -316,9 +313,6 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||||
// Append all the local APIs and return
|
// Append all the local APIs and return
|
||||||
return append(apis, []rpc.API{
|
return append(apis, []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: "eth",
|
|
||||||
Service: NewEthereumAPI(s),
|
|
||||||
}, {
|
|
||||||
Namespace: "miner",
|
Namespace: "miner",
|
||||||
Service: NewMinerAPI(s),
|
Service: NewMinerAPI(s),
|
||||||
}, {
|
}, {
|
||||||
|
|
@ -341,138 +335,6 @@ func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
|
||||||
s.blockchain.ResetWithGenesisBlock(gb)
|
s.blockchain.ResetWithGenesisBlock(gb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
|
||||||
s.lock.RLock()
|
|
||||||
etherbase := s.etherbase
|
|
||||||
s.lock.RUnlock()
|
|
||||||
|
|
||||||
if etherbase != (common.Address{}) {
|
|
||||||
return etherbase, nil
|
|
||||||
}
|
|
||||||
return common.Address{}, errors.New("etherbase must be explicitly specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// isLocalBlock checks whether the specified block is mined
|
|
||||||
// by local miner accounts.
|
|
||||||
//
|
|
||||||
// We regard two types of accounts as local miner account: etherbase
|
|
||||||
// and accounts specified via `txpool.locals` flag.
|
|
||||||
func (s *Ethereum) isLocalBlock(header *types.Header) bool {
|
|
||||||
author, err := s.engine.Author(header)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Failed to retrieve block author", "number", header.Number.Uint64(), "hash", header.Hash(), "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Check whether the given address is etherbase.
|
|
||||||
s.lock.RLock()
|
|
||||||
etherbase := s.etherbase
|
|
||||||
s.lock.RUnlock()
|
|
||||||
if author == etherbase {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Check whether the given address is specified by `txpool.local`
|
|
||||||
// CLI flag.
|
|
||||||
for _, account := range s.config.TxPool.Locals {
|
|
||||||
if account == author {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldPreserve checks whether we should preserve the given block
|
|
||||||
// during the chain reorg depending on whether the author of block
|
|
||||||
// is a local account.
|
|
||||||
func (s *Ethereum) shouldPreserve(header *types.Header) bool {
|
|
||||||
// The reason we need to disable the self-reorg preserving for clique
|
|
||||||
// is it can be probable to introduce a deadlock.
|
|
||||||
//
|
|
||||||
// e.g. If there are 7 available signers
|
|
||||||
//
|
|
||||||
// r1 A
|
|
||||||
// r2 B
|
|
||||||
// r3 C
|
|
||||||
// r4 D
|
|
||||||
// r5 A [X] F G
|
|
||||||
// r6 [X]
|
|
||||||
//
|
|
||||||
// In the round5, the in-turn signer E is offline, so the worst case
|
|
||||||
// is A, F and G sign the block of round5 and reject the block of opponents
|
|
||||||
// and in the round6, the last available signer B is offline, the whole
|
|
||||||
// network is stuck.
|
|
||||||
if _, ok := s.engine.(*clique.Clique); ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return s.isLocalBlock(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEtherbase sets the mining reward address.
|
|
||||||
func (s *Ethereum) SetEtherbase(etherbase common.Address) {
|
|
||||||
s.lock.Lock()
|
|
||||||
s.etherbase = etherbase
|
|
||||||
s.lock.Unlock()
|
|
||||||
|
|
||||||
s.miner.SetEtherbase(etherbase)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartMining starts the miner with the given number of CPU threads. If mining
|
|
||||||
// is already running, this method adjust the number of threads allowed to use
|
|
||||||
// and updates the minimum price required by the transaction pool.
|
|
||||||
func (s *Ethereum) StartMining() error {
|
|
||||||
// If the miner was not running, initialize it
|
|
||||||
if !s.IsMining() {
|
|
||||||
// Propagate the initial price point to the transaction pool
|
|
||||||
s.lock.RLock()
|
|
||||||
price := s.gasPrice
|
|
||||||
s.lock.RUnlock()
|
|
||||||
s.txPool.SetGasTip(price)
|
|
||||||
|
|
||||||
// Configure the local mining address
|
|
||||||
eb, err := s.Etherbase()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Cannot start mining without etherbase", "err", err)
|
|
||||||
return fmt.Errorf("etherbase missing: %v", err)
|
|
||||||
}
|
|
||||||
var cli *clique.Clique
|
|
||||||
if c, ok := s.engine.(*clique.Clique); ok {
|
|
||||||
cli = c
|
|
||||||
} else if cl, ok := s.engine.(*beacon.Beacon); ok {
|
|
||||||
if c, ok := cl.InnerEngine().(*clique.Clique); ok {
|
|
||||||
cli = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cli != nil {
|
|
||||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
|
||||||
if wallet == nil || err != nil {
|
|
||||||
log.Error("Etherbase account unavailable locally", "err", err)
|
|
||||||
return fmt.Errorf("signer missing: %v", err)
|
|
||||||
}
|
|
||||||
cli.Authorize(eb, wallet.SignData)
|
|
||||||
}
|
|
||||||
// If mining is started, we can disable the transaction rejection mechanism
|
|
||||||
// introduced to speed sync times.
|
|
||||||
s.handler.enableSyncedFeatures()
|
|
||||||
|
|
||||||
go s.miner.Start()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopMining terminates the miner, both at the consensus engine level as well as
|
|
||||||
// at the block creation level.
|
|
||||||
func (s *Ethereum) StopMining() {
|
|
||||||
// Update the thread count within the consensus engine
|
|
||||||
type threaded interface {
|
|
||||||
SetThreads(threads int)
|
|
||||||
}
|
|
||||||
if th, ok := s.engine.(threaded); ok {
|
|
||||||
th.SetThreads(-1)
|
|
||||||
}
|
|
||||||
// Stop the block creating itself
|
|
||||||
s.miner.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
|
|
||||||
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
||||||
|
|
||||||
func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
|
func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
|
||||||
|
|
@ -487,11 +349,6 @@ func (s *Ethereum) Synced() bool { return s.handler.synced
|
||||||
func (s *Ethereum) SetSynced() { s.handler.enableSyncedFeatures() }
|
func (s *Ethereum) SetSynced() { s.handler.enableSyncedFeatures() }
|
||||||
func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
|
func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
|
||||||
func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
|
func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
|
||||||
func (s *Ethereum) Merger() *consensus.Merger { return s.merger }
|
|
||||||
func (s *Ethereum) SyncMode() downloader.SyncMode {
|
|
||||||
mode, _ := s.handler.chainSync.modeAndLocalHead()
|
|
||||||
return mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protocols returns all the currently configured
|
// Protocols returns all the currently configured
|
||||||
// network protocols to start.
|
// network protocols to start.
|
||||||
|
|
@ -539,7 +396,6 @@ func (s *Ethereum) Stop() error {
|
||||||
s.bloomIndexer.Close()
|
s.bloomIndexer.Close()
|
||||||
close(s.closeBloomHandler)
|
close(s.closeBloomHandler)
|
||||||
s.txPool.Close()
|
s.txPool.Close()
|
||||||
s.miner.Close()
|
|
||||||
s.blockchain.Stop()
|
s.blockchain.Stop()
|
||||||
s.engine.Close()
|
s.engine.Close()
|
||||||
|
|
||||||
|
|
@ -551,3 +407,29 @@ func (s *Ethereum) Stop() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncMode retrieves the current sync mode, either explicitly set, or derived
|
||||||
|
// from the chain status.
|
||||||
|
func (s *Ethereum) SyncMode() downloader.SyncMode {
|
||||||
|
// If we're in snap sync mode, return that directly
|
||||||
|
if s.handler.snapSync.Load() {
|
||||||
|
return downloader.SnapSync
|
||||||
|
}
|
||||||
|
// We are probably in full sync, but we might have rewound to before the
|
||||||
|
// snap sync pivot, check if we should re-enable snap sync.
|
||||||
|
head := s.blockchain.CurrentBlock()
|
||||||
|
if pivot := rawdb.ReadLastPivotNumber(s.chainDb); pivot != nil {
|
||||||
|
if head.Number.Uint64() < *pivot {
|
||||||
|
return downloader.SnapSync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We are in a full sync, but the associated head state is missing. To complete
|
||||||
|
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
|
||||||
|
// persistent state is corrupted, just mismatch with the head block.
|
||||||
|
if !s.blockchain.HasState(head.Root) {
|
||||||
|
log.Info("Reenabled snap sync as chain is stateless")
|
||||||
|
return downloader.SnapSync
|
||||||
|
}
|
||||||
|
// Nope, we're really full syncing
|
||||||
|
return downloader.FullSync
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -267,12 +267,6 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
||||||
finalized := api.remoteBlocks.get(update.FinalizedBlockHash)
|
finalized := api.remoteBlocks.get(update.FinalizedBlockHash)
|
||||||
|
|
||||||
// Header advertised via a past newPayload request. Start syncing to it.
|
// Header advertised via a past newPayload request. Start syncing to it.
|
||||||
// Before we do however, make sure any legacy sync in switched off so we
|
|
||||||
// don't accidentally have 2 cycles running.
|
|
||||||
if merger := api.eth.Merger(); !merger.TDDReached() {
|
|
||||||
merger.ReachTTD()
|
|
||||||
api.eth.Downloader().Cancel()
|
|
||||||
}
|
|
||||||
context := []interface{}{"number", header.Number, "hash", header.Hash()}
|
context := []interface{}{"number", header.Number, "hash", header.Hash()}
|
||||||
if update.FinalizedBlockHash != (common.Hash{}) {
|
if update.FinalizedBlockHash != (common.Hash{}) {
|
||||||
if finalized == nil {
|
if finalized == nil {
|
||||||
|
|
@ -334,9 +328,6 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
||||||
// If the beacon client also advertised a finalized block, mark the local
|
// If the beacon client also advertised a finalized block, mark the local
|
||||||
// chain final and completely in PoS mode.
|
// chain final and completely in PoS mode.
|
||||||
if update.FinalizedBlockHash != (common.Hash{}) {
|
if update.FinalizedBlockHash != (common.Hash{}) {
|
||||||
if merger := api.eth.Merger(); !merger.PoSFinalized() {
|
|
||||||
merger.FinalizePoS()
|
|
||||||
}
|
|
||||||
// If the finalized block is not in our canonical tree, something is wrong
|
// If the finalized block is not in our canonical tree, something is wrong
|
||||||
finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
|
finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
|
||||||
if finalBlock == nil {
|
if finalBlock == nil {
|
||||||
|
|
@ -620,13 +611,6 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
||||||
|
|
||||||
return api.invalid(err, parent.Header()), nil
|
return api.invalid(err, parent.Header()), nil
|
||||||
}
|
}
|
||||||
// We've accepted a valid payload from the beacon client. Mark the local
|
|
||||||
// chain transitions to notify other subsystems (e.g. downloader) of the
|
|
||||||
// behavioral change.
|
|
||||||
if merger := api.eth.Merger(); !merger.TDDReached() {
|
|
||||||
merger.ReachTTD()
|
|
||||||
api.eth.Downloader().Cancel()
|
|
||||||
}
|
|
||||||
hash := block.Hash()
|
hash := block.Hash()
|
||||||
return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
|
return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -784,26 +768,23 @@ func (api *ConsensusAPI) heartbeat() {
|
||||||
|
|
||||||
// If there have been no updates for the past while, warn the user
|
// If there have been no updates for the past while, warn the user
|
||||||
// that the beacon client is probably offline
|
// that the beacon client is probably offline
|
||||||
if api.eth.BlockChain().Config().TerminalTotalDifficultyPassed || api.eth.Merger().TDDReached() {
|
if time.Since(lastForkchoiceUpdate) <= beaconUpdateConsensusTimeout || time.Since(lastNewPayloadUpdate) <= beaconUpdateConsensusTimeout {
|
||||||
if time.Since(lastForkchoiceUpdate) <= beaconUpdateConsensusTimeout || time.Since(lastNewPayloadUpdate) <= beaconUpdateConsensusTimeout {
|
offlineLogged = time.Time{}
|
||||||
offlineLogged = time.Time{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Since(offlineLogged) > beaconUpdateWarnFrequency {
|
|
||||||
if lastForkchoiceUpdate.IsZero() && lastNewPayloadUpdate.IsZero() {
|
|
||||||
if lastTransitionUpdate.IsZero() {
|
|
||||||
log.Warn("Post-merge network, but no beacon client seen. Please launch one to follow the chain!")
|
|
||||||
} else {
|
|
||||||
log.Warn("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Warn("Beacon client online, but no consensus updates received in a while. Please fix your beacon client to follow the chain!")
|
|
||||||
}
|
|
||||||
offlineLogged = time.Now()
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if time.Since(offlineLogged) > beaconUpdateWarnFrequency {
|
||||||
|
if lastForkchoiceUpdate.IsZero() && lastNewPayloadUpdate.IsZero() {
|
||||||
|
if lastTransitionUpdate.IsZero() {
|
||||||
|
log.Warn("Post-merge network, but no beacon client seen. Please launch one to follow the chain!")
|
||||||
|
} else {
|
||||||
|
log.Warn("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warn("Beacon client online, but no consensus updates received in a while. Please fix your beacon client to follow the chain!")
|
||||||
|
}
|
||||||
|
offlineLogged = time.Now()
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -447,7 +447,9 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
|
||||||
t.Fatal("can't create node:", err)
|
t.Fatal("can't create node:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
|
mcfg := miner.DefaultConfig
|
||||||
|
mcfg.PendingFeeRecipient = testAddr
|
||||||
|
ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: mcfg}
|
||||||
ethservice, err := eth.New(n, ethcfg)
|
ethservice, err := eth.New(n, ethcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("can't create eth service:", err)
|
t.Fatal("can't create eth service:", err)
|
||||||
|
|
@ -460,7 +462,6 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
|
||||||
t.Fatal("can't import test blocks:", err)
|
t.Fatal("can't import test blocks:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ethservice.SetEtherbase(testAddr)
|
|
||||||
ethservice.SetSynced()
|
ethservice.SetSynced()
|
||||||
return n, ethservice
|
return n, ethservice
|
||||||
}
|
}
|
||||||
|
|
@ -862,7 +863,6 @@ func TestTrickRemoteBlockCache(t *testing.T) {
|
||||||
func TestInvalidBloom(t *testing.T) {
|
func TestInvalidBloom(t *testing.T) {
|
||||||
genesis, preMergeBlocks := generateMergeChain(10, false)
|
genesis, preMergeBlocks := generateMergeChain(10, false)
|
||||||
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||||
ethservice.Merger().ReachTTD()
|
|
||||||
defer n.Close()
|
defer n.Close()
|
||||||
|
|
||||||
commonAncestor := ethservice.BlockChain().CurrentBlock()
|
commonAncestor := ethservice.BlockChain().CurrentBlock()
|
||||||
|
|
@ -1044,7 +1044,6 @@ func TestWithdrawals(t *testing.T) {
|
||||||
genesis.Config.ShanghaiTime = &time
|
genesis.Config.ShanghaiTime = &time
|
||||||
|
|
||||||
n, ethservice := startEthService(t, genesis, blocks)
|
n, ethservice := startEthService(t, genesis, blocks)
|
||||||
ethservice.Merger().ReachTTD()
|
|
||||||
defer n.Close()
|
defer n.Close()
|
||||||
|
|
||||||
api := NewConsensusAPI(ethservice)
|
api := NewConsensusAPI(ethservice)
|
||||||
|
|
@ -1162,7 +1161,6 @@ func TestNilWithdrawals(t *testing.T) {
|
||||||
genesis.Config.ShanghaiTime = &time
|
genesis.Config.ShanghaiTime = &time
|
||||||
|
|
||||||
n, ethservice := startEthService(t, genesis, blocks)
|
n, ethservice := startEthService(t, genesis, blocks)
|
||||||
ethservice.Merger().ReachTTD()
|
|
||||||
defer n.Close()
|
defer n.Close()
|
||||||
|
|
||||||
api := NewConsensusAPI(ethservice)
|
api := NewConsensusAPI(ethservice)
|
||||||
|
|
@ -1589,7 +1587,6 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
||||||
genesis.Config.CancunTime = &time
|
genesis.Config.CancunTime = &time
|
||||||
|
|
||||||
n, ethservice := startEthService(t, genesis, blocks)
|
n, ethservice := startEthService(t, genesis, blocks)
|
||||||
ethservice.Merger().ReachTTD()
|
|
||||||
defer n.Close()
|
defer n.Close()
|
||||||
|
|
||||||
api := NewConsensusAPI(ethservice)
|
api := NewConsensusAPI(ethservice)
|
||||||
|
|
|
||||||
88
eth/catalyst/blsync.go
Normal file
88
eth/catalyst/blsync.go
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// 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 catalyst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Blsync tracks the head of the beacon chain through the beacon light client
|
||||||
|
// and drives the local node via ConsensusAPI.
|
||||||
|
type Blsync struct {
|
||||||
|
engine *ConsensusAPI
|
||||||
|
client Client
|
||||||
|
headCh chan types.ChainHeadEvent
|
||||||
|
headSub event.Subscription
|
||||||
|
|
||||||
|
quitCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
SubscribeChainHeadEvent(ch chan<- types.ChainHeadEvent) event.Subscription
|
||||||
|
Start()
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlsync creates a new beacon light syncer.
|
||||||
|
func NewBlsync(client Client, eth *eth.Ethereum) *Blsync {
|
||||||
|
return &Blsync{
|
||||||
|
engine: newConsensusAPIWithoutHeartbeat(eth),
|
||||||
|
client: client,
|
||||||
|
headCh: make(chan types.ChainHeadEvent, 16),
|
||||||
|
quitCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts underlying beacon light client and the sync logic for driving
|
||||||
|
// the local node.
|
||||||
|
func (b *Blsync) Start() error {
|
||||||
|
log.Info("Beacon light sync started")
|
||||||
|
b.headSub = b.client.SubscribeChainHeadEvent(b.headCh)
|
||||||
|
go b.client.Start()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-b.quitCh:
|
||||||
|
return nil
|
||||||
|
case head := <-b.headCh:
|
||||||
|
if _, err := b.engine.NewPayloadV2(*head.HeadBlock); err != nil {
|
||||||
|
log.Error("failed to send new payload", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
update := engine.ForkchoiceStateV1{
|
||||||
|
HeadBlockHash: head.HeadBlock.BlockHash,
|
||||||
|
SafeBlockHash: head.Finalized, //TODO pass finalized or empty hash here?
|
||||||
|
FinalizedBlockHash: head.Finalized,
|
||||||
|
}
|
||||||
|
if _, err := b.engine.ForkchoiceUpdatedV1(update, nil); err != nil {
|
||||||
|
log.Error("failed to send forkchoice updated", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop signals to the light client and syncer to exit.
|
||||||
|
func (b *Blsync) Stop() error {
|
||||||
|
b.client.Stop()
|
||||||
|
close(b.quitCh)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -48,7 +49,7 @@ func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis) (*node.
|
||||||
t.Fatal("can't create node:", err)
|
t.Fatal("can't create node:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
|
ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: miner.DefaultConfig}
|
||||||
ethservice, err := eth.New(n, ethcfg)
|
ethservice, err := eth.New(n, ethcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("can't create eth service:", err)
|
t.Fatal("can't create eth service:", err)
|
||||||
|
|
|
||||||
|
|
@ -149,8 +149,6 @@ func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error
|
||||||
notifier.Notify(rpcSub.ID, status)
|
notifier.Notify(rpcSub.ID, status)
|
||||||
case <-rpcSub.Err():
|
case <-rpcSub.Err():
|
||||||
return
|
return
|
||||||
case <-notifier.Closed():
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -165,15 +165,14 @@ type Config struct {
|
||||||
// Clique is allowed for now to live standalone, but ethash is forbidden and can
|
// Clique is allowed for now to live standalone, but ethash is forbidden and can
|
||||||
// only exist on already merged networks.
|
// only exist on already merged networks.
|
||||||
func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) {
|
func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) {
|
||||||
// If proof-of-authority is requested, set it up
|
// Geth v1.14.0 dropped support for non-merged networks in any consensus
|
||||||
|
// mode. If such a network is requested, reject startup.
|
||||||
|
if !config.TerminalTotalDifficultyPassed {
|
||||||
|
return nil, errors.New("only PoS networks are supported, please transition old ones with Geth v1.13.x")
|
||||||
|
}
|
||||||
|
// Wrap previously supported consensus engines into their post-merge counterpart
|
||||||
if config.Clique != nil {
|
if config.Clique != nil {
|
||||||
return beacon.New(clique.New(config.Clique, db)), nil
|
return beacon.New(clique.New(config.Clique, db)), nil
|
||||||
}
|
}
|
||||||
// If defaulting to proof-of-work, enforce an already merged network since
|
|
||||||
// we cannot run PoW algorithms anymore, so we cannot even follow a chain
|
|
||||||
// not coordinated by a beacon node.
|
|
||||||
if !config.TerminalTotalDifficultyPassed {
|
|
||||||
return nil, errors.New("ethash is only supported as a historical component of already merged networks")
|
|
||||||
}
|
|
||||||
return beacon.New(ethash.NewFaker()), nil
|
return beacon.New(ethash.NewFaker()), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,939 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// 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 fetcher contains the announcement based header, blocks or transaction synchronisation.
|
|
||||||
package fetcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/prque"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
lightTimeout = time.Millisecond // Time allowance before an announced header is explicitly requested
|
|
||||||
arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block/transaction is explicitly requested
|
|
||||||
gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches
|
|
||||||
fetchTimeout = 5 * time.Second // Maximum allotted time to return an explicitly requested block/transaction
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxUncleDist = 7 // Maximum allowed backward distance from the chain head
|
|
||||||
maxQueueDist = 32 // Maximum allowed distance from the chain head to queue
|
|
||||||
hashLimit = 256 // Maximum number of unique blocks or headers a peer may have announced
|
|
||||||
blockLimit = 64 // Maximum number of unique blocks a peer may have delivered
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
blockAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/in", nil)
|
|
||||||
blockAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/announces/out", nil)
|
|
||||||
blockAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/drop", nil)
|
|
||||||
blockAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/dos", nil)
|
|
||||||
|
|
||||||
blockBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/in", nil)
|
|
||||||
blockBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/broadcasts/out", nil)
|
|
||||||
blockBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/drop", nil)
|
|
||||||
blockBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/dos", nil)
|
|
||||||
|
|
||||||
headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/headers", nil)
|
|
||||||
bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/bodies", nil)
|
|
||||||
|
|
||||||
headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/in", nil)
|
|
||||||
headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/out", nil)
|
|
||||||
bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/in", nil)
|
|
||||||
bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/out", nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
var errTerminated = errors.New("terminated")
|
|
||||||
|
|
||||||
// HeaderRetrievalFn is a callback type for retrieving a header from the local chain.
|
|
||||||
type HeaderRetrievalFn func(common.Hash) *types.Header
|
|
||||||
|
|
||||||
// blockRetrievalFn is a callback type for retrieving a block from the local chain.
|
|
||||||
type blockRetrievalFn func(common.Hash) *types.Block
|
|
||||||
|
|
||||||
// headerRequesterFn is a callback type for sending a header retrieval request.
|
|
||||||
type headerRequesterFn func(common.Hash, chan *eth.Response) (*eth.Request, error)
|
|
||||||
|
|
||||||
// bodyRequesterFn is a callback type for sending a body retrieval request.
|
|
||||||
type bodyRequesterFn func([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
|
||||||
|
|
||||||
// headerVerifierFn is a callback type to verify a block's header for fast propagation.
|
|
||||||
type headerVerifierFn 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)
|
|
||||||
|
|
||||||
// chainHeightFn is a callback type to retrieve the current chain height.
|
|
||||||
type chainHeightFn func() uint64
|
|
||||||
|
|
||||||
// headersInsertFn is a callback type to insert a batch of headers into the local chain.
|
|
||||||
type headersInsertFn func(headers []*types.Header) (int, error)
|
|
||||||
|
|
||||||
// chainInsertFn is a callback type to insert a batch of blocks into the local chain.
|
|
||||||
type chainInsertFn func(types.Blocks) (int, error)
|
|
||||||
|
|
||||||
// peerDropFn is a callback type for dropping a peer detected as malicious.
|
|
||||||
type peerDropFn func(id string)
|
|
||||||
|
|
||||||
// blockAnnounce is the hash notification of the availability of a new block in the
|
|
||||||
// network.
|
|
||||||
type blockAnnounce struct {
|
|
||||||
hash common.Hash // Hash of the block being announced
|
|
||||||
number uint64 // Number of the block being announced (0 = unknown | old protocol)
|
|
||||||
header *types.Header // Header of the block partially reassembled (new protocol)
|
|
||||||
time time.Time // Timestamp of the announcement
|
|
||||||
|
|
||||||
origin string // Identifier of the peer originating the notification
|
|
||||||
|
|
||||||
fetchHeader headerRequesterFn // Fetcher function to retrieve the header of an announced block
|
|
||||||
fetchBodies bodyRequesterFn // Fetcher function to retrieve the body of an announced block
|
|
||||||
}
|
|
||||||
|
|
||||||
// headerFilterTask represents a batch of headers needing fetcher filtering.
|
|
||||||
type headerFilterTask struct {
|
|
||||||
peer string // The source peer of block headers
|
|
||||||
headers []*types.Header // Collection of headers to filter
|
|
||||||
time time.Time // Arrival time of the headers
|
|
||||||
}
|
|
||||||
|
|
||||||
// bodyFilterTask represents a batch of block bodies (transactions and uncles)
|
|
||||||
// needing fetcher filtering.
|
|
||||||
type bodyFilterTask struct {
|
|
||||||
peer string // The source peer of block bodies
|
|
||||||
transactions [][]*types.Transaction // Collection of transactions per block bodies
|
|
||||||
uncles [][]*types.Header // Collection of uncles per block bodies
|
|
||||||
time time.Time // Arrival time of the blocks' contents
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockOrHeaderInject represents a schedules import operation.
|
|
||||||
type blockOrHeaderInject struct {
|
|
||||||
origin string
|
|
||||||
|
|
||||||
header *types.Header // Used for light mode fetcher which only cares about header.
|
|
||||||
block *types.Block // Used for normal mode fetcher which imports full block.
|
|
||||||
}
|
|
||||||
|
|
||||||
// number returns the block number of the injected object.
|
|
||||||
func (inject *blockOrHeaderInject) number() uint64 {
|
|
||||||
if inject.header != nil {
|
|
||||||
return inject.header.Number.Uint64()
|
|
||||||
}
|
|
||||||
return inject.block.NumberU64()
|
|
||||||
}
|
|
||||||
|
|
||||||
// number returns the block hash of the injected object.
|
|
||||||
func (inject *blockOrHeaderInject) hash() common.Hash {
|
|
||||||
if inject.header != nil {
|
|
||||||
return inject.header.Hash()
|
|
||||||
}
|
|
||||||
return inject.block.Hash()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockFetcher is responsible for accumulating block announcements from various peers
|
|
||||||
// and scheduling them for retrieval.
|
|
||||||
type BlockFetcher struct {
|
|
||||||
light bool // The indicator whether it's a light fetcher or normal one.
|
|
||||||
|
|
||||||
// Various event channels
|
|
||||||
notify chan *blockAnnounce
|
|
||||||
inject chan *blockOrHeaderInject
|
|
||||||
|
|
||||||
headerFilter chan chan *headerFilterTask
|
|
||||||
bodyFilter chan chan *bodyFilterTask
|
|
||||||
|
|
||||||
done chan common.Hash
|
|
||||||
quit chan struct{}
|
|
||||||
|
|
||||||
// Announce states
|
|
||||||
announces map[string]int // Per peer blockAnnounce counts to prevent memory exhaustion
|
|
||||||
announced map[common.Hash][]*blockAnnounce // Announced blocks, scheduled for fetching
|
|
||||||
fetching map[common.Hash]*blockAnnounce // Announced blocks, currently fetching
|
|
||||||
fetched map[common.Hash][]*blockAnnounce // Blocks with headers fetched, scheduled for body retrieval
|
|
||||||
completing map[common.Hash]*blockAnnounce // Blocks with headers, currently body-completing
|
|
||||||
|
|
||||||
// Block cache
|
|
||||||
queue *prque.Prque[int64, *blockOrHeaderInject] // Queue containing the import operations (block number sorted)
|
|
||||||
queues map[string]int // Per peer block counts to prevent memory exhaustion
|
|
||||||
queued map[common.Hash]*blockOrHeaderInject // Set of already queued blocks (to dedup imports)
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
getHeader HeaderRetrievalFn // Retrieves a header from the local chain
|
|
||||||
getBlock blockRetrievalFn // Retrieves a block from the local chain
|
|
||||||
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
|
|
||||||
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
|
|
||||||
chainHeight chainHeightFn // Retrieves the current chain's height
|
|
||||||
insertHeaders headersInsertFn // Injects a batch of headers into the chain
|
|
||||||
insertChain chainInsertFn // Injects a batch of blocks into the chain
|
|
||||||
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 blockAnnounce list
|
|
||||||
queueChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a block from the import queue
|
|
||||||
fetchingHook func([]common.Hash) // Method to call upon starting a block (eth/61) or header (eth/62) fetch
|
|
||||||
completingHook func([]common.Hash) // Method to call upon starting a block body fetch (eth/62)
|
|
||||||
importedHook func(*types.Header, *types.Block) // Method to call upon successful header or block import (both eth/61 and eth/62)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBlockFetcher creates a block fetcher to retrieve blocks based on hash announcements.
|
|
||||||
func NewBlockFetcher(light bool, getHeader HeaderRetrievalFn, getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertHeaders headersInsertFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher {
|
|
||||||
return &BlockFetcher{
|
|
||||||
light: light,
|
|
||||||
notify: make(chan *blockAnnounce),
|
|
||||||
inject: make(chan *blockOrHeaderInject),
|
|
||||||
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][]*blockAnnounce),
|
|
||||||
fetching: make(map[common.Hash]*blockAnnounce),
|
|
||||||
fetched: make(map[common.Hash][]*blockAnnounce),
|
|
||||||
completing: make(map[common.Hash]*blockAnnounce),
|
|
||||||
queue: prque.New[int64, *blockOrHeaderInject](nil),
|
|
||||||
queues: make(map[string]int),
|
|
||||||
queued: make(map[common.Hash]*blockOrHeaderInject),
|
|
||||||
getHeader: getHeader,
|
|
||||||
getBlock: getBlock,
|
|
||||||
verifyHeader: verifyHeader,
|
|
||||||
broadcastBlock: broadcastBlock,
|
|
||||||
chainHeight: chainHeight,
|
|
||||||
insertHeaders: insertHeaders,
|
|
||||||
insertChain: insertChain,
|
|
||||||
dropPeer: dropPeer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start boots up the announcement based synchroniser, accepting and processing
|
|
||||||
// hash notifications and block fetches until termination requested.
|
|
||||||
func (f *BlockFetcher) Start() {
|
|
||||||
go f.loop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop terminates the announcement based synchroniser, canceling all pending
|
|
||||||
// operations.
|
|
||||||
func (f *BlockFetcher) Stop() {
|
|
||||||
close(f.quit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify announces the fetcher of the potential availability of a new block in
|
|
||||||
// the network.
|
|
||||||
func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time,
|
|
||||||
headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error {
|
|
||||||
block := &blockAnnounce{
|
|
||||||
hash: hash,
|
|
||||||
number: number,
|
|
||||||
time: time,
|
|
||||||
origin: peer,
|
|
||||||
fetchHeader: headerFetcher,
|
|
||||||
fetchBodies: bodyFetcher,
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case f.notify <- block:
|
|
||||||
return nil
|
|
||||||
case <-f.quit:
|
|
||||||
return errTerminated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enqueue tries to fill gaps the fetcher's future import queue.
|
|
||||||
func (f *BlockFetcher) Enqueue(peer string, block *types.Block) error {
|
|
||||||
op := &blockOrHeaderInject{
|
|
||||||
origin: peer,
|
|
||||||
block: block,
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case f.inject <- op:
|
|
||||||
return nil
|
|
||||||
case <-f.quit:
|
|
||||||
return errTerminated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterHeaders extracts all the headers that were explicitly requested by the fetcher,
|
|
||||||
// returning those that should be handled differently.
|
|
||||||
func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time time.Time) []*types.Header {
|
|
||||||
log.Trace("Filtering headers", "peer", peer, "headers", len(headers))
|
|
||||||
|
|
||||||
// Send the filter channel to the fetcher
|
|
||||||
filter := make(chan *headerFilterTask)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case f.headerFilter <- filter:
|
|
||||||
case <-f.quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Request the filtering of the header list
|
|
||||||
select {
|
|
||||||
case filter <- &headerFilterTask{peer: peer, headers: headers, time: time}:
|
|
||||||
case <-f.quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Retrieve the headers remaining after filtering
|
|
||||||
select {
|
|
||||||
case task := <-filter:
|
|
||||||
return task.headers
|
|
||||||
case <-f.quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterBodies extracts all the block bodies that were explicitly requested by
|
|
||||||
// the fetcher, returning those that should be handled differently.
|
|
||||||
func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) {
|
|
||||||
log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles))
|
|
||||||
|
|
||||||
// Send the filter channel to the fetcher
|
|
||||||
filter := make(chan *bodyFilterTask)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case f.bodyFilter <- filter:
|
|
||||||
case <-f.quit:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// Request the filtering of the body list
|
|
||||||
select {
|
|
||||||
case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, time: time}:
|
|
||||||
case <-f.quit:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// Retrieve the bodies remaining after filtering
|
|
||||||
select {
|
|
||||||
case task := <-filter:
|
|
||||||
return task.transactions, task.uncles
|
|
||||||
case <-f.quit:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop is the main fetcher loop, checking and processing various notification
|
|
||||||
// events.
|
|
||||||
func (f *BlockFetcher) loop() {
|
|
||||||
// Iterate the block fetching until a quit is requested
|
|
||||||
var (
|
|
||||||
fetchTimer = time.NewTimer(0)
|
|
||||||
completeTimer = time.NewTimer(0)
|
|
||||||
)
|
|
||||||
<-fetchTimer.C // clear out the channel
|
|
||||||
<-completeTimer.C
|
|
||||||
defer fetchTimer.Stop()
|
|
||||||
defer completeTimer.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Clean up any expired block fetches
|
|
||||||
for hash, announce := range f.fetching {
|
|
||||||
if time.Since(announce.time) > fetchTimeout {
|
|
||||||
f.forgetHash(hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Import any queued blocks that could potentially fit
|
|
||||||
height := f.chainHeight()
|
|
||||||
for !f.queue.Empty() {
|
|
||||||
op := f.queue.PopItem()
|
|
||||||
hash := op.hash()
|
|
||||||
if f.queueChangeHook != nil {
|
|
||||||
f.queueChangeHook(hash, false)
|
|
||||||
}
|
|
||||||
// If too high up the chain or phase, continue later
|
|
||||||
number := op.number()
|
|
||||||
if number > height+1 {
|
|
||||||
f.queue.Push(op, -int64(number))
|
|
||||||
if f.queueChangeHook != nil {
|
|
||||||
f.queueChangeHook(hash, true)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Otherwise if fresh and still unknown, try and import
|
|
||||||
if (number+maxUncleDist < height) || (f.light && f.getHeader(hash) != nil) || (!f.light && f.getBlock(hash) != nil) {
|
|
||||||
f.forgetBlock(hash)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if f.light {
|
|
||||||
f.importHeaders(op.origin, op.header)
|
|
||||||
} else {
|
|
||||||
f.importBlocks(op.origin, op.block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Wait for an outside event to occur
|
|
||||||
select {
|
|
||||||
case <-f.quit:
|
|
||||||
// BlockFetcher terminating, abort all operations
|
|
||||||
return
|
|
||||||
|
|
||||||
case notification := <-f.notify:
|
|
||||||
// A block was announced, make sure the peer isn't DOSing us
|
|
||||||
blockAnnounceInMeter.Mark(1)
|
|
||||||
|
|
||||||
count := f.announces[notification.origin] + 1
|
|
||||||
if count > hashLimit {
|
|
||||||
log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit)
|
|
||||||
blockAnnounceDOSMeter.Mark(1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if notification.number == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// If we have a valid block number, check that it's potentially useful
|
|
||||||
if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
|
|
||||||
log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist)
|
|
||||||
blockAnnounceDropMeter.Mark(1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// All is well, schedule the announce if block's not yet downloading
|
|
||||||
if _, ok := f.fetching[notification.hash]; ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if _, ok := f.completing[notification.hash]; ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
f.announces[notification.origin] = count
|
|
||||||
f.announced[notification.hash] = append(f.announced[notification.hash], notification)
|
|
||||||
if f.announceChangeHook != nil && len(f.announced[notification.hash]) == 1 {
|
|
||||||
f.announceChangeHook(notification.hash, true)
|
|
||||||
}
|
|
||||||
if len(f.announced) == 1 {
|
|
||||||
f.rescheduleFetch(fetchTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
case op := <-f.inject:
|
|
||||||
// A direct block insertion was requested, try and fill any pending gaps
|
|
||||||
blockBroadcastInMeter.Mark(1)
|
|
||||||
|
|
||||||
// Now only direct block injection is allowed, drop the header injection
|
|
||||||
// here silently if we receive.
|
|
||||||
if f.light {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f.enqueue(op.origin, nil, op.block)
|
|
||||||
|
|
||||||
case hash := <-f.done:
|
|
||||||
// A pending import finished, remove all traces of the notification
|
|
||||||
f.forgetHash(hash)
|
|
||||||
f.forgetBlock(hash)
|
|
||||||
|
|
||||||
case <-fetchTimer.C:
|
|
||||||
// At least one block's timer ran out, check for needing retrieval
|
|
||||||
request := make(map[string][]common.Hash)
|
|
||||||
|
|
||||||
for hash, announces := range f.announced {
|
|
||||||
// In current LES protocol(les2/les3), only header announce is
|
|
||||||
// available, no need to wait too much time for header broadcast.
|
|
||||||
timeout := arriveTimeout - gatherSlack
|
|
||||||
if f.light {
|
|
||||||
timeout = 0
|
|
||||||
}
|
|
||||||
if time.Since(announces[0].time) > timeout {
|
|
||||||
// Pick a random peer to retrieve from, reset all others
|
|
||||||
announce := announces[rand.Intn(len(announces))]
|
|
||||||
f.forgetHash(hash)
|
|
||||||
|
|
||||||
// If the block still didn't arrive, queue for fetching
|
|
||||||
if (f.light && f.getHeader(hash) == nil) || (!f.light && f.getBlock(hash) == nil) {
|
|
||||||
request[announce.origin] = append(request[announce.origin], hash)
|
|
||||||
f.fetching[hash] = announce
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Send out all block header requests
|
|
||||||
for peer, hashes := range request {
|
|
||||||
log.Trace("Fetching scheduled headers", "peer", peer, "list", hashes)
|
|
||||||
|
|
||||||
// Create a closure of the fetch and schedule in on a new thread
|
|
||||||
fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes
|
|
||||||
go func(peer string) {
|
|
||||||
if f.fetchingHook != nil {
|
|
||||||
f.fetchingHook(hashes)
|
|
||||||
}
|
|
||||||
for _, hash := range hashes {
|
|
||||||
headerFetchMeter.Mark(1)
|
|
||||||
go func(hash common.Hash) {
|
|
||||||
resCh := make(chan *eth.Response)
|
|
||||||
|
|
||||||
req, err := fetchHeader(hash, resCh)
|
|
||||||
if err != nil {
|
|
||||||
return // Legacy code, yolo
|
|
||||||
}
|
|
||||||
defer req.Close()
|
|
||||||
|
|
||||||
timeout := time.NewTimer(2 * fetchTimeout) // 2x leeway before dropping the peer
|
|
||||||
defer timeout.Stop()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case res := <-resCh:
|
|
||||||
res.Done <- nil
|
|
||||||
f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersRequest), time.Now())
|
|
||||||
|
|
||||||
case <-timeout.C:
|
|
||||||
// The peer didn't respond in time. The request
|
|
||||||
// was already rescheduled at this point, we were
|
|
||||||
// waiting for a catchup. With an unresponsive
|
|
||||||
// peer however, it's a protocol violation.
|
|
||||||
f.dropPeer(peer)
|
|
||||||
}
|
|
||||||
}(hash)
|
|
||||||
}
|
|
||||||
}(peer)
|
|
||||||
}
|
|
||||||
// Schedule the next fetch if blocks are still pending
|
|
||||||
f.rescheduleFetch(fetchTimer)
|
|
||||||
|
|
||||||
case <-completeTimer.C:
|
|
||||||
// At least one header's timer ran out, retrieve everything
|
|
||||||
request := make(map[string][]common.Hash)
|
|
||||||
|
|
||||||
for hash, announces := range f.fetched {
|
|
||||||
// Pick a random peer to retrieve from, reset all others
|
|
||||||
announce := announces[rand.Intn(len(announces))]
|
|
||||||
f.forgetHash(hash)
|
|
||||||
|
|
||||||
// If the block still didn't arrive, queue for completion
|
|
||||||
if f.getBlock(hash) == nil {
|
|
||||||
request[announce.origin] = append(request[announce.origin], hash)
|
|
||||||
f.completing[hash] = announce
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Send out all block body requests
|
|
||||||
for peer, hashes := range request {
|
|
||||||
log.Trace("Fetching scheduled bodies", "peer", peer, "list", hashes)
|
|
||||||
|
|
||||||
// Create a closure of the fetch and schedule in on a new thread
|
|
||||||
if f.completingHook != nil {
|
|
||||||
f.completingHook(hashes)
|
|
||||||
}
|
|
||||||
fetchBodies := f.completing[hashes[0]].fetchBodies
|
|
||||||
bodyFetchMeter.Mark(int64(len(hashes)))
|
|
||||||
|
|
||||||
go func(peer string, hashes []common.Hash) {
|
|
||||||
resCh := make(chan *eth.Response)
|
|
||||||
|
|
||||||
req, err := fetchBodies(hashes, resCh)
|
|
||||||
if err != nil {
|
|
||||||
return // Legacy code, yolo
|
|
||||||
}
|
|
||||||
defer req.Close()
|
|
||||||
|
|
||||||
timeout := time.NewTimer(2 * fetchTimeout) // 2x leeway before dropping the peer
|
|
||||||
defer timeout.Stop()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case res := <-resCh:
|
|
||||||
res.Done <- nil
|
|
||||||
// Ignoring withdrawals here, since the block fetcher is not used post-merge.
|
|
||||||
txs, uncles, _ := res.Res.(*eth.BlockBodiesResponse).Unpack()
|
|
||||||
f.FilterBodies(peer, txs, uncles, time.Now())
|
|
||||||
|
|
||||||
case <-timeout.C:
|
|
||||||
// The peer didn't respond in time. The request
|
|
||||||
// was already rescheduled at this point, we were
|
|
||||||
// waiting for a catchup. With an unresponsive
|
|
||||||
// peer however, it's a protocol violation.
|
|
||||||
f.dropPeer(peer)
|
|
||||||
}
|
|
||||||
}(peer, hashes)
|
|
||||||
}
|
|
||||||
// Schedule the next fetch if blocks are still pending
|
|
||||||
f.rescheduleComplete(completeTimer)
|
|
||||||
|
|
||||||
case filter := <-f.headerFilter:
|
|
||||||
// Headers arrived from a remote peer. Extract those that were explicitly
|
|
||||||
// requested by the fetcher, and return everything else so it's delivered
|
|
||||||
// to other parts of the system.
|
|
||||||
var task *headerFilterTask
|
|
||||||
select {
|
|
||||||
case task = <-filter:
|
|
||||||
case <-f.quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
headerFilterInMeter.Mark(int64(len(task.headers)))
|
|
||||||
|
|
||||||
// Split the batch of headers into unknown ones (to return to the caller),
|
|
||||||
// known incomplete ones (requiring body retrievals) and completed blocks.
|
|
||||||
unknown, incomplete, complete, lightHeaders := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}, []*blockAnnounce{}
|
|
||||||
for _, header := range task.headers {
|
|
||||||
hash := header.Hash()
|
|
||||||
|
|
||||||
// Filter fetcher-requested headers from other synchronisation algorithms
|
|
||||||
if announce := f.fetching[hash]; announce != nil && announce.origin == task.peer && f.fetched[hash] == nil && f.completing[hash] == nil && f.queued[hash] == nil {
|
|
||||||
// If the delivered header does not match the promised number, drop the announcer
|
|
||||||
if header.Number.Uint64() != announce.number {
|
|
||||||
log.Trace("Invalid block number fetched", "peer", announce.origin, "hash", header.Hash(), "announced", announce.number, "provided", header.Number)
|
|
||||||
f.dropPeer(announce.origin)
|
|
||||||
f.forgetHash(hash)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Collect all headers only if we are running in light
|
|
||||||
// mode and the headers are not imported by other means.
|
|
||||||
if f.light {
|
|
||||||
if f.getHeader(hash) == nil {
|
|
||||||
announce.header = header
|
|
||||||
lightHeaders = append(lightHeaders, announce)
|
|
||||||
}
|
|
||||||
f.forgetHash(hash)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Only keep if not imported by other means
|
|
||||||
if f.getBlock(hash) == nil {
|
|
||||||
announce.header = header
|
|
||||||
announce.time = task.time
|
|
||||||
|
|
||||||
// If the block is empty (header only), short circuit into the final import queue
|
|
||||||
if header.TxHash == types.EmptyTxsHash && header.UncleHash == types.EmptyUncleHash {
|
|
||||||
log.Trace("Block empty, skipping body retrieval", "peer", announce.origin, "number", header.Number, "hash", header.Hash())
|
|
||||||
|
|
||||||
block := types.NewBlockWithHeader(header)
|
|
||||||
block.ReceivedAt = task.time
|
|
||||||
|
|
||||||
complete = append(complete, block)
|
|
||||||
f.completing[hash] = announce
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Otherwise add to the list of blocks needing completion
|
|
||||||
incomplete = append(incomplete, announce)
|
|
||||||
} else {
|
|
||||||
log.Trace("Block already imported, discarding header", "peer", announce.origin, "number", header.Number, "hash", header.Hash())
|
|
||||||
f.forgetHash(hash)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// BlockFetcher doesn't know about it, add to the return list
|
|
||||||
unknown = append(unknown, header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
headerFilterOutMeter.Mark(int64(len(unknown)))
|
|
||||||
select {
|
|
||||||
case filter <- &headerFilterTask{headers: unknown, time: task.time}:
|
|
||||||
case <-f.quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Schedule the retrieved headers for body completion
|
|
||||||
for _, announce := range incomplete {
|
|
||||||
hash := announce.header.Hash()
|
|
||||||
if _, ok := f.completing[hash]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f.fetched[hash] = append(f.fetched[hash], announce)
|
|
||||||
if len(f.fetched) == 1 {
|
|
||||||
f.rescheduleComplete(completeTimer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Schedule the header for light fetcher import
|
|
||||||
for _, announce := range lightHeaders {
|
|
||||||
f.enqueue(announce.origin, announce.header, nil)
|
|
||||||
}
|
|
||||||
// Schedule the header-only blocks for import
|
|
||||||
for _, block := range complete {
|
|
||||||
if announce := f.completing[block.Hash()]; announce != nil {
|
|
||||||
f.enqueue(announce.origin, nil, block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case filter := <-f.bodyFilter:
|
|
||||||
// Block bodies arrived, extract any explicitly requested blocks, return the rest
|
|
||||||
var task *bodyFilterTask
|
|
||||||
select {
|
|
||||||
case task = <-filter:
|
|
||||||
case <-f.quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bodyFilterInMeter.Mark(int64(len(task.transactions)))
|
|
||||||
blocks := []*types.Block{}
|
|
||||||
// abort early if there's nothing explicitly requested
|
|
||||||
if len(f.completing) > 0 {
|
|
||||||
for i := 0; i < len(task.transactions) && i < len(task.uncles); i++ {
|
|
||||||
// Match up a body to any possible completion request
|
|
||||||
var (
|
|
||||||
matched = false
|
|
||||||
uncleHash common.Hash // calculated lazily and reused
|
|
||||||
txnHash common.Hash // calculated lazily and reused
|
|
||||||
)
|
|
||||||
for hash, announce := range f.completing {
|
|
||||||
if f.queued[hash] != nil || announce.origin != task.peer {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if uncleHash == (common.Hash{}) {
|
|
||||||
uncleHash = types.CalcUncleHash(task.uncles[i])
|
|
||||||
}
|
|
||||||
if uncleHash != announce.header.UncleHash {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if txnHash == (common.Hash{}) {
|
|
||||||
txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil))
|
|
||||||
}
|
|
||||||
if txnHash != announce.header.TxHash {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Mark the body matched, reassemble if still unknown
|
|
||||||
matched = true
|
|
||||||
if f.getBlock(hash) == nil {
|
|
||||||
block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i])
|
|
||||||
block.ReceivedAt = task.time
|
|
||||||
blocks = append(blocks, block)
|
|
||||||
} else {
|
|
||||||
f.forgetHash(hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if matched {
|
|
||||||
task.transactions = append(task.transactions[:i], task.transactions[i+1:]...)
|
|
||||||
task.uncles = append(task.uncles[:i], task.uncles[i+1:]...)
|
|
||||||
i--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bodyFilterOutMeter.Mark(int64(len(task.transactions)))
|
|
||||||
select {
|
|
||||||
case filter <- task:
|
|
||||||
case <-f.quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Schedule the retrieved blocks for ordered import
|
|
||||||
for _, block := range blocks {
|
|
||||||
if announce := f.completing[block.Hash()]; announce != nil {
|
|
||||||
f.enqueue(announce.origin, nil, block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rescheduleFetch resets the specified fetch timer to the next blockAnnounce timeout.
|
|
||||||
func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) {
|
|
||||||
// Short circuit if no blocks are announced
|
|
||||||
if len(f.announced) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Schedule announcement retrieval quickly for light mode
|
|
||||||
// since server won't send any headers to client.
|
|
||||||
if f.light {
|
|
||||||
fetch.Reset(lightTimeout)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Otherwise find the earliest expiring announcement
|
|
||||||
earliest := time.Now()
|
|
||||||
for _, announces := range f.announced {
|
|
||||||
if earliest.After(announces[0].time) {
|
|
||||||
earliest = announces[0].time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetch.Reset(arriveTimeout - time.Since(earliest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// rescheduleComplete resets the specified completion timer to the next fetch timeout.
|
|
||||||
func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) {
|
|
||||||
// Short circuit if no headers are fetched
|
|
||||||
if len(f.fetched) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Otherwise find the earliest expiring announcement
|
|
||||||
earliest := time.Now()
|
|
||||||
for _, announces := range f.fetched {
|
|
||||||
if earliest.After(announces[0].time) {
|
|
||||||
earliest = announces[0].time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
complete.Reset(gatherSlack - time.Since(earliest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// enqueue schedules a new header or block import operation, if the component
|
|
||||||
// to be imported has not yet been seen.
|
|
||||||
func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.Block) {
|
|
||||||
var (
|
|
||||||
hash common.Hash
|
|
||||||
number uint64
|
|
||||||
)
|
|
||||||
if header != nil {
|
|
||||||
hash, number = header.Hash(), header.Number.Uint64()
|
|
||||||
} else {
|
|
||||||
hash, number = block.Hash(), block.NumberU64()
|
|
||||||
}
|
|
||||||
// Ensure the peer isn't DOSing us
|
|
||||||
count := f.queues[peer] + 1
|
|
||||||
if count > blockLimit {
|
|
||||||
log.Debug("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit)
|
|
||||||
blockBroadcastDOSMeter.Mark(1)
|
|
||||||
f.forgetHash(hash)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Discard any past or too distant blocks
|
|
||||||
if dist := int64(number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
|
|
||||||
log.Debug("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist)
|
|
||||||
blockBroadcastDropMeter.Mark(1)
|
|
||||||
f.forgetHash(hash)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Schedule the block for future importing
|
|
||||||
if _, ok := f.queued[hash]; !ok {
|
|
||||||
op := &blockOrHeaderInject{origin: peer}
|
|
||||||
if header != nil {
|
|
||||||
op.header = header
|
|
||||||
} else {
|
|
||||||
op.block = block
|
|
||||||
}
|
|
||||||
f.queues[peer] = count
|
|
||||||
f.queued[hash] = op
|
|
||||||
f.queue.Push(op, -int64(number))
|
|
||||||
if f.queueChangeHook != nil {
|
|
||||||
f.queueChangeHook(hash, true)
|
|
||||||
}
|
|
||||||
log.Debug("Queued delivered header or block", "peer", peer, "number", number, "hash", hash, "queued", f.queue.Size())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// importHeaders spawns a new goroutine to run a header insertion into the chain.
|
|
||||||
// If the header's number is at the same height as the current import phase, it
|
|
||||||
// updates the phase states accordingly.
|
|
||||||
func (f *BlockFetcher) importHeaders(peer string, header *types.Header) {
|
|
||||||
hash := header.Hash()
|
|
||||||
log.Debug("Importing propagated header", "peer", peer, "number", header.Number, "hash", hash)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() { f.done <- hash }()
|
|
||||||
// If the parent's unknown, abort insertion
|
|
||||||
parent := f.getHeader(header.ParentHash)
|
|
||||||
if parent == nil {
|
|
||||||
log.Debug("Unknown parent of propagated header", "peer", peer, "number", header.Number, "hash", hash, "parent", header.ParentHash)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Validate the header and if something went wrong, drop the peer
|
|
||||||
if err := f.verifyHeader(header); err != nil && err != consensus.ErrFutureBlock {
|
|
||||||
log.Debug("Propagated header verification failed", "peer", peer, "number", header.Number, "hash", hash, "err", err)
|
|
||||||
f.dropPeer(peer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Run the actual import and log any issues
|
|
||||||
if _, err := f.insertHeaders([]*types.Header{header}); err != nil {
|
|
||||||
log.Debug("Propagated header import failed", "peer", peer, "number", header.Number, "hash", hash, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Invoke the testing hook if needed
|
|
||||||
if f.importedHook != nil {
|
|
||||||
f.importedHook(header, nil)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// importBlocks spawns a new goroutine to run a block insertion into the chain. If the
|
|
||||||
// block's number is at the same height as the current import phase, it updates
|
|
||||||
// the phase states accordingly.
|
|
||||||
func (f *BlockFetcher) importBlocks(peer string, block *types.Block) {
|
|
||||||
hash := block.Hash()
|
|
||||||
|
|
||||||
// Run the import on a new thread
|
|
||||||
log.Debug("Importing propagated block", "peer", peer, "number", block.Number(), "hash", hash)
|
|
||||||
go func() {
|
|
||||||
defer func() { f.done <- hash }()
|
|
||||||
|
|
||||||
// If the parent's unknown, abort insertion
|
|
||||||
parent := f.getBlock(block.ParentHash())
|
|
||||||
if parent == nil {
|
|
||||||
log.Debug("Unknown parent of propagated block", "peer", peer, "number", block.Number(), "hash", hash, "parent", block.ParentHash())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Quickly validate the header and propagate the block if it passes
|
|
||||||
switch err := f.verifyHeader(block.Header()); err {
|
|
||||||
case nil:
|
|
||||||
// All ok, quickly propagate to our peers
|
|
||||||
blockBroadcastOutTimer.UpdateSince(block.ReceivedAt)
|
|
||||||
go f.broadcastBlock(block, true)
|
|
||||||
|
|
||||||
case consensus.ErrFutureBlock:
|
|
||||||
// Weird future block, don't fail, but neither propagate
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Something went very wrong, drop the peer
|
|
||||||
log.Debug("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.insertChain(types.Blocks{block}); err != nil {
|
|
||||||
log.Debug("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// If import succeeded, broadcast the block
|
|
||||||
blockAnnounceOutTimer.UpdateSince(block.ReceivedAt)
|
|
||||||
go f.broadcastBlock(block, false)
|
|
||||||
|
|
||||||
// Invoke the testing hook if needed
|
|
||||||
if f.importedHook != nil {
|
|
||||||
f.importedHook(nil, block)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// forgetHash removes all traces of a block announcement from the fetcher's
|
|
||||||
// internal state.
|
|
||||||
func (f *BlockFetcher) forgetHash(hash common.Hash) {
|
|
||||||
// Remove all pending announces and decrement DOS counters
|
|
||||||
if announceMap, ok := f.announced[hash]; ok {
|
|
||||||
for _, announce := range announceMap {
|
|
||||||
f.announces[announce.origin]--
|
|
||||||
if f.announces[announce.origin] <= 0 {
|
|
||||||
delete(f.announces, announce.origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(f.announced, hash)
|
|
||||||
if f.announceChangeHook != nil {
|
|
||||||
f.announceChangeHook(hash, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remove any pending fetches and decrement the DOS counters
|
|
||||||
if announce := f.fetching[hash]; announce != nil {
|
|
||||||
f.announces[announce.origin]--
|
|
||||||
if f.announces[announce.origin] <= 0 {
|
|
||||||
delete(f.announces, announce.origin)
|
|
||||||
}
|
|
||||||
delete(f.fetching, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any pending completion requests and decrement the DOS counters
|
|
||||||
for _, announce := range f.fetched[hash] {
|
|
||||||
f.announces[announce.origin]--
|
|
||||||
if f.announces[announce.origin] <= 0 {
|
|
||||||
delete(f.announces, announce.origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(f.fetched, hash)
|
|
||||||
|
|
||||||
// Remove any pending completions and decrement the DOS counters
|
|
||||||
if announce := f.completing[hash]; announce != nil {
|
|
||||||
f.announces[announce.origin]--
|
|
||||||
if f.announces[announce.origin] <= 0 {
|
|
||||||
delete(f.announces, announce.origin)
|
|
||||||
}
|
|
||||||
delete(f.completing, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// forgetBlock removes all traces of a queued block from the fetcher's internal
|
|
||||||
// state.
|
|
||||||
func (f *BlockFetcher) forgetBlock(hash common.Hash) {
|
|
||||||
if insert := f.queued[hash]; insert != nil {
|
|
||||||
f.queues[insert.origin]--
|
|
||||||
if f.queues[insert.origin] == 0 {
|
|
||||||
delete(f.queues, insert.origin)
|
|
||||||
}
|
|
||||||
delete(f.queued, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,949 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// 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 fetcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
testdb = rawdb.NewMemoryDatabase()
|
|
||||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
||||||
testAddress = crypto.PubkeyToAddress(testKey.PublicKey)
|
|
||||||
gspec = &core.Genesis{
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
Alloc: types.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}},
|
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
||||||
}
|
|
||||||
genesis = gspec.MustCommit(testdb, triedb.NewDatabase(testdb, triedb.HashDefaults))
|
|
||||||
unknownBlock = types.NewBlock(&types.Header{Root: types.EmptyRootHash, GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
// makeChain creates a chain of n blocks starting at and including parent.
|
|
||||||
// the returned hash chain is ordered head->parent. In addition, every 3rd block
|
|
||||||
// contains a transaction and every 5th an uncle to allow testing correct block
|
|
||||||
// reassembly.
|
|
||||||
func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) {
|
|
||||||
blocks, _ := core.GenerateChain(gspec.Config, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) {
|
|
||||||
block.SetCoinbase(common.Address{seed})
|
|
||||||
|
|
||||||
// If the block number is multiple of 3, send a bonus transaction to the miner
|
|
||||||
if parent == genesis && i%3 == 0 {
|
|
||||||
signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp())
|
|
||||||
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
block.AddTx(tx)
|
|
||||||
}
|
|
||||||
// If the block number is a multiple of 5, add a bonus uncle to the block
|
|
||||||
if i > 0 && i%5 == 0 {
|
|
||||||
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
hashes := make([]common.Hash, n+1)
|
|
||||||
hashes[len(hashes)-1] = parent.Hash()
|
|
||||||
blockm := make(map[common.Hash]*types.Block, n+1)
|
|
||||||
blockm[parent.Hash()] = parent
|
|
||||||
for i, b := range blocks {
|
|
||||||
hashes[len(hashes)-i-2] = b.Hash()
|
|
||||||
blockm[b.Hash()] = b
|
|
||||||
}
|
|
||||||
return hashes, blockm
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetcherTester is a test simulator for mocking out local block chain.
|
|
||||||
type fetcherTester struct {
|
|
||||||
fetcher *BlockFetcher
|
|
||||||
|
|
||||||
hashes []common.Hash // Hash chain belonging to the tester
|
|
||||||
headers map[common.Hash]*types.Header // Headers belonging to the tester
|
|
||||||
blocks map[common.Hash]*types.Block // Blocks belonging to the tester
|
|
||||||
drops map[string]bool // Map of peers dropped by the fetcher
|
|
||||||
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTester creates a new fetcher test mocker.
|
|
||||||
func newTester(light bool) *fetcherTester {
|
|
||||||
tester := &fetcherTester{
|
|
||||||
hashes: []common.Hash{genesis.Hash()},
|
|
||||||
headers: map[common.Hash]*types.Header{genesis.Hash(): genesis.Header()},
|
|
||||||
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
|
|
||||||
drops: make(map[string]bool),
|
|
||||||
}
|
|
||||||
tester.fetcher = NewBlockFetcher(light, tester.getHeader, tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertHeaders, tester.insertChain, tester.dropPeer)
|
|
||||||
tester.fetcher.Start()
|
|
||||||
|
|
||||||
return tester
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHeader retrieves a header from the tester's block chain.
|
|
||||||
func (f *fetcherTester) getHeader(hash common.Hash) *types.Header {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
|
|
||||||
return f.headers[hash]
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBlock retrieves a block from the tester's block chain.
|
|
||||||
func (f *fetcherTester) getBlock(hash common.Hash) *types.Block {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
|
|
||||||
return f.blocks[hash]
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyHeader is a nop placeholder for the block header verification.
|
|
||||||
func (f *fetcherTester) verifyHeader(header *types.Header) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// broadcastBlock is a nop placeholder for the block broadcasting.
|
|
||||||
func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// chainHeight retrieves the current height (block number) of the chain.
|
|
||||||
func (f *fetcherTester) chainHeight() uint64 {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
|
|
||||||
if f.fetcher.light {
|
|
||||||
return f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64()
|
|
||||||
}
|
|
||||||
return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64()
|
|
||||||
}
|
|
||||||
|
|
||||||
// insertChain injects a new headers into the simulated chain.
|
|
||||||
func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
|
|
||||||
for i, header := range headers {
|
|
||||||
// Make sure the parent in known
|
|
||||||
if _, ok := f.headers[header.ParentHash]; !ok {
|
|
||||||
return i, errors.New("unknown parent")
|
|
||||||
}
|
|
||||||
// Discard any new blocks if the same height already exists
|
|
||||||
if header.Number.Uint64() <= f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() {
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
// Otherwise build our current chain
|
|
||||||
f.hashes = append(f.hashes, header.Hash())
|
|
||||||
f.headers[header.Hash()] = header
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// insertChain injects a new blocks into the simulated chain.
|
|
||||||
func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
|
|
||||||
for i, block := range blocks {
|
|
||||||
// Make sure the parent in known
|
|
||||||
if _, ok := f.blocks[block.ParentHash()]; !ok {
|
|
||||||
return i, errors.New("unknown parent")
|
|
||||||
}
|
|
||||||
// Discard any new blocks if the same height already exists
|
|
||||||
if block.NumberU64() <= f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() {
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
// Otherwise build our current chain
|
|
||||||
f.hashes = append(f.hashes, block.Hash())
|
|
||||||
f.blocks[block.Hash()] = block
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dropPeer is an emulator for the peer removal, simply accumulating the various
|
|
||||||
// peers dropped by the fetcher.
|
|
||||||
func (f *fetcherTester) dropPeer(peer string) {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
|
|
||||||
f.drops[peer] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeHeaderFetcher retrieves a block header fetcher associated with a simulated peer.
|
|
||||||
func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn {
|
|
||||||
closure := make(map[common.Hash]*types.Block)
|
|
||||||
for hash, block := range blocks {
|
|
||||||
closure[hash] = block
|
|
||||||
}
|
|
||||||
// Create a function that return a header from the closure
|
|
||||||
return func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
|
||||||
// Gather the blocks to return
|
|
||||||
headers := make([]*types.Header, 0, 1)
|
|
||||||
if block, ok := closure[hash]; ok {
|
|
||||||
headers = append(headers, block.Header())
|
|
||||||
}
|
|
||||||
// Return on a new thread
|
|
||||||
req := ð.Request{
|
|
||||||
Peer: peer,
|
|
||||||
}
|
|
||||||
res := ð.Response{
|
|
||||||
Req: req,
|
|
||||||
Res: (*eth.BlockHeadersRequest)(&headers),
|
|
||||||
Time: drift,
|
|
||||||
Done: make(chan error, 1), // Ignore the returned status
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
sink <- res
|
|
||||||
}()
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeBodyFetcher retrieves a block body fetcher associated with a simulated peer.
|
|
||||||
func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) bodyRequesterFn {
|
|
||||||
closure := make(map[common.Hash]*types.Block)
|
|
||||||
for hash, block := range blocks {
|
|
||||||
closure[hash] = block
|
|
||||||
}
|
|
||||||
// Create a function that returns blocks from the closure
|
|
||||||
return func(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
|
||||||
// Gather the block bodies to return
|
|
||||||
transactions := make([][]*types.Transaction, 0, len(hashes))
|
|
||||||
uncles := make([][]*types.Header, 0, len(hashes))
|
|
||||||
|
|
||||||
for _, hash := range hashes {
|
|
||||||
if block, ok := closure[hash]; ok {
|
|
||||||
transactions = append(transactions, block.Transactions())
|
|
||||||
uncles = append(uncles, block.Uncles())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return on a new thread
|
|
||||||
bodies := make([]*eth.BlockBody, len(transactions))
|
|
||||||
for i, txs := range transactions {
|
|
||||||
bodies[i] = ð.BlockBody{
|
|
||||||
Transactions: txs,
|
|
||||||
Uncles: uncles[i],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
req := ð.Request{
|
|
||||||
Peer: peer,
|
|
||||||
}
|
|
||||||
res := ð.Response{
|
|
||||||
Req: req,
|
|
||||||
Res: (*eth.BlockBodiesResponse)(&bodies),
|
|
||||||
Time: drift,
|
|
||||||
Done: make(chan error, 1), // Ignore the returned status
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
sink <- res
|
|
||||||
}()
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyFetchingEvent verifies that one single event arrive on a fetching channel.
|
|
||||||
func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if arrive {
|
|
||||||
select {
|
|
||||||
case <-fetching:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("fetching timeout")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case <-fetching:
|
|
||||||
t.Fatalf("fetching invoked")
|
|
||||||
case <-time.After(10 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyCompletingEvent verifies that one single event arrive on an completing channel.
|
|
||||||
func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive bool) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if arrive {
|
|
||||||
select {
|
|
||||||
case <-completing:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("completing timeout")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case <-completing:
|
|
||||||
t.Fatalf("completing invoked")
|
|
||||||
case <-time.After(10 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyImportEvent verifies that one single event arrive on an import channel.
|
|
||||||
func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if arrive {
|
|
||||||
select {
|
|
||||||
case <-imported:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("import timeout")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case <-imported:
|
|
||||||
t.Fatalf("import invoked")
|
|
||||||
case <-time.After(20 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyImportCount verifies that exactly count number of events arrive on an
|
|
||||||
// import hook channel.
|
|
||||||
func verifyImportCount(t *testing.T, imported chan interface{}, count int) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
select {
|
|
||||||
case <-imported:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("block %d: import timeout", i+1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
verifyImportDone(t, imported)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyImportDone verifies that no more events are arriving on an import channel.
|
|
||||||
func verifyImportDone(t *testing.T, imported chan interface{}) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-imported:
|
|
||||||
t.Fatalf("extra block imported")
|
|
||||||
case <-time.After(50 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyChainHeight verifies the chain height is as expected.
|
|
||||||
func verifyChainHeight(t *testing.T, fetcher *fetcherTester, height uint64) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if fetcher.chainHeight() != height {
|
|
||||||
t.Fatalf("chain height mismatch, got %d, want %d", fetcher.chainHeight(), height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that a fetcher accepts block/header announcements and initiates retrievals
|
|
||||||
// for them, successfully importing into the local chain.
|
|
||||||
func TestFullSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, false) }
|
|
||||||
func TestLightSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, true) }
|
|
||||||
|
|
||||||
func testSequentialAnnouncements(t *testing.T, light bool) {
|
|
||||||
// Create a chain of blocks to import
|
|
||||||
targetBlocks := 4 * hashLimit
|
|
||||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
|
||||||
|
|
||||||
tester := newTester(light)
|
|
||||||
defer tester.fetcher.Stop()
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
|
||||||
|
|
||||||
// Iteratively announce blocks until all are imported
|
|
||||||
imported := make(chan interface{})
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
|
|
||||||
if light {
|
|
||||||
if header == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty header")
|
|
||||||
}
|
|
||||||
imported <- header
|
|
||||||
} else {
|
|
||||||
if block == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty block")
|
|
||||||
}
|
|
||||||
imported <- block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
verifyChainHeight(t, tester, uint64(len(hashes)-1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that if blocks are announced by multiple peers (or even the same buggy
|
|
||||||
// peer), they will only get downloaded at most once.
|
|
||||||
func TestFullConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, false) }
|
|
||||||
func TestLightConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, true) }
|
|
||||||
|
|
||||||
func testConcurrentAnnouncements(t *testing.T, light bool) {
|
|
||||||
// Create a chain of blocks to import
|
|
||||||
targetBlocks := 4 * hashLimit
|
|
||||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
|
||||||
|
|
||||||
// Assemble a tester with a built in counter for the requests
|
|
||||||
tester := newTester(light)
|
|
||||||
firstHeaderFetcher := tester.makeHeaderFetcher("first", blocks, -gatherSlack)
|
|
||||||
firstBodyFetcher := tester.makeBodyFetcher("first", blocks, 0)
|
|
||||||
secondHeaderFetcher := tester.makeHeaderFetcher("second", blocks, -gatherSlack)
|
|
||||||
secondBodyFetcher := tester.makeBodyFetcher("second", blocks, 0)
|
|
||||||
|
|
||||||
var counter atomic.Uint32
|
|
||||||
firstHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
|
||||||
counter.Add(1)
|
|
||||||
return firstHeaderFetcher(hash, sink)
|
|
||||||
}
|
|
||||||
secondHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
|
||||||
counter.Add(1)
|
|
||||||
return secondHeaderFetcher(hash, sink)
|
|
||||||
}
|
|
||||||
// Iteratively announce blocks until all are imported
|
|
||||||
imported := make(chan interface{})
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
|
|
||||||
if light {
|
|
||||||
if header == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty header")
|
|
||||||
}
|
|
||||||
imported <- header
|
|
||||||
} else {
|
|
||||||
if block == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty block")
|
|
||||||
}
|
|
||||||
imported <- block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := len(hashes) - 2; i >= 0; i-- {
|
|
||||||
tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher)
|
|
||||||
tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher)
|
|
||||||
tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), secondHeaderWrapper, secondBodyFetcher)
|
|
||||||
verifyImportEvent(t, imported, true)
|
|
||||||
}
|
|
||||||
verifyImportDone(t, imported)
|
|
||||||
|
|
||||||
// Make sure no blocks were retrieved twice
|
|
||||||
if c := int(counter.Load()); c != targetBlocks {
|
|
||||||
t.Fatalf("retrieval count mismatch: have %v, want %v", c, targetBlocks)
|
|
||||||
}
|
|
||||||
verifyChainHeight(t, tester, uint64(len(hashes)-1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that announcements arriving while a previous is being fetched still
|
|
||||||
// results in a valid import.
|
|
||||||
func TestFullOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, false) }
|
|
||||||
func TestLightOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, true) }
|
|
||||||
|
|
||||||
func testOverlappingAnnouncements(t *testing.T, light bool) {
|
|
||||||
// Create a chain of blocks to import
|
|
||||||
targetBlocks := 4 * hashLimit
|
|
||||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
|
||||||
|
|
||||||
tester := newTester(light)
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
|
||||||
|
|
||||||
// Iteratively announce blocks, but overlap them continuously
|
|
||||||
overlap := 16
|
|
||||||
imported := make(chan interface{}, len(hashes)-1)
|
|
||||||
for i := 0; i < overlap; i++ {
|
|
||||||
imported <- nil
|
|
||||||
}
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
|
|
||||||
if light {
|
|
||||||
if header == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty header")
|
|
||||||
}
|
|
||||||
imported <- header
|
|
||||||
} else {
|
|
||||||
if block == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty block")
|
|
||||||
}
|
|
||||||
imported <- block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
select {
|
|
||||||
case <-imported:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("block %d: import timeout", len(hashes)-i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Wait for all the imports to complete and check count
|
|
||||||
verifyImportCount(t, imported, overlap)
|
|
||||||
verifyChainHeight(t, tester, uint64(len(hashes)-1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that announces already being retrieved will not be duplicated.
|
|
||||||
func TestFullPendingDeduplication(t *testing.T) { testPendingDeduplication(t, false) }
|
|
||||||
func TestLightPendingDeduplication(t *testing.T) { testPendingDeduplication(t, true) }
|
|
||||||
|
|
||||||
func testPendingDeduplication(t *testing.T, light bool) {
|
|
||||||
// Create a hash and corresponding block
|
|
||||||
hashes, blocks := makeChain(1, 0, genesis)
|
|
||||||
|
|
||||||
// Assemble a tester with a built in counter and delayed fetcher
|
|
||||||
tester := newTester(light)
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("repeater", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("repeater", blocks, 0)
|
|
||||||
|
|
||||||
delay := 50 * time.Millisecond
|
|
||||||
var counter atomic.Uint32
|
|
||||||
headerWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
|
||||||
counter.Add(1)
|
|
||||||
|
|
||||||
// Simulate a long running fetch
|
|
||||||
resink := make(chan *eth.Response)
|
|
||||||
req, err := headerFetcher(hash, resink)
|
|
||||||
if err == nil {
|
|
||||||
go func() {
|
|
||||||
res := <-resink
|
|
||||||
time.Sleep(delay)
|
|
||||||
sink <- res
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return req, err
|
|
||||||
}
|
|
||||||
checkNonExist := func() bool {
|
|
||||||
return tester.getBlock(hashes[0]) == nil
|
|
||||||
}
|
|
||||||
if light {
|
|
||||||
checkNonExist = func() bool {
|
|
||||||
return tester.getHeader(hashes[0]) == nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Announce the same block many times until it's fetched (wait for any pending ops)
|
|
||||||
for checkNonExist() {
|
|
||||||
tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
}
|
|
||||||
time.Sleep(delay)
|
|
||||||
|
|
||||||
// Check that all blocks were imported and none fetched twice
|
|
||||||
if c := counter.Load(); c != 1 {
|
|
||||||
t.Fatalf("retrieval count mismatch: have %v, want %v", c, 1)
|
|
||||||
}
|
|
||||||
verifyChainHeight(t, tester, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that announcements retrieved in a random order are cached and eventually
|
|
||||||
// imported when all the gaps are filled in.
|
|
||||||
func TestFullRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, false) }
|
|
||||||
func TestLightRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, true) }
|
|
||||||
|
|
||||||
func testRandomArrivalImport(t *testing.T, light bool) {
|
|
||||||
// Create a chain of blocks to import, and choose one to delay
|
|
||||||
targetBlocks := maxQueueDist
|
|
||||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
|
||||||
skip := targetBlocks / 2
|
|
||||||
|
|
||||||
tester := newTester(light)
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
|
||||||
|
|
||||||
// Iteratively announce blocks, skipping one entry
|
|
||||||
imported := make(chan interface{}, len(hashes)-1)
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
|
|
||||||
if light {
|
|
||||||
if header == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty header")
|
|
||||||
}
|
|
||||||
imported <- header
|
|
||||||
} else {
|
|
||||||
if block == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty block")
|
|
||||||
}
|
|
||||||
imported <- block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := len(hashes) - 1; i >= 0; i-- {
|
|
||||||
if i != skip {
|
|
||||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Finally announce the skipped entry and check full import
|
|
||||||
tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
|
||||||
verifyImportCount(t, imported, len(hashes)-1)
|
|
||||||
verifyChainHeight(t, tester, uint64(len(hashes)-1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that direct block enqueues (due to block propagation vs. hash announce)
|
|
||||||
// are correctly schedule, filling and import queue gaps.
|
|
||||||
func TestQueueGapFill(t *testing.T) {
|
|
||||||
// Create a chain of blocks to import, and choose one to not announce at all
|
|
||||||
targetBlocks := maxQueueDist
|
|
||||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
|
||||||
skip := targetBlocks / 2
|
|
||||||
|
|
||||||
tester := newTester(false)
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
|
||||||
|
|
||||||
// Iteratively announce blocks, skipping one entry
|
|
||||||
imported := make(chan interface{}, len(hashes)-1)
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
|
|
||||||
|
|
||||||
for i := len(hashes) - 1; i >= 0; i-- {
|
|
||||||
if i != skip {
|
|
||||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fill the missing block directly as if propagated
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[skip]])
|
|
||||||
verifyImportCount(t, imported, len(hashes)-1)
|
|
||||||
verifyChainHeight(t, tester, uint64(len(hashes)-1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that blocks arriving from various sources (multiple propagations, hash
|
|
||||||
// announces, etc) do not get scheduled for import multiple times.
|
|
||||||
func TestImportDeduplication(t *testing.T) {
|
|
||||||
// Create two blocks to import (one for duplication, the other for stalling)
|
|
||||||
hashes, blocks := makeChain(2, 0, genesis)
|
|
||||||
|
|
||||||
// Create the tester and wrap the importer with a counter
|
|
||||||
tester := newTester(false)
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
|
||||||
|
|
||||||
var counter atomic.Uint32
|
|
||||||
tester.fetcher.insertChain = func(blocks types.Blocks) (int, error) {
|
|
||||||
counter.Add(uint32(len(blocks)))
|
|
||||||
return tester.insertChain(blocks)
|
|
||||||
}
|
|
||||||
// Instrument the fetching and imported events
|
|
||||||
fetching := make(chan []common.Hash)
|
|
||||||
imported := make(chan interface{}, len(hashes)-1)
|
|
||||||
tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes }
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
|
|
||||||
|
|
||||||
// Announce the duplicating block, wait for retrieval, and also propagate directly
|
|
||||||
tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
|
||||||
<-fetching
|
|
||||||
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[0]])
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[0]])
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[0]])
|
|
||||||
|
|
||||||
// Fill the missing block directly as if propagated, and check import uniqueness
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[1]])
|
|
||||||
verifyImportCount(t, imported, 2)
|
|
||||||
|
|
||||||
if c := counter.Load(); c != 2 {
|
|
||||||
t.Fatalf("import invocation count mismatch: have %v, want %v", c, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that blocks with numbers much lower or higher than out current head get
|
|
||||||
// discarded to prevent wasting resources on useless blocks from faulty peers.
|
|
||||||
func TestDistantPropagationDiscarding(t *testing.T) {
|
|
||||||
// Create a long chain to import and define the discard boundaries
|
|
||||||
hashes, blocks := makeChain(3*maxQueueDist, 0, genesis)
|
|
||||||
head := hashes[len(hashes)/2]
|
|
||||||
|
|
||||||
low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1
|
|
||||||
|
|
||||||
// Create a tester and simulate a head block being the middle of the above chain
|
|
||||||
tester := newTester(false)
|
|
||||||
|
|
||||||
tester.lock.Lock()
|
|
||||||
tester.hashes = []common.Hash{head}
|
|
||||||
tester.blocks = map[common.Hash]*types.Block{head: blocks[head]}
|
|
||||||
tester.lock.Unlock()
|
|
||||||
|
|
||||||
// Ensure that a block with a lower number than the threshold is discarded
|
|
||||||
tester.fetcher.Enqueue("lower", blocks[hashes[low]])
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
if !tester.fetcher.queue.Empty() {
|
|
||||||
t.Fatalf("fetcher queued stale block")
|
|
||||||
}
|
|
||||||
// Ensure that a block with a higher number than the threshold is discarded
|
|
||||||
tester.fetcher.Enqueue("higher", blocks[hashes[high]])
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
if !tester.fetcher.queue.Empty() {
|
|
||||||
t.Fatalf("fetcher queued future block")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that announcements with numbers much lower or higher than out current
|
|
||||||
// head get discarded to prevent wasting resources on useless blocks from faulty
|
|
||||||
// peers.
|
|
||||||
func TestFullDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, false) }
|
|
||||||
func TestLightDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, true) }
|
|
||||||
|
|
||||||
func testDistantAnnouncementDiscarding(t *testing.T, light bool) {
|
|
||||||
// Create a long chain to import and define the discard boundaries
|
|
||||||
hashes, blocks := makeChain(3*maxQueueDist, 0, genesis)
|
|
||||||
head := hashes[len(hashes)/2]
|
|
||||||
|
|
||||||
low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1
|
|
||||||
|
|
||||||
// Create a tester and simulate a head block being the middle of the above chain
|
|
||||||
tester := newTester(light)
|
|
||||||
|
|
||||||
tester.lock.Lock()
|
|
||||||
tester.hashes = []common.Hash{head}
|
|
||||||
tester.headers = map[common.Hash]*types.Header{head: blocks[head].Header()}
|
|
||||||
tester.blocks = map[common.Hash]*types.Block{head: blocks[head]}
|
|
||||||
tester.lock.Unlock()
|
|
||||||
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("lower", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("lower", blocks, 0)
|
|
||||||
|
|
||||||
fetching := make(chan struct{}, 2)
|
|
||||||
tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} }
|
|
||||||
|
|
||||||
// Ensure that a block with a lower number than the threshold is discarded
|
|
||||||
tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
|
||||||
select {
|
|
||||||
case <-time.After(50 * time.Millisecond):
|
|
||||||
case <-fetching:
|
|
||||||
t.Fatalf("fetcher requested stale header")
|
|
||||||
}
|
|
||||||
// Ensure that a block with a higher number than the threshold is discarded
|
|
||||||
tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
|
|
||||||
select {
|
|
||||||
case <-time.After(50 * time.Millisecond):
|
|
||||||
case <-fetching:
|
|
||||||
t.Fatalf("fetcher requested future header")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that peers announcing blocks with invalid numbers (i.e. not matching
|
|
||||||
// the headers provided afterwards) get dropped as malicious.
|
|
||||||
func TestFullInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, false) }
|
|
||||||
func TestLightInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, true) }
|
|
||||||
|
|
||||||
func testInvalidNumberAnnouncement(t *testing.T, light bool) {
|
|
||||||
// Create a single block to import and check numbers against
|
|
||||||
hashes, blocks := makeChain(1, 0, genesis)
|
|
||||||
|
|
||||||
tester := newTester(light)
|
|
||||||
badHeaderFetcher := tester.makeHeaderFetcher("bad", blocks, -gatherSlack)
|
|
||||||
badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0)
|
|
||||||
|
|
||||||
imported := make(chan interface{})
|
|
||||||
announced := make(chan interface{}, 2)
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
|
|
||||||
if light {
|
|
||||||
if header == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty header")
|
|
||||||
}
|
|
||||||
imported <- header
|
|
||||||
} else {
|
|
||||||
if block == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty block")
|
|
||||||
}
|
|
||||||
imported <- block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Announce a block with a bad number, check for immediate drop
|
|
||||||
tester.fetcher.announceChangeHook = func(hash common.Hash, b bool) {
|
|
||||||
announced <- nil
|
|
||||||
}
|
|
||||||
tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher)
|
|
||||||
verifyAnnounce := func() {
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
select {
|
|
||||||
case <-announced:
|
|
||||||
continue
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
t.Fatal("announce timeout")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
verifyAnnounce()
|
|
||||||
verifyImportEvent(t, imported, false)
|
|
||||||
tester.lock.RLock()
|
|
||||||
dropped := tester.drops["bad"]
|
|
||||||
tester.lock.RUnlock()
|
|
||||||
|
|
||||||
if !dropped {
|
|
||||||
t.Fatalf("peer with invalid numbered announcement not dropped")
|
|
||||||
}
|
|
||||||
goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack)
|
|
||||||
goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0)
|
|
||||||
// Make sure a good announcement passes without a drop
|
|
||||||
tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher)
|
|
||||||
verifyAnnounce()
|
|
||||||
verifyImportEvent(t, imported, true)
|
|
||||||
|
|
||||||
tester.lock.RLock()
|
|
||||||
dropped = tester.drops["good"]
|
|
||||||
tester.lock.RUnlock()
|
|
||||||
|
|
||||||
if dropped {
|
|
||||||
t.Fatalf("peer with valid numbered announcement dropped")
|
|
||||||
}
|
|
||||||
verifyImportDone(t, imported)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that if a block is empty (i.e. header only), no body request should be
|
|
||||||
// made, and instead the header should be assembled into a whole block in itself.
|
|
||||||
func TestEmptyBlockShortCircuit(t *testing.T) {
|
|
||||||
// Create a chain of blocks to import
|
|
||||||
hashes, blocks := makeChain(32, 0, genesis)
|
|
||||||
|
|
||||||
tester := newTester(false)
|
|
||||||
defer tester.fetcher.Stop()
|
|
||||||
headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
|
||||||
bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
|
||||||
|
|
||||||
// Add a monitoring hook for all internal events
|
|
||||||
fetching := make(chan []common.Hash)
|
|
||||||
tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes }
|
|
||||||
|
|
||||||
completing := make(chan []common.Hash)
|
|
||||||
tester.fetcher.completingHook = func(hashes []common.Hash) { completing <- hashes }
|
|
||||||
|
|
||||||
imported := make(chan interface{})
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
|
|
||||||
if block == nil {
|
|
||||||
t.Fatalf("Fetcher try to import empty block")
|
|
||||||
}
|
|
||||||
imported <- block
|
|
||||||
}
|
|
||||||
// Iteratively announce blocks until all are imported
|
|
||||||
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)
|
|
||||||
|
|
||||||
// All announces should fetch the header
|
|
||||||
verifyFetchingEvent(t, fetching, true)
|
|
||||||
|
|
||||||
// Only blocks with data contents should request bodies
|
|
||||||
verifyCompletingEvent(t, completing, len(blocks[hashes[i]].Transactions()) > 0 || len(blocks[hashes[i]].Uncles()) > 0)
|
|
||||||
|
|
||||||
// Irrelevant of the construct, import should succeed
|
|
||||||
verifyImportEvent(t, imported, true)
|
|
||||||
}
|
|
||||||
verifyImportDone(t, imported)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that a peer is unable to use unbounded memory with sending infinite
|
|
||||||
// block announcements to a node, but that even in the face of such an attack,
|
|
||||||
// the fetcher remains operational.
|
|
||||||
func TestHashMemoryExhaustionAttack(t *testing.T) {
|
|
||||||
// Create a tester with instrumented import hooks
|
|
||||||
tester := newTester(false)
|
|
||||||
|
|
||||||
imported, announces := make(chan interface{}), atomic.Int32{}
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
|
|
||||||
tester.fetcher.announceChangeHook = func(hash common.Hash, added bool) {
|
|
||||||
if added {
|
|
||||||
announces.Add(1)
|
|
||||||
} else {
|
|
||||||
announces.Add(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create a valid chain and an infinite junk chain
|
|
||||||
targetBlocks := hashLimit + 2*maxQueueDist
|
|
||||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
|
||||||
validHeaderFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
|
|
||||||
validBodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
|
|
||||||
|
|
||||||
attack, _ := makeChain(targetBlocks, 0, unknownBlock)
|
|
||||||
attackerHeaderFetcher := tester.makeHeaderFetcher("attacker", nil, -gatherSlack)
|
|
||||||
attackerBodyFetcher := tester.makeBodyFetcher("attacker", nil, 0)
|
|
||||||
|
|
||||||
// Feed the tester a huge hashset from the attacker, and a limited from the valid peer
|
|
||||||
for i := 0; i < len(attack); i++ {
|
|
||||||
if i < maxQueueDist {
|
|
||||||
tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher)
|
|
||||||
}
|
|
||||||
tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher)
|
|
||||||
}
|
|
||||||
if count := announces.Load(); count != hashLimit+maxQueueDist {
|
|
||||||
t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist)
|
|
||||||
}
|
|
||||||
// Wait for fetches to complete
|
|
||||||
verifyImportCount(t, imported, maxQueueDist)
|
|
||||||
|
|
||||||
// Feed the remaining valid hashes to ensure DOS protection state remains clean
|
|
||||||
for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- {
|
|
||||||
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher)
|
|
||||||
verifyImportEvent(t, imported, true)
|
|
||||||
}
|
|
||||||
verifyImportDone(t, imported)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that blocks sent to the fetcher (either through propagation or via hash
|
|
||||||
// announces and retrievals) don't pile up indefinitely, exhausting available
|
|
||||||
// system memory.
|
|
||||||
func TestBlockMemoryExhaustionAttack(t *testing.T) {
|
|
||||||
// Create a tester with instrumented import hooks
|
|
||||||
tester := newTester(false)
|
|
||||||
|
|
||||||
imported, enqueued := make(chan interface{}), atomic.Int32{}
|
|
||||||
tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
|
|
||||||
tester.fetcher.queueChangeHook = func(hash common.Hash, added bool) {
|
|
||||||
if added {
|
|
||||||
enqueued.Add(1)
|
|
||||||
} else {
|
|
||||||
enqueued.Add(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create a valid chain and a batch of dangling (but in range) blocks
|
|
||||||
targetBlocks := hashLimit + 2*maxQueueDist
|
|
||||||
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
|
||||||
attack := make(map[common.Hash]*types.Block)
|
|
||||||
for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ {
|
|
||||||
hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock)
|
|
||||||
for _, hash := range hashes[:maxQueueDist-2] {
|
|
||||||
attack[hash] = blocks[hash]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Try to feed all the attacker blocks make sure only a limited batch is accepted
|
|
||||||
for _, block := range attack {
|
|
||||||
tester.fetcher.Enqueue("attacker", block)
|
|
||||||
}
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
if queued := enqueued.Load(); queued != blockLimit {
|
|
||||||
t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit)
|
|
||||||
}
|
|
||||||
// Queue up a batch of valid blocks, and check that a new peer is allowed to do so
|
|
||||||
for i := 0; i < maxQueueDist-1; i++ {
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]])
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
if queued := enqueued.Load(); queued != blockLimit+maxQueueDist-1 {
|
|
||||||
t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1)
|
|
||||||
}
|
|
||||||
// Insert the missing piece (and sanity check the import)
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2]])
|
|
||||||
verifyImportCount(t, imported, maxQueueDist)
|
|
||||||
|
|
||||||
// Insert the remaining blocks in chunks to ensure clean DOS protection
|
|
||||||
for i := maxQueueDist; i < len(hashes)-1; i++ {
|
|
||||||
tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2-i]])
|
|
||||||
verifyImportEvent(t, imported, true)
|
|
||||||
}
|
|
||||||
verifyImportDone(t, imported)
|
|
||||||
}
|
|
||||||
|
|
@ -107,6 +107,8 @@ var (
|
||||||
txFetcherFetchingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/hashes", nil)
|
txFetcherFetchingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/hashes", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errTerminated = errors.New("terminated")
|
||||||
|
|
||||||
// txAnnounce is the notification of the availability of a batch
|
// txAnnounce is the notification of the availability of a batch
|
||||||
// of new transactions in the network.
|
// of new transactions in the network.
|
||||||
type txAnnounce struct {
|
type txAnnounce struct {
|
||||||
|
|
@ -783,7 +785,7 @@ func (f *TxFetcher) loop() {
|
||||||
// rescheduleWait iterates over all the transactions currently in the waitlist
|
// rescheduleWait iterates over all the transactions currently in the waitlist
|
||||||
// and schedules the movement into the fetcher for the earliest.
|
// and schedules the movement into the fetcher for the earliest.
|
||||||
//
|
//
|
||||||
// The method has a granularity of 'gatherSlack', since there's not much point in
|
// The method has a granularity of 'txGatherSlack', since there's not much point in
|
||||||
// spinning over all the transactions just to maybe find one that should trigger
|
// spinning over all the transactions just to maybe find one that should trigger
|
||||||
// a few ms earlier.
|
// a few ms earlier.
|
||||||
func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
|
func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
|
||||||
|
|
@ -796,7 +798,7 @@ func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
|
||||||
for _, instance := range f.waittime {
|
for _, instance := range f.waittime {
|
||||||
if earliest > instance {
|
if earliest > instance {
|
||||||
earliest = instance
|
earliest = instance
|
||||||
if txArriveTimeout-time.Duration(now-earliest) < gatherSlack {
|
if txArriveTimeout-time.Duration(now-earliest) < txGatherSlack {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -809,7 +811,7 @@ func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
|
||||||
// rescheduleTimeout iterates over all the transactions currently in flight and
|
// rescheduleTimeout iterates over all the transactions currently in flight and
|
||||||
// schedules a cleanup run when the first would trigger.
|
// schedules a cleanup run when the first would trigger.
|
||||||
//
|
//
|
||||||
// The method has a granularity of 'gatherSlack', since there's not much point in
|
// The method has a granularity of 'txGatherSlack', since there's not much point in
|
||||||
// spinning over all the transactions just to maybe find one that should trigger
|
// spinning over all the transactions just to maybe find one that should trigger
|
||||||
// a few ms earlier.
|
// a few ms earlier.
|
||||||
//
|
//
|
||||||
|
|
@ -834,7 +836,7 @@ func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{}
|
||||||
}
|
}
|
||||||
if earliest > req.time {
|
if earliest > req.time {
|
||||||
earliest = req.time
|
earliest = req.time
|
||||||
if txFetchTimeout-time.Duration(now-earliest) < gatherSlack {
|
if txFetchTimeout-time.Duration(now-earliest) < txGatherSlack {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,8 +179,6 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool)
|
||||||
}
|
}
|
||||||
case <-rpcSub.Err():
|
case <-rpcSub.Err():
|
||||||
return
|
return
|
||||||
case <-notifier.Closed():
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -241,8 +239,6 @@ func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
|
||||||
notifier.Notify(rpcSub.ID, h)
|
notifier.Notify(rpcSub.ID, h)
|
||||||
case <-rpcSub.Err():
|
case <-rpcSub.Err():
|
||||||
return
|
return
|
||||||
case <-notifier.Closed():
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -278,8 +274,6 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc
|
||||||
}
|
}
|
||||||
case <-rpcSub.Err(): // client send an unsubscribe request
|
case <-rpcSub.Err(): // client send an unsubscribe request
|
||||||
return
|
return
|
||||||
case <-notifier.Closed(): // connection dropped
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ
|
||||||
|
|
||||||
// pendingLogs returns the logs matching the filter criteria within the pending block.
|
// pendingLogs returns the logs matching the filter criteria within the pending block.
|
||||||
func (f *Filter) pendingLogs() []*types.Log {
|
func (f *Filter) pendingLogs() []*types.Log {
|
||||||
block, receipts := f.sys.backend.PendingBlockAndReceipts()
|
block, receipts, _ := f.sys.backend.Pending()
|
||||||
if block == nil || receipts == nil {
|
if block == nil || receipts == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
|
@ -62,7 +63,7 @@ type Backend interface {
|
||||||
GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error)
|
GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error)
|
||||||
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
|
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
|
||||||
GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)
|
GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)
|
||||||
PendingBlockAndReceipts() (*types.Block, types.Receipts)
|
Pending() (*types.Block, types.Receipts, *state.StateDB)
|
||||||
|
|
||||||
CurrentHeader() *types.Header
|
CurrentHeader() *types.Header
|
||||||
ChainConfig() *params.ChainConfig
|
ChainConfig() *params.ChainConfig
|
||||||
|
|
@ -70,7 +71,6 @@ type Backend interface {
|
||||||
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
||||||
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
||||||
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
||||||
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
|
|
||||||
|
|
||||||
BloomStatus() (uint64, uint64)
|
BloomStatus() (uint64, uint64)
|
||||||
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
|
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
|
||||||
|
|
@ -198,20 +198,18 @@ type EventSystem struct {
|
||||||
lastHead *types.Header
|
lastHead *types.Header
|
||||||
|
|
||||||
// Subscriptions
|
// Subscriptions
|
||||||
txsSub event.Subscription // Subscription for new transaction event
|
txsSub event.Subscription // Subscription for new transaction event
|
||||||
logsSub event.Subscription // Subscription for new log event
|
logsSub event.Subscription // Subscription for new log event
|
||||||
rmLogsSub event.Subscription // Subscription for removed log event
|
rmLogsSub event.Subscription // Subscription for removed log event
|
||||||
pendingLogsSub event.Subscription // Subscription for pending log event
|
chainSub event.Subscription // Subscription for new chain event
|
||||||
chainSub event.Subscription // Subscription for new chain event
|
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
install chan *subscription // install filter for event notification
|
install chan *subscription // install filter for event notification
|
||||||
uninstall chan *subscription // remove filter for event notification
|
uninstall chan *subscription // remove filter for event notification
|
||||||
txsCh chan core.NewTxsEvent // Channel to receive new transactions event
|
txsCh chan core.NewTxsEvent // Channel to receive new transactions event
|
||||||
logsCh chan []*types.Log // Channel to receive new log event
|
logsCh chan []*types.Log // Channel to receive new log event
|
||||||
pendingLogsCh chan []*types.Log // Channel to receive new log event
|
rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event
|
||||||
rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event
|
chainCh chan core.ChainEvent // Channel to receive new chain event
|
||||||
chainCh chan core.ChainEvent // Channel to receive new chain event
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEventSystem creates a new manager that listens for event on the given mux,
|
// NewEventSystem creates a new manager that listens for event on the given mux,
|
||||||
|
|
@ -222,16 +220,15 @@ type EventSystem struct {
|
||||||
// or by stopping the given mux.
|
// or by stopping the given mux.
|
||||||
func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem {
|
func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem {
|
||||||
m := &EventSystem{
|
m := &EventSystem{
|
||||||
sys: sys,
|
sys: sys,
|
||||||
backend: sys.backend,
|
backend: sys.backend,
|
||||||
lightMode: lightMode,
|
lightMode: lightMode,
|
||||||
install: make(chan *subscription),
|
install: make(chan *subscription),
|
||||||
uninstall: make(chan *subscription),
|
uninstall: make(chan *subscription),
|
||||||
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
||||||
logsCh: make(chan []*types.Log, logsChanSize),
|
logsCh: make(chan []*types.Log, logsChanSize),
|
||||||
rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize),
|
rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize),
|
||||||
pendingLogsCh: make(chan []*types.Log, logsChanSize),
|
chainCh: make(chan core.ChainEvent, chainEvChanSize),
|
||||||
chainCh: make(chan core.ChainEvent, chainEvChanSize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
|
|
@ -239,10 +236,9 @@ func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem {
|
||||||
m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh)
|
m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh)
|
||||||
m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh)
|
m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh)
|
||||||
m.chainSub = m.backend.SubscribeChainEvent(m.chainCh)
|
m.chainSub = m.backend.SubscribeChainEvent(m.chainCh)
|
||||||
m.pendingLogsSub = m.backend.SubscribePendingLogsEvent(m.pendingLogsCh)
|
|
||||||
|
|
||||||
// Make sure none of the subscriptions are empty
|
// Make sure none of the subscriptions are empty
|
||||||
if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil || m.pendingLogsSub == nil {
|
if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil {
|
||||||
log.Crit("Subscribe for event system failed")
|
log.Crit("Subscribe for event system failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -434,12 +430,12 @@ func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) {
|
func (es *EventSystem) handlePendingLogs(filters filterIndex, logs []*types.Log) {
|
||||||
if len(ev) == 0 {
|
if len(logs) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, f := range filters[PendingLogsSubscription] {
|
for _, f := range filters[PendingLogsSubscription] {
|
||||||
matchedLogs := filterLogs(ev, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
|
matchedLogs := filterLogs(logs, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
|
||||||
if len(matchedLogs) > 0 {
|
if len(matchedLogs) > 0 {
|
||||||
f.logs <- matchedLogs
|
f.logs <- matchedLogs
|
||||||
}
|
}
|
||||||
|
|
@ -550,7 +546,6 @@ func (es *EventSystem) eventLoop() {
|
||||||
es.txsSub.Unsubscribe()
|
es.txsSub.Unsubscribe()
|
||||||
es.logsSub.Unsubscribe()
|
es.logsSub.Unsubscribe()
|
||||||
es.rmLogsSub.Unsubscribe()
|
es.rmLogsSub.Unsubscribe()
|
||||||
es.pendingLogsSub.Unsubscribe()
|
|
||||||
es.chainSub.Unsubscribe()
|
es.chainSub.Unsubscribe()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -567,10 +562,29 @@ func (es *EventSystem) eventLoop() {
|
||||||
es.handleLogs(index, ev)
|
es.handleLogs(index, ev)
|
||||||
case ev := <-es.rmLogsCh:
|
case ev := <-es.rmLogsCh:
|
||||||
es.handleLogs(index, ev.Logs)
|
es.handleLogs(index, ev.Logs)
|
||||||
case ev := <-es.pendingLogsCh:
|
|
||||||
es.handlePendingLogs(index, ev)
|
|
||||||
case ev := <-es.chainCh:
|
case ev := <-es.chainCh:
|
||||||
es.handleChainEvent(index, ev)
|
es.handleChainEvent(index, ev)
|
||||||
|
// If we have no pending log subscription,
|
||||||
|
// we don't need to collect any pending logs.
|
||||||
|
if len(index[PendingLogsSubscription]) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull the pending logs if there is a new chain head.
|
||||||
|
pendingBlock, pendingReceipts, _ := es.backend.Pending()
|
||||||
|
if pendingBlock == nil || pendingReceipts == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pendingBlock.ParentHash() != ev.Block.Hash() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var logs []*types.Log
|
||||||
|
for _, receipt := range pendingReceipts {
|
||||||
|
if len(receipt.Logs) > 0 {
|
||||||
|
logs = append(logs, receipt.Logs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
es.handlePendingLogs(index, logs)
|
||||||
|
|
||||||
case f := <-es.install:
|
case f := <-es.install:
|
||||||
if f.typ == MinedAndPendingLogsSubscription {
|
if f.typ == MinedAndPendingLogsSubscription {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
|
@ -48,7 +49,6 @@ type testBackend struct {
|
||||||
txFeed event.Feed
|
txFeed event.Feed
|
||||||
logsFeed event.Feed
|
logsFeed event.Feed
|
||||||
rmLogsFeed event.Feed
|
rmLogsFeed event.Feed
|
||||||
pendingLogsFeed event.Feed
|
|
||||||
chainFeed event.Feed
|
chainFeed event.Feed
|
||||||
pendingBlock *types.Block
|
pendingBlock *types.Block
|
||||||
pendingReceipts types.Receipts
|
pendingReceipts types.Receipts
|
||||||
|
|
@ -125,8 +125,8 @@ func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash, number uint
|
||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
|
func (b *testBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) {
|
||||||
return b.pendingBlock, b.pendingReceipts
|
return b.pendingBlock, b.pendingReceipts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||||||
|
|
@ -141,10 +141,6 @@ func (b *testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
|
||||||
return b.logsFeed.Subscribe(ch)
|
return b.logsFeed.Subscribe(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return b.pendingLogsFeed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||||||
return b.chainFeed.Subscribe(ch)
|
return b.chainFeed.Subscribe(ch)
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +176,20 @@ func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.Matc
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) setPending(block *types.Block, receipts types.Receipts) {
|
||||||
|
b.pendingBlock = block
|
||||||
|
b.pendingReceipts = receipts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) notifyPending(logs []*types.Log) {
|
||||||
|
genesis := &core.Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
}
|
||||||
|
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, func(i int, b *core.BlockGen) {})
|
||||||
|
b.setPending(blocks[1], []*types.Receipt{{Logs: logs}})
|
||||||
|
b.chainFeed.Send(core.ChainEvent{Block: blocks[0]})
|
||||||
|
}
|
||||||
|
|
||||||
func newTestFilterSystem(t testing.TB, db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
|
func newTestFilterSystem(t testing.TB, db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
|
||||||
backend := &testBackend{db: db}
|
backend := &testBackend{db: db}
|
||||||
sys := NewFilterSystem(backend, cfg)
|
sys := NewFilterSystem(backend, cfg)
|
||||||
|
|
@ -203,7 +213,7 @@ func TestBlockSubscription(t *testing.T) {
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
}
|
}
|
||||||
_, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {})
|
_, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {})
|
||||||
chainEvents = []core.ChainEvent{}
|
chainEvents []core.ChainEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, blk := range chain {
|
for _, blk := range chain {
|
||||||
|
|
@ -386,7 +396,7 @@ func TestLogFilterCreation(t *testing.T) {
|
||||||
{FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false},
|
{FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false},
|
||||||
// from block "higher" than to block
|
// from block "higher" than to block
|
||||||
{FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, false},
|
{FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, false},
|
||||||
// topics more then 4
|
// topics more than 4
|
||||||
{FilterCriteria{Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, false},
|
{FilterCriteria{Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, false},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -546,9 +556,9 @@ func TestLogFilter(t *testing.T) {
|
||||||
if nsend := backend.logsFeed.Send(allLogs); nsend == 0 {
|
if nsend := backend.logsFeed.Send(allLogs); nsend == 0 {
|
||||||
t.Fatal("Logs event not delivered")
|
t.Fatal("Logs event not delivered")
|
||||||
}
|
}
|
||||||
if nsend := backend.pendingLogsFeed.Send(allLogs); nsend == 0 {
|
|
||||||
t.Fatal("Pending logs event not delivered")
|
// set pending logs
|
||||||
}
|
backend.notifyPending(allLogs)
|
||||||
|
|
||||||
for i, tt := range testCases {
|
for i, tt := range testCases {
|
||||||
var fetched []*types.Log
|
var fetched []*types.Log
|
||||||
|
|
@ -754,10 +764,12 @@ func TestPendingLogsSubscription(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// raise events
|
// set pending logs
|
||||||
for _, ev := range allLogs {
|
var flattenLogs []*types.Log
|
||||||
backend.pendingLogsFeed.Send(ev)
|
for _, logs := range allLogs {
|
||||||
|
flattenLogs = append(flattenLogs, logs...)
|
||||||
}
|
}
|
||||||
|
backend.notifyPending(flattenLogs)
|
||||||
|
|
||||||
for i := range testCases {
|
for i := range testCases {
|
||||||
err := <-testCases[i].err
|
err := <-testCases[i].err
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,8 @@ func BenchmarkFilters(b *testing.B) {
|
||||||
|
|
||||||
func TestFilters(t *testing.T) {
|
func TestFilters(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
_, sys = newTestFilterSystem(t, db, Config{})
|
backend, sys = newTestFilterSystem(t, db, Config{})
|
||||||
// Sender account
|
// Sender account
|
||||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
addr = crypto.PubkeyToAddress(key1.PublicKey)
|
addr = crypto.PubkeyToAddress(key1.PublicKey)
|
||||||
|
|
@ -277,8 +277,7 @@ func TestFilters(t *testing.T) {
|
||||||
}), signer, key1)
|
}), signer, key1)
|
||||||
gen.AddTx(tx)
|
gen.AddTx(tx)
|
||||||
})
|
})
|
||||||
sys.backend.(*testBackend).pendingBlock = pchain[0]
|
backend.setPending(pchain[0], preceipts[0])
|
||||||
sys.backend.(*testBackend).pendingReceipts = preceipts[0]
|
|
||||||
|
|
||||||
for i, tc := range []struct {
|
for i, tc := range []struct {
|
||||||
f *Filter
|
f *Filter
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNum
|
||||||
)
|
)
|
||||||
switch reqEnd {
|
switch reqEnd {
|
||||||
case rpc.PendingBlockNumber:
|
case rpc.PendingBlockNumber:
|
||||||
if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil {
|
if pendingBlock, pendingReceipts, _ = oracle.backend.Pending(); pendingBlock != nil {
|
||||||
resolved = pendingBlock.Header()
|
resolved = pendingBlock.Header()
|
||||||
} else {
|
} else {
|
||||||
// Pending block not supported by backend, process only until latest block.
|
// Pending block not supported by backend, process only until latest block.
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -54,7 +55,7 @@ type OracleBackend interface {
|
||||||
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
||||||
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
|
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
|
||||||
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
|
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
|
||||||
PendingBlockAndReceipts() (*types.Block, types.Receipts)
|
Pending() (*types.Block, types.Receipts, *state.StateDB)
|
||||||
ChainConfig() *params.ChainConfig
|
ChainConfig() *params.ChainConfig
|
||||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -97,12 +98,13 @@ func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.
|
||||||
return b.chain.GetReceiptsByHash(hash), nil
|
return b.chain.GetReceiptsByHash(hash), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
|
func (b *testBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) {
|
||||||
if b.pending {
|
if b.pending {
|
||||||
block := b.chain.GetBlockByNumber(testHead + 1)
|
block := b.chain.GetBlockByNumber(testHead + 1)
|
||||||
return block, b.chain.GetReceiptsByHash(block.Hash())
|
state, _ := b.chain.StateAt(block.Root())
|
||||||
|
return block, b.chain.GetReceiptsByHash(block.Hash()), state
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) ChainConfig() *params.ChainConfig {
|
func (b *testBackend) ChainConfig() *params.ChainConfig {
|
||||||
|
|
|
||||||
182
eth/handler.go
182
eth/handler.go
|
|
@ -25,8 +25,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/forkid"
|
"github.com/ethereum/go-ethereum/core/forkid"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -91,7 +89,6 @@ type handlerConfig struct {
|
||||||
Database ethdb.Database // Database for direct sync insertions
|
Database ethdb.Database // Database for direct sync insertions
|
||||||
Chain *core.BlockChain // Blockchain to serve data from
|
Chain *core.BlockChain // Blockchain to serve data from
|
||||||
TxPool txPool // Transaction pool to propagate from
|
TxPool txPool // Transaction pool to propagate from
|
||||||
Merger *consensus.Merger // The manager for eth1/2 transition
|
|
||||||
Network uint64 // Network identifier to advertise
|
Network uint64 // Network identifier to advertise
|
||||||
Sync downloader.SyncMode // Whether to snap or full sync
|
Sync downloader.SyncMode // Whether to snap or full sync
|
||||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||||
|
|
@ -112,24 +109,20 @@ type handler struct {
|
||||||
chain *core.BlockChain
|
chain *core.BlockChain
|
||||||
maxPeers int
|
maxPeers int
|
||||||
|
|
||||||
downloader *downloader.Downloader
|
downloader *downloader.Downloader
|
||||||
blockFetcher *fetcher.BlockFetcher
|
txFetcher *fetcher.TxFetcher
|
||||||
txFetcher *fetcher.TxFetcher
|
peers *peerSet
|
||||||
peers *peerSet
|
|
||||||
merger *consensus.Merger
|
|
||||||
|
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
txsCh chan core.NewTxsEvent
|
txsCh chan core.NewTxsEvent
|
||||||
txsSub event.Subscription
|
txsSub event.Subscription
|
||||||
minedBlockSub *event.TypeMuxSubscription
|
|
||||||
|
|
||||||
requiredBlocks map[uint64]common.Hash
|
requiredBlocks map[uint64]common.Hash
|
||||||
|
|
||||||
// channels for fetcher, syncer, txsyncLoop
|
// channels for fetcher, syncer, txsyncLoop
|
||||||
quitSync chan struct{}
|
quitSync chan struct{}
|
||||||
|
|
||||||
chainSync *chainSyncer
|
wg sync.WaitGroup
|
||||||
wg sync.WaitGroup
|
|
||||||
|
|
||||||
handlerStartCh chan struct{}
|
handlerStartCh chan struct{}
|
||||||
handlerDoneCh chan struct{}
|
handlerDoneCh chan struct{}
|
||||||
|
|
@ -150,7 +143,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
txpool: config.TxPool,
|
txpool: config.TxPool,
|
||||||
chain: config.Chain,
|
chain: config.Chain,
|
||||||
peers: newPeerSet(),
|
peers: newPeerSet(),
|
||||||
merger: config.Merger,
|
|
||||||
requiredBlocks: config.RequiredBlocks,
|
requiredBlocks: config.RequiredBlocks,
|
||||||
quitSync: make(chan struct{}),
|
quitSync: make(chan struct{}),
|
||||||
handlerDoneCh: make(chan struct{}),
|
handlerDoneCh: make(chan struct{}),
|
||||||
|
|
@ -190,92 +182,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
}
|
}
|
||||||
// Construct the downloader (long sync)
|
// Construct the downloader (long sync)
|
||||||
h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, h.enableSyncedFeatures)
|
h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, h.enableSyncedFeatures)
|
||||||
if ttd := h.chain.Config().TerminalTotalDifficulty; ttd != nil {
|
|
||||||
if h.chain.Config().TerminalTotalDifficultyPassed {
|
|
||||||
log.Info("Chain post-merge, sync via beacon client")
|
|
||||||
} else {
|
|
||||||
head := h.chain.CurrentBlock()
|
|
||||||
if td := h.chain.GetTd(head.Hash(), head.Number.Uint64()); td.Cmp(ttd) >= 0 {
|
|
||||||
log.Info("Chain post-TTD, sync via beacon client")
|
|
||||||
} else {
|
|
||||||
log.Warn("Chain pre-merge, sync via PoW (ensure beacon client is ready)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if h.chain.Config().TerminalTotalDifficultyPassed {
|
|
||||||
log.Error("Chain configured post-merge, but without TTD. Are you debugging sync?")
|
|
||||||
}
|
|
||||||
// Construct the fetcher (short sync)
|
|
||||||
validator := func(header *types.Header) error {
|
|
||||||
// All the block fetcher activities should be disabled
|
|
||||||
// after the transition. Print the warning log.
|
|
||||||
if h.merger.PoSFinalized() {
|
|
||||||
log.Warn("Unexpected validation activity", "hash", header.Hash(), "number", header.Number)
|
|
||||||
return errors.New("unexpected behavior after transition")
|
|
||||||
}
|
|
||||||
// Reject all the PoS style headers in the first place. No matter
|
|
||||||
// the chain has finished the transition or not, the PoS headers
|
|
||||||
// should only come from the trusted consensus layer instead of
|
|
||||||
// p2p network.
|
|
||||||
if beacon, ok := h.chain.Engine().(*beacon.Beacon); ok {
|
|
||||||
if beacon.IsPoSHeader(header) {
|
|
||||||
return errors.New("unexpected post-merge header")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return h.chain.Engine().VerifyHeader(h.chain, header)
|
|
||||||
}
|
|
||||||
heighter := func() uint64 {
|
|
||||||
return h.chain.CurrentBlock().Number.Uint64()
|
|
||||||
}
|
|
||||||
inserter := func(blocks types.Blocks) (int, error) {
|
|
||||||
// All the block fetcher activities should be disabled
|
|
||||||
// after the transition. Print the warning log.
|
|
||||||
if h.merger.PoSFinalized() {
|
|
||||||
var ctx []interface{}
|
|
||||||
ctx = append(ctx, "blocks", len(blocks))
|
|
||||||
if len(blocks) > 0 {
|
|
||||||
ctx = append(ctx, "firsthash", blocks[0].Hash())
|
|
||||||
ctx = append(ctx, "firstnumber", blocks[0].Number())
|
|
||||||
ctx = append(ctx, "lasthash", blocks[len(blocks)-1].Hash())
|
|
||||||
ctx = append(ctx, "lastnumber", blocks[len(blocks)-1].Number())
|
|
||||||
}
|
|
||||||
log.Warn("Unexpected insertion activity", ctx...)
|
|
||||||
return 0, errors.New("unexpected behavior after transition")
|
|
||||||
}
|
|
||||||
// If snap sync is running, deny importing weird blocks. This is a problematic
|
|
||||||
// clause when starting up a new network, because snap-syncing miners might not
|
|
||||||
// accept each others' blocks until a restart. Unfortunately we haven't figured
|
|
||||||
// out a way yet where nodes can decide unilaterally whether the network is new
|
|
||||||
// or not. This should be fixed if we figure out a solution.
|
|
||||||
if !h.synced.Load() {
|
|
||||||
log.Warn("Syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if h.merger.TDDReached() {
|
|
||||||
// The blocks from the p2p network is regarded as untrusted
|
|
||||||
// after the transition. In theory block gossip should be disabled
|
|
||||||
// entirely whenever the transition is started. But in order to
|
|
||||||
// handle the transition boundary reorg in the consensus-layer,
|
|
||||||
// the legacy blocks are still accepted, but only for the terminal
|
|
||||||
// pow blocks. Spec: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3675.md#halt-the-importing-of-pow-blocks
|
|
||||||
for i, block := range blocks {
|
|
||||||
ptd := h.chain.GetTd(block.ParentHash(), block.NumberU64()-1)
|
|
||||||
if ptd == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
td := new(big.Int).Add(ptd, block.Difficulty())
|
|
||||||
if !h.chain.Config().IsTerminalPoWBlock(ptd, td) {
|
|
||||||
log.Info("Filtered out non-terminal pow block", "number", block.NumberU64(), "hash", block.Hash())
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if err := h.chain.InsertBlockWithoutSetHead(block); err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return h.chain.InsertChain(blocks)
|
|
||||||
}
|
|
||||||
h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer)
|
|
||||||
|
|
||||||
fetchTx := func(peer string, hashes []common.Hash) error {
|
fetchTx := func(peer string, hashes []common.Hash) error {
|
||||||
p := h.peers.peer(peer)
|
p := h.peers.peer(peer)
|
||||||
|
|
@ -288,7 +194,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
return h.txpool.Add(txs, false, false)
|
return h.txpool.Add(txs, false, false)
|
||||||
}
|
}
|
||||||
h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer)
|
h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer)
|
||||||
h.chainSync = newChainSyncer(h)
|
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,8 +303,6 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.chainSync.handlePeerEvent()
|
|
||||||
|
|
||||||
// Propagate existing transactions. new transactions appearing
|
// Propagate existing transactions. new transactions appearing
|
||||||
// after this will be sent via broadcasts.
|
// after this will be sent via broadcasts.
|
||||||
h.syncTransactions(peer)
|
h.syncTransactions(peer)
|
||||||
|
|
@ -526,14 +429,8 @@ func (h *handler) Start(maxPeers int) {
|
||||||
h.txsSub = h.txpool.SubscribeTransactions(h.txsCh, false)
|
h.txsSub = h.txpool.SubscribeTransactions(h.txsCh, false)
|
||||||
go h.txBroadcastLoop()
|
go h.txBroadcastLoop()
|
||||||
|
|
||||||
// broadcast mined blocks
|
|
||||||
h.wg.Add(1)
|
|
||||||
h.minedBlockSub = h.eventMux.Subscribe(core.NewMinedBlockEvent{})
|
|
||||||
go h.minedBroadcastLoop()
|
|
||||||
|
|
||||||
// start sync handlers
|
// start sync handlers
|
||||||
h.wg.Add(1)
|
h.txFetcher.Start()
|
||||||
go h.chainSync.loop()
|
|
||||||
|
|
||||||
// start peer handler tracker
|
// start peer handler tracker
|
||||||
h.wg.Add(1)
|
h.wg.Add(1)
|
||||||
|
|
@ -541,8 +438,9 @@ func (h *handler) Start(maxPeers int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) Stop() {
|
func (h *handler) Stop() {
|
||||||
h.txsSub.Unsubscribe() // quits txBroadcastLoop
|
h.txsSub.Unsubscribe() // quits txBroadcastLoop
|
||||||
h.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
|
h.txFetcher.Stop()
|
||||||
|
h.downloader.Terminate()
|
||||||
|
|
||||||
// Quit chainSync and txsync64.
|
// Quit chainSync and txsync64.
|
||||||
// After this is done, no new peers will be accepted.
|
// After this is done, no new peers will be accepted.
|
||||||
|
|
@ -558,50 +456,6 @@ func (h *handler) Stop() {
|
||||||
log.Info("Ethereum protocol stopped")
|
log.Info("Ethereum protocol stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastBlock will either propagate a block to a subset of its peers, or
|
|
||||||
// will only announce its availability (depending what's requested).
|
|
||||||
func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
|
|
||||||
// Disable the block propagation if the chain has already entered the PoS
|
|
||||||
// stage. The block propagation is delegated to the consensus layer.
|
|
||||||
if h.merger.PoSFinalized() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Disable the block propagation if it's the post-merge block.
|
|
||||||
if beacon, ok := h.chain.Engine().(*beacon.Beacon); ok {
|
|
||||||
if beacon.IsPoSHeader(block.Header()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash := block.Hash()
|
|
||||||
peers := h.peers.peersWithoutBlock(hash)
|
|
||||||
|
|
||||||
// If propagation is requested, send to a subset of the peer
|
|
||||||
if propagate {
|
|
||||||
// Calculate the TD of the block (it's not imported yet, so block.Td is not valid)
|
|
||||||
var td *big.Int
|
|
||||||
if parent := h.chain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil {
|
|
||||||
td = new(big.Int).Add(block.Difficulty(), h.chain.GetTd(block.ParentHash(), block.NumberU64()-1))
|
|
||||||
} else {
|
|
||||||
log.Error("Propagating dangling block", "number", block.Number(), "hash", hash)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Send the block to a subset of our peers
|
|
||||||
transfer := peers[:int(math.Sqrt(float64(len(peers))))]
|
|
||||||
for _, peer := range transfer {
|
|
||||||
peer.AsyncSendNewBlock(block, td)
|
|
||||||
}
|
|
||||||
log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Otherwise if the block is indeed in out own chain, announce it
|
|
||||||
if h.chain.HasBlock(hash, block.NumberU64()) {
|
|
||||||
for _, peer := range peers {
|
|
||||||
peer.AsyncSendNewBlockHash(block)
|
|
||||||
}
|
|
||||||
log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BroadcastTransactions will propagate a batch of transactions
|
// BroadcastTransactions will propagate a batch of transactions
|
||||||
// - To a square root of all peers for non-blob transactions
|
// - To a square root of all peers for non-blob transactions
|
||||||
// - And, separately, as announcements to all peers which are not known to
|
// - And, separately, as announcements to all peers which are not known to
|
||||||
|
|
@ -643,7 +497,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
||||||
}
|
}
|
||||||
// Send the transaction (if it's small enough) directly to a subset of
|
// Send the transaction (if it's small enough) directly to a subset of
|
||||||
// the peers that have not received it yet, ensuring that the flow of
|
// the peers that have not received it yet, ensuring that the flow of
|
||||||
// transactions is groupped by account to (try and) avoid nonce gaps.
|
// transactions is grouped by account to (try and) avoid nonce gaps.
|
||||||
//
|
//
|
||||||
// To do this, we hash the local enode IW with together with a peer's
|
// To do this, we hash the local enode IW with together with a peer's
|
||||||
// enode ID together with the transaction sender and broadcast if
|
// enode ID together with the transaction sender and broadcast if
|
||||||
|
|
@ -684,18 +538,6 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
||||||
"bcastpeers", directPeers, "bcastcount", directCount, "annpeers", annPeers, "anncount", annCount)
|
"bcastpeers", directPeers, "bcastcount", directCount, "annpeers", annPeers, "anncount", annCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// minedBroadcastLoop sends mined blocks to connected peers.
|
|
||||||
func (h *handler) minedBroadcastLoop() {
|
|
||||||
defer h.wg.Done()
|
|
||||||
|
|
||||||
for obj := range h.minedBlockSub.Chan() {
|
|
||||||
if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok {
|
|
||||||
h.BroadcastBlock(ev.Block, true) // First propagate block to peers
|
|
||||||
h.BroadcastBlock(ev.Block, false) // Only then announce to the rest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// txBroadcastLoop announces new transactions to connected peers.
|
// txBroadcastLoop announces new transactions to connected peers.
|
||||||
func (h *handler) txBroadcastLoop() {
|
func (h *handler) txBroadcastLoop() {
|
||||||
defer h.wg.Done()
|
defer h.wg.Done()
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,7 @@ package eth
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
|
|
@ -60,13 +57,6 @@ func (h *ethHandler) AcceptTxs() bool {
|
||||||
func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||||
// Consume any broadcasts and announces, forwarding the rest to the downloader
|
// Consume any broadcasts and announces, forwarding the rest to the downloader
|
||||||
switch packet := packet.(type) {
|
switch packet := packet.(type) {
|
||||||
case *eth.NewBlockHashesPacket:
|
|
||||||
hashes, numbers := packet.Unpack()
|
|
||||||
return h.handleBlockAnnounces(peer, hashes, numbers)
|
|
||||||
|
|
||||||
case *eth.NewBlockPacket:
|
|
||||||
return h.handleBlockBroadcast(peer, packet.Block, packet.TD)
|
|
||||||
|
|
||||||
case *eth.NewPooledTransactionHashesPacket:
|
case *eth.NewPooledTransactionHashesPacket:
|
||||||
return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes)
|
return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes)
|
||||||
|
|
||||||
|
|
@ -85,55 +75,3 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||||
return fmt.Errorf("unexpected eth packet type: %T", packet)
|
return fmt.Errorf("unexpected eth packet type: %T", packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleBlockAnnounces is invoked from a peer's message handler when it transmits a
|
|
||||||
// batch of block announcements for the local node to process.
|
|
||||||
func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, numbers []uint64) error {
|
|
||||||
// Drop all incoming block announces from the p2p network if
|
|
||||||
// the chain already entered the pos stage and disconnect the
|
|
||||||
// remote peer.
|
|
||||||
if h.merger.PoSFinalized() {
|
|
||||||
return errors.New("disallowed block announcement")
|
|
||||||
}
|
|
||||||
// Schedule all the unknown hashes for retrieval
|
|
||||||
var (
|
|
||||||
unknownHashes = make([]common.Hash, 0, len(hashes))
|
|
||||||
unknownNumbers = make([]uint64, 0, len(numbers))
|
|
||||||
)
|
|
||||||
for i := 0; i < len(hashes); i++ {
|
|
||||||
if !h.chain.HasBlock(hashes[i], numbers[i]) {
|
|
||||||
unknownHashes = append(unknownHashes, hashes[i])
|
|
||||||
unknownNumbers = append(unknownNumbers, numbers[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < len(unknownHashes); i++ {
|
|
||||||
h.blockFetcher.Notify(peer.ID(), unknownHashes[i], unknownNumbers[i], time.Now(), peer.RequestOneHeader, peer.RequestBodies)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleBlockBroadcast is invoked from a peer's message handler when it transmits a
|
|
||||||
// block broadcast for the local node to process.
|
|
||||||
func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td *big.Int) error {
|
|
||||||
// Drop all incoming block announces from the p2p network if
|
|
||||||
// the chain already entered the pos stage and disconnect the
|
|
||||||
// remote peer.
|
|
||||||
if h.merger.PoSFinalized() {
|
|
||||||
return errors.New("disallowed block broadcast")
|
|
||||||
}
|
|
||||||
// Schedule the block for import
|
|
||||||
h.blockFetcher.Enqueue(peer.ID(), block)
|
|
||||||
|
|
||||||
// Assuming the block is importable by the peer, but possibly not yet done so,
|
|
||||||
// calculate the head hash and TD that the peer truly must have.
|
|
||||||
var (
|
|
||||||
trueHead = block.ParentHash()
|
|
||||||
trueTD = new(big.Int).Sub(td, block.Difficulty())
|
|
||||||
)
|
|
||||||
// Update the peer's total difficulty if better than the previous
|
|
||||||
if _, td := peer.Head(); trueTD.Cmp(td) > 0 {
|
|
||||||
peer.SetHead(trueHead, trueTD)
|
|
||||||
h.chainSync.handlePeerEvent()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/forkid"
|
"github.com/ethereum/go-ethereum/core/forkid"
|
||||||
|
|
@ -109,7 +108,6 @@ func testForkIDSplit(t *testing.T, protocol uint) {
|
||||||
Database: dbNoFork,
|
Database: dbNoFork,
|
||||||
Chain: chainNoFork,
|
Chain: chainNoFork,
|
||||||
TxPool: newTestTxPool(),
|
TxPool: newTestTxPool(),
|
||||||
Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
|
|
||||||
Network: 1,
|
Network: 1,
|
||||||
Sync: downloader.FullSync,
|
Sync: downloader.FullSync,
|
||||||
BloomCache: 1,
|
BloomCache: 1,
|
||||||
|
|
@ -118,7 +116,6 @@ func testForkIDSplit(t *testing.T, protocol uint) {
|
||||||
Database: dbProFork,
|
Database: dbProFork,
|
||||||
Chain: chainProFork,
|
Chain: chainProFork,
|
||||||
TxPool: newTestTxPool(),
|
TxPool: newTestTxPool(),
|
||||||
Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
|
|
||||||
Network: 1,
|
Network: 1,
|
||||||
Sync: downloader.FullSync,
|
Sync: downloader.FullSync,
|
||||||
BloomCache: 1,
|
BloomCache: 1,
|
||||||
|
|
@ -441,159 +438,3 @@ func testTransactionPropagation(t *testing.T, protocol uint) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that blocks are broadcast to a sqrt number of peers only.
|
|
||||||
func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) }
|
|
||||||
func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) }
|
|
||||||
func TestBroadcastBlock3Peers(t *testing.T) { testBroadcastBlock(t, 3, 1) }
|
|
||||||
func TestBroadcastBlock4Peers(t *testing.T) { testBroadcastBlock(t, 4, 2) }
|
|
||||||
func TestBroadcastBlock5Peers(t *testing.T) { testBroadcastBlock(t, 5, 2) }
|
|
||||||
func TestBroadcastBlock8Peers(t *testing.T) { testBroadcastBlock(t, 9, 3) }
|
|
||||||
func TestBroadcastBlock12Peers(t *testing.T) { testBroadcastBlock(t, 12, 3) }
|
|
||||||
func TestBroadcastBlock16Peers(t *testing.T) { testBroadcastBlock(t, 16, 4) }
|
|
||||||
func TestBroadcastBloc26Peers(t *testing.T) { testBroadcastBlock(t, 26, 5) }
|
|
||||||
func TestBroadcastBlock100Peers(t *testing.T) { testBroadcastBlock(t, 100, 10) }
|
|
||||||
|
|
||||||
func testBroadcastBlock(t *testing.T, peers, bcasts int) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Create a source handler to broadcast blocks from and a number of sinks
|
|
||||||
// to receive them.
|
|
||||||
source := newTestHandlerWithBlocks(1)
|
|
||||||
defer source.close()
|
|
||||||
|
|
||||||
sinks := make([]*testEthHandler, peers)
|
|
||||||
for i := 0; i < len(sinks); i++ {
|
|
||||||
sinks[i] = new(testEthHandler)
|
|
||||||
}
|
|
||||||
// Interconnect all the sink handlers with the source handler
|
|
||||||
var (
|
|
||||||
genesis = source.chain.Genesis()
|
|
||||||
td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
|
|
||||||
)
|
|
||||||
for i, sink := range sinks {
|
|
||||||
sink := sink // Closure for gorotuine below
|
|
||||||
|
|
||||||
sourcePipe, sinkPipe := p2p.MsgPipe()
|
|
||||||
defer sourcePipe.Close()
|
|
||||||
defer sinkPipe.Close()
|
|
||||||
|
|
||||||
sourcePeer := eth.NewPeer(eth.ETH68, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil)
|
|
||||||
sinkPeer := eth.NewPeer(eth.ETH68, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil)
|
|
||||||
defer sourcePeer.Close()
|
|
||||||
defer sinkPeer.Close()
|
|
||||||
|
|
||||||
go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error {
|
|
||||||
return eth.Handle((*ethHandler)(source.handler), peer)
|
|
||||||
})
|
|
||||||
if err := sinkPeer.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
|
|
||||||
t.Fatalf("failed to run protocol handshake")
|
|
||||||
}
|
|
||||||
go eth.Handle(sink, sinkPeer)
|
|
||||||
}
|
|
||||||
// Subscribe to all the transaction pools
|
|
||||||
blockChs := make([]chan *types.Block, len(sinks))
|
|
||||||
for i := 0; i < len(sinks); i++ {
|
|
||||||
blockChs[i] = make(chan *types.Block, 1)
|
|
||||||
defer close(blockChs[i])
|
|
||||||
|
|
||||||
sub := sinks[i].blockBroadcasts.Subscribe(blockChs[i])
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
}
|
|
||||||
// Initiate a block propagation across the peers
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
header := source.chain.CurrentBlock()
|
|
||||||
source.handler.BroadcastBlock(source.chain.GetBlock(header.Hash(), header.Number.Uint64()), true)
|
|
||||||
|
|
||||||
// Iterate through all the sinks and ensure the correct number got the block
|
|
||||||
done := make(chan struct{}, peers)
|
|
||||||
for _, ch := range blockChs {
|
|
||||||
ch := ch
|
|
||||||
go func() {
|
|
||||||
<-ch
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
var received int
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
received++
|
|
||||||
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
if received != bcasts {
|
|
||||||
t.Errorf("broadcast count mismatch: have %d, want %d", received, bcasts)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that a propagated malformed block (uncles or transactions don't match
|
|
||||||
// with the hashes in the header) gets discarded and not broadcast forward.
|
|
||||||
func TestBroadcastMalformedBlock68(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH68) }
|
|
||||||
|
|
||||||
func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Create a source handler to broadcast blocks from and a number of sinks
|
|
||||||
// to receive them.
|
|
||||||
source := newTestHandlerWithBlocks(1)
|
|
||||||
defer source.close()
|
|
||||||
|
|
||||||
// Create a source handler to send messages through and a sink peer to receive them
|
|
||||||
p2pSrc, p2pSink := p2p.MsgPipe()
|
|
||||||
defer p2pSrc.Close()
|
|
||||||
defer p2pSink.Close()
|
|
||||||
|
|
||||||
src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, source.txpool)
|
|
||||||
sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, source.txpool)
|
|
||||||
defer src.Close()
|
|
||||||
defer sink.Close()
|
|
||||||
|
|
||||||
go source.handler.runEthPeer(src, func(peer *eth.Peer) error {
|
|
||||||
return eth.Handle((*ethHandler)(source.handler), peer)
|
|
||||||
})
|
|
||||||
// Run the handshake locally to avoid spinning up a sink handler
|
|
||||||
var (
|
|
||||||
genesis = source.chain.Genesis()
|
|
||||||
td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
|
|
||||||
)
|
|
||||||
if err := sink.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
|
|
||||||
t.Fatalf("failed to run protocol handshake")
|
|
||||||
}
|
|
||||||
// After the handshake completes, the source handler should stream the sink
|
|
||||||
// the blocks, subscribe to inbound network events
|
|
||||||
backend := new(testEthHandler)
|
|
||||||
|
|
||||||
blocks := make(chan *types.Block, 1)
|
|
||||||
sub := backend.blockBroadcasts.Subscribe(blocks)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
|
|
||||||
go eth.Handle(backend, sink)
|
|
||||||
|
|
||||||
// Create various combinations of malformed blocks
|
|
||||||
head := source.chain.CurrentBlock()
|
|
||||||
block := source.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
|
||||||
|
|
||||||
malformedUncles := head
|
|
||||||
malformedUncles.UncleHash[0]++
|
|
||||||
malformedTransactions := head
|
|
||||||
malformedTransactions.TxHash[0]++
|
|
||||||
malformedEverything := head
|
|
||||||
malformedEverything.UncleHash[0]++
|
|
||||||
malformedEverything.TxHash[0]++
|
|
||||||
|
|
||||||
// Try to broadcast all malformations and ensure they all get discarded
|
|
||||||
for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} {
|
|
||||||
block := types.NewBlockWithHeader(header).WithBody(block.Transactions(), block.Uncles())
|
|
||||||
if err := src.SendNewBlock(block, big.NewInt(131136)); err != nil {
|
|
||||||
t.Fatalf("failed to broadcast block: %v", err)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-blocks:
|
|
||||||
t.Fatalf("malformed block forwarded")
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -164,7 +163,6 @@ func newTestHandlerWithBlocks(blocks int) *testHandler {
|
||||||
Database: db,
|
Database: db,
|
||||||
Chain: chain,
|
Chain: chain,
|
||||||
TxPool: txpool,
|
TxPool: txpool,
|
||||||
Merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
|
|
||||||
Network: 1,
|
Network: 1,
|
||||||
Sync: downloader.SnapSync,
|
Sync: downloader.SnapSync,
|
||||||
BloomCache: 1,
|
BloomCache: 1,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package eth
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -192,21 +191,6 @@ func (ps *peerSet) peer(id string) *ethPeer {
|
||||||
return ps.peers[id]
|
return ps.peers[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
// peersWithoutBlock retrieves a list of peers that do not have a given block in
|
|
||||||
// their set of known hashes so it might be propagated to them.
|
|
||||||
func (ps *peerSet) peersWithoutBlock(hash common.Hash) []*ethPeer {
|
|
||||||
ps.lock.RLock()
|
|
||||||
defer ps.lock.RUnlock()
|
|
||||||
|
|
||||||
list := make([]*ethPeer, 0, len(ps.peers))
|
|
||||||
for _, p := range ps.peers {
|
|
||||||
if !p.KnownBlock(hash) {
|
|
||||||
list = append(list, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
// peersWithoutTransaction retrieves a list of peers that do not have a given
|
// peersWithoutTransaction retrieves a list of peers that do not have a given
|
||||||
// transaction in their set of known hashes.
|
// transaction in their set of known hashes.
|
||||||
func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer {
|
func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer {
|
||||||
|
|
@ -240,24 +224,6 @@ func (ps *peerSet) snapLen() int {
|
||||||
return ps.snapPeers
|
return ps.snapPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
// peerWithHighestTD retrieves the known peer with the currently highest total
|
|
||||||
// difficulty, but below the given PoS switchover threshold.
|
|
||||||
func (ps *peerSet) peerWithHighestTD() *eth.Peer {
|
|
||||||
ps.lock.RLock()
|
|
||||||
defer ps.lock.RUnlock()
|
|
||||||
|
|
||||||
var (
|
|
||||||
bestPeer *eth.Peer
|
|
||||||
bestTd *big.Int
|
|
||||||
)
|
|
||||||
for _, p := range ps.peers {
|
|
||||||
if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
|
|
||||||
bestPeer, bestTd = p.Peer, td
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bestPeer
|
|
||||||
}
|
|
||||||
|
|
||||||
// close disconnects all peers.
|
// close disconnects all peers.
|
||||||
func (ps *peerSet) close() {
|
func (ps *peerSet) close() {
|
||||||
ps.lock.Lock()
|
ps.lock.Lock()
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
@ -29,37 +27,6 @@ const (
|
||||||
maxTxPacketSize = 100 * 1024
|
maxTxPacketSize = 100 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
// blockPropagation is a block propagation event, waiting for its turn in the
|
|
||||||
// broadcast queue.
|
|
||||||
type blockPropagation struct {
|
|
||||||
block *types.Block
|
|
||||||
td *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// broadcastBlocks is a write loop that multiplexes blocks and block announcements
|
|
||||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
|
||||||
// node internals and at the same time rate limits queued data.
|
|
||||||
func (p *Peer) broadcastBlocks() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case prop := <-p.queuedBlocks:
|
|
||||||
if err := p.SendNewBlock(prop.block, prop.td); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
|
|
||||||
|
|
||||||
case block := <-p.queuedBlockAnns:
|
|
||||||
if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
|
|
||||||
|
|
||||||
case <-p.term:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// broadcastTransactions is a write loop that schedules transaction broadcasts
|
// broadcastTransactions is a write loop that schedules transaction broadcasts
|
||||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||||
// node internals and at the same time rate limits queued data.
|
// node internals and at the same time rate limits queued data.
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -274,43 +275,11 @@ func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsRequest) [
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error {
|
func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
// A batch of new block announcements just arrived
|
return errors.New("block announcements disallowed") // We dropped support for non-merge networks
|
||||||
ann := new(NewBlockHashesPacket)
|
|
||||||
if err := msg.Decode(ann); err != nil {
|
|
||||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
|
||||||
}
|
|
||||||
// Mark the hashes as present at the remote node
|
|
||||||
for _, block := range *ann {
|
|
||||||
peer.markBlock(block.Hash)
|
|
||||||
}
|
|
||||||
// Deliver them all to the backend for queuing
|
|
||||||
return backend.Handle(peer, ann)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error {
|
func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
// Retrieve and decode the propagated block
|
return errors.New("block broadcasts disallowed") // We dropped support for non-merge networks
|
||||||
ann := new(NewBlockPacket)
|
|
||||||
if err := msg.Decode(ann); err != nil {
|
|
||||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
|
||||||
}
|
|
||||||
if err := ann.sanityCheck(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() {
|
|
||||||
log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash())
|
|
||||||
return nil // TODO(karalabe): return error eventually, but wait a few releases
|
|
||||||
}
|
|
||||||
if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() {
|
|
||||||
log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash())
|
|
||||||
return nil // TODO(karalabe): return error eventually, but wait a few releases
|
|
||||||
}
|
|
||||||
ann.Block.ReceivedAt = msg.Time()
|
|
||||||
ann.Block.ReceivedFrom = peer
|
|
||||||
|
|
||||||
// Mark the peer as owning the block
|
|
||||||
peer.markBlock(ann.Block.Hash())
|
|
||||||
|
|
||||||
return backend.Handle(peer, ann)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
|
func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,6 @@ const (
|
||||||
// before starting to randomly evict them.
|
// before starting to randomly evict them.
|
||||||
maxKnownTxs = 32768
|
maxKnownTxs = 32768
|
||||||
|
|
||||||
// maxKnownBlocks is the maximum block hashes to keep in the known list
|
|
||||||
// before starting to randomly evict them.
|
|
||||||
maxKnownBlocks = 1024
|
|
||||||
|
|
||||||
// maxQueuedTxs is the maximum number of transactions to queue up before dropping
|
// maxQueuedTxs is the maximum number of transactions to queue up before dropping
|
||||||
// older broadcasts.
|
// older broadcasts.
|
||||||
maxQueuedTxs = 4096
|
maxQueuedTxs = 4096
|
||||||
|
|
@ -44,16 +40,6 @@ const (
|
||||||
// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
|
// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
|
||||||
// before dropping older announcements.
|
// before dropping older announcements.
|
||||||
maxQueuedTxAnns = 4096
|
maxQueuedTxAnns = 4096
|
||||||
|
|
||||||
// maxQueuedBlocks is the maximum number of block propagations to queue up before
|
|
||||||
// dropping broadcasts. There's not much point in queueing stale blocks, so a few
|
|
||||||
// that might cover uncles should be enough.
|
|
||||||
maxQueuedBlocks = 4
|
|
||||||
|
|
||||||
// maxQueuedBlockAnns is the maximum number of block announcements to queue up before
|
|
||||||
// dropping broadcasts. Similarly to block propagations, there's no point to queue
|
|
||||||
// above some healthy uncle limit, so use that.
|
|
||||||
maxQueuedBlockAnns = 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// max is a helper function which returns the larger of the two given integers.
|
// max is a helper function which returns the larger of the two given integers.
|
||||||
|
|
@ -75,10 +61,6 @@ type Peer struct {
|
||||||
head common.Hash // Latest advertised head block hash
|
head common.Hash // Latest advertised head block hash
|
||||||
td *big.Int // Latest advertised head block total difficulty
|
td *big.Int // Latest advertised head block total difficulty
|
||||||
|
|
||||||
knownBlocks *knownCache // Set of block hashes known to be known by this peer
|
|
||||||
queuedBlocks chan *blockPropagation // Queue of blocks to broadcast to the peer
|
|
||||||
queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer
|
|
||||||
|
|
||||||
txpool TxPool // Transaction pool used by the broadcasters for liveness checks
|
txpool TxPool // Transaction pool used by the broadcasters for liveness checks
|
||||||
knownTxs *knownCache // Set of transaction hashes known to be known by this peer
|
knownTxs *knownCache // Set of transaction hashes known to be known by this peer
|
||||||
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
|
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
|
||||||
|
|
@ -96,24 +78,20 @@ type Peer struct {
|
||||||
// version.
|
// version.
|
||||||
func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer {
|
func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer {
|
||||||
peer := &Peer{
|
peer := &Peer{
|
||||||
id: p.ID().String(),
|
id: p.ID().String(),
|
||||||
Peer: p,
|
Peer: p,
|
||||||
rw: rw,
|
rw: rw,
|
||||||
version: version,
|
version: version,
|
||||||
knownTxs: newKnownCache(maxKnownTxs),
|
knownTxs: newKnownCache(maxKnownTxs),
|
||||||
knownBlocks: newKnownCache(maxKnownBlocks),
|
txBroadcast: make(chan []common.Hash),
|
||||||
queuedBlocks: make(chan *blockPropagation, maxQueuedBlocks),
|
txAnnounce: make(chan []common.Hash),
|
||||||
queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
|
reqDispatch: make(chan *request),
|
||||||
txBroadcast: make(chan []common.Hash),
|
reqCancel: make(chan *cancel),
|
||||||
txAnnounce: make(chan []common.Hash),
|
resDispatch: make(chan *response),
|
||||||
reqDispatch: make(chan *request),
|
txpool: txpool,
|
||||||
reqCancel: make(chan *cancel),
|
term: make(chan struct{}),
|
||||||
resDispatch: make(chan *response),
|
|
||||||
txpool: txpool,
|
|
||||||
term: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
// Start up all the broadcasters
|
// Start up all the broadcasters
|
||||||
go peer.broadcastBlocks()
|
|
||||||
go peer.broadcastTransactions()
|
go peer.broadcastTransactions()
|
||||||
go peer.announceTransactions()
|
go peer.announceTransactions()
|
||||||
go peer.dispatcher()
|
go peer.dispatcher()
|
||||||
|
|
@ -156,23 +134,11 @@ func (p *Peer) SetHead(hash common.Hash, td *big.Int) {
|
||||||
p.td.Set(td)
|
p.td.Set(td)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KnownBlock returns whether peer is known to already have a block.
|
|
||||||
func (p *Peer) KnownBlock(hash common.Hash) bool {
|
|
||||||
return p.knownBlocks.Contains(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KnownTransaction returns whether peer is known to already have a transaction.
|
// KnownTransaction returns whether peer is known to already have a transaction.
|
||||||
func (p *Peer) KnownTransaction(hash common.Hash) bool {
|
func (p *Peer) KnownTransaction(hash common.Hash) bool {
|
||||||
return p.knownTxs.Contains(hash)
|
return p.knownTxs.Contains(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// markBlock marks a block as known for the peer, ensuring that the block will
|
|
||||||
// never be propagated to this particular peer.
|
|
||||||
func (p *Peer) markBlock(hash common.Hash) {
|
|
||||||
// If we reached the memory allowance, drop a previously known block hash
|
|
||||||
p.knownBlocks.Add(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// markTransaction marks a transaction as known for the peer, ensuring that it
|
// markTransaction marks a transaction as known for the peer, ensuring that it
|
||||||
// will never be propagated to this particular peer.
|
// will never be propagated to this particular peer.
|
||||||
func (p *Peer) markTransaction(hash common.Hash) {
|
func (p *Peer) markTransaction(hash common.Hash) {
|
||||||
|
|
@ -248,55 +214,6 @@ func (p *Peer) ReplyPooledTransactionsRLP(id uint64, hashes []common.Hash, txs [
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNewBlockHashes announces the availability of a number of blocks through
|
|
||||||
// a hash notification.
|
|
||||||
func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
|
|
||||||
// Mark all the block hashes as known, but ensure we don't overflow our limits
|
|
||||||
p.knownBlocks.Add(hashes...)
|
|
||||||
|
|
||||||
request := make(NewBlockHashesPacket, len(hashes))
|
|
||||||
for i := 0; i < len(hashes); i++ {
|
|
||||||
request[i].Hash = hashes[i]
|
|
||||||
request[i].Number = numbers[i]
|
|
||||||
}
|
|
||||||
return p2p.Send(p.rw, NewBlockHashesMsg, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsyncSendNewBlockHash queues the availability of a block for propagation to a
|
|
||||||
// remote peer. If the peer's broadcast queue is full, the event is silently
|
|
||||||
// dropped.
|
|
||||||
func (p *Peer) AsyncSendNewBlockHash(block *types.Block) {
|
|
||||||
select {
|
|
||||||
case p.queuedBlockAnns <- block:
|
|
||||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
|
||||||
p.knownBlocks.Add(block.Hash())
|
|
||||||
default:
|
|
||||||
p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendNewBlock propagates an entire block to a remote peer.
|
|
||||||
func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error {
|
|
||||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
|
||||||
p.knownBlocks.Add(block.Hash())
|
|
||||||
return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{
|
|
||||||
Block: block,
|
|
||||||
TD: td,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
|
|
||||||
// the peer's broadcast queue is full, the event is silently dropped.
|
|
||||||
func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
|
|
||||||
select {
|
|
||||||
case p.queuedBlocks <- &blockPropagation{block: block, td: td}:
|
|
||||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
|
||||||
p.knownBlocks.Add(block.Hash())
|
|
||||||
default:
|
|
||||||
p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplyBlockHeadersRLP is the response to GetBlockHeaders.
|
// ReplyBlockHeadersRLP is the response to GetBlockHeaders.
|
||||||
func (p *Peer) ReplyBlockHeadersRLP(id uint64, headers []rlp.RawValue) error {
|
func (p *Peer) ReplyBlockHeadersRLP(id uint64, headers []rlp.RawValue) error {
|
||||||
return p2p.Send(p.rw, BlockHeadersMsg, &BlockHeadersRLPPacket{
|
return p2p.Send(p.rw, BlockHeadersMsg, &BlockHeadersRLPPacket{
|
||||||
|
|
|
||||||
|
|
@ -189,19 +189,6 @@ type NewBlockPacket struct {
|
||||||
TD *big.Int
|
TD *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanityCheck verifies that the values are reasonable, as a DoS protection
|
|
||||||
func (request *NewBlockPacket) sanityCheck() error {
|
|
||||||
if err := request.Block.SanityCheck(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
|
|
||||||
// larger, it will still fit within 100 bits
|
|
||||||
if tdlen := request.TD.BitLen(); tdlen > 100 {
|
|
||||||
return fmt.Errorf("too large block TD: bitlen %d", tdlen)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlockBodiesRequest represents a block body query.
|
// GetBlockBodiesRequest represents a block body query.
|
||||||
type GetBlockBodiesRequest []common.Hash
|
type GetBlockBodiesRequest []common.Hash
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1502,7 +1502,7 @@ func getCodeByHash(hash common.Hash) []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeAccountTrieNoStorage spits out a trie, along with the leafs
|
// makeAccountTrieNoStorage spits out a trie, along with the leaves
|
||||||
func makeAccountTrieNoStorage(n int, scheme string) (string, *trie.Trie, []*kv) {
|
func makeAccountTrieNoStorage(n int, scheme string) (string, *trie.Trie, []*kv) {
|
||||||
var (
|
var (
|
||||||
db = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
|
db = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
|
||||||
|
|
@ -1650,7 +1650,7 @@ func makeAccountTrieWithStorageWithUniqueStorage(scheme string, accounts, slots
|
||||||
return db.Scheme(), accTrie, entries, storageTries, storageEntries
|
return db.Scheme(), accTrie, entries, storageTries, storageEntries
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeAccountTrieWithStorage spits out a trie, along with the leafs
|
// makeAccountTrieWithStorage spits out a trie, along with the leaves
|
||||||
func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, boundary bool, uneven bool) (*trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) {
|
func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, boundary bool, uneven bool) (*trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) {
|
||||||
var (
|
var (
|
||||||
db = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
|
db = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
|
||||||
|
|
|
||||||
215
eth/sync.go
215
eth/sync.go
|
|
@ -17,21 +17,9 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
forceSyncCycle = 10 * time.Second // Time interval to force syncs, even if few peers are available
|
|
||||||
defaultMinSyncPeers = 5 // Amount of peers desired to start syncing
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// syncTransactions starts sending all currently pending transactions to the given peer.
|
// syncTransactions starts sending all currently pending transactions to the given peer.
|
||||||
|
|
@ -47,206 +35,3 @@ func (h *handler) syncTransactions(p *eth.Peer) {
|
||||||
}
|
}
|
||||||
p.AsyncSendPooledTransactionHashes(hashes)
|
p.AsyncSendPooledTransactionHashes(hashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// chainSyncer coordinates blockchain sync components.
|
|
||||||
type chainSyncer struct {
|
|
||||||
handler *handler
|
|
||||||
force *time.Timer
|
|
||||||
forced bool // true when force timer fired
|
|
||||||
warned time.Time
|
|
||||||
peerEventCh chan struct{}
|
|
||||||
doneCh chan error // non-nil when sync is running
|
|
||||||
}
|
|
||||||
|
|
||||||
// chainSyncOp is a scheduled sync operation.
|
|
||||||
type chainSyncOp struct {
|
|
||||||
mode downloader.SyncMode
|
|
||||||
peer *eth.Peer
|
|
||||||
td *big.Int
|
|
||||||
head common.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// newChainSyncer creates a chainSyncer.
|
|
||||||
func newChainSyncer(handler *handler) *chainSyncer {
|
|
||||||
return &chainSyncer{
|
|
||||||
handler: handler,
|
|
||||||
peerEventCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handlePeerEvent notifies the syncer about a change in the peer set.
|
|
||||||
// This is called for new peers and every time a peer announces a new
|
|
||||||
// chain head.
|
|
||||||
func (cs *chainSyncer) handlePeerEvent() bool {
|
|
||||||
select {
|
|
||||||
case cs.peerEventCh <- struct{}{}:
|
|
||||||
return true
|
|
||||||
case <-cs.handler.quitSync:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop runs in its own goroutine and launches the sync when necessary.
|
|
||||||
func (cs *chainSyncer) loop() {
|
|
||||||
defer cs.handler.wg.Done()
|
|
||||||
|
|
||||||
cs.handler.blockFetcher.Start()
|
|
||||||
cs.handler.txFetcher.Start()
|
|
||||||
defer cs.handler.blockFetcher.Stop()
|
|
||||||
defer cs.handler.txFetcher.Stop()
|
|
||||||
defer cs.handler.downloader.Terminate()
|
|
||||||
|
|
||||||
// The force timer lowers the peer count threshold down to one when it fires.
|
|
||||||
// This ensures we'll always start sync even if there aren't enough peers.
|
|
||||||
cs.force = time.NewTimer(forceSyncCycle)
|
|
||||||
defer cs.force.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if op := cs.nextSyncOp(); op != nil {
|
|
||||||
cs.startSync(op)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-cs.peerEventCh:
|
|
||||||
// Peer information changed, recheck.
|
|
||||||
case err := <-cs.doneCh:
|
|
||||||
cs.doneCh = nil
|
|
||||||
cs.force.Reset(forceSyncCycle)
|
|
||||||
cs.forced = false
|
|
||||||
|
|
||||||
// If we've reached the merge transition but no beacon client is available, or
|
|
||||||
// it has not yet switched us over, keep warning the user that their infra is
|
|
||||||
// potentially flaky.
|
|
||||||
if errors.Is(err, downloader.ErrMergeTransition) && time.Since(cs.warned) > 10*time.Second {
|
|
||||||
log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...")
|
|
||||||
cs.warned = time.Now()
|
|
||||||
}
|
|
||||||
case <-cs.force.C:
|
|
||||||
cs.forced = true
|
|
||||||
|
|
||||||
case <-cs.handler.quitSync:
|
|
||||||
// Disable all insertion on the blockchain. This needs to happen before
|
|
||||||
// terminating the downloader because the downloader waits for blockchain
|
|
||||||
// inserts, and these can take a long time to finish.
|
|
||||||
cs.handler.chain.StopInsert()
|
|
||||||
cs.handler.downloader.Terminate()
|
|
||||||
if cs.doneCh != nil {
|
|
||||||
<-cs.doneCh
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextSyncOp determines whether sync is required at this time.
|
|
||||||
func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
|
|
||||||
if cs.doneCh != nil {
|
|
||||||
return nil // Sync already running
|
|
||||||
}
|
|
||||||
// If a beacon client once took over control, disable the entire legacy sync
|
|
||||||
// path from here on end. Note, there is a slight "race" between reaching TTD
|
|
||||||
// and the beacon client taking over. The downloader will enforce that nothing
|
|
||||||
// above the first TTD will be delivered to the chain for import.
|
|
||||||
//
|
|
||||||
// An alternative would be to check the local chain for exceeding the TTD and
|
|
||||||
// avoid triggering a sync in that case, but that could also miss sibling or
|
|
||||||
// other family TTD block being accepted.
|
|
||||||
if cs.handler.chain.Config().TerminalTotalDifficultyPassed || cs.handler.merger.TDDReached() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Ensure we're at minimum peer count.
|
|
||||||
minPeers := defaultMinSyncPeers
|
|
||||||
if cs.forced {
|
|
||||||
minPeers = 1
|
|
||||||
} else if minPeers > cs.handler.maxPeers {
|
|
||||||
minPeers = cs.handler.maxPeers
|
|
||||||
}
|
|
||||||
if cs.handler.peers.len() < minPeers {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// We have enough peers, pick the one with the highest TD, but avoid going
|
|
||||||
// over the terminal total difficulty. Above that we expect the consensus
|
|
||||||
// clients to direct the chain head to sync to.
|
|
||||||
peer := cs.handler.peers.peerWithHighestTD()
|
|
||||||
if peer == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
mode, ourTD := cs.modeAndLocalHead()
|
|
||||||
op := peerToSyncOp(mode, peer)
|
|
||||||
if op.td.Cmp(ourTD) <= 0 {
|
|
||||||
// We seem to be in sync according to the legacy rules. In the merge
|
|
||||||
// world, it can also mean we're stuck on the merge block, waiting for
|
|
||||||
// a beacon client. In the latter case, notify the user.
|
|
||||||
if ttd := cs.handler.chain.Config().TerminalTotalDifficulty; ttd != nil && ourTD.Cmp(ttd) >= 0 && time.Since(cs.warned) > 10*time.Second {
|
|
||||||
log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...")
|
|
||||||
cs.warned = time.Now()
|
|
||||||
}
|
|
||||||
return nil // We're in sync
|
|
||||||
}
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
|
|
||||||
func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp {
|
|
||||||
peerHead, peerTD := p.Head()
|
|
||||||
return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) {
|
|
||||||
// If we're in snap sync mode, return that directly
|
|
||||||
if cs.handler.snapSync.Load() {
|
|
||||||
block := cs.handler.chain.CurrentSnapBlock()
|
|
||||||
td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64())
|
|
||||||
return downloader.SnapSync, td
|
|
||||||
}
|
|
||||||
// We are probably in full sync, but we might have rewound to before the
|
|
||||||
// snap sync pivot, check if we should re-enable snap sync.
|
|
||||||
head := cs.handler.chain.CurrentBlock()
|
|
||||||
if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil {
|
|
||||||
if head.Number.Uint64() < *pivot {
|
|
||||||
block := cs.handler.chain.CurrentSnapBlock()
|
|
||||||
td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64())
|
|
||||||
return downloader.SnapSync, td
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We are in a full sync, but the associated head state is missing. To complete
|
|
||||||
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
|
|
||||||
// persistent state is corrupted, just mismatch with the head block.
|
|
||||||
if !cs.handler.chain.HasState(head.Root) {
|
|
||||||
block := cs.handler.chain.CurrentSnapBlock()
|
|
||||||
td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64())
|
|
||||||
log.Info("Reenabled snap sync as chain is stateless")
|
|
||||||
return downloader.SnapSync, td
|
|
||||||
}
|
|
||||||
// Nope, we're really full syncing
|
|
||||||
td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64())
|
|
||||||
return downloader.FullSync, td
|
|
||||||
}
|
|
||||||
|
|
||||||
// startSync launches doSync in a new goroutine.
|
|
||||||
func (cs *chainSyncer) startSync(op *chainSyncOp) {
|
|
||||||
cs.doneCh = make(chan error, 1)
|
|
||||||
go func() { cs.doneCh <- cs.handler.doSync(op) }()
|
|
||||||
}
|
|
||||||
|
|
||||||
// doSync synchronizes the local blockchain with a remote peer.
|
|
||||||
func (h *handler) doSync(op *chainSyncOp) error {
|
|
||||||
// Run the sync cycle, and disable snap sync if we're past the pivot block
|
|
||||||
err := h.downloader.LegacySync(op.peer.ID(), op.head, op.td, h.chain.Config().TerminalTotalDifficulty, op.mode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.enableSyncedFeatures()
|
|
||||||
|
|
||||||
head := h.chain.CurrentBlock()
|
|
||||||
if head.Number.Uint64() > 0 {
|
|
||||||
// We've completed a sync cycle, notify all peers of new state. This path is
|
|
||||||
// essential in star-topology networks where a gateway node needs to notify
|
|
||||||
// all its out-of-date peers of the availability of a new block. This failure
|
|
||||||
// scenario will most often crop up in private and hackathon networks with
|
|
||||||
// degenerate connectivity, but it should be healthy for the mainnet too to
|
|
||||||
// more reliably update peers or the local TD state.
|
|
||||||
if block := h.chain.GetBlock(head.Hash(), head.Number.Uint64()); block != nil {
|
|
||||||
h.BroadcastBlock(block, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,11 @@ func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) {
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
|
||||||
// Check that snap sync was disabled
|
// Check that snap sync was disabled
|
||||||
op := peerToSyncOp(downloader.SnapSync, empty.handler.peers.peerWithHighestTD())
|
if err := empty.handler.downloader.BeaconSync(downloader.SnapSync, full.chain.CurrentBlock(), nil); err != nil {
|
||||||
if err := empty.handler.doSync(op); err != nil {
|
|
||||||
t.Fatal("sync failed:", err)
|
t.Fatal("sync failed:", err)
|
||||||
}
|
}
|
||||||
|
empty.handler.enableSyncedFeatures()
|
||||||
|
|
||||||
if empty.handler.snapSync.Load() {
|
if empty.handler.snapSync.Load() {
|
||||||
t.Fatalf("snap sync not disabled after successful synchronisation")
|
t.Fatalf("snap sync not disabled after successful synchronisation")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, conf
|
||||||
}
|
}
|
||||||
sub := notifier.CreateSubscription()
|
sub := notifier.CreateSubscription()
|
||||||
|
|
||||||
resCh := api.traceChain(from, to, config, notifier.Closed())
|
resCh := api.traceChain(from, to, config, sub.Err())
|
||||||
go func() {
|
go func() {
|
||||||
for result := range resCh {
|
for result := range resCh {
|
||||||
notifier.Notify(sub.ID, result)
|
notifier.Notify(sub.ID, result)
|
||||||
|
|
@ -240,7 +240,7 @@ func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, conf
|
||||||
// the end block but excludes the start one. The return value will be one item per
|
// the end block but excludes the start one. The return value will be one item per
|
||||||
// transaction, dependent on the requested tracer.
|
// transaction, dependent on the requested tracer.
|
||||||
// The tracing procedure should be aborted in case the closed signal is received.
|
// The tracing procedure should be aborted in case the closed signal is received.
|
||||||
func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed <-chan interface{}) chan *blockTraceResult {
|
func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed <-chan error) chan *blockTraceResult {
|
||||||
reexec := defaultTraceReexec
|
reexec := defaultTraceReexec
|
||||||
if config != nil && config.Reexec != nil {
|
if config != nil && config.Reexec != nil {
|
||||||
reexec = *config.Reexec
|
reexec = *config.Reexec
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -107,6 +108,11 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
|
||||||
)
|
)
|
||||||
defer state.Close()
|
defer state.Close()
|
||||||
|
|
||||||
|
if test.Genesis.ExcessBlobGas != nil && test.Genesis.BlobGasUsed != nil {
|
||||||
|
excessBlobGas := eip4844.CalcExcessBlobGas(*test.Genesis.ExcessBlobGas, *test.Genesis.BlobGasUsed)
|
||||||
|
context.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas)
|
||||||
|
}
|
||||||
|
|
||||||
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
|
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
|
|
|
||||||
63
eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json
vendored
Normal file
63
eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"genesis": {
|
||||||
|
"baseFeePerGas": "7",
|
||||||
|
"blobGasUsed": "0",
|
||||||
|
"difficulty": "0",
|
||||||
|
"excessBlobGas": "36306944",
|
||||||
|
"extraData": "0xd983010e00846765746888676f312e32312e308664617277696e",
|
||||||
|
"gasLimit": "15639172",
|
||||||
|
"hash": "0xc682259fda061bb9ce8ccb491d5b2d436cb73daf04e1025dd116d045ce4ad28c",
|
||||||
|
"miner": "0x0000000000000000000000000000000000000000",
|
||||||
|
"mixHash": "0xae1a5ba939a4c9ac38aabeff361169fb55a6fc2c9511457e0be6eff9514faec0",
|
||||||
|
"nonce": "0x0000000000000000",
|
||||||
|
"number": "315",
|
||||||
|
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"stateRoot": "0x577f42ab21ccfd946511c57869ace0bdf7c217c36f02b7cd3459df0ed1cffc1a",
|
||||||
|
"timestamp": "1709626771",
|
||||||
|
"totalDifficulty": "1",
|
||||||
|
"withdrawals": [],
|
||||||
|
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||||
|
"alloc": {
|
||||||
|
"0x0000000000000000000000000000000000000000": {
|
||||||
|
"balance": "0x272e0528"
|
||||||
|
},
|
||||||
|
"0x0c2c51a0990aee1d73c1228de158688341557508": {
|
||||||
|
"balance": "0xde0b6b3a7640000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"chainId": 1337,
|
||||||
|
"homesteadBlock": 0,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip155Block": 0,
|
||||||
|
"eip158Block": 0,
|
||||||
|
"byzantiumBlock": 0,
|
||||||
|
"constantinopleBlock": 0,
|
||||||
|
"petersburgBlock": 0,
|
||||||
|
"istanbulBlock": 0,
|
||||||
|
"muirGlacierBlock": 0,
|
||||||
|
"berlinBlock": 0,
|
||||||
|
"londonBlock": 0,
|
||||||
|
"arrowGlacierBlock": 0,
|
||||||
|
"grayGlacierBlock": 0,
|
||||||
|
"shanghaiTime": 0,
|
||||||
|
"cancunTime": 0,
|
||||||
|
"terminalTotalDifficulty": 0,
|
||||||
|
"terminalTotalDifficultyPassed": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"number": "316",
|
||||||
|
"difficulty": "0",
|
||||||
|
"timestamp": "1709626785",
|
||||||
|
"gasLimit": "15654443",
|
||||||
|
"miner": "0x0000000000000000000000000000000000000000"
|
||||||
|
},
|
||||||
|
"input": "0x03f8b1820539806485174876e800825208940c2c51a0990aee1d73c1228de1586883415575088080c083020000f842a00100c9fbdf97f747e85847b4f3fff408f89c26842f77c882858bf2c89923849aa00138e3896f3c27f2389147507f8bcec52028b0efca6ee842ed83c9158873943880a0dbac3f97a532c9b00e6239b29036245a5bfbb96940b9d848634661abee98b945a03eec8525f261c2e79798f7b45a5d6ccaefa24576d53ba5023e919b86841c0675",
|
||||||
|
"result": {
|
||||||
|
"0x0000000000000000000000000000000000000000": { "balance": "0x272e0528" },
|
||||||
|
"0x0c2c51a0990aee1d73c1228de158688341557508": {
|
||||||
|
"balance": "0xde0b6b3a7640000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
|
//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
|
||||||
|
|
@ -112,6 +113,12 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
|
||||||
gasPrice := env.TxContext.GasPrice
|
gasPrice := env.TxContext.GasPrice
|
||||||
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
|
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
|
||||||
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
|
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
|
||||||
|
|
||||||
|
// Add blob fee to the sender's balance.
|
||||||
|
if env.Context.BlobBaseFee != nil && len(env.TxContext.BlobHashes) > 0 {
|
||||||
|
blobGas := uint64(params.BlobTxBlobGasPerBlob * len(env.TxContext.BlobHashes))
|
||||||
|
fromBal.Add(fromBal, new(big.Int).Mul(env.Context.BlobBaseFee, new(big.Int).SetUint64(blobGas)))
|
||||||
|
}
|
||||||
t.pre[from].Balance = fromBal
|
t.pre[from].Balance = fromBal
|
||||||
t.pre[from].Nonce--
|
t.pre[from].Nonce--
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -602,17 +602,22 @@ func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// send a transaction for some interesting pending status
|
// send a transaction for some interesting pending status
|
||||||
|
// and wait for the transaction to be included in the pending block
|
||||||
sendTransaction(ec)
|
sendTransaction(ec)
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
// Check pending transaction count
|
// wait for the transaction to be included in the pending block
|
||||||
pending, err := ec.PendingTransactionCount(context.Background())
|
for {
|
||||||
if err != nil {
|
// Check pending transaction count
|
||||||
t.Fatalf("unexpected error: %v", err)
|
pending, err := ec.PendingTransactionCount(context.Background())
|
||||||
}
|
if err != nil {
|
||||||
if pending != 1 {
|
t.Fatalf("unexpected error: %v", err)
|
||||||
t.Fatalf("unexpected pending, wanted 1 got: %v", pending)
|
}
|
||||||
|
if pending == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query balance
|
// Query balance
|
||||||
balance, err := ec.BalanceAt(context.Background(), testAddr, nil)
|
balance, err := ec.BalanceAt(context.Background(), testAddr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -737,7 +742,7 @@ func sendTransaction(ec *Client) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nonce, err := ec.PendingNonceAt(context.Background(), testAddr)
|
nonce, err := ec.NonceAt(context.Background(), testAddr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ func TestGethClient(t *testing.T) {
|
||||||
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
|
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
|
||||||
},
|
},
|
||||||
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
|
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
|
||||||
// one block. The `testAcessList` fails if the miner has not yet created a
|
// one block. The `testAccessList` fails if the miner has not yet created a
|
||||||
// new pending-block after the import event.
|
// new pending-block after the import event.
|
||||||
// Hence: this test should be last, execute the tests serially.
|
// Hence: this test should be last, execute the tests serially.
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -589,8 +589,8 @@ func (b *batch) Reset() {
|
||||||
func (b *batch) Replay(w ethdb.KeyValueWriter) error {
|
func (b *batch) Replay(w ethdb.KeyValueWriter) error {
|
||||||
reader := b.b.Reader()
|
reader := b.b.Reader()
|
||||||
for {
|
for {
|
||||||
kind, k, v, ok := reader.Next()
|
kind, k, v, ok, err := reader.Next()
|
||||||
if !ok {
|
if !ok || err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// The (k,v) slices might be overwritten if the batch is reset/reused,
|
// The (k,v) slices might be overwritten if the batch is reset/reused,
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import (
|
||||||
ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth"
|
ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
@ -80,13 +79,6 @@ type fullNodeBackend interface {
|
||||||
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// miningNodeBackend encompasses the functionality necessary for a mining node
|
|
||||||
// reporting to ethstats
|
|
||||||
type miningNodeBackend interface {
|
|
||||||
fullNodeBackend
|
|
||||||
Miner() *miner.Miner
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service implements an Ethereum netstats reporting daemon that pushes local
|
// Service implements an Ethereum netstats reporting daemon that pushes local
|
||||||
// chain statistics up to a monitoring server.
|
// chain statistics up to a monitoring server.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
|
@ -777,30 +769,21 @@ func (s *Service) reportPending(conn *connWrapper) error {
|
||||||
type nodeStats struct {
|
type nodeStats struct {
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Syncing bool `json:"syncing"`
|
Syncing bool `json:"syncing"`
|
||||||
Mining bool `json:"mining"`
|
|
||||||
Hashrate int `json:"hashrate"`
|
|
||||||
Peers int `json:"peers"`
|
Peers int `json:"peers"`
|
||||||
GasPrice int `json:"gasPrice"`
|
GasPrice int `json:"gasPrice"`
|
||||||
Uptime int `json:"uptime"`
|
Uptime int `json:"uptime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// reportStats retrieves various stats about the node at the networking and
|
// reportStats retrieves various stats about the node at the networking layer
|
||||||
// mining layer and reports it to the stats server.
|
// and reports it to the stats server.
|
||||||
func (s *Service) reportStats(conn *connWrapper) error {
|
func (s *Service) reportStats(conn *connWrapper) error {
|
||||||
// Gather the syncing and mining infos from the local miner instance
|
// Gather the syncing infos from the local miner instance
|
||||||
var (
|
var (
|
||||||
mining bool
|
|
||||||
hashrate int
|
|
||||||
syncing bool
|
syncing bool
|
||||||
gasprice int
|
gasprice int
|
||||||
)
|
)
|
||||||
// check if backend is a full node
|
// check if backend is a full node
|
||||||
if fullBackend, ok := s.backend.(fullNodeBackend); ok {
|
if fullBackend, ok := s.backend.(fullNodeBackend); ok {
|
||||||
if miningBackend, ok := s.backend.(miningNodeBackend); ok {
|
|
||||||
mining = miningBackend.Miner().Mining()
|
|
||||||
hashrate = int(miningBackend.Miner().Hashrate())
|
|
||||||
}
|
|
||||||
|
|
||||||
sync := fullBackend.SyncProgress()
|
sync := fullBackend.SyncProgress()
|
||||||
syncing = !sync.Done()
|
syncing = !sync.Done()
|
||||||
|
|
||||||
|
|
@ -820,8 +803,6 @@ func (s *Service) reportStats(conn *connWrapper) error {
|
||||||
"id": s.node,
|
"id": s.node,
|
||||||
"stats": &nodeStats{
|
"stats": &nodeStats{
|
||||||
Active: true,
|
Active: true,
|
||||||
Mining: mining,
|
|
||||||
Hashrate: hashrate,
|
|
||||||
Peers: s.server.PeerCount(),
|
Peers: s.server.PeerCount(),
|
||||||
GasPrice: gasprice,
|
GasPrice: gasprice,
|
||||||
Syncing: syncing,
|
Syncing: syncing,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue