",
Description: `The run command runs arbitrary EVM code.`,
Flags: flags.Merge(vmFlags, traceFlags),
@@ -144,7 +144,7 @@ func runCmd(ctx *cli.Context) error {
initialGas = genesisConfig.GasLimit
}
} else {
- genesisConfig.Config = params.AllEthashProtocolChanges
+ genesisConfig.Config = params.AllDevChainProtocolChanges
}
db := rawdb.NewMemoryDatabase()
diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go
index 618ddf2ed..6e751b630 100644
--- a/cmd/evm/staterunner.go
+++ b/cmd/evm/staterunner.go
@@ -100,18 +100,19 @@ func runStateTest(fname string, cfg vm.Config, jsonOut, dump bool) error {
for _, st := range test.Subtests() {
// Run the test and aggregate the result
result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true}
- test.Run(st, cfg, false, rawdb.HashScheme, func(err error, snaps *snapshot.Tree, state *state.StateDB) {
- if state != nil {
- root := state.IntermediateRoot(false)
+ test.Run(st, cfg, false, rawdb.HashScheme, func(err error, snaps *snapshot.Tree, statedb *state.StateDB) {
+ var root common.Hash
+ if statedb != nil {
+ root = statedb.IntermediateRoot(false)
result.Root = &root
if jsonOut {
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
}
- }
- // Dump any state to aid debugging
- if dump {
- dump := state.RawDump(nil)
- result.State = &dump
+ if dump { // Dump any state to aid debugging
+ cpy, _ := state.New(root, statedb.Database(), nil)
+ dump := cpy.RawDump(nil)
+ result.State = &dump
+ }
}
if err != nil {
// Test failed, mark as so
diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go
index 03503d11c..ad36540de 100644
--- a/cmd/evm/t8n_test.go
+++ b/cmd/evm/t8n_test.go
@@ -106,6 +106,7 @@ func (args *t8nOutput) get() (out []string) {
}
func TestT8n(t *testing.T) {
+ t.Parallel()
tt := new(testT8n)
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
for i, tc := range []struct {
@@ -338,6 +339,7 @@ func (args *t9nInput) get(base string) []string {
}
func TestT9n(t *testing.T) {
+ t.Parallel()
tt := new(testT8n)
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
for i, tc := range []struct {
@@ -473,6 +475,7 @@ func (args *b11rInput) get(base string) []string {
}
func TestB11r(t *testing.T) {
+ t.Parallel()
tt := new(testT8n)
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
for i, tc := range []struct {
diff --git a/cmd/faucet/README.md b/cmd/faucet/README.md
deleted file mode 100644
index 7e857fa0e..000000000
--- a/cmd/faucet/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# Faucet
-
-The `faucet` is a simplistic web application with the goal of distributing small amounts of Ether in private and test networks.
-
-Users need to post their Ethereum addresses to fund in a Twitter status update or public Facebook post and share the link to the faucet. The faucet will in turn deduplicate user requests and send the Ether. After a funding round, the faucet prevents the same user from requesting again for a pre-configured amount of time, proportional to the amount of Ether requested.
-
-## Operation
-
-The `faucet` is a single binary app (everything included) with all configurations set via command line flags and a few files.
-
-First things first, the `faucet` needs to connect to an Ethereum network, for which it needs the necessary genesis and network infos. Each of the following flags must be set:
-
-- `-genesis` is a path to a file containing the network `genesis.json`. or using:
- - `-goerli` with the faucet with Görli network config
- - `-sepolia` with the faucet with Sepolia network config
-- `-network` is the devp2p network id used during connection
-- `-bootnodes` is a list of `enode://` ids to join the network through
-
-The `faucet` will use the `les` protocol to join the configured Ethereum network and will store its data in `$HOME/.faucet` (currently not configurable).
-
-## Funding
-
-To be able to distribute funds, the `faucet` needs access to an already funded Ethereum account. This can be configured via:
-
-- `-account.json` is a path to the Ethereum account's JSON key file
-- `-account.pass` is a path to a text file with the decryption passphrase
-
-The faucet is able to distribute various amounts of Ether in exchange for various timeouts. These can be configured via:
-
-- `-faucet.amount` is the number of Ethers to send by default
-- `-faucet.minutes` is the time to wait before allowing a rerequest
-- `-faucet.tiers` is the funding tiers to support (x3 time, x2.5 funds)
-
-## Sybil protection
-
-To prevent the same user from exhausting funds in a loop, the `faucet` ties requests to social networks and captcha resolvers.
-
-Captcha protection uses Google's invisible ReCaptcha, thus the `faucet` needs to run on a live domain. The domain needs to be registered in Google's systems to retrieve the captcha API token and secrets. After doing so, captcha protection may be enabled via:
-
-- `-captcha.token` is the API token for ReCaptcha
-- `-captcha.secret` is the API secret for ReCaptcha
-
-Sybil protection via Twitter requires an API key as of 15th December, 2020. To obtain it, a Twitter user must be upgraded to developer status and a new Twitter App deployed with it. The app's `Bearer` token is required by the faucet to retrieve tweet data:
-
-- `-twitter.token` is the Bearer token for `v2` API access
-- `-twitter.token.v1` is the Bearer token for `v1` API access
-
-Sybil protection via Facebook uses the website to directly download post data thus does not currently require an API configuration.
-
-## Miscellaneous
-
-Beside the above - mostly essential - CLI flags, there are a number that can be used to fine-tune the `faucet`'s operation. Please see `faucet --help` for a full list.
diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go
deleted file mode 100644
index e4d6ad697..000000000
--- a/cmd/faucet/faucet.go
+++ /dev/null
@@ -1,891 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-// faucet is an Ether faucet backed by a light client.
-package main
-
-import (
- "bytes"
- "context"
- _ "embed"
- "encoding/json"
- "errors"
- "flag"
- "fmt"
- "html/template"
- "io"
- "math"
- "math/big"
- "net/http"
- "net/url"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/accounts/keystore"
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/ethconfig"
- "github.com/ethereum/go-ethereum/ethclient"
- "github.com/ethereum/go-ethereum/ethstats"
- "github.com/ethereum/go-ethereum/internal/version"
- "github.com/ethereum/go-ethereum/les"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nat"
- "github.com/ethereum/go-ethereum/params"
- "github.com/gorilla/websocket"
-)
-
-var (
- genesisFlag = flag.String("genesis", "", "Genesis json file to seed the chain with")
- apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection")
- ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection")
- bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with")
- netFlag = flag.Uint64("network", 0, "Network ID to use for the Ethereum protocol")
- statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string")
-
- netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet")
- payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request")
- minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds")
- tiersFlag = flag.Int("faucet.tiers", 3, "Number of funding tiers to enable (x3 time, x2.5 funds)")
-
- accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
- accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
-
- captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
- captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side")
-
- noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication")
- logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet")
-
- twitterTokenFlag = flag.String("twitter.token", "", "Bearer token to authenticate with the v2 Twitter API")
- twitterTokenV1Flag = flag.String("twitter.token.v1", "", "Bearer token to authenticate with the v1.1 Twitter API")
-
- goerliFlag = flag.Bool("goerli", false, "Initializes the faucet with Görli network config")
- sepoliaFlag = flag.Bool("sepolia", false, "Initializes the faucet with Sepolia network config")
-)
-
-var (
- ether = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)
-)
-
-//go:embed faucet.html
-var websiteTmpl string
-
-func main() {
- // Parse the flags and set up the logger to print everything requested
- flag.Parse()
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
-
- // Construct the payout tiers
- amounts := make([]string, *tiersFlag)
- periods := make([]string, *tiersFlag)
- for i := 0; i < *tiersFlag; i++ {
- // Calculate the amount for the next tier and format it
- amount := float64(*payoutFlag) * math.Pow(2.5, float64(i))
- amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64))
- if amount == 1 {
- amounts[i] = strings.TrimSuffix(amounts[i], "s")
- }
- // Calculate the period for the next tier and format it
- period := *minutesFlag * int(math.Pow(3, float64(i)))
- periods[i] = fmt.Sprintf("%d mins", period)
- if period%60 == 0 {
- period /= 60
- periods[i] = fmt.Sprintf("%d hours", period)
-
- if period%24 == 0 {
- period /= 24
- periods[i] = fmt.Sprintf("%d days", period)
- }
- }
- if period == 1 {
- periods[i] = strings.TrimSuffix(periods[i], "s")
- }
- }
- website := new(bytes.Buffer)
- err := template.Must(template.New("").Parse(websiteTmpl)).Execute(website, map[string]interface{}{
- "Network": *netnameFlag,
- "Amounts": amounts,
- "Periods": periods,
- "Recaptcha": *captchaToken,
- "NoAuth": *noauthFlag,
- })
- if err != nil {
- log.Crit("Failed to render the faucet template", "err", err)
- }
- // Load and parse the genesis block requested by the user
- genesis, err := getGenesis(*genesisFlag, *goerliFlag, *sepoliaFlag)
- if err != nil {
- log.Crit("Failed to parse genesis config", "err", err)
- }
- // Convert the bootnodes to internal enode representations
- var enodes []*enode.Node
- for _, boot := range strings.Split(*bootFlag, ",") {
- if url, err := enode.Parse(enode.ValidSchemes, boot); err == nil {
- enodes = append(enodes, url)
- } else {
- log.Error("Failed to parse bootnode URL", "url", boot, "err", err)
- }
- }
- // Load up the account key and decrypt its password
- blob, err := os.ReadFile(*accPassFlag)
- if err != nil {
- log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err)
- }
- pass := strings.TrimSuffix(string(blob), "\n")
-
- ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP)
- if blob, err = os.ReadFile(*accJSONFlag); err != nil {
- log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err)
- }
- acc, err := ks.Import(blob, pass, pass)
- if err != nil && err != keystore.ErrAccountAlreadyExists {
- log.Crit("Failed to import faucet signer account", "err", err)
- }
- if err := ks.Unlock(acc, pass); err != nil {
- log.Crit("Failed to unlock faucet signer account", "err", err)
- }
- // Assemble and start the faucet light service
- faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes())
- if err != nil {
- log.Crit("Failed to start faucet", "err", err)
- }
- defer faucet.close()
-
- if err := faucet.listenAndServe(*apiPortFlag); err != nil {
- log.Crit("Failed to launch faucet API", "err", err)
- }
-}
-
-// request represents an accepted funding request.
-type request struct {
- Avatar string `json:"avatar"` // Avatar URL to make the UI nicer
- Account common.Address `json:"account"` // Ethereum address being funded
- Time time.Time `json:"time"` // Timestamp when the request was accepted
- Tx *types.Transaction `json:"tx"` // Transaction funding the account
-}
-
-// faucet represents a crypto faucet backed by an Ethereum light client.
-type faucet struct {
- config *params.ChainConfig // Chain configurations for signing
- stack *node.Node // Ethereum protocol stack
- client *ethclient.Client // Client connection to the Ethereum chain
- index []byte // Index page to serve up on the web
-
- keystore *keystore.KeyStore // Keystore containing the single signer
- account accounts.Account // Account funding user faucet requests
- head *types.Header // Current head header of the faucet
- balance *big.Int // Current balance of the faucet
- nonce uint64 // Current pending nonce of the faucet
- price *big.Int // Current gas price to issue funds with
-
- conns []*wsConn // Currently live websocket connections
- timeouts map[string]time.Time // History of users and their funding timeouts
- reqs []*request // Currently pending funding requests
- update chan struct{} // Channel to signal request updates
-
- lock sync.RWMutex // Lock protecting the faucet's internals
-}
-
-// wsConn wraps a websocket connection with a write mutex as the underlying
-// websocket library does not synchronize access to the stream.
-type wsConn struct {
- conn *websocket.Conn
- wlock sync.Mutex
-}
-
-func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) {
- // Assemble the raw devp2p protocol stack
- git, _ := version.VCS()
- stack, err := node.New(&node.Config{
- Name: "geth",
- Version: params.VersionWithCommit(git.Commit, git.Date),
- DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"),
- P2P: p2p.Config{
- NAT: nat.Any(),
- NoDiscovery: true,
- DiscoveryV5: true,
- ListenAddr: fmt.Sprintf(":%d", port),
- MaxPeers: 25,
- BootstrapNodesV5: enodes,
- },
- })
- if err != nil {
- return nil, err
- }
-
- // Assemble the Ethereum light client protocol
- cfg := ethconfig.Defaults
- cfg.SyncMode = downloader.LightSync
- cfg.NetworkId = network
- cfg.Genesis = genesis
- utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock().Hash())
-
- lesBackend, err := les.New(stack, &cfg)
- if err != nil {
- return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err)
- }
-
- // Assemble the ethstats monitoring and reporting service'
- if stats != "" {
- if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil {
- return nil, err
- }
- }
- // Boot up the client and ensure it connects to bootnodes
- if err := stack.Start(); err != nil {
- return nil, err
- }
- for _, boot := range enodes {
- old, err := enode.Parse(enode.ValidSchemes, boot.String())
- if err == nil {
- stack.Server().AddPeer(old)
- }
- }
- // Attach to the client and retrieve and interesting metadatas
- api := stack.Attach()
- client := ethclient.NewClient(api)
-
- return &faucet{
- config: genesis.Config,
- stack: stack,
- client: client,
- index: index,
- keystore: ks,
- account: ks.Accounts()[0],
- timeouts: make(map[string]time.Time),
- update: make(chan struct{}, 1),
- }, nil
-}
-
-// close terminates the Ethereum connection and tears down the faucet.
-func (f *faucet) close() error {
- return f.stack.Close()
-}
-
-// listenAndServe registers the HTTP handlers for the faucet and boots it up
-// for service user funding requests.
-func (f *faucet) listenAndServe(port int) error {
- go f.loop()
-
- http.HandleFunc("/", f.webHandler)
- http.HandleFunc("/api", f.apiHandler)
- return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
-}
-
-// webHandler handles all non-api requests, simply flattening and returning the
-// faucet website.
-func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
- w.Write(f.index)
-}
-
-// apiHandler handles requests for Ether grants and transaction statuses.
-func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
- upgrader := websocket.Upgrader{}
- conn, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- return
- }
-
- // Start tracking the connection and drop at the end
- defer conn.Close()
-
- f.lock.Lock()
- wsconn := &wsConn{conn: conn}
- f.conns = append(f.conns, wsconn)
- f.lock.Unlock()
-
- defer func() {
- f.lock.Lock()
- for i, c := range f.conns {
- if c.conn == conn {
- f.conns = append(f.conns[:i], f.conns[i+1:]...)
- break
- }
- }
- f.lock.Unlock()
- }()
- // Gather the initial stats from the network to report
- var (
- head *types.Header
- balance *big.Int
- nonce uint64
- )
- for head == nil || balance == nil {
- // Retrieve the current stats cached by the faucet
- f.lock.RLock()
- if f.head != nil {
- head = types.CopyHeader(f.head)
- }
- if f.balance != nil {
- balance = new(big.Int).Set(f.balance)
- }
- nonce = f.nonce
- f.lock.RUnlock()
-
- if head == nil || balance == nil {
- // Report the faucet offline until initial stats are ready
- //lint:ignore ST1005 This error is to be displayed in the browser
- if err = sendError(wsconn, errors.New("Faucet offline")); err != nil {
- log.Warn("Failed to send faucet error to client", "err", err)
- return
- }
- time.Sleep(3 * time.Second)
- }
- }
- // Send over the initial stats and the latest header
- f.lock.RLock()
- reqs := f.reqs
- f.lock.RUnlock()
- if err = send(wsconn, map[string]interface{}{
- "funds": new(big.Int).Div(balance, ether),
- "funded": nonce,
- "peers": f.stack.Server().PeerCount(),
- "requests": reqs,
- }, 3*time.Second); err != nil {
- log.Warn("Failed to send initial stats to client", "err", err)
- return
- }
- if err = send(wsconn, head, 3*time.Second); err != nil {
- log.Warn("Failed to send initial header to client", "err", err)
- return
- }
- // Keep reading requests from the websocket until the connection breaks
- for {
- // Fetch the next funding request and validate against github
- var msg struct {
- URL string `json:"url"`
- Tier uint `json:"tier"`
- Captcha string `json:"captcha"`
- }
- if err = conn.ReadJSON(&msg); err != nil {
- return
- }
- if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") {
- if err = sendError(wsconn, errors.New("URL doesn't link to supported services")); err != nil {
- log.Warn("Failed to send URL error to client", "err", err)
- return
- }
- continue
- }
- if msg.Tier >= uint(*tiersFlag) {
- //lint:ignore ST1005 This error is to be displayed in the browser
- if err = sendError(wsconn, errors.New("Invalid funding tier requested")); err != nil {
- log.Warn("Failed to send tier error to client", "err", err)
- return
- }
- continue
- }
- log.Info("Faucet funds requested", "url", msg.URL, "tier", msg.Tier)
-
- // If captcha verifications are enabled, make sure we're not dealing with a robot
- if *captchaToken != "" {
- form := url.Values{}
- form.Add("secret", *captchaSecret)
- form.Add("response", msg.Captcha)
-
- res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
- if err != nil {
- if err = sendError(wsconn, err); err != nil {
- log.Warn("Failed to send captcha post error to client", "err", err)
- return
- }
- continue
- }
- var result struct {
- Success bool `json:"success"`
- Errors json.RawMessage `json:"error-codes"`
- }
- err = json.NewDecoder(res.Body).Decode(&result)
- res.Body.Close()
- if err != nil {
- if err = sendError(wsconn, err); err != nil {
- log.Warn("Failed to send captcha decode error to client", "err", err)
- return
- }
- continue
- }
- if !result.Success {
- log.Warn("Captcha verification failed", "err", string(result.Errors))
- //lint:ignore ST1005 it's funny and the robot won't mind
- if err = sendError(wsconn, errors.New("Beep-bop, you're a robot!")); err != nil {
- log.Warn("Failed to send captcha failure to client", "err", err)
- return
- }
- continue
- }
- }
- // Retrieve the Ethereum address to fund, the requesting user and a profile picture
- var (
- id string
- username string
- avatar string
- address common.Address
- )
- switch {
- case strings.HasPrefix(msg.URL, "https://twitter.com/"):
- id, username, avatar, address, err = authTwitter(msg.URL, *twitterTokenV1Flag, *twitterTokenFlag)
- case strings.HasPrefix(msg.URL, "https://www.facebook.com/"):
- username, avatar, address, err = authFacebook(msg.URL)
- id = username
- case *noauthFlag:
- username, avatar, address, err = authNoAuth(msg.URL)
- id = username
- default:
- //lint:ignore ST1005 This error is to be displayed in the browser
- err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues")
- }
- if err != nil {
- if err = sendError(wsconn, err); err != nil {
- log.Warn("Failed to send prefix error to client", "err", err)
- return
- }
- continue
- }
- log.Info("Faucet request valid", "url", msg.URL, "tier", msg.Tier, "user", username, "address", address)
-
- // Ensure the user didn't request funds too recently
- f.lock.Lock()
- var (
- fund bool
- timeout time.Time
- )
- if timeout = f.timeouts[id]; time.Now().After(timeout) {
- // User wasn't funded recently, create the funding transaction
- amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether)
- amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil))
- amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
-
- tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil)
- signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID)
- if err != nil {
- f.lock.Unlock()
- if err = sendError(wsconn, err); err != nil {
- log.Warn("Failed to send transaction creation error to client", "err", err)
- return
- }
- continue
- }
- // Submit the transaction and mark as funded if successful
- if err := f.client.SendTransaction(context.Background(), signed); err != nil {
- f.lock.Unlock()
- if err = sendError(wsconn, err); err != nil {
- log.Warn("Failed to send transaction transmission error to client", "err", err)
- return
- }
- continue
- }
- f.reqs = append(f.reqs, &request{
- Avatar: avatar,
- Account: address,
- Time: time.Now(),
- Tx: signed,
- })
- timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute
- grace := timeout / 288 // 24h timeout => 5m grace
-
- f.timeouts[id] = time.Now().Add(timeout - grace)
- fund = true
- }
- f.lock.Unlock()
-
- // Send an error if too frequent funding, othewise a success
- if !fund {
- if err = sendError(wsconn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple
- log.Warn("Failed to send funding error to client", "err", err)
- return
- }
- continue
- }
- if err = sendSuccess(wsconn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil {
- log.Warn("Failed to send funding success to client", "err", err)
- return
- }
- select {
- case f.update <- struct{}{}:
- default:
- }
- }
-}
-
-// refresh attempts to retrieve the latest header from the chain and extract the
-// associated faucet balance and nonce for connectivity caching.
-func (f *faucet) refresh(head *types.Header) error {
- // Ensure a state update does not run for too long
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
-
- // If no header was specified, use the current chain head
- var err error
- if head == nil {
- if head, err = f.client.HeaderByNumber(ctx, nil); err != nil {
- return err
- }
- }
- // Retrieve the balance, nonce and gas price from the current head
- var (
- balance *big.Int
- nonce uint64
- price *big.Int
- )
- if balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number); err != nil {
- return err
- }
- if nonce, err = f.client.NonceAt(ctx, f.account.Address, head.Number); err != nil {
- return err
- }
- if price, err = f.client.SuggestGasPrice(ctx); err != nil {
- return err
- }
- // Everything succeeded, update the cached stats and eject old requests
- f.lock.Lock()
- f.head, f.balance = head, balance
- f.price, f.nonce = price, nonce
- for len(f.reqs) > 0 && f.reqs[0].Tx.Nonce() < f.nonce {
- f.reqs = f.reqs[1:]
- }
- f.lock.Unlock()
-
- return nil
-}
-
-// loop keeps waiting for interesting events and pushes them out to connected
-// websockets.
-func (f *faucet) loop() {
- // Wait for chain events and push them to clients
- heads := make(chan *types.Header, 16)
- sub, err := f.client.SubscribeNewHead(context.Background(), heads)
- if err != nil {
- log.Crit("Failed to subscribe to head events", "err", err)
- }
- defer sub.Unsubscribe()
-
- // Start a goroutine to update the state from head notifications in the background
- update := make(chan *types.Header)
-
- go func() {
- for head := range update {
- // New chain head arrived, query the current stats and stream to clients
- timestamp := time.Unix(int64(head.Time), 0)
- if time.Since(timestamp) > time.Hour {
- log.Warn("Skipping faucet refresh, head too old", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp))
- continue
- }
- if err := f.refresh(head); err != nil {
- log.Warn("Failed to update faucet state", "block", head.Number, "hash", head.Hash(), "err", err)
- continue
- }
- // Faucet state retrieved, update locally and send to clients
- f.lock.RLock()
- log.Info("Updated faucet state", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp), "balance", f.balance, "nonce", f.nonce, "price", f.price)
-
- balance := new(big.Int).Div(f.balance, ether)
- peers := f.stack.Server().PeerCount()
-
- for _, conn := range f.conns {
- if err := send(conn, map[string]interface{}{
- "funds": balance,
- "funded": f.nonce,
- "peers": peers,
- "requests": f.reqs,
- }, time.Second); err != nil {
- log.Warn("Failed to send stats to client", "err", err)
- conn.conn.Close()
- continue
- }
- if err := send(conn, head, time.Second); err != nil {
- log.Warn("Failed to send header to client", "err", err)
- conn.conn.Close()
- }
- }
- f.lock.RUnlock()
- }
- }()
- // Wait for various events and assing to the appropriate background threads
- for {
- select {
- case head := <-heads:
- // New head arrived, send if for state update if there's none running
- select {
- case update <- head:
- default:
- }
-
- case <-f.update:
- // Pending requests updated, stream to clients
- f.lock.RLock()
- for _, conn := range f.conns {
- if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil {
- log.Warn("Failed to send requests to client", "err", err)
- conn.conn.Close()
- }
- }
- f.lock.RUnlock()
- }
- }
-}
-
-// sends transmits a data packet to the remote end of the websocket, but also
-// setting a write deadline to prevent waiting forever on the node.
-func send(conn *wsConn, value interface{}, timeout time.Duration) error {
- if timeout == 0 {
- timeout = 60 * time.Second
- }
- conn.wlock.Lock()
- defer conn.wlock.Unlock()
- conn.conn.SetWriteDeadline(time.Now().Add(timeout))
- return conn.conn.WriteJSON(value)
-}
-
-// sendError transmits an error to the remote end of the websocket, also setting
-// the write deadline to 1 second to prevent waiting forever.
-func sendError(conn *wsConn, err error) error {
- return send(conn, map[string]string{"error": err.Error()}, time.Second)
-}
-
-// sendSuccess transmits a success message to the remote end of the websocket, also
-// setting the write deadline to 1 second to prevent waiting forever.
-func sendSuccess(conn *wsConn, msg string) error {
- return send(conn, map[string]string{"success": msg}, time.Second)
-}
-
-// authTwitter tries to authenticate a faucet request using Twitter posts, returning
-// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success.
-func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, common.Address, error) {
- // Ensure the user specified a meaningful URL, no fancy nonsense
- parts := strings.Split(url, "/")
- if len(parts) < 4 || parts[len(parts)-2] != "status" {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL")
- }
- // Strip any query parameters from the tweet id and ensure it's numeric
- tweetID := strings.Split(parts[len(parts)-1], "?")[0]
- if !regexp.MustCompile("^[0-9]+$").MatchString(tweetID) {
- return "", "", "", common.Address{}, errors.New("Invalid Tweet URL")
- }
- // Twitter's API isn't really friendly with direct links.
- // It is restricted to 300 queries / 15 minute with an app api key.
- // Anything more will require read only authorization from the users and that we want to avoid.
-
- // If Twitter bearer token is provided, use the API, selecting the version
- // the user would prefer (currently there's a limit of 1 v2 app / developer
- // but unlimited v1.1 apps).
- switch {
- case tokenV1 != "":
- return authTwitterWithTokenV1(tweetID, tokenV1)
- case tokenV2 != "":
- return authTwitterWithTokenV2(tweetID, tokenV2)
- }
- // Twitter API token isn't provided so we just load the public posts
- // and scrape it for the Ethereum address and profile URL. We need to load
- // the mobile page though since the main page loads tweet contents via JS.
- url = strings.Replace(url, "https://twitter.com/", "https://mobile.twitter.com/", 1)
-
- res, err := http.Get(url)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
- defer res.Body.Close()
-
- // Resolve the username from the final redirect, no intermediate junk
- parts = strings.Split(res.Request.URL.String(), "/")
- if len(parts) < 4 || parts[len(parts)-2] != "status" {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL")
- }
- username := parts[len(parts)-3]
-
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
- address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body)))
- if address == (common.Address{}) {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund")
- }
- var avatar string
- if parts = regexp.MustCompile(`src="([^"]+twimg\.com/profile_images[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 {
- avatar = parts[1]
- }
- return username + "@twitter", username, avatar, address, nil
-}
-
-// authTwitterWithTokenV1 tries to authenticate a faucet request using Twitter's v1
-// API, returning the user id, username, avatar URL and Ethereum address to fund on
-// success.
-func authTwitterWithTokenV1(tweetID string, token string) (string, string, string, common.Address, error) {
- // Query the tweet details from Twitter
- url := fmt.Sprintf("https://api.twitter.com/1.1/statuses/show.json?id=%s", tweetID)
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := http.DefaultClient.Do(req)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
- defer res.Body.Close()
-
- var result struct {
- Text string `json:"text"`
- User struct {
- ID string `json:"id_str"`
- Username string `json:"screen_name"`
- Avatar string `json:"profile_image_url"`
- } `json:"user"`
- }
- err = json.NewDecoder(res.Body).Decode(&result)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
- address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Text))
- if address == (common.Address{}) {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund")
- }
- return result.User.ID + "@twitter", result.User.Username, result.User.Avatar, address, nil
-}
-
-// authTwitterWithTokenV2 tries to authenticate a faucet request using Twitter's v2
-// API, returning the user id, username, avatar URL and Ethereum address to fund on
-// success.
-func authTwitterWithTokenV2(tweetID string, token string) (string, string, string, common.Address, error) {
- // Query the tweet details from Twitter
- url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", tweetID)
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- res, err := http.DefaultClient.Do(req)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
- defer res.Body.Close()
-
- var result struct {
- Data struct {
- AuthorID string `json:"author_id"`
- Text string `json:"text"`
- } `json:"data"`
- Includes struct {
- Users []struct {
- ID string `json:"id"`
- Username string `json:"username"`
- Avatar string `json:"profile_image_url"`
- } `json:"users"`
- } `json:"includes"`
- }
-
- err = json.NewDecoder(res.Body).Decode(&result)
- if err != nil {
- return "", "", "", common.Address{}, err
- }
-
- address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Data.Text))
- if address == (common.Address{}) {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund")
- }
- return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].Avatar, address, nil
-}
-
-// authFacebook tries to authenticate a faucet request using Facebook posts,
-// returning the username, avatar URL and Ethereum address to fund on success.
-func authFacebook(url string) (string, string, common.Address, error) {
- // Ensure the user specified a meaningful URL, no fancy nonsense
- parts := strings.Split(strings.Split(url, "?")[0], "/")
- if parts[len(parts)-1] == "" {
- parts = parts[0 : len(parts)-1]
- }
- if len(parts) < 4 || parts[len(parts)-2] != "posts" {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", common.Address{}, errors.New("Invalid Facebook post URL")
- }
- username := parts[len(parts)-3]
-
- // Facebook's Graph API isn't really friendly with direct links. Still, we don't
- // want to do ask read permissions from users, so just load the public posts and
- // scrape it for the Ethereum address and profile URL.
- //
- // Facebook recently changed their desktop webpage to use AJAX for loading post
- // content, so switch over to the mobile site for now. Will probably end up having
- // to use the API eventually.
- crawl := strings.Replace(url, "www.facebook.com", "m.facebook.com", 1)
-
- res, err := http.Get(crawl)
- if err != nil {
- return "", "", common.Address{}, err
- }
- defer res.Body.Close()
-
- body, err := io.ReadAll(res.Body)
- if err != nil {
- return "", "", common.Address{}, err
- }
- address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body)))
- if address == (common.Address{}) {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", common.Address{}, errors.New("No Ethereum address found to fund. Please check the post URL and verify that it can be viewed publicly.")
- }
- var avatar string
- if parts = regexp.MustCompile(`src="([^"]+fbcdn\.net[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 {
- avatar = parts[1]
- }
- return username + "@facebook", avatar, address, nil
-}
-
-// authNoAuth tries to interpret a faucet request as a plain Ethereum address,
-// without actually performing any remote authentication. This mode is prone to
-// Byzantine attack, so only ever use for truly private networks.
-func authNoAuth(url string) (string, string, common.Address, error) {
- address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(url))
- if address == (common.Address{}) {
- //lint:ignore ST1005 This error is to be displayed in the browser
- return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
- }
- return address.Hex() + "@noauth", "", address, nil
-}
-
-// getGenesis returns a genesis based on input args
-func getGenesis(genesisFlag string, goerliFlag bool, sepoliaFlag bool) (*core.Genesis, error) {
- switch {
- case genesisFlag != "":
- var genesis core.Genesis
- err := common.LoadJSON(genesisFlag, &genesis)
- return &genesis, err
- case goerliFlag:
- return core.DefaultGoerliGenesisBlock(), nil
- case sepoliaFlag:
- return core.DefaultSepoliaGenesisBlock(), nil
- default:
- return nil, errors.New("no genesis flag provided")
- }
-}
diff --git a/cmd/faucet/faucet.html b/cmd/faucet/faucet.html
deleted file mode 100644
index dad5ad84f..000000000
--- a/cmd/faucet/faucet.html
+++ /dev/null
@@ -1,233 +0,0 @@
-
-
-
-
-
-
-
- {{.Network}}: Authenticated Faucet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{.Network}} Authenticated Faucet
-
-
-
-
-
-
-
-
-
-
- {{if .Recaptcha}}
- {{end}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- How does this work?
- This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to common 3rd party social network accounts. Anyone having a Twitter or Facebook account may request funds within the permitted limits.
-
-
- - To request funds via Twitter, make a tweet with your Ethereum address pasted into the contents (surrounding text doesn't matter).
Copy-paste the tweets URL into the above input box and fire away!
-
-
- - To request funds via Facebook, publish a new public post with your Ethereum address embedded into the content (surrounding text doesn't matter).
Copy-paste the posts URL into the above input box and fire away!
-
- {{if .NoAuth}}
-
- - To request funds without authentication, simply copy-paste your Ethereum address into the above input box (surrounding text doesn't matter) and fire away.
This mode is susceptible to Byzantine attacks. Only use for debugging or private networks!
- {{end}}
-
- You can track the current pending requests below the input field to see how much you have to wait until your turn comes.
- {{if .Recaptcha}}The faucet is running invisible reCaptcha protection against bots.{{end}}
-
-
-
-
-
- {{if .Recaptcha}}
- {{end}}
-
-
diff --git a/cmd/faucet/faucet_test.go b/cmd/faucet/faucet_test.go
deleted file mode 100644
index 58a1f22b5..000000000
--- a/cmd/faucet/faucet_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-func TestFacebook(t *testing.T) {
- // TODO: Remove facebook auth or implement facebook api, which seems to require an API key
- t.Skipf("The facebook access is flaky, needs to be reimplemented or removed")
- for _, tt := range []struct {
- url string
- want common.Address
- }{
- {
- "https://www.facebook.com/fooz.gazonk/posts/2837228539847129",
- common.HexToAddress("0xDeadDeaDDeaDbEefbEeFbEEfBeeFBeefBeeFbEEF"),
- },
- } {
- _, _, gotAddress, err := authFacebook(tt.url)
- if err != nil {
- t.Fatal(err)
- }
- if gotAddress != tt.want {
- t.Fatalf("address wrong, have %v want %v", gotAddress, tt.want)
- }
- }
-}
diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go
index 84b9c33c2..ea3a7c3b6 100644
--- a/cmd/geth/accountcmd_test.go
+++ b/cmd/geth/accountcmd_test.go
@@ -43,11 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string {
}
func TestAccountListEmpty(t *testing.T) {
+ t.Parallel()
geth := runGeth(t, "account", "list")
geth.ExpectExit()
}
func TestAccountList(t *testing.T) {
+ t.Parallel()
datadir := tmpDatadirWithKeystore(t)
var want = `
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
@@ -74,6 +76,7 @@ Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}\k
}
func TestAccountNew(t *testing.T) {
+ t.Parallel()
geth := runGeth(t, "account", "new", "--lightkdf")
defer geth.ExpectExit()
geth.Expect(`
@@ -96,6 +99,7 @@ Path of the secret key file: .*UTC--.+--[0-9a-f]{40}
}
func TestAccountImport(t *testing.T) {
+ t.Parallel()
tests := []struct{ name, key, output string }{
{
name: "correct account",
@@ -118,6 +122,7 @@ func TestAccountImport(t *testing.T) {
}
func TestAccountHelp(t *testing.T) {
+ t.Parallel()
geth := runGeth(t, "account", "-h")
geth.WaitExit()
if have, want := geth.ExitStatus(), 0; have != want {
@@ -147,6 +152,7 @@ func importAccountWithExpect(t *testing.T, key string, expected string) {
}
func TestAccountNewBadRepeat(t *testing.T) {
+ t.Parallel()
geth := runGeth(t, "account", "new", "--lightkdf")
defer geth.ExpectExit()
geth.Expect(`
@@ -159,6 +165,7 @@ Fatal: Passwords do not match
}
func TestAccountUpdate(t *testing.T) {
+ t.Parallel()
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t, "account", "update",
"--datadir", datadir, "--lightkdf",
@@ -175,6 +182,7 @@ Repeat password: {{.InputLine "foobar2"}}
}
func TestWalletImport(t *testing.T) {
+ t.Parallel()
geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
defer geth.ExpectExit()
geth.Expect(`
@@ -190,6 +198,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
}
func TestWalletImportBadPassword(t *testing.T) {
+ t.Parallel()
geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
defer geth.ExpectExit()
geth.Expect(`
@@ -200,6 +209,7 @@ Fatal: could not decrypt key with given password
}
func TestUnlockFlag(t *testing.T) {
+ t.Parallel()
geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t),
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "console", "--exec", "loadScript('testdata/empty.js')")
geth.Expect(`
@@ -222,6 +232,7 @@ undefined
}
func TestUnlockFlagWrongPassword(t *testing.T) {
+ t.Parallel()
geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t),
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "console", "--exec", "loadScript('testdata/empty.js')")
@@ -240,6 +251,7 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could
// https://github.com/ethereum/go-ethereum/issues/1785
func TestUnlockFlagMultiIndex(t *testing.T) {
+ t.Parallel()
geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t),
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--unlock", "0,2", "console", "--exec", "loadScript('testdata/empty.js')")
@@ -266,6 +278,7 @@ undefined
}
func TestUnlockFlagPasswordFile(t *testing.T) {
+ t.Parallel()
geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t),
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", "testdata/passwords.txt", "--unlock", "0,2", "console", "--exec", "loadScript('testdata/empty.js')")
@@ -287,6 +300,7 @@ undefined
}
func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) {
+ t.Parallel()
geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t),
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password",
"testdata/wrong-passwords.txt", "--unlock", "0,2")
@@ -297,6 +311,7 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password)
}
func TestUnlockFlagAmbiguous(t *testing.T) {
+ t.Parallel()
store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t),
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore",
@@ -336,6 +351,7 @@ undefined
}
func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) {
+ t.Parallel()
store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t),
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore",
diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index 5663963e3..3b4f516af 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -137,20 +137,7 @@ The import-preimages command imports hash preimages from an RLP encoded stream.
It's deprecated, please use "geth db import" instead.
`,
}
- exportPreimagesCommand = &cli.Command{
- Action: exportPreimages,
- Name: "export-preimages",
- Usage: "Export the preimage database into an RLP stream",
- ArgsUsage: "",
- Flags: flags.Merge([]cli.Flag{
- utils.CacheFlag,
- utils.SyncModeFlag,
- }, utils.DatabaseFlags),
- Description: `
-The export-preimages command exports hash preimages to an RLP encoded stream.
-It's deprecated, please use "geth db export" instead.
-`,
- }
+
dumpCommand = &cli.Command{
Action: dump,
Name: "dump",
@@ -211,7 +198,7 @@ func initGenesis(ctx *cli.Context) error {
}
defer chaindb.Close()
- triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false)
+ triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
defer triedb.Close()
_, hash, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
@@ -224,14 +211,21 @@ func initGenesis(ctx *cli.Context) error {
}
func dumpGenesis(ctx *cli.Context) error {
- // if there is a testnet preset enabled, dump that
+ // check if there is a testnet preset enabled
+ var genesis *core.Genesis
if utils.IsNetworkPreset(ctx) {
- genesis := utils.MakeGenesis(ctx)
+ genesis = utils.MakeGenesis(ctx)
+ } else if ctx.IsSet(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
+ genesis = core.DeveloperGenesisBlock(11_500_000, nil)
+ }
+
+ if genesis != nil {
if err := json.NewEncoder(os.Stdout).Encode(genesis); err != nil {
utils.Fatalf("could not encode genesis: %s", err)
}
return nil
}
+
// dump whatever already exists in the datadir
stack, _ := makeConfigNode(ctx)
for _, name := range []string{"chaindata", "lightchaindata"} {
@@ -256,7 +250,7 @@ func dumpGenesis(ctx *cli.Context) error {
if ctx.IsSet(utils.DataDirFlag.Name) {
utils.Fatalf("no existing datadir at %s", stack.Config().DataDir)
}
- utils.Fatalf("no network preset provided, no existing genesis in the default datadir")
+ utils.Fatalf("no network preset provided, and no genesis exists in the default datadir")
return nil
}
@@ -379,6 +373,9 @@ func exportChain(ctx *cli.Context) error {
}
// importPreimages imports preimage data from the specified file.
+// it is deprecated, and the export function has been removed, but
+// the import function is kept around for the time being so that
+// older file formats can still be imported.
func importPreimages(ctx *cli.Context) error {
if ctx.Args().Len() < 1 {
utils.Fatalf("This command requires an argument.")
@@ -398,25 +395,6 @@ func importPreimages(ctx *cli.Context) error {
return nil
}
-// exportPreimages dumps the preimage data to specified json file in streaming way.
-func exportPreimages(ctx *cli.Context) error {
- if ctx.Args().Len() < 1 {
- utils.Fatalf("This command requires an argument.")
- }
- stack, _ := makeConfigNode(ctx)
- defer stack.Close()
-
- db := utils.MakeChainDatabase(ctx, stack, true)
- defer db.Close()
- start := time.Now()
-
- if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil {
- utils.Fatalf("Export error: %v\n", err)
- }
- fmt.Printf("Export done in %v\n", time.Since(start))
- return nil
-}
-
func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, ethdb.Database, common.Hash, error) {
db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close()
@@ -485,7 +463,7 @@ func dump(ctx *cli.Context) error {
if err != nil {
return err
}
- triedb := utils.MakeTrieDatabase(ctx, db, true, true) // always enable preimage lookup
+ triedb := utils.MakeTrieDatabase(ctx, db, true, true, false) // always enable preimage lookup
defer triedb.Close()
state, err := state.New(root, state.NewDatabaseWithNodeDB(db, triedb), nil)
@@ -495,11 +473,6 @@ func dump(ctx *cli.Context) error {
if ctx.Bool(utils.IterativeOutputFlag.Name) {
state.IterativeDump(conf, json.NewEncoder(os.Stdout))
} else {
- if conf.OnlyWithAddresses {
- fmt.Fprintf(os.Stderr, "If you want to include accounts with missing preimages, you need iterative output, since"+
- " otherwise the accounts will overwrite each other in the resulting mapping.")
- return errors.New("incompatible options")
- }
fmt.Println(string(state.Dump(conf)))
}
return nil
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 027dac7bd..5f52f1df5 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/eth/catalyst"
- "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags"
@@ -222,7 +221,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
}
catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon)
stack.RegisterLifecycle(simBeacon)
- } else if cfg.Eth.SyncMode != downloader.LightSync {
+ } else {
err := catalyst.Register(stack, eth)
if err != nil {
utils.Fatalf("failed to register catalyst service: %v", err)
diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go
index 5046906c0..ef6ef5f28 100644
--- a/cmd/geth/consolecmd_test.go
+++ b/cmd/geth/consolecmd_test.go
@@ -50,6 +50,7 @@ func runMinimalGeth(t *testing.T, args ...string) *testgeth {
// Tests that a node embedded within a console can be started up properly and
// then terminated by closing the input stream.
func TestConsoleWelcome(t *testing.T) {
+ t.Parallel()
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
// Start a geth console, make sure it's cleaned up and terminate the console
diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go
index ab2626c12..1d885bd58 100644
--- a/cmd/geth/dbcmd.go
+++ b/cmd/geth/dbcmd.go
@@ -43,12 +43,22 @@ import (
)
var (
+ removeStateDataFlag = &cli.BoolFlag{
+ Name: "remove.state",
+ Usage: "If set, selects the state data for removal",
+ }
+ removeChainDataFlag = &cli.BoolFlag{
+ Name: "remove.chain",
+ Usage: "If set, selects the state data for removal",
+ }
+
removedbCommand = &cli.Command{
Action: removeDB,
Name: "removedb",
Usage: "Remove blockchain and state databases",
ArgsUsage: "",
- Flags: utils.DatabaseFlags,
+ Flags: flags.Merge(utils.DatabaseFlags,
+ []cli.Flag{removeStateDataFlag, removeChainDataFlag}),
Description: `
Remove blockchain and state databases`,
}
@@ -198,60 +208,85 @@ WARNING: This is a low-level operation which may cause database corruption!`,
func removeDB(ctx *cli.Context) error {
stack, config := makeConfigNode(ctx)
- // Remove the full node state database
- path := stack.ResolvePath("chaindata")
- if common.FileExist(path) {
- confirmAndRemoveDB(path, "full node state database")
- } else {
- log.Info("Full node state database missing", "path", path)
- }
- // Remove the full node ancient database
- path = config.Eth.DatabaseFreezer
+ // Resolve folder paths.
+ var (
+ rootDir = stack.ResolvePath("chaindata")
+ ancientDir = config.Eth.DatabaseFreezer
+ )
switch {
- case path == "":
- path = filepath.Join(stack.ResolvePath("chaindata"), "ancient")
- case !filepath.IsAbs(path):
- path = config.Node.ResolvePath(path)
- }
- if common.FileExist(path) {
- confirmAndRemoveDB(path, "full node ancient database")
- } else {
- log.Info("Full node ancient database missing", "path", path)
- }
- // Remove the light node database
- path = stack.ResolvePath("lightchaindata")
- if common.FileExist(path) {
- confirmAndRemoveDB(path, "light node database")
- } else {
- log.Info("Light node database missing", "path", path)
+ case ancientDir == "":
+ ancientDir = filepath.Join(stack.ResolvePath("chaindata"), "ancient")
+ case !filepath.IsAbs(ancientDir):
+ ancientDir = config.Node.ResolvePath(ancientDir)
}
+ // Delete state data
+ statePaths := []string{rootDir, filepath.Join(ancientDir, rawdb.StateFreezerName)}
+ confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name)
+
+ // Delete ancient chain
+ chainPaths := []string{filepath.Join(ancientDir, rawdb.ChainFreezerName)}
+ confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name)
return nil
}
+// removeFolder deletes all files (not folders) inside the directory 'dir' (but
+// not files in subfolders).
+func removeFolder(dir string) {
+ filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ // If we're at the top level folder, recurse into
+ if path == dir {
+ return nil
+ }
+ // Delete all the files, but not subfolders
+ if !info.IsDir() {
+ os.Remove(path)
+ return nil
+ }
+ return filepath.SkipDir
+ })
+}
+
// confirmAndRemoveDB prompts the user for a last confirmation and removes the
-// folder if accepted.
-func confirmAndRemoveDB(database string, kind string) {
- confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database))
+// list of folders if accepted.
+func confirmAndRemoveDB(paths []string, kind string, ctx *cli.Context, removeFlagName string) {
+ var (
+ confirm bool
+ err error
+ )
+ msg := fmt.Sprintf("Location(s) of '%s': \n", kind)
+ for _, path := range paths {
+ msg += fmt.Sprintf("\t- %s\n", path)
+ }
+ fmt.Println(msg)
+ if ctx.IsSet(removeFlagName) {
+ confirm = ctx.Bool(removeFlagName)
+ if confirm {
+ fmt.Printf("Remove '%s'? [y/n] y\n", kind)
+ } else {
+ fmt.Printf("Remove '%s'? [y/n] n\n", kind)
+ }
+ } else {
+ confirm, err = prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove '%s'?", kind))
+ }
switch {
case err != nil:
utils.Fatalf("%v", err)
case !confirm:
- log.Info("Database deletion skipped", "path", database)
+ log.Info("Database deletion skipped", "kind", kind, "paths", paths)
default:
- start := time.Now()
- filepath.Walk(database, func(path string, info os.FileInfo, err error) error {
- // If we're at the top level folder, recurse into
- if path == database {
- return nil
+ var (
+ deleted []string
+ start = time.Now()
+ )
+ for _, path := range paths {
+ if common.FileExist(path) {
+ removeFolder(path)
+ deleted = append(deleted, path)
+ } else {
+ log.Info("Folder is not existent", "path", path)
}
- // Delete all the files, but not subfolders
- if !info.IsDir() {
- os.Remove(path)
- return nil
- }
- return filepath.SkipDir
- })
- log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start)))
+ }
+ log.Info("Database successfully deleted", "kind", kind, "paths", deleted, "elapsed", common.PrettyDuration(time.Since(start)))
}
}
@@ -482,7 +517,7 @@ func dbDumpTrie(ctx *cli.Context) error {
db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close()
- triedb := utils.MakeTrieDatabase(ctx, db, false, true)
+ triedb := utils.MakeTrieDatabase(ctx, db, false, true, false)
defer triedb.Close()
var (
diff --git a/cmd/geth/exportcmd_test.go b/cmd/geth/exportcmd_test.go
index bbf08d820..9570b1ffd 100644
--- a/cmd/geth/exportcmd_test.go
+++ b/cmd/geth/exportcmd_test.go
@@ -27,6 +27,7 @@ import (
// TestExport does a basic test of "geth export", exporting the test-genesis.
func TestExport(t *testing.T) {
+ t.Parallel()
outfile := fmt.Sprintf("%v/testExport.out", os.TempDir())
defer os.Remove(outfile)
geth := runGeth(t, "--datadir", initGeth(t), "export", outfile)
diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go
deleted file mode 100644
index b36c3265a..000000000
--- a/cmd/geth/les_test.go
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "context"
- "fmt"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-type gethrpc struct {
- name string
- rpc *rpc.Client
- geth *testgeth
- nodeInfo *p2p.NodeInfo
-}
-
-func (g *gethrpc) killAndWait() {
- g.geth.Kill()
- g.geth.WaitExit()
-}
-
-func (g *gethrpc) callRPC(result interface{}, method string, args ...interface{}) {
- if err := g.rpc.Call(&result, method, args...); err != nil {
- g.geth.Fatalf("callRPC %v: %v", method, err)
- }
-}
-
-func (g *gethrpc) addPeer(peer *gethrpc) {
- g.geth.Logf("%v.addPeer(%v)", g.name, peer.name)
- enode := peer.getNodeInfo().Enode
- peerCh := make(chan *p2p.PeerEvent)
- sub, err := g.rpc.Subscribe(context.Background(), "admin", peerCh, "peerEvents")
- if err != nil {
- g.geth.Fatalf("subscribe %v: %v", g.name, err)
- }
- defer sub.Unsubscribe()
- g.callRPC(nil, "admin_addPeer", enode)
- dur := 14 * time.Second
- timeout := time.After(dur)
- select {
- case ev := <-peerCh:
- g.geth.Logf("%v received event: type=%v, peer=%v", g.name, ev.Type, ev.Peer)
- case err := <-sub.Err():
- g.geth.Fatalf("%v sub error: %v", g.name, err)
- case <-timeout:
- g.geth.Error("timeout adding peer after", dur)
- }
-}
-
-// Use this function instead of `g.nodeInfo` directly
-func (g *gethrpc) getNodeInfo() *p2p.NodeInfo {
- if g.nodeInfo != nil {
- return g.nodeInfo
- }
- g.nodeInfo = &p2p.NodeInfo{}
- g.callRPC(&g.nodeInfo, "admin_nodeInfo")
- return g.nodeInfo
-}
-
-// ipcEndpoint resolves an IPC endpoint based on a configured value, taking into
-// account the set data folders as well as the designated platform we're currently
-// running on.
-func ipcEndpoint(ipcPath, datadir string) string {
- // On windows we can only use plain top-level pipes
- if runtime.GOOS == "windows" {
- if strings.HasPrefix(ipcPath, `\\.\pipe\`) {
- return ipcPath
- }
- return `\\.\pipe\` + ipcPath
- }
- // Resolve names into the data directory full paths otherwise
- if filepath.Base(ipcPath) == ipcPath {
- if datadir == "" {
- return filepath.Join(os.TempDir(), ipcPath)
- }
- return filepath.Join(datadir, ipcPath)
- }
- return ipcPath
-}
-
-// nextIPC ensures that each ipc pipe gets a unique name.
-// On linux, it works well to use ipc pipes all over the filesystem (in datadirs),
-// but windows require pipes to sit in "\\.\pipe\". Therefore, to run several
-// nodes simultaneously, we need to distinguish between them, which we do by
-// the pipe filename instead of folder.
-var nextIPC atomic.Uint32
-
-func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc {
- ipcName := fmt.Sprintf("geth-%d.ipc", nextIPC.Add(1))
- args = append([]string{"--networkid=42", "--port=0", "--authrpc.port", "0", "--ipcpath", ipcName}, args...)
- t.Logf("Starting %v with rpc: %v", name, args)
-
- g := &gethrpc{
- name: name,
- geth: runGeth(t, args...),
- }
- ipcpath := ipcEndpoint(ipcName, g.geth.Datadir)
- // We can't know exactly how long geth will take to start, so we try 10
- // times over a 5 second period.
- var err error
- for i := 0; i < 10; i++ {
- time.Sleep(500 * time.Millisecond)
- if g.rpc, err = rpc.Dial(ipcpath); err == nil {
- return g
- }
- }
- t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err)
- return nil
-}
-
-func initGeth(t *testing.T) string {
- args := []string{"--networkid=42", "init", "./testdata/clique.json"}
- t.Logf("Initializing geth: %v ", args)
- g := runGeth(t, args...)
- datadir := g.Datadir
- g.WaitExit()
- return datadir
-}
-
-func startLightServer(t *testing.T) *gethrpc {
- datadir := initGeth(t)
- t.Logf("Importing keys to geth")
- runGeth(t, "account", "import", "--datadir", datadir, "--password", "./testdata/password.txt", "--lightkdf", "./testdata/key.prv").WaitExit()
- account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105"
- server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--miner.etherbase=0x02f0d131f1f97aef08aec6e3291b957d9efe7105", "--mine", "--light.serve=100", "--light.maxpeers=1", "--discv4=false", "--nat=extip:127.0.0.1", "--verbosity=4")
- return server
-}
-
-func startClient(t *testing.T, name string) *gethrpc {
- datadir := initGeth(t)
- return startGethWithIpc(t, name, "--datadir", datadir, "--discv4=false", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4")
-}
-
-func TestPriorityClient(t *testing.T) {
- lightServer := startLightServer(t)
- defer lightServer.killAndWait()
-
- // Start client and add lightServer as peer
- freeCli := startClient(t, "freeCli")
- defer freeCli.killAndWait()
- freeCli.addPeer(lightServer)
-
- var peers []*p2p.PeerInfo
- freeCli.callRPC(&peers, "admin_peers")
- if len(peers) != 1 {
- t.Errorf("Expected: # of client peers == 1, actual: %v", len(peers))
- return
- }
-
- // Set up priority client, get its nodeID, increase its balance on the lightServer
- prioCli := startClient(t, "prioCli")
- defer prioCli.killAndWait()
- // 3_000_000_000 once we move to Go 1.13
- tokens := uint64(3000000000)
- lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens)
- prioCli.addPeer(lightServer)
-
- // Check if priority client is actually syncing and the regular client got kicked out
- prioCli.callRPC(&peers, "admin_peers")
- if len(peers) != 1 {
- t.Errorf("Expected: # of prio peers == 1, actual: %v", len(peers))
- }
-
- nodes := map[string]*gethrpc{
- lightServer.getNodeInfo().ID: lightServer,
- freeCli.getNodeInfo().ID: freeCli,
- prioCli.getNodeInfo().ID: prioCli,
- }
- time.Sleep(1 * time.Second)
- lightServer.callRPC(&peers, "admin_peers")
- peersWithNames := make(map[string]string)
- for _, p := range peers {
- peersWithNames[nodes[p.ID].name] = p.ID
- }
- if _, freeClientFound := peersWithNames[freeCli.name]; freeClientFound {
- t.Error("client is still a peer of lightServer", peersWithNames)
- }
- if _, prioClientFound := peersWithNames[prioCli.name]; !prioClientFound {
- t.Error("prio client is not among lightServer peers", peersWithNames)
- }
-}
diff --git a/cmd/geth/logging_test.go b/cmd/geth/logging_test.go
index af50e93f9..b5ce03f4b 100644
--- a/cmd/geth/logging_test.go
+++ b/cmd/geth/logging_test.go
@@ -21,6 +21,7 @@ package main
import (
"bufio"
"bytes"
+ "encoding/json"
"fmt"
"io"
"math/rand"
@@ -58,6 +59,7 @@ func censor(input string, start, end int) string {
}
func TestLogging(t *testing.T) {
+ t.Parallel()
testConsoleLogging(t, "terminal", 6, 24)
testConsoleLogging(t, "logfmt", 2, 26)
}
@@ -97,7 +99,55 @@ func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
}
}
+func TestJsonLogging(t *testing.T) {
+ t.Parallel()
+ haveB, err := runSelf("--log.format", "json", "logtest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ readFile, err := os.Open("testdata/logging/logtest-json.txt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ wantLines := split(readFile)
+ haveLines := split(bytes.NewBuffer(haveB))
+ for i, wantLine := range wantLines {
+ if i > len(haveLines)-1 {
+ t.Fatalf("format %v, line %d missing, want:%v", "json", i, wantLine)
+ }
+ haveLine := haveLines[i]
+ for strings.Contains(haveLine, "Unknown config environment variable") {
+ // This can happen on CI runs. Drop it.
+ haveLines = append(haveLines[:i], haveLines[i+1:]...)
+ haveLine = haveLines[i]
+ }
+ var have, want []byte
+ {
+ var h map[string]any
+ if err := json.Unmarshal([]byte(haveLine), &h); err != nil {
+ t.Fatal(err)
+ }
+ h["t"] = "xxx"
+ have, _ = json.Marshal(h)
+ }
+ {
+ var w map[string]any
+ if err := json.Unmarshal([]byte(wantLine), &w); err != nil {
+ t.Fatal(err)
+ }
+ w["t"] = "xxx"
+ want, _ = json.Marshal(w)
+ }
+ if !bytes.Equal(have, want) {
+ // show an intelligent diff
+ t.Logf(nicediff(have, want))
+ t.Errorf("file content wrong")
+ }
+ }
+}
+
func TestVmodule(t *testing.T) {
+ t.Parallel()
checkOutput := func(level int, want, wantNot string) {
t.Helper()
output, err := runSelf("--log.format", "terminal", "--verbosity=0", "--log.vmodule", fmt.Sprintf("logtestcmd_active.go=%d", level), "logtest")
@@ -145,6 +195,7 @@ func nicediff(have, want []byte) string {
}
func TestFileOut(t *testing.T) {
+ t.Parallel()
var (
have, want []byte
err error
@@ -165,6 +216,7 @@ func TestFileOut(t *testing.T) {
}
func TestRotatingFileOut(t *testing.T) {
+ t.Parallel()
var (
have, want []byte
err error
diff --git a/cmd/geth/logtestcmd_active.go b/cmd/geth/logtestcmd_active.go
index ebcc8de97..5cce1ec6a 100644
--- a/cmd/geth/logtestcmd_active.go
+++ b/cmd/geth/logtestcmd_active.go
@@ -19,12 +19,14 @@
package main
import (
+ "errors"
"fmt"
"math"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/log"
"github.com/holiman/uint256"
"github.com/urfave/cli/v2"
@@ -39,10 +41,19 @@ var logTestCommand = &cli.Command{
This command is only meant for testing.
`}
+type customQuotedStringer struct {
+}
+
+func (c customQuotedStringer) String() string {
+ return "output with 'quotes'"
+}
+
// logTest is an entry point which spits out some logs. This is used by testing
// to verify expected outputs
func logTest(ctx *cli.Context) error {
- log.ResetGlobalState()
+ // clear field padding map
+ debug.ResetLogging()
+
{ // big.Int
ba, _ := new(big.Int).SetString("111222333444555678999", 10) // "111,222,333,444,555,678,999"
bb, _ := new(big.Int).SetString("-111222333444555678999", 10) // "-111,222,333,444,555,678,999"
@@ -83,12 +94,13 @@ func logTest(ctx *cli.Context) error {
colored := fmt.Sprintf("\u001B[%dmColored\u001B[0m[", 35)
log.Info(colored, colored, colored)
+ err := errors.New("this is an 'error'")
+ log.Info("an error message with quotes", "error", err)
}
{ // Custom Stringer() - type
log.Info("Custom Stringer value", "2562047h47m16.854s", common.PrettyDuration(time.Duration(9223372036854775807)))
- }
- { // Lazy eval
- log.Info("Lazy evaluation of value", "key", log.Lazy{Fn: func() interface{} { return "lazy value" }})
+ var c customQuotedStringer
+ log.Info("a custom stringer that emits quoted text", "output", c)
}
{ // Multi-line message
log.Info("A message with wonky \U0001F4A9 characters")
@@ -150,6 +162,10 @@ func logTest(ctx *cli.Context) error {
{ // Logging with 'reserved' keys
log.Info("Using keys 't', 'lvl', 'time', 'level' and 'msg'", "t", "t", "time", "time", "lvl", "lvl", "level", "level", "msg", "msg")
}
+ { // Logging with wrong attr-value pairs
+ log.Info("Odd pair (1 attr)", "key")
+ log.Info("Odd pair (3 attr)", "key", "value", "key2")
+ }
return nil
}
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 308e2b267..8b749d514 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -69,7 +69,7 @@ var (
utils.MinFreeDiskSpaceFlag,
utils.KeyStoreDirFlag,
utils.ExternalSignerFlag,
- utils.NoUSBFlag,
+ utils.NoUSBFlag, // deprecated
utils.USBFlag,
utils.SmartCardDaemonPathFlag,
utils.OverrideCancun,
@@ -94,24 +94,24 @@ var (
utils.ExitWhenSyncedFlag,
utils.GCModeFlag,
utils.SnapshotFlag,
- utils.TxLookupLimitFlag,
+ utils.TxLookupLimitFlag, // deprecated
utils.TransactionHistoryFlag,
utils.StateHistoryFlag,
- utils.LightServeFlag,
- utils.LightIngressFlag,
- utils.LightEgressFlag,
- utils.LightMaxPeersFlag,
- utils.LightNoPruneFlag,
+ utils.LightServeFlag, // deprecated
+ utils.LightIngressFlag, // deprecated
+ utils.LightEgressFlag, // deprecated
+ utils.LightMaxPeersFlag, // deprecated
+ utils.LightNoPruneFlag, // deprecated
utils.LightKDFFlag,
- utils.LightNoSyncServeFlag,
+ utils.LightNoSyncServeFlag, // deprecated
utils.EthRequiredBlocksFlag,
- utils.LegacyWhitelistFlag,
+ utils.LegacyWhitelistFlag, // deprecated
utils.BloomFilterSizeFlag,
utils.CacheFlag,
utils.CacheDatabaseFlag,
utils.CacheTrieFlag,
- utils.CacheTrieJournalFlag,
- utils.CacheTrieRejournalFlag,
+ utils.CacheTrieJournalFlag, // deprecated
+ utils.CacheTrieRejournalFlag, // deprecated
utils.CacheGCFlag,
utils.CacheSnapshotFlag,
utils.CacheNoPrefetchFlag,
@@ -134,7 +134,7 @@ var (
utils.NoDiscoverFlag,
utils.DiscoveryV4Flag,
utils.DiscoveryV5Flag,
- utils.LegacyDiscoveryV5Flag,
+ utils.LegacyDiscoveryV5Flag, // deprecated
utils.NetrestrictFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
@@ -151,6 +151,8 @@ var (
utils.GpoMaxGasPriceFlag,
utils.GpoIgnoreGasPriceFlag,
configFileFlag,
+ utils.LogDebugFlag,
+ utils.LogBacktraceAtFlag,
}, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{
@@ -208,14 +210,12 @@ var app = flags.NewApp("the go-ethereum command line interface")
func init() {
// Initialize the CLI app and start Geth
app.Action = geth
- app.Copyright = "Copyright 2013-2023 The go-ethereum Authors"
app.Commands = []*cli.Command{
// See chaincmd.go:
initCommand,
importCommand,
exportCommand,
importPreimagesCommand,
- exportPreimagesCommand,
removedbCommand,
dumpCommand,
dumpGenesisCommand,
@@ -314,7 +314,7 @@ func prepare(ctx *cli.Context) {
log.Info("Starting Geth on Ethereum mainnet...")
}
// If we're a full node on mainnet without --cache specified, bump default cache allowance
- if ctx.String(utils.SyncModeFlag.Name) != "light" && !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) {
+ if !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) {
// Make sure we're not on any supported preconfigured testnet either
if !ctx.IsSet(utils.HoleskyFlag.Name) &&
!ctx.IsSet(utils.SepoliaFlag.Name) &&
@@ -325,11 +325,6 @@ func prepare(ctx *cli.Context) {
ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096))
}
}
- // If we're running a light client on any network, drop the cache to some meaningfully low amount
- if ctx.String(utils.SyncModeFlag.Name) == "light" && !ctx.IsSet(utils.CacheFlag.Name) {
- log.Info("Dropping default light client cache", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 128)
- ctx.Set(utils.CacheFlag.Name, strconv.Itoa(128))
- }
// Start metrics export if enabled
utils.SetupMetrics(ctx)
diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go
index 2e03dc5ea..1d3288032 100644
--- a/cmd/geth/run_test.go
+++ b/cmd/geth/run_test.go
@@ -55,6 +55,15 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
+func initGeth(t *testing.T) string {
+ args := []string{"--networkid=42", "init", "./testdata/clique.json"}
+ t.Logf("Initializing geth: %v ", args)
+ g := runGeth(t, args...)
+ datadir := g.Datadir
+ g.WaitExit()
+ return datadir
+}
+
// spawns geth with the given command line args. If the args don't set --datadir, the
// child g gets a temporary data directory.
func runGeth(t *testing.T, args ...string) *testgeth {
diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go
index 25c6311c4..4284005a0 100644
--- a/cmd/geth/snapshot.go
+++ b/cmd/geth/snapshot.go
@@ -20,6 +20,7 @@ import (
"bytes"
"encoding/json"
"errors"
+ "fmt"
"os"
"time"
@@ -147,6 +148,17 @@ as the backend data source, making this command a lot faster.
The argument is interpreted as block number or hash. If none is provided, the latest
block is used.
+`,
+ },
+ {
+ Action: snapshotExportPreimages,
+ Name: "export-preimages",
+ Usage: "Export the preimage in snapshot enumeration order",
+ ArgsUsage: " []",
+ Flags: utils.DatabaseFlags,
+ Description: `
+The export-preimages command exports hash preimages to a flat file, in exactly
+the expected order for the overlay tree migration.
`,
},
},
@@ -205,7 +217,7 @@ func verifyState(ctx *cli.Context) error {
log.Error("Failed to load head block")
return errors.New("no head block")
}
- triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true)
+ triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false)
defer triedb.Close()
snapConfig := snapshot.Config{
@@ -260,7 +272,7 @@ func traverseState(ctx *cli.Context) error {
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
- triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true)
+ triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false)
defer triedb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
@@ -369,7 +381,7 @@ func traverseRawState(ctx *cli.Context) error {
chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close()
- triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true)
+ triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false)
defer triedb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
@@ -533,7 +545,7 @@ func dumpState(ctx *cli.Context) error {
if err != nil {
return err
}
- triedb := utils.MakeTrieDatabase(ctx, db, false, true)
+ triedb := utils.MakeTrieDatabase(ctx, db, false, true, false)
defer triedb.Close()
snapConfig := snapshot.Config{
@@ -568,11 +580,11 @@ func dumpState(ctx *cli.Context) error {
return err
}
da := &state.DumpAccount{
- Balance: account.Balance.String(),
- Nonce: account.Nonce,
- Root: account.Root.Bytes(),
- CodeHash: account.CodeHash,
- SecureKey: accIt.Hash().Bytes(),
+ Balance: account.Balance.String(),
+ Nonce: account.Nonce,
+ Root: account.Root.Bytes(),
+ CodeHash: account.CodeHash,
+ AddressHash: accIt.Hash().Bytes(),
}
if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))
@@ -604,6 +616,48 @@ func dumpState(ctx *cli.Context) error {
return nil
}
+// snapshotExportPreimages dumps the preimage data to a flat file.
+func snapshotExportPreimages(ctx *cli.Context) error {
+ if ctx.NArg() < 1 {
+ utils.Fatalf("This command requires an argument.")
+ }
+ stack, _ := makeConfigNode(ctx)
+ defer stack.Close()
+
+ chaindb := utils.MakeChainDatabase(ctx, stack, true)
+ defer chaindb.Close()
+
+ triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false)
+ defer triedb.Close()
+
+ var root common.Hash
+ if ctx.NArg() > 1 {
+ rootBytes := common.FromHex(ctx.Args().Get(1))
+ if len(rootBytes) != common.HashLength {
+ return fmt.Errorf("invalid hash: %s", ctx.Args().Get(1))
+ }
+ root = common.BytesToHash(rootBytes)
+ } else {
+ headBlock := rawdb.ReadHeadBlock(chaindb)
+ if headBlock == nil {
+ log.Error("Failed to load head block")
+ return errors.New("no head block")
+ }
+ root = headBlock.Root()
+ }
+ snapConfig := snapshot.Config{
+ CacheSize: 256,
+ Recovery: false,
+ NoBuild: true,
+ AsyncBuild: false,
+ }
+ snaptree, err := snapshot.New(snapConfig, chaindb, triedb, root)
+ if err != nil {
+ return err
+ }
+ return utils.ExportSnapshotPreimages(chaindb, snaptree, ctx.Args().First(), root)
+}
+
// checkAccount iterates the snap data layers, and looks up the given account
// across all layers.
func checkAccount(ctx *cli.Context) error {
diff --git a/cmd/geth/testdata/logging/logtest-json.txt b/cmd/geth/testdata/logging/logtest-json.txt
index 6cb2476db..d2bd0ad91 100644
--- a/cmd/geth/testdata/logging/logtest-json.txt
+++ b/cmd/geth/testdata/logging/logtest-json.txt
@@ -1,49 +1,52 @@
-{"111,222,333,444,555,678,999":"111222333444555678999","lvl":"info","msg":"big.Int","t":"2023-11-09T08:33:19.464383209+01:00"}
-{"-111,222,333,444,555,678,999":"-111222333444555678999","lvl":"info","msg":"-big.Int","t":"2023-11-09T08:33:19.46455928+01:00"}
-{"11,122,233,344,455,567,899,900":"11122233344455567899900","lvl":"info","msg":"big.Int","t":"2023-11-09T08:33:19.464582073+01:00"}
-{"-11,122,233,344,455,567,899,900":"-11122233344455567899900","lvl":"info","msg":"-big.Int","t":"2023-11-09T08:33:19.464594846+01:00"}
-{"111,222,333,444,555,678,999":"0x607851afc94ca2517","lvl":"info","msg":"uint256","t":"2023-11-09T08:33:19.464607873+01:00"}
-{"11,122,233,344,455,567,899,900":"0x25aeffe8aaa1ef67cfc","lvl":"info","msg":"uint256","t":"2023-11-09T08:33:19.464694639+01:00"}
-{"1,000,000":1000000,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464708835+01:00"}
-{"-1,000,000":-1000000,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464725054+01:00"}
-{"9,223,372,036,854,775,807":9223372036854775807,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464735773+01:00"}
-{"-9,223,372,036,854,775,808":-9223372036854775808,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464744532+01:00"}
-{"1,000,000":1000000,"lvl":"info","msg":"uint64","t":"2023-11-09T08:33:19.464752807+01:00"}
-{"18,446,744,073,709,551,615":18446744073709551615,"lvl":"info","msg":"uint64","t":"2023-11-09T08:33:19.464779296+01:00"}
-{"key":"special \r\n\t chars","lvl":"info","msg":"Special chars in value","t":"2023-11-09T08:33:19.464794181+01:00"}
-{"lvl":"info","msg":"Special chars in key","special \n\t chars":"value","t":"2023-11-09T08:33:19.464827197+01:00"}
-{"lvl":"info","msg":"nospace","nospace":"nospace","t":"2023-11-09T08:33:19.464841118+01:00"}
-{"lvl":"info","msg":"with space","t":"2023-11-09T08:33:19.464862818+01:00","with nospace":"with nospace"}
-{"key":"\u001b[1G\u001b[K\u001b[1A","lvl":"info","msg":"Bash escapes in value","t":"2023-11-09T08:33:19.464876802+01:00"}
-{"\u001b[1G\u001b[K\u001b[1A":"value","lvl":"info","msg":"Bash escapes in key","t":"2023-11-09T08:33:19.464885416+01:00"}
-{"key":"value","lvl":"info","msg":"Bash escapes in message \u001b[1G\u001b[K\u001b[1A end","t":"2023-11-09T08:33:19.464906946+01:00"}
-{"\u001b[35mColored\u001b[0m[":"\u001b[35mColored\u001b[0m[","lvl":"info","msg":"\u001b[35mColored\u001b[0m[","t":"2023-11-09T08:33:19.464921455+01:00"}
-{"2562047h47m16.854s":"2562047h47m16.854s","lvl":"info","msg":"Custom Stringer value","t":"2023-11-09T08:33:19.464943893+01:00"}
-{"key":"lazy value","lvl":"info","msg":"Lazy evaluation of value","t":"2023-11-09T08:33:19.465013552+01:00"}
-{"lvl":"info","msg":"A message with wonky 💩 characters","t":"2023-11-09T08:33:19.465069437+01:00"}
-{"lvl":"info","msg":"A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩","t":"2023-11-09T08:33:19.465083053+01:00"}
-{"lvl":"info","msg":"A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above","t":"2023-11-09T08:33:19.465104289+01:00"}
-{"false":"false","lvl":"info","msg":"boolean","t":"2023-11-09T08:33:19.465117185+01:00","true":"true"}
-{"foo":"beta","lvl":"info","msg":"repeated-key 1","t":"2023-11-09T08:33:19.465143425+01:00"}
-{"lvl":"info","msg":"repeated-key 2","t":"2023-11-09T08:33:19.465156323+01:00","xx":"longer"}
-{"lvl":"info","msg":"log at level info","t":"2023-11-09T08:33:19.465193158+01:00"}
-{"lvl":"warn","msg":"log at level warn","t":"2023-11-09T08:33:19.465228964+01:00"}
-{"lvl":"eror","msg":"log at level error","t":"2023-11-09T08:33:19.465240352+01:00"}
-{"a":"aligned left","bar":"short","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465247226+01:00"}
-{"a":1,"bar":"a long message","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465269028+01:00"}
-{"a":"aligned right","bar":"short","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465313611+01:00"}
-{"lvl":"info","msg":"The following logs should align so that the key-fields make 5 columns","t":"2023-11-09T08:33:19.465328188+01:00"}
-{"gas":1123123,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","lvl":"info","msg":"Inserted known block","number":1012,"other":"first","t":"2023-11-09T08:33:19.465350507+01:00","txs":200}
-{"gas":1123,"hash":"0x0000000000000000000000000000000000000000000000000000000000001235","lvl":"info","msg":"Inserted new block","number":1,"other":"second","t":"2023-11-09T08:33:19.465387952+01:00","txs":2}
-{"gas":1,"hash":"0x0000000000000000000000000000000000000000000000000000000000012322","lvl":"info","msg":"Inserted known block","number":99,"other":"third","t":"2023-11-09T08:33:19.465406687+01:00","txs":10}
-{"gas":99,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","lvl":"warn","msg":"Inserted known block","number":1012,"other":"fourth","t":"2023-11-09T08:33:19.465433025+01:00","txs":200}
-{"\u003cnil\u003e":"\u003cnil\u003e","lvl":"info","msg":"(*big.Int)(nil)","t":"2023-11-09T08:33:19.465450283+01:00"}
-{"\u003cnil\u003e":"nil","lvl":"info","msg":"(*uint256.Int)(nil)","t":"2023-11-09T08:33:19.465472953+01:00"}
-{"lvl":"info","msg":"(fmt.Stringer)(nil)","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465538633+01:00"}
-{"lvl":"info","msg":"nil-concrete-stringer","res":"nil","t":"2023-11-09T08:33:19.465552355+01:00"}
-{"lvl":"info","msg":"error(nil) ","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465601029+01:00"}
-{"lvl":"info","msg":"nil-concrete-error","res":"","t":"2023-11-09T08:33:19.46561622+01:00"}
-{"lvl":"info","msg":"nil-custom-struct","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465638888+01:00"}
-{"lvl":"info","msg":"raw nil","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465673664+01:00"}
-{"lvl":"info","msg":"(*uint64)(nil)","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465700264+01:00"}
-{"level":"level","lvl":"lvl","msg":"msg","t":"t","time":"time"}
+{"t":"2023-11-22T15:42:00.407963+08:00","lvl":"info","msg":"big.Int","111,222,333,444,555,678,999":"111222333444555678999"}
+{"t":"2023-11-22T15:42:00.408084+08:00","lvl":"info","msg":"-big.Int","-111,222,333,444,555,678,999":"-111222333444555678999"}
+{"t":"2023-11-22T15:42:00.408092+08:00","lvl":"info","msg":"big.Int","11,122,233,344,455,567,899,900":"11122233344455567899900"}
+{"t":"2023-11-22T15:42:00.408097+08:00","lvl":"info","msg":"-big.Int","-11,122,233,344,455,567,899,900":"-11122233344455567899900"}
+{"t":"2023-11-22T15:42:00.408127+08:00","lvl":"info","msg":"uint256","111,222,333,444,555,678,999":"111222333444555678999"}
+{"t":"2023-11-22T15:42:00.408133+08:00","lvl":"info","msg":"uint256","11,122,233,344,455,567,899,900":"11122233344455567899900"}
+{"t":"2023-11-22T15:42:00.408137+08:00","lvl":"info","msg":"int64","1,000,000":1000000}
+{"t":"2023-11-22T15:42:00.408145+08:00","lvl":"info","msg":"int64","-1,000,000":-1000000}
+{"t":"2023-11-22T15:42:00.408149+08:00","lvl":"info","msg":"int64","9,223,372,036,854,775,807":9223372036854775807}
+{"t":"2023-11-22T15:42:00.408153+08:00","lvl":"info","msg":"int64","-9,223,372,036,854,775,808":-9223372036854775808}
+{"t":"2023-11-22T15:42:00.408156+08:00","lvl":"info","msg":"uint64","1,000,000":1000000}
+{"t":"2023-11-22T15:42:00.40816+08:00","lvl":"info","msg":"uint64","18,446,744,073,709,551,615":18446744073709551615}
+{"t":"2023-11-22T15:42:00.408164+08:00","lvl":"info","msg":"Special chars in value","key":"special \r\n\t chars"}
+{"t":"2023-11-22T15:42:00.408167+08:00","lvl":"info","msg":"Special chars in key","special \n\t chars":"value"}
+{"t":"2023-11-22T15:42:00.408171+08:00","lvl":"info","msg":"nospace","nospace":"nospace"}
+{"t":"2023-11-22T15:42:00.408174+08:00","lvl":"info","msg":"with space","with nospace":"with nospace"}
+{"t":"2023-11-22T15:42:00.408178+08:00","lvl":"info","msg":"Bash escapes in value","key":"\u001b[1G\u001b[K\u001b[1A"}
+{"t":"2023-11-22T15:42:00.408182+08:00","lvl":"info","msg":"Bash escapes in key","\u001b[1G\u001b[K\u001b[1A":"value"}
+{"t":"2023-11-22T15:42:00.408186+08:00","lvl":"info","msg":"Bash escapes in message \u001b[1G\u001b[K\u001b[1A end","key":"value"}
+{"t":"2023-11-22T15:42:00.408194+08:00","lvl":"info","msg":"\u001b[35mColored\u001b[0m[","\u001b[35mColored\u001b[0m[":"\u001b[35mColored\u001b[0m["}
+{"t":"2023-11-22T15:42:00.408197+08:00","lvl":"info","msg":"an error message with quotes","error":"this is an 'error'"}
+{"t":"2023-11-22T15:42:00.408202+08:00","lvl":"info","msg":"Custom Stringer value","2562047h47m16.854s":"2562047h47m16.854s"}
+{"t":"2023-11-22T15:42:00.408208+08:00","lvl":"info","msg":"a custom stringer that emits quoted text","output":"output with 'quotes'"}
+{"t":"2023-11-22T15:42:00.408219+08:00","lvl":"info","msg":"A message with wonky 💩 characters"}
+{"t":"2023-11-22T15:42:00.408222+08:00","lvl":"info","msg":"A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"}
+{"t":"2023-11-22T15:42:00.408226+08:00","lvl":"info","msg":"A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"}
+{"t":"2023-11-22T15:42:00.408229+08:00","lvl":"info","msg":"boolean","true":true,"false":false}
+{"t":"2023-11-22T15:42:00.408234+08:00","lvl":"info","msg":"repeated-key 1","foo":"alpha","foo":"beta"}
+{"t":"2023-11-22T15:42:00.408237+08:00","lvl":"info","msg":"repeated-key 2","xx":"short","xx":"longer"}
+{"t":"2023-11-22T15:42:00.408241+08:00","lvl":"info","msg":"log at level info"}
+{"t":"2023-11-22T15:42:00.408244+08:00","lvl":"warn","msg":"log at level warn"}
+{"t":"2023-11-22T15:42:00.408247+08:00","lvl":"error","msg":"log at level error"}
+{"t":"2023-11-22T15:42:00.408251+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned left"}
+{"t":"2023-11-22T15:42:00.408254+08:00","lvl":"info","msg":"test","bar":"a long message","a":1}
+{"t":"2023-11-22T15:42:00.408258+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned right"}
+{"t":"2023-11-22T15:42:00.408261+08:00","lvl":"info","msg":"The following logs should align so that the key-fields make 5 columns"}
+{"t":"2023-11-22T15:42:00.408275+08:00","lvl":"info","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":1123123,"other":"first"}
+{"t":"2023-11-22T15:42:00.408281+08:00","lvl":"info","msg":"Inserted new block","number":1,"hash":"0x0000000000000000000000000000000000000000000000000000000000001235","txs":2,"gas":1123,"other":"second"}
+{"t":"2023-11-22T15:42:00.408287+08:00","lvl":"info","msg":"Inserted known block","number":99,"hash":"0x0000000000000000000000000000000000000000000000000000000000012322","txs":10,"gas":1,"other":"third"}
+{"t":"2023-11-22T15:42:00.408296+08:00","lvl":"warn","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":99,"other":"fourth"}
+{"t":"2023-11-22T15:42:00.4083+08:00","lvl":"info","msg":"(*big.Int)(nil)","":""}
+{"t":"2023-11-22T15:42:00.408303+08:00","lvl":"info","msg":"(*uint256.Int)(nil)","":""}
+{"t":"2023-11-22T15:42:00.408311+08:00","lvl":"info","msg":"(fmt.Stringer)(nil)","res":null}
+{"t":"2023-11-22T15:42:00.408318+08:00","lvl":"info","msg":"nil-concrete-stringer","res":""}
+{"t":"2023-11-22T15:42:00.408322+08:00","lvl":"info","msg":"error(nil) ","res":null}
+{"t":"2023-11-22T15:42:00.408326+08:00","lvl":"info","msg":"nil-concrete-error","res":""}
+{"t":"2023-11-22T15:42:00.408334+08:00","lvl":"info","msg":"nil-custom-struct","res":null}
+{"t":"2023-11-22T15:42:00.40835+08:00","lvl":"info","msg":"raw nil","res":null}
+{"t":"2023-11-22T15:42:00.408354+08:00","lvl":"info","msg":"(*uint64)(nil)","res":null}
+{"t":"2023-11-22T15:42:00.408361+08:00","lvl":"info","msg":"Using keys 't', 'lvl', 'time', 'level' and 'msg'","t":"t","time":"time","lvl":"lvl","level":"level","msg":"msg"}
+{"t":"2023-11-29T15:13:00.195655931+01:00","lvl":"info","msg":"Odd pair (1 attr)","key":null,"LOG_ERROR":"Normalized odd number of arguments by adding nil"}
+{"t":"2023-11-29T15:13:00.195681832+01:00","lvl":"info","msg":"Odd pair (3 attr)","key":"value","key2":null,"LOG_ERROR":"Normalized odd number of arguments by adding nil"}
diff --git a/cmd/geth/testdata/logging/logtest-logfmt.txt b/cmd/geth/testdata/logging/logtest-logfmt.txt
index c1e34d193..5c5316b7d 100644
--- a/cmd/geth/testdata/logging/logtest-logfmt.txt
+++ b/cmd/geth/testdata/logging/logtest-logfmt.txt
@@ -1,49 +1,52 @@
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 1,000,000=1,000,000
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 -1,000,000=-1,000,000
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint64 1,000,000=1,000,000
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Special chars in value" key="special \r\n\t chars"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Special chars in key" "special \n\t chars"=value
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nospace nospace=nospace
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="with space" "with nospace"="with nospace"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Lazy evaluation of value" key="lazy value"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A message with wonky 💩 characters"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=boolean true=true false=false
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="repeated-key 1" foo=alpha foo=beta
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="repeated-key 2" xx=short xx=longer
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="log at level info"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=warn msg="log at level warn"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=eror msg="log at level error"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar=short a="aligned left"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar="a long message" a=1
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar=short a="aligned right"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="The following logs should align so that the key-fields make 5 columns"
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1,123,123 other=first
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*big.Int)(nil) =
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*uint256.Int)(nil) =
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(fmt.Stringer)(nil) res=nil
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-concrete-stringer res=nil
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="error(nil) " res=nil
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-concrete-error res=
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-custom-struct res=
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="raw nil" res=nil
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*uint64)(nil) res=
-t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Using keys 't', 'lvl', 'time', 'level' and 'msg'" t=t time=time lvl=lvl level=level msg=msg
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 111,222,333,444,555,678,999=111222333444555678999
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111222333444555678999
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11122233344455567899900
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11122233344455567899900
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 111,222,333,444,555,678,999=111222333444555678999
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 11,122,233,344,455,567,899,900=11122233344455567899900
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 1,000,000=1000000
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -1,000,000=-1000000
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 9,223,372,036,854,775,807=9223372036854775807
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -9,223,372,036,854,775,808=-9223372036854775808
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 1,000,000=1000000
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 18,446,744,073,709,551,615=18446744073709551615
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in value" key="special \r\n\t chars"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in key" "special \n\t chars"=value
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nospace nospace=nospace
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="with space" "with nospace"="with nospace"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="an error message with quotes" error="this is an 'error'"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="a custom stringer that emits quoted text" output="output with 'quotes'"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A message with wonky 💩 characters"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=boolean true=true false=false
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 1" foo=alpha foo=beta
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 2" xx=short xx=longer
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="log at level info"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="log at level warn"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=error msg="log at level error"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned left"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar="a long message" a=1
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned right"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="The following logs should align so that the key-fields make 5 columns"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1123123 other=first
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*big.Int)(nil) =
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint256.Int)(nil) =
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(fmt.Stringer)(nil) res=
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-stringer res=
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="error(nil) " res=
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-error res=""
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-custom-struct res=
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="raw nil" res=
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint64)(nil) res=
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Using keys 't', 'lvl', 'time', 'level' and 'msg'" t=t time=time lvl=lvl level=level msg=msg
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Odd pair (1 attr)" key= LOG_ERROR="Normalized odd number of arguments by adding nil"
+t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Odd pair (3 attr)" key=value key2= LOG_ERROR="Normalized odd number of arguments by adding nil"
diff --git a/cmd/geth/testdata/logging/logtest-terminal.txt b/cmd/geth/testdata/logging/logtest-terminal.txt
index af0de7b9a..e3b562117 100644
--- a/cmd/geth/testdata/logging/logtest-terminal.txt
+++ b/cmd/geth/testdata/logging/logtest-terminal.txt
@@ -1,50 +1,53 @@
-INFO [XX-XX|XX:XX:XX.XXX] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
-INFO [XX-XX|XX:XX:XX.XXX] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
-INFO [XX-XX|XX:XX:XX.XXX] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
-INFO [XX-XX|XX:XX:XX.XXX] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
-INFO [XX-XX|XX:XX:XX.XXX] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
-INFO [XX-XX|XX:XX:XX.XXX] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
-INFO [XX-XX|XX:XX:XX.XXX] int64 1,000,000=1,000,000
-INFO [XX-XX|XX:XX:XX.XXX] int64 -1,000,000=-1,000,000
-INFO [XX-XX|XX:XX:XX.XXX] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
-INFO [XX-XX|XX:XX:XX.XXX] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
-INFO [XX-XX|XX:XX:XX.XXX] uint64 1,000,000=1,000,000
-INFO [XX-XX|XX:XX:XX.XXX] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
-INFO [XX-XX|XX:XX:XX.XXX] Special chars in value key="special \r\n\t chars"
-INFO [XX-XX|XX:XX:XX.XXX] Special chars in key "special \n\t chars"=value
-INFO [XX-XX|XX:XX:XX.XXX] nospace nospace=nospace
-INFO [XX-XX|XX:XX:XX.XXX] with space "with nospace"="with nospace"
-INFO [XX-XX|XX:XX:XX.XXX] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A"
-INFO [XX-XX|XX:XX:XX.XXX] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value
-INFO [XX-XX|XX:XX:XX.XXX] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
-INFO [XX-XX|XX:XX:XX.XXX] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
-INFO [XX-XX|XX:XX:XX.XXX] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s
-INFO [XX-XX|XX:XX:XX.XXX] Lazy evaluation of value key="lazy value"
-INFO [XX-XX|XX:XX:XX.XXX] "A message with wonky 💩 characters"
-INFO [XX-XX|XX:XX:XX.XXX] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
-INFO [XX-XX|XX:XX:XX.XXX] A multiline message
-LALA [XXZXXZXXZXXZXXZXXX] Actually part of message above
-INFO [XX-XX|XX:XX:XX.XXX] boolean true=true false=false
-INFO [XX-XX|XX:XX:XX.XXX] repeated-key 1 foo=alpha foo=beta
-INFO [XX-XX|XX:XX:XX.XXX] repeated-key 2 xx=short xx=longer
-INFO [XX-XX|XX:XX:XX.XXX] log at level info
-WARN [XX-XX|XX:XX:XX.XXX] log at level warn
-ERROR[XX-XX|XX:XX:XX.XXX] log at level error
-INFO [XX-XX|XX:XX:XX.XXX] test bar=short a="aligned left"
-INFO [XX-XX|XX:XX:XX.XXX] test bar="a long message" a=1
-INFO [XX-XX|XX:XX:XX.XXX] test bar=short a="aligned right"
-INFO [XX-XX|XX:XX:XX.XXX] The following logs should align so that the key-fields make 5 columns
-INFO [XX-XX|XX:XX:XX.XXX] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first
-INFO [XX-XX|XX:XX:XX.XXX] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second
-INFO [XX-XX|XX:XX:XX.XXX] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third
-WARN [XX-XX|XX:XX:XX.XXX] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth
-INFO [XX-XX|XX:XX:XX.XXX] (*big.Int)(nil) =
-INFO [XX-XX|XX:XX:XX.XXX] (*uint256.Int)(nil) =
-INFO [XX-XX|XX:XX:XX.XXX] (fmt.Stringer)(nil) res=nil
-INFO [XX-XX|XX:XX:XX.XXX] nil-concrete-stringer res=nil
-INFO [XX-XX|XX:XX:XX.XXX] error(nil) res=nil
-INFO [XX-XX|XX:XX:XX.XXX] nil-concrete-error res=
-INFO [XX-XX|XX:XX:XX.XXX] nil-custom-struct res=
-INFO [XX-XX|XX:XX:XX.XXX] raw nil res=nil
-INFO [XX-XX|XX:XX:XX.XXX] (*uint64)(nil) res=
-INFO [XX-XX|XX:XX:XX.XXX] Using keys 't', 'lvl', 'time', 'level' and 'msg' t=t time=time lvl=lvl level=level msg=msg
+INFO [xx-xx|xx:xx:xx.xxx] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999
+INFO [xx-xx|xx:xx:xx.xxx] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999
+INFO [xx-xx|xx:xx:xx.xxx] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
+INFO [xx-xx|xx:xx:xx.xxx] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900
+INFO [xx-xx|xx:xx:xx.xxx] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999
+INFO [xx-xx|xx:xx:xx.xxx] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900
+INFO [xx-xx|xx:xx:xx.xxx] int64 1,000,000=1,000,000
+INFO [xx-xx|xx:xx:xx.xxx] int64 -1,000,000=-1,000,000
+INFO [xx-xx|xx:xx:xx.xxx] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807
+INFO [xx-xx|xx:xx:xx.xxx] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808
+INFO [xx-xx|xx:xx:xx.xxx] uint64 1,000,000=1,000,000
+INFO [xx-xx|xx:xx:xx.xxx] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615
+INFO [xx-xx|xx:xx:xx.xxx] Special chars in value key="special \r\n\t chars"
+INFO [xx-xx|xx:xx:xx.xxx] Special chars in key "special \n\t chars"=value
+INFO [xx-xx|xx:xx:xx.xxx] nospace nospace=nospace
+INFO [xx-xx|xx:xx:xx.xxx] with space "with nospace"="with nospace"
+INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A"
+INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value
+INFO [xx-xx|xx:xx:xx.xxx] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value
+INFO [xx-xx|xx:xx:xx.xxx] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m["
+INFO [xx-xx|xx:xx:xx.xxx] an error message with quotes error="this is an 'error'"
+INFO [xx-xx|xx:xx:xx.xxx] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s
+INFO [xx-xx|xx:xx:xx.xxx] a custom stringer that emits quoted text output="output with 'quotes'"
+INFO [xx-xx|xx:xx:xx.xxx] "A message with wonky 💩 characters"
+INFO [xx-xx|xx:xx:xx.xxx] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"
+INFO [xx-xx|xx:xx:xx.xxx] A multiline message
+LALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above
+INFO [xx-xx|xx:xx:xx.xxx] boolean true=true false=false
+INFO [xx-xx|xx:xx:xx.xxx] repeated-key 1 foo=alpha foo=beta
+INFO [xx-xx|xx:xx:xx.xxx] repeated-key 2 xx=short xx=longer
+INFO [xx-xx|xx:xx:xx.xxx] log at level info
+WARN [xx-xx|xx:xx:xx.xxx] log at level warn
+ERROR[xx-xx|xx:xx:xx.xxx] log at level error
+INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned left"
+INFO [xx-xx|xx:xx:xx.xxx] test bar="a long message" a=1
+INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned right"
+INFO [xx-xx|xx:xx:xx.xxx] The following logs should align so that the key-fields make 5 columns
+INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first
+INFO [xx-xx|xx:xx:xx.xxx] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second
+INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third
+WARN [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth
+INFO [xx-xx|xx:xx:xx.xxx] (*big.Int)(nil) =
+INFO [xx-xx|xx:xx:xx.xxx] (*uint256.Int)(nil) =
+INFO [xx-xx|xx:xx:xx.xxx] (fmt.Stringer)(nil) res=
+INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-stringer res=
+INFO [xx-xx|xx:xx:xx.xxx] error(nil) res=
+INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-error res=
+INFO [xx-xx|xx:xx:xx.xxx] nil-custom-struct res=
+INFO [xx-xx|xx:xx:xx.xxx] raw nil res=
+INFO [xx-xx|xx:xx:xx.xxx] (*uint64)(nil) res=
+INFO [xx-xx|xx:xx:xx.xxx] Using keys 't', 'lvl', 'time', 'level' and 'msg' t=t time=time lvl=lvl level=level msg=msg
+INFO [xx-xx|xx:xx:xx.xxx] Odd pair (1 attr) key= LOG_ERROR="Normalized odd number of arguments by adding nil"
+INFO [xx-xx|xx:xx:xx.xxx] Odd pair (3 attr) key=value key2= LOG_ERROR="Normalized odd number of arguments by adding nil"
diff --git a/cmd/geth/verkle.go b/cmd/geth/verkle.go
index aa79889e8..420b063d8 100644
--- a/cmd/geth/verkle.go
+++ b/cmd/geth/verkle.go
@@ -84,7 +84,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error
return fmt.Errorf("could not find child %x in db: %w", childC, err)
}
// depth is set to 0, the tree isn't rebuilt so it's not a problem
- childN, err := verkle.ParseNode(childS, 0, childC[:])
+ childN, err := verkle.ParseNode(childS, 0)
if err != nil {
return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err)
}
@@ -145,7 +145,7 @@ func verifyVerkle(ctx *cli.Context) error {
if err != nil {
return err
}
- root, err := verkle.ParseNode(serializedRoot, 0, rootC[:])
+ root, err := verkle.ParseNode(serializedRoot, 0)
if err != nil {
return err
}
@@ -195,7 +195,7 @@ func expandVerkle(ctx *cli.Context) error {
if err != nil {
return err
}
- root, err := verkle.ParseNode(serializedRoot, 0, rootC[:])
+ root, err := verkle.ParseNode(serializedRoot, 0)
if err != nil {
return err
}
diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go
index 4458ab5c0..3676d25d0 100644
--- a/cmd/geth/version_check_test.go
+++ b/cmd/geth/version_check_test.go
@@ -30,14 +30,17 @@ import (
)
func TestVerification(t *testing.T) {
+ t.Parallel()
// Signatures generated with `minisign`. Legacy format, not pre-hashed file.
t.Run("minisig-legacy", func(t *testing.T) {
+ t.Parallel()
// For this test, the pubkey is in testdata/vcheck/minisign.pub
// (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' )
pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp"
testVerification(t, pub, "./testdata/vcheck/minisig-sigs/")
})
t.Run("minisig-new", func(t *testing.T) {
+ t.Parallel()
// For this test, the pubkey is in testdata/vcheck/minisign.pub
// (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' )
// `minisign -S -s ./minisign.sec -m data.json -x ./minisig-sigs-new/data.json.minisig`
@@ -46,6 +49,7 @@ func TestVerification(t *testing.T) {
})
// Signatures generated with `signify-openbsd`
t.Run("signify-openbsd", func(t *testing.T) {
+ t.Parallel()
t.Skip("This currently fails, minisign expects 4 lines of data, signify provides only 2")
// For this test, the pubkey is in testdata/vcheck/signifykey.pub
// (the privkey is `signifykey.sec`, if we want to expand this test. Password 'test' )
@@ -97,6 +101,7 @@ func versionUint(v string) int {
// TestMatching can be used to check that the regexps are correct
func TestMatching(t *testing.T) {
+ t.Parallel()
data, _ := os.ReadFile("./testdata/vcheck/vulnerabilities.json")
var vulns []vulnJson
if err := json.Unmarshal(data, &vulns); err != nil {
@@ -141,6 +146,7 @@ func TestMatching(t *testing.T) {
}
func TestGethPubKeysParseable(t *testing.T) {
+ t.Parallel()
for _, pubkey := range gethPubKeys {
_, err := minisign.NewPublicKey(pubkey)
if err != nil {
@@ -150,6 +156,7 @@ func TestGethPubKeysParseable(t *testing.T) {
}
func TestKeyID(t *testing.T) {
+ t.Parallel()
type args struct {
id [8]byte
}
@@ -163,7 +170,9 @@ func TestKeyID(t *testing.T) {
{"third key", args{id: extractKeyId(gethPubKeys[2])}, "FD9813B2D2098484"},
}
for _, tt := range tests {
+ tt := tt
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
if got := keyID(tt.args.id); got != tt.want {
t.Errorf("keyID() = %v, want %v", got, tt.want)
}
diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go
index a3546d405..a0f5f0d28 100644
--- a/cmd/p2psim/main.go
+++ b/cmd/p2psim/main.go
@@ -417,9 +417,7 @@ func rpcNode(ctx *cli.Context) error {
}
func rpcSubscribe(client *rpc.Client, out io.Writer, method string, args ...string) error {
- parts := strings.SplitN(method, "_", 2)
- namespace := parts[0]
- method = parts[1]
+ namespace, method, _ := strings.Cut(method, "_")
ch := make(chan interface{})
subArgs := make([]interface{}, len(args)+1)
subArgs[0] = method
diff --git a/cmd/rlpdump/rlpdump_test.go b/cmd/rlpdump/rlpdump_test.go
index a9ab57fdb..8d55f4200 100644
--- a/cmd/rlpdump/rlpdump_test.go
+++ b/cmd/rlpdump/rlpdump_test.go
@@ -27,6 +27,7 @@ import (
)
func TestRoundtrip(t *testing.T) {
+ t.Parallel()
for i, want := range []string{
"0xf880806482520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a1010000000000000000000000000000000000000000000000000000000000000001801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28",
"0xd5c0d3cb84746573742a2a808213378667617a6f6e6b",
@@ -51,6 +52,7 @@ func TestRoundtrip(t *testing.T) {
}
func TestTextToRlp(t *testing.T) {
+ t.Parallel()
type tc struct {
text string
want string
diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go
index 16b126057..8b571be1e 100644
--- a/cmd/utils/cmd.go
+++ b/cmd/utils/cmd.go
@@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/ethconfig"
@@ -374,6 +375,101 @@ func ExportPreimages(db ethdb.Database, fn string) error {
return nil
}
+// ExportSnapshotPreimages exports the preimages corresponding to the enumeration of
+// the snapshot for a given root.
+func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn string, root common.Hash) error {
+ log.Info("Exporting preimages", "file", fn)
+
+ fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
+ if err != nil {
+ return err
+ }
+ defer fh.Close()
+
+ // Enable gzip compressing if file name has gz suffix.
+ var writer io.Writer = fh
+ if strings.HasSuffix(fn, ".gz") {
+ gz := gzip.NewWriter(writer)
+ defer gz.Close()
+ writer = gz
+ }
+ buf := bufio.NewWriter(writer)
+ defer buf.Flush()
+ writer = buf
+
+ type hashAndPreimageSize struct {
+ Hash common.Hash
+ Size int
+ }
+ hashCh := make(chan hashAndPreimageSize)
+
+ var (
+ start = time.Now()
+ logged = time.Now()
+ preimages int
+ )
+ go func() {
+ defer close(hashCh)
+ accIt, err := snaptree.AccountIterator(root, common.Hash{})
+ if err != nil {
+ log.Error("Failed to create account iterator", "error", err)
+ return
+ }
+ defer accIt.Release()
+
+ for accIt.Next() {
+ acc, err := types.FullAccount(accIt.Account())
+ if err != nil {
+ log.Error("Failed to get full account", "error", err)
+ return
+ }
+ preimages += 1
+ hashCh <- hashAndPreimageSize{Hash: accIt.Hash(), Size: common.AddressLength}
+
+ if acc.Root != (common.Hash{}) && acc.Root != types.EmptyRootHash {
+ stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{})
+ if err != nil {
+ log.Error("Failed to create storage iterator", "error", err)
+ return
+ }
+ for stIt.Next() {
+ preimages += 1
+ hashCh <- hashAndPreimageSize{Hash: stIt.Hash(), Size: common.HashLength}
+
+ if time.Since(logged) > time.Second*8 {
+ logged = time.Now()
+ log.Info("Exporting preimages", "count", preimages, "elapsed", common.PrettyDuration(time.Since(start)))
+ }
+ }
+ stIt.Release()
+ }
+ if time.Since(logged) > time.Second*8 {
+ logged = time.Now()
+ log.Info("Exporting preimages", "count", preimages, "elapsed", common.PrettyDuration(time.Since(start)))
+ }
+ }
+ }()
+
+ for item := range hashCh {
+ preimage := rawdb.ReadPreimage(chaindb, item.Hash)
+ if len(preimage) == 0 {
+ return fmt.Errorf("missing preimage for %v", item.Hash)
+ }
+ if len(preimage) != item.Size {
+ return fmt.Errorf("invalid preimage size, have %d", len(preimage))
+ }
+ rlpenc, err := rlp.EncodeToBytes(preimage)
+ if err != nil {
+ return fmt.Errorf("error encoding preimage: %w", err)
+ }
+ if _, err := writer.Write(rlpenc); err != nil {
+ return fmt.Errorf("failed to write preimage: %w", err)
+ }
+ }
+ log.Info("Exported preimages", "count", preimages, "elapsed", common.PrettyDuration(time.Since(start)), "file", fn)
+ return nil
+}
+
// exportHeader is used in the export/import flow. When we do an export,
// the first element we output is the exportHeader.
// Whenever a backwards-incompatible change is made, the Version header
@@ -460,7 +556,7 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan
case OpBatchAdd:
batch.Put(key, val)
default:
- return fmt.Errorf("unknown op %d\n", op)
+ return fmt.Errorf("unknown op %d", op)
}
if batch.ValueSize() > ethdb.IdealBatchSize {
if err := batch.Write(); err != nil {
diff --git a/cmd/utils/export_test.go b/cmd/utils/export_test.go
index 445e3fac3..84ba8d0c3 100644
--- a/cmd/utils/export_test.go
+++ b/cmd/utils/export_test.go
@@ -170,6 +170,7 @@ func testDeletion(t *testing.T, f string) {
// TestImportFutureFormat tests that we reject unsupported future versions.
func TestImportFutureFormat(t *testing.T) {
+ t.Parallel()
f := fmt.Sprintf("%v/tempdump-future", os.TempDir())
defer func() {
os.Remove(f)
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 59be56fb1..852fcd4af 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -57,7 +57,6 @@ import (
"github.com/ethereum/go-ethereum/graphql"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags"
- "github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/metrics/exp"
@@ -263,7 +262,7 @@ var (
}
SyncModeFlag = &flags.TextMarshalerFlag{
Name: "syncmode",
- Usage: `Blockchain sync mode ("snap", "full" or "light")`,
+ Usage: `Blockchain sync mode ("snap" or "full")`,
Value: &defaultSyncMode,
Category: flags.StateCategory,
}
@@ -290,41 +289,6 @@ var (
Value: ethconfig.Defaults.TransactionHistory,
Category: flags.StateCategory,
}
- // Light server and client settings
- LightServeFlag = &cli.IntFlag{
- Name: "light.serve",
- Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)",
- Value: ethconfig.Defaults.LightServ,
- Category: flags.LightCategory,
- }
- LightIngressFlag = &cli.IntFlag{
- Name: "light.ingress",
- Usage: "Incoming bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)",
- Value: ethconfig.Defaults.LightIngress,
- Category: flags.LightCategory,
- }
- LightEgressFlag = &cli.IntFlag{
- Name: "light.egress",
- Usage: "Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)",
- Value: ethconfig.Defaults.LightEgress,
- Category: flags.LightCategory,
- }
- LightMaxPeersFlag = &cli.IntFlag{
- Name: "light.maxpeers",
- Usage: "Maximum number of light clients to serve, or light servers to attach to",
- Value: ethconfig.Defaults.LightPeers,
- Category: flags.LightCategory,
- }
- LightNoPruneFlag = &cli.BoolFlag{
- Name: "light.nopruning",
- Usage: "Disable ancient light chain data pruning",
- Category: flags.LightCategory,
- }
- LightNoSyncServeFlag = &cli.BoolFlag{
- Name: "light.nosyncserve",
- Usage: "Enables serving light clients before syncing",
- Category: flags.LightCategory,
- }
// Transaction pool settings
TxPoolLocalsFlag = &cli.StringFlag{
Name: "txpool.locals",
@@ -1137,8 +1101,10 @@ func SplitAndTrim(input string) (ret []string) {
// setHTTP creates the HTTP RPC listener interface string from the set
// command line flags, returning empty if the HTTP endpoint is disabled.
func setHTTP(ctx *cli.Context, cfg *node.Config) {
- if ctx.Bool(HTTPEnabledFlag.Name) && cfg.HTTPHost == "" {
- cfg.HTTPHost = "127.0.0.1"
+ if ctx.Bool(HTTPEnabledFlag.Name) {
+ if cfg.HTTPHost == "" {
+ cfg.HTTPHost = "127.0.0.1"
+ }
if ctx.IsSet(HTTPListenAddrFlag.Name) {
cfg.HTTPHost = ctx.String(HTTPListenAddrFlag.Name)
}
@@ -1202,8 +1168,10 @@ func setGraphQL(ctx *cli.Context, cfg *node.Config) {
// setWS creates the WebSocket RPC listener interface string from the set
// command line flags, returning empty if the HTTP endpoint is disabled.
func setWS(ctx *cli.Context, cfg *node.Config) {
- if ctx.Bool(WSEnabledFlag.Name) && cfg.WSHost == "" {
- cfg.WSHost = "127.0.0.1"
+ if ctx.Bool(WSEnabledFlag.Name) {
+ if cfg.WSHost == "" {
+ cfg.WSHost = "127.0.0.1"
+ }
if ctx.IsSet(WSListenAddrFlag.Name) {
cfg.WSHost = ctx.String(WSListenAddrFlag.Name)
}
@@ -1237,25 +1205,25 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
}
}
-// setLes configures the les server and ultra light client settings from the command line flags.
+// setLes shows the deprecation warnings for LES flags.
func setLes(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.IsSet(LightServeFlag.Name) {
- cfg.LightServ = ctx.Int(LightServeFlag.Name)
+ log.Warn("The light server has been deprecated, please remove this flag", "flag", LightServeFlag.Name)
}
if ctx.IsSet(LightIngressFlag.Name) {
- cfg.LightIngress = ctx.Int(LightIngressFlag.Name)
+ log.Warn("The light server has been deprecated, please remove this flag", "flag", LightIngressFlag.Name)
}
if ctx.IsSet(LightEgressFlag.Name) {
- cfg.LightEgress = ctx.Int(LightEgressFlag.Name)
+ log.Warn("The light server has been deprecated, please remove this flag", "flag", LightEgressFlag.Name)
}
if ctx.IsSet(LightMaxPeersFlag.Name) {
- cfg.LightPeers = ctx.Int(LightMaxPeersFlag.Name)
+ log.Warn("The light server has been deprecated, please remove this flag", "flag", LightMaxPeersFlag.Name)
}
if ctx.IsSet(LightNoPruneFlag.Name) {
- cfg.LightNoPrune = ctx.Bool(LightNoPruneFlag.Name)
+ log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoPruneFlag.Name)
}
if ctx.IsSet(LightNoSyncServeFlag.Name) {
- cfg.LightNoSyncServe = ctx.Bool(LightNoSyncServeFlag.Name)
+ log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoSyncServeFlag.Name)
}
}
@@ -1353,58 +1321,24 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
setBootstrapNodes(ctx, cfg)
setBootstrapNodesV5(ctx, cfg)
- lightClient := ctx.String(SyncModeFlag.Name) == "light"
- lightServer := (ctx.Int(LightServeFlag.Name) != 0)
-
- lightPeers := ctx.Int(LightMaxPeersFlag.Name)
- if lightClient && !ctx.IsSet(LightMaxPeersFlag.Name) {
- // dynamic default - for clients we use 1/10th of the default for servers
- lightPeers /= 10
- }
-
if ctx.IsSet(MaxPeersFlag.Name) {
cfg.MaxPeers = ctx.Int(MaxPeersFlag.Name)
- if lightServer && !ctx.IsSet(LightMaxPeersFlag.Name) {
- cfg.MaxPeers += lightPeers
- }
- } else {
- if lightServer {
- cfg.MaxPeers += lightPeers
- }
- if lightClient && ctx.IsSet(LightMaxPeersFlag.Name) && cfg.MaxPeers < lightPeers {
- cfg.MaxPeers = lightPeers
- }
}
- if !(lightClient || lightServer) {
- lightPeers = 0
- }
- ethPeers := cfg.MaxPeers - lightPeers
- if lightClient {
- ethPeers = 0
- }
- log.Info("Maximum peer count", "ETH", ethPeers, "LES", lightPeers, "total", cfg.MaxPeers)
+ ethPeers := cfg.MaxPeers
+ log.Info("Maximum peer count", "ETH", ethPeers, "total", cfg.MaxPeers)
if ctx.IsSet(MaxPendingPeersFlag.Name) {
cfg.MaxPendingPeers = ctx.Int(MaxPendingPeersFlag.Name)
}
- if ctx.IsSet(NoDiscoverFlag.Name) || lightClient {
+ if ctx.IsSet(NoDiscoverFlag.Name) {
cfg.NoDiscovery = true
}
- // Disallow --nodiscover when used in conjunction with light mode.
- if (lightClient || lightServer) && ctx.Bool(NoDiscoverFlag.Name) {
- Fatalf("Cannot use --" + NoDiscoverFlag.Name + " in light client or light server mode")
- }
CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag)
CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag)
cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name)
cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.Name)
- // If we're running a light client or server, force enable the v5 peer discovery.
- if lightClient || lightServer {
- cfg.DiscoveryV5 = true
- }
-
if netrestrict := ctx.String(NetrestrictFlag.Name); netrestrict != "" {
list, err := netutil.ParseNetlist(netrestrict)
if err != nil {
@@ -1472,6 +1406,13 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
log.Info(fmt.Sprintf("Using %s as db engine", dbEngine))
cfg.DBEngine = dbEngine
}
+ // deprecation notice for log debug flags (TODO: find a more appropriate place to put these?)
+ if ctx.IsSet(LogBacktraceAtFlag.Name) {
+ log.Warn("log.backtrace flag is deprecated")
+ }
+ if ctx.IsSet(LogDebugFlag.Name) {
+ log.Warn("log.debug flag is deprecated")
+ }
}
func setSmartCard(ctx *cli.Context, cfg *node.Config) {
@@ -1515,12 +1456,7 @@ func SetDataDir(ctx *cli.Context, cfg *node.Config) {
}
}
-func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
- // If we are running the light client, apply another group
- // settings for gas oracle.
- if light {
- *cfg = ethconfig.LightClientGPO
- }
+func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
if ctx.IsSet(GpoBlocksFlag.Name) {
cfg.Blocks = ctx.Int(GpoBlocksFlag.Name)
}
@@ -1669,12 +1605,11 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) {
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags
CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag, HoleskyFlag)
- CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light")
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
// Set configurations from CLI flags
setEtherbase(ctx, cfg)
- setGPO(ctx, &cfg.GPO, ctx.String(SyncModeFlag.Name) == "light")
+ setGPO(ctx, &cfg.GPO)
setTxPool(ctx, &cfg.TxPool)
setMiner(ctx, &cfg.Miner)
setRequiredBlocks(ctx, cfg)
@@ -1767,9 +1702,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.TransactionHistory = 0
log.Warn("Disabled transaction unindexing for archive node")
}
- if ctx.IsSet(LightServeFlag.Name) && cfg.TransactionHistory != 0 {
- log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited")
- }
if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) {
cfg.TrieCleanCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100
}
@@ -1782,10 +1714,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(CacheLogSizeFlag.Name) {
cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name)
}
- if !ctx.Bool(SnapshotFlag.Name) {
+ if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
// If snap-sync is requested, this flag is also required
if cfg.SyncMode == downloader.SnapSync {
- log.Info("Snap sync requested, enabling --snapshot")
+ if !ctx.Bool(SnapshotFlag.Name) {
+ log.Warn("Snap sync requested, enabling --snapshot")
+ }
+ if cfg.SnapshotCache == 0 {
+ log.Warn("Snap sync requested, resetting --cache.snapshot")
+ cfg.SnapshotCache = ctx.Int(CacheFlag.Name) * CacheSnapshotFlag.Value / 100
+ }
} else {
cfg.TrieCleanCache += cfg.SnapshotCache
cfg.SnapshotCache = 0 // Disabled
@@ -1898,11 +1836,26 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
log.Info("Using developer account", "address", developer.Address)
// Create a new developer genesis block or reuse existing one
- cfg.Genesis = core.DeveloperGenesisBlock(ctx.Uint64(DeveloperGasLimitFlag.Name), developer.Address)
+ cfg.Genesis = core.DeveloperGenesisBlock(ctx.Uint64(DeveloperGasLimitFlag.Name), &developer.Address)
if ctx.IsSet(DataDirFlag.Name) {
chaindb := tryMakeReadOnlyDatabase(ctx, stack)
if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) {
cfg.Genesis = nil // fallback to db content
+
+ //validate genesis has PoS enabled in block 0
+ genesis, err := core.ReadGenesis(chaindb)
+ if err != nil {
+ Fatalf("Could not read genesis from database: %v", err)
+ }
+ if !genesis.Config.TerminalTotalDifficultyPassed {
+ Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true in developer mode")
+ }
+ if genesis.Config.TerminalTotalDifficulty == nil {
+ Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified.")
+ }
+ if genesis.Difficulty.Cmp(genesis.Config.TerminalTotalDifficulty) != 1 {
+ Fatalf("Bad developer-mode genesis configuration: genesis block difficulty must be > terminalTotalDifficulty")
+ }
}
chaindb.Close()
}
@@ -1941,9 +1894,6 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
return // already set through flags/config
}
protocol := "all"
- if cfg.SyncMode == downloader.LightSync {
- protocol = "les"
- }
if url := params.KnownDNSNetwork(genesis, protocol); url != "" {
cfg.EthDiscoveryURLs = []string{url}
cfg.SnapDiscoveryURLs = cfg.EthDiscoveryURLs
@@ -1951,27 +1901,12 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
}
// RegisterEthService adds an Ethereum client to the stack.
-// The second return value is the full node instance, which may be nil if the
-// node is running as a light client.
+// The second return value is the full node instance.
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
- if cfg.SyncMode == downloader.LightSync {
- backend, err := les.New(stack, cfg)
- if err != nil {
- Fatalf("Failed to register the Ethereum service: %v", err)
- }
- stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))
- return backend.ApiBackend, nil
- }
backend, err := eth.New(stack, cfg)
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
- if cfg.LightServ > 0 {
- _, err := les.NewLesServer(stack, backend, cfg)
- if err != nil {
- Fatalf("Failed to create the LES server: %v", err)
- }
- }
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
return backend.APIBackend, backend
}
@@ -1993,13 +1928,12 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst
// RegisterFilterAPI adds the eth log filtering RPC API to the node.
func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem {
- isLightClient := ethcfg.SyncMode == downloader.LightSync
filterSystem := filters.NewFilterSystem(backend, filters.Config{
LogCacheSize: ethcfg.FilterLogCacheSize,
})
stack.RegisterAPIs([]rpc.API{{
Namespace: "eth",
- Service: filters.NewFilterAPI(filterSystem, isLightClient),
+ Service: filters.NewFilterAPI(filterSystem, false),
}})
return filterSystem
}
@@ -2260,9 +2194,10 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
}
// MakeTrieDatabase constructs a trie database based on the configured scheme.
-func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, readOnly bool) *trie.Database {
+func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *trie.Database {
config := &trie.Config{
Preimages: preimage,
+ IsVerkle: isVerkle,
}
scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk)
if err != nil {
diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go
index 6669ff176..243abd831 100644
--- a/cmd/utils/flags_legacy.go
+++ b/cmd/utils/flags_legacy.go
@@ -39,6 +39,14 @@ var DeprecatedFlags = []cli.Flag{
CacheTrieRejournalFlag,
LegacyDiscoveryV5Flag,
TxLookupLimitFlag,
+ LightServeFlag,
+ LightIngressFlag,
+ LightEgressFlag,
+ LightMaxPeersFlag,
+ LightNoPruneFlag,
+ LightNoSyncServeFlag,
+ LogBacktraceAtFlag,
+ LogDebugFlag,
}
var (
@@ -77,6 +85,53 @@ var (
Value: ethconfig.Defaults.TransactionHistory,
Category: flags.DeprecatedCategory,
}
+ // Light server and client settings, Deprecated November 2023
+ LightServeFlag = &cli.IntFlag{
+ Name: "light.serve",
+ Usage: "Maximum percentage of time allowed for serving LES requests (deprecated)",
+ Value: ethconfig.Defaults.LightServ,
+ Category: flags.LightCategory,
+ }
+ LightIngressFlag = &cli.IntFlag{
+ Name: "light.ingress",
+ Usage: "Incoming bandwidth limit for serving light clients (deprecated)",
+ Value: ethconfig.Defaults.LightIngress,
+ Category: flags.LightCategory,
+ }
+ LightEgressFlag = &cli.IntFlag{
+ Name: "light.egress",
+ Usage: "Outgoing bandwidth limit for serving light clients (deprecated)",
+ Value: ethconfig.Defaults.LightEgress,
+ Category: flags.LightCategory,
+ }
+ LightMaxPeersFlag = &cli.IntFlag{
+ Name: "light.maxpeers",
+ Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated)",
+ Value: ethconfig.Defaults.LightPeers,
+ Category: flags.LightCategory,
+ }
+ LightNoPruneFlag = &cli.BoolFlag{
+ Name: "light.nopruning",
+ Usage: "Disable ancient light chain data pruning (deprecated)",
+ Category: flags.LightCategory,
+ }
+ LightNoSyncServeFlag = &cli.BoolFlag{
+ Name: "light.nosyncserve",
+ Usage: "Enables serving light clients before syncing (deprecated)",
+ Category: flags.LightCategory,
+ }
+ // Deprecated November 2023
+ LogBacktraceAtFlag = &cli.StringFlag{
+ Name: "log.backtrace",
+ Usage: "Request a stack trace at a specific logging statement (deprecated)",
+ Value: "",
+ Category: flags.DeprecatedCategory,
+ }
+ LogDebugFlag = &cli.BoolFlag{
+ Name: "log.debug",
+ Usage: "Prepends log messages with call-site location (deprecated)",
+ Category: flags.DeprecatedCategory,
+ }
)
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
diff --git a/cmd/utils/flags_test.go b/cmd/utils/flags_test.go
index adfdd0903..00c73a526 100644
--- a/cmd/utils/flags_test.go
+++ b/cmd/utils/flags_test.go
@@ -23,6 +23,7 @@ import (
)
func Test_SplitTagsFlag(t *testing.T) {
+ t.Parallel()
tests := []struct {
name string
args string
@@ -55,7 +56,9 @@ func Test_SplitTagsFlag(t *testing.T) {
},
}
for _, tt := range tests {
+ tt := tt
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
if got := SplitTagsFlag(tt.args); !reflect.DeepEqual(got, tt.want) {
t.Errorf("splitTagsFlag() = %v, want %v", got, tt.want)
}
diff --git a/cmd/utils/prompt_test.go b/cmd/utils/prompt_test.go
index 86ee8b652..889bf71de 100644
--- a/cmd/utils/prompt_test.go
+++ b/cmd/utils/prompt_test.go
@@ -22,6 +22,7 @@ import (
)
func TestGetPassPhraseWithList(t *testing.T) {
+ t.Parallel()
type args struct {
text string
confirmation bool
@@ -65,7 +66,9 @@ func TestGetPassPhraseWithList(t *testing.T) {
},
}
for _, tt := range tests {
+ tt := tt
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
if got := GetPassPhraseWithList(tt.args.text, tt.args.confirmation, tt.args.index, tt.args.passwords); got != tt.want {
t.Errorf("GetPassPhraseWithList() = %v, want %v", got, tt.want)
}
diff --git a/common/bitutil/compress_test.go b/common/bitutil/compress_test.go
index 13a13011d..c6f6fe8bc 100644
--- a/common/bitutil/compress_test.go
+++ b/common/bitutil/compress_test.go
@@ -18,6 +18,7 @@ package bitutil
import (
"bytes"
+ "fmt"
"math/rand"
"testing"
@@ -48,19 +49,23 @@ func TestEncodingCycle(t *testing.T) {
"0xdf7070533534333636313639343638373532313536346c1bc333393438373130707063363430353639343638373532313536346c1bc333393438336336346c65fe",
}
for i, tt := range tests {
- data := hexutil.MustDecode(tt)
-
- proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
- if err != nil {
- t.Errorf("test %d: failed to decompress compressed data: %v", i, err)
- continue
- }
- if !bytes.Equal(data, proc) {
- t.Errorf("test %d: compress/decompress mismatch: have %x, want %x", i, proc, data)
+ if err := testEncodingCycle(hexutil.MustDecode(tt)); err != nil {
+ t.Errorf("test %d: %v", i, err)
}
}
}
+func testEncodingCycle(data []byte) error {
+ proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
+ if err != nil {
+ return fmt.Errorf("failed to decompress compressed data: %v", err)
+ }
+ if !bytes.Equal(data, proc) {
+ return fmt.Errorf("compress/decompress mismatch: have %x, want %x", proc, data)
+ }
+ return nil
+}
+
// Tests that data bitset decoding and rencoding works and is bijective.
func TestDecodingCycle(t *testing.T) {
tests := []struct {
@@ -179,3 +184,40 @@ func benchmarkEncoding(b *testing.B, bytes int, fill float64) {
bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
}
}
+
+func FuzzEncoder(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ if err := testEncodingCycle(data); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+func FuzzDecoder(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ fuzzDecode(data)
+ })
+}
+
+// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and
+// reencoding algorithm.
+func fuzzDecode(data []byte) {
+ blob, err := DecompressBytes(data, 1024)
+ if err != nil {
+ return
+ }
+ // re-compress it (it's OK if the re-compressed differs from the
+ // original - the first input may not have been compressed at all)
+ comp := CompressBytes(blob)
+ if len(comp) > len(blob) {
+ // After compression, it must be smaller or equal
+ panic("bad compression")
+ }
+ // But decompressing it once again should work
+ decomp, err := DecompressBytes(data, 1024)
+ if err != nil {
+ panic(err)
+ }
+ if !bytes.Equal(decomp, blob) {
+ panic("content mismatch")
+ }
+}
diff --git a/common/hexutil/json.go b/common/hexutil/json.go
index 50db20811..e0ac98f52 100644
--- a/common/hexutil/json.go
+++ b/common/hexutil/json.go
@@ -23,6 +23,8 @@ import (
"math/big"
"reflect"
"strconv"
+
+ "github.com/holiman/uint256"
)
var (
@@ -30,6 +32,7 @@ var (
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
+ u256T = reflect.TypeOf((*uint256.Int)(nil))
)
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
@@ -225,6 +228,48 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error {
return err
}
+// U256 marshals/unmarshals as a JSON string with 0x prefix.
+// The zero value marshals as "0x0".
+type U256 uint256.Int
+
+// MarshalText implements encoding.TextMarshaler
+func (b U256) MarshalText() ([]byte, error) {
+ u256 := (*uint256.Int)(&b)
+ return []byte(u256.Hex()), nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (b *U256) UnmarshalJSON(input []byte) error {
+ // The uint256.Int.UnmarshalJSON method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ if !isString(input) {
+ return errNonString(u256T)
+ }
+ // The hex decoder needs to accept empty string ("") as '0', which uint256.Int
+ // would reject.
+ if len(input) == 2 {
+ (*uint256.Int)(b).Clear()
+ return nil
+ }
+ err := (*uint256.Int)(b).SetFromHex(string(input[1 : len(input)-1]))
+ if err != nil {
+ return &json.UnmarshalTypeError{Value: err.Error(), Type: u256T}
+ }
+ return nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler
+func (b *U256) UnmarshalText(input []byte) error {
+ // The uint256.Int.UnmarshalText method accepts "dec", "0xhex"; we must be
+ // more strict, hence we check string and invoke SetFromHex directly.
+ return (*uint256.Int)(b).SetFromHex(string(input))
+}
+
+// String returns the hex encoding of b.
+func (b *U256) String() string {
+ return (*uint256.Int)(b).Hex()
+}
+
// Uint64 marshals/unmarshals as a JSON string with 0x prefix.
// The zero value marshals as "0x0".
type Uint64 uint64
diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go
index ed7d6fad1..7cca30095 100644
--- a/common/hexutil/json_test.go
+++ b/common/hexutil/json_test.go
@@ -23,6 +23,8 @@ import (
"errors"
"math/big"
"testing"
+
+ "github.com/holiman/uint256"
)
func checkError(t *testing.T, input string, got, want error) bool {
@@ -176,6 +178,64 @@ func TestUnmarshalBig(t *testing.T) {
}
}
+var unmarshalU256Tests = []unmarshalTest{
+ // invalid encoding
+ {input: "", wantErr: errJSONEOF},
+ {input: "null", wantErr: errNonString(u256T)},
+ {input: "10", wantErr: errNonString(u256T)},
+ {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, u256T)},
+ {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, u256T)},
+ {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, u256T)},
+ {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, u256T)},
+ {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, u256T)},
+ {
+ input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`,
+ wantErr: wrapTypeError(ErrBig256Range, u256T),
+ },
+
+ // valid encoding
+ {input: `""`, want: big.NewInt(0)},
+ {input: `"0x0"`, want: big.NewInt(0)},
+ {input: `"0x2"`, want: big.NewInt(0x2)},
+ {input: `"0x2F2"`, want: big.NewInt(0x2f2)},
+ {input: `"0X2F2"`, want: big.NewInt(0x2f2)},
+ {input: `"0x1122aaff"`, want: big.NewInt(0x1122aaff)},
+ {input: `"0xbBb"`, want: big.NewInt(0xbbb)},
+ {input: `"0xfffffffff"`, want: big.NewInt(0xfffffffff)},
+ {
+ input: `"0x112233445566778899aabbccddeeff"`,
+ want: referenceBig("112233445566778899aabbccddeeff"),
+ },
+ {
+ input: `"0xffffffffffffffffffffffffffffffffffff"`,
+ want: referenceBig("ffffffffffffffffffffffffffffffffffff"),
+ },
+ {
+ input: `"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`,
+ want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+ },
+}
+
+func TestUnmarshalU256(t *testing.T) {
+ for _, test := range unmarshalU256Tests {
+ var v U256
+ err := json.Unmarshal([]byte(test.input), &v)
+ if !checkError(t, test.input, err, test.wantErr) {
+ continue
+ }
+ if test.want == nil {
+ continue
+ }
+ want := new(uint256.Int)
+ want.SetFromBig(test.want.(*big.Int))
+ have := (*uint256.Int)(&v)
+ if want.Cmp(have) != 0 {
+ t.Errorf("input %s: value mismatch: have %x, want %x", test.input, have, want)
+ continue
+ }
+ }
+}
+
func BenchmarkUnmarshalBig(b *testing.B) {
input := []byte(`"0x123456789abcdef123456789abcdef"`)
for i := 0; i < b.N; i++ {
diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go
index f708050ab..c693189ea 100644
--- a/consensus/clique/clique.go
+++ b/consensus/clique/clique.go
@@ -302,9 +302,22 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
if chain.Config().IsShanghai(header.Number, header.Time) {
return errors.New("clique does not support shanghai fork")
}
+ // Verify the non-existence of withdrawalsHash.
+ if header.WithdrawalsHash != nil {
+ return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash)
+ }
if chain.Config().IsCancun(header.Number, header.Time) {
return errors.New("clique does not support cancun fork")
}
+ // Verify the non-existence of cancun-specific header fields
+ switch {
+ case header.ExcessBlobGas != nil:
+ return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
+ case header.BlobGasUsed != nil:
+ return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
+ case header.ParentBeaconRoot != nil:
+ return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
+ }
// All basic checks passed, verify cascading fields
return c.verifyCascadingFields(chain, header, parents)
}
@@ -753,6 +766,15 @@ func encodeSigHeader(w io.Writer, header *types.Header) {
if header.WithdrawalsHash != nil {
panic("unexpected withdrawal hash value in clique")
}
+ if header.ExcessBlobGas != nil {
+ panic("unexpected excess blob gas value in clique")
+ }
+ if header.BlobGasUsed != nil {
+ panic("unexpected blob gas used value in clique")
+ }
+ if header.ParentBeaconRoot != nil {
+ panic("unexpected parent beacon root value in clique")
+ }
if err := rlp.Encode(w, enc); err != nil {
panic("can't encode: " + err.Error())
}
diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go
index 8eb9863da..130dfdf21 100644
--- a/consensus/ethash/consensus.go
+++ b/consensus/ethash/consensus.go
@@ -266,9 +266,22 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa
if chain.Config().IsShanghai(header.Number, header.Time) {
return errors.New("ethash does not support shanghai fork")
}
+ // Verify the non-existence of withdrawalsHash.
+ if header.WithdrawalsHash != nil {
+ return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash)
+ }
if chain.Config().IsCancun(header.Number, header.Time) {
return errors.New("ethash does not support cancun fork")
}
+ // Verify the non-existence of cancun-specific header fields
+ switch {
+ case header.ExcessBlobGas != nil:
+ return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
+ case header.BlobGasUsed != nil:
+ return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
+ case header.ParentBeaconRoot != nil:
+ return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
+ }
// Add some fake checks for tests
if ethash.fakeDelay != nil {
time.Sleep(*ethash.fakeDelay)
@@ -533,6 +546,15 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
if header.WithdrawalsHash != nil {
panic("withdrawal hash set on ethash")
}
+ if header.ExcessBlobGas != nil {
+ panic("excess blob gas set on ethash")
+ }
+ if header.BlobGasUsed != nil {
+ panic("blob gas used set on ethash")
+ }
+ if header.ParentBeaconRoot != nil {
+ panic("parent beacon root set on ethash")
+ }
rlp.Encode(hasher, enc)
hasher.Sum(hash[:0])
return hash
diff --git a/console/bridge.go b/console/bridge.go
index c67686d6c..37578041c 100644
--- a/console/bridge.go
+++ b/console/bridge.go
@@ -78,7 +78,7 @@ func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) {
return nil, err
}
if password != confirm {
- return nil, errors.New("passwords don't match!")
+ return nil, errors.New("passwords don't match")
}
// A single string password was specified, use that
case len(call.Arguments) == 1 && call.Argument(0).ToString() != nil:
diff --git a/console/console_test.go b/console/console_test.go
index ee5c36be4..a13be6a99 100644
--- a/console/console_test.go
+++ b/console/console_test.go
@@ -94,7 +94,7 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
t.Fatalf("failed to create node: %v", err)
}
ethConf := ðconfig.Config{
- Genesis: core.DeveloperGenesisBlock(11_500_000, common.Address{}),
+ Genesis: core.DeveloperGenesisBlock(11_500_000, nil),
Miner: miner.Config{
Etherbase: common.HexToAddress(testAddress),
},
diff --git a/core/asm/asm_test.go b/core/asm/asm_test.go
index 92b26b67a..cd7520ec6 100644
--- a/core/asm/asm_test.go
+++ b/core/asm/asm_test.go
@@ -22,53 +22,37 @@ import (
"encoding/hex"
)
-// Tests disassembling the instructions for valid evm code
-func TestInstructionIteratorValid(t *testing.T) {
- cnt := 0
- script, _ := hex.DecodeString("61000000")
+// Tests disassembling instructions
+func TestInstructionIterator(t *testing.T) {
+ for i, tc := range []struct {
+ want int
+ code string
+ wantErr string
+ }{
+ {2, "61000000", ""}, // valid code
+ {0, "6100", "incomplete push instruction at 0"}, // invalid code
+ {2, "5900", ""}, // push0
+ {0, "", ""}, // empty
- it := NewInstructionIterator(script)
- for it.Next() {
- cnt++
- }
-
- if err := it.Error(); err != nil {
- t.Errorf("Expected 2, but encountered error %v instead.", err)
- }
- if cnt != 2 {
- t.Errorf("Expected 2, but got %v instead.", cnt)
- }
-}
-
-// Tests disassembling the instructions for invalid evm code
-func TestInstructionIteratorInvalid(t *testing.T) {
- cnt := 0
- script, _ := hex.DecodeString("6100")
-
- it := NewInstructionIterator(script)
- for it.Next() {
- cnt++
- }
-
- if it.Error() == nil {
- t.Errorf("Expected an error, but got %v instead.", cnt)
- }
-}
-
-// Tests disassembling the instructions for empty evm code
-func TestInstructionIteratorEmpty(t *testing.T) {
- cnt := 0
- script, _ := hex.DecodeString("")
-
- it := NewInstructionIterator(script)
- for it.Next() {
- cnt++
- }
-
- if err := it.Error(); err != nil {
- t.Errorf("Expected 0, but encountered error %v instead.", err)
- }
- if cnt != 0 {
- t.Errorf("Expected 0, but got %v instead.", cnt)
+ } {
+ var (
+ have int
+ code, _ = hex.DecodeString(tc.code)
+ it = NewInstructionIterator(code)
+ )
+ for it.Next() {
+ have++
+ }
+ var haveErr = ""
+ if it.Error() != nil {
+ haveErr = it.Error().Error()
+ }
+ if haveErr != tc.wantErr {
+ t.Errorf("test %d: encountered error: %q want %q", i, haveErr, tc.wantErr)
+ continue
+ }
+ if have != tc.want {
+ t.Errorf("wrong instruction count, have %d want %d", have, tc.want)
+ }
}
}
diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go
index db634bc14..753a32b7e 100644
--- a/core/forkid/forkid_test.go
+++ b/core/forkid/forkid_test.go
@@ -91,8 +91,10 @@ func TestCreation(t *testing.T) {
{5000000, 0, ID{Hash: checksumToBytes(0x757a1c47), Next: 5062605}}, // Last Berlin block
{5062605, 0, ID{Hash: checksumToBytes(0xB8C6299D), Next: 1678832736}}, // First London block
{6000000, 1678832735, ID{Hash: checksumToBytes(0xB8C6299D), Next: 1678832736}}, // Last London block
- {6000001, 1678832736, ID{Hash: checksumToBytes(0xf9843abf), Next: 0}}, // First Shanghai block
- {6500000, 2678832736, ID{Hash: checksumToBytes(0xf9843abf), Next: 0}}, // Future Shanghai block
+ {6000001, 1678832736, ID{Hash: checksumToBytes(0xf9843abf), Next: 1705473120}}, // First Shanghai block
+ {6500002, 1705473119, ID{Hash: checksumToBytes(0xf9843abf), Next: 1705473120}}, // Last Shanghai block
+ {6500003, 1705473120, ID{Hash: checksumToBytes(0x70cc14e2), Next: 0}}, // First Cancun block
+ {6500003, 2705473120, ID{Hash: checksumToBytes(0x70cc14e2), Next: 0}}, // Future Cancun block
},
},
// Sepolia test cases
@@ -366,8 +368,9 @@ func TestValidation(t *testing.T) {
// TODO(karalabe): Enable this when Cancun is specced
//{params.MainnetChainConfig, 20999999, 1677999999, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, ErrLocalIncompatibleOrStale},
}
+ genesis := core.DefaultGenesisBlock().ToBlock()
for i, tt := range tests {
- filter := newFilter(tt.config, core.DefaultGenesisBlock().ToBlock(), func() (uint64, uint64) { return tt.head, tt.time })
+ filter := newFilter(tt.config, genesis, func() (uint64, uint64) { return tt.head, tt.time })
if err := filter(tt.id); err != tt.err {
t.Errorf("test %d: validation error mismatch: have %v, want %v", i, err, tt.err)
}
diff --git a/core/genesis.go b/core/genesis.go
index 1045815fa..634be9a9e 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
+ "github.com/ethereum/go-ethereum/trie/triedb/pathdb"
)
//go:generate go run github.com/fjl/gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go
@@ -121,10 +122,20 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error {
}
// hash computes the state root according to the genesis specification.
-func (ga *GenesisAlloc) hash() (common.Hash, error) {
+func (ga *GenesisAlloc) hash(isVerkle bool) (common.Hash, error) {
+ // If a genesis-time verkle trie is requested, create a trie config
+ // with the verkle trie enabled so that the tree can be initialized
+ // as such.
+ var config *trie.Config
+ if isVerkle {
+ config = &trie.Config{
+ PathDB: pathdb.Defaults,
+ IsVerkle: true,
+ }
+ }
// Create an ephemeral in-memory database for computing hash,
// all the derived states will be discarded to not pollute disk.
- db := state.NewDatabase(rawdb.NewMemoryDatabase())
+ db := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), config)
statedb, err := state.New(types.EmptyRootHash, db, nil)
if err != nil {
return common.Hash{}, err
@@ -410,9 +421,15 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
}
}
+// IsVerkle indicates whether the state is already stored in a verkle
+// tree at genesis time.
+func (g *Genesis) IsVerkle() bool {
+ return g.Config.IsVerkle(new(big.Int).SetUint64(g.Number), g.Timestamp)
+}
+
// ToBlock returns the genesis block according to genesis specification.
func (g *Genesis) ToBlock() *types.Block {
- root, err := g.Alloc.hash()
+ root, err := g.Alloc.hash(g.IsVerkle())
if err != nil {
panic(err)
}
@@ -563,16 +580,16 @@ func DefaultHoleskyGenesisBlock() *Genesis {
}
// DeveloperGenesisBlock returns the 'geth --dev' genesis block.
-func DeveloperGenesisBlock(gasLimit uint64, faucet common.Address) *Genesis {
+func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
// Override the default period to the user requested one
config := *params.AllDevChainProtocolChanges
// Assemble and return the genesis with the precompiles and faucet pre-funded
- return &Genesis{
+ genesis := &Genesis{
Config: &config,
GasLimit: gasLimit,
BaseFee: big.NewInt(params.InitialBaseFee),
- Difficulty: big.NewInt(0),
+ Difficulty: big.NewInt(1),
Alloc: map[common.Address]GenesisAccount{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
@@ -583,9 +600,12 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet common.Address) *Genesis {
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
- faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))},
},
}
+ if faucet != nil {
+ genesis.Alloc[*faucet] = GenesisAccount{Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))}
+ }
+ return genesis
}
func decodePrealloc(data string) GenesisAlloc {
diff --git a/core/genesis_test.go b/core/genesis_test.go
index fac88ff37..1d85b510c 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -17,6 +17,7 @@
package core
import (
+ "bytes"
"encoding/json"
"math/big"
"reflect"
@@ -231,7 +232,7 @@ func TestReadWriteGenesisAlloc(t *testing.T) {
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
{2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}},
}
- hash, _ = alloc.hash()
+ hash, _ = alloc.hash(false)
)
blob, _ := json.Marshal(alloc)
rawdb.WriteGenesisStateSpec(db, hash, blob)
@@ -261,3 +262,66 @@ func newDbConfig(scheme string) *trie.Config {
}
return &trie.Config{PathDB: pathdb.Defaults}
}
+
+func TestVerkleGenesisCommit(t *testing.T) {
+ var verkleTime uint64 = 0
+ verkleConfig := ¶ms.ChainConfig{
+ ChainID: big.NewInt(1),
+ HomesteadBlock: big.NewInt(0),
+ DAOForkBlock: nil,
+ DAOForkSupport: false,
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ GrayGlacierBlock: big.NewInt(0),
+ MergeNetsplitBlock: nil,
+ ShanghaiTime: &verkleTime,
+ CancunTime: &verkleTime,
+ PragueTime: &verkleTime,
+ VerkleTime: &verkleTime,
+ TerminalTotalDifficulty: big.NewInt(0),
+ TerminalTotalDifficultyPassed: true,
+ Ethash: nil,
+ Clique: nil,
+ }
+
+ genesis := &Genesis{
+ BaseFee: big.NewInt(params.InitialBaseFee),
+ Config: verkleConfig,
+ Timestamp: verkleTime,
+ Difficulty: big.NewInt(0),
+ Alloc: GenesisAlloc{
+ {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
+ },
+ }
+
+ expected := common.Hex2Bytes("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
+ got := genesis.ToBlock().Root().Bytes()
+ if !bytes.Equal(got, expected) {
+ t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
+ }
+
+ db := rawdb.NewMemoryDatabase()
+ triedb := trie.NewDatabase(db, &trie.Config{IsVerkle: true, PathDB: pathdb.Defaults})
+ block := genesis.MustCommit(db, triedb)
+ if !bytes.Equal(block.Root().Bytes(), expected) {
+ t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
+ }
+
+ // Test that the trie is verkle
+ if !triedb.IsVerkle() {
+ t.Fatalf("expected trie to be verkle")
+ }
+
+ if !rawdb.ExistsAccountTrieNode(db, nil) {
+ t.Fatal("could not find node")
+ }
+}
diff --git a/core/rawdb/accessors_trie.go b/core/rawdb/accessors_trie.go
index 78f1a70b1..ea3367db3 100644
--- a/core/rawdb/accessors_trie.go
+++ b/core/rawdb/accessors_trie.go
@@ -292,6 +292,11 @@ func ReadStateScheme(db ethdb.Reader) string {
if len(blob) != 0 {
return PathScheme
}
+ // The root node might be deleted during the initial snap sync, check
+ // the persistent state id then.
+ if id := ReadPersistentStateID(db); id != 0 {
+ return PathScheme
+ }
// In a hash-based scheme, the genesis state is consistently stored
// on the disk. To assess the scheme of the persistent state, it
// suffices to inspect the scheme of the genesis state.
diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go
index 6f409fff1..e88867af0 100644
--- a/core/rawdb/ancient_scheme.go
+++ b/core/rawdb/ancient_scheme.go
@@ -68,14 +68,14 @@ var stateFreezerNoSnappy = map[string]bool{
// The list of identifiers of ancient stores.
var (
- chainFreezerName = "chain" // the folder name of chain segment ancient store.
- stateFreezerName = "state" // the folder name of reverse diff ancient store.
+ ChainFreezerName = "chain" // the folder name of chain segment ancient store.
+ StateFreezerName = "state" // the folder name of reverse diff ancient store.
)
// freezers the collections of all builtin freezers.
-var freezers = []string{chainFreezerName, stateFreezerName}
+var freezers = []string{ChainFreezerName, StateFreezerName}
// NewStateFreezer initializes the freezer for state history.
func NewStateFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) {
- return NewResettableFreezer(filepath.Join(ancientDir, stateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
+ return NewResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
}
diff --git a/core/rawdb/ancient_utils.go b/core/rawdb/ancient_utils.go
index 1b93a9aa5..428cda544 100644
--- a/core/rawdb/ancient_utils.go
+++ b/core/rawdb/ancient_utils.go
@@ -81,14 +81,14 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
var infos []freezerInfo
for _, freezer := range freezers {
switch freezer {
- case chainFreezerName:
- info, err := inspect(chainFreezerName, chainFreezerNoSnappy, db)
+ case ChainFreezerName:
+ info, err := inspect(ChainFreezerName, chainFreezerNoSnappy, db)
if err != nil {
return nil, err
}
infos = append(infos, info)
- case stateFreezerName:
+ case StateFreezerName:
if ReadStateScheme(db) != PathScheme {
continue
}
@@ -102,7 +102,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
}
defer f.Close()
- info, err := inspect(stateFreezerName, stateFreezerNoSnappy, f)
+ info, err := inspect(StateFreezerName, stateFreezerNoSnappy, f)
if err != nil {
return nil, err
}
@@ -125,9 +125,9 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s
tables map[string]bool
)
switch freezerName {
- case chainFreezerName:
+ case ChainFreezerName:
path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy
- case stateFreezerName:
+ case StateFreezerName:
path, tables = filepath.Join(ancient, freezerName), stateFreezerNoSnappy
default:
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go
index cbfaf5b9e..bb2c409db 100644
--- a/core/rawdb/chain_freezer.go
+++ b/core/rawdb/chain_freezer.go
@@ -131,7 +131,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
continue
case *number < threshold:
- log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold)
+ log.Debug("Current full block not old enough to freeze", "number", *number, "hash", hash, "delay", threshold)
backoff = true
continue
diff --git a/core/rawdb/database.go b/core/rawdb/database.go
index 1d7b7d1ca..18b5bccb5 100644
--- a/core/rawdb/database.go
+++ b/core/rawdb/database.go
@@ -178,7 +178,7 @@ func resolveChainFreezerDir(ancient string) string {
// sub folder, if not then two possibilities:
// - chain freezer is not initialized
// - chain freezer exists in legacy location (root ancient folder)
- freezer := path.Join(ancient, chainFreezerName)
+ freezer := path.Join(ancient, ChainFreezerName)
if !common.FileExist(freezer) {
if !common.FileExist(ancient) {
// The entire ancient store is not initialized, still use the sub
diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go
index 61436bf93..4b9d510e8 100644
--- a/core/rawdb/freezer_table.go
+++ b/core/rawdb/freezer_table.go
@@ -467,6 +467,20 @@ func (t *freezerTable) truncateHead(items uint64) error {
return nil
}
+// sizeHidden returns the total data size of hidden items in the freezer table.
+// This function assumes the lock is already held.
+func (t *freezerTable) sizeHidden() (uint64, error) {
+ hidden, offset := t.itemHidden.Load(), t.itemOffset.Load()
+ if hidden <= offset {
+ return 0, nil
+ }
+ indices, err := t.getIndices(hidden-1, 1)
+ if err != nil {
+ return 0, err
+ }
+ return uint64(indices[1].offset), nil
+}
+
// truncateTail discards any recent data before the provided threshold number.
func (t *freezerTable) truncateTail(items uint64) error {
t.lock.Lock()
@@ -495,6 +509,12 @@ func (t *freezerTable) truncateTail(items uint64) error {
newTail.unmarshalBinary(buffer)
newTailId = newTail.filenum
}
+ // Save the old size for metrics tracking. This needs to be done
+ // before any updates to either itemHidden or itemOffset.
+ oldSize, err := t.sizeNolock()
+ if err != nil {
+ return err
+ }
// Update the virtual tail marker and hidden these entries in table.
t.itemHidden.Store(items)
if err := writeMetadata(t.meta, newMetadata(items)); err != nil {
@@ -509,18 +529,12 @@ func (t *freezerTable) truncateTail(items uint64) error {
if t.tailId > newTailId {
return fmt.Errorf("invalid index, tail-file %d, item-file %d", t.tailId, newTailId)
}
- // Hidden items exceed the current tail file, drop the relevant
- // data files. We need to truncate, save the old size for metrics
- // tracking.
- oldSize, err := t.sizeNolock()
- if err != nil {
- return err
- }
// Count how many items can be deleted from the file.
var (
newDeleted = items
deleted = t.itemOffset.Load()
)
+ // Hidden items exceed the current tail file, drop the relevant data files.
for current := items - 1; current >= deleted; current -= 1 {
if _, err := t.index.ReadAt(buffer, int64((current-deleted+1)*indexEntrySize)); err != nil {
return err
@@ -680,6 +694,7 @@ func (t *freezerTable) releaseFilesBefore(num uint32, remove bool) {
func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) {
// Apply the table-offset
from = from - t.itemOffset.Load()
+
// For reading N items, we need N+1 indices.
buffer := make([]byte, (count+1)*indexEntrySize)
if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil {
@@ -870,14 +885,18 @@ func (t *freezerTable) size() (uint64, error) {
return t.sizeNolock()
}
-// sizeNolock returns the total data size in the freezer table without obtaining
-// the mutex first.
+// sizeNolock returns the total data size in the freezer table. This function
+// assumes the lock is already held.
func (t *freezerTable) sizeNolock() (uint64, error) {
stat, err := t.index.Stat()
if err != nil {
return 0, err
}
- total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size())
+ hidden, err := t.sizeHidden()
+ if err != nil {
+ return 0, err
+ }
+ total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) - hidden
return total, nil
}
diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go
index 939d09394..447146393 100644
--- a/core/rawdb/freezer_table_test.go
+++ b/core/rawdb/freezer_table_test.go
@@ -658,6 +658,13 @@ func TestFreezerOffset(t *testing.T) {
}
}
+func assertTableSize(t *testing.T, f *freezerTable, size int) {
+ t.Helper()
+ if got, err := f.size(); got != uint64(size) {
+ t.Fatalf("expected size of %d bytes, got %d, err: %v", size, got, err)
+ }
+}
+
func TestTruncateTail(t *testing.T) {
t.Parallel()
rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
@@ -692,6 +699,9 @@ func TestTruncateTail(t *testing.T) {
5: getChunk(20, 0xaa),
6: getChunk(20, 0x11),
})
+ // maxFileSize*fileCount + headBytes + indexFileSize - hiddenBytes
+ expected := 20*7 + 48 - 0
+ assertTableSize(t, f, expected)
// truncate single element( item 0 ), deletion is only supported at file level
f.truncateTail(1)
@@ -707,6 +717,8 @@ func TestTruncateTail(t *testing.T) {
5: getChunk(20, 0xaa),
6: getChunk(20, 0x11),
})
+ expected = 20*7 + 48 - 20
+ assertTableSize(t, f, expected)
// Reopen the table, the deletion information should be persisted as well
f.Close()
@@ -739,6 +751,8 @@ func TestTruncateTail(t *testing.T) {
5: getChunk(20, 0xaa),
6: getChunk(20, 0x11),
})
+ expected = 20*5 + 36 - 0
+ assertTableSize(t, f, expected)
// Reopen the table, the above testing should still pass
f.Close()
@@ -760,6 +774,23 @@ func TestTruncateTail(t *testing.T) {
6: getChunk(20, 0x11),
})
+ // truncate 3 more elements( item 2, 3, 4), the file 1 should be deleted
+ // file 2 should only contain item 5
+ f.truncateTail(5)
+ checkRetrieveError(t, f, map[uint64]error{
+ 0: errOutOfBounds,
+ 1: errOutOfBounds,
+ 2: errOutOfBounds,
+ 3: errOutOfBounds,
+ 4: errOutOfBounds,
+ })
+ checkRetrieve(t, f, map[uint64][]byte{
+ 5: getChunk(20, 0xaa),
+ 6: getChunk(20, 0x11),
+ })
+ expected = 20*3 + 24 - 20
+ assertTableSize(t, f, expected)
+
// truncate all, the entire freezer should be deleted
f.truncateTail(7)
checkRetrieveError(t, f, map[uint64]error{
@@ -771,6 +802,8 @@ func TestTruncateTail(t *testing.T) {
5: errOutOfBounds,
6: errOutOfBounds,
})
+ expected = 12
+ assertTableSize(t, f, expected)
}
func TestTruncateHead(t *testing.T) {
diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go
index 8e82459e8..be0372355 100644
--- a/core/rawdb/schema.go
+++ b/core/rawdb/schema.go
@@ -132,6 +132,10 @@ var (
CliqueSnapshotPrefix = []byte("clique-")
+ BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash)
+ FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
+ SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
+
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
)
diff --git a/core/state/database.go b/core/state/database.go
index 9467c8f72..b55f870d9 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -20,6 +20,7 @@ import (
"errors"
"fmt"
+ "github.com/crate-crypto/go-ipa/banderwagon"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb"
@@ -28,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
+ "github.com/ethereum/go-ethereum/trie/utils"
)
const (
@@ -36,6 +38,12 @@ const (
// Cache size granted for caching clean code.
codeCacheSize = 64 * 1024 * 1024
+
+ // commitmentSize is the size of commitment stored in cache.
+ commitmentSize = banderwagon.UncompressedSize
+
+ // Cache item granted for caching commitment results.
+ commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength)
)
// Database wraps access to tries and contract code.
@@ -44,7 +52,7 @@ type Database interface {
OpenTrie(root common.Hash) (Trie, error)
// OpenStorageTrie opens the storage trie of an account.
- OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error)
+ OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error)
// CopyTrie returns an independent copy of the given trie.
CopyTrie(Trie) Trie
@@ -70,11 +78,6 @@ type Trie interface {
// TODO(fjl): remove this when StateTrie is removed
GetKey([]byte) []byte
- // GetStorage returns the value for key stored in the trie. The value bytes
- // must not be modified by the caller. If a node was not found in the database,
- // a trie.MissingNodeError is returned.
- GetStorage(addr common.Address, key []byte) ([]byte, error)
-
// GetAccount abstracts an account read from the trie. It retrieves the
// account blob from the trie with provided account address and decodes it
// with associated decoding algorithm. If the specified account is not in
@@ -83,27 +86,32 @@ type Trie interface {
// be returned.
GetAccount(address common.Address) (*types.StateAccount, error)
- // UpdateStorage associates key with value in the trie. If value has length zero,
- // any existing value is deleted from the trie. The value bytes must not be modified
- // by the caller while they are stored in the trie. If a node was not found in the
- // database, a trie.MissingNodeError is returned.
- UpdateStorage(addr common.Address, key, value []byte) error
+ // GetStorage returns the value for key stored in the trie. The value bytes
+ // must not be modified by the caller. If a node was not found in the database,
+ // a trie.MissingNodeError is returned.
+ GetStorage(addr common.Address, key []byte) ([]byte, error)
// UpdateAccount abstracts an account write to the trie. It encodes the
// provided account object with associated algorithm and then updates it
// in the trie with provided address.
UpdateAccount(address common.Address, account *types.StateAccount) error
- // UpdateContractCode abstracts code write to the trie. It is expected
- // to be moved to the stateWriter interface when the latter is ready.
- UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error
+ // UpdateStorage associates key with value in the trie. If value has length zero,
+ // any existing value is deleted from the trie. The value bytes must not be modified
+ // by the caller while they are stored in the trie. If a node was not found in the
+ // database, a trie.MissingNodeError is returned.
+ UpdateStorage(addr common.Address, key, value []byte) error
+
+ // DeleteAccount abstracts an account deletion from the trie.
+ DeleteAccount(address common.Address) error
// DeleteStorage removes any existing value for key from the trie. If a node
// was not found in the database, a trie.MissingNodeError is returned.
DeleteStorage(addr common.Address, key []byte) error
- // DeleteAccount abstracts an account deletion from the trie.
- DeleteAccount(address common.Address) error
+ // UpdateContractCode abstracts code write to the trie. It is expected
+ // to be moved to the stateWriter interface when the latter is ready.
+ UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error
// Hash returns the root hash of the trie. It does not write to the database and
// can be used even if the trie doesn't have one.
@@ -170,6 +178,9 @@ type cachingDB struct {
// OpenTrie opens the main account trie at a specific root hash.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
+ if db.triedb.IsVerkle() {
+ return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems))
+ }
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
return nil, err
@@ -178,7 +189,13 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
}
// OpenStorageTrie opens the storage trie of an account.
-func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error) {
+func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
+ // In the verkle case, there is only one tree. But the two-tree structure
+ // is hardcoded in the codebase. So we need to return the same trie in this
+ // case.
+ if db.triedb.IsVerkle() {
+ return self, nil
+ }
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb)
if err != nil {
return nil, err
diff --git a/core/state/dump.go b/core/state/dump.go
index 9ce6cd394..55abb50f1 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -49,21 +49,24 @@ type DumpCollector interface {
// DumpAccount represents an account in the state.
type DumpAccount struct {
- Balance string `json:"balance"`
- Nonce uint64 `json:"nonce"`
- Root hexutil.Bytes `json:"root"`
- CodeHash hexutil.Bytes `json:"codeHash"`
- Code hexutil.Bytes `json:"code,omitempty"`
- Storage map[common.Hash]string `json:"storage,omitempty"`
- Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
- SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key
+ Balance string `json:"balance"`
+ Nonce uint64 `json:"nonce"`
+ Root hexutil.Bytes `json:"root"`
+ CodeHash hexutil.Bytes `json:"codeHash"`
+ Code hexutil.Bytes `json:"code,omitempty"`
+ Storage map[common.Hash]string `json:"storage,omitempty"`
+ Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
+ AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key
}
// Dump represents the full dump in a collected format, as one large map.
type Dump struct {
- Root string `json:"root"`
- Accounts map[common.Address]DumpAccount `json:"accounts"`
+ Root string `json:"root"`
+ Accounts map[string]DumpAccount `json:"accounts"`
+ // Next can be set to represent that this dump is only partial, and Next
+ // is where an iterator should be positioned in order to continue the dump.
+ Next []byte `json:"next,omitempty"` // nil if no more accounts
}
// OnRoot implements DumpCollector interface
@@ -73,27 +76,11 @@ func (d *Dump) OnRoot(root common.Hash) {
// OnAccount implements DumpCollector interface
func (d *Dump) OnAccount(addr *common.Address, account DumpAccount) {
- if addr != nil {
- d.Accounts[*addr] = account
+ if addr == nil {
+ d.Accounts[fmt.Sprintf("pre(%s)", account.AddressHash)] = account
}
-}
-
-// IteratorDump is an implementation for iterating over data.
-type IteratorDump struct {
- Root string `json:"root"`
- Accounts map[common.Address]DumpAccount `json:"accounts"`
- Next []byte `json:"next,omitempty"` // nil if no more accounts
-}
-
-// OnRoot implements DumpCollector interface
-func (d *IteratorDump) OnRoot(root common.Hash) {
- d.Root = fmt.Sprintf("%x", root)
-}
-
-// OnAccount implements DumpCollector interface
-func (d *IteratorDump) OnAccount(addr *common.Address, account DumpAccount) {
if addr != nil {
- d.Accounts[*addr] = account
+ d.Accounts[(*addr).String()] = account
}
}
@@ -105,14 +92,14 @@ type iterativeDump struct {
// OnAccount implements DumpCollector interface
func (d iterativeDump) OnAccount(addr *common.Address, account DumpAccount) {
dumpAccount := &DumpAccount{
- Balance: account.Balance,
- Nonce: account.Nonce,
- Root: account.Root,
- CodeHash: account.CodeHash,
- Code: account.Code,
- Storage: account.Storage,
- SecureKey: account.SecureKey,
- Address: addr,
+ Balance: account.Balance,
+ Nonce: account.Nonce,
+ Root: account.Root,
+ CodeHash: account.CodeHash,
+ Code: account.Code,
+ Storage: account.Storage,
+ AddressHash: account.AddressHash,
+ Address: addr,
}
d.Encode(dumpAccount)
}
@@ -142,6 +129,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
trieIt, err := s.trie.NodeIterator(conf.Start)
if err != nil {
+ log.Error("Trie dumping error", "err", err)
return nil
}
it := trie.NewIterator(trieIt)
@@ -150,26 +138,27 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
panic(err)
}
- account := DumpAccount{
- Balance: data.Balance.String(),
- Nonce: data.Nonce,
- Root: data.Root[:],
- CodeHash: data.CodeHash,
- SecureKey: it.Key,
- }
var (
- addrBytes = s.trie.GetKey(it.Key)
- addr = common.BytesToAddress(addrBytes)
+ account = DumpAccount{
+ Balance: data.Balance.String(),
+ Nonce: data.Nonce,
+ Root: data.Root[:],
+ CodeHash: data.CodeHash,
+ AddressHash: it.Key,
+ }
address *common.Address
+ addr common.Address
+ addrBytes = s.trie.GetKey(it.Key)
)
if addrBytes == nil {
- // Preimage missing
missingPreimages++
if conf.OnlyWithAddresses {
continue
}
} else {
+ addr = common.BytesToAddress(addrBytes)
address = &addr
+ account.Address = address
}
obj := newObject(s, addr, &data)
if !conf.SkipCode {
@@ -220,12 +209,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
return nextKey
}
-// RawDump returns the entire state an a single large object
+// RawDump returns the state. If the processing is aborted e.g. due to options
+// reaching Max, the `Next` key is set on the returned Dump.
func (s *StateDB) RawDump(opts *DumpConfig) Dump {
dump := &Dump{
- Accounts: make(map[common.Address]DumpAccount),
+ Accounts: make(map[string]DumpAccount),
}
- s.DumpToCollector(dump, opts)
+ dump.Next = s.DumpToCollector(dump, opts)
return *dump
}
@@ -234,7 +224,7 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte {
dump := s.RawDump(opts)
json, err := json.MarshalIndent(dump, "", " ")
if err != nil {
- fmt.Println("Dump err", err)
+ log.Error("Error dumping state", "err", err)
}
return json
}
@@ -243,12 +233,3 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte {
func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) {
s.DumpToCollector(iterativeDump{output}, opts)
}
-
-// IteratorDump dumps out a batch of accounts starts with the given start key
-func (s *StateDB) IteratorDump(opts *DumpConfig) IteratorDump {
- iterator := &IteratorDump{
- Accounts: make(map[common.Address]DumpAccount),
- }
- iterator.Next = s.DumpToCollector(iterator, opts)
- return *iterator
-}
diff --git a/core/state/iterator.go b/core/state/iterator.go
index 683efd73d..dc84ce689 100644
--- a/core/state/iterator.go
+++ b/core/state/iterator.go
@@ -123,7 +123,7 @@ func (it *nodeIterator) step() error {
address := common.BytesToAddress(preimage)
// Traverse the storage slots belong to the account
- dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root)
+ dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.state.trie)
if err != nil {
return err
}
diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go
index adeaa1daa..f455a6db3 100644
--- a/core/state/snapshot/generate.go
+++ b/core/state/snapshot/generate.go
@@ -446,7 +446,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi
// Trie errors should never happen. Still, in case of a bug, expose the
// error here, as the outer code will presume errors are interrupts, not
// some deeper issues.
- log.Error("State snapshotter failed to iterate trie", "err", err)
+ log.Error("State snapshotter failed to iterate trie", "err", iter.Err)
return false, nil, iter.Err
}
// Delete all stale snapshot states remaining
diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go
index 07016b675..c25f3e7e8 100644
--- a/core/state/snapshot/generate_test.go
+++ b/core/state/snapshot/generate_test.go
@@ -601,7 +601,7 @@ func testGenerateWithExtraAccounts(t *testing.T, scheme string) {
}
func enableLogging() {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
}
// Tests that snapshot generation when an extra account with storage exists in the snap state.
diff --git a/core/state/state_object.go b/core/state/state_object.go
index d42d2c34d..9383b98e4 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -98,7 +98,10 @@ func (s *stateObject) empty() bool {
// newObject creates a state object.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
- origin := acct
+ var (
+ origin = acct
+ created = acct == nil // true if the account was not existent
+ )
if acct == nil {
acct = types.NewEmptyStateAccount()
}
@@ -111,6 +114,7 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
originStorage: make(Storage),
pendingStorage: make(Storage),
dirtyStorage: make(Storage),
+ created: created,
}
}
@@ -145,7 +149,7 @@ func (s *stateObject) getTrie() (Trie, error) {
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
}
if s.trie == nil {
- tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root)
+ tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
if err != nil {
return nil, err
}
diff --git a/core/state/state_test.go b/core/state/state_test.go
index 2553133de..2f45ba44b 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -71,6 +71,7 @@ func TestDump(t *testing.T) {
"nonce": 0,
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "address": "0x0000000000000000000000000000000000000001",
"key": "0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d"
},
"0x0000000000000000000000000000000000000002": {
@@ -78,6 +79,7 @@ func TestDump(t *testing.T) {
"nonce": 0,
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
+ "address": "0x0000000000000000000000000000000000000002",
"key": "0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62"
},
"0x0000000000000000000000000000000000000102": {
@@ -86,6 +88,7 @@ func TestDump(t *testing.T) {
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3",
"code": "0x03030303030303",
+ "address": "0x0000000000000000000000000000000000000102",
"key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"
}
}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 1399dec9d..4873e8e5b 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -340,10 +340,10 @@ func (s *StateDB) GetCodeSize(addr common.Address) int {
func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
stateObject := s.getStateObject(addr)
- if stateObject == nil {
- return common.Hash{}
+ if stateObject != nil {
+ return common.BytesToHash(stateObject.CodeHash())
}
- return common.BytesToHash(stateObject.CodeHash())
+ return common.Hash{}
}
// GetState retrieves a value from the given account's storage trie.
@@ -667,9 +667,6 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
delete(s.accountsOrigin, prev.address)
delete(s.storagesOrigin, prev.address)
}
-
- newobj.created = true
-
s.setStateObject(newobj)
if prev != nil && !prev.deleted {
return newobj, prev
@@ -1012,7 +1009,7 @@ func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (boo
// employed when the associated state snapshot is not available. It iterates the
// 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) {
- tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root)
+ tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie)
if err != nil {
return false, 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index ad829a0c8..df1cd5547 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -426,10 +426,12 @@ func (test *snapshotTest) run() bool {
state, _ = New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
snapshotRevs = make([]int, len(test.snapshots))
sindex = 0
+ checkstates = make([]*StateDB, len(test.snapshots))
)
for i, action := range test.actions {
if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
snapshotRevs[sindex] = state.Snapshot()
+ checkstates[sindex] = state.Copy()
sindex++
}
action.fn(action, state)
@@ -437,12 +439,8 @@ func (test *snapshotTest) run() bool {
// Revert all snapshots in reverse order. Each revert must yield a state
// that is equivalent to fresh state with all actions up the snapshot applied.
for sindex--; sindex >= 0; sindex-- {
- checkstate, _ := New(types.EmptyRootHash, state.Database(), nil)
- for _, action := range test.actions[:test.snapshots[sindex]] {
- action.fn(action, checkstate)
- }
state.RevertToSnapshot(snapshotRevs[sindex])
- if err := test.checkEqual(state, checkstate); err != nil {
+ if err := test.checkEqual(state, checkstates[sindex]); err != nil {
test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err)
return false
}
diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go
index 772c698dd..c2a49417d 100644
--- a/core/state/trie_prefetcher.go
+++ b/core/state/trie_prefetcher.go
@@ -305,7 +305,9 @@ func (sf *subfetcher) loop() {
}
sf.trie = trie
} else {
- trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root)
+ // The trie argument can be nil as verkle doesn't support prefetching
+ // yet. TODO FIX IT(rjl493456442), otherwise code will panic here.
+ trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
return
diff --git a/core/state_transition.go b/core/state_transition.go
index fd0bc718b..69df23203 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -32,9 +32,10 @@ import (
// ExecutionResult includes all output after executing given evm
// message no matter the execution itself is successful or not.
type ExecutionResult struct {
- UsedGas uint64 // Total used gas but include the refunded gas
- Err error // Any error encountered during the execution(listed in core/vm/errors.go)
- ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
+ UsedGas uint64 // Total used gas, not including the refunded gas
+ RefundedGas uint64 // Total gas refunded after execution
+ Err error // Any error encountered during the execution(listed in core/vm/errors.go)
+ ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
}
// Unwrap returns the internal evm error which allows us for further
@@ -429,12 +430,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
}
+ var gasRefund uint64
if !rules.IsLondon {
// Before EIP-3529: refunds were capped to gasUsed / 2
- st.refundGas(params.RefundQuotient)
+ gasRefund = st.refundGas(params.RefundQuotient)
} else {
// After EIP-3529: refunds are capped to gasUsed / 5
- st.refundGas(params.RefundQuotientEIP3529)
+ gasRefund = st.refundGas(params.RefundQuotientEIP3529)
}
effectiveTip := msg.GasPrice
if rules.IsLondon {
@@ -456,13 +458,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
return &ExecutionResult{
- UsedGas: st.gasUsed(),
- Err: vmerr,
- ReturnData: ret,
+ UsedGas: st.gasUsed(),
+ RefundedGas: gasRefund,
+ Err: vmerr,
+ ReturnData: ret,
}, nil
}
-func (st *StateTransition) refundGas(refundQuotient uint64) {
+func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
// Apply refund counter, capped to a refund quotient
refund := st.gasUsed() / refundQuotient
if refund > st.state.GetRefund() {
@@ -477,6 +480,8 @@ func (st *StateTransition) refundGas(refundQuotient uint64) {
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gasRemaining)
+
+ return refund
}
// gasUsed returns the amount of gas used up by the state transition.
diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go
index 32c6c0e8f..195697a8f 100644
--- a/core/txpool/blobpool/blobpool.go
+++ b/core/txpool/blobpool/blobpool.go
@@ -738,7 +738,7 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi
}
// Reset implements txpool.SubPool, allowing the blob pool's internal state to be
-// kept in sync with the main transacion pool's internal state.
+// kept in sync with the main transaction pool's internal state.
func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
waitStart := time.Now()
p.lock.Lock()
@@ -972,7 +972,7 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
}
// SetGasTip implements txpool.SubPool, allowing the blob pool's gas requirements
-// to be kept in sync with the main transacion pool's gas requirements.
+// to be kept in sync with the main transaction pool's gas requirements.
func (p *BlobPool) SetGasTip(tip *big.Int) {
p.lock.Lock()
defer p.lock.Unlock()
diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go
index 8914301e1..b709ad0e5 100644
--- a/core/txpool/blobpool/blobpool_test.go
+++ b/core/txpool/blobpool/blobpool_test.go
@@ -319,7 +319,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) {
// - 3. All transactions after a nonce gap must be dropped
// - 4. All transactions after an underpriced one (including it) must be dropped
func TestOpenDrops(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
@@ -594,13 +594,13 @@ func TestOpenDrops(t *testing.T) {
verifyPoolInternals(t, pool)
}
-// Tests that transactions loaded from disk are indexed corrently.
+// Tests that transactions loaded from disk are indexed correctly.
//
// - 1. Transactions must be groupped by sender, sorted by nonce
// - 2. Eviction thresholds are calculated correctly for the sequences
// - 3. Balance usage of an account is totals across all transactions
func TestOpenIndex(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
@@ -689,7 +689,7 @@ func TestOpenIndex(t *testing.T) {
// Tests that after indexing all the loaded transactions from disk, a price heap
// is correctly constructed based on the head basefee and blobfee.
func TestOpenHeap(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
@@ -776,7 +776,7 @@ func TestOpenHeap(t *testing.T) {
// Tests that after the pool's previous state is loaded back, any transactions
// over the new storage cap will get dropped.
func TestOpenCap(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
@@ -868,7 +868,7 @@ func TestOpenCap(t *testing.T) {
// specific to the blob pool. It does not do an exhaustive transaction validity
// check.
func TestAdd(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// seed is a helper tumpe to seed an initial state db and pool
type seed struct {
diff --git a/core/txpool/blobpool/metrics.go b/core/txpool/blobpool/metrics.go
index 070cc5ca4..587804cc6 100644
--- a/core/txpool/blobpool/metrics.go
+++ b/core/txpool/blobpool/metrics.go
@@ -65,7 +65,7 @@ var (
pooltipGauge = metrics.NewRegisteredGauge("blobpool/pooltip", nil)
// addwait/time, resetwait/time and getwait/time track the rough health of
- // the pool and wether or not it's capable of keeping up with the load from
+ // the pool and whether or not it's capable of keeping up with the load from
// the network.
addwaitHist = metrics.NewRegisteredHistogram("blobpool/addwait", nil, metrics.NewExpDecaySample(1028, 0.015))
addtimeHist = metrics.NewRegisteredHistogram("blobpool/addtime", nil, metrics.NewExpDecaySample(1028, 0.015))
diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
index 0e3392327..959e328b9 100644
--- a/core/txpool/legacypool/legacypool.go
+++ b/core/txpool/legacypool/legacypool.go
@@ -923,8 +923,7 @@ func (pool *LegacyPool) addLocals(txs []*types.Transaction) []error {
// addLocal enqueues a single local transaction into the pool if it is valid. This is
// a convenience wrapper around addLocals.
func (pool *LegacyPool) addLocal(tx *types.Transaction) error {
- errs := pool.addLocals([]*types.Transaction{tx})
- return errs[0]
+ return pool.addLocals([]*types.Transaction{tx})[0]
}
// addRemotes enqueues a batch of transactions into the pool if they are valid. If the
@@ -939,8 +938,7 @@ func (pool *LegacyPool) addRemotes(txs []*types.Transaction) []error {
// addRemote enqueues a single transaction into the pool if it is valid. This is a convenience
// wrapper around addRemotes.
func (pool *LegacyPool) addRemote(tx *types.Transaction) error {
- errs := pool.addRemotes([]*types.Transaction{tx})
- return errs[0]
+ return pool.addRemotes([]*types.Transaction{tx})[0]
}
// addRemotesSync is like addRemotes, but waits for pool reorganization. Tests use this method.
@@ -959,6 +957,9 @@ func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error {
// If sync is set, the method will block until all internal maintenance related
// to the add is finished. Only use this during tests for determinism!
func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error {
+ // Do not treat as local if local transactions have been disabled
+ local = local && !pool.config.NoLocals
+
// Filter out known ones without obtaining the pool lock or recovering signatures
var (
errs = make([]error, len(txs))
@@ -976,6 +977,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error
// in transactions before obtaining lock
if err := pool.validateTxBasics(tx, local); err != nil {
errs[i] = err
+ log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
invalidTxMeter.Mark(1)
continue
}
diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go
index a8f3dd7d8..0366a58d6 100644
--- a/core/txpool/legacypool/legacypool_test.go
+++ b/core/txpool/legacypool/legacypool_test.go
@@ -1492,6 +1492,50 @@ func TestRepricing(t *testing.T) {
}
}
+func TestMinGasPriceEnforced(t *testing.T) {
+ t.Parallel()
+
+ // Create the pool to test the pricing enforcement with
+ statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
+ blockchain := newTestBlockChain(eip1559Config, 10000000, statedb, new(event.Feed))
+
+ txPoolConfig := DefaultConfig
+ txPoolConfig.NoLocals = true
+ pool := New(txPoolConfig, blockchain)
+ pool.Init(new(big.Int).SetUint64(txPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ defer pool.Close()
+
+ key, _ := crypto.GenerateKey()
+ testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000))
+
+ tx := pricedTransaction(0, 100000, big.NewInt(2), key)
+ pool.SetGasTip(big.NewInt(tx.GasPrice().Int64() + 1))
+
+ if err := pool.addLocal(tx); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("Min tip not enforced")
+ }
+
+ if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("Min tip not enforced")
+ }
+
+ tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key)
+ pool.SetGasTip(big.NewInt(tx.GasTipCap().Int64() + 1))
+
+ if err := pool.addLocal(tx); !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("Min tip not enforced")
+ }
+
+ if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
+ t.Fatalf("Min tip not enforced")
+ }
+ // Make sure the tx is accepted if locals are enabled
+ pool.config.NoLocals = false
+ if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; err != nil {
+ t.Fatalf("Min tip enforced with locals enabled, error: %v", err)
+ }
+}
+
// Tests that setting the transaction pool gas price to a higher value correctly
// discards everything cheaper (legacy & dynamic fee) than that and moves any
// gapped transactions back from the pending pool to the queue.
diff --git a/core/types/hashes.go b/core/types/hashes.go
index 3a787aa13..43e9130fd 100644
--- a/core/types/hashes.go
+++ b/core/types/hashes.go
@@ -23,7 +23,7 @@ import (
)
var (
- // EmptyRootHash is the known root hash of an empty trie.
+ // EmptyRootHash is the known root hash of an empty merkle trie.
EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
// EmptyUncleHash is the known hash of the empty uncle set.
@@ -40,6 +40,9 @@ var (
// EmptyWithdrawalsHash is the known hash of the empty withdrawal set.
EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
+
+ // EmptyVerkleHash is the known hash of an empty verkle trie.
+ EmptyVerkleHash = common.Hash{}
)
// TrieRootHash returns the hash itself if it's non-empty or the predefined
diff --git a/core/types/rlp_fuzzer_test.go b/core/types/rlp_fuzzer_test.go
new file mode 100644
index 000000000..a3b9f7243
--- /dev/null
+++ b/core/types/rlp_fuzzer_test.go
@@ -0,0 +1,147 @@
+// Copyright 2019 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 .
+
+package types
+
+import (
+ "bytes"
+ "fmt"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/holiman/uint256"
+)
+
+func decodeEncode(input []byte, val interface{}) error {
+ if err := rlp.DecodeBytes(input, val); err != nil {
+ // not valid rlp, nothing to do
+ return nil
+ }
+ // If it _were_ valid rlp, we can encode it again
+ output, err := rlp.EncodeToBytes(val)
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(input, output) {
+ return fmt.Errorf("encode-decode is not equal, \ninput : %x\noutput: %x", input, output)
+ }
+ return nil
+}
+
+func FuzzRLP(f *testing.F) {
+ f.Fuzz(fuzzRlp)
+}
+
+func fuzzRlp(t *testing.T, input []byte) {
+ if len(input) == 0 || len(input) > 500*1024 {
+ return
+ }
+ rlp.Split(input)
+ if elems, _, err := rlp.SplitList(input); err == nil {
+ rlp.CountValues(elems)
+ }
+ rlp.NewStream(bytes.NewReader(input), 0).Decode(new(interface{}))
+ if err := decodeEncode(input, new(interface{})); err != nil {
+ t.Fatal(err)
+ }
+ {
+ var v struct {
+ Int uint
+ String string
+ Bytes []byte
+ }
+ if err := decodeEncode(input, &v); err != nil {
+ t.Fatal(err)
+ }
+ }
+ {
+ type Types struct {
+ Bool bool
+ Raw rlp.RawValue
+ Slice []*Types
+ Iface []interface{}
+ }
+ var v Types
+ if err := decodeEncode(input, &v); err != nil {
+ t.Fatal(err)
+ }
+ }
+ {
+ type AllTypes struct {
+ Int uint
+ String string
+ Bytes []byte
+ Bool bool
+ Raw rlp.RawValue
+ Slice []*AllTypes
+ Array [3]*AllTypes
+ Iface []interface{}
+ }
+ var v AllTypes
+ if err := decodeEncode(input, &v); err != nil {
+ t.Fatal(err)
+ }
+ }
+ {
+ if err := decodeEncode(input, [10]byte{}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ {
+ var v struct {
+ Byte [10]byte
+ Rool [10]bool
+ }
+ if err := decodeEncode(input, &v); err != nil {
+ t.Fatal(err)
+ }
+ }
+ {
+ var h Header
+ if err := decodeEncode(input, &h); err != nil {
+ t.Fatal(err)
+ }
+ var b Block
+ if err := decodeEncode(input, &b); err != nil {
+ t.Fatal(err)
+ }
+ var tx Transaction
+ if err := decodeEncode(input, &tx); err != nil {
+ t.Fatal(err)
+ }
+ var txs Transactions
+ if err := decodeEncode(input, &txs); err != nil {
+ t.Fatal(err)
+ }
+ var rs Receipts
+ if err := decodeEncode(input, &rs); err != nil {
+ t.Fatal(err)
+ }
+ }
+ {
+ var v struct {
+ AnIntPtr *big.Int
+ AnInt big.Int
+ AnU256Ptr *uint256.Int
+ AnU256 uint256.Int
+ NotAnU256 [4]uint64
+ }
+ if err := decodeEncode(input, &v); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 6f83c21d8..9ec0199a0 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -37,6 +37,9 @@ var (
ErrTxTypeNotSupported = errors.New("transaction type not supported")
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
errShortTypedTx = errors.New("typed transaction too short")
+ errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
+ errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
+ errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction")
)
// Transaction types.
diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go
index e5d71a85d..08ce80b07 100644
--- a/core/types/transaction_marshalling.go
+++ b/core/types/transaction_marshalling.go
@@ -57,18 +57,18 @@ func (tx *txJSON) yParityValue() (*big.Int, error) {
if tx.YParity != nil {
val := uint64(*tx.YParity)
if val != 0 && val != 1 {
- return nil, errors.New("'yParity' field must be 0 or 1")
+ return nil, errInvalidYParity
}
bigval := new(big.Int).SetUint64(val)
if tx.V != nil && tx.V.ToInt().Cmp(bigval) != 0 {
- return nil, errors.New("'v' and 'yParity' fields do not match")
+ return nil, errVYParityMismatch
}
return bigval, nil
}
if tx.V != nil {
return tx.V.ToInt(), nil
}
- return nil, errors.New("missing 'yParity' or 'v' field in transaction")
+ return nil, errVYParityMissing
}
// MarshalJSON marshals as JSON with a hash.
@@ -294,9 +294,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'input' in transaction")
}
itx.Data = *dec.Input
- if dec.V == nil {
- return errors.New("missing required field 'v' in transaction")
- }
if dec.AccessList != nil {
itx.AccessList = *dec.AccessList
}
@@ -361,9 +358,6 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'input' in transaction")
}
itx.Data = *dec.Input
- if dec.V == nil {
- return errors.New("missing required field 'v' in transaction")
- }
if dec.AccessList != nil {
itx.AccessList = *dec.AccessList
}
diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go
index 25ced0841..76a010d2e 100644
--- a/core/types/transaction_test.go
+++ b/core/types/transaction_test.go
@@ -451,3 +451,97 @@ func TestTransactionSizes(t *testing.T) {
}
}
}
+
+func TestYParityJSONUnmarshalling(t *testing.T) {
+ baseJson := map[string]interface{}{
+ // type is filled in by the test
+ "chainId": "0x7",
+ "nonce": "0x0",
+ "to": "0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425",
+ "gas": "0x124f8",
+ "gasPrice": "0x693d4ca8",
+ "maxPriorityFeePerGas": "0x3b9aca00",
+ "maxFeePerGas": "0x6fc23ac00",
+ "maxFeePerBlobGas": "0x3b9aca00",
+ "value": "0x0",
+ "input": "0x",
+ "accessList": []interface{}{},
+ "blobVersionedHashes": []string{
+ "0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014",
+ },
+
+ // v and yParity are filled in by the test
+ "r": "0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1",
+ "s": "0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14",
+ }
+
+ tests := []struct {
+ name string
+ v string
+ yParity string
+ wantErr error
+ }{
+ // Valid v and yParity
+ {"valid v and yParity, 0x0", "0x0", "0x0", nil},
+ {"valid v and yParity, 0x1", "0x1", "0x1", nil},
+
+ // Valid v, missing yParity
+ {"valid v, missing yParity, 0x0", "0x0", "", nil},
+ {"valid v, missing yParity, 0x1", "0x1", "", nil},
+
+ // Valid yParity, missing v
+ {"valid yParity, missing v, 0x0", "", "0x0", nil},
+ {"valid yParity, missing v, 0x1", "", "0x1", nil},
+
+ // Invalid yParity
+ {"invalid yParity, 0x2", "", "0x2", errInvalidYParity},
+
+ // Conflicting v and yParity
+ {"conflicting v and yParity", "0x1", "0x0", errVYParityMismatch},
+
+ // Missing v and yParity
+ {"missing v and yParity", "", "", errVYParityMissing},
+ }
+
+ // Run for all types that accept yParity
+ t.Parallel()
+ for _, txType := range []uint64{
+ AccessListTxType,
+ DynamicFeeTxType,
+ BlobTxType,
+ } {
+ txType := txType
+ for _, test := range tests {
+ test := test
+ t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) {
+ // Copy the base json
+ testJson := make(map[string]interface{})
+ for k, v := range baseJson {
+ testJson[k] = v
+ }
+
+ // Set v, yParity and type
+ if test.v != "" {
+ testJson["v"] = test.v
+ }
+ if test.yParity != "" {
+ testJson["yParity"] = test.yParity
+ }
+ testJson["type"] = fmt.Sprintf("0x%x", txType)
+
+ // Marshal the JSON
+ jsonBytes, err := json.Marshal(testJson)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Unmarshal the tx
+ var tx Transaction
+ err = tx.UnmarshalJSON(jsonBytes)
+ if err != test.wantErr {
+ t.Fatalf("wrong error: got %v, want %v", err, test.wantErr)
+ }
+ })
+ }
+ }
+}
diff --git a/tests/fuzzers/rlp/rlp_test.go b/core/vm/contracts_fuzz_test.go
similarity index 57%
rename from tests/fuzzers/rlp/rlp_test.go
rename to core/vm/contracts_fuzz_test.go
index 377b3961b..87c1fff7c 100644
--- a/tests/fuzzers/rlp/rlp_test.go
+++ b/core/vm/contracts_fuzz_test.go
@@ -14,12 +14,31 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package rlp
+package vm
-import "testing"
+import (
+ "testing"
-func Fuzz(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzz(data)
+ "github.com/ethereum/go-ethereum/common"
+)
+
+func FuzzPrecompiledContracts(f *testing.F) {
+ // Create list of addresses
+ var addrs []common.Address
+ for k := range allPrecompiles {
+ addrs = append(addrs, k)
+ }
+ f.Fuzz(func(t *testing.T, addr uint8, input []byte) {
+ a := addrs[int(addr)%len(addrs)]
+ p := allPrecompiles[a]
+ gas := p.RequiredGas(input)
+ if gas > 10_000_000 {
+ return
+ }
+ inWant := string(input)
+ RunPrecompiledContract(p, input, gas)
+ if inHave := string(input); inWant != inHave {
+ t.Errorf("Precompiled %v modified input data", a)
+ }
})
}
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index c7a3a163b..2b9231fe1 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -25,7 +25,7 @@ type OpCode byte
// IsPush specifies if an opcode is a PUSH opcode.
func (op OpCode) IsPush() bool {
- return PUSH1 <= op && op <= PUSH32
+ return PUSH0 <= op && op <= PUSH32
}
// 0x0 range - arithmetic ops.
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index 04c6409eb..bca6d1e83 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -197,7 +197,7 @@ var (
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall)
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode)
gasSelfdestructEIP2929 = makeSelfdestructGasFn(true)
- // gasSelfdestructEIP3529 implements the changes in EIP-2539 (no refunds)
+ // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds)
gasSelfdestructEIP3529 = makeSelfdestructGasFn(false)
// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929
@@ -214,12 +214,12 @@ var (
// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified
gasSStoreEIP2929 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200)
- // gasSStoreEIP2539 implements gas cost for SSTORE according to EIP-2539
+ // gasSStoreEIP3529 implements gas cost for SSTORE according to EIP-3529
// Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800)
gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529)
)
-// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539
+// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-3529
func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (
diff --git a/tests/fuzzers/runtime/runtime_test.go b/core/vm/runtime/runtime_fuzz_test.go
similarity index 87%
rename from tests/fuzzers/runtime/runtime_test.go
rename to core/vm/runtime/runtime_fuzz_test.go
index 2d73a56ca..8a4d31d81 100644
--- a/tests/fuzzers/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_fuzz_test.go
@@ -18,13 +18,11 @@ package runtime
import (
"testing"
-
- "github.com/ethereum/go-ethereum/core/vm/runtime"
)
-func Fuzz(f *testing.F) {
+func FuzzVmRuntime(f *testing.F) {
f.Fuzz(func(t *testing.T, code, input []byte) {
- runtime.Execute(code, input, &runtime.Config{
+ Execute(code, input, &Config{
GasLimit: 12000000,
})
})
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index 796d3b443..e71760bb2 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -671,7 +671,7 @@ func TestColdAccountAccessCost(t *testing.T) {
for ii, op := range tracer.StructLogs() {
t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
}
- t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
+ t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
}
}
}
diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go
index ef2a3a379..74408d06d 100644
--- a/crypto/secp256k1/secp256_test.go
+++ b/crypto/secp256k1/secp256_test.go
@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
+//go:build !gofuzz && cgo
+// +build !gofuzz,cgo
+
package secp256k1
import (
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 601e55515..84eb20009 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -249,7 +249,7 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
return nil
}
-func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) {
+func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM {
if vmConfig == nil {
vmConfig = b.eth.blockchain.GetVMConfig()
}
@@ -260,7 +260,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st
} else {
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
}
- return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error
+ return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig)
}
func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
diff --git a/eth/api_debug.go b/eth/api_debug.go
index dc9f56814..05010a396 100644
--- a/eth/api_debug.go
+++ b/eth/api_debug.go
@@ -133,7 +133,7 @@ func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error)
const AccountRangeMaxResults = 256
// AccountRange enumerates all accounts in the given block and start point in paging request
-func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) {
+func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.Dump, error) {
var stateDb *state.StateDB
var err error
@@ -144,7 +144,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
// the miner and operate on those
_, stateDb = api.eth.miner.Pending()
if stateDb == nil {
- return state.IteratorDump{}, errors.New("pending state is not available")
+ return state.Dump{}, errors.New("pending state is not available")
}
} else {
var header *types.Header
@@ -158,29 +158,29 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
default:
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
if block == nil {
- return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
+ return state.Dump{}, fmt.Errorf("block #%d not found", number)
}
header = block.Header()
}
if header == nil {
- return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
+ return state.Dump{}, fmt.Errorf("block #%d not found", number)
}
stateDb, err = api.eth.BlockChain().StateAt(header.Root)
if err != nil {
- return state.IteratorDump{}, err
+ return state.Dump{}, err
}
}
} else if hash, ok := blockNrOrHash.Hash(); ok {
block := api.eth.blockchain.GetBlockByHash(hash)
if block == nil {
- return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex())
+ return state.Dump{}, fmt.Errorf("block %s not found", hash.Hex())
}
stateDb, err = api.eth.BlockChain().StateAt(block.Root())
if err != nil {
- return state.IteratorDump{}, err
+ return state.Dump{}, err
}
} else {
- return state.IteratorDump{}, errors.New("either block number or block hash must be specified")
+ return state.Dump{}, errors.New("either block number or block hash must be specified")
}
opts := &state.DumpConfig{
@@ -193,7 +193,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
if maxResults > AccountRangeMaxResults || maxResults <= 0 {
opts.Max = AccountRangeMaxResults
}
- return stateDb.IteratorDump(opts), nil
+ return stateDb.RawDump(opts), nil
}
// StorageRangeResult is the result of a debug_storageRangeAt API call.
diff --git a/eth/api_debug_test.go b/eth/api_debug_test.go
index 3d3444a87..184b90dd0 100644
--- a/eth/api_debug_test.go
+++ b/eth/api_debug_test.go
@@ -21,6 +21,7 @@ import (
"fmt"
"math/big"
"reflect"
+ "strings"
"testing"
"github.com/davecgh/go-spew/spew"
@@ -35,8 +36,8 @@ import (
var dumper = spew.ConfigState{Indent: " "}
-func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump {
- result := statedb.IteratorDump(&state.DumpConfig{
+func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump {
+ result := statedb.RawDump(&state.DumpConfig{
SkipCode: true,
SkipStorage: true,
OnlyWithAddresses: false,
@@ -47,12 +48,12 @@ func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, st
if len(result.Accounts) != expectedNum {
t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts))
}
- for address := range result.Accounts {
- if address == (common.Address{}) {
- t.Fatalf("empty address returned")
+ for addr, acc := range result.Accounts {
+ if strings.HasSuffix(addr, "pre") || acc.Address == nil {
+ t.Fatalf("account without prestate (address) returned: %v", addr)
}
- if !statedb.Exist(address) {
- t.Fatalf("account not found in state %s", address.Hex())
+ if !statedb.Exist(*acc.Address) {
+ t.Fatalf("account not found in state %s", acc.Address.Hex())
}
}
return result
@@ -92,16 +93,16 @@ func TestAccountRange(t *testing.T) {
secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults)
hList := make([]common.Hash, 0)
- for addr1 := range firstResult.Accounts {
- // If address is empty, then it makes no sense to compare
+ for addr1, acc := range firstResult.Accounts {
+ // If address is non-available, then it makes no sense to compare
// them as they might be two different accounts.
- if addr1 == (common.Address{}) {
+ if acc.Address == nil {
continue
}
if _, duplicate := secondResult.Accounts[addr1]; duplicate {
t.Fatalf("pagination test failed: results should not overlap")
}
- hList = append(hList, crypto.Keccak256Hash(addr1.Bytes()))
+ hList = append(hList, crypto.Keccak256Hash(acc.Address.Bytes()))
}
// Test to see if it's possible to recover from the middle of the previous
// set and get an even split between the first and second sets.
@@ -140,7 +141,7 @@ func TestEmptyAccountRange(t *testing.T) {
st.Commit(0, true)
st, _ = state.New(types.EmptyRootHash, statedb, nil)
- results := st.IteratorDump(&state.DumpConfig{
+ results := st.RawDump(&state.DumpConfig{
SkipCode: true,
SkipStorage: true,
OnlyWithAddresses: true,
diff --git a/eth/backend.go b/eth/backend.go
index 09559f0ac..774ffaf24 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -108,7 +108,7 @@ type Ethereum struct {
func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Ensure configuration values are compatible and sane
if config.SyncMode == downloader.LightSync {
- return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
+ return nil, errors.New("can't run eth.Ethereum in light sync mode, light mode has been deprecated")
}
if !config.SyncMode.IsValid() {
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index d1e199141..37b0248f2 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -611,7 +611,8 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (engine.PayloadS
// Although we don't want to trigger a sync, if there is one already in
// progress, try to extend if with the current payload request to relieve
// some strain from the forkchoice update.
- if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil {
+ err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header())
+ if err == nil {
log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash())
return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
}
@@ -623,12 +624,12 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (engine.PayloadS
// In full sync mode, failure to import a well-formed block can only mean
// that the parent state is missing and the syncer rejected extending the
// current cycle with the new payload.
- log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash())
+ log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash(), "reason", err)
} else {
// In non-full sync mode (i.e. snap sync) all payloads are rejected until
// snap sync terminates as snap sync relies on direct database injections
// and cannot afford concurrent out-if-band modifications via imports.
- log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash())
+ log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash(), "reason", err)
}
return engine.PayloadStatusV1{Status: engine.SYNCING}, nil
}
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
index 59f44fafe..c875c485d 100644
--- a/eth/catalyst/api_test.go
+++ b/eth/catalyst/api_test.go
@@ -1562,7 +1562,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
// This checks that beaconRoot is applied to the state from the engine API.
func TestParentBeaconBlockRoot(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), log.LevelTrace, true)))
genesis, blocks := generateMergeChain(10, true)
diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go
index a9a2bb4a9..3c081074c 100644
--- a/eth/catalyst/simulated_beacon.go
+++ b/eth/catalyst/simulated_beacon.go
@@ -19,16 +19,17 @@ package catalyst
import (
"crypto/rand"
"errors"
+ "math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
@@ -81,11 +82,12 @@ type SimulatedBeacon struct {
lastBlockTime uint64
}
+// NewSimulatedBeacon constructs a new simulated beacon chain.
+// Period sets the period in which blocks should be produced.
+//
+// - If period is set to 0, a block is produced on every transaction.
+// via Commit, Fork and AdjustTime.
func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, error) {
- chainConfig := eth.APIBackend.ChainConfig()
- if !chainConfig.IsDevMode {
- return nil, errors.New("incompatible pre-existing chain configuration")
- }
block := eth.BlockChain().CurrentBlock()
current := engine.ForkchoiceStateV1{
HeadBlockHash: block.Hash(),
@@ -120,7 +122,9 @@ func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) {
// Start invokes the SimulatedBeacon life-cycle function in a goroutine.
func (c *SimulatedBeacon) Start() error {
if c.period == 0 {
- go c.loopOnDemand()
+ // if period is set to 0, do not mine at all
+ // this is used in the simulated backend where blocks
+ // are explicitly mined via Commit, AdjustTime and Fork
} else {
go c.loop()
}
@@ -135,10 +139,9 @@ func (c *SimulatedBeacon) Stop() error {
// sealBlock initiates payload building for a new block and creates a new block
// with the completed payload.
-func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
- tstamp := uint64(time.Now().Unix())
- if tstamp <= c.lastBlockTime {
- tstamp = c.lastBlockTime + 1
+func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp uint64) error {
+ if timestamp <= c.lastBlockTime {
+ timestamp = c.lastBlockTime + 1
}
c.feeRecipientLock.Lock()
feeRecipient := c.feeRecipient
@@ -153,7 +156,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
var random [32]byte
rand.Read(random[:])
fcResponse, err := c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, &engine.PayloadAttributes{
- Timestamp: tstamp,
+ Timestamp: timestamp,
SuggestedFeeRecipient: feeRecipient,
Withdrawals: withdrawals,
Random: random,
@@ -187,6 +190,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
return err
}
c.setCurrentState(payload.BlockHash, finalizedHash)
+
// Mark the block containing the payload as canonical
if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil {
return err
@@ -195,32 +199,6 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
return nil
}
-// loopOnDemand runs the block production loop for "on-demand" configuration (period = 0)
-func (c *SimulatedBeacon) loopOnDemand() {
- var (
- newTxs = make(chan core.NewTxsEvent)
- sub = c.eth.TxPool().SubscribeTransactions(newTxs, true)
- )
- defer sub.Unsubscribe()
-
- for {
- select {
- case <-c.shutdownCh:
- return
- case w := <-c.withdrawals.pending:
- withdrawals := append(c.withdrawals.gatherPending(9), w)
- if err := c.sealBlock(withdrawals); err != nil {
- log.Warn("Error performing sealing work", "err", err)
- }
- case <-newTxs:
- withdrawals := c.withdrawals.gatherPending(10)
- if err := c.sealBlock(withdrawals); err != nil {
- log.Warn("Error performing sealing work", "err", err)
- }
- }
- }
-}
-
// loop runs the block production loop for non-zero period configuration
func (c *SimulatedBeacon) loop() {
timer := time.NewTimer(0)
@@ -230,7 +208,7 @@ func (c *SimulatedBeacon) loop() {
return
case <-timer.C:
withdrawals := c.withdrawals.gatherPending(10)
- if err := c.sealBlock(withdrawals); err != nil {
+ if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
log.Warn("Error performing sealing work", "err", err)
} else {
timer.Reset(time.Second * time.Duration(c.period))
@@ -239,8 +217,8 @@ func (c *SimulatedBeacon) loop() {
}
}
-// finalizedBlockHash returns the block hash of the finalized block corresponding to the given number
-// or nil if doesn't exist in the chain.
+// finalizedBlockHash returns the block hash of the finalized block corresponding
+// to the given number or nil if doesn't exist in the chain.
func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
var finalizedNumber uint64
if number%devEpochLength == 0 {
@@ -248,7 +226,6 @@ func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
} else {
finalizedNumber = (number - 1) / devEpochLength * devEpochLength
}
-
if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil {
fh := finalizedBlock.Hash()
return &fh
@@ -265,11 +242,60 @@ func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
}
}
+// Commit seals a block on demand.
+func (c *SimulatedBeacon) Commit() common.Hash {
+ withdrawals := c.withdrawals.gatherPending(10)
+ if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
+ log.Warn("Error performing sealing work", "err", err)
+ }
+ return c.eth.BlockChain().CurrentBlock().Hash()
+}
+
+// Rollback un-sends previously added transactions.
+func (c *SimulatedBeacon) Rollback() {
+ // Flush all transactions from the transaction pools
+ maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1)
+ c.eth.TxPool().SetGasTip(maxUint256)
+ // Set the gas tip back to accept new transactions
+ // TODO (Marius van der Wijden): set gas tip to parameter passed by config
+ c.eth.TxPool().SetGasTip(big.NewInt(params.GWei))
+}
+
+// Fork sets the head to the provided hash.
+func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
+ if len(c.eth.TxPool().Pending(false)) != 0 {
+ return errors.New("pending block dirty")
+ }
+ parent := c.eth.BlockChain().GetBlockByHash(parentHash)
+ if parent == nil {
+ return errors.New("parent not found")
+ }
+ return c.eth.BlockChain().SetHead(parent.NumberU64())
+}
+
+// AdjustTime creates a new block with an adjusted timestamp.
+func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
+ if len(c.eth.TxPool().Pending(false)) != 0 {
+ return errors.New("could not adjust time on non-empty block")
+ }
+ parent := c.eth.BlockChain().CurrentBlock()
+ if parent == nil {
+ return errors.New("parent not found")
+ }
+ withdrawals := c.withdrawals.gatherPending(10)
+ return c.sealBlock(withdrawals, parent.Time+uint64(adjustment))
+}
+
func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
+ api := &api{sim}
+ if sim.period == 0 {
+ // mine on demand if period is set to 0
+ go api.loop()
+ }
stack.RegisterAPIs([]rpc.API{
{
Namespace: "dev",
- Service: &api{sim},
+ Service: api,
Version: "1.0",
},
})
diff --git a/eth/catalyst/simulated_beacon_api.go b/eth/catalyst/simulated_beacon_api.go
index 93670257f..73d0a5921 100644
--- a/eth/catalyst/simulated_beacon_api.go
+++ b/eth/catalyst/simulated_beacon_api.go
@@ -18,19 +18,44 @@ package catalyst
import (
"context"
+ "time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
)
type api struct {
- simBeacon *SimulatedBeacon
+ sim *SimulatedBeacon
+}
+
+func (a *api) loop() {
+ var (
+ newTxs = make(chan core.NewTxsEvent)
+ sub = a.sim.eth.TxPool().SubscribeTransactions(newTxs, true)
+ )
+ defer sub.Unsubscribe()
+
+ for {
+ select {
+ case <-a.sim.shutdownCh:
+ return
+ case w := <-a.sim.withdrawals.pending:
+ withdrawals := append(a.sim.withdrawals.gatherPending(9), w)
+ if err := a.sim.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
+ log.Warn("Error performing sealing work", "err", err)
+ }
+ case <-newTxs:
+ a.sim.Commit()
+ }
+ }
}
func (a *api) AddWithdrawal(ctx context.Context, withdrawal *types.Withdrawal) error {
- return a.simBeacon.withdrawals.add(withdrawal)
+ return a.sim.withdrawals.add(withdrawal)
}
func (a *api) SetFeeRecipient(ctx context.Context, feeRecipient common.Address) {
- a.simBeacon.setFeeRecipient(feeRecipient)
+ a.sim.setFeeRecipient(feeRecipient)
}
diff --git a/eth/catalyst/simulated_beacon_test.go b/eth/catalyst/simulated_beacon_test.go
index 0df195fb9..6fa97ad87 100644
--- a/eth/catalyst/simulated_beacon_test.go
+++ b/eth/catalyst/simulated_beacon_test.go
@@ -85,7 +85,7 @@ func TestSimulatedBeaconSendWithdrawals(t *testing.T) {
// short period (1 second) for testing purposes
var gasLimit uint64 = 10_000_000
- genesis := core.DeveloperGenesisBlock(gasLimit, testAddr)
+ genesis := core.DeveloperGenesisBlock(gasLimit, &testAddr)
node, ethService, mock := startSimulatedBeaconEthService(t, genesis)
_ = mock
defer node.Close()
diff --git a/eth/downloader/api.go b/eth/downloader/api.go
index b3f7113bc..606c6d4e7 100644
--- a/eth/downloader/api.go
+++ b/eth/downloader/api.go
@@ -101,16 +101,15 @@ func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error
go func() {
statuses := make(chan interface{})
sub := api.SubscribeSyncStatus(statuses)
+ defer sub.Unsubscribe()
for {
select {
case status := <-statuses:
notifier.Notify(rpcSub.ID, status)
case <-rpcSub.Err():
- sub.Unsubscribe()
return
case <-notifier.Closed():
- sub.Unsubscribe()
return
}
}
diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go
index 2ca7e328c..f1cfa92d5 100644
--- a/eth/downloader/downloader.go
+++ b/eth/downloader/downloader.go
@@ -576,7 +576,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
// For non-merged networks, if there is a checkpoint available, then calculate
// the ancientLimit through that. Otherwise calculate the ancient limit through
// the advertised height of the remote peer. This most is mostly a fallback for
- // legacy networks, but should eventually be droppped. TODO(karalabe).
+ // legacy networks, but should eventually be dropped. TODO(karalabe).
if beaconMode {
// Beacon sync, use the latest finalized block as the ancient limit
// or a reasonable height if no finalized block is yet announced.
diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go
index a8b1b45e0..50b9031a2 100644
--- a/eth/downloader/queue_test.go
+++ b/eth/downloader/queue_test.go
@@ -20,6 +20,7 @@ import (
"fmt"
"math/big"
"math/rand"
+ "os"
"sync"
"testing"
"time"
@@ -31,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
+ "golang.org/x/exp/slog"
)
// makeChain creates a chain of n blocks starting at and including parent.
@@ -271,7 +273,7 @@ func XTestDelivery(t *testing.T) {
world.chain = blo
world.progress(10)
if false {
- log.Root().SetHandler(log.StdoutHandler)
+ log.SetDefault(log.NewLogger(slog.NewTextHandler(os.Stdout, nil)))
}
q := newQueue(10, 10)
var wg sync.WaitGroup
diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go
index 7f7f5a89e..e4323c04e 100644
--- a/eth/downloader/resultstore.go
+++ b/eth/downloader/resultstore.go
@@ -142,7 +142,7 @@ func (r *resultStore) HasCompletedItems() bool {
// countCompleted returns the number of items ready for delivery, stopping at
// the first non-complete item.
//
-// The mthod assumes (at least) rlock is held.
+// The method assumes (at least) rlock is held.
func (r *resultStore) countCompleted() int {
// We iterate from the already known complete point, and see
// if any more has completed since last count
diff --git a/eth/downloader/skeleton.go b/eth/downloader/skeleton.go
index 4f1f46204..f40ca24d9 100644
--- a/eth/downloader/skeleton.go
+++ b/eth/downloader/skeleton.go
@@ -69,9 +69,17 @@ var errSyncReorged = errors.New("sync reorged")
// might still be propagating.
var errTerminated = errors.New("terminated")
-// errReorgDenied is returned if an attempt is made to extend the beacon chain
-// with a new header, but it does not link up to the existing sync.
-var errReorgDenied = errors.New("non-forced head reorg denied")
+// errChainReorged is an internal helper error to signal that the header chain
+// of the current sync cycle was (partially) reorged.
+var errChainReorged = errors.New("chain reorged")
+
+// errChainGapped is an internal helper error to signal that the header chain
+// of the current sync cycle is gaped with the one advertised by consensus client.
+var errChainGapped = errors.New("chain gapped")
+
+// errChainForked is an internal helper error to signal that the header chain
+// of the current sync cycle is forked with the one advertised by consensus client.
+var errChainForked = errors.New("chain forked")
func init() {
// Tuning parameters is nice, but the scratch space must be assignable in
@@ -271,9 +279,9 @@ func (s *skeleton) startup() {
newhead, err := s.sync(head)
switch {
case err == errSyncLinked:
- // Sync cycle linked up to the genesis block. Tear down the loop
- // and restart it so, it can properly notify the backfiller. Don't
- // account a new head.
+ // Sync cycle linked up to the genesis block, or the existent chain
+ // segment. Tear down the loop and restart it so, it can properly
+ // notify the backfiller. Don't account a new head.
head = nil
case err == errSyncMerged:
@@ -457,15 +465,16 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) {
// we don't seamlessly integrate reorgs to keep things simple. If the
// network starts doing many mini reorgs, it might be worthwhile handling
// a limited depth without an error.
- if reorged := s.processNewHead(event.header, event.final, event.force); reorged {
+ if err := s.processNewHead(event.header, event.final); err != nil {
// If a reorg is needed, and we're forcing the new head, signal
// the syncer to tear down and start over. Otherwise, drop the
// non-force reorg.
if event.force {
event.errc <- nil // forced head reorg accepted
+ log.Info("Restarting sync cycle", "reason", err)
return event.header, errSyncReorged
}
- event.errc <- errReorgDenied
+ event.errc <- err
continue
}
event.errc <- nil // head extension accepted
@@ -610,7 +619,7 @@ func (s *skeleton) saveSyncStatus(db ethdb.KeyValueWriter) {
// accepts and integrates it into the skeleton or requests a reorg. Upon reorg,
// the syncer will tear itself down and restart with a fresh head. It is simpler
// to reconstruct the sync state than to mutate it and hope for the best.
-func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force bool) bool {
+func (s *skeleton) processNewHead(head *types.Header, final *types.Header) error {
// If a new finalized block was announced, update the sync process independent
// of what happens with the sync head below
if final != nil {
@@ -631,26 +640,17 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force
// once more, ignore it instead of tearing down sync for a noop.
if lastchain.Head == lastchain.Tail {
if current := rawdb.ReadSkeletonHeader(s.db, number); current.Hash() == head.Hash() {
- return false
+ return nil
}
}
// Not a noop / double head announce, abort with a reorg
- if force {
- log.Warn("Beacon chain reorged", "tail", lastchain.Tail, "head", lastchain.Head, "newHead", number)
- }
- return true
+ return fmt.Errorf("%w, tail: %d, head: %d, newHead: %d", errChainReorged, lastchain.Tail, lastchain.Head, number)
}
if lastchain.Head+1 < number {
- if force {
- log.Warn("Beacon chain gapped", "head", lastchain.Head, "newHead", number)
- }
- return true
+ return fmt.Errorf("%w, head: %d, newHead: %d", errChainGapped, lastchain.Head, number)
}
if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash {
- if force {
- log.Warn("Beacon chain forked", "ancestor", number-1, "hash", parent.Hash(), "want", head.ParentHash)
- }
- return true
+ return fmt.Errorf("%w, ancestor: %d, hash: %s, want: %s", errChainForked, number-1, parent.Hash(), head.ParentHash)
}
// New header seems to be in the last subchain range. Unwind any extra headers
// from the chain tip and insert the new head. We won't delete any trimmed
@@ -666,7 +666,7 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force
if err := batch.Write(); err != nil {
log.Crit("Failed to write skeleton sync status", "err", err)
}
- return false
+ return nil
}
// assignTasks attempts to match idle peers to pending header retrievals.
diff --git a/eth/downloader/skeleton_test.go b/eth/downloader/skeleton_test.go
index c31007765..aceadd00d 100644
--- a/eth/downloader/skeleton_test.go
+++ b/eth/downloader/skeleton_test.go
@@ -434,7 +434,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
newstate: []*subchain{
{Head: 49, Tail: 49},
},
- err: errReorgDenied,
+ err: errChainReorged,
},
// Initialize a sync and try to extend it with a number-wise sequential
// header, but a hash wise non-linking one.
@@ -444,7 +444,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
newstate: []*subchain{
{Head: 49, Tail: 49},
},
- err: errReorgDenied,
+ err: errChainForked,
},
// Initialize a sync and try to extend it with a non-linking future block.
{
@@ -453,7 +453,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
newstate: []*subchain{
{Head: 49, Tail: 49},
},
- err: errReorgDenied,
+ err: errChainGapped,
},
// Initialize a sync and try to extend it with a past canonical block.
{
@@ -462,7 +462,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
newstate: []*subchain{
{Head: 50, Tail: 50},
},
- err: errReorgDenied,
+ err: errChainReorged,
},
// Initialize a sync and try to extend it with a past sidechain block.
{
@@ -471,7 +471,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
newstate: []*subchain{
{Head: 50, Tail: 50},
},
- err: errReorgDenied,
+ err: errChainReorged,
},
}
for i, tt := range tests {
@@ -487,7 +487,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
skeleton.Sync(tt.head, nil, true)
<-wait
- if err := skeleton.Sync(tt.extend, nil, false); err != tt.err {
+ if err := skeleton.Sync(tt.extend, nil, false); !errors.Is(err, tt.err) {
t.Errorf("test %d: extension failure mismatch: have %v, want %v", i, err, tt.err)
}
skeleton.Terminate()
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 876c3c602..ab8a1611d 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -47,16 +47,6 @@ var FullNodeGPO = gasprice.Config{
IgnorePrice: gasprice.DefaultIgnorePrice,
}
-// LightClientGPO contains default gasprice oracle settings for light client.
-var LightClientGPO = gasprice.Config{
- Blocks: 2,
- Percentile: 60,
- MaxHeaderHistory: 300,
- MaxBlockHistory: 5,
- MaxPrice: gasprice.DefaultMaxPrice,
- IgnorePrice: gasprice.DefaultIgnorePrice,
-}
-
// Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{
SyncMode: downloader.SnapSync,
diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go
index 8751c4e3e..126eaaea7 100644
--- a/eth/fetcher/block_fetcher.go
+++ b/eth/fetcher/block_fetcher.go
@@ -483,7 +483,7 @@ func (f *BlockFetcher) loop() {
select {
case res := <-resCh:
res.Done <- nil
- f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersRequest), time.Now().Add(res.Time))
+ f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersRequest), time.Now())
case <-timeout.C:
// The peer didn't respond in time. The request
diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go
index 77b89085d..4a62e579b 100644
--- a/eth/fetcher/tx_fetcher_test.go
+++ b/eth/fetcher/tx_fetcher_test.go
@@ -186,7 +186,7 @@ func TestTransactionFetcherWaiting(t *testing.T) {
// waitlist, and none of them are scheduled for retrieval until the wait expires.
//
// This test is an extended version of TestTransactionFetcherWaiting. It's mostly
-// to cover the metadata checkes without bloating up the basic behavioral tests
+// to cover the metadata checks without bloating up the basic behavioral tests
// with all the useless extra fields.
func TestTransactionFetcherWaitingWithMeta(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
@@ -1030,7 +1030,7 @@ func TestTransactionFetcherRateLimiting(t *testing.T) {
}
// Tests that if huge transactions are announced, only a small number of them will
-// be requested at a time, to keep the responses below a resonable level.
+// be requested at a time, to keep the responses below a reasonable level.
func TestTransactionFetcherBandwidthLimiting(t *testing.T) {
testTransactionFetcherParallel(t, txFetcherTest{
init: func() *TxFetcher {
diff --git a/eth/filters/api.go b/eth/filters/api.go
index a4eaa9cec..8cf701ec5 100644
--- a/eth/filters/api.go
+++ b/eth/filters/api.go
@@ -159,6 +159,8 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool)
go func() {
txs := make(chan []*types.Transaction, 128)
pendingTxSub := api.events.SubscribePendingTxs(txs)
+ defer pendingTxSub.Unsubscribe()
+
chainConfig := api.sys.backend.ChainConfig()
for {
@@ -176,10 +178,8 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool)
}
}
case <-rpcSub.Err():
- pendingTxSub.Unsubscribe()
return
case <-notifier.Closed():
- pendingTxSub.Unsubscribe()
return
}
}
@@ -233,16 +233,15 @@ func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
go func() {
headers := make(chan *types.Header)
headersSub := api.events.SubscribeNewHeads(headers)
+ defer headersSub.Unsubscribe()
for {
select {
case h := <-headers:
notifier.Notify(rpcSub.ID, h)
case <-rpcSub.Err():
- headersSub.Unsubscribe()
return
case <-notifier.Closed():
- headersSub.Unsubscribe()
return
}
}
@@ -269,6 +268,7 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc
}
go func() {
+ defer logsSub.Unsubscribe()
for {
select {
case logs := <-matchedLogs:
@@ -277,10 +277,8 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc
notifier.Notify(rpcSub.ID, &log)
}
case <-rpcSub.Err(): // client send an unsubscribe request
- logsSub.Unsubscribe()
return
case <-notifier.Closed(): // connection dropped
- logsSub.Unsubscribe()
return
}
}
diff --git a/eth/filters/filter.go b/eth/filters/filter.go
index a5750c193..83e3284a2 100644
--- a/eth/filters/filter.go
+++ b/eth/filters/filter.go
@@ -114,7 +114,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
// special case for pending logs
if beginPending && !endPending {
- return nil, errors.New("invalid block range")
+ return nil, errInvalidBlockRange
}
// Short-cut if all we care about is pending logs
diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go
index 4e09a9038..1db917c96 100644
--- a/eth/filters/filter_test.go
+++ b/eth/filters/filter_test.go
@@ -353,7 +353,7 @@ func TestFilters(t *testing.T) {
},
{
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
- err: "invalid block range",
+ err: errInvalidBlockRange.Error(),
},
} {
logs, err := tc.f.Logs(context.Background())
diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go
new file mode 100644
index 000000000..a36c67074
--- /dev/null
+++ b/eth/gasestimator/gasestimator.go
@@ -0,0 +1,235 @@
+// 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 .
+
+package gasestimator
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "math"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "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/vm"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// Options are the contextual parameters to execute the requested call.
+//
+// Whilst it would be possible to pass a blockchain object that aggregates all
+// these together, it would be excessively hard to test. Splitting the parts out
+// allows testing without needing a proper live chain.
+type Options struct {
+ Config *params.ChainConfig // Chain configuration for hard fork selection
+ Chain core.ChainContext // Chain context to access past block hashes
+ Header *types.Header // Header defining the block context to execute in
+ State *state.StateDB // Pre-state on top of which to estimate the gas
+
+ ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination
+}
+
+// Estimate returns the lowest possible gas limit that allows the transaction to
+// run successfully with the provided context options. It returns an error if the
+// transaction would always revert, or if there are unexpected failures.
+func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) {
+ // Binary search the gas limit, as it may need to be higher than the amount used
+ var (
+ lo uint64 // lowest-known gas limit where tx execution fails
+ hi uint64 // lowest-known gas limit where tx execution succeeds
+ )
+ // Determine the highest gas limit can be used during the estimation.
+ hi = opts.Header.GasLimit
+ if call.GasLimit >= params.TxGas {
+ hi = call.GasLimit
+ }
+ // Normalize the max fee per gas the call is willing to spend.
+ var feeCap *big.Int
+ if call.GasFeeCap != nil {
+ feeCap = call.GasFeeCap
+ } else if call.GasPrice != nil {
+ feeCap = call.GasPrice
+ } else {
+ feeCap = common.Big0
+ }
+ // Recap the highest gas limit with account's available balance.
+ if feeCap.BitLen() != 0 {
+ balance := opts.State.GetBalance(call.From)
+
+ available := new(big.Int).Set(balance)
+ if call.Value != nil {
+ if call.Value.Cmp(available) >= 0 {
+ return 0, nil, core.ErrInsufficientFundsForTransfer
+ }
+ available.Sub(available, call.Value)
+ }
+ allowance := new(big.Int).Div(available, feeCap)
+
+ // If the allowance is larger than maximum uint64, skip checking
+ if allowance.IsUint64() && hi > allowance.Uint64() {
+ transfer := call.Value
+ if transfer == nil {
+ transfer = new(big.Int)
+ }
+ log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
+ "sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
+ hi = allowance.Uint64()
+ }
+ }
+ // Recap the highest gas allowance with specified gascap.
+ if gasCap != 0 && hi > gasCap {
+ log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
+ hi = gasCap
+ }
+ // If the transaction is a plain value transfer, short circuit estimation and
+ // directly try 21000. Returning 21000 without any execution is dangerous as
+ // some tx field combos might bump the price up even for plain transfers (e.g.
+ // unused access list items). Ever so slightly wasteful, but safer overall.
+ if len(call.Data) == 0 {
+ if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 {
+ failed, _, err := execute(ctx, call, opts, params.TxGas)
+ if !failed && err == nil {
+ return params.TxGas, nil, nil
+ }
+ }
+ }
+ // We first execute the transaction at the highest allowable gas limit, since if this fails we
+ // can return error immediately.
+ failed, result, err := execute(ctx, call, opts, hi)
+ if err != nil {
+ return 0, nil, err
+ }
+ if failed {
+ if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
+ return 0, result.Revert(), result.Err
+ }
+ return 0, nil, fmt.Errorf("gas required exceeds allowance (%d)", hi)
+ }
+ // For almost any transaction, the gas consumed by the unconstrained execution
+ // above lower-bounds the gas limit required for it to succeed. One exception
+ // is those that explicitly check gas remaining in order to execute within a
+ // given limit, but we probably don't want to return the lowest possible gas
+ // limit for these cases anyway.
+ lo = result.UsedGas - 1
+
+ // There's a fairly high chance for the transaction to execute successfully
+ // with gasLimit set to the first execution's usedGas + gasRefund. Explicitly
+ // check that gas amount and use as a limit for the binary search.
+ optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63
+ if optimisticGasLimit < hi {
+ failed, _, err = execute(ctx, call, opts, optimisticGasLimit)
+ if err != nil {
+ // This should not happen under normal conditions since if we make it this far the
+ // transaction had run without error at least once before.
+ log.Error("Execution error in estimate gas", "err", err)
+ return 0, nil, err
+ }
+ if failed {
+ lo = optimisticGasLimit
+ } else {
+ hi = optimisticGasLimit
+ }
+ }
+ // Binary search for the smallest gas limit that allows the tx to execute successfully.
+ for lo+1 < hi {
+ if opts.ErrorRatio > 0 {
+ // It is a bit pointless to return a perfect estimation, as changing
+ // network conditions require the caller to bump it up anyway. Since
+ // wallets tend to use 20-25% bump, allowing a small approximation
+ // error is fine (as long as it's upwards).
+ if float64(hi-lo)/float64(hi) < opts.ErrorRatio {
+ break
+ }
+ }
+ mid := (hi + lo) / 2
+ if mid > lo*2 {
+ // Most txs don't need much higher gas limit than their gas used, and most txs don't
+ // require near the full block limit of gas, so the selection of where to bisect the
+ // range here is skewed to favor the low side.
+ mid = lo * 2
+ }
+ failed, _, err = execute(ctx, call, opts, mid)
+ if err != nil {
+ // This should not happen under normal conditions since if we make it this far the
+ // transaction had run without error at least once before.
+ log.Error("Execution error in estimate gas", "err", err)
+ return 0, nil, err
+ }
+ if failed {
+ lo = mid
+ } else {
+ hi = mid
+ }
+ }
+ return hi, nil, nil
+}
+
+// execute is a helper that executes the transaction under a given gas limit and
+// returns true if the transaction fails for a reason that might be related to
+// not enough gas. A non-nil error means execution failed due to reasons unrelated
+// to the gas limit.
+func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64) (bool, *core.ExecutionResult, error) {
+ // Configure the call for this specific execution (and revert the change after)
+ defer func(gas uint64) { call.GasLimit = gas }(call.GasLimit)
+ call.GasLimit = gasLimit
+
+ // Execute the call and separate execution faults caused by a lack of gas or
+ // other non-fixable conditions
+ result, err := run(ctx, call, opts)
+ if err != nil {
+ if errors.Is(err, core.ErrIntrinsicGas) {
+ return true, nil, nil // Special case, raise gas limit
+ }
+ return true, nil, err // Bail out
+ }
+ return result.Failed(), result, nil
+}
+
+// run assembles the EVM as defined by the consensus rules and runs the requested
+// call invocation.
+func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) {
+ // Assemble the call and the call context
+ var (
+ msgContext = core.NewEVMTxContext(call)
+ evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
+
+ dirtyState = opts.State.Copy()
+ evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
+ )
+ // Monitor the outer context and interrupt the EVM upon cancellation. To avoid
+ // a dangling goroutine until the outer estimation finishes, create an internal
+ // context for the lifetime of this method call.
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ go func() {
+ <-ctx.Done()
+ evm.Cancel()
+ }()
+ // Execute the call, returning a wrapped error or the result
+ result, err := core.ApplyMessage(evm, call, new(core.GasPool).AddGas(math.MaxUint64))
+ if vmerr := dirtyState.Error(); vmerr != nil {
+ return nil, vmerr
+ }
+ if err != nil {
+ return result, fmt.Errorf("failed with %d gas: %w", call.GasLimit, err)
+ }
+ return result, nil
+}
diff --git a/eth/handler.go b/eth/handler.go
index f0021e564..a327af611 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -178,6 +178,10 @@ func newHandler(config *handlerConfig) (*handler, error) {
log.Info("Enabled snap sync", "head", head.Number, "hash", head.Hash())
}
}
+ // If snap sync is requested but snapshots are disabled, fail loudly
+ if h.snapSync.Load() && config.Chain.Snapshots() == nil {
+ return nil, errors.New("snap sync not supported with snapshots disabled")
+ }
// Construct the downloader (long sync)
h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, h.enableSyncedFeatures)
if ttd := h.chain.Config().TerminalTotalDifficulty; ttd != nil {
diff --git a/eth/protocols/eth/dispatcher.go b/eth/protocols/eth/dispatcher.go
index 3f81e045b..ae98820cd 100644
--- a/eth/protocols/eth/dispatcher.go
+++ b/eth/protocols/eth/dispatcher.go
@@ -41,7 +41,7 @@ var (
// Request is a pending request to allow tracking it and delivering a response
// back to the requester on their chosen channel.
type Request struct {
- peer *Peer // Peer to which this request belogs for untracking
+ peer *Peer // Peer to which this request belongs for untracking
id uint64 // Request ID to match up replies to
sink chan *Response // Channel to deliver the response on
@@ -224,7 +224,7 @@ func (p *Peer) dispatcher() {
switch {
case res.Req == nil:
// Response arrived with an untracked ID. Since even cancelled
- // requests are tracked until fulfilment, a dangling response
+ // requests are tracked until fulfillment, a dangling response
// means the remote peer implements the protocol badly.
resOp.fail <- errDanglingResponse
diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go
index 938af0cab..98ad22a8c 100644
--- a/eth/protocols/eth/peer.go
+++ b/eth/protocols/eth/peer.go
@@ -84,7 +84,7 @@ type Peer struct {
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests
- reqDispatch chan *request // Dispatch channel to send requests and track then until fulfilment
+ reqDispatch chan *request // Dispatch channel to send requests and track then until fulfillment
reqCancel chan *cancel // Dispatch channel to cancel pending requests and untrack them
resDispatch chan *response // Dispatch channel to fulfil pending requests and untrack them
diff --git a/tests/fuzzers/snap/fuzz_handler.go b/eth/protocols/snap/handler_fuzzing_test.go
similarity index 77%
rename from tests/fuzzers/snap/fuzz_handler.go
rename to eth/protocols/snap/handler_fuzzing_test.go
index 20521bb92..daed7ed44 100644
--- a/tests/fuzzers/snap/fuzz_handler.go
+++ b/eth/protocols/snap/handler_fuzzing_test.go
@@ -21,6 +21,7 @@ import (
"encoding/binary"
"fmt"
"math/big"
+ "testing"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -28,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params"
@@ -36,6 +36,56 @@ import (
fuzz "github.com/google/gofuzz"
)
+func FuzzARange(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ doFuzz(data, &GetAccountRangePacket{}, GetAccountRangeMsg)
+ })
+}
+
+func FuzzSRange(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ doFuzz(data, &GetStorageRangesPacket{}, GetStorageRangesMsg)
+ })
+}
+
+func FuzzByteCodes(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ doFuzz(data, &GetByteCodesPacket{}, GetByteCodesMsg)
+ })
+}
+
+func FuzzTrieNodes(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ doFuzz(data, &GetTrieNodesPacket{}, GetTrieNodesMsg)
+ })
+}
+
+func doFuzz(input []byte, obj interface{}, code int) {
+ bc := getChain()
+ defer bc.Stop()
+ fuzz.NewFromGoFuzz(input).Fuzz(obj)
+ var data []byte
+ switch p := obj.(type) {
+ case *GetTrieNodesPacket:
+ p.Root = trieRoot
+ data, _ = rlp.EncodeToBytes(obj)
+ default:
+ data, _ = rlp.EncodeToBytes(obj)
+ }
+ cli := &dummyRW{
+ code: uint64(code),
+ data: data,
+ }
+ peer := NewFakePeer(65, "gazonk01", cli)
+ err := HandleMessage(&dummyBackend{bc}, peer)
+ switch {
+ case err == nil && cli.writeCount != 1:
+ panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount))
+ case err != nil && cli.writeCount != 0:
+ panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount))
+ }
+}
+
var trieRoot common.Hash
func getChain() *core.BlockChain {
@@ -86,10 +136,10 @@ type dummyBackend struct {
chain *core.BlockChain
}
-func (d *dummyBackend) Chain() *core.BlockChain { return d.chain }
-func (d *dummyBackend) RunPeer(*snap.Peer, snap.Handler) error { return nil }
-func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" }
-func (d *dummyBackend) Handle(*snap.Peer, snap.Packet) error { return nil }
+func (d *dummyBackend) Chain() *core.BlockChain { return d.chain }
+func (d *dummyBackend) RunPeer(*Peer, Handler) error { return nil }
+func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" }
+func (d *dummyBackend) Handle(*Peer, Packet) error { return nil }
type dummyRW struct {
code uint64
@@ -110,34 +160,3 @@ func (d *dummyRW) WriteMsg(msg p2p.Msg) error {
d.writeCount++
return nil
}
-
-func doFuzz(input []byte, obj interface{}, code int) int {
- if len(input) > 1024*4 {
- return -1
- }
- bc := getChain()
- defer bc.Stop()
- backend := &dummyBackend{bc}
- fuzz.NewFromGoFuzz(input).Fuzz(obj)
- var data []byte
- switch p := obj.(type) {
- case *snap.GetTrieNodesPacket:
- p.Root = trieRoot
- data, _ = rlp.EncodeToBytes(obj)
- default:
- data, _ = rlp.EncodeToBytes(obj)
- }
- cli := &dummyRW{
- code: uint64(code),
- data: data,
- }
- peer := snap.NewFakePeer(65, "gazonk01", cli)
- err := snap.HandleMessage(backend, peer)
- switch {
- case err == nil && cli.writeCount != 1:
- panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount))
- case err != nil && cli.writeCount != 0:
- panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount))
- }
- return 1
-}
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 6c7023f1c..cac89aa82 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -164,6 +164,7 @@ type TraceCallConfig struct {
TraceConfig
StateOverrides *ethapi.StateOverride
BlockOverrides *ethapi.BlockOverrides
+ TxIndex *hexutil.Uint
}
// StdTraceConfig holds extra parameters to standard-json trace functions.
@@ -863,11 +864,17 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
// TraceCall lets you trace a given eth_call. It collects the structured logs
// created during the execution of EVM if the given transaction was added on
// top of the provided block and returns them as a JSON object.
+// If no transaction index is specified, the trace will be conducted on the state
+// after executing the specified block. However, if a transaction index is provided,
+// the trace will be conducted on the state after executing the specified transaction
+// within the specified block.
func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
// Try to retrieve the specified block
var (
- err error
- block *types.Block
+ err error
+ block *types.Block
+ statedb *state.StateDB
+ release StateReleaseFunc
)
if hash, ok := blockNrOrHash.Hash(); ok {
block, err = api.blockByHash(ctx, hash)
@@ -892,7 +899,12 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
- statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
+
+ if config != nil && config.TxIndex != nil {
+ _, _, statedb, release, err = api.backend.StateAtTransaction(ctx, block, int(*config.TxIndex), reexec)
+ } else {
+ statedb, release, err = api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
+ }
if err != nil {
return nil, err
}
diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go
index 0f78af9a0..49c3ebb67 100644
--- a/eth/tracers/api_test.go
+++ b/eth/tracers/api_test.go
@@ -200,13 +200,51 @@ func TestTraceCall(t *testing.T) {
}
genBlocks := 10
signer := types.HomesteadSigner{}
+ nonce := uint64(0)
backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
// Transfer from account[0] to account[1]
// value: 1000 wei
// fee: 0 wei
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
+ tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
+ Nonce: nonce,
+ To: &accounts[1].addr,
+ Value: big.NewInt(1000),
+ Gas: params.TxGas,
+ GasPrice: b.BaseFee(),
+ Data: nil}),
+ signer, accounts[0].key)
b.AddTx(tx)
+ nonce++
+
+ if i == genBlocks-2 {
+ // Transfer from account[0] to account[2]
+ tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
+ Nonce: nonce,
+ To: &accounts[2].addr,
+ Value: big.NewInt(1000),
+ Gas: params.TxGas,
+ GasPrice: b.BaseFee(),
+ Data: nil}),
+ signer, accounts[0].key)
+ b.AddTx(tx)
+ nonce++
+
+ // Transfer from account[0] to account[1] again
+ tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
+ Nonce: nonce,
+ To: &accounts[1].addr,
+ Value: big.NewInt(1000),
+ Gas: params.TxGas,
+ GasPrice: b.BaseFee(),
+ Data: nil}),
+ signer, accounts[0].key)
+ b.AddTx(tx)
+ nonce++
+ }
})
+
+ uintPtr := func(i int) *hexutil.Uint { x := hexutil.Uint(i); return &x }
+
defer backend.teardown()
api := NewAPI(backend)
var testSuite = []struct {
@@ -240,6 +278,51 @@ func TestTraceCall(t *testing.T) {
expectErr: nil,
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
},
+ // Upon the last state, default to the post block's state
+ {
+ blockNumber: rpc.BlockNumber(genBlocks - 1),
+ call: ethapi.TransactionArgs{
+ From: &accounts[2].addr,
+ To: &accounts[0].addr,
+ Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))),
+ },
+ config: nil,
+ expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
+ },
+ // Before the first transaction, should be failed
+ {
+ blockNumber: rpc.BlockNumber(genBlocks - 1),
+ call: ethapi.TransactionArgs{
+ From: &accounts[2].addr,
+ To: &accounts[0].addr,
+ Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))),
+ },
+ config: &TraceCallConfig{TxIndex: uintPtr(0)},
+ expectErr: fmt.Errorf("tracing failed: insufficient funds for gas * price + value: address %s have 1000000000000000000 want 1000000000000000100", accounts[2].addr),
+ },
+ // Before the target transaction, should be failed
+ {
+ blockNumber: rpc.BlockNumber(genBlocks - 1),
+ call: ethapi.TransactionArgs{
+ From: &accounts[2].addr,
+ To: &accounts[0].addr,
+ Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))),
+ },
+ config: &TraceCallConfig{TxIndex: uintPtr(1)},
+ expectErr: fmt.Errorf("tracing failed: insufficient funds for gas * price + value: address %s have 1000000000000000000 want 1000000000000000100", accounts[2].addr),
+ },
+ // After the target transaction, should be succeed
+ {
+ blockNumber: rpc.BlockNumber(genBlocks - 1),
+ call: ethapi.TransactionArgs{
+ From: &accounts[2].addr,
+ To: &accounts[0].addr,
+ Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))),
+ },
+ config: &TraceCallConfig{TxIndex: uintPtr(2)},
+ expectErr: nil,
+ expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
+ },
// Standard JSON trace upon the non-existent block, error expects
{
blockNumber: rpc.BlockNumber(genBlocks + 1),
@@ -297,8 +380,8 @@ func TestTraceCall(t *testing.T) {
t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr)
continue
}
- if !reflect.DeepEqual(err, testspec.expectErr) {
- t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err)
+ if !reflect.DeepEqual(err.Error(), testspec.expectErr.Error()) {
+ t.Errorf("test %d: error mismatch, want '%v', got '%v'", i, testspec.expectErr, err)
}
} else {
if err != nil {
@@ -338,7 +421,14 @@ func TestTraceTransaction(t *testing.T) {
// Transfer from account[0] to account[1]
// value: 1000 wei
// fee: 0 wei
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
+ tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
+ Nonce: uint64(i),
+ To: &accounts[1].addr,
+ Value: big.NewInt(1000),
+ Gas: params.TxGas,
+ GasPrice: b.BaseFee(),
+ Data: nil}),
+ signer, accounts[0].key)
b.AddTx(tx)
target = tx.Hash()
})
@@ -388,7 +478,14 @@ func TestTraceBlock(t *testing.T) {
// Transfer from account[0] to account[1]
// value: 1000 wei
// fee: 0 wei
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
+ tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
+ Nonce: uint64(i),
+ To: &accounts[1].addr,
+ Value: big.NewInt(1000),
+ Gas: params.TxGas,
+ GasPrice: b.BaseFee(),
+ Data: nil}),
+ signer, accounts[0].key)
b.AddTx(tx)
txHash = tx.Hash()
})
@@ -478,7 +575,14 @@ func TestTracingWithOverrides(t *testing.T) {
// Transfer from account[0] to account[1]
// value: 1000 wei
// fee: 0 wei
- tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
+ tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
+ Nonce: uint64(i),
+ To: &accounts[1].addr,
+ Value: big.NewInt(1000),
+ Gas: params.TxGas,
+ GasPrice: b.BaseFee(),
+ Data: nil}),
+ signer, accounts[0].key)
b.AddTx(tx)
})
defer backend.chain.Stop()
diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go
index d22d14098..07c138bae 100644
--- a/eth/tracers/js/goja.go
+++ b/eth/tracers/js/goja.go
@@ -142,19 +142,29 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer
vm: vm,
ctx: make(map[string]goja.Value),
}
+
+ t.setTypeConverters()
+ t.setBuiltinFunctions()
+
if ctx == nil {
ctx = new(tracers.Context)
}
if ctx.BlockHash != (common.Hash{}) {
- t.ctx["blockHash"] = vm.ToValue(ctx.BlockHash.Bytes())
+ blockHash, err := t.toBuf(vm, ctx.BlockHash.Bytes())
+ if err != nil {
+ return nil, err
+ }
+ t.ctx["blockHash"] = blockHash
if ctx.TxHash != (common.Hash{}) {
t.ctx["txIndex"] = vm.ToValue(ctx.TxIndex)
- t.ctx["txHash"] = vm.ToValue(ctx.TxHash.Bytes())
+ txHash, err := t.toBuf(vm, ctx.TxHash.Bytes())
+ if err != nil {
+ return nil, err
+ }
+ t.ctx["txHash"] = txHash
}
}
- t.setTypeConverters()
- t.setBuiltinFunctions()
ret, err := vm.RunString("(" + code + ")")
if err != nil {
return nil, err
@@ -224,6 +234,10 @@ func (t *jsTracer) CaptureTxEnd(restGas uint64) {
// CaptureStart implements the Tracer interface to initialize the tracing operation.
func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ cancel := func(err error) {
+ t.err = err
+ t.env.Cancel()
+ }
t.env = env
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
t.dbValue = db.setupObject()
@@ -232,19 +246,34 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
} else {
t.ctx["type"] = t.vm.ToValue("CALL")
}
- t.ctx["from"] = t.vm.ToValue(from.Bytes())
- t.ctx["to"] = t.vm.ToValue(to.Bytes())
- t.ctx["input"] = t.vm.ToValue(input)
+ fromVal, err := t.toBuf(t.vm, from.Bytes())
+ if err != nil {
+ cancel(err)
+ return
+ }
+ t.ctx["from"] = fromVal
+ toVal, err := t.toBuf(t.vm, to.Bytes())
+ if err != nil {
+ cancel(err)
+ return
+ }
+ t.ctx["to"] = toVal
+ inputVal, err := t.toBuf(t.vm, input)
+ if err != nil {
+ cancel(err)
+ return
+ }
+ t.ctx["input"] = inputVal
t.ctx["gas"] = t.vm.ToValue(t.gasLimit)
gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String())
if err != nil {
- t.err = err
+ cancel(err)
return
}
t.ctx["gasPrice"] = gasPriceBig
valueBig, err := t.toBig(t.vm, value.String())
if err != nil {
- t.err = err
+ cancel(err)
return
}
t.ctx["value"] = valueBig
@@ -293,10 +322,15 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
- t.ctx["output"] = t.vm.ToValue(output)
if err != nil {
t.ctx["error"] = t.vm.ToValue(err.Error())
}
+ outputVal, err := t.toBuf(t.vm, output)
+ if err != nil {
+ t.err = err
+ return
+ }
+ t.ctx["output"] = outputVal
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
@@ -465,13 +499,13 @@ func (t *jsTracer) setBuiltinFunctions() {
}
return false
})
- vm.Set("slice", func(slice goja.Value, start, end int) goja.Value {
+ vm.Set("slice", func(slice goja.Value, start, end int64) goja.Value {
b, err := t.fromBuf(vm, slice, false)
if err != nil {
vm.Interrupt(err)
return nil
}
- if start < 0 || start > end || end > len(b) {
+ if start < 0 || start > end || end > int64(len(b)) {
vm.Interrupt(fmt.Sprintf("Tracer accessed out of bound memory: available %d, offset %d, size %d", len(b), start, end-start))
return nil
}
diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go
index df06a9ee6..b406cb344 100644
--- a/eth/tracers/logger/gen_structlog.go
+++ b/eth/tracers/logger/gen_structlog.go
@@ -23,7 +23,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
GasCost math.HexOrDecimal64 `json:"gasCost"`
Memory hexutil.Bytes `json:"memory,omitempty"`
MemorySize int `json:"memSize"`
- Stack []uint256.Int `json:"stack"`
+ Stack []hexutil.U256 `json:"stack"`
ReturnData hexutil.Bytes `json:"returnData,omitempty"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
@@ -39,7 +39,12 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
enc.GasCost = math.HexOrDecimal64(s.GasCost)
enc.Memory = s.Memory
enc.MemorySize = s.MemorySize
- enc.Stack = s.Stack
+ if s.Stack != nil {
+ enc.Stack = make([]hexutil.U256, len(s.Stack))
+ for k, v := range s.Stack {
+ enc.Stack[k] = hexutil.U256(v)
+ }
+ }
enc.ReturnData = s.ReturnData
enc.Storage = s.Storage
enc.Depth = s.Depth
@@ -59,7 +64,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
GasCost *math.HexOrDecimal64 `json:"gasCost"`
Memory *hexutil.Bytes `json:"memory,omitempty"`
MemorySize *int `json:"memSize"`
- Stack []uint256.Int `json:"stack"`
+ Stack []hexutil.U256 `json:"stack"`
ReturnData *hexutil.Bytes `json:"returnData,omitempty"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"`
@@ -89,7 +94,10 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
s.MemorySize = *dec.MemorySize
}
if dec.Stack != nil {
- s.Stack = dec.Stack
+ s.Stack = make([]uint256.Int, len(dec.Stack))
+ for k, v := range dec.Stack {
+ s.Stack[k] = uint256.Int(v)
+ }
}
if dec.ReturnData != nil {
s.ReturnData = *dec.ReturnData
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
index 4c9b910a2..2b36f9f49 100644
--- a/eth/tracers/logger/logger.go
+++ b/eth/tracers/logger/logger.go
@@ -83,6 +83,7 @@ type structLogMarshaling struct {
GasCost math.HexOrDecimal64
Memory hexutil.Bytes
ReturnData hexutil.Bytes
+ Stack []hexutil.U256
OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON
}
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go
index e8a201f71..900335988 100644
--- a/ethclient/ethclient.go
+++ b/ethclient/ethclient.go
@@ -307,10 +307,8 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash,
func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
var r *types.Receipt
err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash)
- if err == nil {
- if r == nil {
- return nil, ethereum.NotFound
- }
+ if err == nil && r == nil {
+ return nil, ethereum.NotFound
}
return r, err
}
diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go
index a718246bd..fdd94a7d7 100644
--- a/ethclient/gethclient/gethclient_test.go
+++ b/ethclient/gethclient/gethclient_test.go
@@ -450,7 +450,7 @@ func testCallContract(t *testing.T, client *rpc.Client) {
func TestOverrideAccountMarshal(t *testing.T) {
om := map[common.Address]OverrideAccount{
{0x11}: {
- // Zero-valued nonce is not overriddden, but simply dropped by the encoder.
+ // Zero-valued nonce is not overridden, but simply dropped by the encoder.
Nonce: 0,
},
{0xaa}: {
diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go
new file mode 100644
index 000000000..54675b6dd
--- /dev/null
+++ b/ethclient/simulated/backend.go
@@ -0,0 +1,190 @@
+// 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 .
+
+package simulated
+
+import (
+ "time"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/catalyst"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
+ "github.com/ethereum/go-ethereum/eth/filters"
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+// Backend is a simulated blockchain. You can use it to test your contracts or
+// other code that interacts with the Ethereum chain.
+type Backend struct {
+ eth *eth.Ethereum
+ beacon *catalyst.SimulatedBeacon
+ client simClient
+}
+
+// simClient wraps ethclient. This exists to prevent extracting ethclient.Client
+// from the Client interface returned by Backend.
+type simClient struct {
+ *ethclient.Client
+}
+
+// Client exposes the methods provided by the Ethereum RPC client.
+type Client interface {
+ ethereum.BlockNumberReader
+ ethereum.ChainReader
+ ethereum.ChainStateReader
+ ethereum.ContractCaller
+ ethereum.GasEstimator
+ ethereum.GasPricer
+ ethereum.GasPricer1559
+ ethereum.FeeHistoryReader
+ ethereum.LogFilterer
+ ethereum.PendingStateReader
+ ethereum.PendingContractCaller
+ ethereum.TransactionReader
+ ethereum.TransactionSender
+ ethereum.ChainIDReader
+}
+
+// New creates a new binding backend using a simulated blockchain
+// for testing purposes.
+// A simulated backend always uses chainID 1337.
+func New(alloc core.GenesisAlloc, gasLimit uint64) *Backend {
+ // Setup the node object
+ nodeConf := node.DefaultConfig
+ nodeConf.DataDir = ""
+ nodeConf.P2P = p2p.Config{NoDiscovery: true}
+ stack, err := node.New(&nodeConf)
+ if err != nil {
+ // This should never happen, if it does, please open an issue
+ panic(err)
+ }
+
+ // Setup ethereum
+ genesis := core.Genesis{
+ Config: params.AllDevChainProtocolChanges,
+ GasLimit: gasLimit,
+ Alloc: alloc,
+ }
+ conf := ethconfig.Defaults
+ conf.Genesis = &genesis
+ conf.SyncMode = downloader.FullSync
+ conf.TxPool.NoLocals = true
+ sim, err := newWithNode(stack, &conf, 0)
+ if err != nil {
+ // This should never happen, if it does, please open an issue
+ panic(err)
+ }
+ return sim
+}
+
+// newWithNode sets up a simulated backend on an existing node
+// this allows users to do persistent simulations.
+// The provided node must not be started and will be started by newWithNode
+func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) {
+ backend, err := eth.New(stack, conf)
+ if err != nil {
+ return nil, err
+ }
+
+ // Register the filter system
+ filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
+ stack.RegisterAPIs([]rpc.API{{
+ Namespace: "eth",
+ Service: filters.NewFilterAPI(filterSystem, false),
+ }})
+
+ // Start the node
+ if err := stack.Start(); err != nil {
+ return nil, err
+ }
+
+ // Set up the simulated beacon
+ beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, backend)
+ if err != nil {
+ return nil, err
+ }
+
+ // Reorg our chain back to genesis
+ if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil {
+ return nil, err
+ }
+
+ return &Backend{
+ eth: backend,
+ beacon: beacon,
+ client: simClient{ethclient.NewClient(stack.Attach())},
+ }, nil
+}
+
+// Close shuts down the simBackend.
+// The simulated backend can't be used afterwards.
+func (n *Backend) Close() error {
+ if n.client.Client != nil {
+ n.client.Close()
+ n.client = simClient{}
+ }
+ if n.beacon != nil {
+ err := n.beacon.Stop()
+ n.beacon = nil
+ return err
+ }
+ return nil
+}
+
+// Commit seals a block and moves the chain forward to a new empty block.
+func (n *Backend) Commit() common.Hash {
+ return n.beacon.Commit()
+}
+
+// Rollback removes all pending transactions, reverting to the last committed state.
+func (n *Backend) Rollback() {
+ n.beacon.Rollback()
+}
+
+// Fork creates a side-chain that can be used to simulate reorgs.
+//
+// This function should be called with the ancestor block where the new side
+// chain should be started. Transactions (old and new) can then be applied on
+// top and Commit-ed.
+//
+// Note, the side-chain will only become canonical (and trigger the events) when
+// it becomes longer. Until then CallContract will still operate on the current
+// canonical chain.
+//
+// There is a % chance that the side chain becomes canonical at the same length
+// to simulate live network behavior.
+func (n *Backend) Fork(parentHash common.Hash) error {
+ return n.beacon.Fork(parentHash)
+}
+
+// AdjustTime changes the block timestamp and creates a new block.
+// It can only be called on empty blocks.
+func (n *Backend) AdjustTime(adjustment time.Duration) error {
+ return n.beacon.AdjustTime(adjustment)
+}
+
+// Client returns a client that accesses the simulated chain.
+func (n *Backend) Client() Client {
+ return n.client
+}
diff --git a/ethclient/simulated/backend_test.go b/ethclient/simulated/backend_test.go
new file mode 100644
index 000000000..16a2acdf4
--- /dev/null
+++ b/ethclient/simulated/backend_test.go
@@ -0,0 +1,309 @@
+// Copyright 2019 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 .
+
+package simulated
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "math/big"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+var _ bind.ContractBackend = (Client)(nil)
+
+var (
+ testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+)
+
+func simTestBackend(testAddr common.Address) *Backend {
+ return New(
+ core.GenesisAlloc{
+ testAddr: {Balance: big.NewInt(10000000000000000)},
+ }, 10000000,
+ )
+}
+
+func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
+ client := sim.Client()
+
+ // create a signed transaction to send
+ head, _ := client.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
+ gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
+ addr := crypto.PubkeyToAddress(key.PublicKey)
+ chainid, _ := client.ChainID(context.Background())
+ nonce, err := client.PendingNonceAt(context.Background(), addr)
+ if err != nil {
+ return nil, err
+ }
+ tx := types.NewTx(&types.DynamicFeeTx{
+ ChainID: chainid,
+ Nonce: nonce,
+ GasTipCap: big.NewInt(1),
+ GasFeeCap: gasPrice,
+ Gas: 21000,
+ To: &addr,
+ })
+ return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
+}
+
+func TestNewSim(t *testing.T) {
+ sim := New(core.GenesisAlloc{}, 30_000_000)
+ defer sim.Close()
+
+ client := sim.Client()
+ num, err := client.BlockNumber(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if num != 0 {
+ t.Fatalf("expected 0 got %v", num)
+ }
+ // Create a block
+ sim.Commit()
+ num, err = client.BlockNumber(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if num != 1 {
+ t.Fatalf("expected 1 got %v", num)
+ }
+}
+
+func TestAdjustTime(t *testing.T) {
+ sim := New(core.GenesisAlloc{}, 10_000_000)
+ defer sim.Close()
+
+ client := sim.Client()
+ block1, _ := client.BlockByNumber(context.Background(), nil)
+
+ // Create a block
+ if err := sim.AdjustTime(time.Minute); err != nil {
+ t.Fatal(err)
+ }
+ block2, _ := client.BlockByNumber(context.Background(), nil)
+ prevTime := block1.Time()
+ newTime := block2.Time()
+ if newTime-prevTime != uint64(time.Minute) {
+ t.Errorf("adjusted time not equal to 60 seconds. prev: %v, new: %v", prevTime, newTime)
+ }
+}
+
+func TestSendTransaction(t *testing.T) {
+ sim := simTestBackend(testAddr)
+ defer sim.Close()
+
+ client := sim.Client()
+ ctx := context.Background()
+
+ signedTx, err := newTx(sim, testKey)
+ if err != nil {
+ t.Errorf("could not create transaction: %v", err)
+ }
+ // send tx to simulated backend
+ err = client.SendTransaction(ctx, signedTx)
+ if err != nil {
+ t.Errorf("could not add tx to pending block: %v", err)
+ }
+ sim.Commit()
+ block, err := client.BlockByNumber(ctx, big.NewInt(1))
+ if err != nil {
+ t.Errorf("could not get block at height 1: %v", err)
+ }
+
+ if signedTx.Hash() != block.Transactions()[0].Hash() {
+ t.Errorf("did not commit sent transaction. expected hash %v got hash %v", block.Transactions()[0].Hash(), signedTx.Hash())
+ }
+}
+
+// TestFork check that the chain length after a reorg is correct.
+// Steps:
+// 1. Save the current block which will serve as parent for the fork.
+// 2. Mine n blocks with n ∈ [0, 20].
+// 3. Assert that the chain length is n.
+// 4. Fork by using the parent block as ancestor.
+// 5. Mine n+1 blocks which should trigger a reorg.
+// 6. Assert that the chain length is n+1.
+// Since Commit() was called 2n+1 times in total,
+// having a chain length of just n+1 means that a reorg occurred.
+func TestFork(t *testing.T) {
+ t.Parallel()
+ testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
+ sim := simTestBackend(testAddr)
+ defer sim.Close()
+
+ client := sim.Client()
+ ctx := context.Background()
+
+ // 1.
+ parent, _ := client.HeaderByNumber(ctx, nil)
+
+ // 2.
+ n := int(rand.Int31n(21))
+ for i := 0; i < n; i++ {
+ sim.Commit()
+ }
+
+ // 3.
+ b, _ := client.BlockNumber(ctx)
+ if b != uint64(n) {
+ t.Error("wrong chain length")
+ }
+
+ // 4.
+ sim.Fork(parent.Hash())
+
+ // 5.
+ for i := 0; i < n+1; i++ {
+ sim.Commit()
+ }
+
+ // 6.
+ b, _ = client.BlockNumber(ctx)
+ if b != uint64(n+1) {
+ t.Error("wrong chain length")
+ }
+}
+
+// TestForkResendTx checks that re-sending a TX after a fork
+// is possible and does not cause a "nonce mismatch" panic.
+// Steps:
+// 1. Save the current block which will serve as parent for the fork.
+// 2. Send a transaction.
+// 3. Check that the TX is included in block 1.
+// 4. Fork by using the parent block as ancestor.
+// 5. Mine a block, Re-send the transaction and mine another one.
+// 6. Check that the TX is now included in block 2.
+func TestForkResendTx(t *testing.T) {
+ t.Parallel()
+ testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
+ sim := simTestBackend(testAddr)
+ defer sim.Close()
+
+ client := sim.Client()
+ ctx := context.Background()
+
+ // 1.
+ parent, _ := client.HeaderByNumber(ctx, nil)
+
+ // 2.
+ tx, err := newTx(sim, testKey)
+ if err != nil {
+ t.Fatalf("could not create transaction: %v", err)
+ }
+ client.SendTransaction(ctx, tx)
+ sim.Commit()
+
+ // 3.
+ receipt, _ := client.TransactionReceipt(ctx, tx.Hash())
+ if h := receipt.BlockNumber.Uint64(); h != 1 {
+ t.Errorf("TX included in wrong block: %d", h)
+ }
+
+ // 4.
+ if err := sim.Fork(parent.Hash()); err != nil {
+ t.Errorf("forking: %v", err)
+ }
+
+ // 5.
+ sim.Commit()
+ if err := client.SendTransaction(ctx, tx); err != nil {
+ t.Fatalf("sending transaction: %v", err)
+ }
+ sim.Commit()
+ receipt, _ = client.TransactionReceipt(ctx, tx.Hash())
+ if h := receipt.BlockNumber.Uint64(); h != 2 {
+ t.Errorf("TX included in wrong block: %d", h)
+ }
+}
+
+func TestCommitReturnValue(t *testing.T) {
+ t.Parallel()
+ testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
+ sim := simTestBackend(testAddr)
+ defer sim.Close()
+
+ client := sim.Client()
+ ctx := context.Background()
+
+ // Test if Commit returns the correct block hash
+ h1 := sim.Commit()
+ cur, _ := client.HeaderByNumber(ctx, nil)
+ if h1 != cur.Hash() {
+ t.Error("Commit did not return the hash of the last block.")
+ }
+
+ // Create a block in the original chain (containing a transaction to force different block hashes)
+ head, _ := client.HeaderByNumber(ctx, nil) // Should be child's, good enough
+ gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
+ _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
+ tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey)
+ client.SendTransaction(ctx, tx)
+
+ h2 := sim.Commit()
+
+ // Create another block in the original chain
+ sim.Commit()
+
+ // Fork at the first bock
+ if err := sim.Fork(h1); err != nil {
+ t.Errorf("forking: %v", err)
+ }
+
+ // Test if Commit returns the correct block hash after the reorg
+ h2fork := sim.Commit()
+ if h2 == h2fork {
+ t.Error("The block in the fork and the original block are the same block!")
+ }
+ if header, err := client.HeaderByHash(ctx, h2fork); err != nil || header == nil {
+ t.Error("Could not retrieve the just created block (side-chain)")
+ }
+}
+
+// TestAdjustTimeAfterFork ensures that after a fork, AdjustTime uses the pending fork
+// block's parent rather than the canonical head's parent.
+func TestAdjustTimeAfterFork(t *testing.T) {
+ t.Parallel()
+ testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
+ sim := simTestBackend(testAddr)
+ defer sim.Close()
+
+ client := sim.Client()
+ ctx := context.Background()
+
+ sim.Commit() // h1
+ h1, _ := client.HeaderByNumber(ctx, nil)
+
+ sim.Commit() // h2
+ sim.Fork(h1.Hash())
+ sim.AdjustTime(1 * time.Second)
+ sim.Commit()
+
+ head, _ := client.HeaderByNumber(ctx, nil)
+ if head.Number.Uint64() == 2 && head.ParentHash != h1.Hash() {
+ t.Errorf("failed to build block on fork")
+ }
+}
diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go
index 0d3d5f5aa..29bd24364 100644
--- a/ethdb/dbtest/testsuite.go
+++ b/ethdb/dbtest/testsuite.go
@@ -273,9 +273,13 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
b.Put([]byte("5"), nil)
b.Delete([]byte("1"))
b.Put([]byte("6"), nil)
- b.Delete([]byte("3"))
+
+ b.Delete([]byte("3")) // delete then put
b.Put([]byte("3"), nil)
+ b.Put([]byte("7"), nil) // put then delete
+ b.Delete([]byte("7"))
+
if err := b.Write(); err != nil {
t.Fatal(err)
}
diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go
index f9f74322b..2a939f9a1 100644
--- a/ethdb/memorydb/memorydb.go
+++ b/ethdb/memorydb/memorydb.go
@@ -207,7 +207,7 @@ func (db *Database) Len() int {
// keyvalue is a key-value tuple tagged with a deletion field to allow creating
// memory-database write batches.
type keyvalue struct {
- key []byte
+ key string
value []byte
delete bool
}
@@ -222,14 +222,14 @@ type batch struct {
// Put inserts the given value into the batch for later committing.
func (b *batch) Put(key, value []byte) error {
- b.writes = append(b.writes, keyvalue{common.CopyBytes(key), common.CopyBytes(value), false})
+ b.writes = append(b.writes, keyvalue{string(key), common.CopyBytes(value), false})
b.size += len(key) + len(value)
return nil
}
// Delete inserts the a key removal into the batch for later committing.
func (b *batch) Delete(key []byte) error {
- b.writes = append(b.writes, keyvalue{common.CopyBytes(key), nil, true})
+ b.writes = append(b.writes, keyvalue{string(key), nil, true})
b.size += len(key)
return nil
}
@@ -249,10 +249,10 @@ func (b *batch) Write() error {
}
for _, keyvalue := range b.writes {
if keyvalue.delete {
- delete(b.db.db, string(keyvalue.key))
+ delete(b.db.db, keyvalue.key)
continue
}
- b.db.db[string(keyvalue.key)] = keyvalue.value
+ b.db.db[keyvalue.key] = keyvalue.value
}
return nil
}
@@ -267,12 +267,12 @@ func (b *batch) Reset() {
func (b *batch) Replay(w ethdb.KeyValueWriter) error {
for _, keyvalue := range b.writes {
if keyvalue.delete {
- if err := w.Delete(keyvalue.key); err != nil {
+ if err := w.Delete([]byte(keyvalue.key)); err != nil {
return err
}
continue
}
- if err := w.Put(keyvalue.key, keyvalue.value); err != nil {
+ if err := w.Put([]byte(keyvalue.key), keyvalue.value); err != nil {
return err
}
}
diff --git a/ethdb/memorydb/memorydb_test.go b/ethdb/memorydb/memorydb_test.go
index dba18ad30..51499c3b1 100644
--- a/ethdb/memorydb/memorydb_test.go
+++ b/ethdb/memorydb/memorydb_test.go
@@ -17,6 +17,7 @@
package memorydb
import (
+ "encoding/binary"
"testing"
"github.com/ethereum/go-ethereum/ethdb"
@@ -30,3 +31,20 @@ func TestMemoryDB(t *testing.T) {
})
})
}
+
+// BenchmarkBatchAllocs measures the time/allocs for storing 120 kB of data
+func BenchmarkBatchAllocs(b *testing.B) {
+ b.ReportAllocs()
+ var key = make([]byte, 20)
+ var val = make([]byte, 100)
+ // 120 * 1_000 -> 120_000 == 120kB
+ for i := 0; i < b.N; i++ {
+ batch := New().NewBatch()
+ for j := uint64(0); j < 1000; j++ {
+ binary.BigEndian.PutUint64(key, j)
+ binary.BigEndian.PutUint64(val, j)
+ batch.Put(key, val)
+ }
+ batch.Write()
+ }
+}
diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go
index 6d0ea9496..af4686cf5 100644
--- a/ethdb/pebble/pebble.go
+++ b/ethdb/pebble/pebble.go
@@ -25,7 +25,6 @@ import (
"sync/atomic"
"time"
- "github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble"
"github.com/cockroachdb/pebble/bloom"
"github.com/ethereum/go-ethereum/common"
@@ -131,7 +130,7 @@ func (l panicLogger) Errorf(format string, args ...interface{}) {
}
func (l panicLogger) Fatalf(format string, args ...interface{}) {
- panic(errors.Errorf("fatal: "+format, args...))
+ panic(fmt.Errorf("fatal: "+format, args...))
}
// New returns a wrapped pebble DB object. The namespace is the prefix that the
@@ -609,9 +608,12 @@ func (b *batch) Replay(w ethdb.KeyValueWriter) error {
// pebbleIterator is a wrapper of underlying iterator in storage engine.
// The purpose of this structure is to implement the missing APIs.
+//
+// The pebble iterator is not thread-safe.
type pebbleIterator struct {
- iter *pebble.Iterator
- moved bool
+ iter *pebble.Iterator
+ moved bool
+ released bool
}
// NewIterator creates a binary-alphabetical iterator over a subset
@@ -623,7 +625,7 @@ func (d *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
UpperBound: upperBound(prefix),
})
iter.First()
- return &pebbleIterator{iter: iter, moved: true}
+ return &pebbleIterator{iter: iter, moved: true, released: false}
}
// Next moves the iterator to the next key/value pair. It returns whether the
@@ -658,4 +660,9 @@ func (iter *pebbleIterator) Value() []byte {
// Release releases associated resources. Release should always succeed and can
// be called multiple times without causing error.
-func (iter *pebbleIterator) Release() { iter.iter.Close() }
+func (iter *pebbleIterator) Release() {
+ if !iter.released {
+ iter.iter.Close()
+ iter.released = true
+ }
+}
diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go
index 84a672280..75d0faac5 100644
--- a/ethstats/ethstats.go
+++ b/ethstats/ethstats.go
@@ -38,7 +38,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
@@ -486,7 +485,7 @@ func (s *Service) login(conn *connWrapper) error {
if info := infos.Protocols["eth"]; info != nil {
network = fmt.Sprintf("%d", info.(*ethproto.NodeInfo).Network)
} else {
- network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network)
+ return errors.New("no eth protocol available")
}
auth := &authMsg{
ID: s.node,
diff --git a/go.mod b/go.mod
index dff2bfde7..2db07860b 100644
--- a/go.mod
+++ b/go.mod
@@ -10,12 +10,12 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.18.45
github.com/aws/aws-sdk-go-v2/credentials v1.13.43
github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2
- github.com/btcsuite/btcd/btcec/v2 v2.2.0
+ github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/cespare/cp v0.1.0
github.com/cloudflare/cloudflare-go v0.79.0
- github.com/cockroachdb/errors v1.8.1
github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593
github.com/consensys/gnark-crypto v0.12.1
+ github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233
github.com/crate-crypto/go-kzg-4844 v0.7.0
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set/v2 v2.1.0
@@ -26,20 +26,19 @@ require (
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/fsnotify/fsnotify v1.6.0
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
- github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b
- github.com/go-stack/stack v1.8.1
+ github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46
github.com/gofrs/flock v0.8.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
- github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa
+ github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.3.0
- github.com/gorilla/websocket v1.4.2
+ github.com/gorilla/websocket v1.5.0
github.com/graph-gophers/graphql-go v1.3.0
github.com/hashicorp/go-bexpr v0.1.10
github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7
github.com/holiman/bloomfilter/v2 v2.0.3
- github.com/holiman/uint256 v1.2.3
+ github.com/holiman/uint256 v1.2.4
github.com/huin/goupnp v1.3.0
github.com/influxdata/influxdb-client-go/v2 v2.4.0
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
@@ -52,10 +51,10 @@ require (
github.com/mattn/go-isatty v0.0.17
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/olekukonko/tablewriter v0.0.5
- github.com/openrelayxyz/plugeth-utils v1.4.0
+ github.com/openrelayxyz/plugeth-utils v1.5.0
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7
- github.com/rs/cors v1.7.0
+ github.com/rs/cors v1.8.2
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
github.com/status-im/keycard-go v0.2.0
github.com/stretchr/testify v1.8.4
@@ -64,13 +63,13 @@ require (
github.com/tyler-smith/go-bip39 v1.1.0
github.com/urfave/cli/v2 v2.25.7
go.uber.org/automaxprocs v1.5.2
- golang.org/x/crypto v0.14.0
- golang.org/x/exp v0.0.0-20230905200255-921286631fa9
- golang.org/x/sync v0.3.0
- golang.org/x/sys v0.13.0
- golang.org/x/text v0.13.0
+ golang.org/x/crypto v0.17.0
+ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
+ golang.org/x/sync v0.5.0
+ golang.org/x/sys v0.15.0
+ golang.org/x/text v0.14.0
golang.org/x/time v0.3.0
- golang.org/x/tools v0.13.0
+ golang.org/x/tools v0.15.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -90,15 +89,15 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect
github.com/aws/smithy-go v1.15.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/bits-and-blooms/bitset v1.7.0 // indirect
+ github.com/bits-and-blooms/bitset v1.10.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/cockroachdb/errors v1.8.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect
github.com/cockroachdb/redact v1.0.8 // indirect
github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
- github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/deepmap/oapi-codegen v1.6.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
@@ -124,6 +123,7 @@ require (
github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect
+ github.com/openrelayxyz/cardinal-types v1.1.1 // indirect
github.com/opentracing/opentracing-go v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -132,13 +132,13 @@ require (
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
- github.com/rogpeppe/go-internal v1.9.0 // indirect
+ github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
- golang.org/x/mod v0.12.0 // indirect
- golang.org/x/net v0.17.0 // indirect
+ golang.org/x/mod v0.14.0 // indirect
+ golang.org/x/net v0.18.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
diff --git a/go.sum b/go.sum
index d8ae36c36..f313107cf 100644
--- a/go.sum
+++ b/go.sum
@@ -66,6 +66,7 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/aws/aws-sdk-go v1.44.36/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA=
github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM=
github.com/aws/aws-sdk-go-v2/config v1.18.45 h1:Aka9bI7n8ysuwPeFdm77nfbyHCAKQ3z9ghB3S/38zes=
@@ -97,10 +98,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo=
-github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
-github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
-github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
+github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88=
+github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
@@ -145,8 +146,8 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80 h1:DuBDHVjgGMPki7bAyh91+3cF1Vh34sAEdH8JQgbc2R0=
-github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80/go.mod h1:gzbVz57IDJgQ9rLQwfSk696JGWof8ftznEL9GoAv3NI=
+github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ=
+github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs=
github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA=
github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -201,8 +202,8 @@ github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILD
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
-github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b h1:vMT47RYsrftsHSTQhqXwC3BYflo38OLC3Y4LtXtLyU0=
-github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b/go.mod h1:CDncRYVRSDqwakm282WEkjfaAj1hxU/v5RXxk5nXOiI=
+github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE=
+github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
@@ -228,7 +229,6 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
@@ -299,8 +299,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
-github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -321,8 +321,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
@@ -341,8 +341,8 @@ github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZ
github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
-github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o=
-github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
+github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
+github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
@@ -350,6 +350,7 @@ github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:q
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
+github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k=
github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8=
@@ -418,7 +419,6 @@ github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvf
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
-github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -427,6 +427,7 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -484,8 +485,10 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/openrelayxyz/plugeth-utils v1.4.0 h1:tuvb8m49ZRG6A9ehLdbDPD6TQRKcQrk4Fcp/gXsX4NI=
-github.com/openrelayxyz/plugeth-utils v1.4.0/go.mod h1:VpDN61dxy64zGff05F0adujR5enD/JEdXBkTQ+PaIsQ=
+github.com/openrelayxyz/cardinal-types v1.1.1 h1:Lw6Lr/eiHYCnLi851rciCzw/1S3UytUX7kj5zh3QS/Y=
+github.com/openrelayxyz/cardinal-types v1.1.1/go.mod h1:8aaMg6i94V0hhWe3V6Fzc0RSggMx+/Kabsf5o7wMf/E=
+github.com/openrelayxyz/plugeth-utils v1.5.0 h1:4hzAvMKEo5uCnXy5cCHDc7OfXpwAavFNjr3M/ZfTlEA=
+github.com/openrelayxyz/plugeth-utils v1.5.0/go.mod h1:COwKAuTZIsCouCOrIDBhvHZqpbOO1Ojgdy5KTvL8mJg=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -527,18 +530,22 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c=
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
+github.com/pubnub/go-metrics-statsd v0.0.0-20170124014003-7da61f429d6b/go.mod h1:5UoZ1X6PWZWpPxwpR8qZ/qTN2BXIrrYTV9j+6TaQngA=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
-github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
+github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/savaki/cloudmetrics v0.0.0-20160314183336-c82bfea3c09e/go.mod h1:KzTM/+pS9NbNPoC7/EBZq77Za7His7hp1NJhA0DrMns=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
@@ -619,8 +626,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -631,8 +638,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -654,8 +661,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -694,9 +701,11 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220615171555-694bf12d69de/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
+golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -715,9 +724,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -771,7 +779,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -781,8 +790,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -795,8 +804,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -851,8 +860,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
-golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
+golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go
index a83d6bbd4..f91229d01 100644
--- a/graphql/graphql_test.go
+++ b/graphql/graphql_test.go
@@ -139,7 +139,7 @@ func TestGraphQLBlockSerialization(t *testing.T) {
// should return `estimateGas` as decimal
{
body: `{"query": "{block{ estimateGas(data:{}) }}"}`,
- want: `{"data":{"block":{"estimateGas":"0xcf08"}}}`,
+ want: `{"data":{"block":{"estimateGas":"0xd221"}}}`,
code: 200,
},
// should return `status` as decimal
diff --git a/interfaces.go b/interfaces.go
index eb9af6007..1892309ed 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -29,8 +29,6 @@ import (
// NotFound is returned by API methods if the requested item does not exist.
var NotFound = errors.New("not found")
-// TODO: move subscription to package event
-
// Subscription represents an event subscription where events are
// delivered on a data channel.
type Subscription interface {
@@ -201,6 +199,16 @@ type GasPricer interface {
SuggestGasPrice(ctx context.Context) (*big.Int, error)
}
+// GasPricer1559 provides access to the EIP-1559 gas price oracle.
+type GasPricer1559 interface {
+ SuggestGasTipCap(ctx context.Context) (*big.Int, error)
+}
+
+// FeeHistoryReader provides access to the fee history oracle.
+type FeeHistoryReader interface {
+ FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*FeeHistory, error)
+}
+
// FeeHistory provides recent fee market data that consumers can use to determine
// a reasonable maxPriorityFeePerGas value.
type FeeHistory struct {
@@ -241,3 +249,13 @@ type GasEstimator interface {
type PendingStateEventer interface {
SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error)
}
+
+// BlockNumberReader provides access to the current block number.
+type BlockNumberReader interface {
+ BlockNumber(ctx context.Context) (uint64, error)
+}
+
+// ChainIDReader provides access to the chain ID.
+type ChainIDReader interface {
+ ChainID(ctx context.Context) (*big.Int, error)
+}
diff --git a/internal/build/gotool.go b/internal/build/gotool.go
index 32ca20e86..2a4746041 100644
--- a/internal/build/gotool.go
+++ b/internal/build/gotool.go
@@ -144,7 +144,6 @@ func Version(csdb *ChecksumDB, version string) (string, error) {
continue
}
if parts[0] == version {
- log.Printf("Found version %q", parts[1])
return parts[1], nil
}
}
diff --git a/internal/build/util.go b/internal/build/util.go
index 5c77b236d..b41014a16 100644
--- a/internal/build/util.go
+++ b/internal/build/util.go
@@ -68,6 +68,27 @@ func MustRunCommand(cmd string, args ...string) {
MustRun(exec.Command(cmd, args...))
}
+// MustRunCommandWithOutput runs the given command, and ensures that some output will be
+// printed while it runs. This is useful for CI builds where the process will be stopped
+// when there is no output.
+func MustRunCommandWithOutput(cmd string, args ...string) {
+ interval := time.NewTicker(time.Minute)
+ done := make(chan struct{})
+ defer interval.Stop()
+ defer close(done)
+ go func() {
+ for {
+ select {
+ case <-interval.C:
+ fmt.Printf("Waiting for command %q\n", cmd)
+ case <-done:
+ return
+ }
+ }
+ }()
+ MustRun(exec.Command(cmd, args...))
+}
+
var warnedAboutGit bool
// RunGit runs a git subcommand and returns its output.
diff --git a/internal/debug/api.go b/internal/debug/api.go
index 42d0fa15e..482989e0d 100644
--- a/internal/debug/api.go
+++ b/internal/debug/api.go
@@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/hashicorp/go-bexpr"
+ "golang.org/x/exp/slog"
)
// Handler is the global debugging handler.
@@ -56,7 +57,7 @@ type HandlerT struct {
// Verbosity sets the log verbosity ceiling. The verbosity of individual packages
// and source files can be raised using Vmodule.
func (*HandlerT) Verbosity(level int) {
- glogger.Verbosity(log.Lvl(level))
+ glogger.Verbosity(slog.Level(level))
}
// Vmodule sets the log verbosity pattern. See package log for details on the
@@ -65,12 +66,6 @@ func (*HandlerT) Vmodule(pattern string) error {
return glogger.Vmodule(pattern)
}
-// BacktraceAt sets the log backtrace location. See package log for details on
-// the pattern syntax.
-func (*HandlerT) BacktraceAt(location string) error {
- return glogger.BacktraceAt(location)
-}
-
// MemStats returns detailed runtime memory statistics.
func (*HandlerT) MemStats() *runtime.MemStats {
s := new(runtime.MemStats)
diff --git a/internal/debug/flags.go b/internal/debug/flags.go
index 4f0f5fe86..23e4745e8 100644
--- a/internal/debug/flags.go
+++ b/internal/debug/flags.go
@@ -34,6 +34,7 @@ import (
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
+ "golang.org/x/exp/slog"
"gopkg.in/natefinch/lumberjack.v2"
)
@@ -75,17 +76,6 @@ var (
Usage: "Write logs to a file",
Category: flags.LoggingCategory,
}
- backtraceAtFlag = &cli.StringFlag{
- Name: "log.backtrace",
- Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")",
- Value: "",
- Category: flags.LoggingCategory,
- }
- debugFlag = &cli.BoolFlag{
- Name: "log.debug",
- Usage: "Prepends log messages with call-site location (file and line number)",
- Category: flags.LoggingCategory,
- }
logRotateFlag = &cli.BoolFlag{
Name: "log.rotate",
Usage: "Enables log file rotation",
@@ -160,8 +150,6 @@ var Flags = []cli.Flag{
verbosityFlag,
logVmoduleFlag,
vmoduleFlag,
- backtraceAtFlag,
- debugFlag,
logjsonFlag,
logFormatFlag,
logFileFlag,
@@ -180,45 +168,34 @@ var Flags = []cli.Flag{
}
var (
- glogger *log.GlogHandler
- logOutputStream log.Handler
+ glogger *log.GlogHandler
+ logOutputFile io.WriteCloser
+ defaultTerminalHandler *log.TerminalHandler
)
func init() {
- glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
+ defaultTerminalHandler = log.NewTerminalHandler(os.Stderr, false)
+ glogger = log.NewGlogHandler(defaultTerminalHandler)
glogger.Verbosity(log.LvlInfo)
- log.Root().SetHandler(glogger)
+ log.SetDefault(log.NewLogger(glogger))
+}
+
+func ResetLogging() {
+ if defaultTerminalHandler != nil {
+ defaultTerminalHandler.ResetFieldPadding()
+ }
}
// Setup initializes profiling and logging based on the CLI flags.
// It should be called as early as possible in the program.
func Setup(ctx *cli.Context) error {
var (
- logfmt log.Format
- output = io.Writer(os.Stderr)
- logFmtFlag = ctx.String(logFormatFlag.Name)
+ handler slog.Handler
+ terminalOutput = io.Writer(os.Stderr)
+ output io.Writer
+ logFmtFlag = ctx.String(logFormatFlag.Name)
)
- switch {
- case ctx.Bool(logjsonFlag.Name):
- // Retain backwards compatibility with `--log.json` flag if `--log.format` not set
- defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead")
- logfmt = log.JSONFormat()
- case logFmtFlag == "json":
- logfmt = log.JSONFormat()
- case logFmtFlag == "logfmt":
- logfmt = log.LogfmtFormat()
- case logFmtFlag == "", logFmtFlag == "terminal":
- useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
- if useColor {
- output = colorable.NewColorableStderr()
- }
- logfmt = log.TerminalFormat(useColor)
- default:
- // Unknown log format specified
- return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name))
- }
var (
- ostream = log.StreamHandler(output, logfmt)
logFile = ctx.String(logFileFlag.Name)
rotation = ctx.Bool(logRotateFlag.Name)
)
@@ -241,27 +218,55 @@ func Setup(ctx *cli.Context) error {
} else {
context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log"))
}
- lumberWriter := &lumberjack.Logger{
+ logOutputFile = &lumberjack.Logger{
Filename: logFile,
MaxSize: ctx.Int(logMaxSizeMBsFlag.Name),
MaxBackups: ctx.Int(logMaxBackupsFlag.Name),
MaxAge: ctx.Int(logMaxAgeFlag.Name),
Compress: ctx.Bool(logCompressFlag.Name),
}
- ostream = log.StreamHandler(io.MultiWriter(output, lumberWriter), logfmt)
+ output = io.MultiWriter(terminalOutput, logOutputFile)
} else if logFile != "" {
- f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
- if err != nil {
+ var err error
+ if logOutputFile, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err != nil {
return err
}
- ostream = log.StreamHandler(io.MultiWriter(output, f), logfmt)
+ output = io.MultiWriter(logOutputFile, terminalOutput)
context = append(context, "location", logFile)
+ } else {
+ output = terminalOutput
}
- glogger.SetHandler(ostream)
+
+ switch {
+ case ctx.Bool(logjsonFlag.Name):
+ // Retain backwards compatibility with `--log.json` flag if `--log.format` not set
+ defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead")
+ handler = log.JSONHandler(output)
+ case logFmtFlag == "json":
+ handler = log.JSONHandler(output)
+ case logFmtFlag == "logfmt":
+ handler = log.LogfmtHandler(output)
+ case logFmtFlag == "", logFmtFlag == "terminal":
+ useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
+ if useColor {
+ terminalOutput = colorable.NewColorableStderr()
+ if logOutputFile != nil {
+ output = io.MultiWriter(logOutputFile, terminalOutput)
+ } else {
+ output = terminalOutput
+ }
+ }
+ handler = log.NewTerminalHandler(output, useColor)
+ default:
+ // Unknown log format specified
+ return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name))
+ }
+
+ glogger = log.NewGlogHandler(handler)
// logging
- verbosity := ctx.Int(verbosityFlag.Name)
- glogger.Verbosity(log.Lvl(verbosity))
+ verbosity := log.FromLegacyLevel(ctx.Int(verbosityFlag.Name))
+ glogger.Verbosity(verbosity)
vmodule := ctx.String(logVmoduleFlag.Name)
if vmodule == "" {
// Retain backwards compatibility with `--vmodule` flag if `--log.vmodule` not set
@@ -272,16 +277,7 @@ func Setup(ctx *cli.Context) error {
}
glogger.Vmodule(vmodule)
- debug := ctx.Bool(debugFlag.Name)
- if ctx.IsSet(debugFlag.Name) {
- debug = ctx.Bool(debugFlag.Name)
- }
- log.PrintOrigins(debug)
-
- backtrace := ctx.String(backtraceAtFlag.Name)
- glogger.BacktraceAt(backtrace)
-
- log.Root().SetHandler(glogger)
+ log.SetDefault(log.NewLogger(glogger))
// profiling, tracing
runtime.MemProfileRate = memprofilerateFlag.Value
@@ -341,8 +337,8 @@ func StartPProf(address string, withMetrics bool) {
func Exit() {
Handler.StopCPUProfile()
Handler.StopGoTrace()
- if closer, ok := logOutputStream.(io.Closer); ok {
- closer.Close()
+ if logOutputFile != nil {
+ logOutputFile.Close()
}
}
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 38a792412..c0b28e4b6 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -40,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth/gasestimator"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
@@ -50,6 +51,10 @@ import (
"github.com/tyler-smith/go-bip39"
)
+// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is
+// allowed to produce in order to speed up calculations.
+const estimateGasErrorRatio = 0.015
+
// EthereumAPI provides an API to access Ethereum related information.
type EthereumAPI struct {
b Backend
@@ -1083,7 +1088,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
if blockOverrides != nil {
blockOverrides.Apply(&blockCtx)
}
- evm, vmError := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
+ evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
@@ -1095,7 +1100,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
// Execute the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
result, err := core.ApplyMessage(evm, msg, gp)
- if err := vmError(); err != nil {
+ if err := state.Error(); err != nil {
return nil, err
}
@@ -1120,15 +1125,16 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap)
}
-func newRevertError(result *core.ExecutionResult) *revertError {
- reason, errUnpack := abi.UnpackRevert(result.Revert())
- err := errors.New("execution reverted")
+func newRevertError(revert []byte) *revertError {
+ err := vm.ErrExecutionReverted
+
+ reason, errUnpack := abi.UnpackRevert(revert)
if errUnpack == nil {
- err = fmt.Errorf("execution reverted: %v", reason)
+ err = fmt.Errorf("%w: %v", vm.ErrExecutionReverted, reason)
}
return &revertError{
error: err,
- reason: hexutil.Encode(result.Revert()),
+ reason: hexutil.Encode(revert),
}
}
@@ -1167,147 +1173,45 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO
}
// If the result contains a revert reason, try to unpack and return it.
if len(result.Revert()) > 0 {
- return nil, newRevertError(result)
+ return nil, newRevertError(result.Revert())
}
return result.Return(), result.Err
}
-// executeEstimate is a helper that executes the transaction under a given gas limit and returns
-// true if the transaction fails for a reason that might be related to not enough gas. A non-nil
-// error means execution failed due to reasons unrelated to the gas limit.
-func executeEstimate(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, gasCap uint64, gasLimit uint64) (bool, *core.ExecutionResult, error) {
- args.Gas = (*hexutil.Uint64)(&gasLimit)
- result, err := doCall(ctx, b, args, state, header, nil, nil, 0, gasCap)
- if err != nil {
- if errors.Is(err, core.ErrIntrinsicGas) {
- return true, nil, nil // Special case, raise gas limit
- }
- return true, nil, err // Bail out
- }
- return result.Failed(), result, nil
-}
-
// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run
// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if
// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil &
// non-zero) and `gasCap` (if non-zero).
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) {
- // Binary search the gas limit, as it may need to be higher than the amount used
- var (
- lo uint64 // lowest-known gas limit where tx execution fails
- hi uint64 // lowest-known gas limit where tx execution succeeds
- )
- // Use zero address if sender unspecified.
- if args.From == nil {
- args.From = new(common.Address)
- }
- // Determine the highest gas limit can be used during the estimation.
- if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
- hi = uint64(*args.Gas)
- } else {
- // Retrieve the block to act as the gas ceiling
- block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
- if err != nil {
- return 0, err
- }
- if block == nil {
- return 0, errors.New("block not found")
- }
- hi = block.GasLimit()
- }
- // Normalize the max fee per gas the call is willing to spend.
- var feeCap *big.Int
- if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
- return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
- } else if args.GasPrice != nil {
- feeCap = args.GasPrice.ToInt()
- } else if args.MaxFeePerGas != nil {
- feeCap = args.MaxFeePerGas.ToInt()
- } else {
- feeCap = common.Big0
- }
-
+ // Retrieve the base state and mutate it with any overrides
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return 0, err
}
- if err := overrides.Apply(state); err != nil {
+ if err = overrides.Apply(state); err != nil {
return 0, err
}
-
- // Recap the highest gas limit with account's available balance.
- if feeCap.BitLen() != 0 {
- balance := state.GetBalance(*args.From) // from can't be nil
- available := new(big.Int).Set(balance)
- if args.Value != nil {
- if args.Value.ToInt().Cmp(available) >= 0 {
- return 0, core.ErrInsufficientFundsForTransfer
- }
- available.Sub(available, args.Value.ToInt())
- }
- allowance := new(big.Int).Div(available, feeCap)
-
- // If the allowance is larger than maximum uint64, skip checking
- if allowance.IsUint64() && hi > allowance.Uint64() {
- transfer := args.Value
- if transfer == nil {
- transfer = new(hexutil.Big)
- }
- log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
- "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance)
- hi = allowance.Uint64()
- }
+ // Construct the gas estimator option from the user input
+ opts := &gasestimator.Options{
+ Config: b.ChainConfig(),
+ Chain: NewChainContext(ctx, b),
+ Header: header,
+ State: state,
+ ErrorRatio: estimateGasErrorRatio,
}
- // Recap the highest gas allowance with specified gascap.
- if gasCap != 0 && hi > gasCap {
- log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
- hi = gasCap
- }
-
- // We first execute the transaction at the highest allowable gas limit, since if this fails we
- // can return error immediately.
- failed, result, err := executeEstimate(ctx, b, args, state.Copy(), header, gasCap, hi)
+ // Run the gas estimation andwrap any revertals into a custom return
+ call, err := args.ToMessage(gasCap, header.BaseFee)
if err != nil {
return 0, err
}
- if failed {
- if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
- if len(result.Revert()) > 0 {
- return 0, newRevertError(result)
- }
- return 0, result.Err
+ estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
+ if err != nil {
+ if len(revert) > 0 {
+ return 0, newRevertError(revert)
}
- return 0, fmt.Errorf("gas required exceeds allowance (%d)", hi)
+ return 0, err
}
- // For almost any transaction, the gas consumed by the unconstrained execution above
- // lower-bounds the gas limit required for it to succeed. One exception is those txs that
- // explicitly check gas remaining in order to successfully execute within a given limit, but we
- // probably don't want to return a lowest possible gas limit for these cases anyway.
- lo = result.UsedGas - 1
-
- // Binary search for the smallest gas limit that allows the tx to execute successfully.
- for lo+1 < hi {
- mid := (hi + lo) / 2
- if mid > lo*2 {
- // Most txs don't need much higher gas limit than their gas used, and most txs don't
- // require near the full block limit of gas, so the selection of where to bisect the
- // range here is skewed to favor the low side.
- mid = lo * 2
- }
- failed, _, err = executeEstimate(ctx, b, args, state.Copy(), header, gasCap, mid)
- if err != nil {
- // This should not happen under normal conditions since if we make it this far the
- // transaction had run without error at least once before.
- log.Error("execution error in estimate gas", "err", err)
- return 0, err
- }
- if failed {
- lo = mid
- } else {
- hi = mid
- }
- }
- return hexutil.Uint64(hi), nil
+ return hexutil.Uint64(estimate), nil
}
// EstimateGas returns the lowest possible gas limit that allows the transaction to run
@@ -1640,7 +1544,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
// Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, NoBaseFee: true}
- vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil)
+ vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil)
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit))
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go
index a67bd1203..c2490ac70 100644
--- a/internal/ethapi/api_test.go
+++ b/internal/ethapi/api_test.go
@@ -536,8 +536,7 @@ func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
}
return big.NewInt(1)
}
-func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) (*vm.EVM, func() error) {
- vmError := func() error { return nil }
+func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) *vm.EVM {
if vmConfig == nil {
vmConfig = b.chain.GetVMConfig()
}
@@ -546,7 +545,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state
if blockContext != nil {
context = *blockContext
}
- return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig), vmError
+ return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig)
}
func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
panic("implement me")
@@ -736,7 +735,7 @@ func TestEstimateGas(t *testing.T) {
t.Errorf("test %d: want no error, have %v", i, err)
continue
}
- if uint64(result) != tc.want {
+ if float64(result) > float64(tc.want)*(1+estimateGasErrorRatio) {
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want)
}
}
@@ -911,18 +910,18 @@ func TestCall(t *testing.T) {
}
}
-type Account struct {
+type account struct {
key *ecdsa.PrivateKey
addr common.Address
}
-func newAccounts(n int) (accounts []Account) {
+func newAccounts(n int) (accounts []account) {
for i := 0; i < n; i++ {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
- accounts = append(accounts, Account{key: key, addr: addr})
+ accounts = append(accounts, account{key: key, addr: addr})
}
- slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) })
+ slices.SortFunc(accounts, func(a, b account) int { return a.addr.Cmp(b.addr) })
return accounts
}
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 458fb811e..50f338f5c 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -68,7 +68,7 @@ type Backend interface {
PendingBlockAndReceipts() (*types.Block, types.Receipts)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
GetTd(ctx context.Context, hash common.Hash) *big.Int
- GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error)
+ GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go
index e4cf81a3f..aaf2c05d8 100644
--- a/internal/ethapi/transaction_args.go
+++ b/internal/ethapi/transaction_args.go
@@ -137,20 +137,35 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
- // If the tx has completely specified a fee mechanism, no default is needed. This allows users
- // who are not yet synced past London to get defaults for other tx values. See
- // https://github.com/ethereum/go-ethereum/pull/23274 for more information.
+ // If the tx has completely specified a fee mechanism, no default is needed.
+ // This allows users who are not yet synced past London to get defaults for
+ // other tx values. See https://github.com/ethereum/go-ethereum/pull/23274
+ // for more information.
eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil
- if (args.GasPrice != nil && !eip1559ParamsSet) || (args.GasPrice == nil && eip1559ParamsSet) {
- // Sanity check the EIP-1559 fee parameters if present.
- if args.GasPrice == nil && args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
+
+ // Sanity check the EIP-1559 fee parameters if present.
+ if args.GasPrice == nil && eip1559ParamsSet {
+ if args.MaxFeePerGas.ToInt().Sign() == 0 {
+ return errors.New("maxFeePerGas must be non-zero")
+ }
+ if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
}
- return nil
+ return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas
}
- // Now attempt to fill in default value depending on whether London is active or not.
+ // Sanity check the non-EIP-1559 fee parameters.
head := b.CurrentHeader()
- if b.ChainConfig().IsLondon(head.Number) {
+ isLondon := b.ChainConfig().IsLondon(head.Number)
+ if args.GasPrice != nil && !eip1559ParamsSet {
+ // Zero gas-price is not allowed after London fork
+ if args.GasPrice.ToInt().Sign() == 0 && isLondon {
+ return errors.New("gasPrice must be non-zero after london fork")
+ }
+ return nil // No need to set anything, user already set GasPrice
+ }
+
+ // Now attempt to fill in default value depending on whether London is active or not.
+ if isLondon {
// London is active, set maxPriorityFeePerGas and maxFeePerGas.
if err := args.setLondonFeeDefaults(ctx, head, b); err != nil {
return err
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
index 9161d5e68..ab7c2f70e 100644
--- a/internal/ethapi/transaction_args_test.go
+++ b/internal/ethapi/transaction_args_test.go
@@ -52,6 +52,7 @@ func TestSetFeeDefaults(t *testing.T) {
var (
b = newBackendMock()
+ zero = (*hexutil.Big)(big.NewInt(0))
fortytwo = (*hexutil.Big)(big.NewInt(42))
maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt()))
al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}}
@@ -66,6 +67,13 @@ func TestSetFeeDefaults(t *testing.T) {
&TransactionArgs{GasPrice: fortytwo},
nil,
},
+ {
+ "legacy tx pre-London with zero price",
+ false,
+ &TransactionArgs{GasPrice: zero},
+ &TransactionArgs{GasPrice: zero},
+ nil,
+ },
{
"legacy tx post-London, explicit gas price",
true,
@@ -73,6 +81,13 @@ func TestSetFeeDefaults(t *testing.T) {
&TransactionArgs{GasPrice: fortytwo},
nil,
},
+ {
+ "legacy tx post-London with zero price",
+ true,
+ &TransactionArgs{GasPrice: zero},
+ nil,
+ errors.New("gasPrice must be non-zero after london fork"),
+ },
// Access list txs
{
@@ -161,6 +176,13 @@ func TestSetFeeDefaults(t *testing.T) {
nil,
errors.New("maxFeePerGas (0x7) < maxPriorityFeePerGas (0x2a)"),
},
+ {
+ "dynamic fee tx post-London, explicit gas price",
+ true,
+ &TransactionArgs{MaxFeePerGas: zero, MaxPriorityFeePerGas: zero},
+ nil,
+ errors.New("maxFeePerGas must be non-zero"),
+ },
// Misc
{
@@ -305,8 +327,8 @@ func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number
return nil, nil
}
func (b *backendMock) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil }
-func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) {
- return nil, nil
+func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM {
+ return nil
}
func (b *backendMock) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { return nil }
func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
diff --git a/internal/flags/categories.go b/internal/flags/categories.go
index 487684d98..3ff076792 100644
--- a/internal/flags/categories.go
+++ b/internal/flags/categories.go
@@ -35,6 +35,7 @@ const (
LoggingCategory = "LOGGING AND DEBUGGING"
MetricsCategory = "METRICS AND STATS"
MiscCategory = "MISC"
+ TestingCategory = "TESTING"
DeprecatedCategory = "ALIASED (deprecated)"
)
diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go
index d4b8e373c..369a931e8 100644
--- a/internal/flags/helpers.go
+++ b/internal/flags/helpers.go
@@ -41,7 +41,7 @@ func NewApp(usage string) *cli.App {
app.EnableBashCompletion = true
app.Version = params.VersionWithCommit(git.Commit, git.Date)
app.Usage = usage
- app.Copyright = "Copyright 2013-2023 The go-ethereum Authors"
+ app.Copyright = "Copyright 2013-2024 The go-ethereum Authors"
app.Before = func(ctx *cli.Context) error {
MigrateGlobalFlags(ctx)
return nil
@@ -105,7 +105,7 @@ func MigrateGlobalFlags(ctx *cli.Context) {
func doMigrateFlags(ctx *cli.Context) {
// Figure out if there are any aliases of commands. If there are, we want
// to ignore them when iterating over the flags.
- var aliases = make(map[string]bool)
+ aliases := make(map[string]bool)
for _, fl := range ctx.Command.Flags {
for _, alias := range fl.Names()[1:] {
aliases[alias] = true
@@ -239,15 +239,24 @@ func AutoEnvVars(flags []cli.Flag, prefix string) {
case *cli.StringFlag:
flag.EnvVars = append(flag.EnvVars, envvar)
+ case *cli.StringSliceFlag:
+ flag.EnvVars = append(flag.EnvVars, envvar)
+
case *cli.BoolFlag:
flag.EnvVars = append(flag.EnvVars, envvar)
case *cli.IntFlag:
flag.EnvVars = append(flag.EnvVars, envvar)
+ case *cli.Int64Flag:
+ flag.EnvVars = append(flag.EnvVars, envvar)
+
case *cli.Uint64Flag:
flag.EnvVars = append(flag.EnvVars, envvar)
+ case *cli.Float64Flag:
+ flag.EnvVars = append(flag.EnvVars, envvar)
+
case *cli.DurationFlag:
flag.EnvVars = append(flag.EnvVars, envvar)
diff --git a/internal/jsre/deps/web3.js b/internal/jsre/deps/web3.js
index 7a09fddab..f23c65584 100644
--- a/internal/jsre/deps/web3.js
+++ b/internal/jsre/deps/web3.js
@@ -1033,7 +1033,7 @@ var formatOutputInt = function (param) {
*
* @method formatOutputUInt
* @param {SolidityParam}
- * @returns {BigNumeber} right-aligned output bytes formatted to uint
+ * @returns {BigNumber} right-aligned output bytes formatted to uint
*/
var formatOutputUInt = function (param) {
var value = param.staticPart() || "0";
diff --git a/internal/testlog/testlog.go b/internal/testlog/testlog.go
index 684339f16..037b7ee9c 100644
--- a/internal/testlog/testlog.go
+++ b/internal/testlog/testlog.go
@@ -18,26 +18,19 @@
package testlog
import (
+ "bytes"
+ "context"
+ "fmt"
"sync"
"testing"
"github.com/ethereum/go-ethereum/log"
+ "golang.org/x/exp/slog"
)
-// Handler returns a log handler which logs to the unit test log of t.
-func Handler(t *testing.T, level log.Lvl) log.Handler {
- return log.LvlFilterHandler(level, &handler{t, log.TerminalFormat(false)})
-}
-
-type handler struct {
- t *testing.T
- fmt log.Format
-}
-
-func (h *handler) Log(r *log.Record) error {
- h.t.Logf("%s", h.fmt.Format(r))
- return nil
-}
+const (
+ termTimeFormat = "01-02|15:04:05.000"
+)
// logger implements log.Logger such that all output goes to the unit test log via
// t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test
@@ -51,25 +44,64 @@ type logger struct {
}
type bufHandler struct {
- buf []*log.Record
- fmt log.Format
+ buf []slog.Record
+ attrs []slog.Attr
+ level slog.Level
}
-func (h *bufHandler) Log(r *log.Record) error {
+func (h *bufHandler) Handle(_ context.Context, r slog.Record) error {
h.buf = append(h.buf, r)
return nil
}
-// Logger returns a logger which logs to the unit test log of t.
-func Logger(t *testing.T, level log.Lvl) log.Logger {
- l := &logger{
- t: t,
- l: log.New(),
- mu: new(sync.Mutex),
- h: &bufHandler{fmt: log.TerminalFormat(false)},
+func (h *bufHandler) Enabled(_ context.Context, lvl slog.Level) bool {
+ return lvl <= h.level
+}
+
+func (h *bufHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ records := make([]slog.Record, len(h.buf))
+ copy(records[:], h.buf[:])
+ return &bufHandler{
+ records,
+ append(h.attrs, attrs...),
+ h.level,
}
- l.l.SetHandler(log.LvlFilterHandler(level, l.h))
- return l
+}
+
+func (h *bufHandler) WithGroup(_ string) slog.Handler {
+ panic("not implemented")
+}
+
+// Logger returns a logger which logs to the unit test log of t.
+func Logger(t *testing.T, level slog.Level) log.Logger {
+ handler := bufHandler{
+ []slog.Record{},
+ []slog.Attr{},
+ level,
+ }
+ return &logger{
+ t: t,
+ l: log.NewLogger(&handler),
+ mu: new(sync.Mutex),
+ h: &handler,
+ }
+}
+
+// LoggerWithHandler returns
+func LoggerWithHandler(t *testing.T, handler slog.Handler) log.Logger {
+ var bh bufHandler
+ return &logger{
+ t: t,
+ l: log.NewLogger(handler),
+ mu: new(sync.Mutex),
+ h: &bh,
+ }
+}
+
+func (l *logger) Write(level slog.Level, msg string, ctx ...interface{}) {}
+
+func (l *logger) Enabled(ctx context.Context, level slog.Level) bool {
+ return l.l.Enabled(ctx, level)
}
func (l *logger) Trace(msg string, ctx ...interface{}) {
@@ -80,6 +112,14 @@ func (l *logger) Trace(msg string, ctx ...interface{}) {
l.flush()
}
+func (l *logger) Log(level slog.Level, msg string, ctx ...interface{}) {
+ l.t.Helper()
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.l.Log(level, msg, ctx...)
+ l.flush()
+}
+
func (l *logger) Debug(msg string, ctx ...interface{}) {
l.t.Helper()
l.mu.Lock()
@@ -120,23 +160,44 @@ func (l *logger) Crit(msg string, ctx ...interface{}) {
l.flush()
}
+func (l *logger) With(ctx ...interface{}) log.Logger {
+ return &logger{l.t, l.l.With(ctx...), l.mu, l.h}
+}
+
func (l *logger) New(ctx ...interface{}) log.Logger {
- return &logger{l.t, l.l.New(ctx...), l.mu, l.h}
+ return l.With(ctx...)
}
-func (l *logger) GetHandler() log.Handler {
- return l.l.GetHandler()
-}
+// terminalFormat formats a message similarly to the NewTerminalHandler in the log package.
+// The difference is that terminalFormat does not escape messages/attributes and does not pad attributes.
+func (h *bufHandler) terminalFormat(r slog.Record) string {
+ buf := &bytes.Buffer{}
+ lvl := log.LevelAlignedString(r.Level)
+ attrs := []slog.Attr{}
+ r.Attrs(func(attr slog.Attr) bool {
+ attrs = append(attrs, attr)
+ return true
+ })
-func (l *logger) SetHandler(h log.Handler) {
- l.l.SetHandler(h)
+ attrs = append(h.attrs, attrs...)
+
+ fmt.Fprintf(buf, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Message)
+ if length := len(r.Message); length < 40 {
+ buf.Write(bytes.Repeat([]byte{' '}, 40-length))
+ }
+
+ for _, attr := range attrs {
+ fmt.Fprintf(buf, " %s=%s", attr.Key, string(log.FormatSlogValue(attr.Value, nil)))
+ }
+ buf.WriteByte('\n')
+ return buf.String()
}
// flush writes all buffered messages and clears the buffer.
func (l *logger) flush() {
l.t.Helper()
for _, r := range l.h.buf {
- l.t.Logf("%s", l.h.fmt.Format(r))
+ l.t.Logf("%s", l.h.terminalFormat(r))
}
l.h.buf = nil
}
diff --git a/les/api.go b/les/api.go
deleted file mode 100644
index e8490f7b0..000000000
--- a/les/api.go
+++ /dev/null
@@ -1,349 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "errors"
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- vfs "github.com/ethereum/go-ethereum/les/vflux/server"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-var errUnknownBenchmarkType = errors.New("unknown benchmark type")
-
-// LightServerAPI provides an API to access the LES light server.
-type LightServerAPI struct {
- server *LesServer
- defaultPosFactors, defaultNegFactors vfs.PriceFactors
-}
-
-// NewLightServerAPI creates a new LES light server API.
-func NewLightServerAPI(server *LesServer) *LightServerAPI {
- return &LightServerAPI{
- server: server,
- defaultPosFactors: defaultPosFactors,
- defaultNegFactors: defaultNegFactors,
- }
-}
-
-// parseNode parses either an enode address a raw hex node id
-func parseNode(node string) (enode.ID, error) {
- if id, err := enode.ParseID(node); err == nil {
- return id, nil
- }
- if node, err := enode.Parse(enode.ValidSchemes, node); err == nil {
- return node.ID(), nil
- } else {
- return enode.ID{}, err
- }
-}
-
-// ServerInfo returns global server parameters
-func (api *LightServerAPI) ServerInfo() map[string]interface{} {
- res := make(map[string]interface{})
- res["minimumCapacity"] = api.server.minCapacity
- res["maximumCapacity"] = api.server.maxCapacity
- _, res["totalCapacity"] = api.server.clientPool.Limits()
- _, res["totalConnectedCapacity"] = api.server.clientPool.Active()
- res["priorityConnectedCapacity"] = 0 //TODO connect when token sale module is added
- return res
-}
-
-// ClientInfo returns information about clients listed in the ids list or matching the given tags
-func (api *LightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]interface{} {
- var ids []enode.ID
- for _, node := range nodes {
- if id, err := parseNode(node); err == nil {
- ids = append(ids, id)
- }
- }
-
- res := make(map[enode.ID]map[string]interface{})
- if len(ids) == 0 {
- ids = api.server.peers.ids()
- }
- for _, id := range ids {
- if peer := api.server.peers.peer(id); peer != nil {
- res[id] = api.clientInfo(peer, peer.balance)
- } else {
- api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) {
- res[id] = api.clientInfo(nil, balance)
- })
- }
- }
- return res
-}
-
-// PriorityClientInfo returns information about clients with a positive balance
-// in the given ID range (stop excluded). If stop is null then the iterator stops
-// only at the end of the ID space. MaxCount limits the number of results returned.
-// If maxCount limit is applied but there are more potential results then the ID
-// of the next potential result is included in the map with an empty structure
-// assigned to it.
-func (api *LightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int) map[enode.ID]map[string]interface{} {
- res := make(map[enode.ID]map[string]interface{})
- ids := api.server.clientPool.GetPosBalanceIDs(start, stop, maxCount+1)
- if len(ids) > maxCount {
- res[ids[maxCount]] = make(map[string]interface{})
- ids = ids[:maxCount]
- }
- for _, id := range ids {
- if peer := api.server.peers.peer(id); peer != nil {
- res[id] = api.clientInfo(peer, peer.balance)
- } else {
- api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) {
- res[id] = api.clientInfo(nil, balance)
- })
- }
- }
- return res
-}
-
-// clientInfo creates a client info data structure
-func (api *LightServerAPI) clientInfo(peer *clientPeer, balance vfs.ReadOnlyBalance) map[string]interface{} {
- info := make(map[string]interface{})
- pb, nb := balance.GetBalance()
- info["isConnected"] = peer != nil
- info["pricing/balance"] = pb
- info["priority"] = pb != 0
- // cb := api.server.clientPool.ndb.getCurrencyBalance(id)
- // info["pricing/currency"] = cb.amount
- if peer != nil {
- info["connectionTime"] = float64(mclock.Now()-peer.connectedAt) / float64(time.Second)
- info["capacity"] = peer.getCapacity()
- info["pricing/negBalance"] = nb
- }
- return info
-}
-
-// setParams either sets the given parameters for a single connected client (if specified)
-// or the default parameters applicable to clients connected in the future
-func (api *LightServerAPI) setParams(params map[string]interface{}, client *clientPeer, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) {
- defParams := client == nil
- for name, value := range params {
- errValue := func() error {
- return fmt.Errorf("invalid value for parameter '%s'", name)
- }
- setFactor := func(v *float64) {
- if val, ok := value.(float64); ok && val >= 0 {
- *v = val / float64(time.Second)
- updateFactors = true
- } else {
- err = errValue()
- }
- }
-
- switch {
- case name == "pricing/timeFactor":
- setFactor(&posFactors.TimeFactor)
- case name == "pricing/capacityFactor":
- setFactor(&posFactors.CapacityFactor)
- case name == "pricing/requestCostFactor":
- setFactor(&posFactors.RequestFactor)
- case name == "pricing/negative/timeFactor":
- setFactor(&negFactors.TimeFactor)
- case name == "pricing/negative/capacityFactor":
- setFactor(&negFactors.CapacityFactor)
- case name == "pricing/negative/requestCostFactor":
- setFactor(&negFactors.RequestFactor)
- case !defParams && name == "capacity":
- if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity {
- _, err = api.server.clientPool.SetCapacity(client.Node(), uint64(capacity), 0, false)
- // time factor recalculation is performed automatically by the balance tracker
- } else {
- err = errValue()
- }
- default:
- if defParams {
- err = fmt.Errorf("invalid default parameter '%s'", name)
- } else {
- err = fmt.Errorf("invalid client parameter '%s'", name)
- }
- }
- if err != nil {
- return
- }
- }
- return
-}
-
-// SetClientParams sets client parameters for all clients listed in the ids list
-// or all connected clients if the list is empty
-func (api *LightServerAPI) SetClientParams(nodes []string, params map[string]interface{}) error {
- var err error
- for _, node := range nodes {
- var id enode.ID
- if id, err = parseNode(node); err != nil {
- return err
- }
- if peer := api.server.peers.peer(id); peer != nil {
- posFactors, negFactors := peer.balance.GetPriceFactors()
- update, e := api.setParams(params, peer, &posFactors, &negFactors)
- if update {
- peer.balance.SetPriceFactors(posFactors, negFactors)
- }
- if e != nil {
- err = e
- }
- } else {
- err = fmt.Errorf("client %064x is not connected", id)
- }
- }
- return err
-}
-
-// SetDefaultParams sets the default parameters applicable to clients connected in the future
-func (api *LightServerAPI) SetDefaultParams(params map[string]interface{}) error {
- update, err := api.setParams(params, nil, &api.defaultPosFactors, &api.defaultNegFactors)
- if update {
- api.server.clientPool.SetDefaultFactors(api.defaultPosFactors, api.defaultNegFactors)
- }
- return err
-}
-
-// SetConnectedBias set the connection bias, which is applied to already connected clients
-// So that already connected client won't be kicked out very soon and we can ensure all
-// connected clients can have enough time to request or sync some data.
-// When the input parameter `bias` < 0 (illegal), return error.
-func (api *LightServerAPI) SetConnectedBias(bias time.Duration) error {
- if bias < time.Duration(0) {
- return fmt.Errorf("bias illegal: %v less than 0", bias)
- }
- api.server.clientPool.SetConnectedBias(bias)
- return nil
-}
-
-// AddBalance adds the given amount to the balance of a client if possible and returns
-// the balance before and after the operation
-func (api *LightServerAPI) AddBalance(node string, amount int64) (balance [2]uint64, err error) {
- var id enode.ID
- if id, err = parseNode(node); err != nil {
- return
- }
- api.server.clientPool.BalanceOperation(id, "", func(nb vfs.AtomicBalanceOperator) {
- balance[0], balance[1], err = nb.AddBalance(amount)
- })
- return
-}
-
-// Benchmark runs a request performance benchmark with a given set of measurement setups
-// in multiple passes specified by passCount. The measurement time for each setup in each
-// pass is specified in milliseconds by length.
-//
-// Note: measurement time is adjusted for each pass depending on the previous ones.
-// Therefore a controlled total measurement time is achievable in multiple passes.
-func (api *LightServerAPI) Benchmark(setups []map[string]interface{}, passCount, length int) ([]map[string]interface{}, error) {
- benchmarks := make([]requestBenchmark, len(setups))
- for i, setup := range setups {
- if t, ok := setup["type"].(string); ok {
- getInt := func(field string, def int) int {
- if value, ok := setup[field].(float64); ok {
- return int(value)
- }
- return def
- }
- getBool := func(field string, def bool) bool {
- if value, ok := setup[field].(bool); ok {
- return value
- }
- return def
- }
- switch t {
- case "header":
- benchmarks[i] = &benchmarkBlockHeaders{
- amount: getInt("amount", 1),
- skip: getInt("skip", 1),
- byHash: getBool("byHash", false),
- reverse: getBool("reverse", false),
- }
- case "body":
- benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: false}
- case "receipts":
- benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: true}
- case "proof":
- benchmarks[i] = &benchmarkProofsOrCode{code: false}
- case "code":
- benchmarks[i] = &benchmarkProofsOrCode{code: true}
- case "cht":
- benchmarks[i] = &benchmarkHelperTrie{
- bloom: false,
- reqCount: getInt("amount", 1),
- }
- case "bloom":
- benchmarks[i] = &benchmarkHelperTrie{
- bloom: true,
- reqCount: getInt("amount", 1),
- }
- case "txSend":
- benchmarks[i] = &benchmarkTxSend{}
- case "txStatus":
- benchmarks[i] = &benchmarkTxStatus{}
- default:
- return nil, errUnknownBenchmarkType
- }
- } else {
- return nil, errUnknownBenchmarkType
- }
- }
- rs := api.server.handler.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length))
- result := make([]map[string]interface{}, len(setups))
- for i, r := range rs {
- res := make(map[string]interface{})
- if r.err == nil {
- res["totalCount"] = r.totalCount
- res["avgTime"] = r.avgTime
- res["maxInSize"] = r.maxInSize
- res["maxOutSize"] = r.maxOutSize
- } else {
- res["error"] = r.err.Error()
- }
- result[i] = res
- }
- return result, nil
-}
-
-// DebugAPI provides an API to debug LES light server functionality.
-type DebugAPI struct {
- server *LesServer
-}
-
-// NewDebugAPI creates a new LES light server debug API.
-func NewDebugAPI(server *LesServer) *DebugAPI {
- return &DebugAPI{
- server: server,
- }
-}
-
-// FreezeClient forces a temporary client freeze which normally happens when the server is overloaded
-func (api *DebugAPI) FreezeClient(node string) error {
- var (
- id enode.ID
- err error
- )
- if id, err = parseNode(node); err != nil {
- return err
- }
- if peer := api.server.peers.peer(id); peer != nil {
- peer.freeze()
- return nil
- } else {
- return fmt.Errorf("client %064x is not connected", id[:])
- }
-}
diff --git a/les/api_backend.go b/les/api_backend.go
deleted file mode 100644
index 3e9dbadce..000000000
--- a/les/api_backend.go
+++ /dev/null
@@ -1,337 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "context"
- "errors"
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/bloombits"
- "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/vm"
- "github.com/ethereum/go-ethereum/eth/gasprice"
- "github.com/ethereum/go-ethereum/eth/tracers"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-type LesApiBackend struct {
- extRPCEnabled bool
- allowUnprotectedTxs bool
- eth *LightEthereum
- gpo *gasprice.Oracle
-}
-
-func (b *LesApiBackend) ChainConfig() *params.ChainConfig {
- return b.eth.chainConfig
-}
-
-func (b *LesApiBackend) CurrentBlock() *types.Header {
- return b.eth.BlockChain().CurrentHeader()
-}
-
-func (b *LesApiBackend) SetHead(number uint64) {
- b.eth.blockchain.SetHead(number)
-}
-
-func (b *LesApiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
- // Return the latest current as the pending one since there
- // is no pending notion in the light client. TODO(rjl493456442)
- // unify the behavior of `HeaderByNumber` and `PendingBlockAndReceipts`.
- if number == rpc.PendingBlockNumber {
- return b.eth.blockchain.CurrentHeader(), nil
- }
- if number == rpc.LatestBlockNumber {
- return b.eth.blockchain.CurrentHeader(), nil
- }
- return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(number))
-}
-
-func (b *LesApiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
- if blockNr, ok := blockNrOrHash.Number(); ok {
- return b.HeaderByNumber(ctx, blockNr)
- }
- if hash, ok := blockNrOrHash.Hash(); ok {
- header, err := b.HeaderByHash(ctx, hash)
- if err != nil {
- return nil, err
- }
- if header == nil {
- return nil, errors.New("header for hash not found")
- }
- if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
- return nil, errors.New("hash is not currently canonical")
- }
- return header, nil
- }
- return nil, errors.New("invalid arguments; neither block nor hash specified")
-}
-
-func (b *LesApiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
- return b.eth.blockchain.GetHeaderByHash(hash), nil
-}
-
-func (b *LesApiBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
- header, err := b.HeaderByNumber(ctx, number)
- if header == nil || err != nil {
- return nil, err
- }
- return b.BlockByHash(ctx, header.Hash())
-}
-
-func (b *LesApiBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
- return b.eth.blockchain.GetBlockByHash(ctx, hash)
-}
-
-func (b *LesApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
- if blockNr, ok := blockNrOrHash.Number(); ok {
- return b.BlockByNumber(ctx, blockNr)
- }
- if hash, ok := blockNrOrHash.Hash(); ok {
- block, err := b.BlockByHash(ctx, hash)
- if err != nil {
- return nil, err
- }
- if block == nil {
- return nil, errors.New("header found, but block body is missing")
- }
- if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(block.NumberU64()) != hash {
- return nil, errors.New("hash is not currently canonical")
- }
- return block, nil
- }
- return nil, errors.New("invalid arguments; neither block nor hash specified")
-}
-
-func (b *LesApiBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
- return light.GetBody(ctx, b.eth.odr, hash, uint64(number))
-}
-
-func (b *LesApiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
- return nil, nil
-}
-
-func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
- header, err := b.HeaderByNumber(ctx, number)
- if err != nil {
- return nil, nil, err
- }
- if header == nil {
- return nil, nil, errors.New("header not found")
- }
- return light.NewState(ctx, header, b.eth.odr), header, nil
-}
-
-func (b *LesApiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
- if blockNr, ok := blockNrOrHash.Number(); ok {
- return b.StateAndHeaderByNumber(ctx, blockNr)
- }
- if hash, ok := blockNrOrHash.Hash(); ok {
- header := b.eth.blockchain.GetHeaderByHash(hash)
- if header == nil {
- return nil, nil, errors.New("header for hash not found")
- }
- if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
- return nil, nil, errors.New("hash is not currently canonical")
- }
- return light.NewState(ctx, header, b.eth.odr), header, nil
- }
- return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
-}
-
-func (b *LesApiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
- if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil {
- return light.GetBlockReceipts(ctx, b.eth.odr, hash, *number)
- }
- return nil, nil
-}
-
-func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
- return light.GetBlockLogs(ctx, b.eth.odr, hash, number)
-}
-
-func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
- if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil {
- return b.eth.blockchain.GetTdOdr(ctx, hash, *number)
- }
- return nil
-}
-
-func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) {
- if vmConfig == nil {
- vmConfig = new(vm.Config)
- }
- txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(header, b.eth.blockchain, nil)
- if blockCtx != nil {
- context = *blockCtx
- }
- return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error
-}
-
-func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
- return b.eth.txPool.Add(ctx, signedTx)
-}
-
-func (b *LesApiBackend) RemoveTx(txHash common.Hash) {
- b.eth.txPool.RemoveTx(txHash)
-}
-
-func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) {
- return b.eth.txPool.GetTransactions()
-}
-
-func (b *LesApiBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction {
- return b.eth.txPool.GetTransaction(txHash)
-}
-
-func (b *LesApiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
- return light.GetTransaction(ctx, b.eth.odr, txHash)
-}
-
-func (b *LesApiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
- return b.eth.txPool.GetNonce(ctx, addr)
-}
-
-func (b *LesApiBackend) Stats() (pending int, queued int) {
- return b.eth.txPool.Stats(), 0
-}
-
-func (b *LesApiBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
- return b.eth.txPool.Content()
-}
-
-func (b *LesApiBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
- return b.eth.txPool.ContentFrom(addr)
-}
-
-func (b *LesApiBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
- return b.eth.txPool.SubscribeNewTxsEvent(ch)
-}
-
-func (b *LesApiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
- return b.eth.blockchain.SubscribeChainEvent(ch)
-}
-
-func (b *LesApiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
- return b.eth.blockchain.SubscribeChainHeadEvent(ch)
-}
-
-func (b *LesApiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
- return b.eth.blockchain.SubscribeChainSideEvent(ch)
-}
-
-func (b *LesApiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
- return b.eth.blockchain.SubscribeLogsEvent(ch)
-}
-
-func (b *LesApiBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
- return event.NewSubscription(func(quit <-chan struct{}) error {
- <-quit
- return nil
- })
-}
-
-func (b *LesApiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
- return b.eth.blockchain.SubscribeRemovedLogsEvent(ch)
-}
-
-func (b *LesApiBackend) SyncProgress() ethereum.SyncProgress {
- return ethereum.SyncProgress{}
-}
-
-func (b *LesApiBackend) ProtocolVersion() int {
- return b.eth.LesVersion() + 10000
-}
-
-func (b *LesApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
- return b.gpo.SuggestTipCap(ctx)
-}
-
-func (b *LesApiBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
- return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
-}
-
-func (b *LesApiBackend) ChainDb() ethdb.Database {
- return b.eth.chainDb
-}
-
-func (b *LesApiBackend) AccountManager() *accounts.Manager {
- return b.eth.accountManager
-}
-
-func (b *LesApiBackend) ExtRPCEnabled() bool {
- return b.extRPCEnabled
-}
-
-func (b *LesApiBackend) UnprotectedAllowed() bool {
- return b.allowUnprotectedTxs
-}
-
-func (b *LesApiBackend) RPCGasCap() uint64 {
- return b.eth.config.RPCGasCap
-}
-
-func (b *LesApiBackend) RPCEVMTimeout() time.Duration {
- return b.eth.config.RPCEVMTimeout
-}
-
-func (b *LesApiBackend) RPCTxFeeCap() float64 {
- return b.eth.config.RPCTxFeeCap
-}
-
-func (b *LesApiBackend) BloomStatus() (uint64, uint64) {
- if b.eth.bloomIndexer == nil {
- return 0, 0
- }
- sections, _, _ := b.eth.bloomIndexer.Sections()
- return params.BloomBitsBlocksClient, sections
-}
-
-func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
- for i := 0; i < bloomFilterThreads; i++ {
- go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
- }
-}
-
-func (b *LesApiBackend) Engine() consensus.Engine {
- return b.eth.engine
-}
-
-func (b *LesApiBackend) CurrentHeader() *types.Header {
- return b.eth.blockchain.CurrentHeader()
-}
-
-func (b *LesApiBackend) 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)
-}
-
-func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
- return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
-}
diff --git a/les/api_test.go b/les/api_test.go
deleted file mode 100644
index 484c95504..000000000
--- a/les/api_test.go
+++ /dev/null
@@ -1,512 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "context"
- crand "crypto/rand"
- "errors"
- "flag"
- "math/rand"
- "os"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/eth/ethconfig"
- "github.com/ethereum/go-ethereum/les/flowcontrol"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/mattn/go-colorable"
-)
-
-// Additional command line flags for the test binary.
-var (
- loglevel = flag.Int("loglevel", 0, "verbosity of logs")
- simAdapter = flag.String("adapter", "exec", "type of simulation: sim|socket|exec|docker")
-)
-
-func TestMain(m *testing.M) {
- flag.Parse()
- log.PrintOrigins(true)
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
- // register the Delivery service which will run as a devp2p
- // protocol when using the exec adapter
- adapters.RegisterLifecycles(services)
- os.Exit(m.Run())
-}
-
-// This test is not meant to be a part of the automatic testing process because it
-// runs for a long time and also requires a large database in order to do a meaningful
-// request performance test. When testServerDataDir is empty, the test is skipped.
-
-const (
- testServerDataDir = "" // should always be empty on the master branch
- testServerCapacity = 200
- testMaxClients = 10
- testTolerance = 0.1
- minRelCap = 0.2
-)
-
-func TestCapacityAPI3(t *testing.T) {
- testCapacityAPI(t, 3)
-}
-
-func TestCapacityAPI6(t *testing.T) {
- testCapacityAPI(t, 6)
-}
-
-func TestCapacityAPI10(t *testing.T) {
- testCapacityAPI(t, 10)
-}
-
-// testCapacityAPI runs an end-to-end simulation test connecting one server with
-// a given number of clients. It sets different priority capacities to all clients
-// except a randomly selected one which runs in free client mode. All clients send
-// similar requests at the maximum allowed rate and the test verifies whether the
-// ratio of processed requests is close enough to the ratio of assigned capacities.
-// Running multiple rounds with different settings ensures that changing capacity
-// while connected and going back and forth between free and priority mode with
-// the supplied API calls is also thoroughly tested.
-func testCapacityAPI(t *testing.T, clientCount int) {
- // Skip test if no data dir specified
- if testServerDataDir == "" {
- return
- }
- for !testSim(t, 1, clientCount, []string{testServerDataDir}, nil, func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool {
- if len(servers) != 1 {
- t.Fatalf("Invalid number of servers: %d", len(servers))
- }
- server := servers[0]
-
- serverRpcClient, err := server.Client()
- if err != nil {
- t.Fatalf("Failed to obtain rpc client: %v", err)
- }
- headNum, headHash := getHead(ctx, t, serverRpcClient)
- minCap, totalCap := getCapacityInfo(ctx, t, serverRpcClient)
- testCap := totalCap * 3 / 4
- t.Logf("Server testCap: %d minCap: %d head number: %d head hash: %064x\n", testCap, minCap, headNum, headHash)
- reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1)))
- if minCap > reqMinCap {
- t.Fatalf("Minimum client capacity (%d) bigger than required minimum for this test (%d)", minCap, reqMinCap)
- }
- freeIdx := rand.Intn(len(clients))
-
- clientRpcClients := make([]*rpc.Client, len(clients))
- for i, client := range clients {
- var err error
- clientRpcClients[i], err = client.Client()
- if err != nil {
- t.Fatalf("Failed to obtain rpc client: %v", err)
- }
- t.Log("connecting client", i)
- if i != freeIdx {
- setCapacity(ctx, t, serverRpcClient, client.ID(), testCap/uint64(len(clients)))
- }
- net.Connect(client.ID(), server.ID())
-
- for {
- select {
- case <-ctx.Done():
- t.Fatalf("Timeout")
- default:
- }
- num, hash := getHead(ctx, t, clientRpcClients[i])
- if num == headNum && hash == headHash {
- t.Log("client", i, "synced")
- break
- }
- time.Sleep(time.Millisecond * 200)
- }
- }
-
- var wg sync.WaitGroup
- stop := make(chan struct{})
-
- reqCount := make([]atomic.Uint64, len(clientRpcClients))
-
- // Send light request like crazy.
- for i, c := range clientRpcClients {
- wg.Add(1)
- i, c := i, c
- go func() {
- defer wg.Done()
-
- queue := make(chan struct{}, 100)
- reqCount[i].Store(0)
- for {
- select {
- case queue <- struct{}{}:
- select {
- case <-stop:
- return
- case <-ctx.Done():
- return
- default:
- wg.Add(1)
- go func() {
- ok := testRequest(ctx, t, c)
- wg.Done()
- <-queue
- if ok {
- if reqCount[i].Add(1)%10000 == 0 {
- freezeClient(ctx, t, serverRpcClient, clients[i].ID())
- }
- }
- }()
- }
- case <-stop:
- return
- case <-ctx.Done():
- return
- }
- }
- }()
- }
-
- processedSince := func(start []uint64) []uint64 {
- res := make([]uint64, len(reqCount))
- for i := range reqCount {
- res[i] = reqCount[i].Load()
- if start != nil {
- res[i] -= start[i]
- }
- }
- return res
- }
-
- weights := make([]float64, len(clients))
- for c := 0; c < 5; c++ {
- setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), minCap)
- freeIdx = rand.Intn(len(clients))
- var sum float64
- for i := range clients {
- if i == freeIdx {
- weights[i] = 0
- } else {
- weights[i] = rand.Float64()*(1-minRelCap) + minRelCap
- }
- sum += weights[i]
- }
- for i, client := range clients {
- weights[i] *= float64(testCap-minCap-100) / sum
- capacity := uint64(weights[i])
- if i != freeIdx && capacity < getCapacity(ctx, t, serverRpcClient, client.ID()) {
- setCapacity(ctx, t, serverRpcClient, client.ID(), capacity)
- }
- }
- setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), 0)
- for i, client := range clients {
- capacity := uint64(weights[i])
- if i != freeIdx && capacity > getCapacity(ctx, t, serverRpcClient, client.ID()) {
- setCapacity(ctx, t, serverRpcClient, client.ID(), capacity)
- }
- }
- weights[freeIdx] = float64(minCap)
- for i := range clients {
- weights[i] /= float64(testCap)
- }
-
- time.Sleep(flowcontrol.DecParamDelay)
- t.Log("Starting measurement")
- t.Logf("Relative weights:")
- for i := range clients {
- t.Logf(" %f", weights[i])
- }
- t.Log()
- start := processedSince(nil)
- for {
- select {
- case <-ctx.Done():
- t.Fatalf("Timeout")
- default:
- }
-
- _, totalCap = getCapacityInfo(ctx, t, serverRpcClient)
- if totalCap < testCap {
- t.Log("Total capacity underrun")
- close(stop)
- wg.Wait()
- return false
- }
-
- processed := processedSince(start)
- var avg uint64
- t.Logf("Processed")
- for i, p := range processed {
- t.Logf(" %d", p)
- processed[i] = uint64(float64(p) / weights[i])
- avg += processed[i]
- }
- avg /= uint64(len(processed))
-
- if avg >= 10000 {
- var maxDev float64
- for _, p := range processed {
- dev := float64(int64(p-avg)) / float64(avg)
- t.Logf(" %7.4f", dev)
- if dev < 0 {
- dev = -dev
- }
- if dev > maxDev {
- maxDev = dev
- }
- }
- t.Logf(" max deviation: %f totalCap: %d\n", maxDev, totalCap)
- if maxDev <= testTolerance {
- t.Log("success")
- break
- }
- } else {
- t.Log()
- }
- time.Sleep(time.Millisecond * 200)
- }
- }
-
- close(stop)
- wg.Wait()
-
- for i := range reqCount {
- t.Log("client", i, "processed", reqCount[i].Load())
- }
- return true
- }) {
- t.Log("restarting test")
- }
-}
-
-func getHead(ctx context.Context, t *testing.T, client *rpc.Client) (uint64, common.Hash) {
- res := make(map[string]interface{})
- if err := client.CallContext(ctx, &res, "eth_getBlockByNumber", "latest", false); err != nil {
- t.Fatalf("Failed to obtain head block: %v", err)
- }
- numStr, ok := res["number"].(string)
- if !ok {
- t.Fatalf("RPC block number field invalid")
- }
- num, err := hexutil.DecodeUint64(numStr)
- if err != nil {
- t.Fatalf("Failed to decode RPC block number: %v", err)
- }
- hashStr, ok := res["hash"].(string)
- if !ok {
- t.Fatalf("RPC block number field invalid")
- }
- hash := common.HexToHash(hashStr)
- return num, hash
-}
-
-func testRequest(ctx context.Context, t *testing.T, client *rpc.Client) bool {
- var res string
- var addr common.Address
- crand.Read(addr[:])
- c, cancel := context.WithTimeout(ctx, time.Second*12)
- defer cancel()
- err := client.CallContext(c, &res, "eth_getBalance", addr, "latest")
- if err != nil {
- t.Log("request error:", err)
- }
- return err == nil
-}
-
-func freezeClient(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID) {
- if err := server.CallContext(ctx, nil, "debug_freezeClient", clientID); err != nil {
- t.Fatalf("Failed to freeze client: %v", err)
- }
-}
-
-func setCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID, cap uint64) {
- params := make(map[string]interface{})
- params["capacity"] = cap
- if err := server.CallContext(ctx, nil, "les_setClientParams", []enode.ID{clientID}, []string{}, params); err != nil {
- t.Fatalf("Failed to set client capacity: %v", err)
- }
-}
-
-func getCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID) uint64 {
- var res map[enode.ID]map[string]interface{}
- if err := server.CallContext(ctx, &res, "les_clientInfo", []enode.ID{clientID}, []string{}); err != nil {
- t.Fatalf("Failed to get client info: %v", err)
- }
- info, ok := res[clientID]
- if !ok {
- t.Fatalf("Missing client info")
- }
- v, ok := info["capacity"]
- if !ok {
- t.Fatalf("Missing field in client info: capacity")
- }
- vv, ok := v.(float64)
- if !ok {
- t.Fatalf("Failed to decode capacity field")
- }
- return uint64(vv)
-}
-
-func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (minCap, totalCap uint64) {
- var res map[string]interface{}
- if err := server.CallContext(ctx, &res, "les_serverInfo"); err != nil {
- t.Fatalf("Failed to query server info: %v", err)
- }
- decode := func(s string) uint64 {
- v, ok := res[s]
- if !ok {
- t.Fatalf("Missing field in server info: %s", s)
- }
- vv, ok := v.(float64)
- if !ok {
- t.Fatalf("Failed to decode server info field: %s", s)
- }
- return uint64(vv)
- }
- minCap = decode("minimumCapacity")
- totalCap = decode("totalCapacity")
- return
-}
-
-var services = adapters.LifecycleConstructors{
- "lesclient": newLesClientService,
- "lesserver": newLesServerService,
-}
-
-func NewNetwork() (*simulations.Network, func(), error) {
- adapter, adapterTeardown, err := NewAdapter(*simAdapter, services)
- if err != nil {
- return nil, adapterTeardown, err
- }
- defaultService := "streamer"
- net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
- ID: "0",
- DefaultService: defaultService,
- })
- teardown := func() {
- adapterTeardown()
- net.Shutdown()
- }
- return net, teardown, nil
-}
-
-func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (adapter adapters.NodeAdapter, teardown func(), err error) {
- teardown = func() {}
- switch adapterType {
- case "sim":
- adapter = adapters.NewSimAdapter(services)
- // case "socket":
- // adapter = adapters.NewSocketAdapter(services)
- case "exec":
- baseDir, err0 := os.MkdirTemp("", "les-test")
- if err0 != nil {
- return nil, teardown, err0
- }
- teardown = func() { os.RemoveAll(baseDir) }
- adapter = adapters.NewExecAdapter(baseDir)
- /*case "docker":
- adapter, err = adapters.NewDockerAdapter()
- if err != nil {
- return nil, teardown, err
- }*/
- default:
- return nil, teardown, errors.New("adapter needs to be one of sim, socket, exec, docker")
- }
- return adapter, teardown, nil
-}
-
-func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []string, test func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool) bool {
- net, teardown, err := NewNetwork()
- defer teardown()
- if err != nil {
- t.Fatalf("Failed to create network: %v", err)
- }
- timeout := 1800 * time.Second
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
-
- servers := make([]*simulations.Node, serverCount)
- clients := make([]*simulations.Node, clientCount)
-
- for i := range clients {
- clientconf := adapters.RandomNodeConfig()
- clientconf.Lifecycles = []string{"lesclient"}
- if len(clientDir) == clientCount {
- clientconf.DataDir = clientDir[i]
- }
- client, err := net.NewNodeWithConfig(clientconf)
- if err != nil {
- t.Fatalf("Failed to create client: %v", err)
- }
- clients[i] = client
- }
-
- for i := range servers {
- serverconf := adapters.RandomNodeConfig()
- serverconf.Lifecycles = []string{"lesserver"}
- if len(serverDir) == serverCount {
- serverconf.DataDir = serverDir[i]
- }
- server, err := net.NewNodeWithConfig(serverconf)
- if err != nil {
- t.Fatalf("Failed to create server: %v", err)
- }
- servers[i] = server
- }
-
- for _, client := range clients {
- if err := net.Start(client.ID()); err != nil {
- t.Fatalf("Failed to start client node: %v", err)
- }
- }
- for _, server := range servers {
- if err := net.Start(server.ID()); err != nil {
- t.Fatalf("Failed to start server node: %v", err)
- }
- }
-
- return test(ctx, net, servers, clients)
-}
-
-func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- config := ethconfig.Defaults
- config.SyncMode = downloader.LightSync
- return New(stack, &config)
-}
-
-func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- config := ethconfig.Defaults
- config.SyncMode = downloader.FullSync
- config.LightServ = testServerCapacity
- config.LightPeers = testMaxClients
- ethereum, err := eth.New(stack, &config)
- if err != nil {
- return nil, err
- }
- _, err = NewLesServer(stack, ethereum, &config)
- if err != nil {
- return nil, err
- }
- return ethereum, nil
-}
diff --git a/les/benchmark.go b/les/benchmark.go
deleted file mode 100644
index ab9351834..000000000
--- a/les/benchmark.go
+++ /dev/null
@@ -1,351 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- crand "crypto/rand"
- "encoding/binary"
- "errors"
- "math/big"
- "math/rand"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "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/les/flowcontrol"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// requestBenchmark is an interface for different randomized request generators
-type requestBenchmark interface {
- // init initializes the generator for generating the given number of randomized requests
- init(h *serverHandler, count int) error
- // request initiates sending a single request to the given peer
- request(peer *serverPeer, index int) error
-}
-
-// benchmarkBlockHeaders implements requestBenchmark
-type benchmarkBlockHeaders struct {
- amount, skip int
- reverse, byHash bool
- offset, randMax int64
- hashes []common.Hash
-}
-
-func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error {
- d := int64(b.amount-1) * int64(b.skip+1)
- b.offset = 0
- b.randMax = h.blockchain.CurrentHeader().Number.Int64() + 1 - d
- if b.randMax < 0 {
- return errors.New("chain is too short")
- }
- if b.reverse {
- b.offset = d
- }
- if b.byHash {
- b.hashes = make([]common.Hash, count)
- for i := range b.hashes {
- b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(b.offset+rand.Int63n(b.randMax)))
- }
- }
- return nil
-}
-
-func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error {
- if b.byHash {
- return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse)
- }
- return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse)
-}
-
-// benchmarkBodiesOrReceipts implements requestBenchmark
-type benchmarkBodiesOrReceipts struct {
- receipts bool
- hashes []common.Hash
-}
-
-func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error {
- randMax := h.blockchain.CurrentHeader().Number.Int64() + 1
- b.hashes = make([]common.Hash, count)
- for i := range b.hashes {
- b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(rand.Int63n(randMax)))
- }
- return nil
-}
-
-func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error {
- if b.receipts {
- return peer.requestReceipts(0, []common.Hash{b.hashes[index]})
- }
- return peer.requestBodies(0, []common.Hash{b.hashes[index]})
-}
-
-// benchmarkProofsOrCode implements requestBenchmark
-type benchmarkProofsOrCode struct {
- code bool
- headHash common.Hash
-}
-
-func (b *benchmarkProofsOrCode) init(h *serverHandler, count int) error {
- b.headHash = h.blockchain.CurrentHeader().Hash()
- return nil
-}
-
-func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error {
- key := make([]byte, 32)
- crand.Read(key)
- if b.code {
- return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccountAddress: key}})
- }
- return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}})
-}
-
-// benchmarkHelperTrie implements requestBenchmark
-type benchmarkHelperTrie struct {
- bloom bool
- reqCount int
- sectionCount, headNum uint64
-}
-
-func (b *benchmarkHelperTrie) init(h *serverHandler, count int) error {
- if b.bloom {
- b.sectionCount, b.headNum, _ = h.server.bloomTrieIndexer.Sections()
- } else {
- b.sectionCount, _, _ = h.server.chtIndexer.Sections()
- b.headNum = b.sectionCount*params.CHTFrequency - 1
- }
- if b.sectionCount == 0 {
- return errors.New("no processed sections available")
- }
- return nil
-}
-
-func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error {
- reqs := make([]HelperTrieReq, b.reqCount)
-
- if b.bloom {
- bitIdx := uint16(rand.Intn(2048))
- for i := range reqs {
- key := make([]byte, 10)
- binary.BigEndian.PutUint16(key[:2], bitIdx)
- binary.BigEndian.PutUint64(key[2:], uint64(rand.Int63n(int64(b.sectionCount))))
- reqs[i] = HelperTrieReq{Type: htBloomBits, TrieIdx: b.sectionCount - 1, Key: key}
- }
- } else {
- for i := range reqs {
- key := make([]byte, 8)
- binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum))))
- reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: htAuxHeader}
- }
- }
-
- return peer.requestHelperTrieProofs(0, reqs)
-}
-
-// benchmarkTxSend implements requestBenchmark
-type benchmarkTxSend struct {
- txs types.Transactions
-}
-
-func (b *benchmarkTxSend) init(h *serverHandler, count int) error {
- key, _ := crypto.GenerateKey()
- addr := crypto.PubkeyToAddress(key.PublicKey)
- signer := types.LatestSigner(h.server.chainConfig)
- b.txs = make(types.Transactions, count)
-
- for i := range b.txs {
- data := make([]byte, txSizeCostLimit)
- crand.Read(data)
- tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data), signer, key)
- if err != nil {
- panic(err)
- }
- b.txs[i] = tx
- }
- return nil
-}
-
-func (b *benchmarkTxSend) request(peer *serverPeer, index int) error {
- enc, _ := rlp.EncodeToBytes(types.Transactions{b.txs[index]})
- return peer.sendTxs(0, 1, enc)
-}
-
-// benchmarkTxStatus implements requestBenchmark
-type benchmarkTxStatus struct{}
-
-func (b *benchmarkTxStatus) init(h *serverHandler, count int) error {
- return nil
-}
-
-func (b *benchmarkTxStatus) request(peer *serverPeer, index int) error {
- var hash common.Hash
- crand.Read(hash[:])
- return peer.requestTxStatus(0, []common.Hash{hash})
-}
-
-// benchmarkSetup stores measurement data for a single benchmark type
-type benchmarkSetup struct {
- req requestBenchmark
- totalCount int
- totalTime, avgTime time.Duration
- maxInSize, maxOutSize uint32
- err error
-}
-
-// runBenchmark runs a benchmark cycle for all benchmark types in the specified
-// number of passes
-func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount int, targetTime time.Duration) []*benchmarkSetup {
- setup := make([]*benchmarkSetup, len(benchmarks))
- for i, b := range benchmarks {
- setup[i] = &benchmarkSetup{req: b}
- }
- for i := 0; i < passCount; i++ {
- log.Info("Running benchmark", "pass", i+1, "total", passCount)
- todo := make([]*benchmarkSetup, len(benchmarks))
- copy(todo, setup)
- for len(todo) > 0 {
- // select a random element
- index := rand.Intn(len(todo))
- next := todo[index]
- todo[index] = todo[len(todo)-1]
- todo = todo[:len(todo)-1]
-
- if next.err == nil {
- // calculate request count
- count := 50
- if next.totalTime > 0 {
- count = int(uint64(next.totalCount) * uint64(targetTime) / uint64(next.totalTime))
- }
- if err := h.measure(next, count); err != nil {
- next.err = err
- }
- }
- }
- }
- log.Info("Benchmark completed")
-
- for _, s := range setup {
- if s.err == nil {
- s.avgTime = s.totalTime / time.Duration(s.totalCount)
- }
- }
- return setup
-}
-
-// meteredPipe implements p2p.MsgReadWriter and remembers the largest single
-// message size sent through the pipe
-type meteredPipe struct {
- rw p2p.MsgReadWriter
- maxSize uint32
-}
-
-func (m *meteredPipe) ReadMsg() (p2p.Msg, error) {
- return m.rw.ReadMsg()
-}
-
-func (m *meteredPipe) WriteMsg(msg p2p.Msg) error {
- if msg.Size > m.maxSize {
- m.maxSize = msg.Size
- }
- return m.rw.WriteMsg(msg)
-}
-
-// measure runs a benchmark for a single type in a single pass, with the given
-// number of requests
-func (h *serverHandler) measure(setup *benchmarkSetup, count int) error {
- clientPipe, serverPipe := p2p.MsgPipe()
- clientMeteredPipe := &meteredPipe{rw: clientPipe}
- serverMeteredPipe := &meteredPipe{rw: serverPipe}
- var id enode.ID
- crand.Read(id[:])
-
- peer1 := newServerPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "client", nil), clientMeteredPipe)
- peer2 := newClientPeer(lpv2, NetworkId, p2p.NewPeer(id, "server", nil), serverMeteredPipe)
- peer2.announceType = announceTypeNone
- peer2.fcCosts = make(requestCostTable)
- c := &requestCosts{}
- for code := range requests {
- peer2.fcCosts[code] = c
- }
- peer2.fcParams = flowcontrol.ServerParams{BufLimit: 1, MinRecharge: 1}
- peer2.fcClient = flowcontrol.NewClientNode(h.server.fcManager, peer2.fcParams)
- defer peer2.fcClient.Disconnect()
-
- if err := setup.req.init(h, count); err != nil {
- return err
- }
-
- errCh := make(chan error, 10)
- start := mclock.Now()
-
- go func() {
- for i := 0; i < count; i++ {
- if err := setup.req.request(peer1, i); err != nil {
- errCh <- err
- return
- }
- }
- }()
- go func() {
- for i := 0; i < count; i++ {
- if err := h.handleMsg(peer2, &sync.WaitGroup{}); err != nil {
- errCh <- err
- return
- }
- }
- }()
- go func() {
- for i := 0; i < count; i++ {
- msg, err := clientPipe.ReadMsg()
- if err != nil {
- errCh <- err
- return
- }
- var i interface{}
- msg.Decode(&i)
- }
- // at this point we can be sure that the other two
- // goroutines finished successfully too
- close(errCh)
- }()
- select {
- case err := <-errCh:
- if err != nil {
- return err
- }
- case <-h.closeCh:
- clientPipe.Close()
- serverPipe.Close()
- return errors.New("Benchmark cancelled")
- }
-
- setup.totalTime += time.Duration(mclock.Now() - start)
- setup.totalCount += count
- setup.maxInSize = clientMeteredPipe.maxSize
- setup.maxOutSize = serverMeteredPipe.maxSize
- clientPipe.Close()
- serverPipe.Close()
- return nil
-}
diff --git a/les/bloombits.go b/les/bloombits.go
deleted file mode 100644
index a98524ce2..000000000
--- a/les/bloombits.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2017 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 .
-
-package les
-
-import (
- "time"
-
- "github.com/ethereum/go-ethereum/common/bitutil"
- "github.com/ethereum/go-ethereum/light"
-)
-
-const (
- // bloomServiceThreads is the number of goroutines used globally by an Ethereum
- // instance to service bloombits lookups for all running filters.
- bloomServiceThreads = 16
-
- // bloomFilterThreads is the number of goroutines used locally per filter to
- // multiplex requests onto the global servicing goroutines.
- bloomFilterThreads = 3
-
- // bloomRetrievalBatch is the maximum number of bloom bit retrievals to service
- // in a single batch.
- bloomRetrievalBatch = 16
-
- // bloomRetrievalWait is the maximum time to wait for enough bloom bit requests
- // to accumulate request an entire batch (avoiding hysteresis).
- bloomRetrievalWait = time.Microsecond * 100
-)
-
-// startBloomHandlers starts a batch of goroutines to accept bloom bit database
-// retrievals from possibly a range of filters and serving the data to satisfy.
-func (eth *LightEthereum) startBloomHandlers(sectionSize uint64) {
- for i := 0; i < bloomServiceThreads; i++ {
- go func() {
- defer eth.wg.Done()
- for {
- select {
- case <-eth.closeCh:
- return
-
- case request := <-eth.bloomRequests:
- task := <-request
- task.Bitsets = make([][]byte, len(task.Sections))
- compVectors, err := light.GetBloomBits(task.Context, eth.odr, task.Bit, task.Sections)
- if err == nil {
- for i := range task.Sections {
- if blob, err := bitutil.DecompressBytes(compVectors[i], int(sectionSize/8)); err == nil {
- task.Bitsets[i] = blob
- } else {
- task.Error = err
- }
- }
- } else {
- task.Error = err
- }
- request <- task
- }
- }
- }()
- }
-}
diff --git a/les/client.go b/les/client.go
deleted file mode 100644
index be5e9fd56..000000000
--- a/les/client.go
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright 2019 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 .
-
-// Package les implements the Light Ethereum Subprotocol.
-package les
-
-import (
- "errors"
- "strings"
- "time"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/bloombits"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/eth/ethconfig"
- "github.com/ethereum/go-ethereum/eth/gasprice"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/internal/ethapi"
- "github.com/ethereum/go-ethereum/internal/shutdowncheck"
- "github.com/ethereum/go-ethereum/les/vflux"
- vfc "github.com/ethereum/go-ethereum/les/vflux/client"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-type LightEthereum struct {
- lesCommons
-
- peers *serverPeerSet
- reqDist *requestDistributor
- retriever *retrieveManager
- odr *LesOdr
- relay *lesTxRelay
- handler *clientHandler
- txPool *light.TxPool
- blockchain *light.LightChain
- serverPool *vfc.ServerPool
- serverPoolIterator enode.Iterator
- merger *consensus.Merger
-
- bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
- bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
-
- ApiBackend *LesApiBackend
- eventMux *event.TypeMux
- engine consensus.Engine
- accountManager *accounts.Manager
- netRPCService *ethapi.NetAPI
-
- p2pServer *p2p.Server
- p2pConfig *p2p.Config
- udpEnabled bool
-
- shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
-}
-
-// New creates an instance of the light client.
-func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
- chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false)
- if err != nil {
- return nil, err
- }
- lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/", false)
- if err != nil {
- return nil, err
- }
- var overrides core.ChainOverrides
- if config.OverrideCancun != nil {
- overrides.OverrideCancun = config.OverrideCancun
- }
- if config.OverrideVerkle != nil {
- overrides.OverrideVerkle = config.OverrideVerkle
- }
- triedb := trie.NewDatabase(chainDb, trie.HashDefaults)
- chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, triedb, config.Genesis, &overrides)
- if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat {
- return nil, genesisErr
- }
- engine, err := ethconfig.CreateConsensusEngine(chainConfig, chainDb)
- if err != nil {
- return nil, err
- }
- log.Info("")
- log.Info(strings.Repeat("-", 153))
- for _, line := range strings.Split(chainConfig.Description(), "\n") {
- log.Info(line)
- }
- log.Info(strings.Repeat("-", 153))
- log.Info("")
-
- peers := newServerPeerSet()
- merger := consensus.NewMerger(chainDb)
- leth := &LightEthereum{
- lesCommons: lesCommons{
- genesis: genesisHash,
- config: config,
- chainConfig: chainConfig,
- iConfig: light.DefaultClientIndexerConfig,
- chainDb: chainDb,
- lesDb: lesDb,
- closeCh: make(chan struct{}),
- },
- peers: peers,
- eventMux: stack.EventMux(),
- reqDist: newRequestDistributor(peers, &mclock.System{}),
- accountManager: stack.AccountManager(),
- merger: merger,
- engine: engine,
- bloomRequests: make(chan chan *bloombits.Retrieval),
- bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
- p2pServer: stack.Server(),
- p2pConfig: &stack.Config().P2P,
- udpEnabled: stack.Config().P2P.DiscoveryV5,
- shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
- }
-
- var prenegQuery vfc.QueryFunc
- if leth.udpEnabled {
- prenegQuery = leth.prenegQuery
- }
- leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, nil, requestList)
- leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter)
-
- leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout)
- leth.relay = newLesTxRelay(peers, leth.retriever)
-
- leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever)
- leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune)
- leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune)
- leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)
-
- // Note: NewLightChain adds the trusted checkpoint so it needs an ODR with
- // indexers already set but not started yet
- if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
- return nil, err
- }
- leth.chainReader = leth.blockchain
- leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
-
- // Note: AddChildIndexer starts the update process for the child
- leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
- leth.chtIndexer.Start(leth.blockchain)
- leth.bloomIndexer.Start(leth.blockchain)
-
- // Rewind the chain in case of an incompatible config upgrade.
- if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
- log.Warn("Rewinding chain to upgrade configuration", "err", compat)
- if compat.RewindToTime > 0 {
- leth.blockchain.SetHeadWithTimestamp(compat.RewindToTime)
- } else {
- leth.blockchain.SetHead(compat.RewindToBlock)
- }
- rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
- }
-
- leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth, nil}
- gpoParams := config.GPO
- if gpoParams.Default == nil {
- gpoParams.Default = config.Miner.GasPrice
- }
- leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
-
- leth.handler = newClientHandler(leth)
- leth.netRPCService = ethapi.NewNetAPI(leth.p2pServer, leth.config.NetworkId)
-
- // Register the backend on the node
- stack.RegisterAPIs(leth.APIs())
- stack.RegisterProtocols(leth.Protocols())
- stack.RegisterLifecycle(leth)
-
- // Successful startup; push a marker and check previous unclean shutdowns.
- leth.shutdownTracker.MarkStartup()
-
- return leth, nil
-}
-
-// VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses
-func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies {
- if !s.udpEnabled {
- return nil
- }
- reqsEnc, _ := rlp.EncodeToBytes(&reqs)
- repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc)
- var replies vflux.Replies
- if len(repliesEnc) == 0 || rlp.DecodeBytes(repliesEnc, &replies) != nil {
- return nil
- }
- return replies
-}
-
-// vfxVersion returns the version number of the "les" service subdomain of the vflux UDP
-// service, as advertised in the ENR record
-func (s *LightEthereum) vfxVersion(n *enode.Node) uint {
- if n.Seq() == 0 {
- var err error
- if !s.udpEnabled {
- return 0
- }
- if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 {
- s.serverPool.Persist(n)
- } else {
- return 0
- }
- }
-
- var les []rlp.RawValue
- if err := n.Load(enr.WithEntry("les", &les)); err != nil || len(les) < 1 {
- return 0
- }
- var version uint
- rlp.DecodeBytes(les[0], &version) // Ignore additional fields (for forward compatibility).
- return version
-}
-
-// prenegQuery sends a capacity query to the given server node to determine whether
-// a connection slot is immediately available
-func (s *LightEthereum) prenegQuery(n *enode.Node) int {
- if s.vfxVersion(n) < 1 {
- // UDP query not supported, always try TCP connection
- return 1
- }
-
- var requests vflux.Requests
- requests.Add("les", vflux.CapacityQueryName, vflux.CapacityQueryReq{
- Bias: 180,
- AddTokens: []vflux.IntOrInf{{}},
- })
- replies := s.VfluxRequest(n, requests)
- var cqr vflux.CapacityQueryReply
- if replies.Get(0, &cqr) != nil || len(cqr) != 1 { // Note: Get returns an error if replies is nil
- return -1
- }
- if cqr[0] > 0 {
- return 1
- }
- return 0
-}
-
-type LightDummyAPI struct{}
-
-// Etherbase is the address that mining rewards will be sent to
-func (s *LightDummyAPI) Etherbase() (common.Address, error) {
- return common.Address{}, errors.New("mining is not supported in light mode")
-}
-
-// Coinbase is the address that mining rewards will be sent to (alias for Etherbase)
-func (s *LightDummyAPI) Coinbase() (common.Address, error) {
- return common.Address{}, errors.New("mining is not supported in light mode")
-}
-
-// Hashrate returns the POW hashrate
-func (s *LightDummyAPI) Hashrate() hexutil.Uint {
- return 0
-}
-
-// Mining returns an indication if this node is currently mining.
-func (s *LightDummyAPI) Mining() bool {
- return false
-}
-
-// APIs returns the collection of RPC services the ethereum package offers.
-// NOTE, some of these services probably need to be moved to somewhere else.
-func (s *LightEthereum) APIs() []rpc.API {
- apis := ethapi.GetAPIs(s.ApiBackend)
- apis = append(apis, s.engine.APIs(s.BlockChain().HeaderChain())...)
- return append(apis, []rpc.API{
- {
- Namespace: "eth",
- Service: &LightDummyAPI{},
- }, {
- Namespace: "net",
- Service: s.netRPCService,
- }, {
- Namespace: "vflux",
- Service: s.serverPool.API(),
- },
- }...)
-}
-
-func (s *LightEthereum) ResetWithGenesisBlock(gb *types.Block) {
- s.blockchain.ResetWithGenesisBlock(gb)
-}
-
-func (s *LightEthereum) BlockChain() *light.LightChain { return s.blockchain }
-func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool }
-func (s *LightEthereum) Engine() consensus.Engine { return s.engine }
-func (s *LightEthereum) LesVersion() int { return int(ClientProtocolVersions[0]) }
-func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux }
-func (s *LightEthereum) Merger() *consensus.Merger { return s.merger }
-
-// Protocols returns all the currently configured network protocols to start.
-func (s *LightEthereum) Protocols() []p2p.Protocol {
- return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
- if p := s.peers.peer(id.String()); p != nil {
- return p.Info()
- }
- return nil
- }, s.serverPoolIterator)
-}
-
-// Start implements node.Lifecycle, starting all internal goroutines needed by the
-// light ethereum protocol implementation.
-func (s *LightEthereum) Start() error {
- log.Warn("Light client mode is an experimental feature")
-
- // Regularly update shutdown marker
- s.shutdownTracker.Start()
-
- if s.udpEnabled && s.p2pServer.DiscV5 == nil {
- s.udpEnabled = false
- log.Error("Discovery v5 is not initialized")
- }
- discovery, err := s.setupDiscovery()
- if err != nil {
- return err
- }
- s.serverPool.AddSource(discovery)
- s.serverPool.Start()
- // Start bloom request workers.
- s.wg.Add(bloomServiceThreads)
- s.startBloomHandlers(params.BloomBitsBlocksClient)
-
- return nil
-}
-
-// Stop implements node.Lifecycle, terminating all internal goroutines used by the
-// Ethereum protocol.
-func (s *LightEthereum) Stop() error {
- close(s.closeCh)
- s.serverPool.Stop()
- s.peers.close()
- s.reqDist.close()
- s.odr.Stop()
- s.relay.Stop()
- s.bloomIndexer.Close()
- s.chtIndexer.Close()
- s.blockchain.Stop()
- s.handler.stop()
- s.txPool.Stop()
- s.engine.Close()
- s.eventMux.Stop()
- // Clean shutdown marker as the last thing before closing db
- s.shutdownTracker.Stop()
-
- s.chainDb.Close()
- s.lesDb.Close()
- s.wg.Wait()
- log.Info("Light ethereum stopped")
- return nil
-}
diff --git a/les/client_handler.go b/les/client_handler.go
deleted file mode 100644
index 50f6dce87..000000000
--- a/les/client_handler.go
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// clientHandler is responsible for receiving and processing all incoming server
-// responses.
-type clientHandler struct {
- forkFilter forkid.Filter
- backend *LightEthereum
-
- closeCh chan struct{}
- wg sync.WaitGroup // WaitGroup used to track all connected peers.
-}
-
-func newClientHandler(backend *LightEthereum) *clientHandler {
- handler := &clientHandler{
- forkFilter: forkid.NewFilter(backend.blockchain),
- backend: backend,
- closeCh: make(chan struct{}),
- }
- return handler
-}
-
-func (h *clientHandler) stop() {
- close(h.closeCh)
- h.wg.Wait()
-}
-
-// runPeer is the p2p protocol run function for the given version.
-func (h *clientHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error {
- peer := newServerPeer(int(version), h.backend.config.NetworkId, false, p, newMeteredMsgWriter(rw, int(version)))
- defer peer.close()
- h.wg.Add(1)
- defer h.wg.Done()
- err := h.handle(peer, false)
- return err
-}
-
-func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error {
- if h.backend.peers.len() >= h.backend.config.LightPeers && !p.Peer.Info().Network.Trusted {
- return p2p.DiscTooManyPeers
- }
- p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
-
- // Execute the LES handshake
- forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.BlockChain().Genesis(), h.backend.blockchain.CurrentHeader().Number.Uint64(), h.backend.blockchain.CurrentHeader().Time)
- if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil {
- p.Log().Debug("Light Ethereum handshake failed", "err", err)
- return err
- }
- // Register peer with the server pool
- if h.backend.serverPool != nil {
- if nvt, err := h.backend.serverPool.RegisterNode(p.Node()); err == nil {
- p.setValueTracker(nvt)
- p.updateVtParams()
- defer func() {
- p.setValueTracker(nil)
- h.backend.serverPool.UnregisterNode(p.Node())
- }()
- } else {
- return err
- }
- }
- // Register the peer locally
- if err := h.backend.peers.register(p); err != nil {
- p.Log().Error("Light Ethereum peer registration failed", "err", err)
- return err
- }
-
- serverConnectionGauge.Update(int64(h.backend.peers.len()))
-
- connectedAt := mclock.Now()
- defer func() {
- h.backend.peers.unregister(p.id)
- connectionTimer.Update(time.Duration(mclock.Now() - connectedAt))
- serverConnectionGauge.Update(int64(h.backend.peers.len()))
- }()
-
- // Mark the peer starts to be served.
- p.serving.Store(true)
- defer p.serving.Store(false)
-
- // Spawn a main loop to handle all incoming messages.
- for {
- if err := h.handleMsg(p); err != nil {
- p.Log().Debug("Light Ethereum message handling failed", "err", err)
- p.fcServer.DumpLogs()
- return err
- }
- }
-}
-
-// handleMsg is invoked whenever an inbound message is received from a remote
-// peer. The remote connection is torn down upon returning any error.
-func (h *clientHandler) handleMsg(p *serverPeer) error {
- // Read the next message from the remote peer, and ensure it's fully consumed
- msg, err := p.rw.ReadMsg()
- if err != nil {
- return err
- }
- p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size)
-
- if msg.Size > ProtocolMaxMsgSize {
- return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
- }
- defer msg.Discard()
-
- var deliverMsg *Msg
-
- // Handle the message depending on its contents
- switch {
- case msg.Code == AnnounceMsg:
- p.Log().Trace("Received announce message")
- var req announceData
- if err := msg.Decode(&req); err != nil {
- return errResp(ErrDecode, "%v: %v", msg, err)
- }
- if err := req.sanityCheck(); err != nil {
- return err
- }
- update, size := req.Update.decode()
- if p.rejectUpdate(size) {
- return errResp(ErrRequestRejected, "")
- }
- p.updateFlowControl(update)
- p.updateVtParams()
-
- if req.Hash != (common.Hash{}) {
- if p.announceType == announceTypeNone {
- return errResp(ErrUnexpectedResponse, "")
- }
- if p.announceType == announceTypeSigned {
- if err := req.checkSignature(p.ID(), update); err != nil {
- p.Log().Trace("Invalid announcement signature", "err", err)
- return err
- }
- p.Log().Trace("Valid announcement signature")
- }
- p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth)
-
- // Update peer head information first and then notify the announcement
- p.updateHead(req.Hash, req.Number, req.Td)
- }
- case msg.Code == BlockHeadersMsg:
- p.Log().Trace("Received block header response message")
- var resp struct {
- ReqID, BV uint64
- Headers []*types.Header
- }
- if err := msg.Decode(&resp); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
- p.answeredRequest(resp.ReqID)
-
- deliverMsg = &Msg{
- MsgType: MsgBlockHeaders,
- ReqID: resp.ReqID,
- Obj: resp.Headers,
- }
- case msg.Code == BlockBodiesMsg:
- p.Log().Trace("Received block bodies response")
- var resp struct {
- ReqID, BV uint64
- Data []*types.Body
- }
- if err := msg.Decode(&resp); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
- p.answeredRequest(resp.ReqID)
- deliverMsg = &Msg{
- MsgType: MsgBlockBodies,
- ReqID: resp.ReqID,
- Obj: resp.Data,
- }
- case msg.Code == CodeMsg:
- p.Log().Trace("Received code response")
- var resp struct {
- ReqID, BV uint64
- Data [][]byte
- }
- if err := msg.Decode(&resp); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
- p.answeredRequest(resp.ReqID)
- deliverMsg = &Msg{
- MsgType: MsgCode,
- ReqID: resp.ReqID,
- Obj: resp.Data,
- }
- case msg.Code == ReceiptsMsg:
- p.Log().Trace("Received receipts response")
- var resp struct {
- ReqID, BV uint64
- Receipts []types.Receipts
- }
- if err := msg.Decode(&resp); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
- p.answeredRequest(resp.ReqID)
- deliverMsg = &Msg{
- MsgType: MsgReceipts,
- ReqID: resp.ReqID,
- Obj: resp.Receipts,
- }
- case msg.Code == ProofsV2Msg:
- p.Log().Trace("Received les/2 proofs response")
- var resp struct {
- ReqID, BV uint64
- Data trienode.ProofList
- }
- if err := msg.Decode(&resp); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
- p.answeredRequest(resp.ReqID)
- deliverMsg = &Msg{
- MsgType: MsgProofsV2,
- ReqID: resp.ReqID,
- Obj: resp.Data,
- }
- case msg.Code == HelperTrieProofsMsg:
- p.Log().Trace("Received helper trie proof response")
- var resp struct {
- ReqID, BV uint64
- Data HelperTrieResps
- }
- if err := msg.Decode(&resp); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
- p.answeredRequest(resp.ReqID)
- deliverMsg = &Msg{
- MsgType: MsgHelperTrieProofs,
- ReqID: resp.ReqID,
- Obj: resp.Data,
- }
- case msg.Code == TxStatusMsg:
- p.Log().Trace("Received tx status response")
- var resp struct {
- ReqID, BV uint64
- Status []light.TxStatus
- }
- if err := msg.Decode(&resp); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
- p.answeredRequest(resp.ReqID)
- deliverMsg = &Msg{
- MsgType: MsgTxStatus,
- ReqID: resp.ReqID,
- Obj: resp.Status,
- }
- case msg.Code == StopMsg && p.version >= lpv3:
- p.freeze()
- h.backend.retriever.frozen(p)
- p.Log().Debug("Service stopped")
- case msg.Code == ResumeMsg && p.version >= lpv3:
- var bv uint64
- if err := msg.Decode(&bv); err != nil {
- return errResp(ErrDecode, "msg %v: %v", msg, err)
- }
- p.fcServer.ResumeFreeze(bv)
- p.unfreeze()
- p.Log().Debug("Service resumed")
- default:
- p.Log().Trace("Received invalid message", "code", msg.Code)
- return errResp(ErrInvalidMsgCode, "%v", msg.Code)
- }
- // Deliver the received response to retriever.
- if deliverMsg != nil {
- if err := h.backend.retriever.deliver(p, deliverMsg); err != nil {
- if val := p.errCount.Add(1, mclock.Now()); val > maxResponseErrors {
- return err
- }
- }
- }
- return nil
-}
diff --git a/les/commons.go b/les/commons.go
deleted file mode 100644
index cb3fc430b..000000000
--- a/les/commons.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2018 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 .
-
-package les
-
-import (
- "fmt"
- "math/big"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "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/eth/ethconfig"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
-)
-
-func errResp(code errCode, format string, v ...interface{}) error {
- return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
-}
-
-type chainReader interface {
- CurrentHeader() *types.Header
-}
-
-// lesCommons contains fields needed by both server and client.
-type lesCommons struct {
- genesis common.Hash
- config *ethconfig.Config
- chainConfig *params.ChainConfig
- iConfig *light.IndexerConfig
- chainDb, lesDb ethdb.Database
- chainReader chainReader
- chtIndexer, bloomTrieIndexer *core.ChainIndexer
-
- closeCh chan struct{}
- wg sync.WaitGroup
-}
-
-// NodeInfo represents a short summary of the Ethereum sub-protocol metadata
-// known about the host peer.
-type NodeInfo struct {
- Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Goerli=5)
- Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
- Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
- Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
- Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block
-}
-
-// makeProtocols creates protocol descriptors for the given LES versions.
-func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error, peerInfo func(id enode.ID) interface{}, dialCandidates enode.Iterator) []p2p.Protocol {
- protos := make([]p2p.Protocol, len(versions))
- for i, version := range versions {
- version := version
- protos[i] = p2p.Protocol{
- Name: "les",
- Version: version,
- Length: ProtocolLengths[version],
- NodeInfo: c.nodeInfo,
- Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
- return runPeer(version, peer, rw)
- },
- PeerInfo: peerInfo,
- DialCandidates: dialCandidates,
- }
- }
- return protos
-}
-
-// nodeInfo retrieves some protocol metadata about the running host node.
-func (c *lesCommons) nodeInfo() interface{} {
- head := c.chainReader.CurrentHeader()
- hash := head.Hash()
- return &NodeInfo{
- Network: c.config.NetworkId,
- Difficulty: rawdb.ReadTd(c.chainDb, hash, head.Number.Uint64()),
- Genesis: c.genesis,
- Config: c.chainConfig,
- Head: hash,
- }
-}
diff --git a/les/costtracker.go b/les/costtracker.go
deleted file mode 100644
index 695d54e14..000000000
--- a/les/costtracker.go
+++ /dev/null
@@ -1,517 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "encoding/binary"
- "math"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/eth/ethconfig"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/flowcontrol"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-const makeCostStats = false // make request cost statistics during operation
-
-var (
- // average request cost estimates based on serving time
- reqAvgTimeCost = requestCostTable{
- GetBlockHeadersMsg: {150000, 30000},
- GetBlockBodiesMsg: {0, 700000},
- GetReceiptsMsg: {0, 1000000},
- GetCodeMsg: {0, 450000},
- GetProofsV2Msg: {0, 600000},
- GetHelperTrieProofsMsg: {0, 1000000},
- SendTxV2Msg: {0, 450000},
- GetTxStatusMsg: {0, 250000},
- }
- // maximum incoming message size estimates
- reqMaxInSize = requestCostTable{
- GetBlockHeadersMsg: {40, 0},
- GetBlockBodiesMsg: {0, 40},
- GetReceiptsMsg: {0, 40},
- GetCodeMsg: {0, 80},
- GetProofsV2Msg: {0, 80},
- GetHelperTrieProofsMsg: {0, 20},
- SendTxV2Msg: {0, 16500},
- GetTxStatusMsg: {0, 50},
- }
- // maximum outgoing message size estimates
- reqMaxOutSize = requestCostTable{
- GetBlockHeadersMsg: {0, 556},
- GetBlockBodiesMsg: {0, 100000},
- GetReceiptsMsg: {0, 200000},
- GetCodeMsg: {0, 50000},
- GetProofsV2Msg: {0, 4000},
- GetHelperTrieProofsMsg: {0, 4000},
- SendTxV2Msg: {0, 100},
- GetTxStatusMsg: {0, 100},
- }
- // request amounts that have to fit into the minimum buffer size minBufferMultiplier times
- minBufferReqAmount = map[uint64]uint64{
- GetBlockHeadersMsg: 192,
- GetBlockBodiesMsg: 1,
- GetReceiptsMsg: 1,
- GetCodeMsg: 1,
- GetProofsV2Msg: 1,
- GetHelperTrieProofsMsg: 16,
- SendTxV2Msg: 8,
- GetTxStatusMsg: 64,
- }
- minBufferMultiplier = 3
-)
-
-const (
- maxCostFactor = 2 // ratio of maximum and average cost estimates
- bufLimitRatio = 6000 // fixed bufLimit/MRR ratio
- gfUsageThreshold = 0.5
- gfUsageTC = time.Second
- gfRaiseTC = time.Second * 200
- gfDropTC = time.Second * 50
- gfDbKey = "_globalCostFactorV6"
-)
-
-// costTracker is responsible for calculating costs and cost estimates on the
-// server side. It continuously updates the global cost factor which is defined
-// as the number of cost units per nanosecond of serving time in a single thread.
-// It is based on statistics collected during serving requests in high-load periods
-// and practically acts as a one-dimension request price scaling factor over the
-// pre-defined cost estimate table.
-//
-// The reason for dynamically maintaining the global factor on the server side is:
-// the estimated time cost of the request is fixed(hardcoded) but the configuration
-// of the machine running the server is really different. Therefore, the request serving
-// time in different machine will vary greatly. And also, the request serving time
-// in same machine may vary greatly with different request pressure.
-//
-// In order to more effectively limit resources, we apply the global factor to serving
-// time to make the result as close as possible to the estimated time cost no matter
-// the server is slow or fast. And also we scale the totalRecharge with global factor
-// so that fast server can serve more requests than estimation and slow server can
-// reduce request pressure.
-//
-// Instead of scaling the cost values, the real value of cost units is changed by
-// applying the factor to the serving times. This is more convenient because the
-// changes in the cost factor can be applied immediately without always notifying
-// the clients about the changed cost tables.
-type costTracker struct {
- db ethdb.Database
- stopCh chan chan struct{}
-
- inSizeFactor float64
- outSizeFactor float64
- factor float64
- utilTarget float64
- minBufLimit uint64
-
- gfLock sync.RWMutex
- reqInfoCh chan reqInfo
- totalRechargeCh chan uint64
-
- stats map[uint64][]atomic.Uint64 // Used for testing purpose.
-
- // TestHooks
- testing bool // Disable real cost evaluation for testing purpose.
- testCostList RequestCostList // Customized cost table for testing purpose.
-}
-
-// newCostTracker creates a cost tracker and loads the cost factor statistics from the database.
-// It also returns the minimum capacity that can be assigned to any peer.
-func newCostTracker(db ethdb.Database, config *ethconfig.Config) (*costTracker, uint64) {
- utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100
- ct := &costTracker{
- db: db,
- stopCh: make(chan chan struct{}),
- reqInfoCh: make(chan reqInfo, 100),
- utilTarget: utilTarget,
- }
- if config.LightIngress > 0 {
- ct.inSizeFactor = utilTarget / float64(config.LightIngress)
- }
- if config.LightEgress > 0 {
- ct.outSizeFactor = utilTarget / float64(config.LightEgress)
- }
- if makeCostStats {
- ct.stats = make(map[uint64][]atomic.Uint64)
- for code := range reqAvgTimeCost {
- ct.stats[code] = make([]atomic.Uint64, 10)
- }
- }
- ct.gfLoop()
- costList := ct.makeCostList(ct.globalFactor() * 1.25)
- for _, c := range costList {
- amount := minBufferReqAmount[c.MsgCode]
- cost := c.BaseCost + amount*c.ReqCost
- if cost > ct.minBufLimit {
- ct.minBufLimit = cost
- }
- }
- ct.minBufLimit *= uint64(minBufferMultiplier)
- return ct, (ct.minBufLimit-1)/bufLimitRatio + 1
-}
-
-// stop stops the cost tracker and saves the cost factor statistics to the database
-func (ct *costTracker) stop() {
- stopCh := make(chan struct{})
- ct.stopCh <- stopCh
- <-stopCh
- if makeCostStats {
- ct.printStats()
- }
-}
-
-// makeCostList returns upper cost estimates based on the hardcoded cost estimate
-// tables and the optionally specified incoming/outgoing bandwidth limits
-func (ct *costTracker) makeCostList(globalFactor float64) RequestCostList {
- maxCost := func(avgTimeCost, inSize, outSize uint64) uint64 {
- cost := avgTimeCost * maxCostFactor
- inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor)
- if inSizeCost > cost {
- cost = inSizeCost
- }
- outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor)
- if outSizeCost > cost {
- cost = outSizeCost
- }
- return cost
- }
- var list RequestCostList
- for code, data := range reqAvgTimeCost {
- baseCost := maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost)
- reqCost := maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost)
- if ct.minBufLimit != 0 {
- // if minBufLimit is set then always enforce maximum request cost <= minBufLimit
- maxCost := baseCost + reqCost*minBufferReqAmount[code]
- if maxCost > ct.minBufLimit {
- mul := 0.999 * float64(ct.minBufLimit) / float64(maxCost)
- baseCost = uint64(float64(baseCost) * mul)
- reqCost = uint64(float64(reqCost) * mul)
- }
- }
-
- list = append(list, requestCostListItem{
- MsgCode: code,
- BaseCost: baseCost,
- ReqCost: reqCost,
- })
- }
- return list
-}
-
-// reqInfo contains the estimated time cost and the actual request serving time
-// which acts as a feed source to update factor maintained by costTracker.
-type reqInfo struct {
- // avgTimeCost is the estimated time cost corresponding to maxCostTable.
- avgTimeCost float64
-
- // servingTime is the CPU time corresponding to the actual processing of
- // the request.
- servingTime float64
-
- // msgCode indicates the type of request.
- msgCode uint64
-}
-
-// gfLoop starts an event loop which updates the global cost factor which is
-// calculated as a weighted average of the average estimate / serving time ratio.
-// The applied weight equals the serving time if gfUsage is over a threshold,
-// zero otherwise. gfUsage is the recent average serving time per time unit in
-// an exponential moving window. This ensures that statistics are collected only
-// under high-load circumstances where the measured serving times are relevant.
-// The total recharge parameter of the flow control system which controls the
-// total allowed serving time per second but nominated in cost units, should
-// also be scaled with the cost factor and is also updated by this loop.
-func (ct *costTracker) gfLoop() {
- var (
- factor, totalRecharge float64
- gfLog, recentTime, recentAvg float64
-
- lastUpdate, expUpdate = mclock.Now(), mclock.Now()
- )
-
- // Load historical cost factor statistics from the database.
- data, _ := ct.db.Get([]byte(gfDbKey))
- if len(data) == 8 {
- gfLog = math.Float64frombits(binary.BigEndian.Uint64(data[:]))
- }
- ct.factor = math.Exp(gfLog)
- factor, totalRecharge = ct.factor, ct.utilTarget*ct.factor
-
- // In order to perform factor data statistics under the high request pressure,
- // we only adjust factor when recent factor usage beyond the threshold.
- threshold := gfUsageThreshold * float64(gfUsageTC) * ct.utilTarget / flowcontrol.FixedPointMultiplier
-
- go func() {
- saveCostFactor := func() {
- var data [8]byte
- binary.BigEndian.PutUint64(data[:], math.Float64bits(gfLog))
- ct.db.Put([]byte(gfDbKey), data[:])
- log.Debug("global cost factor saved", "value", factor)
- }
- saveTicker := time.NewTicker(time.Minute * 10)
- defer saveTicker.Stop()
-
- for {
- select {
- case r := <-ct.reqInfoCh:
- relCost := int64(factor * r.servingTime * 100 / r.avgTimeCost) // Convert the value to a percentage form
-
- // Record more metrics if we are debugging
- if metrics.EnabledExpensive {
- switch r.msgCode {
- case GetBlockHeadersMsg:
- relativeCostHeaderHistogram.Update(relCost)
- case GetBlockBodiesMsg:
- relativeCostBodyHistogram.Update(relCost)
- case GetReceiptsMsg:
- relativeCostReceiptHistogram.Update(relCost)
- case GetCodeMsg:
- relativeCostCodeHistogram.Update(relCost)
- case GetProofsV2Msg:
- relativeCostProofHistogram.Update(relCost)
- case GetHelperTrieProofsMsg:
- relativeCostHelperProofHistogram.Update(relCost)
- case SendTxV2Msg:
- relativeCostSendTxHistogram.Update(relCost)
- case GetTxStatusMsg:
- relativeCostTxStatusHistogram.Update(relCost)
- }
- }
- // SendTxV2 and GetTxStatus requests are two special cases.
- // All other requests will only put pressure on the database, and
- // the corresponding delay is relatively stable. While these two
- // requests involve txpool query, which is usually unstable.
- //
- // TODO(rjl493456442) fixes this.
- if r.msgCode == SendTxV2Msg || r.msgCode == GetTxStatusMsg {
- continue
- }
- requestServedMeter.Mark(int64(r.servingTime))
- requestServedTimer.Update(time.Duration(r.servingTime))
- requestEstimatedMeter.Mark(int64(r.avgTimeCost / factor))
- requestEstimatedTimer.Update(time.Duration(r.avgTimeCost / factor))
- relativeCostHistogram.Update(relCost)
-
- now := mclock.Now()
- dt := float64(now - expUpdate)
- expUpdate = now
- exp := math.Exp(-dt / float64(gfUsageTC))
-
- // calculate factor correction until now, based on previous values
- var gfCorr float64
- max := recentTime
- if recentAvg > max {
- max = recentAvg
- }
- // we apply continuous correction when MAX(recentTime, recentAvg) > threshold
- if max > threshold {
- // calculate correction time between last expUpdate and now
- if max*exp >= threshold {
- gfCorr = dt
- } else {
- gfCorr = math.Log(max/threshold) * float64(gfUsageTC)
- }
- // calculate log(factor) correction with the right direction and time constant
- if recentTime > recentAvg {
- // drop factor if actual serving times are larger than average estimates
- gfCorr /= -float64(gfDropTC)
- } else {
- // raise factor if actual serving times are smaller than average estimates
- gfCorr /= float64(gfRaiseTC)
- }
- }
- // update recent cost values with current request
- recentTime = recentTime*exp + r.servingTime
- recentAvg = recentAvg*exp + r.avgTimeCost/factor
-
- if gfCorr != 0 {
- // Apply the correction to factor
- gfLog += gfCorr
- factor = math.Exp(gfLog)
- // Notify outside modules the new factor and totalRecharge.
- if time.Duration(now-lastUpdate) > time.Second {
- totalRecharge, lastUpdate = ct.utilTarget*factor, now
- ct.gfLock.Lock()
- ct.factor = factor
- ch := ct.totalRechargeCh
- ct.gfLock.Unlock()
- if ch != nil {
- select {
- case ct.totalRechargeCh <- uint64(totalRecharge):
- default:
- }
- }
- globalFactorGauge.Update(int64(1000 * factor))
- log.Debug("global cost factor updated", "factor", factor)
- }
- }
- recentServedGauge.Update(int64(recentTime))
- recentEstimatedGauge.Update(int64(recentAvg))
-
- case <-saveTicker.C:
- saveCostFactor()
-
- case stopCh := <-ct.stopCh:
- saveCostFactor()
- close(stopCh)
- return
- }
- }
- }()
-}
-
-// globalFactor returns the current value of the global cost factor
-func (ct *costTracker) globalFactor() float64 {
- ct.gfLock.RLock()
- defer ct.gfLock.RUnlock()
-
- return ct.factor
-}
-
-// totalRecharge returns the current total recharge parameter which is used by
-// flowcontrol.ClientManager and is scaled by the global cost factor
-func (ct *costTracker) totalRecharge() uint64 {
- ct.gfLock.RLock()
- defer ct.gfLock.RUnlock()
-
- return uint64(ct.factor * ct.utilTarget)
-}
-
-// subscribeTotalRecharge returns all future updates to the total recharge value
-// through a channel and also returns the current value
-func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 {
- ct.gfLock.Lock()
- defer ct.gfLock.Unlock()
-
- ct.totalRechargeCh = ch
- return uint64(ct.factor * ct.utilTarget)
-}
-
-// updateStats updates the global cost factor and (if enabled) the real cost vs.
-// average estimate statistics
-func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) {
- avg := reqAvgTimeCost[code]
- avgTimeCost := avg.baseCost + amount*avg.reqCost
- select {
- case ct.reqInfoCh <- reqInfo{float64(avgTimeCost), float64(servingTime), code}:
- default:
- }
- if makeCostStats {
- realCost <<= 4
- l := 0
- for l < 9 && realCost > avgTimeCost {
- l++
- realCost >>= 1
- }
- ct.stats[code][l].Add(1)
- }
-}
-
-// realCost calculates the final cost of a request based on actual serving time,
-// incoming and outgoing message size
-//
-// Note: message size is only taken into account if bandwidth limitation is applied
-// and the cost based on either message size is greater than the cost based on
-// serving time. A maximum of the three costs is applied instead of their sum
-// because the three limited resources (serving thread time and i/o bandwidth) can
-// also be maxed out simultaneously.
-func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 {
- cost := float64(servingTime)
- inSizeCost := float64(inSize) * ct.inSizeFactor
- if inSizeCost > cost {
- cost = inSizeCost
- }
- outSizeCost := float64(outSize) * ct.outSizeFactor
- if outSizeCost > cost {
- cost = outSizeCost
- }
- return uint64(cost * ct.globalFactor())
-}
-
-// printStats prints the distribution of real request cost relative to the average estimates
-func (ct *costTracker) printStats() {
- if ct.stats == nil {
- return
- }
- for code, arr := range ct.stats {
- log.Info("Request cost statistics", "code", code, "1/16", arr[0].Load(), "1/8", arr[1].Load(), "1/4", arr[2].Load(), "1/2", arr[3].Load(), "1", arr[4].Load(), "2", arr[5].Load(), "4", arr[6].Load(), "8", arr[7].Load(), "16", arr[8].Load(), ">16", arr[9].Load())
- }
-}
-
-type (
- // requestCostTable assigns a cost estimate function to each request type
- // which is a linear function of the requested amount
- // (cost = baseCost + reqCost * amount)
- requestCostTable map[uint64]*requestCosts
- requestCosts struct {
- baseCost, reqCost uint64
- }
-
- // RequestCostList is a list representation of request costs which is used for
- // database storage and communication through the network
- RequestCostList []requestCostListItem
- requestCostListItem struct {
- MsgCode, BaseCost, ReqCost uint64
- }
-)
-
-// getMaxCost calculates the estimated cost for a given request type and amount
-func (table requestCostTable) getMaxCost(code, amount uint64) uint64 {
- costs := table[code]
- return costs.baseCost + amount*costs.reqCost
-}
-
-// decode converts a cost list to a cost table
-func (list RequestCostList) decode(protocolLength uint64) requestCostTable {
- table := make(requestCostTable)
- for _, e := range list {
- if e.MsgCode < protocolLength {
- table[e.MsgCode] = &requestCosts{
- baseCost: e.BaseCost,
- reqCost: e.ReqCost,
- }
- }
- }
- return table
-}
-
-// testCostList returns a dummy request cost list used by tests
-func testCostList(testCost uint64) RequestCostList {
- cl := make(RequestCostList, len(reqAvgTimeCost))
- var max uint64
- for code := range reqAvgTimeCost {
- if code > max {
- max = code
- }
- }
- i := 0
- for code := uint64(0); code <= max; code++ {
- if _, ok := reqAvgTimeCost[code]; ok {
- cl[i].MsgCode = code
- cl[i].BaseCost = testCost
- cl[i].ReqCost = 0
- i++
- }
- }
- return cl
-}
diff --git a/les/distributor.go b/les/distributor.go
deleted file mode 100644
index a0319c67f..000000000
--- a/les/distributor.go
+++ /dev/null
@@ -1,313 +0,0 @@
-// Copyright 2017 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 .
-
-package les
-
-import (
- "container/list"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/les/utils"
-)
-
-// requestDistributor implements a mechanism that distributes requests to
-// suitable peers, obeying flow control rules and prioritizing them in creation
-// order (even when a resend is necessary).
-type requestDistributor struct {
- clock mclock.Clock
- reqQueue *list.List
- lastReqOrder uint64
- peers map[distPeer]struct{}
- peerLock sync.RWMutex
- loopChn chan struct{}
- loopNextSent bool
- lock sync.Mutex
-
- closeCh chan struct{}
- wg sync.WaitGroup
-}
-
-// distPeer is an LES server peer interface for the request distributor.
-// waitBefore returns either the necessary waiting time before sending a request
-// with the given upper estimated cost or the estimated remaining relative buffer
-// value after sending such a request (in which case the request can be sent
-// immediately). At least one of these values is always zero.
-type distPeer interface {
- waitBefore(uint64) (time.Duration, float64)
- canQueue() bool
- queueSend(f func()) bool
-}
-
-// distReq is the request abstraction used by the distributor. It is based on
-// three callback functions:
-// - getCost returns the upper estimate of the cost of sending the request to a given peer
-// - canSend tells if the server peer is suitable to serve the request
-// - request prepares sending the request to the given peer and returns a function that
-// does the actual sending. Request order should be preserved but the callback itself should not
-// block until it is sent because other peers might still be able to receive requests while
-// one of them is blocking. Instead, the returned function is put in the peer's send queue.
-type distReq struct {
- getCost func(distPeer) uint64
- canSend func(distPeer) bool
- request func(distPeer) func()
-
- reqOrder uint64
- sentChn chan distPeer
- element *list.Element
- waitForPeers mclock.AbsTime
- enterQueue mclock.AbsTime
-}
-
-// newRequestDistributor creates a new request distributor
-func newRequestDistributor(peers *serverPeerSet, clock mclock.Clock) *requestDistributor {
- d := &requestDistributor{
- clock: clock,
- reqQueue: list.New(),
- loopChn: make(chan struct{}, 2),
- closeCh: make(chan struct{}),
- peers: make(map[distPeer]struct{}),
- }
- if peers != nil {
- peers.subscribe(d)
- }
- d.wg.Add(1)
- go d.loop()
- return d
-}
-
-// registerPeer implements peerSetNotify
-func (d *requestDistributor) registerPeer(p *serverPeer) {
- d.peerLock.Lock()
- d.peers[p] = struct{}{}
- d.peerLock.Unlock()
-}
-
-// unregisterPeer implements peerSetNotify
-func (d *requestDistributor) unregisterPeer(p *serverPeer) {
- d.peerLock.Lock()
- delete(d.peers, p)
- d.peerLock.Unlock()
-}
-
-// registerTestPeer adds a new test peer
-func (d *requestDistributor) registerTestPeer(p distPeer) {
- d.peerLock.Lock()
- d.peers[p] = struct{}{}
- d.peerLock.Unlock()
-}
-
-var (
- // distMaxWait is the maximum waiting time after which further necessary waiting
- // times are recalculated based on new feedback from the servers
- distMaxWait = time.Millisecond * 50
-
- // waitForPeers is the time window in which a request does not fail even if it
- // has no suitable peers to send to at the moment
- waitForPeers = time.Second * 3
-)
-
-// main event loop
-func (d *requestDistributor) loop() {
- defer d.wg.Done()
- for {
- select {
- case <-d.closeCh:
- d.lock.Lock()
- elem := d.reqQueue.Front()
- for elem != nil {
- req := elem.Value.(*distReq)
- close(req.sentChn)
- req.sentChn = nil
- elem = elem.Next()
- }
- d.lock.Unlock()
- return
- case <-d.loopChn:
- d.lock.Lock()
- d.loopNextSent = false
- loop:
- for {
- peer, req, wait := d.nextRequest()
- if req != nil && wait == 0 {
- chn := req.sentChn // save sentChn because remove sets it to nil
- d.remove(req)
- send := req.request(peer)
- if send != nil {
- peer.queueSend(send)
- requestSendDelay.Update(time.Duration(d.clock.Now() - req.enterQueue))
- }
- chn <- peer
- close(chn)
- } else {
- if wait == 0 {
- // no request to send and nothing to wait for; the next
- // queued request will wake up the loop
- break loop
- }
- d.loopNextSent = true // a "next" signal has been sent, do not send another one until this one has been received
- if wait > distMaxWait {
- // waiting times may be reduced by incoming request replies, if it is too long, recalculate it periodically
- wait = distMaxWait
- }
- go func() {
- d.clock.Sleep(wait)
- d.loopChn <- struct{}{}
- }()
- break loop
- }
- }
- d.lock.Unlock()
- }
- }
-}
-
-// selectPeerItem represents a peer to be selected for a request by weightedRandomSelect
-type selectPeerItem struct {
- peer distPeer
- req *distReq
- weight uint64
-}
-
-func selectPeerWeight(i interface{}) uint64 {
- return i.(selectPeerItem).weight
-}
-
-// nextRequest returns the next possible request from any peer, along with the
-// associated peer and necessary waiting time
-func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) {
- checkedPeers := make(map[distPeer]struct{})
- elem := d.reqQueue.Front()
- var (
- bestWait time.Duration
- sel *utils.WeightedRandomSelect
- )
-
- d.peerLock.RLock()
- defer d.peerLock.RUnlock()
-
- peerCount := len(d.peers)
- for (len(checkedPeers) < peerCount || elem == d.reqQueue.Front()) && elem != nil {
- req := elem.Value.(*distReq)
- canSend := false
- now := d.clock.Now()
- if req.waitForPeers > now {
- canSend = true
- wait := time.Duration(req.waitForPeers - now)
- if bestWait == 0 || wait < bestWait {
- bestWait = wait
- }
- }
- for peer := range d.peers {
- if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) {
- canSend = true
- cost := req.getCost(peer)
- wait, bufRemain := peer.waitBefore(cost)
- if wait == 0 {
- if sel == nil {
- sel = utils.NewWeightedRandomSelect(selectPeerWeight)
- }
- sel.Update(selectPeerItem{peer: peer, req: req, weight: uint64(bufRemain*1000000) + 1})
- } else {
- if bestWait == 0 || wait < bestWait {
- bestWait = wait
- }
- }
- checkedPeers[peer] = struct{}{}
- }
- }
- next := elem.Next()
- if !canSend && elem == d.reqQueue.Front() {
- close(req.sentChn)
- d.remove(req)
- }
- elem = next
- }
-
- if sel != nil {
- c := sel.Choose().(selectPeerItem)
- return c.peer, c.req, 0
- }
- return nil, nil, bestWait
-}
-
-// queue adds a request to the distribution queue, returns a channel where the
-// receiving peer is sent once the request has been sent (request callback returned).
-// If the request is cancelled or timed out without suitable peers, the channel is
-// closed without sending any peer references to it.
-func (d *requestDistributor) queue(r *distReq) chan distPeer {
- d.lock.Lock()
- defer d.lock.Unlock()
-
- if r.reqOrder == 0 {
- d.lastReqOrder++
- r.reqOrder = d.lastReqOrder
- r.waitForPeers = d.clock.Now().Add(waitForPeers)
- }
- // Assign the timestamp when the request is queued no matter it's
- // a new one or re-queued one.
- r.enterQueue = d.clock.Now()
-
- back := d.reqQueue.Back()
- if back == nil || r.reqOrder > back.Value.(*distReq).reqOrder {
- r.element = d.reqQueue.PushBack(r)
- } else {
- before := d.reqQueue.Front()
- for before.Value.(*distReq).reqOrder < r.reqOrder {
- before = before.Next()
- }
- r.element = d.reqQueue.InsertBefore(r, before)
- }
-
- if !d.loopNextSent {
- d.loopNextSent = true
- d.loopChn <- struct{}{}
- }
-
- r.sentChn = make(chan distPeer, 1)
- return r.sentChn
-}
-
-// cancel removes a request from the queue if it has not been sent yet (returns
-// false if it has been sent already). It is guaranteed that the callback functions
-// will not be called after cancel returns.
-func (d *requestDistributor) cancel(r *distReq) bool {
- d.lock.Lock()
- defer d.lock.Unlock()
-
- if r.sentChn == nil {
- return false
- }
-
- close(r.sentChn)
- d.remove(r)
- return true
-}
-
-// remove removes a request from the queue
-func (d *requestDistributor) remove(r *distReq) {
- r.sentChn = nil
- if r.element != nil {
- d.reqQueue.Remove(r.element)
- r.element = nil
- }
-}
-
-func (d *requestDistributor) close() {
- close(d.closeCh)
- d.wg.Wait()
-}
diff --git a/les/distributor_test.go b/les/distributor_test.go
deleted file mode 100644
index 9a93dba14..000000000
--- a/les/distributor_test.go
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2017 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 .
-
-package les
-
-import (
- "math/rand"
- "sync"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-type testDistReq struct {
- cost, procTime, order uint64
- canSendTo map[*testDistPeer]struct{}
-}
-
-func (r *testDistReq) getCost(dp distPeer) uint64 {
- return r.cost
-}
-
-func (r *testDistReq) canSend(dp distPeer) bool {
- _, ok := r.canSendTo[dp.(*testDistPeer)]
- return ok
-}
-
-func (r *testDistReq) request(dp distPeer) func() {
- return func() { dp.(*testDistPeer).send(r) }
-}
-
-type testDistPeer struct {
- sent []*testDistReq
- sumCost uint64
- lock sync.RWMutex
-}
-
-func (p *testDistPeer) send(r *testDistReq) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- p.sent = append(p.sent, r)
- p.sumCost += r.cost
-}
-
-func (p *testDistPeer) worker(t *testing.T, checkOrder bool, stop chan struct{}) {
- var last uint64
- for {
- wait := time.Millisecond
- p.lock.Lock()
- if len(p.sent) > 0 {
- rq := p.sent[0]
- wait = time.Duration(rq.procTime)
- p.sumCost -= rq.cost
- if checkOrder {
- if rq.order <= last {
- t.Errorf("Requests processed in wrong order")
- }
- last = rq.order
- }
- p.sent = p.sent[1:]
- }
- p.lock.Unlock()
- select {
- case <-stop:
- return
- case <-time.After(wait):
- }
- }
-}
-
-const (
- testDistBufLimit = 10000000
- testDistMaxCost = 1000000
- testDistPeerCount = 2
- testDistReqCount = 10
- testDistMaxResendCount = 3
-)
-
-func (p *testDistPeer) waitBefore(cost uint64) (time.Duration, float64) {
- p.lock.RLock()
- sumCost := p.sumCost + cost
- p.lock.RUnlock()
- if sumCost < testDistBufLimit {
- return 0, float64(testDistBufLimit-sumCost) / float64(testDistBufLimit)
- }
- return time.Duration(sumCost - testDistBufLimit), 0
-}
-
-func (p *testDistPeer) canQueue() bool {
- return true
-}
-
-func (p *testDistPeer) queueSend(f func()) bool {
- f()
- return true
-}
-
-func TestRequestDistributor(t *testing.T) {
- testRequestDistributor(t, false)
-}
-
-func TestRequestDistributorResend(t *testing.T) {
- testRequestDistributor(t, true)
-}
-
-func testRequestDistributor(t *testing.T, resend bool) {
- stop := make(chan struct{})
- defer close(stop)
-
- dist := newRequestDistributor(nil, &mclock.System{})
- var peers [testDistPeerCount]*testDistPeer
- for i := range peers {
- peers[i] = &testDistPeer{}
- go peers[i].worker(t, !resend, stop)
- dist.registerTestPeer(peers[i])
- }
- // Disable the mechanism that we will wait a few time for request
- // even there is no suitable peer to send right now.
- waitForPeers = 0
-
- var wg sync.WaitGroup
-
- for i := 1; i <= testDistReqCount; i++ {
- cost := uint64(rand.Int63n(testDistMaxCost))
- procTime := uint64(rand.Int63n(int64(cost + 1)))
- rq := &testDistReq{
- cost: cost,
- procTime: procTime,
- order: uint64(i),
- canSendTo: make(map[*testDistPeer]struct{}),
- }
- for _, peer := range peers {
- if rand.Intn(2) != 0 {
- rq.canSendTo[peer] = struct{}{}
- }
- }
-
- wg.Add(1)
- req := &distReq{
- getCost: rq.getCost,
- canSend: rq.canSend,
- request: rq.request,
- }
- chn := dist.queue(req)
- go func() {
- cnt := 1
- if resend && len(rq.canSendTo) != 0 {
- cnt = rand.Intn(testDistMaxResendCount) + 1
- }
- for i := 0; i < cnt; i++ {
- if i != 0 {
- chn = dist.queue(req)
- }
- p := <-chn
- if p == nil {
- if len(rq.canSendTo) != 0 {
- t.Errorf("Request that could have been sent was dropped")
- }
- } else {
- peer := p.(*testDistPeer)
- if _, ok := rq.canSendTo[peer]; !ok {
- t.Errorf("Request sent to wrong peer")
- }
- }
- }
- wg.Done()
- }()
- if rand.Intn(1000) == 0 {
- time.Sleep(time.Duration(rand.Intn(5000000)))
- }
- }
-
- wg.Wait()
-}
diff --git a/les/enr_entry.go b/les/enr_entry.go
deleted file mode 100644
index 307313fb1..000000000
--- a/les/enr_entry.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/p2p/dnsdisc"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// lesEntry is the "les" ENR entry. This is set for LES servers only.
-type lesEntry struct {
- // Ignore additional fields (for forward compatibility).
- VfxVersion uint
- Rest []rlp.RawValue `rlp:"tail"`
-}
-
-func (lesEntry) ENRKey() string { return "les" }
-
-// ethEntry is the "eth" ENR entry. This is redeclared here to avoid depending on package eth.
-type ethEntry struct {
- ForkID forkid.ID
- Tail []rlp.RawValue `rlp:"tail"`
-}
-
-func (ethEntry) ENRKey() string { return "eth" }
-
-// setupDiscovery creates the node discovery source for the eth protocol.
-func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) {
- it := enode.NewFairMix(0)
-
- // Enable DNS discovery.
- if len(eth.config.EthDiscoveryURLs) != 0 {
- client := dnsdisc.NewClient(dnsdisc.Config{})
- dns, err := client.NewIterator(eth.config.EthDiscoveryURLs...)
- if err != nil {
- return nil, err
- }
- it.AddSource(dns)
- }
-
- // Enable DHT.
- if eth.udpEnabled {
- it.AddSource(eth.p2pServer.DiscV5.RandomNodes())
- }
-
- forkFilter := forkid.NewFilter(eth.blockchain)
- iterator := enode.Filter(it, func(n *enode.Node) bool { return nodeIsServer(forkFilter, n) })
- return iterator, nil
-}
-
-// nodeIsServer checks whether n is an LES server node.
-func nodeIsServer(forkFilter forkid.Filter, n *enode.Node) bool {
- var les lesEntry
- var eth ethEntry
- return n.Load(&les) == nil && n.Load(ð) == nil && forkFilter(eth.ForkID) == nil
-}
diff --git a/les/flowcontrol/control.go b/les/flowcontrol/control.go
deleted file mode 100644
index 76a241fa5..000000000
--- a/les/flowcontrol/control.go
+++ /dev/null
@@ -1,433 +0,0 @@
-// Copyright 2016 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 .
-
-// Package flowcontrol implements a client side flow control mechanism
-package flowcontrol
-
-import (
- "fmt"
- "math"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/log"
-)
-
-const (
- // fcTimeConst is the time constant applied for MinRecharge during linear
- // buffer recharge period
- fcTimeConst = time.Millisecond
- // DecParamDelay is applied at server side when decreasing capacity in order to
- // avoid a buffer underrun error due to requests sent by the client before
- // receiving the capacity update announcement
- DecParamDelay = time.Second * 2
- // keepLogs is the duration of keeping logs; logging is not used if zero
- keepLogs = 0
-)
-
-// ServerParams are the flow control parameters specified by a server for a client
-//
-// Note: a server can assign different amounts of capacity to each client by giving
-// different parameters to them.
-type ServerParams struct {
- BufLimit, MinRecharge uint64
-}
-
-// scheduledUpdate represents a delayed flow control parameter update
-type scheduledUpdate struct {
- time mclock.AbsTime
- params ServerParams
-}
-
-// ClientNode is the flow control system's representation of a client
-// (used in server mode only)
-type ClientNode struct {
- params ServerParams
- bufValue int64
- lastTime mclock.AbsTime
- updateSchedule []scheduledUpdate
- sumCost uint64 // sum of req costs received from this client
- accepted map[uint64]uint64 // value = sumCost after accepting the given req
- connected bool
- lock sync.Mutex
- cm *ClientManager
- log *logger
- cmNodeFields
-}
-
-// NewClientNode returns a new ClientNode
-func NewClientNode(cm *ClientManager, params ServerParams) *ClientNode {
- node := &ClientNode{
- cm: cm,
- params: params,
- bufValue: int64(params.BufLimit),
- lastTime: cm.clock.Now(),
- accepted: make(map[uint64]uint64),
- connected: true,
- }
- if keepLogs > 0 {
- node.log = newLogger(keepLogs)
- }
- cm.connect(node)
- return node
-}
-
-// Disconnect should be called when a client is disconnected
-func (node *ClientNode) Disconnect() {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- node.connected = false
- node.cm.disconnect(node)
-}
-
-// BufferStatus returns the current buffer value and limit
-func (node *ClientNode) BufferStatus() (uint64, uint64) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- if !node.connected {
- return 0, 0
- }
- now := node.cm.clock.Now()
- node.update(now)
- node.cm.updateBuffer(node, 0, now)
- bv := node.bufValue
- if bv < 0 {
- bv = 0
- }
- return uint64(bv), node.params.BufLimit
-}
-
-// OneTimeCost subtracts the given amount from the node's buffer.
-//
-// Note: this call can take the buffer into the negative region internally.
-// In this case zero buffer value is returned by exported calls and no requests
-// are accepted.
-func (node *ClientNode) OneTimeCost(cost uint64) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- now := node.cm.clock.Now()
- node.update(now)
- node.bufValue -= int64(cost)
- node.cm.updateBuffer(node, -int64(cost), now)
-}
-
-// Freeze notifies the client manager about a client freeze event in which case
-// the total capacity allowance is slightly reduced.
-func (node *ClientNode) Freeze() {
- node.lock.Lock()
- frozenCap := node.params.MinRecharge
- node.lock.Unlock()
- node.cm.reduceTotalCapacity(frozenCap)
-}
-
-// update recalculates the buffer value at a specified time while also performing
-// scheduled flow control parameter updates if necessary
-func (node *ClientNode) update(now mclock.AbsTime) {
- for len(node.updateSchedule) > 0 && node.updateSchedule[0].time <= now {
- node.recalcBV(node.updateSchedule[0].time)
- node.updateParams(node.updateSchedule[0].params, now)
- node.updateSchedule = node.updateSchedule[1:]
- }
- node.recalcBV(now)
-}
-
-// recalcBV recalculates the buffer value at a specified time
-func (node *ClientNode) recalcBV(now mclock.AbsTime) {
- dt := uint64(now - node.lastTime)
- if now < node.lastTime {
- dt = 0
- }
- node.bufValue += int64(node.params.MinRecharge * dt / uint64(fcTimeConst))
- if node.bufValue > int64(node.params.BufLimit) {
- node.bufValue = int64(node.params.BufLimit)
- }
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("updated bv=%d MRR=%d BufLimit=%d", node.bufValue, node.params.MinRecharge, node.params.BufLimit))
- }
- node.lastTime = now
-}
-
-// UpdateParams updates the flow control parameters of a client node
-func (node *ClientNode) UpdateParams(params ServerParams) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- now := node.cm.clock.Now()
- node.update(now)
- if params.MinRecharge >= node.params.MinRecharge {
- node.updateSchedule = nil
- node.updateParams(params, now)
- } else {
- for i, s := range node.updateSchedule {
- if params.MinRecharge >= s.params.MinRecharge {
- s.params = params
- node.updateSchedule = node.updateSchedule[:i+1]
- return
- }
- }
- node.updateSchedule = append(node.updateSchedule, scheduledUpdate{time: now.Add(DecParamDelay), params: params})
- }
-}
-
-// updateParams updates the flow control parameters of the node
-func (node *ClientNode) updateParams(params ServerParams, now mclock.AbsTime) {
- diff := int64(params.BufLimit - node.params.BufLimit)
- if diff > 0 {
- node.bufValue += diff
- } else if node.bufValue > int64(params.BufLimit) {
- node.bufValue = int64(params.BufLimit)
- }
- node.cm.updateParams(node, params, now)
-}
-
-// AcceptRequest returns whether a new request can be accepted and the missing
-// buffer amount if it was rejected due to a buffer underrun. If accepted, maxCost
-// is deducted from the flow control buffer.
-func (node *ClientNode) AcceptRequest(reqID, index, maxCost uint64) (accepted bool, bufShort uint64, priority int64) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- now := node.cm.clock.Now()
- node.update(now)
- if int64(maxCost) > node.bufValue {
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("rejected reqID=%d bv=%d maxCost=%d", reqID, node.bufValue, maxCost))
- node.log.dump(now)
- }
- return false, maxCost - uint64(node.bufValue), 0
- }
- node.bufValue -= int64(maxCost)
- node.sumCost += maxCost
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("accepted reqID=%d bv=%d maxCost=%d sumCost=%d", reqID, node.bufValue, maxCost, node.sumCost))
- }
- node.accepted[index] = node.sumCost
- return true, 0, node.cm.accepted(node, maxCost, now)
-}
-
-// RequestProcessed should be called when the request has been processed
-func (node *ClientNode) RequestProcessed(reqID, index, maxCost, realCost uint64) uint64 {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- now := node.cm.clock.Now()
- node.update(now)
- node.cm.processed(node, maxCost, realCost, now)
- bv := node.bufValue + int64(node.sumCost-node.accepted[index])
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("processed reqID=%d bv=%d maxCost=%d realCost=%d sumCost=%d oldSumCost=%d reportedBV=%d", reqID, node.bufValue, maxCost, realCost, node.sumCost, node.accepted[index], bv))
- }
- delete(node.accepted, index)
- if bv < 0 {
- return 0
- }
- return uint64(bv)
-}
-
-// ServerNode is the flow control system's representation of a server
-// (used in client mode only)
-type ServerNode struct {
- clock mclock.Clock
- bufEstimate uint64
- bufRecharge bool
- lastTime mclock.AbsTime
- params ServerParams
- sumCost uint64 // sum of req costs sent to this server
- pending map[uint64]uint64 // value = sumCost after sending the given req
- log *logger
- lock sync.RWMutex
-}
-
-// NewServerNode returns a new ServerNode
-func NewServerNode(params ServerParams, clock mclock.Clock) *ServerNode {
- node := &ServerNode{
- clock: clock,
- bufEstimate: params.BufLimit,
- bufRecharge: false,
- lastTime: clock.Now(),
- params: params,
- pending: make(map[uint64]uint64),
- }
- if keepLogs > 0 {
- node.log = newLogger(keepLogs)
- }
- return node
-}
-
-// UpdateParams updates the flow control parameters of the node
-func (node *ServerNode) UpdateParams(params ServerParams) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- node.recalcBLE(mclock.Now())
- if params.BufLimit > node.params.BufLimit {
- node.bufEstimate += params.BufLimit - node.params.BufLimit
- } else {
- if node.bufEstimate > params.BufLimit {
- node.bufEstimate = params.BufLimit
- }
- }
- node.params = params
-}
-
-// recalcBLE recalculates the lowest estimate for the client's buffer value at
-// the given server at the specified time
-func (node *ServerNode) recalcBLE(now mclock.AbsTime) {
- if now < node.lastTime {
- return
- }
- if node.bufRecharge {
- dt := uint64(now - node.lastTime)
- node.bufEstimate += node.params.MinRecharge * dt / uint64(fcTimeConst)
- if node.bufEstimate >= node.params.BufLimit {
- node.bufEstimate = node.params.BufLimit
- node.bufRecharge = false
- }
- }
- node.lastTime = now
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("updated bufEst=%d MRR=%d BufLimit=%d", node.bufEstimate, node.params.MinRecharge, node.params.BufLimit))
- }
-}
-
-// safetyMargin is added to the flow control waiting time when estimated buffer value is low
-const safetyMargin = time.Millisecond
-
-// CanSend returns the minimum waiting time required before sending a request
-// with the given maximum estimated cost. Second return value is the relative
-// estimated buffer level after sending the request (divided by BufLimit).
-func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) {
- node.lock.RLock()
- defer node.lock.RUnlock()
-
- if node.params.BufLimit == 0 {
- return time.Duration(math.MaxInt64), 0
- }
- now := node.clock.Now()
- node.recalcBLE(now)
- maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst)
- if maxCost > node.params.BufLimit {
- maxCost = node.params.BufLimit
- }
- if node.bufEstimate >= maxCost {
- relBuf := float64(node.bufEstimate-maxCost) / float64(node.params.BufLimit)
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d true relBuf=%f", node.bufEstimate, maxCost, relBuf))
- }
- return 0, relBuf
- }
- timeLeft := time.Duration((maxCost - node.bufEstimate) * uint64(fcTimeConst) / node.params.MinRecharge)
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d false timeLeft=%v", node.bufEstimate, maxCost, timeLeft))
- }
- return timeLeft, 0
-}
-
-// QueuedRequest should be called when the request has been assigned to the given
-// server node, before putting it in the send queue. It is mandatory that requests
-// are sent in the same order as the QueuedRequest calls are made.
-func (node *ServerNode) QueuedRequest(reqID, maxCost uint64) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- now := node.clock.Now()
- node.recalcBLE(now)
- // Note: we do not know when requests actually arrive to the server so bufRecharge
- // is not turned on here if buffer was full; in this case it is going to be turned
- // on by the first reply's bufValue feedback
- if node.bufEstimate >= maxCost {
- node.bufEstimate -= maxCost
- } else {
- log.Error("Queued request with insufficient buffer estimate")
- node.bufEstimate = 0
- }
- node.sumCost += maxCost
- node.pending[reqID] = node.sumCost
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("queued reqID=%d bufEst=%d maxCost=%d sumCost=%d", reqID, node.bufEstimate, maxCost, node.sumCost))
- }
-}
-
-// ReceivedReply adjusts estimated buffer value according to the value included in
-// the latest request reply.
-func (node *ServerNode) ReceivedReply(reqID, bv uint64) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- now := node.clock.Now()
- node.recalcBLE(now)
- if bv > node.params.BufLimit {
- bv = node.params.BufLimit
- }
- sc, ok := node.pending[reqID]
- if !ok {
- return
- }
- delete(node.pending, reqID)
- cc := node.sumCost - sc
- newEstimate := uint64(0)
- if bv > cc {
- newEstimate = bv - cc
- }
- if newEstimate > node.bufEstimate {
- // Note: we never reduce the buffer estimate based on the reported value because
- // this can only happen because of the delayed delivery of the latest reply.
- // The lowest estimate based on the previous reply can still be considered valid.
- node.bufEstimate = newEstimate
- }
-
- node.bufRecharge = node.bufEstimate < node.params.BufLimit
- node.lastTime = now
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("received reqID=%d bufEst=%d reportedBv=%d sumCost=%d oldSumCost=%d", reqID, node.bufEstimate, bv, node.sumCost, sc))
- }
-}
-
-// ResumeFreeze cleans all pending requests and sets the buffer estimate to the
-// reported value after resuming from a frozen state
-func (node *ServerNode) ResumeFreeze(bv uint64) {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- for reqID := range node.pending {
- delete(node.pending, reqID)
- }
- now := node.clock.Now()
- node.recalcBLE(now)
- if bv > node.params.BufLimit {
- bv = node.params.BufLimit
- }
- node.bufEstimate = bv
- node.bufRecharge = node.bufEstimate < node.params.BufLimit
- node.lastTime = now
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("unfreeze bv=%d sumCost=%d", bv, node.sumCost))
- }
-}
-
-// DumpLogs dumps the event log if logging is used
-func (node *ServerNode) DumpLogs() {
- node.lock.Lock()
- defer node.lock.Unlock()
-
- if node.log != nil {
- node.log.dump(node.clock.Now())
- }
-}
diff --git a/les/flowcontrol/logger.go b/les/flowcontrol/logger.go
deleted file mode 100644
index 428d7fbf2..000000000
--- a/les/flowcontrol/logger.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2019 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 .
-
-package flowcontrol
-
-import (
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-// logger collects events in string format and discards events older than the
-// "keep" parameter
-type logger struct {
- events map[uint64]logEvent
- writePtr, delPtr uint64
- keep time.Duration
-}
-
-// logEvent describes a single event
-type logEvent struct {
- time mclock.AbsTime
- event string
-}
-
-// newLogger creates a new logger
-func newLogger(keep time.Duration) *logger {
- return &logger{
- events: make(map[uint64]logEvent),
- keep: keep,
- }
-}
-
-// add adds a new event and discards old events if possible
-func (l *logger) add(now mclock.AbsTime, event string) {
- keepAfter := now - mclock.AbsTime(l.keep)
- for l.delPtr < l.writePtr && l.events[l.delPtr].time <= keepAfter {
- delete(l.events, l.delPtr)
- l.delPtr++
- }
- l.events[l.writePtr] = logEvent{now, event}
- l.writePtr++
-}
-
-// dump prints all stored events
-func (l *logger) dump(now mclock.AbsTime) {
- for i := l.delPtr; i < l.writePtr; i++ {
- e := l.events[i]
- fmt.Println(time.Duration(e.time-now), e.event)
- }
-}
diff --git a/les/flowcontrol/manager.go b/les/flowcontrol/manager.go
deleted file mode 100644
index b7cc9bd90..000000000
--- a/les/flowcontrol/manager.go
+++ /dev/null
@@ -1,476 +0,0 @@
-// Copyright 2016 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 .
-
-package flowcontrol
-
-import (
- "fmt"
- "math"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/common/prque"
-)
-
-// cmNodeFields are ClientNode fields used by the client manager
-// Note: these fields are locked by the client manager's mutex
-type cmNodeFields struct {
- corrBufValue int64 // buffer value adjusted with the extra recharge amount
- rcLastIntValue int64 // past recharge integrator value when corrBufValue was last updated
- rcFullIntValue int64 // future recharge integrator value when corrBufValue will reach maximum
- queueIndex int // position in the recharge queue (-1 if not queued)
-}
-
-// FixedPointMultiplier is applied to the recharge integrator and the recharge curve.
-//
-// Note: fixed point arithmetic is required for the integrator because it is a
-// constantly increasing value that can wrap around int64 limits (which behavior is
-// also supported by the priority queue). A floating point value would gradually lose
-// precision in this application.
-// The recharge curve and all recharge values are encoded as fixed point because
-// sumRecharge is frequently updated by adding or subtracting individual recharge
-// values and perfect precision is required.
-const FixedPointMultiplier = 1000000
-
-var (
- capacityDropFactor = 0.1
- capacityRaiseTC = 1 / (3 * float64(time.Hour)) // time constant for raising the capacity factor
- capacityRaiseThresholdRatio = 1.125 // total/connected capacity ratio threshold for raising the capacity factor
-)
-
-// ClientManager controls the capacity assigned to the clients of a server.
-// Since ServerParams guarantee a safe lower estimate for processable requests
-// even in case of all clients being active, ClientManager calculates a
-// corrugated buffer value and usually allows a higher remaining buffer value
-// to be returned with each reply.
-type ClientManager struct {
- clock mclock.Clock
- lock sync.Mutex
- stop chan chan struct{}
-
- curve PieceWiseLinear
- sumRecharge, totalRecharge, totalConnected uint64
- logTotalCap, totalCapacity float64
- logTotalCapRaiseLimit float64
- minLogTotalCap, maxLogTotalCap float64
- capacityRaiseThreshold uint64
- capLastUpdate mclock.AbsTime
- totalCapacityCh chan uint64
-
- // recharge integrator is increasing in each moment with a rate of
- // (totalRecharge / sumRecharge)*FixedPointMultiplier or 0 if sumRecharge==0
- rcLastUpdate mclock.AbsTime // last time the recharge integrator was updated
- rcLastIntValue int64 // last updated value of the recharge integrator
- priorityOffset int64 // offset for prque priority values ensures that all priorities stay in the int64 range
- // recharge queue is a priority queue with currently recharging client nodes
- // as elements. The priority value is rcFullIntValue which allows to quickly
- // determine which client will first finish recharge.
- rcQueue *prque.Prque[int64, *ClientNode]
-}
-
-// NewClientManager returns a new client manager.
-// Client manager enhances flow control performance by allowing client buffers
-// to recharge quicker than the minimum guaranteed recharge rate if possible.
-// The sum of all minimum recharge rates (sumRecharge) is updated each time
-// a clients starts or finishes buffer recharging. Then an adjusted total
-// recharge rate is calculated using a piecewise linear recharge curve:
-//
-// totalRecharge = curve(sumRecharge)
-// (totalRecharge >= sumRecharge is enforced)
-//
-// Then the "bonus" buffer recharge is distributed between currently recharging
-// clients proportionally to their minimum recharge rates.
-//
-// Note: total recharge is proportional to the average number of parallel running
-// serving threads. A recharge value of 1000000 corresponds to one thread in average.
-// The maximum number of allowed serving threads should always be considerably
-// higher than the targeted average number.
-//
-// Note 2: although it is possible to specify a curve allowing the total target
-// recharge starting from zero sumRecharge, it makes sense to add a linear ramp
-// starting from zero in order to not let a single low-priority client use up
-// the entire server capacity and thus ensure quick availability for others at
-// any moment.
-func NewClientManager(curve PieceWiseLinear, clock mclock.Clock) *ClientManager {
- cm := &ClientManager{
- clock: clock,
- rcQueue: prque.New[int64, *ClientNode](func(a *ClientNode, i int) { a.queueIndex = i }),
- capLastUpdate: clock.Now(),
- stop: make(chan chan struct{}),
- }
- if curve != nil {
- cm.SetRechargeCurve(curve)
- }
- go func() {
- // regularly recalculate and update total capacity
- for {
- select {
- case <-time.After(time.Minute):
- cm.lock.Lock()
- cm.updateTotalCapacity(cm.clock.Now(), true)
- cm.lock.Unlock()
- case stop := <-cm.stop:
- close(stop)
- return
- }
- }
- }()
- return cm
-}
-
-// Stop stops the client manager
-func (cm *ClientManager) Stop() {
- stop := make(chan struct{})
- cm.stop <- stop
- <-stop
-}
-
-// SetRechargeCurve updates the recharge curve
-func (cm *ClientManager) SetRechargeCurve(curve PieceWiseLinear) {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- now := cm.clock.Now()
- cm.updateRecharge(now)
- cm.curve = curve
- if len(curve) > 0 {
- cm.totalRecharge = curve[len(curve)-1].Y
- } else {
- cm.totalRecharge = 0
- }
-}
-
-// SetCapacityLimits sets a threshold value used for raising capFactor.
-// Either if the difference between total allowed and connected capacity is less
-// than this threshold or if their ratio is less than capacityRaiseThresholdRatio
-// then capFactor is allowed to slowly raise.
-func (cm *ClientManager) SetCapacityLimits(min, max, raiseThreshold uint64) {
- if min < 1 {
- min = 1
- }
- cm.minLogTotalCap = math.Log(float64(min))
- if max < 1 {
- max = 1
- }
- cm.maxLogTotalCap = math.Log(float64(max))
- cm.logTotalCap = cm.maxLogTotalCap
- cm.capacityRaiseThreshold = raiseThreshold
- cm.refreshCapacity()
-}
-
-// connect should be called when a client is connected, before passing it to any
-// other ClientManager function
-func (cm *ClientManager) connect(node *ClientNode) {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- now := cm.clock.Now()
- cm.updateRecharge(now)
- node.corrBufValue = int64(node.params.BufLimit)
- node.rcLastIntValue = cm.rcLastIntValue
- node.queueIndex = -1
- cm.updateTotalCapacity(now, true)
- cm.totalConnected += node.params.MinRecharge
- cm.updateRaiseLimit()
-}
-
-// disconnect should be called when a client is disconnected
-func (cm *ClientManager) disconnect(node *ClientNode) {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- now := cm.clock.Now()
- cm.updateRecharge(cm.clock.Now())
- cm.updateTotalCapacity(now, true)
- cm.totalConnected -= node.params.MinRecharge
- cm.updateRaiseLimit()
-}
-
-// accepted is called when a request with given maximum cost is accepted.
-// It returns a priority indicator for the request which is used to determine placement
-// in the serving queue. Older requests have higher priority by default. If the client
-// is almost out of buffer, request priority is reduced.
-func (cm *ClientManager) accepted(node *ClientNode, maxCost uint64, now mclock.AbsTime) (priority int64) {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- cm.updateNodeRc(node, -int64(maxCost), &node.params, now)
- rcTime := (node.params.BufLimit - uint64(node.corrBufValue)) * FixedPointMultiplier / node.params.MinRecharge
- return -int64(now) - int64(rcTime)
-}
-
-// processed updates the client buffer according to actual request cost after
-// serving has been finished.
-//
-// Note: processed should always be called for all accepted requests
-func (cm *ClientManager) processed(node *ClientNode, maxCost, realCost uint64, now mclock.AbsTime) {
- if realCost > maxCost {
- realCost = maxCost
- }
- cm.updateBuffer(node, int64(maxCost-realCost), now)
-}
-
-// updateBuffer recalculates the corrected buffer value, adds the given value to it
-// and updates the node's actual buffer value if possible
-func (cm *ClientManager) updateBuffer(node *ClientNode, add int64, now mclock.AbsTime) {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- cm.updateNodeRc(node, add, &node.params, now)
- if node.corrBufValue > node.bufValue {
- if node.log != nil {
- node.log.add(now, fmt.Sprintf("corrected bv=%d oldBv=%d", node.corrBufValue, node.bufValue))
- }
- node.bufValue = node.corrBufValue
- }
-}
-
-// updateParams updates the flow control parameters of a client node
-func (cm *ClientManager) updateParams(node *ClientNode, params ServerParams, now mclock.AbsTime) {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- cm.updateRecharge(now)
- cm.updateTotalCapacity(now, true)
- cm.totalConnected += params.MinRecharge - node.params.MinRecharge
- cm.updateRaiseLimit()
- cm.updateNodeRc(node, 0, ¶ms, now)
-}
-
-// updateRaiseLimit recalculates the limiting value until which logTotalCap
-// can be raised when no client freeze events occur
-func (cm *ClientManager) updateRaiseLimit() {
- if cm.capacityRaiseThreshold == 0 {
- cm.logTotalCapRaiseLimit = 0
- return
- }
- limit := float64(cm.totalConnected + cm.capacityRaiseThreshold)
- limit2 := float64(cm.totalConnected) * capacityRaiseThresholdRatio
- if limit2 > limit {
- limit = limit2
- }
- if limit < 1 {
- limit = 1
- }
- cm.logTotalCapRaiseLimit = math.Log(limit)
-}
-
-// updateRecharge updates the recharge integrator and checks the recharge queue
-// for nodes with recently filled buffers
-func (cm *ClientManager) updateRecharge(now mclock.AbsTime) {
- lastUpdate := cm.rcLastUpdate
- cm.rcLastUpdate = now
- // updating is done in multiple steps if node buffers are filled and sumRecharge
- // is decreased before the given target time
- for cm.sumRecharge > 0 {
- sumRecharge := cm.sumRecharge
- if sumRecharge > cm.totalRecharge {
- sumRecharge = cm.totalRecharge
- }
- bonusRatio := float64(1)
- v := cm.curve.ValueAt(sumRecharge)
- s := float64(sumRecharge)
- if v > s && s > 0 {
- bonusRatio = v / s
- }
- dt := now - lastUpdate
- // fetch the client that finishes first
- rcqNode := cm.rcQueue.PopItem() // if sumRecharge > 0 then the queue cannot be empty
- // check whether it has already finished
- dtNext := mclock.AbsTime(float64(rcqNode.rcFullIntValue-cm.rcLastIntValue) / bonusRatio)
- if dt < dtNext {
- // not finished yet, put it back, update integrator according
- // to current bonusRatio and return
- cm.addToQueue(rcqNode)
- cm.rcLastIntValue += int64(bonusRatio * float64(dt))
- return
- }
- lastUpdate += dtNext
- // finished recharging, update corrBufValue and sumRecharge if necessary and do next step
- if rcqNode.corrBufValue < int64(rcqNode.params.BufLimit) {
- rcqNode.corrBufValue = int64(rcqNode.params.BufLimit)
- cm.sumRecharge -= rcqNode.params.MinRecharge
- }
- cm.rcLastIntValue = rcqNode.rcFullIntValue
- }
-}
-
-func (cm *ClientManager) addToQueue(node *ClientNode) {
- if cm.priorityOffset-node.rcFullIntValue < -0x4000000000000000 {
- cm.priorityOffset += 0x4000000000000000
- // recreate priority queue with new offset to avoid overflow; should happen very rarely
- newRcQueue := prque.New[int64, *ClientNode](func(a *ClientNode, i int) { a.queueIndex = i })
- for cm.rcQueue.Size() > 0 {
- n := cm.rcQueue.PopItem()
- newRcQueue.Push(n, cm.priorityOffset-n.rcFullIntValue)
- }
- cm.rcQueue = newRcQueue
- }
- cm.rcQueue.Push(node, cm.priorityOffset-node.rcFullIntValue)
-}
-
-// updateNodeRc updates a node's corrBufValue and adds an external correction value.
-// It also adds or removes the rcQueue entry and updates ServerParams and sumRecharge if necessary.
-func (cm *ClientManager) updateNodeRc(node *ClientNode, bvc int64, params *ServerParams, now mclock.AbsTime) {
- cm.updateRecharge(now)
- wasFull := true
- if node.corrBufValue != int64(node.params.BufLimit) {
- wasFull = false
- node.corrBufValue += (cm.rcLastIntValue - node.rcLastIntValue) * int64(node.params.MinRecharge) / FixedPointMultiplier
- if node.corrBufValue > int64(node.params.BufLimit) {
- node.corrBufValue = int64(node.params.BufLimit)
- }
- node.rcLastIntValue = cm.rcLastIntValue
- }
- node.corrBufValue += bvc
- diff := int64(params.BufLimit - node.params.BufLimit)
- if diff > 0 {
- node.corrBufValue += diff
- }
- isFull := false
- if node.corrBufValue >= int64(params.BufLimit) {
- node.corrBufValue = int64(params.BufLimit)
- isFull = true
- }
- if !wasFull {
- cm.sumRecharge -= node.params.MinRecharge
- }
- if params != &node.params {
- node.params = *params
- }
- if !isFull {
- cm.sumRecharge += node.params.MinRecharge
- if node.queueIndex != -1 {
- cm.rcQueue.Remove(node.queueIndex)
- }
- node.rcLastIntValue = cm.rcLastIntValue
- node.rcFullIntValue = cm.rcLastIntValue + (int64(node.params.BufLimit)-node.corrBufValue)*FixedPointMultiplier/int64(node.params.MinRecharge)
- cm.addToQueue(node)
- }
-}
-
-// reduceTotalCapacity reduces the total capacity allowance in case of a client freeze event
-func (cm *ClientManager) reduceTotalCapacity(frozenCap uint64) {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- ratio := float64(1)
- if frozenCap < cm.totalConnected {
- ratio = float64(frozenCap) / float64(cm.totalConnected)
- }
- now := cm.clock.Now()
- cm.updateTotalCapacity(now, false)
- cm.logTotalCap -= capacityDropFactor * ratio
- if cm.logTotalCap < cm.minLogTotalCap {
- cm.logTotalCap = cm.minLogTotalCap
- }
- cm.updateTotalCapacity(now, true)
-}
-
-// updateTotalCapacity updates the total capacity factor. The capacity factor allows
-// the total capacity of the system to go over the allowed total recharge value
-// if clients go to frozen state sufficiently rarely.
-// The capacity factor is dropped instantly by a small amount if a clients is frozen.
-// It is raised slowly (with a large time constant) if the total connected capacity
-// is close to the total allowed amount and no clients are frozen.
-func (cm *ClientManager) updateTotalCapacity(now mclock.AbsTime, refresh bool) {
- dt := now - cm.capLastUpdate
- cm.capLastUpdate = now
-
- if cm.logTotalCap < cm.logTotalCapRaiseLimit {
- cm.logTotalCap += capacityRaiseTC * float64(dt)
- if cm.logTotalCap > cm.logTotalCapRaiseLimit {
- cm.logTotalCap = cm.logTotalCapRaiseLimit
- }
- }
- if cm.logTotalCap > cm.maxLogTotalCap {
- cm.logTotalCap = cm.maxLogTotalCap
- }
- if refresh {
- cm.refreshCapacity()
- }
-}
-
-// refreshCapacity recalculates the total capacity value and sends an update to the subscription
-// channel if the relative change of the value since the last update is more than 0.1 percent
-func (cm *ClientManager) refreshCapacity() {
- totalCapacity := math.Exp(cm.logTotalCap)
- if totalCapacity >= cm.totalCapacity*0.999 && totalCapacity <= cm.totalCapacity*1.001 {
- return
- }
- cm.totalCapacity = totalCapacity
- if cm.totalCapacityCh != nil {
- select {
- case cm.totalCapacityCh <- uint64(cm.totalCapacity):
- default:
- }
- }
-}
-
-// SubscribeTotalCapacity returns all future updates to the total capacity value
-// through a channel and also returns the current value
-func (cm *ClientManager) SubscribeTotalCapacity(ch chan uint64) uint64 {
- cm.lock.Lock()
- defer cm.lock.Unlock()
-
- cm.totalCapacityCh = ch
- return uint64(cm.totalCapacity)
-}
-
-// PieceWiseLinear is used to describe recharge curves
-type PieceWiseLinear []struct{ X, Y uint64 }
-
-// ValueAt returns the curve's value at a given point
-func (pwl PieceWiseLinear) ValueAt(x uint64) float64 {
- l := 0
- h := len(pwl)
- if h == 0 {
- return 0
- }
- for h != l {
- m := (l + h) / 2
- if x > pwl[m].X {
- l = m + 1
- } else {
- h = m
- }
- }
- if l == 0 {
- return float64(pwl[0].Y)
- }
- l--
- if h == len(pwl) {
- return float64(pwl[l].Y)
- }
- dx := pwl[h].X - pwl[l].X
- if dx < 1 {
- return float64(pwl[l].Y)
- }
- return float64(pwl[l].Y) + float64(pwl[h].Y-pwl[l].Y)*float64(x-pwl[l].X)/float64(dx)
-}
-
-// Valid returns true if the X coordinates of the curve points are non-strictly monotonic
-func (pwl PieceWiseLinear) Valid() bool {
- var lastX uint64
- for _, i := range pwl {
- if i.X < lastX {
- return false
- }
- lastX = i.X
- }
- return true
-}
diff --git a/les/flowcontrol/manager_test.go b/les/flowcontrol/manager_test.go
deleted file mode 100644
index 3afc31272..000000000
--- a/les/flowcontrol/manager_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2019 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 .
-
-package flowcontrol
-
-import (
- "math"
- "math/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-type testNode struct {
- node *ClientNode
- bufLimit, capacity uint64
- waitUntil mclock.AbsTime
- index, totalCost uint64
-}
-
-const (
- testMaxCost = 1000000
- testLength = 100000
-)
-
-// testConstantTotalCapacity simulates multiple request sender nodes and verifies
-// whether the total amount of served requests matches the expected value based on
-// the total capacity and the duration of the test.
-// Some nodes are sending requests occasionally so that their buffer should regularly
-// reach the maximum while other nodes (the "max capacity nodes") are sending at the
-// maximum permitted rate. The max capacity nodes are changed multiple times during
-// a single test.
-func TestConstantTotalCapacity(t *testing.T) {
- testConstantTotalCapacity(t, 10, 1, 0, false)
- testConstantTotalCapacity(t, 10, 1, 1, false)
- testConstantTotalCapacity(t, 30, 1, 0, false)
- testConstantTotalCapacity(t, 30, 2, 3, false)
- testConstantTotalCapacity(t, 100, 1, 0, false)
- testConstantTotalCapacity(t, 100, 3, 5, false)
- testConstantTotalCapacity(t, 100, 5, 10, false)
- testConstantTotalCapacity(t, 100, 3, 5, true)
-}
-
-func testConstantTotalCapacity(t *testing.T, nodeCount, maxCapacityNodes, randomSend int, priorityOverflow bool) {
- clock := &mclock.Simulated{}
- nodes := make([]*testNode, nodeCount)
- var totalCapacity uint64
- for i := range nodes {
- nodes[i] = &testNode{capacity: uint64(50000 + rand.Intn(100000))}
- totalCapacity += nodes[i].capacity
- }
- m := NewClientManager(PieceWiseLinear{{0, totalCapacity}}, clock)
- if priorityOverflow {
- // provoke a situation where rcLastUpdate overflow needs to be handled
- m.rcLastIntValue = math.MaxInt64 - 10000000000
- }
- for _, n := range nodes {
- n.bufLimit = n.capacity * 6000
- n.node = NewClientNode(m, ServerParams{BufLimit: n.bufLimit, MinRecharge: n.capacity})
- }
- maxNodes := make([]int, maxCapacityNodes)
- for i := range maxNodes {
- // we don't care if some indexes are selected multiple times
- // in that case we have fewer max nodes
- maxNodes[i] = rand.Intn(nodeCount)
- }
-
- var sendCount int
- for i := 0; i < testLength; i++ {
- now := clock.Now()
- for _, idx := range maxNodes {
- for nodes[idx].send(t, now) {
- }
- }
- if rand.Intn(testLength) < maxCapacityNodes*3 {
- maxNodes[rand.Intn(maxCapacityNodes)] = rand.Intn(nodeCount)
- }
-
- sendCount += randomSend
- failCount := randomSend * 10
- for sendCount > 0 && failCount > 0 {
- if nodes[rand.Intn(nodeCount)].send(t, now) {
- sendCount--
- } else {
- failCount--
- }
- }
- clock.Run(time.Millisecond)
- }
-
- var totalCost uint64
- for _, n := range nodes {
- totalCost += n.totalCost
- }
- ratio := float64(totalCost) / float64(totalCapacity) / testLength
- if ratio < 0.98 || ratio > 1.02 {
- t.Errorf("totalCost/totalCapacity/testLength ratio incorrect (expected: 1, got: %f)", ratio)
- }
-}
-
-func (n *testNode) send(t *testing.T, now mclock.AbsTime) bool {
- if now < n.waitUntil {
- return false
- }
- n.index++
- if ok, _, _ := n.node.AcceptRequest(0, n.index, testMaxCost); !ok {
- t.Fatalf("Rejected request after expected waiting time has passed")
- }
- rcost := uint64(rand.Int63n(testMaxCost))
- bv := n.node.RequestProcessed(0, n.index, testMaxCost, rcost)
- if bv < testMaxCost {
- n.waitUntil = now + mclock.AbsTime((testMaxCost-bv)*1001000/n.capacity)
- }
- n.totalCost += rcost
- return true
-}
diff --git a/les/handler_test.go b/les/handler_test.go
deleted file mode 100644
index c803a5ddb..000000000
--- a/les/handler_test.go
+++ /dev/null
@@ -1,754 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "encoding/binary"
- "math/big"
- "math/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "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/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/eth/downloader"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}) error {
- type resp struct {
- ReqID, BV uint64
- Data interface{}
- }
- return p2p.ExpectMsg(r, msgcode, resp{reqID, bv, data})
-}
-
-// Tests that block headers can be retrieved from a remote chain based on user queries.
-func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) }
-func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) }
-func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) }
-
-func testGetBlockHeaders(t *testing.T, protocol int) {
- netconfig := testnetConfig{
- blocks: downloader.MaxHeaderFetch + 15,
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
- bc := server.handler.blockchain
-
- // Create a "random" unknown hash for testing
- var unknown common.Hash
- for i := range unknown {
- unknown[i] = byte(i)
- }
- // Create a batch of tests for various scenarios
- limit := uint64(MaxHeaderFetch)
- tests := []struct {
- query *GetBlockHeadersData // The query to execute for header retrieval
- expect []common.Hash // The hashes of the block whose headers are expected
- }{
- // A single random block should be retrievable by hash and number too
- {
- &GetBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
- []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()},
- }, {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1},
- []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()},
- },
- // Multiple headers should be retrievable in both directions
- {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3},
- []common.Hash{
- bc.GetBlockByNumber(limit / 2).Hash(),
- bc.GetBlockByNumber(limit/2 + 1).Hash(),
- bc.GetBlockByNumber(limit/2 + 2).Hash(),
- },
- }, {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
- []common.Hash{
- bc.GetBlockByNumber(limit / 2).Hash(),
- bc.GetBlockByNumber(limit/2 - 1).Hash(),
- bc.GetBlockByNumber(limit/2 - 2).Hash(),
- },
- },
- // Multiple headers with skip lists should be retrievable
- {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
- []common.Hash{
- bc.GetBlockByNumber(limit / 2).Hash(),
- bc.GetBlockByNumber(limit/2 + 4).Hash(),
- bc.GetBlockByNumber(limit/2 + 8).Hash(),
- },
- }, {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
- []common.Hash{
- bc.GetBlockByNumber(limit / 2).Hash(),
- bc.GetBlockByNumber(limit/2 - 4).Hash(),
- bc.GetBlockByNumber(limit/2 - 8).Hash(),
- },
- },
- // The chain endpoints should be retrievable
- {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1},
- []common.Hash{bc.GetBlockByNumber(0).Hash()},
- }, {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64()}, Amount: 1},
- []common.Hash{bc.CurrentBlock().Hash()},
- },
- // Ensure protocol limits are honored
- //{
- // &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64()() - 1}, Amount: limit + 10, Reverse: true},
- // []common.Hash{},
- //},
- // Check that requesting more than available is handled gracefully
- {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64() - 4}, Skip: 3, Amount: 3},
- []common.Hash{
- bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64() - 4).Hash(),
- bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64()).Hash(),
- },
- }, {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
- []common.Hash{
- bc.GetBlockByNumber(4).Hash(),
- bc.GetBlockByNumber(0).Hash(),
- },
- },
- // Check that requesting more than available is handled gracefully, even if mid skip
- {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64() - 4}, Skip: 2, Amount: 3},
- []common.Hash{
- bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64() - 4).Hash(),
- bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64() - 1).Hash(),
- },
- }, {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
- []common.Hash{
- bc.GetBlockByNumber(4).Hash(),
- bc.GetBlockByNumber(1).Hash(),
- },
- },
- // Check that non existing headers aren't returned
- {
- &GetBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
- []common.Hash{},
- }, {
- &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64() + 1}, Amount: 1},
- []common.Hash{},
- },
- }
- // Run each of the tests and verify the results against the chain
- var reqID uint64
- for i, tt := range tests {
- // Collect the headers to expect in the response
- var headers []*types.Header
- for _, hash := range tt.expect {
- headers = append(headers, bc.GetHeaderByHash(hash))
- }
- // Send the hash request and verify the response
- reqID++
-
- sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, tt.query)
- if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil {
- t.Errorf("test %d: headers mismatch: %v", i, err)
- }
- }
-}
-
-// Tests that block contents can be retrieved from a remote chain based on their hashes.
-func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) }
-func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) }
-func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) }
-
-func testGetBlockBodies(t *testing.T, protocol int) {
- netconfig := testnetConfig{
- blocks: downloader.MaxHeaderFetch + 15,
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- // Create a batch of tests for various scenarios
- limit := MaxBodyFetch
- tests := []struct {
- random int // Number of blocks to fetch randomly from the chain
- explicit []common.Hash // Explicitly requested blocks
- available []bool // Availability of explicitly requested blocks
- expected int // Total number of existing blocks to expect
- }{
- {1, nil, nil, 1}, // A single random block should be retrievable
- {10, nil, nil, 10}, // Multiple random blocks should be retrievable
- {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable
- //{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
- {0, []common.Hash{bc.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable
- {0, []common.Hash{bc.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
- {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned
-
- // Existing and non-existing blocks interleaved should not cause problems
- {0, []common.Hash{
- {},
- bc.GetBlockByNumber(1).Hash(),
- {},
- bc.GetBlockByNumber(10).Hash(),
- {},
- bc.GetBlockByNumber(100).Hash(),
- {},
- }, []bool{false, true, false, true, false, true, false}, 3},
- }
- // Run each of the tests and verify the results against the chain
- var reqID uint64
- for i, tt := range tests {
- // Collect the hashes to request, and the response to expect
- var hashes []common.Hash
- seen := make(map[int64]bool)
- var bodies []*types.Body
-
- for j := 0; j < tt.random; j++ {
- for {
- num := rand.Int63n(int64(bc.CurrentBlock().Number.Uint64()))
- if !seen[num] {
- seen[num] = true
-
- block := bc.GetBlockByNumber(uint64(num))
- hashes = append(hashes, block.Hash())
- if len(bodies) < tt.expected {
- bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()})
- }
- break
- }
- }
- }
- for j, hash := range tt.explicit {
- hashes = append(hashes, hash)
- if tt.available[j] && len(bodies) < tt.expected {
- block := bc.GetBlockByHash(hash)
- bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()})
- }
- }
- reqID++
-
- // Send the hash request and verify the response
- sendRequest(rawPeer.app, GetBlockBodiesMsg, reqID, hashes)
- if err := expectResponse(rawPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil {
- t.Errorf("test %d: bodies mismatch: %v", i, err)
- }
- }
-}
-
-// Tests that the contract codes can be retrieved based on account addresses.
-func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) }
-func TestGetCodeLes3(t *testing.T) { testGetCode(t, 3) }
-func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) }
-
-func testGetCode(t *testing.T, protocol int) {
- // Assemble the test environment
- netconfig := testnetConfig{
- blocks: 4,
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- var codereqs []*CodeReq
- var codes [][]byte
- for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ {
- header := bc.GetHeaderByNumber(i)
- req := &CodeReq{
- BHash: header.Hash(),
- AccountAddress: testContractAddr[:],
- }
- codereqs = append(codereqs, req)
- if i >= testContractDeployed {
- codes = append(codes, testContractCodeDeployed)
- }
- }
-
- sendRequest(rawPeer.app, GetCodeMsg, 42, codereqs)
- if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil {
- t.Errorf("codes mismatch: %v", err)
- }
-}
-
-// Tests that the stale contract codes can't be retrieved based on account addresses.
-func TestGetStaleCodeLes2(t *testing.T) { testGetStaleCode(t, 2) }
-func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) }
-func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) }
-
-func testGetStaleCode(t *testing.T, protocol int) {
- netconfig := testnetConfig{
- blocks: core.TriesInMemory + 4,
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- check := func(number uint64, expected [][]byte) {
- req := &CodeReq{
- BHash: bc.GetHeaderByNumber(number).Hash(),
- AccountAddress: testContractAddr[:],
- }
- sendRequest(rawPeer.app, GetCodeMsg, 42, []*CodeReq{req})
- if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, expected); err != nil {
- t.Errorf("codes mismatch: %v", err)
- }
- }
- check(0, [][]byte{}) // Non-exist contract
- check(testContractDeployed, [][]byte{}) // Stale contract
- check(bc.CurrentHeader().Number.Uint64(), [][]byte{testContractCodeDeployed}) // Fresh contract
-}
-
-// Tests that the transaction receipts can be retrieved based on hashes.
-func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) }
-func TestGetReceiptLes3(t *testing.T) { testGetReceipt(t, 3) }
-func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) }
-
-func testGetReceipt(t *testing.T, protocol int) {
- // Assemble the test environment
- netconfig := testnetConfig{
- blocks: 4,
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- // Collect the hashes to request, and the response to expect
- var receipts []types.Receipts
- var hashes []common.Hash
- for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ {
- block := bc.GetBlockByNumber(i)
-
- hashes = append(hashes, block.Hash())
- receipts = append(receipts, rawdb.ReadReceipts(server.db, block.Hash(), block.NumberU64(), block.Time(), bc.Config()))
- }
- // Send the hash request and verify the response
- sendRequest(rawPeer.app, GetReceiptsMsg, 42, hashes)
- if err := expectResponse(rawPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil {
- t.Errorf("receipts mismatch: %v", err)
- }
-}
-
-// Tests that trie merkle proofs can be retrieved
-func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) }
-func TestGetProofsLes3(t *testing.T) { testGetProofs(t, 3) }
-func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) }
-
-func testGetProofs(t *testing.T, protocol int) {
- // Assemble the test environment
- netconfig := testnetConfig{
- blocks: 4,
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- var proofreqs []ProofReq
- proofsV2 := trienode.NewProofSet()
-
- accounts := []common.Address{bankAddr, userAddr1, userAddr2, signerAddr, {}}
- for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ {
- header := bc.GetHeaderByNumber(i)
- trie, _ := trie.New(trie.StateTrieID(header.Root), server.backend.Blockchain().TrieDB())
-
- for _, acc := range accounts {
- req := ProofReq{
- BHash: header.Hash(),
- Key: crypto.Keccak256(acc[:]),
- }
- proofreqs = append(proofreqs, req)
- trie.Prove(crypto.Keccak256(acc[:]), proofsV2)
- }
- }
- // Send the proof request and verify the response
- sendRequest(rawPeer.app, GetProofsV2Msg, 42, proofreqs)
- if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.List()); err != nil {
- t.Errorf("proofs mismatch: %v", err)
- }
-}
-
-// Tests that the stale contract codes can't be retrieved based on account addresses.
-func TestGetStaleProofLes2(t *testing.T) { testGetStaleProof(t, 2) }
-func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) }
-func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) }
-
-func testGetStaleProof(t *testing.T, protocol int) {
- netconfig := testnetConfig{
- blocks: core.TriesInMemory + 4,
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- check := func(number uint64, wantOK bool) {
- var (
- header = bc.GetHeaderByNumber(number)
- account = crypto.Keccak256(userAddr1.Bytes())
- )
- req := &ProofReq{
- BHash: header.Hash(),
- Key: account,
- }
- sendRequest(rawPeer.app, GetProofsV2Msg, 42, []*ProofReq{req})
-
- var expected []rlp.RawValue
- if wantOK {
- proofsV2 := trienode.NewProofSet()
- t, _ := trie.New(trie.StateTrieID(header.Root), server.backend.Blockchain().TrieDB())
- t.Prove(account, proofsV2)
- expected = proofsV2.List()
- }
- if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil {
- t.Errorf("codes mismatch: %v", err)
- }
- }
- check(0, false) // Non-exist proof
- check(2, false) // Stale proof
- check(bc.CurrentHeader().Number.Uint64(), true) // Fresh proof
-}
-
-// Tests that CHT proofs can be correctly retrieved.
-func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) }
-func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) }
-func TestGetCHTProofsLes4(t *testing.T) { testGetCHTProofs(t, 4) }
-
-func testGetCHTProofs(t *testing.T, protocol int) {
- var (
- config = light.TestServerIndexerConfig
- waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
- for {
- cs, _, _ := cIndexer.Sections()
- if cs >= 1 {
- break
- }
- time.Sleep(10 * time.Millisecond)
- }
- }
- netconfig = testnetConfig{
- blocks: int(config.ChtSize + config.ChtConfirms),
- protocol: protocol,
- indexFn: waitIndexers,
- nopruning: true,
- }
- )
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- // Assemble the proofs from the different protocols
- header := bc.GetHeaderByNumber(config.ChtSize - 1)
- rlp, _ := rlp.EncodeToBytes(header)
-
- key := make([]byte, 8)
- binary.BigEndian.PutUint64(key, config.ChtSize-1)
-
- proofsV2 := HelperTrieResps{
- AuxData: [][]byte{rlp},
- }
- root := light.GetChtRoot(server.db, 0, bc.GetHeaderByNumber(config.ChtSize-1).Hash())
- trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, string(rawdb.ChtTablePrefix)), trie.HashDefaults))
- trie.Prove(key, &proofsV2.Proofs)
- // Assemble the requests for the different protocols
- requestsV2 := []HelperTrieReq{{
- Type: htCanonical,
- TrieIdx: 0,
- Key: key,
- AuxReq: htAuxHeader,
- }}
- // Send the proof request and verify the response
- sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requestsV2)
- if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil {
- t.Errorf("proofs mismatch: %v", err)
- }
-}
-
-func TestGetBloombitsProofsLes2(t *testing.T) { testGetBloombitsProofs(t, 2) }
-func TestGetBloombitsProofsLes3(t *testing.T) { testGetBloombitsProofs(t, 3) }
-func TestGetBloombitsProofsLes4(t *testing.T) { testGetBloombitsProofs(t, 4) }
-
-// Tests that bloombits proofs can be correctly retrieved.
-func testGetBloombitsProofs(t *testing.T, protocol int) {
- var (
- config = light.TestServerIndexerConfig
- waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
- for {
- bts, _, _ := btIndexer.Sections()
- if bts >= 1 {
- break
- }
- time.Sleep(10 * time.Millisecond)
- }
- }
- netconfig = testnetConfig{
- blocks: int(config.BloomTrieSize + config.BloomTrieConfirms),
- protocol: protocol,
- indexFn: waitIndexers,
- nopruning: true,
- }
- )
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- bc := server.handler.blockchain
-
- // Request and verify each bit of the bloom bits proofs
- for bit := 0; bit < 2048; bit++ {
- // Assemble the request and proofs for the bloombits
- key := make([]byte, 10)
-
- binary.BigEndian.PutUint16(key[:2], uint16(bit))
- // Only the first bloom section has data.
- binary.BigEndian.PutUint64(key[2:], 0)
-
- requests := []HelperTrieReq{{
- Type: htBloomBits,
- TrieIdx: 0,
- Key: key,
- }}
- var proofs HelperTrieResps
-
- root := light.GetBloomTrieRoot(server.db, 0, bc.GetHeaderByNumber(config.BloomTrieSize-1).Hash())
- trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, string(rawdb.BloomTrieTablePrefix)), trie.HashDefaults))
- trie.Prove(key, &proofs.Proofs)
-
- // Send the proof request and verify the response
- sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requests)
- if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil {
- t.Errorf("bit %d: proofs mismatch: %v", bit, err)
- }
- }
-}
-
-func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, lpv2) }
-func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, lpv3) }
-func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, lpv4) }
-
-func testTransactionStatus(t *testing.T, protocol int) {
- netconfig := testnetConfig{
- protocol: protocol,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- server.handler.addTxsSync = true
-
- chain := server.handler.blockchain
-
- var reqID uint64
-
- test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) {
- t.Helper()
-
- reqID++
- if send {
- sendRequest(rawPeer.app, SendTxV2Msg, reqID, types.Transactions{tx})
- } else {
- sendRequest(rawPeer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()})
- }
- if err := expectResponse(rawPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
- t.Errorf("transaction status mismatch: %v", err)
- }
- }
- signer := types.HomesteadSigner{}
-
- // test error status by sending an underpriced transaction
- tx0, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey)
- test(tx0, true, light.TxStatus{Status: txpool.TxStatusUnknown, Error: "transaction underpriced: tip needed 1, tip permitted 0"})
-
- tx1, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey)
- test(tx1, false, light.TxStatus{Status: txpool.TxStatusUnknown}) // query before sending, should be unknown
- test(tx1, true, light.TxStatus{Status: txpool.TxStatusPending}) // send valid processable tx, should return pending
- test(tx1, true, light.TxStatus{Status: txpool.TxStatusPending}) // adding it again should not return an error
-
- tx2, _ := types.SignTx(types.NewTransaction(1, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey)
- tx3, _ := types.SignTx(types.NewTransaction(2, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey)
- // send transactions in the wrong order, tx3 should be queued
- test(tx3, true, light.TxStatus{Status: txpool.TxStatusQueued})
- test(tx2, true, light.TxStatus{Status: txpool.TxStatusPending})
- // query again, now tx3 should be pending too
- test(tx3, false, light.TxStatus{Status: txpool.TxStatusPending})
-
- // generate and add a block with tx1 and tx2 included
- gchain, _ := core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), server.db, 1, func(i int, block *core.BlockGen) {
- block.AddTx(tx1)
- block.AddTx(tx2)
- })
- if _, err := chain.InsertChain(gchain); err != nil {
- panic(err)
- }
- // wait until TxPool processes the inserted block
- for i := 0; i < 10; i++ {
- if pending, _ := server.handler.txpool.Stats(); pending == 1 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- if pending, _ := server.handler.txpool.Stats(); pending != 1 {
- t.Fatalf("pending count mismatch: have %d, want 1", pending)
- }
- // Discard new block announcement
- msg, _ := rawPeer.app.ReadMsg()
- msg.Discard()
-
- // check if their status is included now
- block1hash := rawdb.ReadCanonicalHash(server.db, 1)
- test(tx1, false, light.TxStatus{Status: txpool.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}})
-
- test(tx2, false, light.TxStatus{Status: txpool.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}})
-
- // create a reorg that rolls them back
- gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), server.db, 2, func(i int, block *core.BlockGen) {})
- if _, err := chain.InsertChain(gchain); err != nil {
- panic(err)
- }
- // wait until TxPool processes the reorg
- for i := 0; i < 10; i++ {
- if pending, _ := server.handler.txpool.Stats(); pending == 3 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- if pending, _ := server.handler.txpool.Stats(); pending != 3 {
- t.Fatalf("pending count mismatch: have %d, want 3", pending)
- }
- // Discard new block announcement
- msg, _ = rawPeer.app.ReadMsg()
- msg.Discard()
-
- // check if their status is pending again
- test(tx1, false, light.TxStatus{Status: txpool.TxStatusPending})
- test(tx2, false, light.TxStatus{Status: txpool.TxStatusPending})
-}
-
-func TestStopResumeLES3(t *testing.T) { testStopResume(t, lpv3) }
-func TestStopResumeLES4(t *testing.T) { testStopResume(t, lpv4) }
-
-func testStopResume(t *testing.T, protocol int) {
- netconfig := testnetConfig{
- protocol: protocol,
- simClock: true,
- nopruning: true,
- }
- server, _, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- server.handler.server.costTracker.testing = true
- server.handler.server.costTracker.testCostList = testCostList(testBufLimit / 10)
-
- rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
- defer closePeer()
-
- var (
- reqID uint64
- expBuf = testBufLimit
- testCost = testBufLimit / 10
- )
- header := server.handler.blockchain.CurrentHeader()
- req := func() {
- reqID++
- sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1})
- }
- for i := 1; i <= 5; i++ {
- // send requests while we still have enough buffer and expect a response
- for expBuf >= testCost {
- req()
- expBuf -= testCost
- if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil {
- t.Errorf("expected response and failed: %v", err)
- }
- }
- // send some more requests in excess and expect a single StopMsg
- c := i
- for c > 0 {
- req()
- c--
- }
- if err := p2p.ExpectMsg(rawPeer.app, StopMsg, nil); err != nil {
- t.Errorf("expected StopMsg and failed: %v", err)
- }
- // wait until the buffer is recharged by half of the limit
- wait := testBufLimit / testBufRecharge / 2
- server.clock.(*mclock.Simulated).Run(time.Millisecond * time.Duration(wait))
-
- // expect a ResumeMsg with the partially recharged buffer value
- expBuf += testBufRecharge * wait
- if err := p2p.ExpectMsg(rawPeer.app, ResumeMsg, expBuf); err != nil {
- t.Errorf("expected ResumeMsg and failed: %v", err)
- }
- }
-}
diff --git a/les/metrics.go b/les/metrics.go
deleted file mode 100644
index 07d3133c9..000000000
--- a/les/metrics.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p"
-)
-
-var (
- miscInPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/total", nil)
- miscInTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/total", nil)
- miscInHeaderPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/header", nil)
- miscInHeaderTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/header", nil)
- miscInBodyPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/body", nil)
- miscInBodyTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/body", nil)
- miscInCodePacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/code", nil)
- miscInCodeTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/code", nil)
- miscInReceiptPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/receipt", nil)
- miscInReceiptTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/receipt", nil)
- miscInTrieProofPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/proof", nil)
- miscInTrieProofTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/proof", nil)
- miscInHelperTriePacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/helperTrie", nil)
- miscInHelperTrieTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/helperTrie", nil)
- miscInTxsPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/txs", nil)
- miscInTxsTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/txs", nil)
- miscInTxStatusPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/txStatus", nil)
- miscInTxStatusTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/txStatus", nil)
-
- miscOutPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/total", nil)
- miscOutTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/total", nil)
- miscOutHeaderPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/header", nil)
- miscOutHeaderTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/header", nil)
- miscOutBodyPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/body", nil)
- miscOutBodyTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/body", nil)
- miscOutCodePacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/code", nil)
- miscOutCodeTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/code", nil)
- miscOutReceiptPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/receipt", nil)
- miscOutReceiptTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/receipt", nil)
- miscOutTrieProofPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/proof", nil)
- miscOutTrieProofTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/proof", nil)
- miscOutHelperTriePacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/helperTrie", nil)
- miscOutHelperTrieTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/helperTrie", nil)
- miscOutTxsPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/txs", nil)
- miscOutTxsTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/txs", nil)
- miscOutTxStatusPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/txStatus", nil)
- miscOutTxStatusTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/txStatus", nil)
-
- miscServingTimeHeaderTimer = metrics.NewRegisteredTimer("les/misc/serve/header", nil)
- miscServingTimeBodyTimer = metrics.NewRegisteredTimer("les/misc/serve/body", nil)
- miscServingTimeCodeTimer = metrics.NewRegisteredTimer("les/misc/serve/code", nil)
- miscServingTimeReceiptTimer = metrics.NewRegisteredTimer("les/misc/serve/receipt", nil)
- miscServingTimeTrieProofTimer = metrics.NewRegisteredTimer("les/misc/serve/proof", nil)
- miscServingTimeHelperTrieTimer = metrics.NewRegisteredTimer("les/misc/serve/helperTrie", nil)
- miscServingTimeTxTimer = metrics.NewRegisteredTimer("les/misc/serve/txs", nil)
- miscServingTimeTxStatusTimer = metrics.NewRegisteredTimer("les/misc/serve/txStatus", nil)
-
- connectionTimer = metrics.NewRegisteredTimer("les/connection/duration", nil)
- serverConnectionGauge = metrics.NewRegisteredGauge("les/connection/server", nil)
-
- totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil)
- totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil)
- blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil)
-
- requestServedMeter = metrics.NewRegisteredMeter("les/server/req/avgServedTime", nil)
- requestServedTimer = metrics.NewRegisteredTimer("les/server/req/servedTime", nil)
- requestEstimatedMeter = metrics.NewRegisteredMeter("les/server/req/avgEstimatedTime", nil)
- requestEstimatedTimer = metrics.NewRegisteredTimer("les/server/req/estimatedTime", nil)
- relativeCostHistogram = metrics.NewRegisteredHistogram("les/server/req/relative", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostHeaderHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/header", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostBodyHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/body", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostReceiptHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/receipt", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostCodeHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/code", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostProofHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/proof", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostHelperProofHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/helperTrie", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostSendTxHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/txs", nil, metrics.NewExpDecaySample(1028, 0.015))
- relativeCostTxStatusHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/txStatus", nil, metrics.NewExpDecaySample(1028, 0.015))
-
- globalFactorGauge = metrics.NewRegisteredGauge("les/server/globalFactor", nil)
- recentServedGauge = metrics.NewRegisteredGauge("les/server/recentRequestServed", nil)
- recentEstimatedGauge = metrics.NewRegisteredGauge("les/server/recentRequestEstimated", nil)
- sqServedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/served", nil)
- sqQueuedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/queued", nil)
-
- clientFreezeMeter = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil)
- clientErrorMeter = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil)
-
- requestRTT = metrics.NewRegisteredTimer("les/client/req/rtt", nil)
- requestSendDelay = metrics.NewRegisteredTimer("les/client/req/sendDelay", nil)
-
- serverSelectableGauge = metrics.NewRegisteredGauge("les/client/serverPool/selectable", nil)
- serverDialedMeter = metrics.NewRegisteredMeter("les/client/serverPool/dialed", nil)
- serverConnectedGauge = metrics.NewRegisteredGauge("les/client/serverPool/connected", nil)
- sessionValueMeter = metrics.NewRegisteredMeter("les/client/serverPool/sessionValue", nil)
- totalValueGauge = metrics.NewRegisteredGauge("les/client/serverPool/totalValue", nil)
- suggestedTimeoutGauge = metrics.NewRegisteredGauge("les/client/serverPool/timeout", nil)
-)
-
-// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
-// accumulating the above defined metrics based on the data stream contents.
-type meteredMsgReadWriter struct {
- p2p.MsgReadWriter // Wrapped message stream to meter
- version int // Protocol version to select correct meters
-}
-
-// newMeteredMsgWriter wraps a p2p MsgReadWriter with metering support. If the
-// metrics system is disabled, this function returns the original object.
-func newMeteredMsgWriter(rw p2p.MsgReadWriter, version int) p2p.MsgReadWriter {
- if !metrics.Enabled {
- return rw
- }
- return &meteredMsgReadWriter{MsgReadWriter: rw, version: version}
-}
-
-func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) {
- // Read the message and short circuit in case of an error
- msg, err := rw.MsgReadWriter.ReadMsg()
- if err != nil {
- return msg, err
- }
- // Account for the data traffic
- packets, traffic := miscInPacketsMeter, miscInTrafficMeter
- packets.Mark(1)
- traffic.Mark(int64(msg.Size))
-
- return msg, err
-}
-
-func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error {
- // Account for the data traffic
- packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter
- packets.Mark(1)
- traffic.Mark(int64(msg.Size))
-
- // Send the packet to the p2p layer
- return rw.MsgReadWriter.WriteMsg(msg)
-}
diff --git a/les/odr.go b/les/odr.go
deleted file mode 100644
index 943b05fdf..000000000
--- a/les/odr.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "context"
- "math/rand"
- "sort"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/light"
-)
-
-// LesOdr implements light.OdrBackend
-type LesOdr struct {
- db ethdb.Database
- indexerConfig *light.IndexerConfig
- chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer
- peers *serverPeerSet
- retriever *retrieveManager
- stop chan struct{}
-}
-
-func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, peers *serverPeerSet, retriever *retrieveManager) *LesOdr {
- return &LesOdr{
- db: db,
- indexerConfig: config,
- peers: peers,
- retriever: retriever,
- stop: make(chan struct{}),
- }
-}
-
-// Stop cancels all pending retrievals
-func (odr *LesOdr) Stop() {
- close(odr.stop)
-}
-
-// Database returns the backing database
-func (odr *LesOdr) Database() ethdb.Database {
- return odr.db
-}
-
-// SetIndexers adds the necessary chain indexers to the ODR backend
-func (odr *LesOdr) SetIndexers(chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer) {
- odr.chtIndexer = chtIndexer
- odr.bloomTrieIndexer = bloomTrieIndexer
- odr.bloomIndexer = bloomIndexer
-}
-
-// ChtIndexer returns the CHT chain indexer
-func (odr *LesOdr) ChtIndexer() *core.ChainIndexer {
- return odr.chtIndexer
-}
-
-// BloomTrieIndexer returns the bloom trie chain indexer
-func (odr *LesOdr) BloomTrieIndexer() *core.ChainIndexer {
- return odr.bloomTrieIndexer
-}
-
-// BloomIndexer returns the bloombits chain indexer
-func (odr *LesOdr) BloomIndexer() *core.ChainIndexer {
- return odr.bloomIndexer
-}
-
-// IndexerConfig returns the indexer config.
-func (odr *LesOdr) IndexerConfig() *light.IndexerConfig {
- return odr.indexerConfig
-}
-
-const (
- MsgBlockHeaders = iota
- MsgBlockBodies
- MsgCode
- MsgReceipts
- MsgProofsV2
- MsgHelperTrieProofs
- MsgTxStatus
-)
-
-// Msg encodes a LES message that delivers reply data for a request
-type Msg struct {
- MsgType int
- ReqID uint64
- Obj interface{}
-}
-
-// peerByTxHistory is a heap.Interface implementation which can sort
-// the peerset by transaction history.
-type peerByTxHistory []*serverPeer
-
-func (h peerByTxHistory) Len() int { return len(h) }
-func (h peerByTxHistory) Less(i, j int) bool {
- if h[i].txHistory == txIndexUnlimited {
- return false
- }
- if h[j].txHistory == txIndexUnlimited {
- return true
- }
- return h[i].txHistory < h[j].txHistory
-}
-func (h peerByTxHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
-
-const (
- maxTxStatusRetry = 3 // The maximum retries will be made for tx status request.
- maxTxStatusCandidates = 5 // The maximum les servers the tx status requests will be sent to.
-)
-
-// RetrieveTxStatus retrieves the transaction status from the LES network.
-// There is no guarantee in the LES protocol that the mined transaction will
-// be retrieved back for sure because of different reasons(the transaction
-// is unindexed, the malicious server doesn't reply it deliberately, etc).
-// Therefore, unretrieved transactions(UNKNOWN) will receive a certain number
-// of retries, thus giving a weak guarantee.
-func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequest) error {
- // Sort according to the transaction history supported by the peer and
- // select the peers with longest history.
- var (
- retries int
- peers []*serverPeer
- missing = len(req.Hashes)
- result = make([]light.TxStatus, len(req.Hashes))
- canSend = make(map[string]bool)
- )
- for _, peer := range odr.peers.allPeers() {
- if peer.txHistory == txIndexDisabled {
- continue
- }
- peers = append(peers, peer)
- }
- sort.Sort(sort.Reverse(peerByTxHistory(peers)))
- for i := 0; i < maxTxStatusCandidates && i < len(peers); i++ {
- canSend[peers[i].id] = true
- }
- // Send out the request and assemble the result.
- for {
- if retries >= maxTxStatusRetry || len(canSend) == 0 {
- break
- }
- var (
- // Deep copy the request, so that the partial result won't be mixed.
- req = &TxStatusRequest{Hashes: req.Hashes}
- id = rand.Uint64()
- distreq = &distReq{
- getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) },
- canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] },
- request: func(dp distPeer) func() {
- p := dp.(*serverPeer)
- p.fcServer.QueuedRequest(id, req.GetCost(p))
- delete(canSend, p.id)
- return func() { req.Request(id, p) }
- },
- }
- )
- if err := odr.retriever.retrieve(ctx, id, distreq, func(p distPeer, msg *Msg) error { return req.Validate(odr.db, msg) }, odr.stop); err != nil {
- return err
- }
- // Collect the response and assemble them to the final result.
- // All the response is not verifiable, so always pick the first
- // one we get.
- for index, status := range req.Status {
- if result[index].Status != txpool.TxStatusUnknown {
- continue
- }
- if status.Status == txpool.TxStatusUnknown {
- continue
- }
- result[index], missing = status, missing-1
- }
- // Abort the procedure if all the status are retrieved
- if missing == 0 {
- break
- }
- retries += 1
- }
- req.Status = result
- return nil
-}
-
-// Retrieve tries to fetch an object from the LES network. It's a common API
-// for most of the LES requests except for the TxStatusRequest which needs
-// the additional retry mechanism.
-// If the network retrieval was successful, it stores the object in local db.
-func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) {
- lreq := LesRequest(req)
-
- reqID := rand.Uint64()
- rq := &distReq{
- getCost: func(dp distPeer) uint64 {
- return lreq.GetCost(dp.(*serverPeer))
- },
- canSend: func(dp distPeer) bool {
- p := dp.(*serverPeer)
- if !p.onlyAnnounce {
- return lreq.CanSend(p)
- }
- return false
- },
- request: func(dp distPeer) func() {
- p := dp.(*serverPeer)
- cost := lreq.GetCost(p)
- p.fcServer.QueuedRequest(reqID, cost)
- return func() { lreq.Request(reqID, p) }
- },
- }
-
- defer func(sent mclock.AbsTime) {
- if err != nil {
- return
- }
- requestRTT.Update(time.Duration(mclock.Now() - sent))
- }(mclock.Now())
-
- if err := odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err != nil {
- return err
- }
- req.StoreResult(odr.db)
- return nil
-}
diff --git a/les/odr_requests.go b/les/odr_requests.go
deleted file mode 100644
index c90701859..000000000
--- a/les/odr_requests.go
+++ /dev/null
@@ -1,537 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "encoding/binary"
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "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/ethdb"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-var (
- errInvalidMessageType = errors.New("invalid message type")
- errInvalidEntryCount = errors.New("invalid number of response entries")
- errHeaderUnavailable = errors.New("header unavailable")
- errTxHashMismatch = errors.New("transaction hash mismatch")
- errUncleHashMismatch = errors.New("uncle hash mismatch")
- errReceiptHashMismatch = errors.New("receipt hash mismatch")
- errDataHashMismatch = errors.New("data hash mismatch")
- errCHTHashMismatch = errors.New("cht hash mismatch")
- errCHTNumberMismatch = errors.New("cht number mismatch")
- errUselessNodes = errors.New("useless nodes in merkle proof nodeset")
-)
-
-type LesOdrRequest interface {
- GetCost(*serverPeer) uint64
- CanSend(*serverPeer) bool
- Request(uint64, *serverPeer) error
- Validate(ethdb.Database, *Msg) error
-}
-
-func LesRequest(req light.OdrRequest) LesOdrRequest {
- switch r := req.(type) {
- case *light.BlockRequest:
- return (*BlockRequest)(r)
- case *light.ReceiptsRequest:
- return (*ReceiptsRequest)(r)
- case *light.TrieRequest:
- return (*TrieRequest)(r)
- case *light.CodeRequest:
- return (*CodeRequest)(r)
- case *light.ChtRequest:
- return (*ChtRequest)(r)
- case *light.BloomRequest:
- return (*BloomRequest)(r)
- case *light.TxStatusRequest:
- return (*TxStatusRequest)(r)
- default:
- return nil
- }
-}
-
-// BlockRequest is the ODR request type for block bodies
-type BlockRequest light.BlockRequest
-
-// GetCost returns the cost of the given ODR request according to the serving
-// peer's cost table (implementation of LesOdrRequest)
-func (r *BlockRequest) GetCost(peer *serverPeer) uint64 {
- return peer.getRequestCost(GetBlockBodiesMsg, 1)
-}
-
-// CanSend tells if a certain peer is suitable for serving the given request
-func (r *BlockRequest) CanSend(peer *serverPeer) bool {
- return peer.HasBlock(r.Hash, r.Number, false)
-}
-
-// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
-func (r *BlockRequest) Request(reqID uint64, peer *serverPeer) error {
- peer.Log().Debug("Requesting block body", "hash", r.Hash)
- return peer.requestBodies(reqID, []common.Hash{r.Hash})
-}
-
-// Validate processes an ODR request reply message from the LES network
-// returns true and stores results in memory if the message was a valid reply
-// to the request (implementation of LesOdrRequest)
-func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error {
- log.Debug("Validating block body", "hash", r.Hash)
-
- // Ensure we have a correct message with a single block body
- if msg.MsgType != MsgBlockBodies {
- return errInvalidMessageType
- }
- bodies := msg.Obj.([]*types.Body)
- if len(bodies) != 1 {
- return errInvalidEntryCount
- }
- body := bodies[0]
-
- // Retrieve our stored header and validate block content against it
- if r.Header == nil {
- r.Header = rawdb.ReadHeader(db, r.Hash, r.Number)
- }
- if r.Header == nil {
- return errHeaderUnavailable
- }
- if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)) {
- return errTxHashMismatch
- }
- if r.Header.UncleHash != types.CalcUncleHash(body.Uncles) {
- return errUncleHashMismatch
- }
- // Validations passed, encode and store RLP
- data, err := rlp.EncodeToBytes(body)
- if err != nil {
- return err
- }
- r.Rlp = data
- return nil
-}
-
-// ReceiptsRequest is the ODR request type for block receipts by block hash
-type ReceiptsRequest light.ReceiptsRequest
-
-// GetCost returns the cost of the given ODR request according to the serving
-// peer's cost table (implementation of LesOdrRequest)
-func (r *ReceiptsRequest) GetCost(peer *serverPeer) uint64 {
- return peer.getRequestCost(GetReceiptsMsg, 1)
-}
-
-// CanSend tells if a certain peer is suitable for serving the given request
-func (r *ReceiptsRequest) CanSend(peer *serverPeer) bool {
- return peer.HasBlock(r.Hash, r.Number, false)
-}
-
-// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
-func (r *ReceiptsRequest) Request(reqID uint64, peer *serverPeer) error {
- peer.Log().Debug("Requesting block receipts", "hash", r.Hash)
- return peer.requestReceipts(reqID, []common.Hash{r.Hash})
-}
-
-// Validate processes an ODR request reply message from the LES network
-// returns true and stores results in memory if the message was a valid reply
-// to the request (implementation of LesOdrRequest)
-func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error {
- log.Debug("Validating block receipts", "hash", r.Hash)
-
- // Ensure we have a correct message with a single block receipt
- if msg.MsgType != MsgReceipts {
- return errInvalidMessageType
- }
- receipts := msg.Obj.([]types.Receipts)
- if len(receipts) != 1 {
- return errInvalidEntryCount
- }
- receipt := receipts[0]
-
- // Retrieve our stored header and validate receipt content against it
- if r.Header == nil {
- r.Header = rawdb.ReadHeader(db, r.Hash, r.Number)
- }
- if r.Header == nil {
- return errHeaderUnavailable
- }
- if r.Header.ReceiptHash != types.DeriveSha(receipt, trie.NewStackTrie(nil)) {
- return errReceiptHashMismatch
- }
- // Validations passed, store and return
- r.Receipts = receipt
- return nil
-}
-
-type ProofReq struct {
- BHash common.Hash
- AccountAddress, Key []byte
- FromLevel uint
-}
-
-// ODR request type for state/storage trie entries, see LesOdrRequest interface
-type TrieRequest light.TrieRequest
-
-// GetCost returns the cost of the given ODR request according to the serving
-// peer's cost table (implementation of LesOdrRequest)
-func (r *TrieRequest) GetCost(peer *serverPeer) uint64 {
- return peer.getRequestCost(GetProofsV2Msg, 1)
-}
-
-// CanSend tells if a certain peer is suitable for serving the given request
-func (r *TrieRequest) CanSend(peer *serverPeer) bool {
- return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber, true)
-}
-
-// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
-func (r *TrieRequest) Request(reqID uint64, peer *serverPeer) error {
- peer.Log().Debug("Requesting trie proof", "root", r.Id.Root, "key", r.Key)
- req := ProofReq{
- BHash: r.Id.BlockHash,
- AccountAddress: r.Id.AccountAddress,
- Key: r.Key,
- }
- return peer.requestProofs(reqID, []ProofReq{req})
-}
-
-// Validate processes an ODR request reply message from the LES network
-// returns true and stores results in memory if the message was a valid reply
-// to the request (implementation of LesOdrRequest)
-func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
- log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key)
-
- if msg.MsgType != MsgProofsV2 {
- return errInvalidMessageType
- }
- proofs := msg.Obj.(trienode.ProofList)
- // Verify the proof and store if checks out
- nodeSet := proofs.Set()
- reads := &readTraceDB{db: nodeSet}
- if _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil {
- return fmt.Errorf("merkle proof verification failed: %v", err)
- }
- // check if all nodes have been read by VerifyProof
- if len(reads.reads) != nodeSet.KeyCount() {
- return errUselessNodes
- }
- r.Proof = nodeSet
- return nil
-}
-
-type CodeReq struct {
- BHash common.Hash
- AccountAddress []byte
-}
-
-// CodeRequest is the ODR request type for node data (used for retrieving contract code), see LesOdrRequest interface
-type CodeRequest light.CodeRequest
-
-// GetCost returns the cost of the given ODR request according to the serving
-// peer's cost table (implementation of LesOdrRequest)
-func (r *CodeRequest) GetCost(peer *serverPeer) uint64 {
- return peer.getRequestCost(GetCodeMsg, 1)
-}
-
-// CanSend tells if a certain peer is suitable for serving the given request
-func (r *CodeRequest) CanSend(peer *serverPeer) bool {
- return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber, true)
-}
-
-// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
-func (r *CodeRequest) Request(reqID uint64, peer *serverPeer) error {
- peer.Log().Debug("Requesting code data", "hash", r.Hash)
- req := CodeReq{
- BHash: r.Id.BlockHash,
- AccountAddress: r.Id.AccountAddress,
- }
- return peer.requestCode(reqID, []CodeReq{req})
-}
-
-// Validate processes an ODR request reply message from the LES network
-// returns true and stores results in memory if the message was a valid reply
-// to the request (implementation of LesOdrRequest)
-func (r *CodeRequest) Validate(db ethdb.Database, msg *Msg) error {
- log.Debug("Validating code data", "hash", r.Hash)
-
- // Ensure we have a correct message with a single code element
- if msg.MsgType != MsgCode {
- return errInvalidMessageType
- }
- reply := msg.Obj.([][]byte)
- if len(reply) != 1 {
- return errInvalidEntryCount
- }
- data := reply[0]
-
- // Verify the data and store if checks out
- if hash := crypto.Keccak256Hash(data); r.Hash != hash {
- return errDataHashMismatch
- }
- r.Data = data
- return nil
-}
-
-const (
- // helper trie type constants
- htCanonical = iota // Canonical hash trie
- htBloomBits // BloomBits trie
-
- // helper trie auxiliary types
- // htAuxNone = 1 ; deprecated number, used in les2/3 previously.
- htAuxHeader = 2 // applicable for htCanonical, requests for relevant headers
-)
-
-type HelperTrieReq struct {
- Type uint
- TrieIdx uint64
- Key []byte
- FromLevel, AuxReq uint
-}
-
-type HelperTrieResps struct { // describes all responses, not just a single one
- Proofs trienode.ProofList
- AuxData [][]byte
-}
-
-// ChtRequest is the ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface
-type ChtRequest light.ChtRequest
-
-// GetCost returns the cost of the given ODR request according to the serving
-// peer's cost table (implementation of LesOdrRequest)
-func (r *ChtRequest) GetCost(peer *serverPeer) uint64 {
- return peer.getRequestCost(GetHelperTrieProofsMsg, 1)
-}
-
-// CanSend tells if a certain peer is suitable for serving the given request
-func (r *ChtRequest) CanSend(peer *serverPeer) bool {
- peer.lock.RLock()
- defer peer.lock.RUnlock()
-
- return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize
-}
-
-// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
-func (r *ChtRequest) Request(reqID uint64, peer *serverPeer) error {
- peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum)
- var encNum [8]byte
- binary.BigEndian.PutUint64(encNum[:], r.BlockNum)
- req := HelperTrieReq{
- Type: htCanonical,
- TrieIdx: r.ChtNum,
- Key: encNum[:],
- AuxReq: htAuxHeader,
- }
- return peer.requestHelperTrieProofs(reqID, []HelperTrieReq{req})
-}
-
-// Validate processes an ODR request reply message from the LES network
-// returns true and stores results in memory if the message was a valid reply
-// to the request (implementation of LesOdrRequest)
-func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
- log.Debug("Validating CHT", "cht", r.ChtNum, "block", r.BlockNum)
-
- if msg.MsgType != MsgHelperTrieProofs {
- return errInvalidMessageType
- }
- resp := msg.Obj.(HelperTrieResps)
- if len(resp.AuxData) != 1 {
- return errInvalidEntryCount
- }
- nodeSet := resp.Proofs.Set()
- headerEnc := resp.AuxData[0]
- if len(headerEnc) == 0 {
- return errHeaderUnavailable
- }
- header := new(types.Header)
- if err := rlp.DecodeBytes(headerEnc, header); err != nil {
- return errHeaderUnavailable
- }
- // Verify the CHT
- var (
- node light.ChtNode
- encNumber [8]byte
- )
- binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
-
- reads := &readTraceDB{db: nodeSet}
- value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
- if err != nil {
- return fmt.Errorf("merkle proof verification failed: %v", err)
- }
- if len(reads.reads) != nodeSet.KeyCount() {
- return errUselessNodes
- }
- if err := rlp.DecodeBytes(value, &node); err != nil {
- return err
- }
- if node.Hash != header.Hash() {
- return errCHTHashMismatch
- }
- if r.BlockNum != header.Number.Uint64() {
- return errCHTNumberMismatch
- }
- // Verifications passed, store and return
- r.Header = header
- r.Proof = nodeSet
- r.Td = node.Td
- return nil
-}
-
-type BloomReq struct {
- BloomTrieNum, BitIdx, SectionIndex, FromLevel uint64
-}
-
-// BloomRequest is the ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface
-type BloomRequest light.BloomRequest
-
-// GetCost returns the cost of the given ODR request according to the serving
-// peer's cost table (implementation of LesOdrRequest)
-func (r *BloomRequest) GetCost(peer *serverPeer) uint64 {
- return peer.getRequestCost(GetHelperTrieProofsMsg, len(r.SectionIndexList))
-}
-
-// CanSend tells if a certain peer is suitable for serving the given request
-func (r *BloomRequest) CanSend(peer *serverPeer) bool {
- peer.lock.RLock()
- defer peer.lock.RUnlock()
-
- if peer.version < lpv2 {
- return false
- }
- return peer.headInfo.Number >= r.Config.BloomTrieConfirms && r.BloomTrieNum <= (peer.headInfo.Number-r.Config.BloomTrieConfirms)/r.Config.BloomTrieSize
-}
-
-// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
-func (r *BloomRequest) Request(reqID uint64, peer *serverPeer) error {
- peer.Log().Debug("Requesting BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList)
- reqs := make([]HelperTrieReq, len(r.SectionIndexList))
-
- var encNumber [10]byte
- binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx))
-
- for i, sectionIdx := range r.SectionIndexList {
- binary.BigEndian.PutUint64(encNumber[2:], sectionIdx)
- reqs[i] = HelperTrieReq{
- Type: htBloomBits,
- TrieIdx: r.BloomTrieNum,
- Key: common.CopyBytes(encNumber[:]),
- }
- }
- return peer.requestHelperTrieProofs(reqID, reqs)
-}
-
-// Validate processes an ODR request reply message from the LES network
-// returns true and stores results in memory if the message was a valid reply
-// to the request (implementation of LesOdrRequest)
-func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error {
- log.Debug("Validating BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList)
-
- // Ensure we have a correct message with a single proof element
- if msg.MsgType != MsgHelperTrieProofs {
- return errInvalidMessageType
- }
- resps := msg.Obj.(HelperTrieResps)
- proofs := resps.Proofs
- nodeSet := proofs.Set()
- reads := &readTraceDB{db: nodeSet}
-
- r.BloomBits = make([][]byte, len(r.SectionIndexList))
-
- // Verify the proofs
- var encNumber [10]byte
- binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx))
-
- for i, idx := range r.SectionIndexList {
- binary.BigEndian.PutUint64(encNumber[2:], idx)
- value, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads)
- if err != nil {
- return err
- }
- r.BloomBits[i] = value
- }
-
- if len(reads.reads) != nodeSet.KeyCount() {
- return errUselessNodes
- }
- r.Proofs = nodeSet
- return nil
-}
-
-// TxStatusRequest is the ODR request type for transaction status
-type TxStatusRequest light.TxStatusRequest
-
-// GetCost returns the cost of the given ODR request according to the serving
-// peer's cost table (implementation of LesOdrRequest)
-func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 {
- return peer.getRequestCost(GetTxStatusMsg, len(r.Hashes))
-}
-
-// CanSend tells if a certain peer is suitable for serving the given request
-func (r *TxStatusRequest) CanSend(peer *serverPeer) bool {
- return peer.txHistory != txIndexDisabled
-}
-
-// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
-func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error {
- peer.Log().Debug("Requesting transaction status", "count", len(r.Hashes))
- return peer.requestTxStatus(reqID, r.Hashes)
-}
-
-// Validate processes an ODR request reply message from the LES network
-// returns true and stores results in memory if the message was a valid reply
-// to the request (implementation of LesOdrRequest)
-func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error {
- log.Debug("Validating transaction status", "count", len(r.Hashes))
-
- if msg.MsgType != MsgTxStatus {
- return errInvalidMessageType
- }
- status := msg.Obj.([]light.TxStatus)
- if len(status) != len(r.Hashes) {
- return errInvalidEntryCount
- }
- r.Status = status
- return nil
-}
-
-// readTraceDB stores the keys of database reads. We use this to check that received node
-// sets contain only the trie nodes necessary to make proofs pass.
-type readTraceDB struct {
- db ethdb.KeyValueReader
- reads map[string]struct{}
-}
-
-// Get returns a stored node
-func (db *readTraceDB) Get(k []byte) ([]byte, error) {
- if db.reads == nil {
- db.reads = make(map[string]struct{})
- }
- db.reads[string(k)] = struct{}{}
- return db.db.Get(k)
-}
-
-// Has returns true if the node set contains the given key
-func (db *readTraceDB) Has(key []byte) (bool, error) {
- _, err := db.Get(key)
- return err == nil, nil
-}
diff --git a/les/odr_test.go b/les/odr_test.go
deleted file mode 100644
index 69824a92d..000000000
--- a/les/odr_test.go
+++ /dev/null
@@ -1,458 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-// Note: these tests are disabled now because they cannot work with the old sync
-// mechanism removed but will be useful again once the PoS ultralight mode is implemented
-
-/*
-import (
- "bytes"
- "context"
- "crypto/rand"
- "fmt"
- "math/big"
- "reflect"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte
-
-func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetBlock) }
-func TestOdrGetBlockLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetBlock) }
-func TestOdrGetBlockLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetBlock) }
-
-func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
- var block *types.Block
- if bc != nil {
- block = bc.GetBlockByHash(bhash)
- } else {
- block, _ = lc.GetBlockByHash(ctx, bhash)
- }
- if block == nil {
- return nil
- }
- rlp, _ := rlp.EncodeToBytes(block)
- return rlp
-}
-
-func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetReceipts) }
-func TestOdrGetReceiptsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetReceipts) }
-func TestOdrGetReceiptsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetReceipts) }
-
-func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
- var receipts types.Receipts
- if bc != nil {
- if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
- if header := rawdb.ReadHeader(db, bhash, *number); header != nil {
- receipts = rawdb.ReadReceipts(db, bhash, *number, header.Time, config)
- }
- }
- } else {
- if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
- receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, *number)
- }
- }
- if receipts == nil {
- return nil
- }
- rlp, _ := rlp.EncodeToBytes(receipts)
- return rlp
-}
-
-func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) }
-func TestOdrAccountsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrAccounts) }
-func TestOdrAccountsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrAccounts) }
-
-func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
- dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
- acc := []common.Address{bankAddr, userAddr1, userAddr2, dummyAddr}
-
- var (
- res []byte
- st *state.StateDB
- err error
- )
- for _, addr := range acc {
- if bc != nil {
- header := bc.GetHeaderByHash(bhash)
- st, err = state.New(header.Root, bc.StateCache(), nil)
- } else {
- header := lc.GetHeaderByHash(bhash)
- st = light.NewState(ctx, header, lc.Odr())
- }
- if err == nil {
- bal := st.GetBalance(addr)
- rlp, _ := rlp.EncodeToBytes(bal)
- res = append(res, rlp...)
- }
- }
- return res
-}
-
-func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) }
-func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) }
-func TestOdrContractCallLes4(t *testing.T) { testOdr(t, 4, 2, true, odrContractCall) }
-
-func odrContractCall(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
- data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000")
-
- var res []byte
- for i := 0; i < 3; i++ {
- data[35] = byte(i)
- if bc != nil {
- header := bc.GetHeaderByHash(bhash)
- statedb, err := state.New(header.Root, bc.StateCache(), nil)
-
- if err == nil {
- from := statedb.GetOrNewStateObject(bankAddr)
- from.SetBalance(math.MaxBig256)
-
- msg := &core.Message{
- From: from.Address(),
- To: &testContractAddr,
- Value: new(big.Int),
- GasLimit: 100000,
- GasPrice: big.NewInt(params.InitialBaseFee),
- GasFeeCap: big.NewInt(params.InitialBaseFee),
- GasTipCap: new(big.Int),
- Data: data,
- SkipAccountChecks: true,
- }
-
- context := core.NewEVMBlockContext(header, bc, nil)
- txContext := core.NewEVMTxContext(msg)
- vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{NoBaseFee: true})
-
- //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
- gp := new(core.GasPool).AddGas(math.MaxUint64)
- result, _ := core.ApplyMessage(vmenv, msg, gp)
- res = append(res, result.Return()...)
- }
- } else {
- header := lc.GetHeaderByHash(bhash)
- state := light.NewState(ctx, header, lc.Odr())
- state.SetBalance(bankAddr, math.MaxBig256)
- msg := &core.Message{
- From: bankAddr,
- To: &testContractAddr,
- Value: new(big.Int),
- GasLimit: 100000,
- GasPrice: big.NewInt(params.InitialBaseFee),
- GasFeeCap: big.NewInt(params.InitialBaseFee),
- GasTipCap: new(big.Int),
- Data: data,
- SkipAccountChecks: true,
- }
- context := core.NewEVMBlockContext(header, lc, nil)
- txContext := core.NewEVMTxContext(msg)
- vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true})
- gp := new(core.GasPool).AddGas(math.MaxUint64)
- result, _ := core.ApplyMessage(vmenv, msg, gp)
- if state.Error() == nil {
- res = append(res, result.Return()...)
- }
- }
- }
- return res
-}
-
-func TestOdrTxStatusLes2(t *testing.T) { testOdr(t, 2, 1, false, odrTxStatus) }
-func TestOdrTxStatusLes3(t *testing.T) { testOdr(t, 3, 1, false, odrTxStatus) }
-func TestOdrTxStatusLes4(t *testing.T) { testOdr(t, 4, 1, false, odrTxStatus) }
-
-func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
- var txs types.Transactions
- if bc != nil {
- block := bc.GetBlockByHash(bhash)
- txs = block.Transactions()
- } else {
- if block, _ := lc.GetBlockByHash(ctx, bhash); block != nil {
- btxs := block.Transactions()
- txs = make(types.Transactions, len(btxs))
- for i, tx := range btxs {
- var err error
- txs[i], _, _, _, err = light.GetTransaction(ctx, lc.Odr(), tx.Hash())
- if err != nil {
- return nil
- }
- }
- }
- }
- rlp, _ := rlp.EncodeToBytes(txs)
- return rlp
-}
-
-// testOdr tests odr requests whose validation guaranteed by block headers.
-func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) {
- // Assemble the test environment
- netconfig := testnetConfig{
- blocks: 4,
- protocol: protocol,
- connect: true,
- nopruning: true,
- }
- server, client, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- // Ensure the client has synced all necessary data.
- clientHead := client.handler.backend.blockchain.CurrentHeader()
- if clientHead.Number.Uint64() != 4 {
- t.Fatalf("Failed to sync the chain with server, head: %v", clientHead.Number.Uint64())
- }
- // Disable the mechanism that we will wait a few time for request
- // even there is no suitable peer to send right now.
- waitForPeers = 0
-
- test := func(expFail uint64) {
- // Mark this as a helper to put the failures at the correct lines
- t.Helper()
-
- for i := uint64(0); i <= server.handler.blockchain.CurrentHeader().Number.Uint64(); i++ {
- bhash := rawdb.ReadCanonicalHash(server.db, i)
- b1 := fn(light.NoOdr, server.db, server.handler.server.chainConfig, server.handler.blockchain, nil, bhash)
-
- // Set the timeout as 1 second here, ensure there is enough time
- // for travis to make the action.
- ctx, cancel := context.WithTimeout(context.Background(), time.Second)
- b2 := fn(ctx, client.db, client.handler.backend.chainConfig, nil, client.handler.backend.blockchain, bhash)
- cancel()
-
- eq := bytes.Equal(b1, b2)
- exp := i < expFail
- if exp && !eq {
- t.Fatalf("odr mismatch: have %x, want %x", b2, b1)
- }
- if !exp && eq {
- t.Fatalf("unexpected odr match")
- }
- }
- }
-
- // expect retrievals to fail (except genesis block) without a les peer
- client.handler.backend.peers.lock.Lock()
- client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return false }
- client.handler.backend.peers.lock.Unlock()
- test(expFail)
-
- // expect all retrievals to pass
- client.handler.backend.peers.lock.Lock()
- client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return true }
- client.handler.backend.peers.lock.Unlock()
- test(5)
-
- // still expect all retrievals to pass, now data should be cached locally
- if checkCached {
- client.handler.backend.peers.unregister(client.peer.speer.id)
- time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
- test(5)
- }
-}
-
-func TestGetTxStatusFromUnindexedPeersLES4(t *testing.T) { testGetTxStatusFromUnindexedPeers(t, lpv4) }
-
-func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) {
- var (
- blocks = 8
- netconfig = testnetConfig{
- blocks: blocks,
- protocol: protocol,
- nopruning: true,
- }
- )
- server, client, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- // Iterate the chain, create the tx indexes locally
- var (
- testHash common.Hash
- testStatus light.TxStatus
-
- txs = make(map[common.Hash]*types.Transaction) // Transaction objects set
- blockNumbers = make(map[common.Hash]uint64) // Transaction hash to block number mappings
- blockHashes = make(map[common.Hash]common.Hash) // Transaction hash to block hash mappings
- intraIndex = make(map[common.Hash]uint64) // Transaction intra-index in block
- )
- for number := uint64(1); number < server.backend.Blockchain().CurrentBlock().Number.Uint64(); number++ {
- block := server.backend.Blockchain().GetBlockByNumber(number)
- if block == nil {
- t.Fatalf("Failed to retrieve block %d", number)
- }
- for index, tx := range block.Transactions() {
- txs[tx.Hash()] = tx
- blockNumbers[tx.Hash()] = number
- blockHashes[tx.Hash()] = block.Hash()
- intraIndex[tx.Hash()] = uint64(index)
-
- if testHash == (common.Hash{}) {
- testHash = tx.Hash()
- testStatus = light.TxStatus{
- Status: txpool.TxStatusIncluded,
- Lookup: &rawdb.LegacyTxLookupEntry{
- BlockHash: block.Hash(),
- BlockIndex: block.NumberU64(),
- Index: uint64(index),
- },
- }
- }
- }
- }
- // serveMsg processes incoming GetTxStatusMsg and sends the response back.
- serveMsg := func(peer *testPeer, txLookup uint64) error {
- msg, err := peer.app.ReadMsg()
- if err != nil {
- return err
- }
- if msg.Code != GetTxStatusMsg {
- return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, GetTxStatusMsg)
- }
- var r GetTxStatusPacket
- if err := msg.Decode(&r); err != nil {
- return err
- }
- stats := make([]light.TxStatus, len(r.Hashes))
- for i, hash := range r.Hashes {
- number, exist := blockNumbers[hash]
- if !exist {
- continue // Filter out unknown transactions
- }
- min := uint64(blocks) - txLookup
- if txLookup != txIndexUnlimited && (txLookup == txIndexDisabled || number < min) {
- continue // Filter out unindexed transactions
- }
- stats[i].Status = txpool.TxStatusIncluded
- stats[i].Lookup = &rawdb.LegacyTxLookupEntry{
- BlockHash: blockHashes[hash],
- BlockIndex: number,
- Index: intraIndex[hash],
- }
- }
- data, _ := rlp.EncodeToBytes(stats)
- reply := &reply{peer.app, TxStatusMsg, r.ReqID, data}
- reply.send(testBufLimit)
- return nil
- }
-
- var testspecs = []struct {
- peers int
- txLookups []uint64
- txs []common.Hash
- results []light.TxStatus
- }{
- // Retrieve mined transaction from the empty peerset
- {
- peers: 0,
- txLookups: []uint64{},
- txs: []common.Hash{testHash},
- results: []light.TxStatus{{}},
- },
- // Retrieve unknown transaction from the full peers
- {
- peers: 3,
- txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
- txs: []common.Hash{randomHash()},
- results: []light.TxStatus{{}},
- },
- // Retrieve mined transaction from the full peers
- {
- peers: 3,
- txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
- txs: []common.Hash{testHash},
- results: []light.TxStatus{testStatus},
- },
- // Retrieve mixed transactions from the full peers
- {
- peers: 3,
- txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
- txs: []common.Hash{randomHash(), testHash},
- results: []light.TxStatus{{}, testStatus},
- },
- // Retrieve mixed transactions from unindexed peer(but the target is still available)
- {
- peers: 3,
- txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2},
- txs: []common.Hash{randomHash(), testHash},
- results: []light.TxStatus{{}, testStatus},
- },
- // Retrieve mixed transactions from unindexed peer(but the target is not available)
- {
- peers: 3,
- txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2},
- txs: []common.Hash{randomHash(), testHash},
- results: []light.TxStatus{{}, {}},
- },
- }
- for _, testspec := range testspecs {
- // Create a bunch of server peers with different tx history
- var (
- closeFns []func()
- )
- for i := 0; i < testspec.peers; i++ {
- peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i])
- closeFns = append(closeFns, closePeer)
-
- // Create a one-time routine for serving message
- go func(i int, peer *testPeer, lookup uint64) {
- serveMsg(peer, lookup)
- }(i, peer, testspec.txLookups[i])
- }
-
- // Send out the GetTxStatus requests, compare the result with
- // expected value.
- r := &light.TxStatusRequest{Hashes: testspec.txs}
- ctx, cancel := context.WithTimeout(context.Background(), time.Second)
- defer cancel()
-
- err := client.handler.backend.odr.RetrieveTxStatus(ctx, r)
- if err != nil {
- t.Errorf("Failed to retrieve tx status %v", err)
- } else {
- if !reflect.DeepEqual(testspec.results, r.Status) {
- t.Errorf("Result mismatch, diff")
- }
- }
-
- // Close all connected peers and start the next round
- for _, closeFn := range closeFns {
- closeFn()
- }
- }
-}
-
-// randomHash generates a random blob of data and returns it as a hash.
-func randomHash() common.Hash {
- var hash common.Hash
- if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
- panic(err)
- }
- return hash
-}
-*/
diff --git a/les/peer.go b/les/peer.go
deleted file mode 100644
index 58cb92870..000000000
--- a/les/peer.go
+++ /dev/null
@@ -1,1362 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "crypto/ecdsa"
- "errors"
- "fmt"
- "math/big"
- "math/rand"
- "net"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/les/flowcontrol"
- "github.com/ethereum/go-ethereum/les/utils"
- vfc "github.com/ethereum/go-ethereum/les/vflux/client"
- vfs "github.com/ethereum/go-ethereum/les/vflux/server"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-var (
- errClosed = errors.New("peer set is closed")
- errAlreadyRegistered = errors.New("peer is already registered")
- errNotRegistered = errors.New("peer is not registered")
-)
-
-const (
- maxRequestErrors = 20 // number of invalid requests tolerated (makes the protocol less brittle but still avoids spam)
- maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam)
-
- allowedUpdateBytes = 100000 // initial/maximum allowed update size
- allowedUpdateRate = time.Millisecond * 10 // time constant for recharging one byte of allowance
-
- freezeTimeBase = time.Millisecond * 700 // fixed component of client freeze time
- freezeTimeRandom = time.Millisecond * 600 // random component of client freeze time
- freezeCheckPeriod = time.Millisecond * 100 // buffer value recheck period after initial freeze time has elapsed
-
- // If the total encoded size of a sent transaction batch is over txSizeCostLimit
- // per transaction then the request cost is calculated as proportional to the
- // encoded size instead of the transaction count
- txSizeCostLimit = 0x4000
-
- // handshakeTimeout is the timeout LES handshake will be treated as failed.
- handshakeTimeout = 5 * time.Second
-)
-
-const (
- announceTypeNone = iota
- announceTypeSimple
- announceTypeSigned
-)
-
-type keyValueEntry struct {
- Key string
- Value rlp.RawValue
-}
-
-type keyValueList []keyValueEntry
-type keyValueMap map[string]rlp.RawValue
-
-func (l keyValueList) add(key string, val interface{}) keyValueList {
- var entry keyValueEntry
- entry.Key = key
- if val == nil {
- val = uint64(0)
- }
- enc, err := rlp.EncodeToBytes(val)
- if err == nil {
- entry.Value = enc
- }
- return append(l, entry)
-}
-
-func (l keyValueList) decode() (keyValueMap, uint64) {
- m := make(keyValueMap)
- var size uint64
- for _, entry := range l {
- m[entry.Key] = entry.Value
- size += uint64(len(entry.Key)) + uint64(len(entry.Value)) + 8
- }
- return m, size
-}
-
-func (m keyValueMap) get(key string, val interface{}) error {
- enc, ok := m[key]
- if !ok {
- return errResp(ErrMissingKey, "%s", key)
- }
- if val == nil {
- return nil
- }
- return rlp.DecodeBytes(enc, val)
-}
-
-// peerCommons contains fields needed by both server peer and client peer.
-type peerCommons struct {
- *p2p.Peer
- rw p2p.MsgReadWriter
-
- id string // Peer identity.
- version int // Protocol version negotiated.
- network uint64 // Network ID being on.
- frozen atomic.Bool // Flag whether the peer is frozen.
- announceType uint64 // New block announcement type.
- serving atomic.Bool // The status indicates the peer is served.
- headInfo blockInfo // Last announced block information.
-
- // Background task queue for caching peer tasks and executing in order.
- sendQueue *utils.ExecQueue
-
- // Flow control agreement.
- fcParams flowcontrol.ServerParams // The config for token bucket.
- fcCosts requestCostTable // The Maximum request cost table.
-
- closeCh chan struct{}
- lock sync.RWMutex // Lock used to protect all thread-sensitive fields.
-}
-
-// isFrozen returns true if the client is frozen or the server has put our
-// client in frozen state
-func (p *peerCommons) isFrozen() bool {
- return p.frozen.Load()
-}
-
-// canQueue returns an indicator whether the peer can queue an operation.
-func (p *peerCommons) canQueue() bool {
- return p.sendQueue.CanQueue() && !p.isFrozen()
-}
-
-// queueSend caches a peer operation in the background task queue.
-// Please ensure to check `canQueue` before call this function
-func (p *peerCommons) queueSend(f func()) bool {
- return p.sendQueue.Queue(f)
-}
-
-// String implements fmt.Stringer.
-func (p *peerCommons) String() string {
- return fmt.Sprintf("Peer %s [%s]", p.id, fmt.Sprintf("les/%d", p.version))
-}
-
-// PeerInfo represents a short summary of the `eth` sub-protocol metadata known
-// about a connected peer.
-type PeerInfo struct {
- Version int `json:"version"` // Ethereum protocol version negotiated
- Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain
- Head string `json:"head"` // SHA3 hash of the peer's best owned block
-}
-
-// Info gathers and returns a collection of metadata known about a peer.
-func (p *peerCommons) Info() *PeerInfo {
- return &PeerInfo{
- Version: p.version,
- Difficulty: p.Td(),
- Head: fmt.Sprintf("%x", p.Head()),
- }
-}
-
-// Head retrieves a copy of the current head (most recent) hash of the peer.
-func (p *peerCommons) Head() (hash common.Hash) {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- return p.headInfo.Hash
-}
-
-// Td retrieves the current total difficulty of a peer.
-func (p *peerCommons) Td() *big.Int {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- return new(big.Int).Set(p.headInfo.Td)
-}
-
-// HeadAndTd retrieves the current head hash and total difficulty of a peer.
-func (p *peerCommons) HeadAndTd() (hash common.Hash, td *big.Int) {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- return p.headInfo.Hash, new(big.Int).Set(p.headInfo.Td)
-}
-
-// sendReceiveHandshake exchanges handshake packet with remote peer and returns any error
-// if failed to send or receive packet.
-func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList, error) {
- var (
- errc = make(chan error, 2)
- recvList keyValueList
- )
- // Send out own handshake in a new thread
- go func() {
- errc <- p2p.Send(p.rw, StatusMsg, &sendList)
- }()
- go func() {
- // In the mean time retrieve the remote status message
- msg, err := p.rw.ReadMsg()
- if err != nil {
- errc <- err
- return
- }
- if msg.Code != StatusMsg {
- errc <- errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
- return
- }
- if msg.Size > ProtocolMaxMsgSize {
- errc <- errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
- return
- }
- // Decode the handshake
- if err := msg.Decode(&recvList); err != nil {
- errc <- errResp(ErrDecode, "msg %v: %v", msg, err)
- return
- }
- errc <- nil
- }()
- timeout := time.NewTimer(handshakeTimeout)
- defer timeout.Stop()
- for i := 0; i < 2; i++ {
- select {
- case err := <-errc:
- if err != nil {
- return nil, err
- }
- case <-timeout.C:
- return nil, p2p.DiscReadTimeout
- }
- }
- return recvList, nil
-}
-
-// handshake executes the les protocol handshake, negotiating version number,
-// network IDs, difficulties, head and genesis blocks. Besides the basic handshake
-// fields, server and client can exchange and resolve some specified fields through
-// two callback functions.
-func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- var send keyValueList
-
- // Add some basic handshake fields
- send = send.add("protocolVersion", uint64(p.version))
- send = send.add("networkId", p.network)
- // Note: the head info announced at handshake is only used in case of server peers
- // but dummy values are still announced by clients for compatibility with older servers
- send = send.add("headTd", td)
- send = send.add("headHash", head)
- send = send.add("headNum", headNum)
- send = send.add("genesisHash", genesis)
-
- // If the protocol version is beyond les4, then pass the forkID
- // as well. Check http://eips.ethereum.org/EIPS/eip-2124 for more
- // spec detail.
- if p.version >= lpv4 {
- send = send.add("forkID", forkID)
- }
- // Add client-specified or server-specified fields
- if sendCallback != nil {
- sendCallback(&send)
- }
- // Exchange the handshake packet and resolve the received one.
- recvList, err := p.sendReceiveHandshake(send)
- if err != nil {
- return err
- }
- recv, size := recvList.decode()
- if size > allowedUpdateBytes {
- return errResp(ErrRequestRejected, "")
- }
- var rGenesis common.Hash
- var rVersion, rNetwork uint64
- if err := recv.get("protocolVersion", &rVersion); err != nil {
- return err
- }
- if err := recv.get("networkId", &rNetwork); err != nil {
- return err
- }
- if err := recv.get("genesisHash", &rGenesis); err != nil {
- return err
- }
- if rGenesis != genesis {
- return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8])
- }
- if rNetwork != p.network {
- return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network)
- }
- if int(rVersion) != p.version {
- return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version)
- }
- // Check forkID if the protocol version is beyond the les4
- if p.version >= lpv4 {
- var forkID forkid.ID
- if err := recv.get("forkID", &forkID); err != nil {
- return err
- }
- if err := forkFilter(forkID); err != nil {
- return errResp(ErrForkIDRejected, "%v", err)
- }
- }
- if recvCallback != nil {
- return recvCallback(recv)
- }
- return nil
-}
-
-// close closes the channel and notifies all background routines to exit.
-func (p *peerCommons) close() {
- close(p.closeCh)
- p.sendQueue.Quit()
-}
-
-// serverPeer represents each node to which the client is connected.
-// The node here refers to the les server.
-type serverPeer struct {
- peerCommons
-
- // Status fields
- trusted bool // The flag whether the server is selected as trusted server.
- onlyAnnounce bool // The flag whether the server sends announcement only.
- chainSince, chainRecent uint64 // The range of chain server peer can serve.
- stateSince, stateRecent uint64 // The range of state server peer can serve.
- txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled
-
- fcServer *flowcontrol.ServerNode // Client side mirror token bucket.
- vtLock sync.Mutex
- nodeValueTracker *vfc.NodeValueTracker
- sentReqs map[uint64]sentReqEntry
-
- // Statistics
- errCount utils.LinearExpiredValue // Counter the invalid responses server has replied
- updateCount uint64
- updateTime mclock.AbsTime
-
- // Test callback hooks
- hasBlockHook func(common.Hash, uint64, bool) bool // Used to determine whether the server has the specified block.
-}
-
-func newServerPeer(version int, network uint64, trusted bool, p *p2p.Peer, rw p2p.MsgReadWriter) *serverPeer {
- return &serverPeer{
- peerCommons: peerCommons{
- Peer: p,
- rw: rw,
- id: p.ID().String(),
- version: version,
- network: network,
- sendQueue: utils.NewExecQueue(100),
- closeCh: make(chan struct{}),
- },
- trusted: trusted,
- errCount: utils.LinearExpiredValue{Rate: mclock.AbsTime(time.Hour)},
- }
-}
-
-// rejectUpdate returns true if a parameter update has to be rejected because
-// the size and/or rate of updates exceed the capacity limitation
-func (p *serverPeer) rejectUpdate(size uint64) bool {
- now := mclock.Now()
- if p.updateCount == 0 {
- p.updateTime = now
- } else {
- dt := now - p.updateTime
- p.updateTime = now
-
- r := uint64(dt / mclock.AbsTime(allowedUpdateRate))
- if p.updateCount > r {
- p.updateCount -= r
- } else {
- p.updateCount = 0
- }
- }
- p.updateCount += size
- return p.updateCount > allowedUpdateBytes
-}
-
-// freeze processes Stop messages from the given server and set the status as
-// frozen.
-func (p *serverPeer) freeze() {
- if p.frozen.CompareAndSwap(false, true) {
- p.sendQueue.Clear()
- }
-}
-
-// unfreeze processes Resume messages from the given server and set the status
-// as unfrozen.
-func (p *serverPeer) unfreeze() {
- p.frozen.Store(false)
-}
-
-// sendRequest send a request to the server based on the given message type
-// and content.
-func sendRequest(w p2p.MsgWriter, msgcode, reqID uint64, data interface{}) error {
- type req struct {
- ReqID uint64
- Data interface{}
- }
- return p2p.Send(w, msgcode, &req{reqID, data})
-}
-
-func (p *serverPeer) sendRequest(msgcode, reqID uint64, data interface{}, amount int) error {
- p.sentRequest(reqID, uint32(msgcode), uint32(amount))
- return sendRequest(p.rw, msgcode, reqID, data)
-}
-
-// requestHeadersByHash fetches a batch of blocks' headers corresponding to the
-// specified header query, based on the hash of an origin block.
-func (p *serverPeer) requestHeadersByHash(reqID uint64, origin common.Hash, amount int, skip int, reverse bool) error {
- p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
- return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount)
-}
-
-// requestHeadersByNumber fetches a batch of blocks' headers corresponding to the
-// specified header query, based on the number of an origin block.
-func (p *serverPeer) requestHeadersByNumber(reqID, origin uint64, amount int, skip int, reverse bool) error {
- p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
- return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount)
-}
-
-// requestBodies fetches a batch of blocks' bodies corresponding to the hashes
-// specified.
-func (p *serverPeer) requestBodies(reqID uint64, hashes []common.Hash) error {
- p.Log().Debug("Fetching batch of block bodies", "count", len(hashes))
- return p.sendRequest(GetBlockBodiesMsg, reqID, hashes, len(hashes))
-}
-
-// requestCode fetches a batch of arbitrary data from a node's known state
-// data, corresponding to the specified hashes.
-func (p *serverPeer) requestCode(reqID uint64, reqs []CodeReq) error {
- p.Log().Debug("Fetching batch of codes", "count", len(reqs))
- return p.sendRequest(GetCodeMsg, reqID, reqs, len(reqs))
-}
-
-// requestReceipts fetches a batch of transaction receipts from a remote node.
-func (p *serverPeer) requestReceipts(reqID uint64, hashes []common.Hash) error {
- p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
- return p.sendRequest(GetReceiptsMsg, reqID, hashes, len(hashes))
-}
-
-// requestProofs fetches a batch of merkle proofs from a remote node.
-func (p *serverPeer) requestProofs(reqID uint64, reqs []ProofReq) error {
- p.Log().Debug("Fetching batch of proofs", "count", len(reqs))
- return p.sendRequest(GetProofsV2Msg, reqID, reqs, len(reqs))
-}
-
-// requestHelperTrieProofs fetches a batch of HelperTrie merkle proofs from a remote node.
-func (p *serverPeer) requestHelperTrieProofs(reqID uint64, reqs []HelperTrieReq) error {
- p.Log().Debug("Fetching batch of HelperTrie proofs", "count", len(reqs))
- return p.sendRequest(GetHelperTrieProofsMsg, reqID, reqs, len(reqs))
-}
-
-// requestTxStatus fetches a batch of transaction status records from a remote node.
-func (p *serverPeer) requestTxStatus(reqID uint64, txHashes []common.Hash) error {
- p.Log().Debug("Requesting transaction status", "count", len(txHashes))
- return p.sendRequest(GetTxStatusMsg, reqID, txHashes, len(txHashes))
-}
-
-// sendTxs creates a reply with a batch of transactions to be added to the remote transaction pool.
-func (p *serverPeer) sendTxs(reqID uint64, amount int, txs rlp.RawValue) error {
- p.Log().Debug("Sending batch of transactions", "amount", amount, "size", len(txs))
- sizeFactor := (len(txs) + txSizeCostLimit/2) / txSizeCostLimit
- if sizeFactor > amount {
- amount = sizeFactor
- }
- return p.sendRequest(SendTxV2Msg, reqID, txs, amount)
-}
-
-// waitBefore implements distPeer interface
-func (p *serverPeer) waitBefore(maxCost uint64) (time.Duration, float64) {
- return p.fcServer.CanSend(maxCost)
-}
-
-// getRequestCost returns an estimated request cost according to the flow control
-// rules negotiated between the server and the client.
-func (p *serverPeer) getRequestCost(msgcode uint64, amount int) uint64 {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- costs := p.fcCosts[msgcode]
- if costs == nil {
- return 0
- }
- cost := costs.baseCost + costs.reqCost*uint64(amount)
- if cost > p.fcParams.BufLimit {
- cost = p.fcParams.BufLimit
- }
- return cost
-}
-
-// getTxRelayCost returns an estimated relay cost according to the flow control
-// rules negotiated between the server and the client.
-func (p *serverPeer) getTxRelayCost(amount, size int) uint64 {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- costs := p.fcCosts[SendTxV2Msg]
- if costs == nil {
- return 0
- }
- cost := costs.baseCost + costs.reqCost*uint64(amount)
- sizeCost := costs.baseCost + costs.reqCost*uint64(size)/txSizeCostLimit
- if sizeCost > cost {
- cost = sizeCost
- }
- if cost > p.fcParams.BufLimit {
- cost = p.fcParams.BufLimit
- }
- return cost
-}
-
-// HasBlock checks if the peer has a given block
-func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bool {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- if p.hasBlockHook != nil {
- return p.hasBlockHook(hash, number, hasState)
- }
- head := p.headInfo.Number
- var since, recent uint64
- if hasState {
- since = p.stateSince
- recent = p.stateRecent
- } else {
- since = p.chainSince
- recent = p.chainRecent
- }
- return head >= number && number >= since && (recent == 0 || number+recent+4 > head)
-}
-
-// updateFlowControl updates the flow control parameters belonging to the server
-// node if the announced key/value set contains relevant fields
-func (p *serverPeer) updateFlowControl(update keyValueMap) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- // If any of the flow control params is nil, refuse to update.
- var params flowcontrol.ServerParams
- if update.get("flowControl/BL", ¶ms.BufLimit) == nil && update.get("flowControl/MRR", ¶ms.MinRecharge) == nil {
- // todo can light client set a minimal acceptable flow control params?
- p.fcParams = params
- p.fcServer.UpdateParams(params)
- }
- var MRC RequestCostList
- if update.get("flowControl/MRC", &MRC) == nil {
- costUpdate := MRC.decode(ProtocolLengths[uint(p.version)])
- for code, cost := range costUpdate {
- p.fcCosts[code] = cost
- }
- }
-}
-
-// updateHead updates the head information based on the announcement from
-// the peer.
-func (p *serverPeer) updateHead(hash common.Hash, number uint64, td *big.Int) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- p.headInfo = blockInfo{Hash: hash, Number: number, Td: td}
-}
-
-// Handshake executes the les protocol handshake, negotiating version number,
-// network IDs and genesis blocks.
-func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter forkid.Filter) error {
- // Note: there is no need to share local head with a server but older servers still
- // require these fields so we announce zero values.
- return p.handshake(common.Big0, common.Hash{}, 0, genesis, forkid, forkFilter, func(lists *keyValueList) {
- // Add some client-specific handshake fields
- //
- // Enable signed announcement randomly even the server is not trusted.
- p.announceType = announceTypeSimple
- if p.trusted {
- p.announceType = announceTypeSigned
- }
- *lists = (*lists).add("announceType", p.announceType)
- }, func(recv keyValueMap) error {
- var (
- rHash common.Hash
- rNum uint64
- rTd *big.Int
- )
- if err := recv.get("headTd", &rTd); err != nil {
- return err
- }
- if err := recv.get("headHash", &rHash); err != nil {
- return err
- }
- if err := recv.get("headNum", &rNum); err != nil {
- return err
- }
- p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd}
- if recv.get("serveChainSince", &p.chainSince) != nil {
- p.onlyAnnounce = true
- }
- if recv.get("serveRecentChain", &p.chainRecent) != nil {
- p.chainRecent = 0
- }
- if recv.get("serveStateSince", &p.stateSince) != nil {
- p.onlyAnnounce = true
- }
- if recv.get("serveRecentState", &p.stateRecent) != nil {
- p.stateRecent = 0
- }
- if recv.get("txRelay", nil) != nil {
- p.onlyAnnounce = true
- }
- if p.version >= lpv4 {
- var recentTx uint
- if err := recv.get("recentTxLookup", &recentTx); err != nil {
- return err
- }
- p.txHistory = uint64(recentTx)
- } else {
- // The weak assumption is held here that legacy les server(les2,3)
- // has unlimited transaction history. The les serving in these legacy
- // versions is disabled if the transaction is unindexed.
- p.txHistory = txIndexUnlimited
- }
- if p.onlyAnnounce && !p.trusted {
- return errResp(ErrUselessPeer, "peer cannot serve requests")
- }
- // Parse flow control handshake packet.
- var sParams flowcontrol.ServerParams
- if err := recv.get("flowControl/BL", &sParams.BufLimit); err != nil {
- return err
- }
- if err := recv.get("flowControl/MRR", &sParams.MinRecharge); err != nil {
- return err
- }
- var MRC RequestCostList
- if err := recv.get("flowControl/MRC", &MRC); err != nil {
- return err
- }
- p.fcParams = sParams
- p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{})
- p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)])
-
- if !p.onlyAnnounce {
- for msgCode := range reqAvgTimeCost {
- if p.fcCosts[msgCode] == nil {
- return errResp(ErrUselessPeer, "peer does not support message %d", msgCode)
- }
- }
- }
- return nil
- })
-}
-
-// setValueTracker sets the value tracker references for connected servers. Note that the
-// references should be removed upon disconnection by setValueTracker(nil, nil).
-func (p *serverPeer) setValueTracker(nvt *vfc.NodeValueTracker) {
- p.vtLock.Lock()
- p.nodeValueTracker = nvt
- if nvt != nil {
- p.sentReqs = make(map[uint64]sentReqEntry)
- } else {
- p.sentReqs = nil
- }
- p.vtLock.Unlock()
-}
-
-// updateVtParams updates the server's price table in the value tracker.
-func (p *serverPeer) updateVtParams() {
- p.vtLock.Lock()
- defer p.vtLock.Unlock()
-
- if p.nodeValueTracker == nil {
- return
- }
- reqCosts := make([]uint64, len(requestList))
- for code, costs := range p.fcCosts {
- if m, ok := requestMapping[uint32(code)]; ok {
- reqCosts[m.first] = costs.baseCost + costs.reqCost
- if m.rest != -1 {
- reqCosts[m.rest] = costs.reqCost
- }
- }
- }
- p.nodeValueTracker.UpdateCosts(reqCosts)
-}
-
-// sentReqEntry remembers sent requests and their sending times
-type sentReqEntry struct {
- reqType, amount uint32
- at mclock.AbsTime
-}
-
-// sentRequest marks a request sent at the current moment to this server.
-func (p *serverPeer) sentRequest(id uint64, reqType, amount uint32) {
- p.vtLock.Lock()
- if p.sentReqs != nil {
- p.sentReqs[id] = sentReqEntry{reqType, amount, mclock.Now()}
- }
- p.vtLock.Unlock()
-}
-
-// answeredRequest marks a request answered at the current moment by this server.
-func (p *serverPeer) answeredRequest(id uint64) {
- p.vtLock.Lock()
- if p.sentReqs == nil {
- p.vtLock.Unlock()
- return
- }
- e, ok := p.sentReqs[id]
- delete(p.sentReqs, id)
- nvt := p.nodeValueTracker
- p.vtLock.Unlock()
- if !ok {
- return
- }
- var (
- vtReqs [2]vfc.ServedRequest
- reqCount int
- )
- m := requestMapping[e.reqType]
- if m.rest == -1 || e.amount <= 1 {
- reqCount = 1
- vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount}
- } else {
- reqCount = 2
- vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: 1}
- vtReqs[1] = vfc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1}
- }
- dt := time.Duration(mclock.Now() - e.at)
- nvt.Served(vtReqs[:reqCount], dt)
-}
-
-// clientPeer represents each node to which the les server is connected.
-// The node here refers to the light client.
-type clientPeer struct {
- peerCommons
-
- // responseLock ensures that responses are queued in the same order as
- // RequestProcessed is called
- responseLock sync.Mutex
- responseCount uint64 // Counter to generate an unique id for request processing.
-
- balance vfs.ConnectedBalance
-
- // invalidLock is used for protecting invalidCount.
- invalidLock sync.RWMutex
- invalidCount utils.LinearExpiredValue // Counter the invalid request the client peer has made.
-
- capacity uint64
- // lastAnnounce is the last broadcast created by the server; may be newer than the last head
- // sent to the specific client (stored in headInfo) if capacity is zero. In this case the
- // latest head is sent when the client gains non-zero capacity.
- lastAnnounce announceData
-
- connectedAt mclock.AbsTime
- server bool
- errCh chan error
- fcClient *flowcontrol.ClientNode // Server side mirror token bucket.
-}
-
-func newClientPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *clientPeer {
- return &clientPeer{
- peerCommons: peerCommons{
- Peer: p,
- rw: rw,
- id: p.ID().String(),
- version: version,
- network: network,
- sendQueue: utils.NewExecQueue(100),
- closeCh: make(chan struct{}),
- },
- invalidCount: utils.LinearExpiredValue{Rate: mclock.AbsTime(time.Hour)},
- errCh: make(chan error, 1),
- }
-}
-
-// FreeClientId returns a string identifier for the peer. Multiple peers with
-// the same identifier can not be connected in free mode simultaneously.
-func (p *clientPeer) FreeClientId() string {
- if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok {
- if addr.IP.IsLoopback() {
- // using peer id instead of loopback ip address allows multiple free
- // connections from local machine to own server
- return p.id
- } else {
- return addr.IP.String()
- }
- }
- return p.id
-}
-
-// sendStop notifies the client about being in frozen state
-func (p *clientPeer) sendStop() error {
- return p2p.Send(p.rw, StopMsg, struct{}{})
-}
-
-// sendResume notifies the client about getting out of frozen state
-func (p *clientPeer) sendResume(bv uint64) error {
- return p2p.Send(p.rw, ResumeMsg, bv)
-}
-
-// freeze temporarily puts the client in a frozen state which means all unprocessed
-// and subsequent requests are dropped. Unfreezing happens automatically after a short
-// time if the client's buffer value is at least in the slightly positive region.
-// The client is also notified about being frozen/unfrozen with a Stop/Resume message.
-func (p *clientPeer) freeze() {
- if p.version < lpv3 {
- // if Stop/Resume is not supported then just drop the peer after setting
- // its frozen status permanently
- p.frozen.Store(true)
- p.Peer.Disconnect(p2p.DiscUselessPeer)
- return
- }
- if !p.frozen.Swap(true) {
- go func() {
- p.sendStop()
- time.Sleep(freezeTimeBase + time.Duration(rand.Int63n(int64(freezeTimeRandom))))
- for {
- bufValue, bufLimit := p.fcClient.BufferStatus()
- if bufLimit == 0 {
- return
- }
- if bufValue <= bufLimit/8 {
- time.Sleep(freezeCheckPeriod)
- continue
- }
- p.frozen.Store(false)
- p.sendResume(bufValue)
- return
- }
- }()
- }
-}
-
-// reply struct represents a reply with the actual data already RLP encoded and
-// only the bv (buffer value) missing. This allows the serving mechanism to
-// calculate the bv value which depends on the data size before sending the reply.
-type reply struct {
- w p2p.MsgWriter
- msgcode, reqID uint64
- data rlp.RawValue
-}
-
-// send sends the reply with the calculated buffer value
-func (r *reply) send(bv uint64) error {
- type resp struct {
- ReqID, BV uint64
- Data rlp.RawValue
- }
- return p2p.Send(r.w, r.msgcode, &resp{r.reqID, bv, r.data})
-}
-
-// size returns the RLP encoded size of the message data
-func (r *reply) size() uint32 {
- return uint32(len(r.data))
-}
-
-// replyBlockHeaders creates a reply with a batch of block headers
-func (p *clientPeer) replyBlockHeaders(reqID uint64, headers []*types.Header) *reply {
- data, _ := rlp.EncodeToBytes(headers)
- return &reply{p.rw, BlockHeadersMsg, reqID, data}
-}
-
-// replyBlockBodiesRLP creates a reply with a batch of block contents from
-// an already RLP encoded format.
-func (p *clientPeer) replyBlockBodiesRLP(reqID uint64, bodies []rlp.RawValue) *reply {
- data, _ := rlp.EncodeToBytes(bodies)
- return &reply{p.rw, BlockBodiesMsg, reqID, data}
-}
-
-// replyCode creates a reply with a batch of arbitrary internal data, corresponding to the
-// hashes requested.
-func (p *clientPeer) replyCode(reqID uint64, codes [][]byte) *reply {
- data, _ := rlp.EncodeToBytes(codes)
- return &reply{p.rw, CodeMsg, reqID, data}
-}
-
-// replyReceiptsRLP creates a reply with a batch of transaction receipts, corresponding to the
-// ones requested from an already RLP encoded format.
-func (p *clientPeer) replyReceiptsRLP(reqID uint64, receipts []rlp.RawValue) *reply {
- data, _ := rlp.EncodeToBytes(receipts)
- return &reply{p.rw, ReceiptsMsg, reqID, data}
-}
-
-// replyProofsV2 creates a reply with a batch of merkle proofs, corresponding to the ones requested.
-func (p *clientPeer) replyProofsV2(reqID uint64, proofs trienode.ProofList) *reply {
- data, _ := rlp.EncodeToBytes(proofs)
- return &reply{p.rw, ProofsV2Msg, reqID, data}
-}
-
-// replyHelperTrieProofs creates a reply with a batch of HelperTrie proofs, corresponding to the ones requested.
-func (p *clientPeer) replyHelperTrieProofs(reqID uint64, resp HelperTrieResps) *reply {
- data, _ := rlp.EncodeToBytes(resp)
- return &reply{p.rw, HelperTrieProofsMsg, reqID, data}
-}
-
-// replyTxStatus creates a reply with a batch of transaction status records, corresponding to the ones requested.
-func (p *clientPeer) replyTxStatus(reqID uint64, stats []light.TxStatus) *reply {
- data, _ := rlp.EncodeToBytes(stats)
- return &reply{p.rw, TxStatusMsg, reqID, data}
-}
-
-// sendAnnounce announces the availability of a number of blocks through
-// a hash notification.
-func (p *clientPeer) sendAnnounce(request announceData) error {
- return p2p.Send(p.rw, AnnounceMsg, request)
-}
-
-// InactiveAllowance implements vfs.clientPeer
-func (p *clientPeer) InactiveAllowance() time.Duration {
- return 0 // will return more than zero for les/5 clients
-}
-
-// getCapacity returns the current capacity of the peer
-func (p *clientPeer) getCapacity() uint64 {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- return p.capacity
-}
-
-// UpdateCapacity updates the request serving capacity assigned to a given client
-// and also sends an announcement about the updated flow control parameters.
-// Note: UpdateCapacity implements vfs.clientPeer and should not block. The requested
-// parameter is true if the callback was initiated by ClientPool.SetCapacity on the given peer.
-func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- if newCap != p.fcParams.MinRecharge {
- p.fcParams = flowcontrol.ServerParams{MinRecharge: newCap, BufLimit: newCap * bufLimitRatio}
- p.fcClient.UpdateParams(p.fcParams)
- var kvList keyValueList
- kvList = kvList.add("flowControl/MRR", newCap)
- kvList = kvList.add("flowControl/BL", newCap*bufLimitRatio)
- p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) })
- }
-
- if p.capacity == 0 && newCap != 0 {
- p.sendLastAnnounce()
- }
- p.capacity = newCap
-}
-
-// announceOrStore sends the given head announcement to the client if the client is
-// active (capacity != 0) and the same announcement hasn't been sent before. If the
-// client is inactive the announcement is stored and sent later if the client is
-// activated again.
-func (p *clientPeer) announceOrStore(announce announceData) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- p.lastAnnounce = announce
- if p.capacity != 0 {
- p.sendLastAnnounce()
- }
-}
-
-// announce sends the given head announcement to the client if it hasn't been sent before
-func (p *clientPeer) sendLastAnnounce() {
- if p.lastAnnounce.Td == nil {
- return
- }
- if p.headInfo.Td == nil || p.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 {
- if !p.queueSend(func() { p.sendAnnounce(p.lastAnnounce) }) {
- p.Log().Debug("Dropped announcement because queue is full", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash)
- } else {
- p.Log().Debug("Sent announcement", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash)
- }
- p.headInfo = blockInfo{Hash: p.lastAnnounce.Hash, Number: p.lastAnnounce.Number, Td: p.lastAnnounce.Td}
- }
-}
-
-// Handshake executes the les protocol handshake, negotiating version number,
-// network IDs, difficulties, head and genesis blocks.
-func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error {
- recentTx := server.handler.blockchain.TxLookupLimit()
- if recentTx != txIndexUnlimited {
- if recentTx < blockSafetyMargin {
- recentTx = txIndexDisabled
- } else {
- recentTx -= blockSafetyMargin - txIndexRecentOffset
- }
- }
- if recentTx != txIndexUnlimited && p.version < lpv4 {
- return errors.New("Cannot serve old clients without a complete tx index")
- }
- // Note: clientPeer.headInfo should contain the last head announced to the client by us.
- // The values announced in the handshake are dummy values for compatibility reasons and should be ignored.
- p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td}
- return p.handshake(td, head, headNum, genesis, forkID, forkFilter, func(lists *keyValueList) {
- // Add some information which services server can offer.
- *lists = (*lists).add("serveHeaders", nil)
- *lists = (*lists).add("serveChainSince", uint64(0))
- *lists = (*lists).add("serveStateSince", uint64(0))
-
- // If local ethereum node is running in archive mode, advertise ourselves we have
- // all version state data. Otherwise only recent state is available.
- stateRecent := uint64(core.TriesInMemory - blockSafetyMargin)
- if server.archiveMode {
- stateRecent = 0
- }
- *lists = (*lists).add("serveRecentState", stateRecent)
- *lists = (*lists).add("txRelay", nil)
- if p.version >= lpv4 {
- *lists = (*lists).add("recentTxLookup", recentTx)
- }
- *lists = (*lists).add("flowControl/BL", server.defParams.BufLimit)
- *lists = (*lists).add("flowControl/MRR", server.defParams.MinRecharge)
-
- var costList RequestCostList
- if server.costTracker.testCostList != nil {
- costList = server.costTracker.testCostList
- } else {
- costList = server.costTracker.makeCostList(server.costTracker.globalFactor())
- }
- *lists = (*lists).add("flowControl/MRC", costList)
- p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
- p.fcParams = server.defParams
- }, func(recv keyValueMap) error {
- p.server = recv.get("flowControl/MRR", nil) == nil
- if p.server {
- p.announceType = announceTypeNone // connected to another server, send no messages
- } else {
- if recv.get("announceType", &p.announceType) != nil {
- // set default announceType on server side
- p.announceType = announceTypeSimple
- }
- }
- return nil
- })
-}
-
-func (p *clientPeer) bumpInvalid() {
- p.invalidLock.Lock()
- p.invalidCount.Add(1, mclock.Now())
- p.invalidLock.Unlock()
-}
-
-func (p *clientPeer) getInvalid() uint64 {
- p.invalidLock.RLock()
- defer p.invalidLock.RUnlock()
- return p.invalidCount.Value(mclock.Now())
-}
-
-// Disconnect implements vfs.clientPeer
-func (p *clientPeer) Disconnect() {
- p.Peer.Disconnect(p2p.DiscRequested)
-}
-
-// serverPeerSubscriber is an interface to notify services about added or
-// removed server peers
-type serverPeerSubscriber interface {
- registerPeer(*serverPeer)
- unregisterPeer(*serverPeer)
-}
-
-// serverPeerSet represents the set of active server peers currently
-// participating in the Light Ethereum sub-protocol.
-type serverPeerSet struct {
- peers map[string]*serverPeer
- // subscribers is a batch of subscribers and peerset will notify
- // these subscribers when the peerset changes(new server peer is
- // added or removed)
- subscribers []serverPeerSubscriber
- closed bool
- lock sync.RWMutex
-}
-
-// newServerPeerSet creates a new peer set to track the active server peers.
-func newServerPeerSet() *serverPeerSet {
- return &serverPeerSet{peers: make(map[string]*serverPeer)}
-}
-
-// subscribe adds a service to be notified about added or removed
-// peers and also register all active peers into the given service.
-func (ps *serverPeerSet) subscribe(sub serverPeerSubscriber) {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- ps.subscribers = append(ps.subscribers, sub)
- for _, p := range ps.peers {
- sub.registerPeer(p)
- }
-}
-
-// register adds a new server peer into the set, or returns an error if the
-// peer is already known.
-func (ps *serverPeerSet) register(peer *serverPeer) error {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- if ps.closed {
- return errClosed
- }
- if _, exist := ps.peers[peer.id]; exist {
- return errAlreadyRegistered
- }
- ps.peers[peer.id] = peer
- for _, sub := range ps.subscribers {
- sub.registerPeer(peer)
- }
- return nil
-}
-
-// unregister removes a remote peer from the active set, disabling any further
-// actions to/from that particular entity. It also initiates disconnection at
-// the networking layer.
-func (ps *serverPeerSet) unregister(id string) error {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- p, ok := ps.peers[id]
- if !ok {
- return errNotRegistered
- }
- delete(ps.peers, id)
- for _, sub := range ps.subscribers {
- sub.unregisterPeer(p)
- }
- p.Peer.Disconnect(p2p.DiscRequested)
- return nil
-}
-
-// ids returns a list of all registered peer IDs
-func (ps *serverPeerSet) ids() []string {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- var ids []string
- for id := range ps.peers {
- ids = append(ids, id)
- }
- return ids
-}
-
-// peer retrieves the registered peer with the given id.
-func (ps *serverPeerSet) peer(id string) *serverPeer {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- return ps.peers[id]
-}
-
-// len returns if the current number of peers in the set.
-func (ps *serverPeerSet) len() int {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- return len(ps.peers)
-}
-
-// allServerPeers returns all server peers in a list.
-func (ps *serverPeerSet) allPeers() []*serverPeer {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- list := make([]*serverPeer, 0, len(ps.peers))
- for _, p := range ps.peers {
- list = append(list, p)
- }
- return list
-}
-
-// close disconnects all peers. No new peers can be registered
-// after close has returned.
-func (ps *serverPeerSet) close() {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- for _, p := range ps.peers {
- p.Disconnect(p2p.DiscQuitting)
- }
- ps.closed = true
-}
-
-// clientPeerSet represents the set of active client peers currently
-// participating in the Light Ethereum sub-protocol.
-type clientPeerSet struct {
- peers map[enode.ID]*clientPeer
- lock sync.RWMutex
- closed bool
-
- privateKey *ecdsa.PrivateKey
- lastAnnounce, signedAnnounce announceData
-}
-
-// newClientPeerSet creates a new peer set to track the client peers.
-func newClientPeerSet() *clientPeerSet {
- return &clientPeerSet{peers: make(map[enode.ID]*clientPeer)}
-}
-
-// register adds a new peer into the peer set, or returns an error if the
-// peer is already known.
-func (ps *clientPeerSet) register(peer *clientPeer) error {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- if ps.closed {
- return errClosed
- }
- if _, exist := ps.peers[peer.ID()]; exist {
- return errAlreadyRegistered
- }
- ps.peers[peer.ID()] = peer
- ps.announceOrStore(peer)
- return nil
-}
-
-// unregister removes a remote peer from the peer set, disabling any further
-// actions to/from that particular entity. It also initiates disconnection
-// at the networking layer.
-func (ps *clientPeerSet) unregister(id enode.ID) error {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- p, ok := ps.peers[id]
- if !ok {
- return errNotRegistered
- }
- delete(ps.peers, id)
- p.Peer.Disconnect(p2p.DiscRequested)
- return nil
-}
-
-// ids returns a list of all registered peer IDs
-func (ps *clientPeerSet) ids() []enode.ID {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- var ids []enode.ID
- for id := range ps.peers {
- ids = append(ids, id)
- }
- return ids
-}
-
-// peer retrieves the registered peer with the given id.
-func (ps *clientPeerSet) peer(id enode.ID) *clientPeer {
- ps.lock.RLock()
- defer ps.lock.RUnlock()
-
- return ps.peers[id]
-}
-
-// setSignerKey sets the signer key for signed announcements. Should be called before
-// starting the protocol handler.
-func (ps *clientPeerSet) setSignerKey(privateKey *ecdsa.PrivateKey) {
- ps.privateKey = privateKey
-}
-
-// broadcast sends the given announcements to all active peers
-func (ps *clientPeerSet) broadcast(announce announceData) {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- ps.lastAnnounce = announce
- for _, peer := range ps.peers {
- ps.announceOrStore(peer)
- }
-}
-
-// announceOrStore sends the requested type of announcement to the given peer or stores
-// it for later if the peer is inactive (capacity == 0).
-func (ps *clientPeerSet) announceOrStore(p *clientPeer) {
- if ps.lastAnnounce.Td == nil {
- return
- }
- switch p.announceType {
- case announceTypeSimple:
- p.announceOrStore(ps.lastAnnounce)
- case announceTypeSigned:
- if ps.signedAnnounce.Hash != ps.lastAnnounce.Hash {
- ps.signedAnnounce = ps.lastAnnounce
- ps.signedAnnounce.sign(ps.privateKey)
- }
- p.announceOrStore(ps.signedAnnounce)
- }
-}
-
-// close disconnects all peers. No new peers can be registered
-// after close has returned.
-func (ps *clientPeerSet) close() {
- ps.lock.Lock()
- defer ps.lock.Unlock()
-
- for _, p := range ps.peers {
- p.Peer.Disconnect(p2p.DiscQuitting)
- }
- ps.closed = true
-}
-
-// serverSet is a special set which contains all connected les servers.
-// Les servers will also be discovered by discovery protocol because they
-// also run the LES protocol. We can't drop them although they are useless
-// for us(server) but for other protocols(e.g. ETH) upon the devp2p they
-// may be useful.
-type serverSet struct {
- lock sync.Mutex
- set map[string]*clientPeer
- closed bool
-}
-
-func newServerSet() *serverSet {
- return &serverSet{set: make(map[string]*clientPeer)}
-}
-
-func (s *serverSet) register(peer *clientPeer) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- if s.closed {
- return errClosed
- }
- if _, exist := s.set[peer.id]; exist {
- return errAlreadyRegistered
- }
- s.set[peer.id] = peer
- return nil
-}
-
-func (s *serverSet) unregister(peer *clientPeer) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- if s.closed {
- return errClosed
- }
- if _, exist := s.set[peer.id]; !exist {
- return errNotRegistered
- }
- delete(s.set, peer.id)
- peer.Peer.Disconnect(p2p.DiscQuitting)
- return nil
-}
-
-func (s *serverSet) close() {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- for _, p := range s.set {
- p.Peer.Disconnect(p2p.DiscQuitting)
- }
- s.closed = true
-}
diff --git a/les/peer_test.go b/les/peer_test.go
deleted file mode 100644
index 0881dd292..000000000
--- a/les/peer_test.go
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "crypto/rand"
- "errors"
- "math/big"
- "reflect"
- "sort"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
-)
-
-type testServerPeerSub struct {
- regCh chan *serverPeer
- unregCh chan *serverPeer
-}
-
-func newTestServerPeerSub() *testServerPeerSub {
- return &testServerPeerSub{
- regCh: make(chan *serverPeer, 1),
- unregCh: make(chan *serverPeer, 1),
- }
-}
-
-func (t *testServerPeerSub) registerPeer(p *serverPeer) { t.regCh <- p }
-func (t *testServerPeerSub) unregisterPeer(p *serverPeer) { t.unregCh <- p }
-
-func TestPeerSubscription(t *testing.T) {
- peers := newServerPeerSet()
- defer peers.close()
-
- checkIds := func(expect []string) {
- given := peers.ids()
- if len(given) == 0 && len(expect) == 0 {
- return
- }
- sort.Strings(given)
- sort.Strings(expect)
- if !reflect.DeepEqual(given, expect) {
- t.Fatalf("all peer ids mismatch, want %v, given %v", expect, given)
- }
- }
- checkPeers := func(peerCh chan *serverPeer) {
- select {
- case <-peerCh:
- case <-time.NewTimer(100 * time.Millisecond).C:
- t.Fatalf("timeout, no event received")
- }
- select {
- case <-peerCh:
- t.Fatalf("unexpected event received")
- case <-time.NewTimer(10 * time.Millisecond).C:
- }
- }
- checkIds([]string{})
-
- sub := newTestServerPeerSub()
- peers.subscribe(sub)
-
- // Generate a random id and create the peer
- var id enode.ID
- rand.Read(id[:])
- peer := newServerPeer(2, NetworkId, false, p2p.NewPeer(id, "name", nil), nil)
- peers.register(peer)
-
- checkIds([]string{peer.id})
- checkPeers(sub.regCh)
-
- peers.unregister(peer.id)
- checkIds([]string{})
- checkPeers(sub.unregCh)
-}
-
-type fakeChain struct{}
-
-func (f *fakeChain) Config() *params.ChainConfig { return params.MainnetChainConfig }
-func (f *fakeChain) Genesis() *types.Block {
- return core.DefaultGenesisBlock().ToBlock()
-}
-func (f *fakeChain) CurrentHeader() *types.Header { return &types.Header{Number: big.NewInt(10000000)} }
-
-func TestHandshake(t *testing.T) {
- // Create a message pipe to communicate through
- app, net := p2p.MsgPipe()
-
- // Generate a random id and create the peer
- var id enode.ID
- rand.Read(id[:])
-
- peer1 := newClientPeer(2, NetworkId, p2p.NewPeer(id, "name", nil), net)
- peer2 := newServerPeer(2, NetworkId, true, p2p.NewPeer(id, "name", nil), app)
-
- var (
- errCh1 = make(chan error, 1)
- errCh2 = make(chan error, 1)
-
- td = big.NewInt(100)
- head = common.HexToHash("deadbeef")
- headNum = uint64(10)
- genesis = common.HexToHash("cafebabe")
-
- chain1, chain2 = &fakeChain{}, &fakeChain{}
- forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis(), chain1.CurrentHeader().Number.Uint64(), chain1.CurrentHeader().Time)
- forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis(), chain2.CurrentHeader().Number.Uint64(), chain2.CurrentHeader().Time)
- filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2)
- )
-
- go func() {
- errCh1 <- peer1.handshake(td, head, headNum, genesis, forkID1, filter1, func(list *keyValueList) {
- var announceType uint64 = announceTypeSigned
- *list = (*list).add("announceType", announceType)
- }, nil)
- }()
- go func() {
- errCh2 <- peer2.handshake(td, head, headNum, genesis, forkID2, filter2, nil, func(recv keyValueMap) error {
- var reqType uint64
- err := recv.get("announceType", &reqType)
- if err != nil {
- return err
- }
- if reqType != announceTypeSigned {
- return errors.New("Expected announceTypeSigned")
- }
- return nil
- })
- }()
-
- for i := 0; i < 2; i++ {
- select {
- case err := <-errCh1:
- if err != nil {
- t.Fatalf("handshake failed, %v", err)
- }
- case err := <-errCh2:
- if err != nil {
- t.Fatalf("handshake failed, %v", err)
- }
- case <-time.After(time.Second):
- t.Fatalf("timeout")
- }
- }
-}
diff --git a/les/protocol.go b/les/protocol.go
deleted file mode 100644
index cfebdbfb9..000000000
--- a/les/protocol.go
+++ /dev/null
@@ -1,327 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "crypto/ecdsa"
- "errors"
- "fmt"
- "io"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- vfc "github.com/ethereum/go-ethereum/les/vflux/client"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// Constants to match up protocol versions and messages
-const (
- lpv2 = 2
- lpv3 = 3
- lpv4 = 4
-)
-
-// Supported versions of the les protocol (first is primary)
-var (
- ClientProtocolVersions = []uint{lpv2, lpv3, lpv4}
- ServerProtocolVersions = []uint{lpv2, lpv3, lpv4}
-)
-
-// ProtocolLengths is the number of implemented message corresponding to different protocol versions.
-var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24}
-
-const (
- NetworkId = 1
- ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
- blockSafetyMargin = 4 // safety margin applied to block ranges specified relative to head block
-
- txIndexUnlimited = 0 // this value in the "recentTxLookup" handshake field means the entire tx index history is served
- txIndexDisabled = 1 // this value means tx index is not served at all
- txIndexRecentOffset = 1 // txIndexRecentOffset + N in the handshake field means then tx index of the last N blocks is supported
-)
-
-// les protocol message codes
-const (
- // Protocol messages inherited from LPV1
- StatusMsg = 0x00
- AnnounceMsg = 0x01
- GetBlockHeadersMsg = 0x02
- BlockHeadersMsg = 0x03
- GetBlockBodiesMsg = 0x04
- BlockBodiesMsg = 0x05
- GetReceiptsMsg = 0x06
- ReceiptsMsg = 0x07
- GetCodeMsg = 0x0a
- CodeMsg = 0x0b
- // Protocol messages introduced in LPV2
- GetProofsV2Msg = 0x0f
- ProofsV2Msg = 0x10
- GetHelperTrieProofsMsg = 0x11
- HelperTrieProofsMsg = 0x12
- SendTxV2Msg = 0x13
- GetTxStatusMsg = 0x14
- TxStatusMsg = 0x15
- // Protocol messages introduced in LPV3
- StopMsg = 0x16
- ResumeMsg = 0x17
-)
-
-// GetBlockHeadersData represents a block header query (the request ID is not included)
-type GetBlockHeadersData struct {
- Origin hashOrNumber // Block from which to retrieve headers
- Amount uint64 // Maximum number of headers to retrieve
- Skip uint64 // Blocks to skip between consecutive headers
- Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis)
-}
-
-// GetBlockHeadersPacket represents a block header request
-type GetBlockHeadersPacket struct {
- ReqID uint64
- Query GetBlockHeadersData
-}
-
-// GetBlockBodiesPacket represents a block body request
-type GetBlockBodiesPacket struct {
- ReqID uint64
- Hashes []common.Hash
-}
-
-// GetCodePacket represents a contract code request
-type GetCodePacket struct {
- ReqID uint64
- Reqs []CodeReq
-}
-
-// GetReceiptsPacket represents a block receipts request
-type GetReceiptsPacket struct {
- ReqID uint64
- Hashes []common.Hash
-}
-
-// GetProofsPacket represents a proof request
-type GetProofsPacket struct {
- ReqID uint64
- Reqs []ProofReq
-}
-
-// GetHelperTrieProofsPacket represents a helper trie proof request
-type GetHelperTrieProofsPacket struct {
- ReqID uint64
- Reqs []HelperTrieReq
-}
-
-// SendTxPacket represents a transaction propagation request
-type SendTxPacket struct {
- ReqID uint64
- Txs []*types.Transaction
-}
-
-// GetTxStatusPacket represents a transaction status query
-type GetTxStatusPacket struct {
- ReqID uint64
- Hashes []common.Hash
-}
-
-type requestInfo struct {
- name string
- maxCount uint64
- refBasketFirst, refBasketRest float64
-}
-
-// reqMapping maps an LES request to one or two vflux service vector entries.
-// If rest != -1 and the request type is used with amounts larger than one then the
-// first one of the multi-request is mapped to first while the rest is mapped to rest.
-type reqMapping struct {
- first, rest int
-}
-
-var (
- // requests describes the available LES request types and their initializing amounts
- // in the vfc.ValueTracker reference basket. Initial values are estimates
- // based on the same values as the server's default cost estimates (reqAvgTimeCost).
- requests = map[uint64]requestInfo{
- GetBlockHeadersMsg: {"GetBlockHeaders", MaxHeaderFetch, 10, 1000},
- GetBlockBodiesMsg: {"GetBlockBodies", MaxBodyFetch, 1, 0},
- GetReceiptsMsg: {"GetReceipts", MaxReceiptFetch, 1, 0},
- GetCodeMsg: {"GetCode", MaxCodeFetch, 1, 0},
- GetProofsV2Msg: {"GetProofsV2", MaxProofsFetch, 10, 0},
- GetHelperTrieProofsMsg: {"GetHelperTrieProofs", MaxHelperTrieProofsFetch, 10, 100},
- SendTxV2Msg: {"SendTxV2", MaxTxSend, 1, 0},
- GetTxStatusMsg: {"GetTxStatus", MaxTxStatus, 10, 0},
- }
- requestList []vfc.RequestInfo
- requestMapping map[uint32]reqMapping
-)
-
-// init creates a request list and mapping between protocol message codes and vflux
-// service vector indices.
-func init() {
- requestMapping = make(map[uint32]reqMapping)
- for code, req := range requests {
- cost := reqAvgTimeCost[code]
- rm := reqMapping{len(requestList), -1}
- requestList = append(requestList, vfc.RequestInfo{
- Name: req.name + ".first",
- InitAmount: req.refBasketFirst,
- InitValue: float64(cost.baseCost + cost.reqCost),
- })
- if req.refBasketRest != 0 {
- rm.rest = len(requestList)
- requestList = append(requestList, vfc.RequestInfo{
- Name: req.name + ".rest",
- InitAmount: req.refBasketRest,
- InitValue: float64(cost.reqCost),
- })
- }
- requestMapping[uint32(code)] = rm
- }
-}
-
-type errCode int
-
-const (
- ErrMsgTooLarge = iota
- ErrDecode
- ErrInvalidMsgCode
- ErrProtocolVersionMismatch
- ErrNetworkIdMismatch
- ErrGenesisBlockMismatch
- ErrNoStatusMsg
- ErrExtraStatusMsg
- ErrSuspendedPeer
- ErrUselessPeer
- ErrRequestRejected
- ErrUnexpectedResponse
- ErrInvalidResponse
- ErrTooManyTimeouts
- ErrMissingKey
- ErrForkIDRejected
-)
-
-func (e errCode) String() string {
- return errorToString[int(e)]
-}
-
-// XXX change once legacy code is out
-var errorToString = map[int]string{
- ErrMsgTooLarge: "Message too long",
- ErrDecode: "Invalid message",
- ErrInvalidMsgCode: "Invalid message code",
- ErrProtocolVersionMismatch: "Protocol version mismatch",
- ErrNetworkIdMismatch: "NetworkId mismatch",
- ErrGenesisBlockMismatch: "Genesis block mismatch",
- ErrNoStatusMsg: "No status message",
- ErrExtraStatusMsg: "Extra status message",
- ErrSuspendedPeer: "Suspended peer",
- ErrRequestRejected: "Request rejected",
- ErrUnexpectedResponse: "Unexpected response",
- ErrInvalidResponse: "Invalid response",
- ErrTooManyTimeouts: "Too many request timeouts",
- ErrMissingKey: "Key missing from list",
- ErrForkIDRejected: "ForkID rejected",
-}
-
-// announceData is the network packet for the block announcements.
-type announceData struct {
- Hash common.Hash // Hash of one particular block being announced
- Number uint64 // Number of one particular block being announced
- Td *big.Int // Total difficulty of one particular block being announced
- ReorgDepth uint64
- Update keyValueList
-}
-
-// sanityCheck verifies that the values are reasonable, as a DoS protection
-func (a *announceData) sanityCheck() error {
- if tdlen := a.Td.BitLen(); tdlen > 100 {
- return fmt.Errorf("too large block TD: bitlen %d", tdlen)
- }
- return nil
-}
-
-// sign adds a signature to the block announcement by the given privKey
-func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
- rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td})
- sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey)
- a.Update = a.Update.add("sign", sig)
-}
-
-// checkSignature verifies if the block announcement has a valid signature by the given pubKey
-func (a *announceData) checkSignature(id enode.ID, update keyValueMap) error {
- var sig []byte
- if err := update.get("sign", &sig); err != nil {
- return err
- }
- rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td})
- recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig)
- if err != nil {
- return err
- }
- if id == enode.PubkeyToIDV4(recPubkey) {
- return nil
- }
- return errors.New("wrong signature")
-}
-
-type blockInfo struct {
- Hash common.Hash // Hash of one particular block being announced
- Number uint64 // Number of one particular block being announced
- Td *big.Int // Total difficulty of one particular block being announced
-}
-
-// hashOrNumber is a combined field for specifying an origin block.
-type hashOrNumber struct {
- Hash common.Hash // Block hash from which to retrieve headers (excludes Number)
- Number uint64 // Block hash from which to retrieve headers (excludes Hash)
-}
-
-// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the
-// two contained union fields.
-func (hn *hashOrNumber) EncodeRLP(w io.Writer) error {
- if hn.Hash == (common.Hash{}) {
- return rlp.Encode(w, hn.Number)
- }
- if hn.Number != 0 {
- return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
- }
- return rlp.Encode(w, hn.Hash)
-}
-
-// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents
-// into either a block hash or a block number.
-func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
- _, size, err := s.Kind()
- switch {
- case err != nil:
- return err
- case size == 32:
- hn.Number = 0
- return s.Decode(&hn.Hash)
- case size <= 8:
- hn.Hash = common.Hash{}
- return s.Decode(&hn.Number)
- default:
- return fmt.Errorf("invalid input size %d for origin", size)
- }
-}
-
-// CodeData is the network response packet for a node data retrieval.
-type CodeData []struct {
- Value []byte
-}
diff --git a/les/request_test.go b/les/request_test.go
deleted file mode 100644
index 5e354b7ef..000000000
--- a/les/request_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-// Note: these tests are disabled now because they cannot work with the old sync
-// mechanism removed but will be useful again once the PoS ultralight mode is implemented
-
-/*
-import (
- "context"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "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/ethdb"
- "github.com/ethereum/go-ethereum/light"
-)
-
-var testBankSecureTrieKey = secAddr(bankAddr)
-
-func secAddr(addr common.Address) []byte {
- return crypto.Keccak256(addr[:])
-}
-
-type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest
-
-func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) }
-func TestBlockAccessLes3(t *testing.T) { testAccess(t, 3, tfBlockAccess) }
-func TestBlockAccessLes4(t *testing.T) { testAccess(t, 4, tfBlockAccess) }
-
-func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
- return &light.BlockRequest{Hash: bhash, Number: number}
-}
-
-func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) }
-func TestReceiptsAccessLes3(t *testing.T) { testAccess(t, 3, tfReceiptsAccess) }
-func TestReceiptsAccessLes4(t *testing.T) { testAccess(t, 4, tfReceiptsAccess) }
-
-func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
- return &light.ReceiptsRequest{Hash: bhash, Number: number}
-}
-
-func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) }
-func TestTrieEntryAccessLes3(t *testing.T) { testAccess(t, 3, tfTrieEntryAccess) }
-func TestTrieEntryAccessLes4(t *testing.T) { testAccess(t, 4, tfTrieEntryAccess) }
-
-func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
- if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
- return &light.TrieRequest{Id: light.StateTrieID(rawdb.ReadHeader(db, bhash, *number)), Key: testBankSecureTrieKey}
- }
- return nil
-}
-
-func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) }
-func TestCodeAccessLes3(t *testing.T) { testAccess(t, 3, tfCodeAccess) }
-func TestCodeAccessLes4(t *testing.T) { testAccess(t, 4, tfCodeAccess) }
-
-func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest {
- number := rawdb.ReadHeaderNumber(db, bhash)
- if number != nil {
- return nil
- }
- header := rawdb.ReadHeader(db, bhash, *number)
- if header.Number.Uint64() < testContractDeployed {
- return nil
- }
- sti := light.StateTrieID(header)
- ci := light.StorageTrieID(sti, testContractAddr, types.EmptyRootHash)
- return &light.CodeRequest{Id: ci, Hash: crypto.Keccak256Hash(testContractCodeDeployed)}
-}
-
-func testAccess(t *testing.T, protocol int, fn accessTestFn) {
- // Assemble the test environment
- netconfig := testnetConfig{
- blocks: 4,
- protocol: protocol,
- indexFn: nil,
- connect: true,
- nopruning: true,
- }
- server, client, tearDown := newClientServerEnv(t, netconfig)
- defer tearDown()
-
- // Ensure the client has synced all necessary data.
- clientHead := client.handler.backend.blockchain.CurrentHeader()
- if clientHead.Number.Uint64() != 4 {
- t.Fatalf("Failed to sync the chain with server, head: %v", clientHead.Number.Uint64())
- }
-
- test := func(expFail uint64) {
- for i := uint64(0); i <= server.handler.blockchain.CurrentHeader().Number.Uint64(); i++ {
- bhash := rawdb.ReadCanonicalHash(server.db, i)
- if req := fn(client.db, bhash, i); req != nil {
- ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
-
- err := client.handler.backend.odr.Retrieve(ctx, req)
- cancel()
-
- got := err == nil
- exp := i < expFail
- if exp && !got {
- t.Errorf("object retrieval failed")
- }
- if !exp && got {
- t.Errorf("unexpected object retrieval success")
- }
- }
- }
- }
- test(5)
-}
-*/
diff --git a/les/retrieve.go b/les/retrieve.go
deleted file mode 100644
index 728f960a5..000000000
--- a/les/retrieve.go
+++ /dev/null
@@ -1,421 +0,0 @@
-// Copyright 2017 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 .
-
-package les
-
-import (
- "context"
- "errors"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/light"
-)
-
-var (
- retryQueue = time.Millisecond * 100
- hardRequestTimeout = time.Second * 10
-)
-
-// retrieveManager is a layer on top of requestDistributor which takes care of
-// matching replies by request ID and handles timeouts and resends if necessary.
-type retrieveManager struct {
- dist *requestDistributor
- peers *serverPeerSet
- softRequestTimeout func() time.Duration
-
- lock sync.RWMutex
- sentReqs map[uint64]*sentReq
-}
-
-// validatorFunc is a function that processes a reply message
-type validatorFunc func(distPeer, *Msg) error
-
-// sentReq represents a request sent and tracked by retrieveManager
-type sentReq struct {
- rm *retrieveManager
- req *distReq
- id uint64
- validate validatorFunc
-
- eventsCh chan reqPeerEvent
- stopCh chan struct{}
- stopped bool
- err error
-
- lock sync.RWMutex // protect access to sentTo map
- sentTo map[distPeer]sentReqToPeer
-
- lastReqQueued bool // last request has been queued but not sent
- lastReqSentTo distPeer // if not nil then last request has been sent to given peer but not timed out
- reqSrtoCount int // number of requests that reached soft (but not hard) timeout
-}
-
-// sentReqToPeer notifies the request-from-peer goroutine (tryRequest) about a response
-// delivered by the given peer. Only one delivery is allowed per request per peer,
-// after which delivered is set to true, the validity of the response is sent on the
-// valid channel and no more responses are accepted.
-type sentReqToPeer struct {
- delivered, frozen bool
- event chan int
-}
-
-// reqPeerEvent is sent by the request-from-peer goroutine (tryRequest) to the
-// request state machine (retrieveLoop) through the eventsCh channel.
-type reqPeerEvent struct {
- event int
- peer distPeer
-}
-
-const (
- rpSent = iota // if peer == nil, not sent (no suitable peers)
- rpSoftTimeout
- rpHardTimeout
- rpDeliveredValid
- rpDeliveredInvalid
- rpNotDelivered
-)
-
-// newRetrieveManager creates the retrieve manager
-func newRetrieveManager(peers *serverPeerSet, dist *requestDistributor, srto func() time.Duration) *retrieveManager {
- return &retrieveManager{
- peers: peers,
- dist: dist,
- sentReqs: make(map[uint64]*sentReq),
- softRequestTimeout: srto,
- }
-}
-
-// retrieve sends a request (to multiple peers if necessary) and waits for an answer
-// that is delivered through the deliver function and successfully validated by the
-// validator callback. It returns when a valid answer is delivered or the context is
-// cancelled.
-func (rm *retrieveManager) retrieve(ctx context.Context, reqID uint64, req *distReq, val validatorFunc, shutdown chan struct{}) error {
- sentReq := rm.sendReq(reqID, req, val)
- select {
- case <-sentReq.stopCh:
- case <-ctx.Done():
- sentReq.stop(ctx.Err())
- case <-shutdown:
- sentReq.stop(errors.New("client is shutting down"))
- }
- return sentReq.getError()
-}
-
-// sendReq starts a process that keeps trying to retrieve a valid answer for a
-// request from any suitable peers until stopped or succeeded.
-func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc) *sentReq {
- r := &sentReq{
- rm: rm,
- req: req,
- id: reqID,
- sentTo: make(map[distPeer]sentReqToPeer),
- stopCh: make(chan struct{}),
- eventsCh: make(chan reqPeerEvent, 10),
- validate: val,
- }
-
- canSend := req.canSend
- req.canSend = func(p distPeer) bool {
- // add an extra check to canSend: the request has not been sent to the same peer before
- r.lock.RLock()
- _, sent := r.sentTo[p]
- r.lock.RUnlock()
- return !sent && canSend(p)
- }
-
- request := req.request
- req.request = func(p distPeer) func() {
- // before actually sending the request, put an entry into the sentTo map
- r.lock.Lock()
- r.sentTo[p] = sentReqToPeer{delivered: false, frozen: false, event: make(chan int, 1)}
- r.lock.Unlock()
- return request(p)
- }
- rm.lock.Lock()
- rm.sentReqs[reqID] = r
- rm.lock.Unlock()
-
- go r.retrieveLoop()
- return r
-}
-
-// deliver is called by the LES protocol manager to deliver reply messages to waiting requests
-func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error {
- rm.lock.RLock()
- req, ok := rm.sentReqs[msg.ReqID]
- rm.lock.RUnlock()
-
- if ok {
- return req.deliver(peer, msg)
- }
- return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID)
-}
-
-// frozen is called by the LES protocol manager when a server has suspended its service and we
-// should not expect an answer for the requests already sent there
-func (rm *retrieveManager) frozen(peer distPeer) {
- rm.lock.RLock()
- defer rm.lock.RUnlock()
-
- for _, req := range rm.sentReqs {
- req.frozen(peer)
- }
-}
-
-// reqStateFn represents a state of the retrieve loop state machine
-type reqStateFn func() reqStateFn
-
-// retrieveLoop is the retrieval state machine event loop
-func (r *sentReq) retrieveLoop() {
- go r.tryRequest()
- r.lastReqQueued = true
- state := r.stateRequesting
-
- for state != nil {
- state = state()
- }
-
- r.rm.lock.Lock()
- delete(r.rm.sentReqs, r.id)
- r.rm.lock.Unlock()
-}
-
-// stateRequesting: a request has been queued or sent recently; when it reaches soft timeout,
-// a new request is sent to a new peer
-func (r *sentReq) stateRequesting() reqStateFn {
- select {
- case ev := <-r.eventsCh:
- r.update(ev)
- switch ev.event {
- case rpSent:
- if ev.peer == nil {
- // request send failed, no more suitable peers
- if r.waiting() {
- // we are already waiting for sent requests which may succeed so keep waiting
- return r.stateNoMorePeers
- }
- // nothing to wait for, no more peers to ask, return with error
- r.stop(light.ErrNoPeers)
- // no need to go to stopped state because waiting() already returned false
- return nil
- }
- case rpSoftTimeout:
- // last request timed out, try asking a new peer
- go r.tryRequest()
- r.lastReqQueued = true
- return r.stateRequesting
- case rpDeliveredInvalid, rpNotDelivered:
- // if it was the last sent request (set to nil by update) then start a new one
- if !r.lastReqQueued && r.lastReqSentTo == nil {
- go r.tryRequest()
- r.lastReqQueued = true
- }
- return r.stateRequesting
- case rpDeliveredValid:
- r.stop(nil)
- return r.stateStopped
- }
- return r.stateRequesting
- case <-r.stopCh:
- return r.stateStopped
- }
-}
-
-// stateNoMorePeers: could not send more requests because no suitable peers are available.
-// Peers may become suitable for a certain request later or new peers may appear so we
-// keep trying.
-func (r *sentReq) stateNoMorePeers() reqStateFn {
- select {
- case <-time.After(retryQueue):
- go r.tryRequest()
- r.lastReqQueued = true
- return r.stateRequesting
- case ev := <-r.eventsCh:
- r.update(ev)
- if ev.event == rpDeliveredValid {
- r.stop(nil)
- return r.stateStopped
- }
- if r.waiting() {
- return r.stateNoMorePeers
- }
- r.stop(light.ErrNoPeers)
- return nil
- case <-r.stopCh:
- return r.stateStopped
- }
-}
-
-// stateStopped: request succeeded or cancelled, just waiting for some peers
-// to either answer or time out hard
-func (r *sentReq) stateStopped() reqStateFn {
- for r.waiting() {
- r.update(<-r.eventsCh)
- }
- return nil
-}
-
-// update updates the queued/sent flags and timed out peers counter according to the event
-func (r *sentReq) update(ev reqPeerEvent) {
- switch ev.event {
- case rpSent:
- r.lastReqQueued = false
- r.lastReqSentTo = ev.peer
- case rpSoftTimeout:
- r.lastReqSentTo = nil
- r.reqSrtoCount++
- case rpHardTimeout:
- r.reqSrtoCount--
- case rpDeliveredValid, rpDeliveredInvalid, rpNotDelivered:
- if ev.peer == r.lastReqSentTo {
- r.lastReqSentTo = nil
- } else {
- r.reqSrtoCount--
- }
- }
-}
-
-// waiting returns true if the retrieval mechanism is waiting for an answer from
-// any peer
-func (r *sentReq) waiting() bool {
- return r.lastReqQueued || r.lastReqSentTo != nil || r.reqSrtoCount > 0
-}
-
-// tryRequest tries to send the request to a new peer and waits for it to either
-// succeed or time out if it has been sent. It also sends the appropriate reqPeerEvent
-// messages to the request's event channel.
-func (r *sentReq) tryRequest() {
- sent := r.rm.dist.queue(r.req)
- var p distPeer
- select {
- case p = <-sent:
- case <-r.stopCh:
- if r.rm.dist.cancel(r.req) {
- p = nil
- } else {
- p = <-sent
- }
- }
-
- r.eventsCh <- reqPeerEvent{rpSent, p}
- if p == nil {
- return
- }
-
- hrto := false
-
- r.lock.RLock()
- s, ok := r.sentTo[p]
- r.lock.RUnlock()
- if !ok {
- panic(nil)
- }
-
- defer func() {
- pp, ok := p.(*serverPeer)
- if hrto && ok {
- pp.Log().Debug("Request timed out hard")
- if r.rm.peers != nil {
- r.rm.peers.unregister(pp.id)
- }
- }
- }()
-
- select {
- case event := <-s.event:
- if event == rpNotDelivered {
- r.lock.Lock()
- delete(r.sentTo, p)
- r.lock.Unlock()
- }
- r.eventsCh <- reqPeerEvent{event, p}
- return
- case <-time.After(r.rm.softRequestTimeout()):
- r.eventsCh <- reqPeerEvent{rpSoftTimeout, p}
- }
-
- select {
- case event := <-s.event:
- if event == rpNotDelivered {
- r.lock.Lock()
- delete(r.sentTo, p)
- r.lock.Unlock()
- }
- r.eventsCh <- reqPeerEvent{event, p}
- case <-time.After(hardRequestTimeout):
- hrto = true
- r.eventsCh <- reqPeerEvent{rpHardTimeout, p}
- }
-}
-
-// deliver a reply belonging to this request
-func (r *sentReq) deliver(peer distPeer, msg *Msg) error {
- r.lock.Lock()
- defer r.lock.Unlock()
-
- s, ok := r.sentTo[peer]
- if !ok || s.delivered {
- return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID)
- }
- if s.frozen {
- return nil
- }
- valid := r.validate(peer, msg) == nil
- r.sentTo[peer] = sentReqToPeer{delivered: true, frozen: false, event: s.event}
- if valid {
- s.event <- rpDeliveredValid
- } else {
- s.event <- rpDeliveredInvalid
- }
- if !valid {
- return errResp(ErrInvalidResponse, "reqID = %v", msg.ReqID)
- }
- return nil
-}
-
-// frozen sends a "not delivered" event to the peer event channel belonging to the
-// given peer if the request has been sent there, causing the state machine to not
-// expect an answer and potentially even send the request to the same peer again
-// when canSend allows it.
-func (r *sentReq) frozen(peer distPeer) {
- r.lock.Lock()
- defer r.lock.Unlock()
-
- s, ok := r.sentTo[peer]
- if ok && !s.delivered && !s.frozen {
- r.sentTo[peer] = sentReqToPeer{delivered: false, frozen: true, event: s.event}
- s.event <- rpNotDelivered
- }
-}
-
-// stop stops the retrieval process and sets an error code that will be returned
-// by getError
-func (r *sentReq) stop(err error) {
- r.lock.Lock()
- if !r.stopped {
- r.stopped = true
- r.err = err
- close(r.stopCh)
- }
- r.lock.Unlock()
-}
-
-// getError returns any retrieval error (either internally generated or set by the
-// stop function) after stopCh has been closed
-func (r *sentReq) getError() error {
- return r.err
-}
diff --git a/les/server.go b/les/server.go
deleted file mode 100644
index d84856c7f..000000000
--- a/les/server.go
+++ /dev/null
@@ -1,281 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "crypto/ecdsa"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/eth/ethconfig"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/flowcontrol"
- vfs "github.com/ethereum/go-ethereum/les/vflux/server"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-var (
- defaultPosFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}
- defaultNegFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}
-)
-
-const defaultConnectedBias = time.Minute * 3
-
-type ethBackend interface {
- ArchiveMode() bool
- BlockChain() *core.BlockChain
- BloomIndexer() *core.ChainIndexer
- ChainDb() ethdb.Database
- Synced() bool
- TxPool() *txpool.TxPool
-}
-
-type LesServer struct {
- lesCommons
-
- archiveMode bool // Flag whether the ethereum node runs in archive mode.
- handler *serverHandler
- peers *clientPeerSet
- serverset *serverSet
- vfluxServer *vfs.Server
- privateKey *ecdsa.PrivateKey
-
- // Flow control and capacity management
- fcManager *flowcontrol.ClientManager
- costTracker *costTracker
- defParams flowcontrol.ServerParams
- servingQueue *servingQueue
- clientPool *vfs.ClientPool
-
- minCapacity, maxCapacity uint64
- threadsIdle int // Request serving threads count when system is idle.
- threadsBusy int // Request serving threads count when system is busy(block insertion).
-
- p2pSrv *p2p.Server
-}
-
-func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) {
- lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/lesserver/", false)
- if err != nil {
- return nil, err
- }
- // Calculate the number of threads used to service the light client
- // requests based on the user-specified value.
- threads := config.LightServ * 4 / 100
- if threads < 4 {
- threads = 4
- }
- srv := &LesServer{
- lesCommons: lesCommons{
- genesis: e.BlockChain().Genesis().Hash(),
- config: config,
- chainConfig: e.BlockChain().Config(),
- iConfig: light.DefaultServerIndexerConfig,
- chainDb: e.ChainDb(),
- lesDb: lesDb,
- chainReader: e.BlockChain(),
- chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations, true),
- bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true),
- closeCh: make(chan struct{}),
- },
- archiveMode: e.ArchiveMode(),
- peers: newClientPeerSet(),
- serverset: newServerSet(),
- vfluxServer: vfs.NewServer(time.Millisecond * 10),
- fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
- servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100),
- threadsBusy: config.LightServ/100 + 1,
- threadsIdle: threads,
- p2pSrv: node.Server(),
- }
- issync := e.Synced
- if config.LightNoSyncServe {
- issync = func() bool { return true }
- }
- srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), issync)
- srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config)
-
- // Initialize the bloom trie indexer.
- e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer)
-
- // Initialize server capacity management fields.
- srv.defParams = flowcontrol.ServerParams{
- BufLimit: srv.minCapacity * bufLimitRatio,
- MinRecharge: srv.minCapacity,
- }
- // LES flow control tries to more or less guarantee the possibility for the
- // clients to send a certain amount of requests at any time and get a quick
- // response. Most of the clients want this guarantee but don't actually need
- // to send requests most of the time. Our goal is to serve as many clients as
- // possible while the actually used server capacity does not exceed the limits
- totalRecharge := srv.costTracker.totalRecharge()
- srv.maxCapacity = srv.minCapacity * uint64(srv.config.LightPeers)
- if totalRecharge > srv.maxCapacity {
- srv.maxCapacity = totalRecharge
- }
- srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2)
- srv.clientPool = vfs.NewClientPool(lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, issync)
- srv.clientPool.Start()
- srv.clientPool.SetDefaultFactors(defaultPosFactors, defaultNegFactors)
- srv.vfluxServer.Register(srv.clientPool, "les", "Ethereum light client service")
- srv.chtIndexer.Start(e.BlockChain())
-
- node.RegisterProtocols(srv.Protocols())
- node.RegisterAPIs(srv.APIs())
- node.RegisterLifecycle(srv)
- return srv, nil
-}
-
-func (s *LesServer) APIs() []rpc.API {
- return []rpc.API{
- {
- Namespace: "les",
- Service: NewLightServerAPI(s),
- },
- {
- Namespace: "debug",
- Service: NewDebugAPI(s),
- },
- }
-}
-
-func (s *LesServer) Protocols() []p2p.Protocol {
- ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
- if p := s.peers.peer(id); p != nil {
- return p.Info()
- }
- return nil
- }, nil)
- // Add "les" ENR entries.
- for i := range ps {
- ps[i].Attributes = []enr.Entry{&lesEntry{
- VfxVersion: 1,
- }}
- }
- return ps
-}
-
-// Start starts the LES server
-func (s *LesServer) Start() error {
- s.privateKey = s.p2pSrv.PrivateKey
- s.peers.setSignerKey(s.privateKey)
- s.handler.start()
- s.wg.Add(1)
- go s.capacityManagement()
- if s.p2pSrv.DiscV5 != nil {
- s.p2pSrv.DiscV5.RegisterTalkHandler("vfx", s.vfluxServer.ServeEncoded)
- }
- return nil
-}
-
-// Stop stops the LES service
-func (s *LesServer) Stop() error {
- close(s.closeCh)
-
- s.clientPool.Stop()
- if s.serverset != nil {
- s.serverset.close()
- }
- s.peers.close()
- s.fcManager.Stop()
- s.costTracker.stop()
- s.handler.stop()
- s.servingQueue.stop()
- if s.vfluxServer != nil {
- s.vfluxServer.Stop()
- }
-
- // Note, bloom trie indexer is closed by parent bloombits indexer.
- if s.chtIndexer != nil {
- s.chtIndexer.Close()
- }
- if s.lesDb != nil {
- s.lesDb.Close()
- }
- s.wg.Wait()
- log.Info("Les server stopped")
-
- return nil
-}
-
-// capacityManagement starts an event handler loop that updates the recharge curve of
-// the client manager and adjusts the client pool's size according to the total
-// capacity updates coming from the client manager
-func (s *LesServer) capacityManagement() {
- defer s.wg.Done()
-
- processCh := make(chan bool, 100)
- sub := s.handler.blockchain.SubscribeBlockProcessingEvent(processCh)
- defer sub.Unsubscribe()
-
- totalRechargeCh := make(chan uint64, 100)
- totalRecharge := s.costTracker.subscribeTotalRecharge(totalRechargeCh)
-
- totalCapacityCh := make(chan uint64, 100)
- totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
- s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity)
-
- var (
- busy bool
- freePeers uint64
- blockProcess mclock.AbsTime
- )
- updateRecharge := func() {
- if busy {
- s.servingQueue.setThreads(s.threadsBusy)
- s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}})
- } else {
- s.servingQueue.setThreads(s.threadsIdle)
- s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 10, totalRecharge}, {totalRecharge, totalRecharge}})
- }
- }
- updateRecharge()
-
- for {
- select {
- case busy = <-processCh:
- if busy {
- blockProcess = mclock.Now()
- } else {
- blockProcessingTimer.Update(time.Duration(mclock.Now() - blockProcess))
- }
- updateRecharge()
- case totalRecharge = <-totalRechargeCh:
- totalRechargeGauge.Update(int64(totalRecharge))
- updateRecharge()
- case totalCapacity = <-totalCapacityCh:
- totalCapacityGauge.Update(int64(totalCapacity))
- newFreePeers := totalCapacity / s.minCapacity
- if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) {
- log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers)
- }
- freePeers = newFreePeers
- s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity)
- case <-s.closeCh:
- return
- }
- }
-}
diff --git a/les/server_handler.go b/les/server_handler.go
deleted file mode 100644
index 5b3505064..000000000
--- a/les/server_handler.go
+++ /dev/null
@@ -1,436 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "errors"
- "fmt"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/flowcontrol"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-const (
- softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
- estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
-
- MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request
- MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request
- MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request
- MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request
- MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
- MaxHelperTrieProofsFetch = 64 // Amount of helper tries to be fetched per retrieval request
- MaxTxSend = 64 // Amount of transactions to be send per request
- MaxTxStatus = 256 // Amount of transactions to queried per request
-)
-
-var (
- errTooManyInvalidRequest = errors.New("too many invalid requests made")
-)
-
-// serverHandler is responsible for serving light client and process
-// all incoming light requests.
-type serverHandler struct {
- forkFilter forkid.Filter
- blockchain *core.BlockChain
- chainDb ethdb.Database
- txpool *txpool.TxPool
- server *LesServer
-
- closeCh chan struct{} // Channel used to exit all background routines of handler.
- wg sync.WaitGroup // WaitGroup used to track all background routines of handler.
- synced func() bool // Callback function used to determine whether local node is synced.
-
- // Testing fields
- addTxsSync bool
-}
-
-func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *txpool.TxPool, synced func() bool) *serverHandler {
- handler := &serverHandler{
- forkFilter: forkid.NewFilter(blockchain),
- server: server,
- blockchain: blockchain,
- chainDb: chainDb,
- txpool: txpool,
- closeCh: make(chan struct{}),
- synced: synced,
- }
- return handler
-}
-
-// start starts the server handler.
-func (h *serverHandler) start() {
- h.wg.Add(1)
- go h.broadcastLoop()
-}
-
-// stop stops the server handler.
-func (h *serverHandler) stop() {
- close(h.closeCh)
- h.wg.Wait()
-}
-
-// runPeer is the p2p protocol run function for the given version.
-func (h *serverHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error {
- peer := newClientPeer(int(version), h.server.config.NetworkId, p, newMeteredMsgWriter(rw, int(version)))
- defer peer.close()
- h.wg.Add(1)
- defer h.wg.Done()
- return h.handle(peer)
-}
-
-func (h *serverHandler) handle(p *clientPeer) error {
- p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
-
- // Execute the LES handshake
- var (
- head = h.blockchain.CurrentHeader()
- hash = head.Hash()
- number = head.Number.Uint64()
- td = h.blockchain.GetTd(hash, number)
- forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis(), number, head.Time)
- )
- if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil {
- p.Log().Debug("Light Ethereum handshake failed", "err", err)
- return err
- }
- // Connected to another server, no messages expected, just wait for disconnection
- if p.server {
- if err := h.server.serverset.register(p); err != nil {
- return err
- }
- _, err := p.rw.ReadMsg()
- h.server.serverset.unregister(p)
- return err
- }
- // Setup flow control mechanism for the peer
- p.fcClient = flowcontrol.NewClientNode(h.server.fcManager, p.fcParams)
- defer p.fcClient.Disconnect()
-
- // Reject light clients if server is not synced. Put this checking here, so
- // that "non-synced" les-server peers are still allowed to keep the connection.
- if !h.synced() {
- p.Log().Debug("Light server not synced, rejecting peer")
- return p2p.DiscRequested
- }
-
- // Register the peer into the peerset and clientpool
- if err := h.server.peers.register(p); err != nil {
- return err
- }
- if p.balance = h.server.clientPool.Register(p); p.balance == nil {
- h.server.peers.unregister(p.ID())
- p.Log().Debug("Client pool already closed")
- return p2p.DiscRequested
- }
- p.connectedAt = mclock.Now()
-
- var wg sync.WaitGroup // Wait group used to track all in-flight task routines.
- defer func() {
- wg.Wait() // Ensure all background task routines have exited.
- h.server.clientPool.Unregister(p)
- h.server.peers.unregister(p.ID())
- p.balance = nil
- connectionTimer.Update(time.Duration(mclock.Now() - p.connectedAt))
- }()
-
- // Mark the peer as being served.
- p.serving.Store(true)
- defer p.serving.Store(false)
-
- // Spawn a main loop to handle all incoming messages.
- for {
- select {
- case err := <-p.errCh:
- p.Log().Debug("Failed to send light ethereum response", "err", err)
- return err
- default:
- }
- if err := h.handleMsg(p, &wg); err != nil {
- p.Log().Debug("Light Ethereum message handling failed", "err", err)
- return err
- }
- }
-}
-
-// beforeHandle will do a series of prechecks before handling message.
-func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, reqCnt uint64, maxCount uint64) (*servingTask, uint64) {
- // Ensure that the request sent by client peer is valid
- inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0)
- if reqCnt == 0 || reqCnt > maxCount {
- p.fcClient.OneTimeCost(inSizeCost)
- return nil, 0
- }
- // Ensure that the client peer complies with the flow control
- // rules agreed by both sides.
- if p.isFrozen() {
- p.fcClient.OneTimeCost(inSizeCost)
- return nil, 0
- }
- maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt)
- accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost)
- if !accepted {
- p.freeze()
- p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
- p.fcClient.OneTimeCost(inSizeCost)
- return nil, 0
- }
- // Create a multi-stage task, estimate the time it takes for the task to
- // execute, and cache it in the request service queue.
- factor := h.server.costTracker.globalFactor()
- if factor < 0.001 {
- factor = 1
- p.Log().Error("Invalid global cost factor", "factor", factor)
- }
- maxTime := uint64(float64(maxCost) / factor)
- task := h.server.servingQueue.newTask(p, maxTime, priority)
- if !task.start() {
- p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost)
- return nil, 0
- }
- return task, maxCost
-}
-
-// Afterhandle will perform a series of operations after message handling,
-// such as updating flow control data, sending reply, etc.
-func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, maxCost uint64, reqCnt uint64, task *servingTask, reply *reply) {
- if reply != nil {
- task.done()
- }
- p.responseLock.Lock()
- defer p.responseLock.Unlock()
-
- // Short circuit if the client is already frozen.
- if p.isFrozen() {
- realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0)
- p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
- return
- }
- // Positive correction buffer value with real cost.
- var replySize uint32
- if reply != nil {
- replySize = reply.size()
- }
- var realCost uint64
- if h.server.costTracker.testing {
- realCost = maxCost // Assign a fake cost for testing purpose
- } else {
- realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize)
- if realCost > maxCost {
- realCost = maxCost
- }
- }
- bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
- if reply != nil {
- // Feed cost tracker request serving statistic.
- h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost)
- // Reduce priority "balance" for the specific peer.
- p.balance.RequestServed(realCost)
- p.queueSend(func() {
- if err := reply.send(bv); err != nil {
- select {
- case p.errCh <- err:
- default:
- }
- }
- })
- }
-}
-
-// handleMsg is invoked whenever an inbound message is received from a remote
-// peer. The remote connection is torn down upon returning any error.
-func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error {
- // Read the next message from the remote peer, and ensure it's fully consumed
- msg, err := p.rw.ReadMsg()
- if err != nil {
- return err
- }
- p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size)
-
- // Discard large message which exceeds the limitation.
- if msg.Size > ProtocolMaxMsgSize {
- clientErrorMeter.Mark(1)
- return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize)
- }
- defer msg.Discard()
-
- // Lookup the request handler table, ensure it's supported
- // message type by the protocol.
- req, ok := Les3[msg.Code]
- if !ok {
- p.Log().Trace("Received invalid message", "code", msg.Code)
- clientErrorMeter.Mark(1)
- return errResp(ErrInvalidMsgCode, "%v", msg.Code)
- }
- p.Log().Trace("Received " + req.Name)
-
- // Decode the p2p message, resolve the concrete handler for it.
- serve, reqID, reqCnt, err := req.Handle(msg)
- if err != nil {
- clientErrorMeter.Mark(1)
- return errResp(ErrDecode, "%v: %v", msg, err)
- }
- if metrics.EnabledExpensive {
- req.InPacketsMeter.Mark(1)
- req.InTrafficMeter.Mark(int64(msg.Size))
- }
- p.responseCount++
- responseCount := p.responseCount
-
- // First check this client message complies all rules before
- // handling it and return a processor if all checks are passed.
- task, maxCost := h.beforeHandle(p, reqID, responseCount, msg, reqCnt, req.MaxCount)
- if task == nil {
- return nil
- }
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- reply := serve(h, p, task.waitOrStop)
- h.afterHandle(p, reqID, responseCount, msg, maxCost, reqCnt, task, reply)
-
- if metrics.EnabledExpensive {
- size := uint32(0)
- if reply != nil {
- size = reply.size()
- }
- req.OutPacketsMeter.Mark(1)
- req.OutTrafficMeter.Mark(int64(size))
- req.ServingTimeMeter.Update(time.Duration(task.servingTime))
- }
- }()
- // If the client has made too much invalid request(e.g. request a non-existent data),
- // reject them to prevent SPAM attack.
- if p.getInvalid() > maxRequestErrors {
- clientErrorMeter.Mark(1)
- return errTooManyInvalidRequest
- }
- return nil
-}
-
-// BlockChain implements serverBackend
-func (h *serverHandler) BlockChain() *core.BlockChain {
- return h.blockchain
-}
-
-// TxPool implements serverBackend
-func (h *serverHandler) TxPool() *txpool.TxPool {
- return h.txpool
-}
-
-// ArchiveMode implements serverBackend
-func (h *serverHandler) ArchiveMode() bool {
- return h.server.archiveMode
-}
-
-// AddTxsSync implements serverBackend
-func (h *serverHandler) AddTxsSync() bool {
- return h.addTxsSync
-}
-
-// getAccount retrieves an account from the state based on root.
-func getAccount(triedb *trie.Database, root common.Hash, addr common.Address) (types.StateAccount, error) {
- trie, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
- if err != nil {
- return types.StateAccount{}, err
- }
- acc, err := trie.GetAccount(addr)
- if err != nil {
- return types.StateAccount{}, err
- }
- if acc == nil {
- return types.StateAccount{}, fmt.Errorf("account %#x is not present", addr)
- }
- return *acc, nil
-}
-
-// GetHelperTrie returns the post-processed trie root for the given trie ID and section index
-func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie {
- var (
- root common.Hash
- prefix string
- )
- switch typ {
- case htCanonical:
- sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.ChtSize-1)
- root, prefix = light.GetChtRoot(h.chainDb, index, sectionHead), string(rawdb.ChtTablePrefix)
- case htBloomBits:
- sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.BloomTrieSize-1)
- root, prefix = light.GetBloomTrieRoot(h.chainDb, index, sectionHead), string(rawdb.BloomTrieTablePrefix)
- }
- if root == (common.Hash{}) {
- return nil
- }
- triedb := trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix), trie.HashDefaults)
- trie, _ := trie.New(trie.TrieID(root), triedb)
- return trie
-}
-
-// broadcastLoop broadcasts new block information to all connected light
-// clients. According to the agreement between client and server, server should
-// only broadcast new announcement if the total difficulty is higher than the
-// last one. Besides server will add the signature if client requires.
-func (h *serverHandler) broadcastLoop() {
- defer h.wg.Done()
-
- headCh := make(chan core.ChainHeadEvent, 10)
- headSub := h.blockchain.SubscribeChainHeadEvent(headCh)
- defer headSub.Unsubscribe()
-
- var (
- lastHead = h.blockchain.CurrentHeader()
- lastTd = common.Big0
- )
- for {
- select {
- case ev := <-headCh:
- header := ev.Block.Header()
- hash, number := header.Hash(), header.Number.Uint64()
- td := h.blockchain.GetTd(hash, number)
- if td == nil || td.Cmp(lastTd) <= 0 {
- continue
- }
- var reorg uint64
- if lastHead != nil {
- // If a setHead has been performed, the common ancestor can be nil.
- if ancestor := rawdb.FindCommonAncestor(h.chainDb, header, lastHead); ancestor != nil {
- reorg = lastHead.Number.Uint64() - ancestor.Number.Uint64()
- }
- }
- lastHead, lastTd = header, td
- log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
- h.server.peers.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg})
- case <-h.closeCh:
- return
- }
- }
-}
diff --git a/les/server_requests.go b/les/server_requests.go
deleted file mode 100644
index 9a249f04c..000000000
--- a/les/server_requests.go
+++ /dev/null
@@ -1,566 +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 .
-
-package les
-
-import (
- "encoding/binary"
- "encoding/json"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// serverBackend defines the backend functions needed for serving LES requests
-type serverBackend interface {
- ArchiveMode() bool
- AddTxsSync() bool
- BlockChain() *core.BlockChain
- TxPool() *txpool.TxPool
- GetHelperTrie(typ uint, index uint64) *trie.Trie
-}
-
-// Decoder is implemented by the messages passed to the handler functions
-type Decoder interface {
- Decode(val interface{}) error
-}
-
-// RequestType is a static struct that describes an LES request type and references
-// its handler function.
-type RequestType struct {
- Name string
- MaxCount uint64
- InPacketsMeter, InTrafficMeter, OutPacketsMeter, OutTrafficMeter metrics.Meter
- ServingTimeMeter metrics.Timer
- Handle func(msg Decoder) (serve serveRequestFn, reqID, amount uint64, err error)
-}
-
-// serveRequestFn is returned by the request handler functions after decoding the request.
-// This function does the actual request serving using the supplied backend. waitOrStop is
-// called between serving individual request items and may block if the serving process
-// needs to be throttled. If it returns false then the process is terminated.
-// The reply is not sent by this function yet. The flow control feedback value is supplied
-// by the protocol handler when calling the send function of the returned reply struct.
-type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop func() bool) *reply
-
-// Les3 contains the request types supported by les/2 and les/3
-var Les3 = map[uint64]RequestType{
- GetBlockHeadersMsg: {
- Name: "block header request",
- MaxCount: MaxHeaderFetch,
- InPacketsMeter: miscInHeaderPacketsMeter,
- InTrafficMeter: miscInHeaderTrafficMeter,
- OutPacketsMeter: miscOutHeaderPacketsMeter,
- OutTrafficMeter: miscOutHeaderTrafficMeter,
- ServingTimeMeter: miscServingTimeHeaderTimer,
- Handle: handleGetBlockHeaders,
- },
- GetBlockBodiesMsg: {
- Name: "block bodies request",
- MaxCount: MaxBodyFetch,
- InPacketsMeter: miscInBodyPacketsMeter,
- InTrafficMeter: miscInBodyTrafficMeter,
- OutPacketsMeter: miscOutBodyPacketsMeter,
- OutTrafficMeter: miscOutBodyTrafficMeter,
- ServingTimeMeter: miscServingTimeBodyTimer,
- Handle: handleGetBlockBodies,
- },
- GetCodeMsg: {
- Name: "code request",
- MaxCount: MaxCodeFetch,
- InPacketsMeter: miscInCodePacketsMeter,
- InTrafficMeter: miscInCodeTrafficMeter,
- OutPacketsMeter: miscOutCodePacketsMeter,
- OutTrafficMeter: miscOutCodeTrafficMeter,
- ServingTimeMeter: miscServingTimeCodeTimer,
- Handle: handleGetCode,
- },
- GetReceiptsMsg: {
- Name: "receipts request",
- MaxCount: MaxReceiptFetch,
- InPacketsMeter: miscInReceiptPacketsMeter,
- InTrafficMeter: miscInReceiptTrafficMeter,
- OutPacketsMeter: miscOutReceiptPacketsMeter,
- OutTrafficMeter: miscOutReceiptTrafficMeter,
- ServingTimeMeter: miscServingTimeReceiptTimer,
- Handle: handleGetReceipts,
- },
- GetProofsV2Msg: {
- Name: "les/2 proofs request",
- MaxCount: MaxProofsFetch,
- InPacketsMeter: miscInTrieProofPacketsMeter,
- InTrafficMeter: miscInTrieProofTrafficMeter,
- OutPacketsMeter: miscOutTrieProofPacketsMeter,
- OutTrafficMeter: miscOutTrieProofTrafficMeter,
- ServingTimeMeter: miscServingTimeTrieProofTimer,
- Handle: handleGetProofs,
- },
- GetHelperTrieProofsMsg: {
- Name: "helper trie proof request",
- MaxCount: MaxHelperTrieProofsFetch,
- InPacketsMeter: miscInHelperTriePacketsMeter,
- InTrafficMeter: miscInHelperTrieTrafficMeter,
- OutPacketsMeter: miscOutHelperTriePacketsMeter,
- OutTrafficMeter: miscOutHelperTrieTrafficMeter,
- ServingTimeMeter: miscServingTimeHelperTrieTimer,
- Handle: handleGetHelperTrieProofs,
- },
- SendTxV2Msg: {
- Name: "new transactions",
- MaxCount: MaxTxSend,
- InPacketsMeter: miscInTxsPacketsMeter,
- InTrafficMeter: miscInTxsTrafficMeter,
- OutPacketsMeter: miscOutTxsPacketsMeter,
- OutTrafficMeter: miscOutTxsTrafficMeter,
- ServingTimeMeter: miscServingTimeTxTimer,
- Handle: handleSendTx,
- },
- GetTxStatusMsg: {
- Name: "transaction status query request",
- MaxCount: MaxTxStatus,
- InPacketsMeter: miscInTxStatusPacketsMeter,
- InTrafficMeter: miscInTxStatusTrafficMeter,
- OutPacketsMeter: miscOutTxStatusPacketsMeter,
- OutTrafficMeter: miscOutTxStatusTrafficMeter,
- ServingTimeMeter: miscServingTimeTxStatusTimer,
- Handle: handleGetTxStatus,
- },
-}
-
-// handleGetBlockHeaders handles a block header request
-func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetBlockHeadersPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- // Gather headers until the fetch or network limits is reached
- var (
- bc = backend.BlockChain()
- hashMode = r.Query.Origin.Hash != (common.Hash{})
- first = true
- maxNonCanonical = uint64(100)
- bytes common.StorageSize
- headers []*types.Header
- unknown bool
- )
- for !unknown && len(headers) < int(r.Query.Amount) && bytes < softResponseLimit {
- if !first && !waitOrStop() {
- return nil
- }
- // Retrieve the next header satisfying the r
- var origin *types.Header
- if hashMode {
- if first {
- origin = bc.GetHeaderByHash(r.Query.Origin.Hash)
- if origin != nil {
- r.Query.Origin.Number = origin.Number.Uint64()
- }
- } else {
- origin = bc.GetHeader(r.Query.Origin.Hash, r.Query.Origin.Number)
- }
- } else {
- origin = bc.GetHeaderByNumber(r.Query.Origin.Number)
- }
- if origin == nil {
- break
- }
- headers = append(headers, origin)
- bytes += estHeaderRlpSize
-
- // Advance to the next header of the r
- switch {
- case hashMode && r.Query.Reverse:
- // Hash based traversal towards the genesis block
- ancestor := r.Query.Skip + 1
- if ancestor == 0 {
- unknown = true
- } else {
- r.Query.Origin.Hash, r.Query.Origin.Number = bc.GetAncestor(r.Query.Origin.Hash, r.Query.Origin.Number, ancestor, &maxNonCanonical)
- unknown = r.Query.Origin.Hash == common.Hash{}
- }
- case hashMode && !r.Query.Reverse:
- // Hash based traversal towards the leaf block
- var (
- current = origin.Number.Uint64()
- next = current + r.Query.Skip + 1
- )
- if next <= current {
- infos, _ := json.Marshal(p.Peer.Info())
- p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", string(infos))
- unknown = true
- } else {
- if header := bc.GetHeaderByNumber(next); header != nil {
- nextHash := header.Hash()
- expOldHash, _ := bc.GetAncestor(nextHash, next, r.Query.Skip+1, &maxNonCanonical)
- if expOldHash == r.Query.Origin.Hash {
- r.Query.Origin.Hash, r.Query.Origin.Number = nextHash, next
- } else {
- unknown = true
- }
- } else {
- unknown = true
- }
- }
- case r.Query.Reverse:
- // Number based traversal towards the genesis block
- if r.Query.Origin.Number >= r.Query.Skip+1 {
- r.Query.Origin.Number -= r.Query.Skip + 1
- } else {
- unknown = true
- }
-
- case !r.Query.Reverse:
- // Number based traversal towards the leaf block
- r.Query.Origin.Number += r.Query.Skip + 1
- }
- first = false
- }
- return p.replyBlockHeaders(r.ReqID, headers)
- }, r.ReqID, r.Query.Amount, nil
-}
-
-// handleGetBlockBodies handles a block body request
-func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetBlockBodiesPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- bytes int
- bodies []rlp.RawValue
- )
- bc := backend.BlockChain()
- for i, hash := range r.Hashes {
- if i != 0 && !waitOrStop() {
- return nil
- }
- if bytes >= softResponseLimit {
- break
- }
- body := bc.GetBodyRLP(hash)
- if body == nil {
- p.bumpInvalid()
- continue
- }
- bodies = append(bodies, body)
- bytes += len(body)
- }
- return p.replyBlockBodiesRLP(r.ReqID, bodies)
- }, r.ReqID, uint64(len(r.Hashes)), nil
-}
-
-// handleGetCode handles a contract code request
-func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetCodePacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- bytes int
- data [][]byte
- )
- bc := backend.BlockChain()
- for i, request := range r.Reqs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- // Look up the root hash belonging to the request
- header := bc.GetHeaderByHash(request.BHash)
- if header == nil {
- p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash)
- p.bumpInvalid()
- continue
- }
- // Refuse to search stale state data in the database since looking for
- // a non-exist key is kind of expensive.
- local := bc.CurrentHeader().Number.Uint64()
- if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
- p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local)
- p.bumpInvalid()
- continue
- }
- address := common.BytesToAddress(request.AccountAddress)
- account, err := getAccount(bc.TrieDB(), header.Root, address)
- if err != nil {
- p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", address, "err", err)
- p.bumpInvalid()
- continue
- }
- code, err := bc.StateCache().ContractCode(address, common.BytesToHash(account.CodeHash))
- if err != nil {
- p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", address, "codehash", common.BytesToHash(account.CodeHash), "err", err)
- continue
- }
- // Accumulate the code and abort if enough data was retrieved
- data = append(data, code)
- if bytes += len(code); bytes >= softResponseLimit {
- break
- }
- }
- return p.replyCode(r.ReqID, data)
- }, r.ReqID, uint64(len(r.Reqs)), nil
-}
-
-// handleGetReceipts handles a block receipts request
-func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetReceiptsPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- bytes int
- receipts []rlp.RawValue
- )
- bc := backend.BlockChain()
- for i, hash := range r.Hashes {
- if i != 0 && !waitOrStop() {
- return nil
- }
- if bytes >= softResponseLimit {
- break
- }
- // Retrieve the requested block's receipts, skipping if unknown to us
- results := bc.GetReceiptsByHash(hash)
- if results == nil {
- if header := bc.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyReceiptsHash {
- p.bumpInvalid()
- continue
- }
- }
- // If known, encode and queue for response packet
- if encoded, err := rlp.EncodeToBytes(results); err != nil {
- log.Error("Failed to encode receipt", "err", err)
- } else {
- receipts = append(receipts, encoded)
- bytes += len(encoded)
- }
- }
- return p.replyReceiptsRLP(r.ReqID, receipts)
- }, r.ReqID, uint64(len(r.Hashes)), nil
-}
-
-// handleGetProofs handles a proof request
-func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetProofsPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- lastBHash common.Hash
- root common.Hash
- header *types.Header
- err error
- )
- bc := backend.BlockChain()
- nodes := trienode.NewProofSet()
-
- for i, request := range r.Reqs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- // Look up the root hash belonging to the request
- if request.BHash != lastBHash {
- root, lastBHash = common.Hash{}, request.BHash
-
- if header = bc.GetHeaderByHash(request.BHash); header == nil {
- p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash)
- p.bumpInvalid()
- continue
- }
- // Refuse to search stale state data in the database since looking for
- // a non-exist key is kind of expensive.
- local := bc.CurrentHeader().Number.Uint64()
- if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
- p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local)
- p.bumpInvalid()
- continue
- }
- root = header.Root
- }
- // If a header lookup failed (non existent), ignore subsequent requests for the same header
- if root == (common.Hash{}) {
- p.bumpInvalid()
- continue
- }
- // Open the account or storage trie for the request
- statedb := bc.StateCache()
-
- var trie state.Trie
- switch len(request.AccountAddress) {
- case 0:
- // No account key specified, open an account trie
- trie, err = statedb.OpenTrie(root)
- if trie == nil || err != nil {
- p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err)
- continue
- }
- default:
- // Account key specified, open a storage trie
- address := common.BytesToAddress(request.AccountAddress)
- account, err := getAccount(bc.TrieDB(), root, address)
- if err != nil {
- p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", address, "err", err)
- p.bumpInvalid()
- continue
- }
- trie, err = statedb.OpenStorageTrie(root, address, account.Root)
- if trie == nil || err != nil {
- p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", address, "root", account.Root, "err", err)
- continue
- }
- }
- // Prove the user's request from the account or storage trie
- if err := trie.Prove(request.Key, nodes); err != nil {
- p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err)
- continue
- }
- if nodes.DataSize() >= softResponseLimit {
- break
- }
- }
- return p.replyProofsV2(r.ReqID, nodes.List())
- }, r.ReqID, uint64(len(r.Reqs)), nil
-}
-
-// handleGetHelperTrieProofs handles a helper trie proof request
-func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetHelperTrieProofsPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- var (
- lastIdx uint64
- lastType uint
- auxTrie *trie.Trie
- auxBytes int
- auxData [][]byte
- )
- bc := backend.BlockChain()
- nodes := trienode.NewProofSet()
- for i, request := range r.Reqs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx {
- lastType, lastIdx = request.Type, request.TrieIdx
- auxTrie = backend.GetHelperTrie(request.Type, request.TrieIdx)
- }
- if auxTrie == nil {
- return nil
- }
- // TODO(rjl493456442) short circuit if the proving is failed.
- // The original client side code has a dirty hack to retrieve
- // the headers with no valid proof. Keep the compatibility for
- // legacy les protocol and drop this hack when the les2/3 are
- // not supported.
- err := auxTrie.Prove(request.Key, nodes)
- if p.version >= lpv4 && err != nil {
- return nil
- }
- if request.Type == htCanonical && request.AuxReq == htAuxHeader && len(request.Key) == 8 {
- header := bc.GetHeaderByNumber(binary.BigEndian.Uint64(request.Key))
- data, err := rlp.EncodeToBytes(header)
- if err != nil {
- log.Error("Failed to encode header", "err", err)
- return nil
- }
- auxData = append(auxData, data)
- auxBytes += len(data)
- }
- if nodes.DataSize()+auxBytes >= softResponseLimit {
- break
- }
- }
- return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.List(), AuxData: auxData})
- }, r.ReqID, uint64(len(r.Reqs)), nil
-}
-
-// handleSendTx handles a transaction propagation request
-func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r SendTxPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- amount := uint64(len(r.Txs))
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- stats := make([]light.TxStatus, len(r.Txs))
- for i, tx := range r.Txs {
- if i != 0 && !waitOrStop() {
- return nil
- }
- hash := tx.Hash()
- stats[i] = txStatus(backend, hash)
- if stats[i].Status == txpool.TxStatusUnknown {
- if errs := backend.TxPool().Add([]*types.Transaction{tx}, false, backend.AddTxsSync()); errs[0] != nil {
- stats[i].Error = errs[0].Error()
- continue
- }
- stats[i] = txStatus(backend, hash)
- }
- }
- return p.replyTxStatus(r.ReqID, stats)
- }, r.ReqID, amount, nil
-}
-
-// handleGetTxStatus handles a transaction status query
-func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) {
- var r GetTxStatusPacket
- if err := msg.Decode(&r); err != nil {
- return nil, 0, 0, err
- }
- return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
- stats := make([]light.TxStatus, len(r.Hashes))
- for i, hash := range r.Hashes {
- if i != 0 && !waitOrStop() {
- return nil
- }
- stats[i] = txStatus(backend, hash)
- }
- return p.replyTxStatus(r.ReqID, stats)
- }, r.ReqID, uint64(len(r.Hashes)), nil
-}
-
-// txStatus returns the status of a specified transaction.
-func txStatus(b serverBackend, hash common.Hash) light.TxStatus {
- var stat light.TxStatus
- // Looking the transaction in txpool first.
- stat.Status = b.TxPool().Status(hash)
-
- // If the transaction is unknown to the pool, try looking it up locally.
- if stat.Status == txpool.TxStatusUnknown {
- lookup := b.BlockChain().GetTransactionLookup(hash)
- if lookup != nil {
- stat.Status = txpool.TxStatusIncluded
- stat.Lookup = lookup
- }
- }
- return stat
-}
diff --git a/les/servingqueue.go b/les/servingqueue.go
deleted file mode 100644
index b258fc3ca..000000000
--- a/les/servingqueue.go
+++ /dev/null
@@ -1,365 +0,0 @@
-// Copyright 2019 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 .
-
-package les
-
-import (
- "sync"
- "sync/atomic"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/common/prque"
- "golang.org/x/exp/slices"
-)
-
-// servingQueue allows running tasks in a limited number of threads and puts the
-// waiting tasks in a priority queue
-type servingQueue struct {
- recentTime, queuedTime uint64
- servingTimeDiff atomic.Uint64
- burstLimit, burstDropLimit uint64
- burstDecRate float64
- lastUpdate mclock.AbsTime
-
- queueAddCh, queueBestCh chan *servingTask
- stopThreadCh, quit chan struct{}
- setThreadsCh chan int
-
- wg sync.WaitGroup
- threadCount int // number of currently running threads
- queue *prque.Prque[int64, *servingTask] // priority queue for waiting or suspended tasks
- best *servingTask // the highest priority task (not included in the queue)
- suspendBias int64 // priority bias against suspending an already running task
-}
-
-// servingTask represents a request serving task. Tasks can be implemented to
-// run in multiple steps, allowing the serving queue to suspend execution between
-// steps if higher priority tasks are entered. The creator of the task should
-// set the following fields:
-//
-// - priority: greater value means higher priority; values can wrap around the int64 range
-// - run: execute a single step; return true if finished
-// - after: executed after run finishes or returns an error, receives the total serving time
-type servingTask struct {
- sq *servingQueue
- servingTime, timeAdded, maxTime, expTime uint64
- peer *clientPeer
- priority int64
- biasAdded bool
- token runToken
- tokenCh chan runToken
-}
-
-// runToken received by servingTask.start allows the task to run. Closing the
-// channel by servingTask.stop signals the thread controller to allow a new task
-// to start running.
-type runToken chan struct{}
-
-// start blocks until the task can start and returns true if it is allowed to run.
-// Returning false means that the task should be cancelled.
-func (t *servingTask) start() bool {
- if t.peer.isFrozen() {
- return false
- }
- t.tokenCh = make(chan runToken, 1)
- select {
- case t.sq.queueAddCh <- t:
- case <-t.sq.quit:
- return false
- }
- select {
- case t.token = <-t.tokenCh:
- case <-t.sq.quit:
- return false
- }
- if t.token == nil {
- return false
- }
- t.servingTime -= uint64(mclock.Now())
- return true
-}
-
-// done signals the thread controller about the task being finished and returns
-// the total serving time of the task in nanoseconds.
-func (t *servingTask) done() uint64 {
- t.servingTime += uint64(mclock.Now())
- close(t.token)
- diff := t.servingTime - t.timeAdded
- t.timeAdded = t.servingTime
- if t.expTime > diff {
- t.expTime -= diff
- t.sq.servingTimeDiff.Add(t.expTime)
- } else {
- t.expTime = 0
- }
- return t.servingTime
-}
-
-// waitOrStop can be called during the execution of the task. It blocks if there
-// is a higher priority task waiting (a bias is applied in favor of the currently
-// running task). Returning true means that the execution can be resumed. False
-// means the task should be cancelled.
-func (t *servingTask) waitOrStop() bool {
- t.done()
- if !t.biasAdded {
- t.priority += t.sq.suspendBias
- t.biasAdded = true
- }
- return t.start()
-}
-
-// newServingQueue returns a new servingQueue
-func newServingQueue(suspendBias int64, utilTarget float64) *servingQueue {
- sq := &servingQueue{
- queue: prque.New[int64, *servingTask](nil),
- suspendBias: suspendBias,
- queueAddCh: make(chan *servingTask, 100),
- queueBestCh: make(chan *servingTask),
- stopThreadCh: make(chan struct{}),
- quit: make(chan struct{}),
- setThreadsCh: make(chan int, 10),
- burstLimit: uint64(utilTarget * bufLimitRatio * 1200000),
- burstDropLimit: uint64(utilTarget * bufLimitRatio * 1000000),
- burstDecRate: utilTarget,
- lastUpdate: mclock.Now(),
- }
- sq.wg.Add(2)
- go sq.queueLoop()
- go sq.threadCountLoop()
- return sq
-}
-
-// newTask creates a new task with the given priority
-func (sq *servingQueue) newTask(peer *clientPeer, maxTime uint64, priority int64) *servingTask {
- return &servingTask{
- sq: sq,
- peer: peer,
- maxTime: maxTime,
- expTime: maxTime,
- priority: priority,
- }
-}
-
-// threadController is started in multiple goroutines and controls the execution
-// of tasks. The number of active thread controllers equals the allowed number of
-// concurrently running threads. It tries to fetch the highest priority queued
-// task first. If there are no queued tasks waiting then it can directly catch
-// run tokens from the token channel and allow the corresponding tasks to run
-// without entering the priority queue.
-func (sq *servingQueue) threadController() {
- defer sq.wg.Done()
- for {
- token := make(runToken)
- select {
- case best := <-sq.queueBestCh:
- best.tokenCh <- token
- case <-sq.stopThreadCh:
- return
- case <-sq.quit:
- return
- }
- select {
- case <-sq.stopThreadCh:
- return
- case <-sq.quit:
- return
- case <-token:
- }
- }
-}
-
-// peerTasks lists the tasks received from a given peer when selecting peers to freeze
-type peerTasks struct {
- peer *clientPeer
- list []*servingTask
- sumTime uint64
- priority float64
-}
-
-// freezePeers selects the peers with the worst priority queued tasks and freezes
-// them until burstTime goes under burstDropLimit or all peers are frozen
-func (sq *servingQueue) freezePeers() {
- peerMap := make(map[*clientPeer]*peerTasks)
- var peerList []*peerTasks
- if sq.best != nil {
- sq.queue.Push(sq.best, sq.best.priority)
- }
- sq.best = nil
- for sq.queue.Size() > 0 {
- task := sq.queue.PopItem()
- tasks := peerMap[task.peer]
- if tasks == nil {
- bufValue, bufLimit := task.peer.fcClient.BufferStatus()
- if bufLimit < 1 {
- bufLimit = 1
- }
- tasks = &peerTasks{
- peer: task.peer,
- priority: float64(bufValue) / float64(bufLimit), // lower value comes first
- }
- peerMap[task.peer] = tasks
- peerList = append(peerList, tasks)
- }
- tasks.list = append(tasks.list, task)
- tasks.sumTime += task.expTime
- }
- slices.SortFunc(peerList, func(a, b *peerTasks) int {
- if a.priority < b.priority {
- return -1
- }
- if a.priority > b.priority {
- return 1
- }
- return 0
- })
- drop := true
- for _, tasks := range peerList {
- if drop {
- tasks.peer.freeze()
- tasks.peer.fcClient.Freeze()
- sq.queuedTime -= tasks.sumTime
- sqQueuedGauge.Update(int64(sq.queuedTime))
- clientFreezeMeter.Mark(1)
- drop = sq.recentTime+sq.queuedTime > sq.burstDropLimit
- for _, task := range tasks.list {
- task.tokenCh <- nil
- }
- } else {
- for _, task := range tasks.list {
- sq.queue.Push(task, task.priority)
- }
- }
- }
- if sq.queue.Size() > 0 {
- sq.best = sq.queue.PopItem()
- }
-}
-
-// updateRecentTime recalculates the recent serving time value
-func (sq *servingQueue) updateRecentTime() {
- subTime := sq.servingTimeDiff.Swap(0)
- now := mclock.Now()
- dt := now - sq.lastUpdate
- sq.lastUpdate = now
- if dt > 0 {
- subTime += uint64(float64(dt) * sq.burstDecRate)
- }
- if sq.recentTime > subTime {
- sq.recentTime -= subTime
- } else {
- sq.recentTime = 0
- }
-}
-
-// addTask inserts a task into the priority queue
-func (sq *servingQueue) addTask(task *servingTask) {
- if sq.best == nil {
- sq.best = task
- } else if task.priority-sq.best.priority > 0 {
- sq.queue.Push(sq.best, sq.best.priority)
- sq.best = task
- } else {
- sq.queue.Push(task, task.priority)
- }
- sq.updateRecentTime()
- sq.queuedTime += task.expTime
- sqServedGauge.Update(int64(sq.recentTime))
- sqQueuedGauge.Update(int64(sq.queuedTime))
- if sq.recentTime+sq.queuedTime > sq.burstLimit {
- sq.freezePeers()
- }
-}
-
-// queueLoop is an event loop running in a goroutine. It receives tasks from queueAddCh
-// and always tries to send the highest priority task to queueBestCh. Successfully sent
-// tasks are removed from the queue.
-func (sq *servingQueue) queueLoop() {
- defer sq.wg.Done()
- for {
- if sq.best != nil {
- expTime := sq.best.expTime
- select {
- case task := <-sq.queueAddCh:
- sq.addTask(task)
- case sq.queueBestCh <- sq.best:
- sq.updateRecentTime()
- sq.queuedTime -= expTime
- sq.recentTime += expTime
- sqServedGauge.Update(int64(sq.recentTime))
- sqQueuedGauge.Update(int64(sq.queuedTime))
- if sq.queue.Size() == 0 {
- sq.best = nil
- } else {
- sq.best = sq.queue.PopItem()
- }
- case <-sq.quit:
- return
- }
- } else {
- select {
- case task := <-sq.queueAddCh:
- sq.addTask(task)
- case <-sq.quit:
- return
- }
- }
- }
-}
-
-// threadCountLoop is an event loop running in a goroutine. It adjusts the number
-// of active thread controller goroutines.
-func (sq *servingQueue) threadCountLoop() {
- var threadCountTarget int
- defer sq.wg.Done()
- for {
- for threadCountTarget > sq.threadCount {
- sq.wg.Add(1)
- go sq.threadController()
- sq.threadCount++
- }
- if threadCountTarget < sq.threadCount {
- select {
- case threadCountTarget = <-sq.setThreadsCh:
- case sq.stopThreadCh <- struct{}{}:
- sq.threadCount--
- case <-sq.quit:
- return
- }
- } else {
- select {
- case threadCountTarget = <-sq.setThreadsCh:
- case <-sq.quit:
- return
- }
- }
- }
-}
-
-// setThreads sets the allowed processing thread count, suspending tasks as soon as
-// possible if necessary.
-func (sq *servingQueue) setThreads(threadCount int) {
- select {
- case sq.setThreadsCh <- threadCount:
- case <-sq.quit:
- return
- }
-}
-
-// stop stops task processing as soon as possible and shuts down the serving queue.
-func (sq *servingQueue) stop() {
- close(sq.quit)
- sq.wg.Wait()
-}
diff --git a/les/state_accessor.go b/les/state_accessor.go
deleted file mode 100644
index 9a8214ac2..000000000
--- a/les/state_accessor.go
+++ /dev/null
@@ -1,80 +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 .
-
-package les
-
-import (
- "context"
- "errors"
- "fmt"
-
- "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/vm"
- "github.com/ethereum/go-ethereum/eth/tracers"
- "github.com/ethereum/go-ethereum/light"
-)
-
-// noopReleaser is returned in case there is no operation expected
-// for releasing state.
-var noopReleaser = tracers.StateReleaseFunc(func() {})
-
-// stateAtBlock retrieves the state database associated with a certain block.
-func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, tracers.StateReleaseFunc, error) {
- return light.NewState(ctx, block.Header(), leth.odr), noopReleaser, nil
-}
-
-// stateAtTransaction returns the execution environment of a certain transaction.
-func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
- // Short circuit if it's genesis block.
- if block.NumberU64() == 0 {
- return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis")
- }
- // Create the parent state database
- parent, err := leth.blockchain.GetBlock(ctx, block.ParentHash(), block.NumberU64()-1)
- if err != nil {
- return nil, vm.BlockContext{}, nil, nil, err
- }
- statedb, release, err := leth.stateAtBlock(ctx, parent, reexec)
- if err != nil {
- return nil, vm.BlockContext{}, nil, nil, err
- }
- if txIndex == 0 && len(block.Transactions()) == 0 {
- return nil, vm.BlockContext{}, statedb, release, nil
- }
- // Recompute transactions up to the target index.
- signer := types.MakeSigner(leth.blockchain.Config(), block.Number(), block.Time())
- for idx, tx := range block.Transactions() {
- // Assemble the transaction call message and return if the requested offset
- msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
- txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil)
- statedb.SetTxContext(tx.Hash(), idx)
- if idx == txIndex {
- return msg, context, statedb, release, nil
- }
- // Not yet the searched for transaction, execute on top of the current state
- vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{})
- if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
- return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
- }
- // Ensure any modifications are committed to the state
- // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
- statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
- }
- return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
-}
diff --git a/les/test_helper.go b/les/test_helper.go
deleted file mode 100644
index 6be13eaec..000000000
--- a/les/test_helper.go
+++ /dev/null
@@ -1,626 +0,0 @@
-// Copyright 2019 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 .
-
-// This file contains some shares testing functionality, common to multiple
-// different files and modules being tested. Client based network and Server
-// based network can be created easily with available APIs.
-
-package les
-
-import (
- "context"
- "crypto/rand"
- "fmt"
- "math/big"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/consensus/ethash"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/forkid"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/txpool/legacypool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/eth/ethconfig"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/les/flowcontrol"
- vfs "github.com/ethereum/go-ethereum/les/vflux/server"
- "github.com/ethereum/go-ethereum/light"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-var (
- bankKey, _ = crypto.GenerateKey()
- bankAddr = crypto.PubkeyToAddress(bankKey.PublicKey)
- bankFunds = big.NewInt(1_000_000_000_000_000_000)
-
- userKey1, _ = crypto.GenerateKey()
- userKey2, _ = crypto.GenerateKey()
- userAddr1 = crypto.PubkeyToAddress(userKey1.PublicKey)
- userAddr2 = crypto.PubkeyToAddress(userKey2.PublicKey)
-
- testContractAddr common.Address
- testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
- testContractCodeDeployed = testContractCode[16:]
- testContractDeployed = uint64(2)
-
- testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029")
-
- // Checkpoint oracle relative fields
- signerKey, _ = crypto.GenerateKey()
- signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey)
-)
-
-var (
- // The token bucket buffer limit for testing purpose.
- testBufLimit = uint64(1000000)
-
- // The buffer recharging speed for testing purpose.
- testBufRecharge = uint64(1000)
-)
-
-/*
-contract test {
-
- uint256[100] data;
-
- function Put(uint256 addr, uint256 value) {
- data[addr] = value;
- }
-
- function Get(uint256 addr) constant returns (uint256 value) {
- return data[addr];
- }
-}
-*/
-
-// prepare pre-commits specified number customized blocks into chain.
-func prepare(n int, backend *backends.SimulatedBackend) {
- var (
- ctx = context.Background()
- signer = types.HomesteadSigner{}
- )
- for i := 0; i < n; i++ {
- switch i {
- case 0:
- // Builtin-block
- // number: 1
- // txs: 1
-
- // bankUser transfers some ether to user1
- nonce, _ := backend.PendingNonceAt(ctx, bankAddr)
- tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10_000_000_000_000_000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey)
- backend.SendTransaction(ctx, tx)
- case 1:
- // Builtin-block
- // number: 2
- // txs: 4
-
- bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
- userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1)
-
- // bankUser transfers more ether to user1
- tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1_000_000_000_000_000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey)
- backend.SendTransaction(ctx, tx1)
-
- // user1 relays ether to user2
- tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1_000_000_000_000_000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, userKey1)
- backend.SendTransaction(ctx, tx2)
-
- // user1 deploys a test contract
- tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(params.InitialBaseFee), testContractCode), signer, userKey1)
- backend.SendTransaction(ctx, tx3)
- testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1)
-
- // user1 deploys a event contract
- tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(params.InitialBaseFee), testEventEmitterCode), signer, userKey1)
- backend.SendTransaction(ctx, tx4)
- case 2:
- // Builtin-block
- // number: 3
- // txs: 2
-
- // bankUser transfer some ether to signer
- bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
- tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey)
- backend.SendTransaction(ctx, tx1)
-
- // invoke test contract
- data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001")
- tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, big.NewInt(params.InitialBaseFee), data), signer, bankKey)
- backend.SendTransaction(ctx, tx2)
- case 3:
- // Builtin-block
- // number: 4
- // txs: 1
-
- // invoke test contract
- bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
- data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
- tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, big.NewInt(params.InitialBaseFee), data), signer, bankKey)
- backend.SendTransaction(ctx, tx)
- }
- backend.Commit()
- }
-}
-
-// testIndexers creates a set of indexers with specified params for testing purpose.
-func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig, disablePruning bool) []*core.ChainIndexer {
- var indexers [3]*core.ChainIndexer
- indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms, disablePruning)
- indexers[1] = core.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms)
- indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize, disablePruning)
- // make bloomTrieIndexer as a child indexer of bloom indexer.
- indexers[1].AddChildIndexer(indexers[2])
- return indexers[:]
-}
-
-func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *serverPeerSet) (*clientHandler, func()) {
- var (
- evmux = new(event.TypeMux)
- engine = ethash.NewFaker()
- gspec = core.Genesis{
- Config: params.AllEthashProtocolChanges,
- Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
- GasLimit: 100000000,
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
- chain, _ := light.NewLightChain(odr, gspec.Config, engine)
-
- client := &LightEthereum{
- lesCommons: lesCommons{
- genesis: genesis.Hash(),
- config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId},
- chainConfig: params.AllEthashProtocolChanges,
- iConfig: light.TestClientIndexerConfig,
- chainDb: db,
- chainReader: chain,
- closeCh: make(chan struct{}),
- },
- peers: peers,
- reqDist: odr.retriever.dist,
- retriever: odr.retriever,
- odr: odr,
- engine: engine,
- blockchain: chain,
- eventMux: evmux,
- merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
- }
- client.handler = newClientHandler(client)
-
- return client.handler, func() {
- client.handler.stop()
- }
-}
-
-func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend, func()) {
- var (
- gspec = core.Genesis{
- Config: params.AllEthashProtocolChanges,
- Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
- GasLimit: 100000000,
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
-
- // create a simulation backend and pre-commit several customized block to the database.
- simulation := backends.NewSimulatedBackendWithDatabase(db, gspec.Alloc, 100000000)
- prepare(blocks, simulation)
-
- txpoolConfig := legacypool.DefaultConfig
- txpoolConfig.Journal = ""
-
- pool := legacypool.New(txpoolConfig, simulation.Blockchain())
- txpool, _ := txpool.New(new(big.Int).SetUint64(txpoolConfig.PriceLimit), simulation.Blockchain(), []txpool.SubPool{pool})
-
- server := &LesServer{
- lesCommons: lesCommons{
- genesis: genesis.Hash(),
- config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId},
- chainConfig: params.AllEthashProtocolChanges,
- iConfig: light.TestServerIndexerConfig,
- chainDb: db,
- chainReader: simulation.Blockchain(),
- closeCh: make(chan struct{}),
- },
- peers: newClientPeerSet(),
- servingQueue: newServingQueue(int64(time.Millisecond*10), 1),
- defParams: flowcontrol.ServerParams{
- BufLimit: testBufLimit,
- MinRecharge: testBufRecharge,
- },
- fcManager: flowcontrol.NewClientManager(nil, clock),
- }
- server.costTracker, server.minCapacity = newCostTracker(db, server.config)
- server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism.
- server.clientPool = vfs.NewClientPool(db, testBufRecharge, defaultConnectedBias, clock, alwaysTrueFn)
- server.clientPool.Start()
- server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool
- server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true })
- server.servingQueue.setThreads(4)
- server.handler.start()
- closer := func() { server.Stop() }
- return server.handler, simulation, closer
-}
-
-func alwaysTrueFn() bool {
- return true
-}
-
-// testPeer is a simulated peer to allow testing direct network calls.
-type testPeer struct {
- cpeer *clientPeer
- speer *serverPeer
-
- net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
- app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side
-}
-
-// handshakeWithServer executes the handshake with the remote server peer.
-func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID) {
- // It only works for the simulated client peer
- if p.cpeer == nil {
- t.Fatal("handshake for client peer only")
- }
- var sendList keyValueList
- sendList = sendList.add("protocolVersion", uint64(p.cpeer.version))
- sendList = sendList.add("networkId", uint64(NetworkId))
- sendList = sendList.add("headTd", td)
- sendList = sendList.add("headHash", head)
- sendList = sendList.add("headNum", headNum)
- sendList = sendList.add("genesisHash", genesis)
- if p.cpeer.version >= lpv4 {
- sendList = sendList.add("forkID", &forkID)
- }
- if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil {
- t.Fatalf("status recv: %v", err)
- }
- if err := p2p.Send(p.app, StatusMsg, &sendList); err != nil {
- t.Fatalf("status send: %v", err)
- }
-}
-
-// handshakeWithClient executes the handshake with the remote client peer.
-// (used by temporarily disabled tests)
-/*func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList, recentTxLookup uint64) {
- // It only works for the simulated client peer
- if p.speer == nil {
- t.Fatal("handshake for server peer only")
- }
- var sendList keyValueList
- sendList = sendList.add("protocolVersion", uint64(p.speer.version))
- sendList = sendList.add("networkId", uint64(NetworkId))
- sendList = sendList.add("headTd", td)
- sendList = sendList.add("headHash", head)
- sendList = sendList.add("headNum", headNum)
- sendList = sendList.add("genesisHash", genesis)
- sendList = sendList.add("serveHeaders", nil)
- sendList = sendList.add("serveChainSince", uint64(0))
- sendList = sendList.add("serveStateSince", uint64(0))
- sendList = sendList.add("serveRecentState", uint64(core.TriesInMemory-4))
- sendList = sendList.add("txRelay", nil)
- sendList = sendList.add("flowControl/BL", testBufLimit)
- sendList = sendList.add("flowControl/MRR", testBufRecharge)
- sendList = sendList.add("flowControl/MRC", costList)
- if p.speer.version >= lpv4 {
- sendList = sendList.add("forkID", &forkID)
- sendList = sendList.add("recentTxLookup", recentTxLookup)
- }
- if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil {
- t.Fatalf("status recv: %v", err)
- }
- if err := p2p.Send(p.app, StatusMsg, &sendList); err != nil {
- t.Fatalf("status send: %v", err)
- }
-}*/
-
-// close terminates the local side of the peer, notifying the remote protocol
-// manager of termination.
-func (p *testPeer) close() {
- p.app.Close()
-}
-
-func newTestPeerPair(name string, version int, server *serverHandler, client *clientHandler, noInitAnnounce bool) (*testPeer, *testPeer, error) {
- // Create a message pipe to communicate through
- app, net := p2p.MsgPipe()
-
- // Generate a random id and create the peer
- var id enode.ID
- rand.Read(id[:])
-
- peer1 := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
- peer2 := newServerPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), app)
-
- // Start the peer on a new thread
- errc1 := make(chan error, 1)
- errc2 := make(chan error, 1)
- go func() {
- select {
- case <-server.closeCh:
- errc1 <- p2p.DiscQuitting
- case errc1 <- server.handle(peer1):
- }
- }()
- go func() {
- select {
- case <-client.closeCh:
- errc2 <- p2p.DiscQuitting
- case errc2 <- client.handle(peer2, noInitAnnounce):
- }
- }()
- // Ensure the connection is established or exits when any error occurs
- for {
- select {
- case err := <-errc1:
- return nil, nil, fmt.Errorf("failed to establish protocol connection %v", err)
- case err := <-errc2:
- return nil, nil, fmt.Errorf("failed to establish protocol connection %v", err)
- default:
- }
- if peer1.serving.Load() && peer2.serving.Load() {
- break
- }
- time.Sleep(50 * time.Millisecond)
- }
- return &testPeer{cpeer: peer1, net: net, app: app}, &testPeer{speer: peer2, net: app, app: net}, nil
-}
-
-type indexerCallback func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer)
-
-// testClient represents a client object for testing with necessary auxiliary fields.
-type testClient struct {
- clock mclock.Clock
- db ethdb.Database
- peer *testPeer
- handler *clientHandler
-
- chtIndexer *core.ChainIndexer
- bloomIndexer *core.ChainIndexer
- bloomTrieIndexer *core.ChainIndexer
-}
-
-// newRawPeer creates a new server peer connects to the server and do the handshake.
-// (used by temporarily disabled tests)
-/*func (client *testClient) newRawPeer(t *testing.T, name string, version int, recentTxLookup uint64) (*testPeer, func(), <-chan error) {
- // Create a message pipe to communicate through
- app, net := p2p.MsgPipe()
-
- // Generate a random id and create the peer
- var id enode.ID
- rand.Read(id[:])
- peer := newServerPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), net)
-
- // Start the peer on a new thread
- errCh := make(chan error, 1)
- go func() {
- select {
- case <-client.handler.closeCh:
- errCh <- p2p.DiscQuitting
- case errCh <- client.handler.handle(peer, false):
- }
- }()
- tp := &testPeer{
- app: app,
- net: net,
- speer: peer,
- }
- var (
- genesis = client.handler.backend.blockchain.Genesis()
- head = client.handler.backend.blockchain.CurrentHeader()
- td = client.handler.backend.blockchain.GetTd(head.Hash(), head.Number.Uint64())
- )
- forkID := forkid.NewID(client.handler.backend.blockchain.Config(), genesis.Hash(), head.Number.Uint64(), head.Time)
- tp.handshakeWithClient(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(0), recentTxLookup) // disable flow control by default
-
- // Ensure the connection is established or exits when any error occurs
- for {
- select {
- case <-errCh:
- return nil, nil, nil
- default:
- }
- if peer.serving.Load() {
- break
- }
- time.Sleep(50 * time.Millisecond)
- }
- closePeer := func() {
- tp.speer.close()
- tp.close()
- }
- return tp, closePeer, errCh
-}*/
-
-// testServer represents a server object for testing with necessary auxiliary fields.
-type testServer struct {
- clock mclock.Clock
- backend *backends.SimulatedBackend
- db ethdb.Database
- peer *testPeer
- handler *serverHandler
-
- chtIndexer *core.ChainIndexer
- bloomIndexer *core.ChainIndexer
- bloomTrieIndexer *core.ChainIndexer
-}
-
-// newRawPeer creates a new client peer connects to the server and do the handshake.
-func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*testPeer, func(), <-chan error) {
- // Create a message pipe to communicate through
- app, net := p2p.MsgPipe()
-
- // Generate a random id and create the peer
- var id enode.ID
- rand.Read(id[:])
- peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
-
- // Start the peer on a new thread
- errCh := make(chan error, 1)
- go func() {
- select {
- case <-server.handler.closeCh:
- errCh <- p2p.DiscQuitting
- case errCh <- server.handler.handle(peer):
- }
- }()
- tp := &testPeer{
- app: app,
- net: net,
- cpeer: peer,
- }
- var (
- genesis = server.handler.blockchain.Genesis()
- head = server.handler.blockchain.CurrentHeader()
- td = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64())
- )
- forkID := forkid.NewID(server.handler.blockchain.Config(), genesis, head.Number.Uint64(), head.Time)
- tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID)
-
- // Ensure the connection is established or exits when any error occurs
- for {
- select {
- case <-errCh:
- return nil, nil, nil
- default:
- }
- if peer.serving.Load() {
- break
- }
- time.Sleep(50 * time.Millisecond)
- }
- closePeer := func() {
- tp.cpeer.close()
- tp.close()
- }
- return tp, closePeer, errCh
-}
-
-// testnetConfig wraps all the configurations for testing network.
-type testnetConfig struct {
- blocks int
- protocol int
- indexFn indexerCallback
- simClock bool
- connect bool
- nopruning bool
-}
-
-func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testClient, func()) {
- var (
- sdb = rawdb.NewMemoryDatabase()
- cdb = rawdb.NewMemoryDatabase()
- speers = newServerPeerSet()
- )
- var clock mclock.Clock = &mclock.System{}
- if config.simClock {
- clock = &mclock.Simulated{}
- }
- dist := newRequestDistributor(speers, clock)
- rm := newRetrieveManager(speers, dist, func() time.Duration { return time.Millisecond * 500 })
- odr := NewLesOdr(cdb, light.TestClientIndexerConfig, speers, rm)
-
- sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig, true)
- cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, config.nopruning)
-
- scIndexer, sbIndexer, sbtIndexer := sindexers[0], sindexers[1], sindexers[2]
- ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2]
- odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer)
-
- server, b, serverClose := newTestServerHandler(config.blocks, sindexers, sdb, clock)
- client, clientClose := newTestClientHandler(b, odr, cIndexers, cdb, speers)
-
- scIndexer.Start(server.blockchain)
- sbIndexer.Start(server.blockchain)
- ccIndexer.Start(client.backend.blockchain)
- cbIndexer.Start(client.backend.blockchain)
-
- if config.indexFn != nil {
- config.indexFn(scIndexer, sbIndexer, sbtIndexer)
- }
- var (
- err error
- speer, cpeer *testPeer
- )
- if config.connect {
- done := make(chan struct{})
- cpeer, speer, err = newTestPeerPair("peer", config.protocol, server, client, false)
- if err != nil {
- t.Fatalf("Failed to connect testing peers %v", err)
- }
- select {
- case <-done:
- case <-time.After(10 * time.Second):
- t.Fatal("test peer did not connect and sync within 3s")
- }
- }
- s := &testServer{
- clock: clock,
- backend: b,
- db: sdb,
- peer: cpeer,
- handler: server,
- chtIndexer: scIndexer,
- bloomIndexer: sbIndexer,
- bloomTrieIndexer: sbtIndexer,
- }
- c := &testClient{
- clock: clock,
- db: cdb,
- peer: speer,
- handler: client,
- chtIndexer: ccIndexer,
- bloomIndexer: cbIndexer,
- bloomTrieIndexer: cbtIndexer,
- }
- teardown := func() {
- if config.connect {
- speer.close()
- cpeer.close()
- cpeer.cpeer.close()
- speer.speer.close()
- }
- ccIndexer.Close()
- cbIndexer.Close()
- scIndexer.Close()
- sbIndexer.Close()
- dist.close()
- serverClose()
- b.Close()
- clientClose()
- }
- return s, c, teardown
-}
-
-// NewFuzzerPeer creates a client peer for test purposes, and also returns
-// a function to close the peer: this is needed to avoid goroutine leaks in the
-// exec queue.
-func NewFuzzerPeer(version int) (p *clientPeer, closer func()) {
- p = newClientPeer(version, 0, p2p.NewPeer(enode.ID{}, "", nil), nil)
- return p, func() { p.peerCommons.close() }
-}
diff --git a/les/txrelay.go b/les/txrelay.go
deleted file mode 100644
index 40a51fb76..000000000
--- a/les/txrelay.go
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
- "context"
- "math/rand"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-type lesTxRelay struct {
- txSent map[common.Hash]*types.Transaction
- txPending map[common.Hash]struct{}
- peerList []*serverPeer
- peerStartPos int
- lock sync.Mutex
- stop chan struct{}
-
- retriever *retrieveManager
-}
-
-func newLesTxRelay(ps *serverPeerSet, retriever *retrieveManager) *lesTxRelay {
- r := &lesTxRelay{
- txSent: make(map[common.Hash]*types.Transaction),
- txPending: make(map[common.Hash]struct{}),
- retriever: retriever,
- stop: make(chan struct{}),
- }
- ps.subscribe(r)
- return r
-}
-
-func (ltrx *lesTxRelay) Stop() {
- close(ltrx.stop)
-}
-
-func (ltrx *lesTxRelay) registerPeer(p *serverPeer) {
- ltrx.lock.Lock()
- defer ltrx.lock.Unlock()
-
- // Short circuit if the peer is announce only.
- if p.onlyAnnounce {
- return
- }
- ltrx.peerList = append(ltrx.peerList, p)
-}
-
-func (ltrx *lesTxRelay) unregisterPeer(p *serverPeer) {
- ltrx.lock.Lock()
- defer ltrx.lock.Unlock()
-
- for i, peer := range ltrx.peerList {
- if peer == p {
- // Remove from the peer list
- ltrx.peerList = append(ltrx.peerList[:i], ltrx.peerList[i+1:]...)
- return
- }
- }
-}
-
-// send sends a list of transactions to at most a given number of peers.
-func (ltrx *lesTxRelay) send(txs types.Transactions, count int) {
- sendTo := make(map[*serverPeer]types.Transactions)
-
- ltrx.peerStartPos++ // rotate the starting position of the peer list
- if ltrx.peerStartPos >= len(ltrx.peerList) {
- ltrx.peerStartPos = 0
- }
-
- for _, tx := range txs {
- hash := tx.Hash()
- _, ok := ltrx.txSent[hash]
- if !ok {
- ltrx.txSent[hash] = tx
- ltrx.txPending[hash] = struct{}{}
- }
- if len(ltrx.peerList) > 0 {
- cnt := count
- pos := ltrx.peerStartPos
- for {
- peer := ltrx.peerList[pos]
- sendTo[peer] = append(sendTo[peer], tx)
- cnt--
- if cnt == 0 {
- break // sent it to the desired number of peers
- }
- pos++
- if pos == len(ltrx.peerList) {
- pos = 0
- }
- if pos == ltrx.peerStartPos {
- break // tried all available peers
- }
- }
- }
- }
-
- for p, list := range sendTo {
- pp := p
- ll := list
- enc, _ := rlp.EncodeToBytes(ll)
-
- reqID := rand.Uint64()
- rq := &distReq{
- getCost: func(dp distPeer) uint64 {
- peer := dp.(*serverPeer)
- return peer.getTxRelayCost(len(ll), len(enc))
- },
- canSend: func(dp distPeer) bool {
- return !dp.(*serverPeer).onlyAnnounce && dp.(*serverPeer) == pp
- },
- request: func(dp distPeer) func() {
- peer := dp.(*serverPeer)
- cost := peer.getTxRelayCost(len(ll), len(enc))
- peer.fcServer.QueuedRequest(reqID, cost)
- return func() { peer.sendTxs(reqID, len(ll), enc) }
- },
- }
- go ltrx.retriever.retrieve(context.Background(), reqID, rq, func(p distPeer, msg *Msg) error { return nil }, ltrx.stop)
- }
-}
-
-func (ltrx *lesTxRelay) Send(txs types.Transactions) {
- ltrx.lock.Lock()
- defer ltrx.lock.Unlock()
-
- ltrx.send(txs, 3)
-}
-
-func (ltrx *lesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) {
- ltrx.lock.Lock()
- defer ltrx.lock.Unlock()
-
- for _, hash := range mined {
- delete(ltrx.txPending, hash)
- }
-
- for _, hash := range rollback {
- ltrx.txPending[hash] = struct{}{}
- }
-
- if len(ltrx.txPending) > 0 {
- txs := make(types.Transactions, len(ltrx.txPending))
- i := 0
- for hash := range ltrx.txPending {
- txs[i] = ltrx.txSent[hash]
- i++
- }
- ltrx.send(txs, 1)
- }
-}
-
-func (ltrx *lesTxRelay) Discard(hashes []common.Hash) {
- ltrx.lock.Lock()
- defer ltrx.lock.Unlock()
-
- for _, hash := range hashes {
- delete(ltrx.txSent, hash)
- delete(ltrx.txPending, hash)
- }
-}
diff --git a/les/utils/exec_queue.go b/les/utils/exec_queue.go
deleted file mode 100644
index 5942b06ec..000000000
--- a/les/utils/exec_queue.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2020 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 .
-
-package utils
-
-import "sync"
-
-// ExecQueue implements a queue that executes function calls in a single thread,
-// in the same order as they have been queued.
-type ExecQueue struct {
- mu sync.Mutex
- cond *sync.Cond
- funcs []func()
- closeWait chan struct{}
-}
-
-// NewExecQueue creates a new execution Queue.
-func NewExecQueue(capacity int) *ExecQueue {
- q := &ExecQueue{funcs: make([]func(), 0, capacity)}
- q.cond = sync.NewCond(&q.mu)
- go q.loop()
- return q
-}
-
-func (q *ExecQueue) loop() {
- for f := q.waitNext(false); f != nil; f = q.waitNext(true) {
- f()
- }
- close(q.closeWait)
-}
-
-func (q *ExecQueue) waitNext(drop bool) (f func()) {
- q.mu.Lock()
- if drop && len(q.funcs) > 0 {
- // Remove the function that just executed. We do this here instead of when
- // dequeuing so len(q.funcs) includes the function that is running.
- q.funcs = append(q.funcs[:0], q.funcs[1:]...)
- }
- for !q.isClosed() {
- if len(q.funcs) > 0 {
- f = q.funcs[0]
- break
- }
- q.cond.Wait()
- }
- q.mu.Unlock()
- return f
-}
-
-func (q *ExecQueue) isClosed() bool {
- return q.closeWait != nil
-}
-
-// CanQueue returns true if more function calls can be added to the execution Queue.
-func (q *ExecQueue) CanQueue() bool {
- q.mu.Lock()
- ok := !q.isClosed() && len(q.funcs) < cap(q.funcs)
- q.mu.Unlock()
- return ok
-}
-
-// Queue adds a function call to the execution Queue. Returns true if successful.
-func (q *ExecQueue) Queue(f func()) bool {
- q.mu.Lock()
- ok := !q.isClosed() && len(q.funcs) < cap(q.funcs)
- if ok {
- q.funcs = append(q.funcs, f)
- q.cond.Signal()
- }
- q.mu.Unlock()
- return ok
-}
-
-// Clear drops all queued functions.
-func (q *ExecQueue) Clear() {
- q.mu.Lock()
- q.funcs = q.funcs[:0]
- q.mu.Unlock()
-}
-
-// Quit stops the exec Queue.
-//
-// Quit waits for the current execution to finish before returning.
-func (q *ExecQueue) Quit() {
- q.mu.Lock()
- if !q.isClosed() {
- q.closeWait = make(chan struct{})
- q.cond.Signal()
- }
- q.mu.Unlock()
- <-q.closeWait
-}
diff --git a/les/utils/exec_queue_test.go b/les/utils/exec_queue_test.go
deleted file mode 100644
index 98601c448..000000000
--- a/les/utils/exec_queue_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2017 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 .
-
-package utils
-
-import "testing"
-
-func TestExecQueue(t *testing.T) {
- var (
- N = 10000
- q = NewExecQueue(N)
- counter int
- execd = make(chan int)
- testexit = make(chan struct{})
- )
- defer q.Quit()
- defer close(testexit)
-
- check := func(state string, wantOK bool) {
- c := counter
- counter++
- qf := func() {
- select {
- case execd <- c:
- case <-testexit:
- }
- }
- if q.CanQueue() != wantOK {
- t.Fatalf("CanQueue() == %t for %s", !wantOK, state)
- }
- if q.Queue(qf) != wantOK {
- t.Fatalf("Queue() == %t for %s", !wantOK, state)
- }
- }
-
- for i := 0; i < N; i++ {
- check("queue below cap", true)
- }
- check("full queue", false)
- for i := 0; i < N; i++ {
- if c := <-execd; c != i {
- t.Fatal("execution out of order")
- }
- }
- q.Quit()
- check("closed queue", false)
-}
diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go
deleted file mode 100644
index 099b61d05..000000000
--- a/les/utils/expiredvalue.go
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2020 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 .
-
-package utils
-
-import (
- "math"
- "sync"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-// ExpiredValue is a scalar value that is continuously expired (decreased
-// exponentially) based on the provided logarithmic expiration offset value.
-//
-// The formula for value calculation is: base*2^(exp-logOffset). In order to
-// simplify the calculation of ExpiredValue, its value is expressed in the form
-// of an exponent with a base of 2.
-//
-// Also here is a trick to reduce a lot of calculations. In theory, when a value X
-// decays over time and then a new value Y is added, the final result should be
-// X*2^(exp-logOffset)+Y. However it's very hard to represent in memory.
-// So the trick is using the idea of inflation instead of exponential decay. At this
-// moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential
-// decay when we actually want to calculate the value.
-//
-// e.g.
-// t0: V = 100
-// t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient
-// t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40
-type ExpiredValue struct {
- Base, Exp uint64 // rlp encoding works by default
-}
-
-// ExpirationFactor is calculated from logOffset. 1 <= Factor < 2 and Factor*2^Exp
-// describes the multiplier applicable for additions and the divider for readouts.
-// If logOffset changes slowly then it saves some expensive operations to not calculate
-// them for each addition and readout but cache this intermediate form for some time.
-// It is also useful for structures where multiple values are expired with the same
-// Expirer.
-type ExpirationFactor struct {
- Exp uint64
- Factor float64
-}
-
-// ExpFactor calculates ExpirationFactor based on logOffset
-func ExpFactor(logOffset Fixed64) ExpirationFactor {
- return ExpirationFactor{Exp: logOffset.ToUint64(), Factor: logOffset.Fraction().Pow2()}
-}
-
-// Value calculates the expired value based on a floating point base and integer
-// power-of-2 exponent. This function should be used by multi-value expired structures.
-func (e ExpirationFactor) Value(base float64, exp uint64) float64 {
- return base / e.Factor * math.Pow(2, float64(int64(exp-e.Exp)))
-}
-
-// Value calculates the value at the given moment.
-func (e ExpiredValue) Value(logOffset Fixed64) uint64 {
- offset := Uint64ToFixed64(e.Exp) - logOffset
- return uint64(float64(e.Base) * offset.Pow2())
-}
-
-// Add adds a signed value at the given moment
-func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 {
- integer, frac := logOffset.ToUint64(), logOffset.Fraction()
- factor := frac.Pow2()
- base := factor * float64(amount)
- if integer < e.Exp {
- base /= math.Pow(2, float64(e.Exp-integer))
- }
- if integer > e.Exp {
- e.Base >>= (integer - e.Exp)
- e.Exp = integer
- }
- if base >= 0 || uint64(-base) <= e.Base {
- // The conversion from negative float64 to
- // uint64 is undefined in golang, and doesn't
- // work with ARMv8. More details at:
- // https://github.com/golang/go/issues/43047
- if base >= 0 {
- e.Base += uint64(base)
- } else {
- e.Base -= uint64(-base)
- }
- return amount
- }
- net := int64(-float64(e.Base) / factor)
- e.Base = 0
- return net
-}
-
-// AddExp adds another ExpiredValue
-func (e *ExpiredValue) AddExp(a ExpiredValue) {
- if e.Exp > a.Exp {
- a.Base >>= (e.Exp - a.Exp)
- }
- if e.Exp < a.Exp {
- e.Base >>= (a.Exp - e.Exp)
- e.Exp = a.Exp
- }
- e.Base += a.Base
-}
-
-// SubExp subtracts another ExpiredValue
-func (e *ExpiredValue) SubExp(a ExpiredValue) {
- if e.Exp > a.Exp {
- a.Base >>= (e.Exp - a.Exp)
- }
- if e.Exp < a.Exp {
- e.Base >>= (a.Exp - e.Exp)
- e.Exp = a.Exp
- }
- if e.Base > a.Base {
- e.Base -= a.Base
- } else {
- e.Base = 0
- }
-}
-
-// IsZero returns true if the value is zero
-func (e *ExpiredValue) IsZero() bool {
- return e.Base == 0
-}
-
-// LinearExpiredValue is very similar with the expiredValue which the value
-// will continuously expired. But the different part is it's expired linearly.
-type LinearExpiredValue struct {
- Offset uint64 // The latest time offset
- Val uint64 // The remaining value, can never be negative
- Rate mclock.AbsTime `rlp:"-"` // Expiration rate(by nanosecond), will ignored by RLP
-}
-
-// Value calculates the value at the given moment. This function always has the
-// assumption that the given timestamp shouldn't less than the recorded one.
-func (e LinearExpiredValue) Value(now mclock.AbsTime) uint64 {
- offset := uint64(now / e.Rate)
- if e.Offset < offset {
- diff := offset - e.Offset
- if e.Val >= diff {
- e.Val -= diff
- } else {
- e.Val = 0
- }
- }
- return e.Val
-}
-
-// Add adds a signed value at the given moment. This function always has the
-// assumption that the given timestamp shouldn't less than the recorded one.
-func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 {
- offset := uint64(now / e.Rate)
- if e.Offset < offset {
- diff := offset - e.Offset
- if e.Val >= diff {
- e.Val -= diff
- } else {
- e.Val = 0
- }
- e.Offset = offset
- }
- if amount < 0 && uint64(-amount) > e.Val {
- e.Val = 0
- } else {
- e.Val = uint64(int64(e.Val) + amount)
- }
- return e.Val
-}
-
-// ValueExpirer controls value expiration rate
-type ValueExpirer interface {
- SetRate(now mclock.AbsTime, rate float64)
- SetLogOffset(now mclock.AbsTime, logOffset Fixed64)
- LogOffset(now mclock.AbsTime) Fixed64
-}
-
-// Expirer changes logOffset with a linear rate which can be changed during operation.
-// It is not thread safe, if access by multiple goroutines is needed then it should be
-// encapsulated into a locked structure.
-// Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset
-// is thread safe.
-type Expirer struct {
- lock sync.RWMutex
- logOffset Fixed64
- rate float64
- lastUpdate mclock.AbsTime
-}
-
-// SetRate changes the expiration rate which is the inverse of the time constant in
-// nanoseconds.
-func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) {
- e.lock.Lock()
- defer e.lock.Unlock()
-
- dt := now - e.lastUpdate
- if dt > 0 {
- e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate)
- }
- e.lastUpdate = now
- e.rate = rate
-}
-
-// SetLogOffset sets logOffset instantly.
-func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) {
- e.lock.Lock()
- defer e.lock.Unlock()
-
- e.lastUpdate = now
- e.logOffset = logOffset
-}
-
-// LogOffset returns the current logarithmic offset.
-func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 {
- e.lock.RLock()
- defer e.lock.RUnlock()
-
- dt := now - e.lastUpdate
- if dt <= 0 {
- return e.logOffset
- }
- return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate)
-}
-
-// fixedFactor is the fixed point multiplier factor used by Fixed64.
-const fixedFactor = 0x1000000
-
-// Fixed64 implements 64-bit fixed point arithmetic functions.
-type Fixed64 int64
-
-// Uint64ToFixed64 converts uint64 integer to Fixed64 format.
-func Uint64ToFixed64(f uint64) Fixed64 {
- return Fixed64(f * fixedFactor)
-}
-
-// Float64ToFixed64 converts float64 to Fixed64 format.
-func Float64ToFixed64(f float64) Fixed64 {
- return Fixed64(f * fixedFactor)
-}
-
-// ToUint64 converts Fixed64 format to uint64.
-func (f64 Fixed64) ToUint64() uint64 {
- return uint64(f64) / fixedFactor
-}
-
-// Fraction returns the fractional part of a Fixed64 value.
-func (f64 Fixed64) Fraction() Fixed64 {
- return f64 % fixedFactor
-}
-
-var (
- logToFixedFactor = float64(fixedFactor) / math.Log(2)
- fixedToLogFactor = math.Log(2) / float64(fixedFactor)
-)
-
-// Pow2 returns the base 2 power of the fixed point value.
-func (f64 Fixed64) Pow2() float64 {
- return math.Exp(float64(f64) * fixedToLogFactor)
-}
diff --git a/les/utils/expiredvalue_test.go b/les/utils/expiredvalue_test.go
deleted file mode 100644
index 1c751d8cc..000000000
--- a/les/utils/expiredvalue_test.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2020 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 .
-
-package utils
-
-import (
- "testing"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-func TestValueExpiration(t *testing.T) {
- var cases = []struct {
- input ExpiredValue
- timeOffset Fixed64
- expect uint64
- }{
- {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 128},
- {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 64},
- {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(2), 32},
- {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(2), 128},
- {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(3), 64},
- }
- for _, c := range cases {
- if got := c.input.Value(c.timeOffset); got != c.expect {
- t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
- }
- }
-}
-
-func TestValueAddition(t *testing.T) {
- var cases = []struct {
- input ExpiredValue
- addend int64
- timeOffset Fixed64
- expect uint64
- expectNet int64
- }{
- // Addition
- {ExpiredValue{Base: 128, Exp: 0}, 128, Uint64ToFixed64(0), 256, 128},
- {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(0), 640, 128},
-
- // Addition with offset
- {ExpiredValue{Base: 128, Exp: 0}, 128, Uint64ToFixed64(1), 192, 128},
- {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(1), 384, 128},
- {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(3), 192, 128},
-
- // Subtraction
- {ExpiredValue{Base: 128, Exp: 0}, -64, Uint64ToFixed64(0), 64, -64},
- {ExpiredValue{Base: 128, Exp: 0}, -128, Uint64ToFixed64(0), 0, -128},
- {ExpiredValue{Base: 128, Exp: 0}, -192, Uint64ToFixed64(0), 0, -128},
-
- // Subtraction with offset
- {ExpiredValue{Base: 128, Exp: 0}, -64, Uint64ToFixed64(1), 0, -64},
- {ExpiredValue{Base: 128, Exp: 0}, -128, Uint64ToFixed64(1), 0, -64},
- {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(1), 128, -128},
- {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(2), 0, -128},
- }
- for _, c := range cases {
- if net := c.input.Add(c.addend, c.timeOffset); net != c.expectNet {
- t.Fatalf("Net amount mismatch, want=%d, got=%d", c.expectNet, net)
- }
- if got := c.input.Value(c.timeOffset); got != c.expect {
- t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
- }
- }
-}
-
-func TestExpiredValueAddition(t *testing.T) {
- var cases = []struct {
- input ExpiredValue
- another ExpiredValue
- timeOffset Fixed64
- expect uint64
- }{
- {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 256},
- {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 384},
- {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 1}, Uint64ToFixed64(0), 384},
- {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 128},
- }
- for _, c := range cases {
- c.input.AddExp(c.another)
- if got := c.input.Value(c.timeOffset); got != c.expect {
- t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
- }
- }
-}
-
-func TestExpiredValueSubtraction(t *testing.T) {
- var cases = []struct {
- input ExpiredValue
- another ExpiredValue
- timeOffset Fixed64
- expect uint64
- }{
- {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 0},
- {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 1}, Uint64ToFixed64(0), 0},
- {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 128},
- {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 64},
- }
- for _, c := range cases {
- c.input.SubExp(c.another)
- if got := c.input.Value(c.timeOffset); got != c.expect {
- t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got)
- }
- }
-}
-
-func TestLinearExpiredValue(t *testing.T) {
- var cases = []struct {
- value LinearExpiredValue
- now mclock.AbsTime
- expect uint64
- }{
- {LinearExpiredValue{
- Offset: 0,
- Val: 0,
- Rate: mclock.AbsTime(1),
- }, 0, 0},
-
- {LinearExpiredValue{
- Offset: 1,
- Val: 1,
- Rate: mclock.AbsTime(1),
- }, 0, 1},
-
- {LinearExpiredValue{
- Offset: 1,
- Val: 1,
- Rate: mclock.AbsTime(1),
- }, mclock.AbsTime(2), 0},
-
- {LinearExpiredValue{
- Offset: 1,
- Val: 1,
- Rate: mclock.AbsTime(1),
- }, mclock.AbsTime(3), 0},
- }
- for _, c := range cases {
- if value := c.value.Value(c.now); value != c.expect {
- t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value)
- }
- }
-}
-
-func TestLinearExpiredAddition(t *testing.T) {
- var cases = []struct {
- value LinearExpiredValue
- amount int64
- now mclock.AbsTime
- expect uint64
- }{
- {LinearExpiredValue{
- Offset: 0,
- Val: 0,
- Rate: mclock.AbsTime(1),
- }, -1, 0, 0},
-
- {LinearExpiredValue{
- Offset: 1,
- Val: 1,
- Rate: mclock.AbsTime(1),
- }, -1, 0, 0},
-
- {LinearExpiredValue{
- Offset: 1,
- Val: 2,
- Rate: mclock.AbsTime(1),
- }, -1, mclock.AbsTime(2), 0},
-
- {LinearExpiredValue{
- Offset: 1,
- Val: 2,
- Rate: mclock.AbsTime(1),
- }, -2, mclock.AbsTime(2), 0},
- }
- for _, c := range cases {
- if value := c.value.Add(c.amount, c.now); value != c.expect {
- t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value)
- }
- }
-}
diff --git a/les/utils/limiter.go b/les/utils/limiter.go
deleted file mode 100644
index 70b7ff64f..000000000
--- a/les/utils/limiter.go
+++ /dev/null
@@ -1,398 +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 .
-
-package utils
-
-import (
- "sync"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
- "golang.org/x/exp/slices"
-)
-
-const maxSelectionWeight = 1000000000 // maximum selection weight of each individual node/address group
-
-// Limiter protects a network request serving mechanism from denial-of-service attacks.
-// It limits the total amount of resources used for serving requests while ensuring that
-// the most valuable connections always have a reasonable chance of being served.
-type Limiter struct {
- lock sync.Mutex
- cond *sync.Cond
- quit bool
-
- nodes map[enode.ID]*nodeQueue
- addresses map[string]*addressGroup
- addressSelect, valueSelect *WeightedRandomSelect
- maxValue float64
- maxCost, sumCost, sumCostLimit uint
- selectAddressNext bool
-}
-
-// nodeQueue represents queued requests coming from a single node ID
-type nodeQueue struct {
- queue []request // always nil if penaltyCost != 0
- id enode.ID
- address string
- value float64
- flatWeight, valueWeight uint64 // current selection weights in the address/value selectors
- sumCost uint // summed cost of requests queued by the node
- penaltyCost uint // cumulative cost of dropped requests since last processed request
- groupIndex int
-}
-
-// addressGroup is a group of node IDs that have sent their last requests from the same
-// network address
-type addressGroup struct {
- nodes []*nodeQueue
- nodeSelect *WeightedRandomSelect
- sumFlatWeight, groupWeight uint64
-}
-
-// request represents an incoming request scheduled for processing
-type request struct {
- process chan chan struct{}
- cost uint
-}
-
-// flatWeight distributes weights equally between each active network address
-func flatWeight(item interface{}) uint64 { return item.(*nodeQueue).flatWeight }
-
-// add adds the node queue to the address group. It is the caller's responsibility to
-// add the address group to the address map and the address selector if it wasn't
-// there before.
-func (ag *addressGroup) add(nq *nodeQueue) {
- if nq.groupIndex != -1 {
- panic("added node queue is already in an address group")
- }
- l := len(ag.nodes)
- nq.groupIndex = l
- ag.nodes = append(ag.nodes, nq)
- ag.sumFlatWeight += nq.flatWeight
- ag.groupWeight = ag.sumFlatWeight / uint64(l+1)
- ag.nodeSelect.Update(ag.nodes[l])
-}
-
-// update updates the selection weight of the node queue inside the address group.
-// It is the caller's responsibility to update the group's selection weight in the
-// address selector.
-func (ag *addressGroup) update(nq *nodeQueue, weight uint64) {
- if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq {
- panic("updated node queue is not in this address group")
- }
- ag.sumFlatWeight += weight - nq.flatWeight
- nq.flatWeight = weight
- ag.groupWeight = ag.sumFlatWeight / uint64(len(ag.nodes))
- ag.nodeSelect.Update(nq)
-}
-
-// remove removes the node queue from the address group. It is the caller's responsibility
-// to remove the address group from the address map if it is empty.
-func (ag *addressGroup) remove(nq *nodeQueue) {
- if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq {
- panic("removed node queue is not in this address group")
- }
-
- l := len(ag.nodes) - 1
- if nq.groupIndex != l {
- ag.nodes[nq.groupIndex] = ag.nodes[l]
- ag.nodes[nq.groupIndex].groupIndex = nq.groupIndex
- }
- nq.groupIndex = -1
- ag.nodes = ag.nodes[:l]
- ag.sumFlatWeight -= nq.flatWeight
- if l >= 1 {
- ag.groupWeight = ag.sumFlatWeight / uint64(l)
- } else {
- ag.groupWeight = 0
- }
- ag.nodeSelect.Remove(nq)
-}
-
-// choose selects one of the node queues belonging to the address group
-func (ag *addressGroup) choose() *nodeQueue {
- return ag.nodeSelect.Choose().(*nodeQueue)
-}
-
-// NewLimiter creates a new Limiter
-func NewLimiter(sumCostLimit uint) *Limiter {
- l := &Limiter{
- addressSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*addressGroup).groupWeight }),
- valueSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*nodeQueue).valueWeight }),
- nodes: make(map[enode.ID]*nodeQueue),
- addresses: make(map[string]*addressGroup),
- sumCostLimit: sumCostLimit,
- }
- l.cond = sync.NewCond(&l.lock)
- go l.processLoop()
- return l
-}
-
-// selectionWeights calculates the selection weights of a node for both the address and
-// the value selector. The selection weight depends on the next request cost or the
-// summed cost of recently dropped requests.
-func (l *Limiter) selectionWeights(reqCost uint, value float64) (flatWeight, valueWeight uint64) {
- if value > l.maxValue {
- l.maxValue = value
- }
- if value > 0 {
- // normalize value to <= 1
- value /= l.maxValue
- }
- if reqCost > l.maxCost {
- l.maxCost = reqCost
- }
- relCost := float64(reqCost) / float64(l.maxCost)
- var f float64
- if relCost <= 0.001 {
- f = 1
- } else {
- f = 0.001 / relCost
- }
- f *= maxSelectionWeight
- flatWeight, valueWeight = uint64(f), uint64(f*value)
- if flatWeight == 0 {
- flatWeight = 1
- }
- return
-}
-
-// Add adds a new request to the node queue belonging to the given id. Value belongs
-// to the requesting node. A higher value gives the request a higher chance of being
-// served quickly in case of heavy load or a DDoS attack. Cost is a rough estimate
-// of the serving cost of the request. A lower cost also gives the request a
-// better chance.
-func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) chan chan struct{} {
- l.lock.Lock()
- defer l.lock.Unlock()
-
- process := make(chan chan struct{}, 1)
- if l.quit {
- close(process)
- return process
- }
- if reqCost == 0 {
- reqCost = 1
- }
- if nq, ok := l.nodes[id]; ok {
- if nq.queue != nil {
- nq.queue = append(nq.queue, request{process, reqCost})
- nq.sumCost += reqCost
- nq.value = value
- if address != nq.address {
- // known id sending request from a new address, move to different address group
- l.removeFromGroup(nq)
- l.addToGroup(nq, address)
- }
- } else {
- // already waiting on a penalty, just add to the penalty cost and drop the request
- nq.penaltyCost += reqCost
- l.update(nq)
- close(process)
- return process
- }
- } else {
- nq := &nodeQueue{
- queue: []request{{process, reqCost}},
- id: id,
- value: value,
- sumCost: reqCost,
- groupIndex: -1,
- }
- nq.flatWeight, nq.valueWeight = l.selectionWeights(reqCost, value)
- if len(l.nodes) == 0 {
- l.cond.Signal()
- }
- l.nodes[id] = nq
- if nq.valueWeight != 0 {
- l.valueSelect.Update(nq)
- }
- l.addToGroup(nq, address)
- }
- l.sumCost += reqCost
- if l.sumCost > l.sumCostLimit {
- l.dropRequests()
- }
- return process
-}
-
-// update updates the selection weights of the node queue
-func (l *Limiter) update(nq *nodeQueue) {
- var cost uint
- if nq.queue != nil {
- cost = nq.queue[0].cost
- } else {
- cost = nq.penaltyCost
- }
- flatWeight, valueWeight := l.selectionWeights(cost, nq.value)
- ag := l.addresses[nq.address]
- ag.update(nq, flatWeight)
- l.addressSelect.Update(ag)
- nq.valueWeight = valueWeight
- l.valueSelect.Update(nq)
-}
-
-// addToGroup adds the node queue to the given address group. The group is created if
-// it does not exist yet.
-func (l *Limiter) addToGroup(nq *nodeQueue, address string) {
- nq.address = address
- ag := l.addresses[address]
- if ag == nil {
- ag = &addressGroup{nodeSelect: NewWeightedRandomSelect(flatWeight)}
- l.addresses[address] = ag
- }
- ag.add(nq)
- l.addressSelect.Update(ag)
-}
-
-// removeFromGroup removes the node queue from its address group
-func (l *Limiter) removeFromGroup(nq *nodeQueue) {
- ag := l.addresses[nq.address]
- ag.remove(nq)
- if len(ag.nodes) == 0 {
- delete(l.addresses, nq.address)
- }
- l.addressSelect.Update(ag)
-}
-
-// remove removes the node queue from its address group, the nodes map and the value
-// selector
-func (l *Limiter) remove(nq *nodeQueue) {
- l.removeFromGroup(nq)
- if nq.valueWeight != 0 {
- l.valueSelect.Remove(nq)
- }
- delete(l.nodes, nq.id)
-}
-
-// choose selects the next node queue to process.
-func (l *Limiter) choose() *nodeQueue {
- if l.valueSelect.IsEmpty() || l.selectAddressNext {
- if ag, ok := l.addressSelect.Choose().(*addressGroup); ok {
- l.selectAddressNext = false
- return ag.choose()
- }
- }
- nq, _ := l.valueSelect.Choose().(*nodeQueue)
- l.selectAddressNext = true
- return nq
-}
-
-// processLoop processes requests sequentially
-func (l *Limiter) processLoop() {
- l.lock.Lock()
- defer l.lock.Unlock()
-
- for {
- if l.quit {
- for _, nq := range l.nodes {
- for _, request := range nq.queue {
- close(request.process)
- }
- }
- return
- }
- nq := l.choose()
- if nq == nil {
- l.cond.Wait()
- continue
- }
- if nq.queue != nil {
- request := nq.queue[0]
- nq.queue = nq.queue[1:]
- nq.sumCost -= request.cost
- l.sumCost -= request.cost
- l.lock.Unlock()
- ch := make(chan struct{})
- request.process <- ch
- <-ch
- l.lock.Lock()
- if len(nq.queue) > 0 {
- l.update(nq)
- } else {
- l.remove(nq)
- }
- } else {
- // penalized queue removed, next request will be added to a clean queue
- l.remove(nq)
- }
- }
-}
-
-// Stop stops the processing loop. All queued and future requests are rejected.
-func (l *Limiter) Stop() {
- l.lock.Lock()
- defer l.lock.Unlock()
-
- l.quit = true
- l.cond.Signal()
-}
-
-type dropListItem struct {
- nq *nodeQueue
- priority float64
-}
-
-// dropRequests selects the nodes with the highest queued request cost to selection
-// weight ratio and drops their queued request. The empty node queues stay in the
-// selectors with a low selection weight in order to penalize these nodes.
-func (l *Limiter) dropRequests() {
- var (
- sumValue float64
- list []dropListItem
- )
- for _, nq := range l.nodes {
- sumValue += nq.value
- }
- for _, nq := range l.nodes {
- if nq.sumCost == 0 {
- continue
- }
- w := 1 / float64(len(l.addresses)*len(l.addresses[nq.address].nodes))
- if sumValue > 0 {
- w += nq.value / sumValue
- }
- list = append(list, dropListItem{
- nq: nq,
- priority: w / float64(nq.sumCost),
- })
- }
- slices.SortFunc(list, func(a, b dropListItem) int {
- if a.priority < b.priority {
- return -1
- }
- if a.priority < b.priority {
- return 1
- }
- return 0
- })
- for _, item := range list {
- for _, request := range item.nq.queue {
- close(request.process)
- }
- // make the queue penalized; no more requests are accepted until the node is
- // selected based on the penalty cost which is the cumulative cost of all dropped
- // requests. This ensures that sending excess requests is always penalized
- // and incentivizes the sender to stop for a while if no replies are received.
- item.nq.queue = nil
- item.nq.penaltyCost = item.nq.sumCost
- l.sumCost -= item.nq.sumCost // penalty costs are not counted in sumCost
- item.nq.sumCost = 0
- l.update(item.nq)
- if l.sumCost <= l.sumCostLimit/2 {
- return
- }
- }
-}
diff --git a/les/utils/limiter_test.go b/les/utils/limiter_test.go
deleted file mode 100644
index c031b21de..000000000
--- a/les/utils/limiter_test.go
+++ /dev/null
@@ -1,206 +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 .
-
-package utils
-
-import (
- "crypto/rand"
- "testing"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-const (
- ltTolerance = 0.03
- ltRounds = 7
-)
-
-type (
- ltNode struct {
- addr, id int
- value, exp float64
- cost uint
- reqRate float64
- reqMax, runCount int
- lastTotalCost uint
-
- served, dropped int
- }
-
- ltResult struct {
- node *ltNode
- ch chan struct{}
- }
-
- limTest struct {
- limiter *Limiter
- results chan ltResult
- runCount int
- expCost, totalCost uint
- }
-)
-
-func (lt *limTest) request(n *ltNode) {
- var (
- address string
- id enode.ID
- )
- if n.addr >= 0 {
- address = string([]byte{byte(n.addr)})
- } else {
- var b [32]byte
- rand.Read(b[:])
- address = string(b[:])
- }
- if n.id >= 0 {
- id = enode.ID{byte(n.id)}
- } else {
- rand.Read(id[:])
- }
- lt.runCount++
- n.runCount++
- cch := lt.limiter.Add(id, address, n.value, n.cost)
- go func() {
- lt.results <- ltResult{n, <-cch}
- }()
-}
-
-func (lt *limTest) moreRequests(n *ltNode) {
- maxStart := int(float64(lt.totalCost-n.lastTotalCost) * n.reqRate)
- if maxStart != 0 {
- n.lastTotalCost = lt.totalCost
- }
- for n.reqMax > n.runCount && maxStart > 0 {
- lt.request(n)
- maxStart--
- }
-}
-
-func (lt *limTest) process() {
- res := <-lt.results
- lt.runCount--
- res.node.runCount--
- if res.ch != nil {
- res.node.served++
- if res.node.exp != 0 {
- lt.expCost += res.node.cost
- }
- lt.totalCost += res.node.cost
- close(res.ch)
- } else {
- res.node.dropped++
- }
-}
-
-func TestLimiter(t *testing.T) {
- limTests := [][]*ltNode{
- { // one id from an individual address and two ids from a shared address
- {addr: 0, id: 0, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.5},
- {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
- {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
- },
- { // varying request costs
- {addr: 0, id: 0, value: 0, cost: 10, reqRate: 0.2, reqMax: 1, exp: 0.5},
- {addr: 1, id: 1, value: 0, cost: 3, reqRate: 0.5, reqMax: 1, exp: 0.25},
- {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
- },
- { // different request rate
- {addr: 0, id: 0, value: 0, cost: 1, reqRate: 2, reqMax: 2, exp: 0.5},
- {addr: 1, id: 1, value: 0, cost: 1, reqRate: 10, reqMax: 10, exp: 0.25},
- {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
- },
- { // adding value
- {addr: 0, id: 0, value: 3, cost: 1, reqRate: 1, reqMax: 1, exp: (0.5 + 0.3) / 2},
- {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25 / 2},
- {addr: 1, id: 2, value: 7, cost: 1, reqRate: 1, reqMax: 1, exp: (0.25 + 0.7) / 2},
- },
- { // DoS attack from a single address with a single id
- {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 3, id: 3, value: 0, cost: 1, reqRate: 10, reqMax: 1000000000, exp: 0},
- },
- { // DoS attack from a single address with different ids
- {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 3, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0},
- },
- { // DDoS attack from different addresses with a single id
- {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: -1, id: 3, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0},
- },
- { // DDoS attack from different addresses with different ids
- {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
- {addr: -1, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0},
- },
- }
-
- lt := &limTest{
- limiter: NewLimiter(100),
- results: make(chan ltResult),
- }
- for _, test := range limTests {
- lt.expCost, lt.totalCost = 0, 0
- iterCount := 10000
- for j := 0; j < ltRounds; j++ {
- // try to reach expected target range in multiple rounds with increasing iteration counts
- last := j == ltRounds-1
- for _, n := range test {
- lt.request(n)
- }
- for i := 0; i < iterCount; i++ {
- lt.process()
- for _, n := range test {
- lt.moreRequests(n)
- }
- }
- for lt.runCount > 0 {
- lt.process()
- }
- if spamRatio := 1 - float64(lt.expCost)/float64(lt.totalCost); spamRatio > 0.5*(1+ltTolerance) {
- t.Errorf("Spam ratio too high (%f)", spamRatio)
- }
- fail, success := false, true
- for _, n := range test {
- if n.exp != 0 {
- if n.dropped > 0 {
- t.Errorf("Dropped %d requests of non-spam node", n.dropped)
- fail = true
- }
- r := float64(n.served) * float64(n.cost) / float64(lt.expCost)
- if r < n.exp*(1-ltTolerance) || r > n.exp*(1+ltTolerance) {
- if last {
- // print error only if the target is still not reached in the last round
- t.Errorf("Request ratio (%f) does not match expected value (%f)", r, n.exp)
- }
- success = false
- }
- }
- }
- if fail || success {
- break
- }
- // neither failed nor succeeded; try more iterations to reach probability targets
- iterCount *= 2
- }
- }
- lt.limiter.Stop()
-}
diff --git a/les/utils/timeutils.go b/les/utils/timeutils.go
deleted file mode 100644
index 62a4285d1..000000000
--- a/les/utils/timeutils.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 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 .
-
-package utils
-
-import (
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-type UpdateTimer struct {
- clock mclock.Clock
- lock sync.Mutex
- last mclock.AbsTime
- threshold time.Duration
-}
-
-func NewUpdateTimer(clock mclock.Clock, threshold time.Duration) *UpdateTimer {
- // We don't accept the update threshold less than 0.
- if threshold < 0 {
- return nil
- }
- // Don't panic for lazy users
- if clock == nil {
- clock = mclock.System{}
- }
- return &UpdateTimer{
- clock: clock,
- last: clock.Now(),
- threshold: threshold,
- }
-}
-
-func (t *UpdateTimer) Update(callback func(diff time.Duration) bool) bool {
- return t.UpdateAt(t.clock.Now(), callback)
-}
-
-func (t *UpdateTimer) UpdateAt(at mclock.AbsTime, callback func(diff time.Duration) bool) bool {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- diff := time.Duration(at - t.last)
- if diff < 0 {
- diff = 0
- }
- if diff < t.threshold {
- return false
- }
- if callback(diff) {
- t.last = at
- return true
- }
- return false
-}
diff --git a/les/utils/timeutils_test.go b/les/utils/timeutils_test.go
deleted file mode 100644
index b219d0439..000000000
--- a/les/utils/timeutils_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 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 .
-
-package utils
-
-import (
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-func TestUpdateTimer(t *testing.T) {
- timer := NewUpdateTimer(mclock.System{}, -1)
- if timer != nil {
- t.Fatalf("Create update timer with negative threshold")
- }
- sim := &mclock.Simulated{}
- timer = NewUpdateTimer(sim, time.Second)
- if updated := timer.Update(func(diff time.Duration) bool { return true }); updated {
- t.Fatalf("Update the clock without reaching the threshold")
- }
- sim.Run(time.Second)
- if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated {
- t.Fatalf("Doesn't update the clock when reaching the threshold")
- }
- if updated := timer.UpdateAt(sim.Now().Add(time.Second), func(diff time.Duration) bool { return true }); !updated {
- t.Fatalf("Doesn't update the clock when reaching the threshold")
- }
- timer = NewUpdateTimer(sim, 0)
- if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated {
- t.Fatalf("Doesn't update the clock without threshold limitaion")
- }
-}
diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go
deleted file mode 100644
index 486b00820..000000000
--- a/les/utils/weighted_select.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2016 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 .
-
-package utils
-
-import (
- "math"
- "math/rand"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-type (
- // WeightedRandomSelect is capable of weighted random selection from a set of items
- WeightedRandomSelect struct {
- root *wrsNode
- idx map[WrsItem]int
- wfn WeightFn
- }
- WrsItem interface{}
- WeightFn func(interface{}) uint64
-)
-
-// NewWeightedRandomSelect returns a new WeightedRandomSelect structure
-func NewWeightedRandomSelect(wfn WeightFn) *WeightedRandomSelect {
- return &WeightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[WrsItem]int), wfn: wfn}
-}
-
-// Update updates an item's weight, adds it if it was non-existent or removes it if
-// the new weight is zero. Note that explicitly updating decreasing weights is not necessary.
-func (w *WeightedRandomSelect) Update(item WrsItem) {
- w.setWeight(item, w.wfn(item))
-}
-
-// Remove removes an item from the set
-func (w *WeightedRandomSelect) Remove(item WrsItem) {
- w.setWeight(item, 0)
-}
-
-// IsEmpty returns true if the set is empty
-func (w *WeightedRandomSelect) IsEmpty() bool {
- return w.root.sumCost == 0
-}
-
-// setWeight sets an item's weight to a specific value (removes it if zero)
-func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) {
- if weight > math.MaxInt64-w.root.sumCost {
- // old weight is still included in sumCost, remove and check again
- w.setWeight(item, 0)
- if weight > math.MaxInt64-w.root.sumCost {
- log.Error("WeightedRandomSelect overflow", "sumCost", w.root.sumCost, "new weight", weight)
- weight = math.MaxInt64 - w.root.sumCost
- }
- }
- idx, ok := w.idx[item]
- if ok {
- w.root.setWeight(idx, weight)
- if weight == 0 {
- delete(w.idx, item)
- }
- } else {
- if weight != 0 {
- if w.root.itemCnt == w.root.maxItems {
- // add a new level
- newRoot := &wrsNode{sumCost: w.root.sumCost, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches}
- newRoot.items[0] = w.root
- newRoot.weights[0] = w.root.sumCost
- w.root = newRoot
- }
- w.idx[item] = w.root.insert(item, weight)
- }
- }
-}
-
-// Choose randomly selects an item from the set, with a chance proportional to its
-// current weight. If the weight of the chosen element has been decreased since the
-// last stored value, returns it with a newWeight/oldWeight chance, otherwise just
-// updates its weight and selects another one
-func (w *WeightedRandomSelect) Choose() WrsItem {
- for {
- if w.root.sumCost == 0 {
- return nil
- }
- val := uint64(rand.Int63n(int64(w.root.sumCost)))
- choice, lastWeight := w.root.choose(val)
- weight := w.wfn(choice)
- if weight != lastWeight {
- w.setWeight(choice, weight)
- }
- if weight >= lastWeight || uint64(rand.Int63n(int64(lastWeight))) < weight {
- return choice
- }
- }
-}
-
-const wrsBranches = 8 // max number of branches in the wrsNode tree
-
-// wrsNode is a node of a tree structure that can store WrsItems or further wrsNodes.
-type wrsNode struct {
- items [wrsBranches]interface{}
- weights [wrsBranches]uint64
- sumCost uint64
- level, itemCnt, maxItems int
-}
-
-// insert recursively inserts a new item to the tree and returns the item index
-func (n *wrsNode) insert(item WrsItem, weight uint64) int {
- branch := 0
- for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) {
- branch++
- if branch == wrsBranches {
- panic(nil)
- }
- }
- n.itemCnt++
- n.sumCost += weight
- n.weights[branch] += weight
- if n.level == 0 {
- n.items[branch] = item
- return branch
- }
- var subNode *wrsNode
- if n.items[branch] == nil {
- subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1}
- n.items[branch] = subNode
- } else {
- subNode = n.items[branch].(*wrsNode)
- }
- subIdx := subNode.insert(item, weight)
- return subNode.maxItems*branch + subIdx
-}
-
-// setWeight updates the weight of a certain item (which should exist) and returns
-// the change of the last weight value stored in the tree
-func (n *wrsNode) setWeight(idx int, weight uint64) uint64 {
- if n.level == 0 {
- oldWeight := n.weights[idx]
- n.weights[idx] = weight
- diff := weight - oldWeight
- n.sumCost += diff
- if weight == 0 {
- n.items[idx] = nil
- n.itemCnt--
- }
- return diff
- }
- branchItems := n.maxItems / wrsBranches
- branch := idx / branchItems
- diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight)
- n.weights[branch] += diff
- n.sumCost += diff
- if weight == 0 {
- n.itemCnt--
- }
- return diff
-}
-
-// choose recursively selects an item from the tree and returns it along with its weight
-func (n *wrsNode) choose(val uint64) (WrsItem, uint64) {
- for i, w := range n.weights {
- if val < w {
- if n.level == 0 {
- return n.items[i].(WrsItem), n.weights[i]
- }
- return n.items[i].(*wrsNode).choose(val)
- }
- val -= w
- }
- panic(nil)
-}
diff --git a/les/utils/weighted_select_test.go b/les/utils/weighted_select_test.go
deleted file mode 100644
index 3e1c0ad98..000000000
--- a/les/utils/weighted_select_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2016 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 .
-
-package utils
-
-import (
- "math/rand"
- "testing"
-)
-
-type testWrsItem struct {
- idx int
- widx *int
-}
-
-func testWeight(i interface{}) uint64 {
- t := i.(*testWrsItem)
- w := *t.widx
- if w == -1 || w == t.idx {
- return uint64(t.idx + 1)
- }
- return 0
-}
-
-func TestWeightedRandomSelect(t *testing.T) {
- testFn := func(cnt int) {
- s := NewWeightedRandomSelect(testWeight)
- w := -1
- list := make([]testWrsItem, cnt)
- for i := range list {
- list[i] = testWrsItem{idx: i, widx: &w}
- s.Update(&list[i])
- }
- w = rand.Intn(cnt)
- c := s.Choose()
- if c == nil {
- t.Errorf("expected item, got nil")
- } else {
- if c.(*testWrsItem).idx != w {
- t.Errorf("expected another item")
- }
- }
- w = -2
- if s.Choose() != nil {
- t.Errorf("expected nil, got item")
- }
- }
- testFn(1)
- testFn(10)
- testFn(100)
- testFn(1000)
- testFn(10000)
- testFn(100000)
- testFn(1000000)
-}
diff --git a/les/vflux/client/api.go b/les/vflux/client/api.go
deleted file mode 100644
index 135273ef9..000000000
--- a/les/vflux/client/api.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// PrivateClientAPI implements the vflux client side API
-type PrivateClientAPI struct {
- vt *ValueTracker
-}
-
-// NewPrivateClientAPI creates a PrivateClientAPI
-func NewPrivateClientAPI(vt *ValueTracker) *PrivateClientAPI {
- return &PrivateClientAPI{vt}
-}
-
-// parseNodeStr converts either an enode address or a plain hex node id to enode.ID
-func parseNodeStr(nodeStr string) (enode.ID, error) {
- if id, err := enode.ParseID(nodeStr); err == nil {
- return id, nil
- }
- if node, err := enode.Parse(enode.ValidSchemes, nodeStr); err == nil {
- return node.ID(), nil
- } else {
- return enode.ID{}, err
- }
-}
-
-// RequestStats returns the current contents of the reference request basket, with
-// request values meaning average per request rather than total.
-func (api *PrivateClientAPI) RequestStats() []RequestStatsItem {
- return api.vt.RequestStats()
-}
-
-// Distribution returns a distribution as a series of (X, Y) chart coordinates,
-// where the X axis is the response time in seconds while the Y axis is the amount of
-// service value received with a response time close to the X coordinate.
-// The distribution is optionally normalized to a sum of 1.
-// If nodeStr == "" then the global distribution is returned, otherwise the individual
-// distribution of the specified server node.
-func (api *PrivateClientAPI) Distribution(nodeStr string, normalized bool) (RtDistribution, error) {
- var expFactor utils.ExpirationFactor
- if !normalized {
- expFactor = utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now()))
- }
- if nodeStr == "" {
- return api.vt.RtStats().Distribution(normalized, expFactor), nil
- }
- if id, err := parseNodeStr(nodeStr); err == nil {
- return api.vt.GetNode(id).RtStats().Distribution(normalized, expFactor), nil
- } else {
- return RtDistribution{}, err
- }
-}
-
-// Timeout suggests a timeout value based on either the global distribution or the
-// distribution of the specified node. The parameter is the desired rate of timeouts
-// assuming a similar distribution in the future.
-// Note that the actual timeout should have a sensible minimum bound so that operating
-// under ideal working conditions for a long time (for example, using a local server
-// with very low response times) will not make it very hard for the system to accommodate
-// longer response times in the future.
-func (api *PrivateClientAPI) Timeout(nodeStr string, failRate float64) (float64, error) {
- if nodeStr == "" {
- return float64(api.vt.RtStats().Timeout(failRate)) / float64(time.Second), nil
- }
- if id, err := parseNodeStr(nodeStr); err == nil {
- return float64(api.vt.GetNode(id).RtStats().Timeout(failRate)) / float64(time.Second), nil
- } else {
- return 0, err
- }
-}
-
-// Value calculates the total service value provided either globally or by the specified
-// server node, using a weight function based on the given timeout.
-func (api *PrivateClientAPI) Value(nodeStr string, timeout float64) (float64, error) {
- wt := TimeoutWeights(time.Duration(timeout * float64(time.Second)))
- expFactor := utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now()))
- if nodeStr == "" {
- return api.vt.RtStats().Value(wt, expFactor), nil
- }
- if id, err := parseNodeStr(nodeStr); err == nil {
- return api.vt.GetNode(id).RtStats().Value(wt, expFactor), nil
- } else {
- return 0, err
- }
-}
diff --git a/les/vflux/client/fillset.go b/les/vflux/client/fillset.go
deleted file mode 100644
index 0da850bca..000000000
--- a/les/vflux/client/fillset.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "sync"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-// FillSet tries to read nodes from an input iterator and add them to a node set by
-// setting the specified node state flag(s) until the size of the set reaches the target.
-// Note that other mechanisms (like other FillSet instances reading from different inputs)
-// can also set the same flag(s) and FillSet will always care about the total number of
-// nodes having those flags.
-type FillSet struct {
- lock sync.Mutex
- cond *sync.Cond
- ns *nodestate.NodeStateMachine
- input enode.Iterator
- closed bool
- flags nodestate.Flags
- count, target int
-}
-
-// NewFillSet creates a new FillSet
-func NewFillSet(ns *nodestate.NodeStateMachine, input enode.Iterator, flags nodestate.Flags) *FillSet {
- fs := &FillSet{
- ns: ns,
- input: input,
- flags: flags,
- }
- fs.cond = sync.NewCond(&fs.lock)
-
- ns.SubscribeState(flags, func(n *enode.Node, oldState, newState nodestate.Flags) {
- fs.lock.Lock()
- if oldState.Equals(flags) {
- fs.count--
- }
- if newState.Equals(flags) {
- fs.count++
- }
- if fs.target > fs.count {
- fs.cond.Signal()
- }
- fs.lock.Unlock()
- })
-
- go fs.readLoop()
- return fs
-}
-
-// readLoop keeps reading nodes from the input and setting the specified flags for them
-// whenever the node set size is under the current target
-func (fs *FillSet) readLoop() {
- for {
- fs.lock.Lock()
- for fs.target <= fs.count && !fs.closed {
- fs.cond.Wait()
- }
-
- fs.lock.Unlock()
- if !fs.input.Next() {
- return
- }
- fs.ns.SetState(fs.input.Node(), fs.flags, nodestate.Flags{}, 0)
- }
-}
-
-// SetTarget sets the current target for node set size. If the previous target was not
-// reached and FillSet was still waiting for the next node from the input then the next
-// incoming node will be added to the set regardless of the target. This ensures that
-// all nodes coming from the input are eventually added to the set.
-func (fs *FillSet) SetTarget(target int) {
- fs.lock.Lock()
- defer fs.lock.Unlock()
-
- fs.target = target
- if fs.target > fs.count {
- fs.cond.Signal()
- }
-}
-
-// Close shuts FillSet down and closes the input iterator
-func (fs *FillSet) Close() {
- fs.lock.Lock()
- defer fs.lock.Unlock()
-
- fs.closed = true
- fs.input.Close()
- fs.cond.Signal()
-}
diff --git a/les/vflux/client/fillset_test.go b/les/vflux/client/fillset_test.go
deleted file mode 100644
index 652dcf9f6..000000000
--- a/les/vflux/client/fillset_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "crypto/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-type testIter struct {
- waitCh chan struct{}
- nodeCh chan *enode.Node
- node *enode.Node
-}
-
-func (i *testIter) Next() bool {
- if _, ok := <-i.waitCh; !ok {
- return false
- }
- i.node = <-i.nodeCh
- return true
-}
-
-func (i *testIter) Node() *enode.Node {
- return i.node
-}
-
-func (i *testIter) Close() {
- close(i.waitCh)
-}
-
-func (i *testIter) push() {
- var id enode.ID
- rand.Read(id[:])
- i.nodeCh <- enode.SignNull(new(enr.Record), id)
-}
-
-func (i *testIter) waiting(timeout time.Duration) bool {
- select {
- case i.waitCh <- struct{}{}:
- return true
- case <-time.After(timeout):
- return false
- }
-}
-
-func TestFillSet(t *testing.T) {
- ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup)
- iter := &testIter{
- waitCh: make(chan struct{}),
- nodeCh: make(chan *enode.Node),
- }
- fs := NewFillSet(ns, iter, sfTest1)
- ns.Start()
-
- expWaiting := func(i int, push bool) {
- for ; i > 0; i-- {
- if !iter.waiting(time.Second * 10) {
- t.Fatalf("FillSet not waiting for new nodes")
- }
- if push {
- iter.push()
- }
- }
- }
-
- expNotWaiting := func() {
- if iter.waiting(time.Millisecond * 100) {
- t.Fatalf("FillSet unexpectedly waiting for new nodes")
- }
- }
-
- expNotWaiting()
- fs.SetTarget(3)
- expWaiting(3, true)
- expNotWaiting()
- fs.SetTarget(100)
- expWaiting(2, true)
- expWaiting(1, false)
- // lower the target before the previous one has been filled up
- fs.SetTarget(0)
- iter.push()
- expNotWaiting()
- fs.SetTarget(10)
- expWaiting(4, true)
- expNotWaiting()
- // remove all previously set flags
- ns.ForEach(sfTest1, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
- ns.SetState(node, nodestate.Flags{}, sfTest1, 0)
- })
- // now expect FillSet to fill the set up again with 10 new nodes
- expWaiting(10, true)
- expNotWaiting()
-
- fs.Close()
- ns.Stop()
-}
diff --git a/les/vflux/client/queueiterator.go b/les/vflux/client/queueiterator.go
deleted file mode 100644
index ad3f8df5b..000000000
--- a/les/vflux/client/queueiterator.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "sync"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-// QueueIterator returns nodes from the specified selectable set in the same order as
-// they entered the set.
-type QueueIterator struct {
- lock sync.Mutex
- cond *sync.Cond
-
- ns *nodestate.NodeStateMachine
- queue []*enode.Node
- nextNode *enode.Node
- waitCallback func(bool)
- fifo, closed bool
-}
-
-// NewQueueIterator creates a new QueueIterator. Nodes are selectable if they have all the required
-// and none of the disabled flags set. When a node is selected the selectedFlag is set which also
-// disables further selectability until it is removed or times out.
-func NewQueueIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags nodestate.Flags, fifo bool, waitCallback func(bool)) *QueueIterator {
- qi := &QueueIterator{
- ns: ns,
- fifo: fifo,
- waitCallback: waitCallback,
- }
- qi.cond = sync.NewCond(&qi.lock)
-
- ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) {
- oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags)
- newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags)
- if newMatch == oldMatch {
- return
- }
-
- qi.lock.Lock()
- defer qi.lock.Unlock()
-
- if newMatch {
- qi.queue = append(qi.queue, n)
- } else {
- id := n.ID()
- for i, qn := range qi.queue {
- if qn.ID() == id {
- copy(qi.queue[i:len(qi.queue)-1], qi.queue[i+1:])
- qi.queue = qi.queue[:len(qi.queue)-1]
- break
- }
- }
- }
- qi.cond.Signal()
- })
- return qi
-}
-
-// Next moves to the next selectable node.
-func (qi *QueueIterator) Next() bool {
- qi.lock.Lock()
- if !qi.closed && len(qi.queue) == 0 {
- if qi.waitCallback != nil {
- qi.waitCallback(true)
- }
- for !qi.closed && len(qi.queue) == 0 {
- qi.cond.Wait()
- }
- if qi.waitCallback != nil {
- qi.waitCallback(false)
- }
- }
- if qi.closed {
- qi.nextNode = nil
- qi.lock.Unlock()
- return false
- }
- // Move to the next node in queue.
- if qi.fifo {
- qi.nextNode = qi.queue[0]
- copy(qi.queue[:len(qi.queue)-1], qi.queue[1:])
- qi.queue = qi.queue[:len(qi.queue)-1]
- } else {
- qi.nextNode = qi.queue[len(qi.queue)-1]
- qi.queue = qi.queue[:len(qi.queue)-1]
- }
- qi.lock.Unlock()
- return true
-}
-
-// Close ends the iterator.
-func (qi *QueueIterator) Close() {
- qi.lock.Lock()
- qi.closed = true
- qi.lock.Unlock()
- qi.cond.Signal()
-}
-
-// Node returns the current node.
-func (qi *QueueIterator) Node() *enode.Node {
- qi.lock.Lock()
- defer qi.lock.Unlock()
-
- return qi.nextNode
-}
diff --git a/les/vflux/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go
deleted file mode 100644
index 400d978e1..000000000
--- a/les/vflux/client/queueiterator_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-func testNode(i int) *enode.Node {
- return enode.SignNull(new(enr.Record), testNodeID(i))
-}
-
-func TestQueueIteratorFIFO(t *testing.T) {
- testQueueIterator(t, true)
-}
-
-func TestQueueIteratorLIFO(t *testing.T) {
- testQueueIterator(t, false)
-}
-
-func testQueueIterator(t *testing.T, fifo bool) {
- ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup)
- qi := NewQueueIterator(ns, sfTest2, sfTest3.Or(sfTest4), fifo, nil)
- ns.Start()
- for i := 1; i <= iterTestNodeCount; i++ {
- ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0)
- }
- next := func() int {
- ch := make(chan struct{})
- go func() {
- qi.Next()
- close(ch)
- }()
- select {
- case <-ch:
- case <-time.After(time.Second * 5):
- t.Fatalf("Iterator.Next() timeout")
- }
- node := qi.Node()
- ns.SetState(node, sfTest4, nodestate.Flags{}, 0)
- return testNodeIndex(node.ID())
- }
- exp := func(i int) {
- n := next()
- if n != i {
- t.Errorf("Wrong item returned by iterator (expected %d, got %d)", i, n)
- }
- }
- explist := func(list []int) {
- for i := range list {
- if fifo {
- exp(list[i])
- } else {
- exp(list[len(list)-1-i])
- }
- }
- }
-
- ns.SetState(testNode(1), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(2), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(3), sfTest2, nodestate.Flags{}, 0)
- explist([]int{1, 2, 3})
- ns.SetState(testNode(4), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(5), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(6), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(5), sfTest3, nodestate.Flags{}, 0)
- explist([]int{4, 6})
- ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0)
- ns.SetState(testNode(2), nodestate.Flags{}, sfTest4, 0)
- ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0)
- ns.SetState(testNode(2), sfTest3, nodestate.Flags{}, 0)
- ns.SetState(testNode(2), nodestate.Flags{}, sfTest3, 0)
- explist([]int{1, 3, 2})
- ns.Stop()
-}
diff --git a/les/vflux/client/requestbasket.go b/les/vflux/client/requestbasket.go
deleted file mode 100644
index 55d4b165d..000000000
--- a/les/vflux/client/requestbasket.go
+++ /dev/null
@@ -1,285 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "io"
-
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const basketFactor = 1000000 // reference basket amount and value scale factor
-
-// referenceBasket keeps track of global request usage statistics and the usual prices
-// of each used request type relative to each other. The amounts in the basket are scaled
-// up by basketFactor because of the exponential expiration of long-term statistical data.
-// Values are scaled so that the sum of all amounts and the sum of all values are equal.
-//
-// reqValues represent the internal relative value estimates for each request type and are
-// calculated as value / amount. The average reqValue of all used requests is 1.
-// In other words: SUM(refBasket[type].amount * reqValue[type]) = SUM(refBasket[type].amount)
-type referenceBasket struct {
- basket requestBasket
- reqValues []float64 // contents are read only, new slice is created for each update
-}
-
-// serverBasket collects served request amount and value statistics for a single server.
-//
-// Values are gradually transferred to the global reference basket with a long time
-// constant so that each server basket represents long term usage and price statistics.
-// When the transferred part is added to the reference basket the values are scaled so
-// that their sum equals the total value calculated according to the previous reqValues.
-// The ratio of request values coming from the server basket represent the pricing of
-// the specific server and modify the global estimates with a weight proportional to
-// the amount of service provided by the server.
-type serverBasket struct {
- basket requestBasket
- rvFactor float64
-}
-
-type (
- // requestBasket holds amounts and values for each request type.
- // These values are exponentially expired (see utils.ExpiredValue). The power of 2
- // exponent is applicable to all values within.
- requestBasket struct {
- items []basketItem
- exp uint64
- }
- // basketItem holds amount and value for a single request type. Value is the total
- // relative request value accumulated for served requests while amount is the counter
- // for each request type.
- // Note that these values are both scaled up by basketFactor because of the exponential
- // expiration.
- basketItem struct {
- amount, value uint64
- }
-)
-
-// setExp sets the power of 2 exponent of the structure, scaling base values (the amounts
-// and request values) up or down if necessary.
-func (b *requestBasket) setExp(exp uint64) {
- if exp > b.exp {
- shift := exp - b.exp
- for i, item := range b.items {
- item.amount >>= shift
- item.value >>= shift
- b.items[i] = item
- }
- b.exp = exp
- }
- if exp < b.exp {
- shift := b.exp - exp
- for i, item := range b.items {
- item.amount <<= shift
- item.value <<= shift
- b.items[i] = item
- }
- b.exp = exp
- }
-}
-
-// init initializes a new server basket with the given service vector size (number of
-// different request types)
-func (s *serverBasket) init(size int) {
- if s.basket.items == nil {
- s.basket.items = make([]basketItem, size)
- }
-}
-
-// add adds the give type and amount of requests to the basket. Cost is calculated
-// according to the server's own cost table.
-func (s *serverBasket) add(reqType, reqAmount uint32, reqCost uint64, expFactor utils.ExpirationFactor) {
- s.basket.setExp(expFactor.Exp)
- i := &s.basket.items[reqType]
- i.amount += uint64(float64(uint64(reqAmount)*basketFactor) * expFactor.Factor)
- i.value += uint64(float64(reqCost) * s.rvFactor * expFactor.Factor)
-}
-
-// updateRvFactor updates the request value factor that scales server costs into the
-// local value dimensions.
-func (s *serverBasket) updateRvFactor(rvFactor float64) {
- s.rvFactor = rvFactor
-}
-
-// transfer decreases amounts and values in the basket with the given ratio and
-// moves the removed amounts into a new basket which is returned and can be added
-// to the global reference basket.
-func (s *serverBasket) transfer(ratio float64) requestBasket {
- res := requestBasket{
- items: make([]basketItem, len(s.basket.items)),
- exp: s.basket.exp,
- }
- for i, v := range s.basket.items {
- ta := uint64(float64(v.amount) * ratio)
- tv := uint64(float64(v.value) * ratio)
- if ta > v.amount {
- ta = v.amount
- }
- if tv > v.value {
- tv = v.value
- }
- s.basket.items[i] = basketItem{v.amount - ta, v.value - tv}
- res.items[i] = basketItem{ta, tv}
- }
- return res
-}
-
-// init initializes the reference basket with the given service vector size (number of
-// different request types)
-func (r *referenceBasket) init(size int) {
- r.reqValues = make([]float64, size)
- r.normalize()
- r.updateReqValues()
-}
-
-// add adds the transferred part of a server basket to the reference basket while scaling
-// value amounts so that their sum equals the total value calculated according to the
-// previous reqValues.
-func (r *referenceBasket) add(newBasket requestBasket) {
- r.basket.setExp(newBasket.exp)
- // scale newBasket to match service unit value
- var (
- totalCost uint64
- totalValue float64
- )
- for i, v := range newBasket.items {
- totalCost += v.value
- totalValue += float64(v.amount) * r.reqValues[i]
- }
- if totalCost > 0 {
- // add to reference with scaled values
- scaleValues := totalValue / float64(totalCost)
- for i, v := range newBasket.items {
- r.basket.items[i].amount += v.amount
- r.basket.items[i].value += uint64(float64(v.value) * scaleValues)
- }
- }
- r.updateReqValues()
-}
-
-// updateReqValues recalculates reqValues after adding transferred baskets. Note that
-// values should be normalized first.
-func (r *referenceBasket) updateReqValues() {
- r.reqValues = make([]float64, len(r.reqValues))
- for i, b := range r.basket.items {
- if b.amount > 0 {
- r.reqValues[i] = float64(b.value) / float64(b.amount)
- } else {
- r.reqValues[i] = 0
- }
- }
-}
-
-// normalize ensures that the sum of values equal the sum of amounts in the basket.
-func (r *referenceBasket) normalize() {
- var sumAmount, sumValue uint64
- for _, b := range r.basket.items {
- sumAmount += b.amount
- sumValue += b.value
- }
- add := float64(int64(sumAmount-sumValue)) / float64(sumValue)
- for i, b := range r.basket.items {
- b.value += uint64(int64(float64(b.value) * add))
- r.basket.items[i] = b
- }
-}
-
-// reqValueFactor calculates the request value factor applicable to the server with
-// the given announced request cost list
-func (r *referenceBasket) reqValueFactor(costList []uint64) float64 {
- var (
- totalCost float64
- totalValue uint64
- )
- for i, b := range r.basket.items {
- totalCost += float64(costList[i]) * float64(b.amount) // use floats to avoid overflow
- totalValue += b.value
- }
- if totalCost < 1 {
- return 0
- }
- return float64(totalValue) * basketFactor / totalCost
-}
-
-// EncodeRLP implements rlp.Encoder
-func (b *basketItem) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, []interface{}{b.amount, b.value})
-}
-
-// DecodeRLP implements rlp.Decoder
-func (b *basketItem) DecodeRLP(s *rlp.Stream) error {
- var item struct {
- Amount, Value uint64
- }
- if err := s.Decode(&item); err != nil {
- return err
- }
- b.amount, b.value = item.Amount, item.Value
- return nil
-}
-
-// EncodeRLP implements rlp.Encoder
-func (r *requestBasket) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, []interface{}{r.items, r.exp})
-}
-
-// DecodeRLP implements rlp.Decoder
-func (r *requestBasket) DecodeRLP(s *rlp.Stream) error {
- var enc struct {
- Items []basketItem
- Exp uint64
- }
- if err := s.Decode(&enc); err != nil {
- return err
- }
- r.items, r.exp = enc.Items, enc.Exp
- return nil
-}
-
-// convertMapping converts a basket loaded from the database into the current format.
-// If the available request types and their mapping into the service vector differ from
-// the one used when saving the basket then this function reorders old fields and fills
-// in previously unknown fields by scaling up amounts and values taken from the
-// initialization basket.
-func (r requestBasket) convertMapping(oldMapping, newMapping []string, initBasket requestBasket) requestBasket {
- nameMap := make(map[string]int)
- for i, name := range oldMapping {
- nameMap[name] = i
- }
- rc := requestBasket{items: make([]basketItem, len(newMapping))}
- var scale, oldScale, newScale float64
- for i, name := range newMapping {
- if ii, ok := nameMap[name]; ok {
- rc.items[i] = r.items[ii]
- oldScale += float64(initBasket.items[i].amount) * float64(initBasket.items[i].amount)
- newScale += float64(rc.items[i].amount) * float64(initBasket.items[i].amount)
- }
- }
- if oldScale > 1e-10 {
- scale = newScale / oldScale
- } else {
- scale = 1
- }
- for i, name := range newMapping {
- if _, ok := nameMap[name]; !ok {
- rc.items[i].amount = uint64(float64(initBasket.items[i].amount) * scale)
- rc.items[i].value = uint64(float64(initBasket.items[i].value) * scale)
- }
- }
- return rc
-}
diff --git a/les/vflux/client/requestbasket_test.go b/les/vflux/client/requestbasket_test.go
deleted file mode 100644
index 7c5f87c61..000000000
--- a/les/vflux/client/requestbasket_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "math/rand"
- "testing"
-
- "github.com/ethereum/go-ethereum/les/utils"
-)
-
-func checkU64(t *testing.T, name string, value, exp uint64) {
- if value != exp {
- t.Errorf("Incorrect value for %s: got %d, expected %d", name, value, exp)
- }
-}
-
-func checkF64(t *testing.T, name string, value, exp, tol float64) {
- if value < exp-tol || value > exp+tol {
- t.Errorf("Incorrect value for %s: got %f, expected %f", name, value, exp)
- }
-}
-
-func TestServerBasket(t *testing.T) {
- var s serverBasket
- s.init(2)
- // add some requests with different request value factors
- s.updateRvFactor(1)
- noexp := utils.ExpirationFactor{Factor: 1}
- s.add(0, 1000, 10000, noexp)
- s.add(1, 3000, 60000, noexp)
- s.updateRvFactor(10)
- s.add(0, 4000, 4000, noexp)
- s.add(1, 2000, 4000, noexp)
- s.updateRvFactor(10)
- // check basket contents directly
- checkU64(t, "s.basket[0].amount", s.basket.items[0].amount, 5000*basketFactor)
- checkU64(t, "s.basket[0].value", s.basket.items[0].value, 50000)
- checkU64(t, "s.basket[1].amount", s.basket.items[1].amount, 5000*basketFactor)
- checkU64(t, "s.basket[1].value", s.basket.items[1].value, 100000)
- // transfer 50% of the contents of the basket
- transfer1 := s.transfer(0.5)
- checkU64(t, "transfer1[0].amount", transfer1.items[0].amount, 2500*basketFactor)
- checkU64(t, "transfer1[0].value", transfer1.items[0].value, 25000)
- checkU64(t, "transfer1[1].amount", transfer1.items[1].amount, 2500*basketFactor)
- checkU64(t, "transfer1[1].value", transfer1.items[1].value, 50000)
- // add more requests
- s.updateRvFactor(100)
- s.add(0, 1000, 100, noexp)
- // transfer 25% of the contents of the basket
- transfer2 := s.transfer(0.25)
- checkU64(t, "transfer2[0].amount", transfer2.items[0].amount, (2500+1000)/4*basketFactor)
- checkU64(t, "transfer2[0].value", transfer2.items[0].value, (25000+10000)/4)
- checkU64(t, "transfer2[1].amount", transfer2.items[1].amount, 2500/4*basketFactor)
- checkU64(t, "transfer2[1].value", transfer2.items[1].value, 50000/4)
-}
-
-func TestConvertMapping(t *testing.T) {
- b := requestBasket{items: []basketItem{{3, 3}, {1, 1}, {2, 2}}}
- oldMap := []string{"req3", "req1", "req2"}
- newMap := []string{"req1", "req2", "req3", "req4"}
- init := requestBasket{items: []basketItem{{2, 2}, {4, 4}, {6, 6}, {8, 8}}}
- bc := b.convertMapping(oldMap, newMap, init)
- checkU64(t, "bc[0].amount", bc.items[0].amount, 1)
- checkU64(t, "bc[1].amount", bc.items[1].amount, 2)
- checkU64(t, "bc[2].amount", bc.items[2].amount, 3)
- checkU64(t, "bc[3].amount", bc.items[3].amount, 4) // 8 should be scaled down to 4
-}
-
-func TestReqValueFactor(t *testing.T) {
- var ref referenceBasket
- ref.basket = requestBasket{items: make([]basketItem, 4)}
- for i := range ref.basket.items {
- ref.basket.items[i].amount = uint64(i+1) * basketFactor
- ref.basket.items[i].value = uint64(i+1) * basketFactor
- }
- ref.init(4)
- rvf := ref.reqValueFactor([]uint64{1000, 2000, 3000, 4000})
- // expected value is (1000000+2000000+3000000+4000000) / (1*1000+2*2000+3*3000+4*4000) = 10000000/30000 = 333.333
- checkF64(t, "reqValueFactor", rvf, 333.333, 1)
-}
-
-func TestNormalize(t *testing.T) {
- for cycle := 0; cycle < 100; cycle += 1 {
- // Initialize data for testing
- valueRange, lower := 1000000, 1000000
- ref := referenceBasket{basket: requestBasket{items: make([]basketItem, 10)}}
- for i := 0; i < 10; i++ {
- ref.basket.items[i].amount = uint64(rand.Intn(valueRange) + lower)
- ref.basket.items[i].value = uint64(rand.Intn(valueRange) + lower)
- }
- ref.normalize()
-
- // Check whether SUM(amount) ~= SUM(value)
- var sumAmount, sumValue uint64
- for i := 0; i < 10; i++ {
- sumAmount += ref.basket.items[i].amount
- sumValue += ref.basket.items[i].value
- }
- var epsilon = 0.01
- if float64(sumAmount)*(1+epsilon) < float64(sumValue) || float64(sumAmount)*(1-epsilon) > float64(sumValue) {
- t.Fatalf("Failed to normalize sumAmount: %d sumValue: %d", sumAmount, sumValue)
- }
- }
-}
-
-func TestReqValueAdjustment(t *testing.T) {
- var s1, s2 serverBasket
- s1.init(3)
- s2.init(3)
- cost1 := []uint64{30000, 60000, 90000}
- cost2 := []uint64{100000, 200000, 300000}
- var ref referenceBasket
- ref.basket = requestBasket{items: make([]basketItem, 3)}
- for i := range ref.basket.items {
- ref.basket.items[i].amount = 123 * basketFactor
- ref.basket.items[i].value = 123 * basketFactor
- }
- ref.init(3)
- // initial reqValues are expected to be {1, 1, 1}
- checkF64(t, "reqValues[0]", ref.reqValues[0], 1, 0.01)
- checkF64(t, "reqValues[1]", ref.reqValues[1], 1, 0.01)
- checkF64(t, "reqValues[2]", ref.reqValues[2], 1, 0.01)
- var logOffset utils.Fixed64
- for period := 0; period < 1000; period++ {
- exp := utils.ExpFactor(logOffset)
- s1.updateRvFactor(ref.reqValueFactor(cost1))
- s2.updateRvFactor(ref.reqValueFactor(cost2))
- // throw in random requests into each basket using their internal pricing
- for i := 0; i < 1000; i++ {
- reqType, reqAmount := uint32(rand.Intn(3)), uint32(rand.Intn(10)+1)
- reqCost := uint64(reqAmount) * cost1[reqType]
- s1.add(reqType, reqAmount, reqCost, exp)
- reqType, reqAmount = uint32(rand.Intn(3)), uint32(rand.Intn(10)+1)
- reqCost = uint64(reqAmount) * cost2[reqType]
- s2.add(reqType, reqAmount, reqCost, exp)
- }
- ref.add(s1.transfer(0.1))
- ref.add(s2.transfer(0.1))
- ref.normalize()
- ref.updateReqValues()
- logOffset += utils.Float64ToFixed64(0.1)
- }
- checkF64(t, "reqValues[0]", ref.reqValues[0], 0.5, 0.01)
- checkF64(t, "reqValues[1]", ref.reqValues[1], 1, 0.01)
- checkF64(t, "reqValues[2]", ref.reqValues[2], 1.5, 0.01)
-}
diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go
deleted file mode 100644
index 271d6e022..000000000
--- a/les/vflux/client/serverpool.go
+++ /dev/null
@@ -1,605 +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 .
-
-package client
-
-import (
- "errors"
- "math/rand"
- "reflect"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const (
- minTimeout = time.Millisecond * 500 // minimum request timeout suggested by the server pool
- timeoutRefresh = time.Second * 5 // recalculate timeout if older than this
- dialCost = 10000 // cost of a TCP dial (used for known node selection weight calculation)
- dialWaitStep = 1.5 // exponential multiplier of redial wait time when no value was provided by the server
- queryCost = 500 // cost of a UDP pre-negotiation query
- queryWaitStep = 1.02 // exponential multiplier of redial wait time when no value was provided by the server
- waitThreshold = time.Hour * 2000 // drop node if waiting time is over the threshold
- nodeWeightMul = 1000000 // multiplier constant for node weight calculation
- nodeWeightThreshold = 100 // minimum weight for keeping a node in the known (valuable) set
- minRedialWait = 10 // minimum redial wait time in seconds
- preNegLimit = 5 // maximum number of simultaneous pre-negotiation queries
- warnQueryFails = 20 // number of consecutive UDP query failures before we print a warning
- maxQueryFails = 100 // number of consecutive UDP query failures when then chance of skipping a query reaches 50%
-)
-
-// ServerPool provides a node iterator for dial candidates. The output is a mix of newly discovered
-// nodes, a weighted random selection of known (previously valuable) nodes and trusted/paid nodes.
-type ServerPool struct {
- clock mclock.Clock
- unixTime func() int64
- db ethdb.KeyValueStore
-
- ns *nodestate.NodeStateMachine
- vt *ValueTracker
- mixer *enode.FairMix
- mixSources []enode.Iterator
- dialIterator enode.Iterator
- validSchemes enr.IdentityScheme
- trustedURLs []string
- fillSet *FillSet
- started, queryFails uint32
-
- timeoutLock sync.RWMutex
- timeout time.Duration
- timeWeights ResponseTimeWeights
- timeoutRefreshed mclock.AbsTime
-
- suggestedTimeoutGauge, totalValueGauge metrics.Gauge
- sessionValueMeter metrics.Meter
-}
-
-// nodeHistory keeps track of dial costs which determine node weight together with the
-// service value calculated by ValueTracker.
-type nodeHistory struct {
- dialCost utils.ExpiredValue
- redialWaitStart, redialWaitEnd int64 // unix time (seconds)
-}
-
-type nodeHistoryEnc struct {
- DialCost utils.ExpiredValue
- RedialWaitStart, RedialWaitEnd uint64
-}
-
-// QueryFunc sends a pre-negotiation query and blocks until a response arrives or timeout occurs.
-// It returns 1 if the remote node has confirmed that connection is possible, 0 if not
-// possible and -1 if no response arrived (timeout).
-type QueryFunc func(*enode.Node) int
-
-var (
- clientSetup = &nodestate.Setup{Version: 2}
- sfHasValue = clientSetup.NewPersistentFlag("hasValue")
- sfQuery = clientSetup.NewFlag("query")
- sfCanDial = clientSetup.NewFlag("canDial")
- sfDialing = clientSetup.NewFlag("dialed")
- sfWaitDialTimeout = clientSetup.NewFlag("dialTimeout")
- sfConnected = clientSetup.NewFlag("connected")
- sfRedialWait = clientSetup.NewFlag("redialWait")
- sfAlwaysConnect = clientSetup.NewFlag("alwaysConnect")
- sfDialProcess = nodestate.MergeFlags(sfQuery, sfCanDial, sfDialing, sfConnected, sfRedialWait)
-
- sfiNodeHistory = clientSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}),
- func(field interface{}) ([]byte, error) {
- if n, ok := field.(nodeHistory); ok {
- ne := nodeHistoryEnc{
- DialCost: n.dialCost,
- RedialWaitStart: uint64(n.redialWaitStart),
- RedialWaitEnd: uint64(n.redialWaitEnd),
- }
- enc, err := rlp.EncodeToBytes(&ne)
- return enc, err
- }
- return nil, errors.New("invalid field type")
- },
- func(enc []byte) (interface{}, error) {
- var ne nodeHistoryEnc
- err := rlp.DecodeBytes(enc, &ne)
- n := nodeHistory{
- dialCost: ne.DialCost,
- redialWaitStart: int64(ne.RedialWaitStart),
- redialWaitEnd: int64(ne.RedialWaitEnd),
- }
- return n, err
- },
- )
- sfiNodeWeight = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0)))
- sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{}))
- sfiLocalAddress = clientSetup.NewPersistentField("localAddress", reflect.TypeOf(&enr.Record{}),
- func(field interface{}) ([]byte, error) {
- if enr, ok := field.(*enr.Record); ok {
- enc, err := rlp.EncodeToBytes(enr)
- return enc, err
- }
- return nil, errors.New("invalid field type")
- },
- func(enc []byte) (interface{}, error) {
- var enr enr.Record
- if err := rlp.DecodeBytes(enc, &enr); err != nil {
- return nil, err
- }
- return &enr, nil
- },
- )
-)
-
-// NewServerPool creates a new server pool
-func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query QueryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) {
- s := &ServerPool{
- db: db,
- clock: clock,
- unixTime: func() int64 { return time.Now().Unix() },
- validSchemes: enode.ValidSchemes,
- trustedURLs: trustedURLs,
- vt: NewValueTracker(db, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)),
- ns: nodestate.NewNodeStateMachine(db, []byte(string(dbKey)+"ns:"), clock, clientSetup),
- }
- s.recalTimeout()
- s.mixer = enode.NewFairMix(mixTimeout)
- knownSelector := NewWrsIterator(s.ns, sfHasValue, sfDialProcess, sfiNodeWeight)
- alwaysConnect := NewQueueIterator(s.ns, sfAlwaysConnect, sfDialProcess, true, nil)
- s.mixSources = append(s.mixSources, knownSelector)
- s.mixSources = append(s.mixSources, alwaysConnect)
-
- s.dialIterator = s.mixer
- if query != nil {
- s.dialIterator = s.addPreNegFilter(s.dialIterator, query)
- }
-
- s.ns.SubscribeState(nodestate.MergeFlags(sfWaitDialTimeout, sfConnected), func(n *enode.Node, oldState, newState nodestate.Flags) {
- if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() {
- // dial timeout, no connection
- s.setRedialWait(n, dialCost, dialWaitStep)
- s.ns.SetStateSub(n, nodestate.Flags{}, sfDialing, 0)
- }
- })
-
- return s, &serverPoolIterator{
- dialIterator: s.dialIterator,
- nextFn: func(node *enode.Node) {
- s.ns.Operation(func() {
- s.ns.SetStateSub(node, sfDialing, sfCanDial, 0)
- s.ns.SetStateSub(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10)
- })
- },
- nodeFn: s.DialNode,
- }
-}
-
-type serverPoolIterator struct {
- dialIterator enode.Iterator
- nextFn func(*enode.Node)
- nodeFn func(*enode.Node) *enode.Node
-}
-
-// Next implements enode.Iterator
-func (s *serverPoolIterator) Next() bool {
- if s.dialIterator.Next() {
- s.nextFn(s.dialIterator.Node())
- return true
- }
- return false
-}
-
-// Node implements enode.Iterator
-func (s *serverPoolIterator) Node() *enode.Node {
- return s.nodeFn(s.dialIterator.Node())
-}
-
-// Close implements enode.Iterator
-func (s *serverPoolIterator) Close() {
- s.dialIterator.Close()
-}
-
-// AddMetrics adds metrics to the server pool. Should be called before Start().
-func (s *ServerPool) AddMetrics(
- suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge metrics.Gauge,
- sessionValueMeter, serverDialedMeter metrics.Meter) {
- s.suggestedTimeoutGauge = suggestedTimeoutGauge
- s.totalValueGauge = totalValueGauge
- s.sessionValueMeter = sessionValueMeter
- if serverSelectableGauge != nil {
- s.ns.AddLogMetrics(sfHasValue, sfDialProcess, "selectable", nil, nil, serverSelectableGauge)
- }
- if serverDialedMeter != nil {
- s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil)
- }
- if serverConnectedGauge != nil {
- s.ns.AddLogMetrics(sfConnected, nodestate.Flags{}, "connected", nil, nil, serverConnectedGauge)
- }
-}
-
-// AddSource adds a node discovery source to the server pool (should be called before start)
-func (s *ServerPool) AddSource(source enode.Iterator) {
- if source != nil {
- s.mixSources = append(s.mixSources, source)
- }
-}
-
-// addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query.
-// Nodes that are filtered out and does not appear on the output iterator are put back
-// into redialWait state.
-func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enode.Iterator {
- s.fillSet = NewFillSet(s.ns, input, sfQuery)
- s.ns.SubscribeState(sfDialProcess, func(n *enode.Node, oldState, newState nodestate.Flags) {
- if !newState.Equals(sfQuery) {
- if newState.HasAll(sfQuery) {
- // remove query flag if the node is already somewhere in the dial process
- s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0)
- }
- return
- }
- fails := atomic.LoadUint32(&s.queryFails)
- failMax := fails
- if failMax > maxQueryFails {
- failMax = maxQueryFails
- }
- if rand.Intn(maxQueryFails*2) < int(failMax) {
- // skip pre-negotiation with increasing chance, max 50%
- // this ensures that the client can operate even if UDP is not working at all
- s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10)
- // set canDial before resetting queried so that FillSet will not read more
- // candidates unnecessarily
- s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0)
- return
- }
- go func() {
- q := query(n)
- if q == -1 {
- atomic.AddUint32(&s.queryFails, 1)
- fails++
- if fails%warnQueryFails == 0 {
- // warn if a large number of consecutive queries have failed
- log.Warn("UDP connection queries failed", "count", fails)
- }
- } else {
- atomic.StoreUint32(&s.queryFails, 0)
- }
- s.ns.Operation(func() {
- // we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait
- if q == 1 {
- s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10)
- } else {
- s.setRedialWait(n, queryCost, queryWaitStep)
- }
- s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0)
- })
- }()
- })
- return NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) {
- if waiting {
- s.fillSet.SetTarget(preNegLimit)
- } else {
- s.fillSet.SetTarget(0)
- }
- })
-}
-
-// Start starts the server pool. Note that NodeStateMachine should be started first.
-func (s *ServerPool) Start() {
- s.ns.Start()
- for _, iter := range s.mixSources {
- // add sources to mixer at startup because the mixer instantly tries to read them
- // which should only happen after NodeStateMachine has been started
- s.mixer.AddSource(iter)
- }
- for _, url := range s.trustedURLs {
- if node, err := enode.Parse(s.validSchemes, url); err == nil {
- s.ns.SetState(node, sfAlwaysConnect, nodestate.Flags{}, 0)
- } else {
- log.Error("Invalid trusted server URL", "url", url, "error", err)
- }
- }
- unixTime := s.unixTime()
- s.ns.Operation(func() {
- s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
- s.calculateWeight(node)
- if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime {
- wait := n.redialWaitEnd - unixTime
- lastWait := n.redialWaitEnd - n.redialWaitStart
- if wait > lastWait {
- // if the time until expiration is larger than the last suggested
- // waiting time then the system clock was probably adjusted
- wait = lastWait
- }
- s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second)
- }
- })
- })
- atomic.StoreUint32(&s.started, 1)
-}
-
-// Stop stops the server pool
-func (s *ServerPool) Stop() {
- if s.fillSet != nil {
- s.fillSet.Close()
- }
- s.ns.Operation(func() {
- s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) {
- // recalculate weight of connected nodes in order to update hasValue flag if necessary
- s.calculateWeight(n)
- })
- })
- s.ns.Stop()
- s.vt.Stop()
-}
-
-// RegisterNode implements serverPeerSubscriber
-func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) {
- if atomic.LoadUint32(&s.started) == 0 {
- return nil, errors.New("server pool not started yet")
- }
- nvt := s.vt.Register(node.ID())
- s.ns.Operation(func() {
- s.ns.SetStateSub(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0)
- s.ns.SetFieldSub(node, sfiConnectedStats, nvt.RtStats())
- if node.IP().IsLoopback() {
- s.ns.SetFieldSub(node, sfiLocalAddress, node.Record())
- }
- })
- return nvt, nil
-}
-
-// UnregisterNode implements serverPeerSubscriber
-func (s *ServerPool) UnregisterNode(node *enode.Node) {
- s.ns.Operation(func() {
- s.setRedialWait(node, dialCost, dialWaitStep)
- s.ns.SetStateSub(node, nodestate.Flags{}, sfConnected, 0)
- s.ns.SetFieldSub(node, sfiConnectedStats, nil)
- })
- s.vt.Unregister(node.ID())
-}
-
-// recalTimeout calculates the current recommended timeout. This value is used by
-// the client as a "soft timeout" value. It also affects the service value calculation
-// of individual nodes.
-func (s *ServerPool) recalTimeout() {
- // Use cached result if possible, avoid recalculating too frequently.
- s.timeoutLock.RLock()
- refreshed := s.timeoutRefreshed
- s.timeoutLock.RUnlock()
- now := s.clock.Now()
- if refreshed != 0 && time.Duration(now-refreshed) < timeoutRefresh {
- return
- }
- // Cached result is stale, recalculate a new one.
- rts := s.vt.RtStats()
-
- // Add a fake statistic here. It is an easy way to initialize with some
- // conservative values when the database is new. As soon as we have a
- // considerable amount of real stats this small value won't matter.
- rts.Add(time.Second*2, 10, s.vt.StatsExpFactor())
-
- // Use either 10% failure rate timeout or twice the median response time
- // as the recommended timeout.
- timeout := minTimeout
- if t := rts.Timeout(0.1); t > timeout {
- timeout = t
- }
- if t := rts.Timeout(0.5) * 2; t > timeout {
- timeout = t
- }
- s.timeoutLock.Lock()
- if s.timeout != timeout {
- s.timeout = timeout
- s.timeWeights = TimeoutWeights(s.timeout)
-
- if s.suggestedTimeoutGauge != nil {
- s.suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond))
- }
- if s.totalValueGauge != nil {
- s.totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor())))
- }
- }
- s.timeoutRefreshed = now
- s.timeoutLock.Unlock()
-}
-
-// GetTimeout returns the recommended request timeout.
-func (s *ServerPool) GetTimeout() time.Duration {
- s.recalTimeout()
- s.timeoutLock.RLock()
- defer s.timeoutLock.RUnlock()
- return s.timeout
-}
-
-// getTimeoutAndWeight returns the recommended request timeout as well as the
-// response time weight which is necessary to calculate service value.
-func (s *ServerPool) getTimeoutAndWeight() (time.Duration, ResponseTimeWeights) {
- s.recalTimeout()
- s.timeoutLock.RLock()
- defer s.timeoutLock.RUnlock()
- return s.timeout, s.timeWeights
-}
-
-// addDialCost adds the given amount of dial cost to the node history and returns the current
-// amount of total dial cost
-func (s *ServerPool) addDialCost(n *nodeHistory, amount int64) uint64 {
- logOffset := s.vt.StatsExpirer().LogOffset(s.clock.Now())
- if amount > 0 {
- n.dialCost.Add(amount, logOffset)
- }
- totalDialCost := n.dialCost.Value(logOffset)
- if totalDialCost < dialCost {
- totalDialCost = dialCost
- }
- return totalDialCost
-}
-
-// serviceValue returns the service value accumulated in this session and in total
-func (s *ServerPool) serviceValue(node *enode.Node) (sessionValue, totalValue float64) {
- nvt := s.vt.GetNode(node.ID())
- if nvt == nil {
- return 0, 0
- }
- currentStats := nvt.RtStats()
- _, timeWeights := s.getTimeoutAndWeight()
- expFactor := s.vt.StatsExpFactor()
-
- totalValue = currentStats.Value(timeWeights, expFactor)
- if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(ResponseTimeStats); ok {
- diff := currentStats
- diff.SubStats(&connStats)
- sessionValue = diff.Value(timeWeights, expFactor)
- if s.sessionValueMeter != nil {
- s.sessionValueMeter.Mark(int64(sessionValue))
- }
- }
- return
-}
-
-// updateWeight calculates the node weight and updates the nodeWeight field and the
-// hasValue flag. It also saves the node state if necessary.
-// Note: this function should run inside a NodeStateMachine operation
-func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) {
- weight := uint64(totalValue * nodeWeightMul / float64(totalDialCost))
- if weight >= nodeWeightThreshold {
- s.ns.SetStateSub(node, sfHasValue, nodestate.Flags{}, 0)
- s.ns.SetFieldSub(node, sfiNodeWeight, weight)
- } else {
- s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0)
- s.ns.SetFieldSub(node, sfiNodeWeight, nil)
- s.ns.SetFieldSub(node, sfiNodeHistory, nil)
- s.ns.SetFieldSub(node, sfiLocalAddress, nil)
- }
- s.ns.Persist(node) // saved if node history or hasValue changed
-}
-
-// setRedialWait calculates and sets the redialWait timeout based on the service value
-// and dial cost accumulated during the last session/attempt and in total.
-// The waiting time is raised exponentially if no service value has been received in order
-// to prevent dialing an unresponsive node frequently for a very long time just because it
-// was useful in the past. It can still be occasionally dialed though and once it provides
-// a significant amount of service value again its waiting time is quickly reduced or reset
-// to the minimum.
-// Note: node weight is also recalculated and updated by this function.
-// Note 2: this function should run inside a NodeStateMachine operation
-func (s *ServerPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) {
- n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory)
- sessionValue, totalValue := s.serviceValue(node)
- totalDialCost := s.addDialCost(&n, addDialCost)
-
- // if the current dial session has yielded at least the average value/dial cost ratio
- // then the waiting time should be reset to the minimum. If the session value
- // is below average but still positive then timeout is limited to the ratio of
- // average / current service value multiplied by the minimum timeout. If the attempt
- // was unsuccessful then timeout is raised exponentially without limitation.
- // Note: dialCost is used in the formula below even if dial was not attempted at all
- // because the pre-negotiation query did not return a positive result. In this case
- // the ratio has no meaning anyway and waitFactor is always raised, though in smaller
- // steps because queries are cheaper and therefore we can allow more failed attempts.
- unixTime := s.unixTime()
- plannedTimeout := float64(n.redialWaitEnd - n.redialWaitStart) // last planned redialWait timeout
- var actualWait float64 // actual waiting time elapsed
- if unixTime > n.redialWaitEnd {
- // the planned timeout has elapsed
- actualWait = plannedTimeout
- } else {
- // if the node was redialed earlier then we do not raise the planned timeout
- // exponentially because that could lead to the timeout rising very high in
- // a short amount of time
- // Note that in case of an early redial actualWait also includes the dial
- // timeout or connection time of the last attempt but it still serves its
- // purpose of preventing the timeout rising quicker than linearly as a function
- // of total time elapsed without a successful connection.
- actualWait = float64(unixTime - n.redialWaitStart)
- }
- // raise timeout exponentially if the last planned timeout has elapsed
- // (use at least the last planned timeout otherwise)
- nextTimeout := actualWait * waitStep
- if plannedTimeout > nextTimeout {
- nextTimeout = plannedTimeout
- }
- // we reduce the waiting time if the server has provided service value during the
- // connection (but never under the minimum)
- a := totalValue * dialCost * float64(minRedialWait)
- b := float64(totalDialCost) * sessionValue
- if a < b*nextTimeout {
- nextTimeout = a / b
- }
- if nextTimeout < minRedialWait {
- nextTimeout = minRedialWait
- }
- wait := time.Duration(float64(time.Second) * nextTimeout)
- if wait < waitThreshold {
- n.redialWaitStart = unixTime
- n.redialWaitEnd = unixTime + int64(nextTimeout)
- s.ns.SetFieldSub(node, sfiNodeHistory, n)
- s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, wait)
- s.updateWeight(node, totalValue, totalDialCost)
- } else {
- // discard known node statistics if waiting time is very long because the node
- // hasn't been responsive for a very long time
- s.ns.SetFieldSub(node, sfiNodeHistory, nil)
- s.ns.SetFieldSub(node, sfiNodeWeight, nil)
- s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0)
- }
-}
-
-// calculateWeight calculates and sets the node weight without altering the node history.
-// This function should be called during startup and shutdown only, otherwise setRedialWait
-// will keep the weights updated as the underlying statistics are adjusted.
-// Note: this function should run inside a NodeStateMachine operation
-func (s *ServerPool) calculateWeight(node *enode.Node) {
- n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory)
- _, totalValue := s.serviceValue(node)
- totalDialCost := s.addDialCost(&n, 0)
- s.updateWeight(node, totalValue, totalDialCost)
-}
-
-// API returns the vflux client API
-func (s *ServerPool) API() *PrivateClientAPI {
- return NewPrivateClientAPI(s.vt)
-}
-
-type dummyIdentity enode.ID
-
-func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil }
-func (id dummyIdentity) NodeAddr(r *enr.Record) []byte { return id[:] }
-
-// DialNode replaces the given enode with a locally generated one containing the ENR
-// stored in the sfiLocalAddress field if present. This workaround ensures that nodes
-// on the local network can be dialed at the local address if a connection has been
-// successfully established previously.
-// Note that NodeStateMachine always remembers the enode with the latest version of
-// the remote signed ENR. ENR filtering should be performed on that version while
-// dialNode should be used for dialing the node over TCP or UDP.
-func (s *ServerPool) DialNode(n *enode.Node) *enode.Node {
- if enr, ok := s.ns.GetField(n, sfiLocalAddress).(*enr.Record); ok {
- n, _ := enode.New(dummyIdentity(n.ID()), enr)
- return n
- }
- return n
-}
-
-// Persist immediately stores the state of a node in the node database
-func (s *ServerPool) Persist(n *enode.Node) {
- s.ns.Persist(n)
-}
diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go
deleted file mode 100644
index f1fd987d7..000000000
--- a/les/vflux/client/serverpool_test.go
+++ /dev/null
@@ -1,392 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "math/rand"
- "strconv"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/ethdb/memorydb"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-const (
- spTestNodes = 1000
- spTestTarget = 5
- spTestLength = 10000
- spMinTotal = 40000
- spMaxTotal = 50000
-)
-
-func testNodeID(i int) enode.ID {
- return enode.ID{42, byte(i % 256), byte(i / 256)}
-}
-
-func testNodeIndex(id enode.ID) int {
- if id[0] != 42 {
- return -1
- }
- return int(id[1]) + int(id[2])*256
-}
-
-type ServerPoolTest struct {
- db ethdb.KeyValueStore
- clock *mclock.Simulated
- quit chan chan struct{}
- preNeg, preNegFail bool
- sp *ServerPool
- spi enode.Iterator
- input enode.Iterator
- testNodes []spTestNode
- trusted []string
- waitCount, waitEnded int32
-
- // preNegLock protects the cycle counter, testNodes list and its connected field
- // (accessed from both the main thread and the preNeg callback)
- preNegLock sync.Mutex
- queryWg *sync.WaitGroup // a new wait group is created each time the simulation is started
- stopping bool // stopping avoid calling queryWg.Add after queryWg.Wait
-
- cycle, conn, servedConn int
- serviceCycles, dialCount int
- disconnect map[int][]int
-}
-
-type spTestNode struct {
- connectCycles, waitCycles int
- nextConnCycle, totalConn int
- connected, service bool
- node *enode.Node
-}
-
-func newServerPoolTest(preNeg, preNegFail bool) *ServerPoolTest {
- nodes := make([]*enode.Node, spTestNodes)
- for i := range nodes {
- nodes[i] = enode.SignNull(&enr.Record{}, testNodeID(i))
- }
- return &ServerPoolTest{
- clock: &mclock.Simulated{},
- db: memorydb.New(),
- input: enode.CycleNodes(nodes),
- testNodes: make([]spTestNode, spTestNodes),
- preNeg: preNeg,
- preNegFail: preNegFail,
- }
-}
-
-func (s *ServerPoolTest) beginWait() {
- // ensure that dialIterator and the maximal number of pre-neg queries are not all stuck in a waiting state
- for atomic.AddInt32(&s.waitCount, 1) > preNegLimit {
- atomic.AddInt32(&s.waitCount, -1)
- s.clock.Run(time.Second)
- }
-}
-
-func (s *ServerPoolTest) endWait() {
- atomic.AddInt32(&s.waitCount, -1)
- atomic.AddInt32(&s.waitEnded, 1)
-}
-
-func (s *ServerPoolTest) addTrusted(i int) {
- s.trusted = append(s.trusted, enode.SignNull(&enr.Record{}, testNodeID(i)).String())
-}
-
-func (s *ServerPoolTest) start() {
- var testQuery QueryFunc
- s.queryWg = new(sync.WaitGroup)
- if s.preNeg {
- testQuery = func(node *enode.Node) int {
- s.preNegLock.Lock()
- if s.stopping {
- s.preNegLock.Unlock()
- return 0
- }
- s.queryWg.Add(1)
- idx := testNodeIndex(node.ID())
- n := &s.testNodes[idx]
- canConnect := !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle
- s.preNegLock.Unlock()
- defer s.queryWg.Done()
-
- if s.preNegFail {
- // simulate a scenario where UDP queries never work
- s.beginWait()
- s.clock.Sleep(time.Second * 5)
- s.endWait()
- return -1
- }
- switch idx % 3 {
- case 0:
- // pre-neg returns true only if connection is possible
- if canConnect {
- return 1
- }
- return 0
- case 1:
- // pre-neg returns true but connection might still fail
- return 1
- case 2:
- // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive)
- if canConnect {
- return 1
- }
- s.beginWait()
- s.clock.Sleep(time.Second * 5)
- s.endWait()
- return -1
- }
- return -1
- }
- }
-
- requestList := make([]RequestInfo, testReqTypes)
- for i := range requestList {
- requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1}
- }
-
- s.sp, s.spi = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList)
- s.sp.AddSource(s.input)
- s.sp.validSchemes = enode.ValidSchemesForTesting
- s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) }
- s.disconnect = make(map[int][]int)
- s.sp.Start()
- s.quit = make(chan chan struct{})
- go func() {
- last := int32(-1)
- for {
- select {
- case <-time.After(time.Millisecond * 100):
- c := atomic.LoadInt32(&s.waitEnded)
- if c == last {
- // advance clock if test is stuck (might happen in rare cases)
- s.clock.Run(time.Second)
- }
- last = c
- case quit := <-s.quit:
- close(quit)
- return
- }
- }
- }()
-}
-
-func (s *ServerPoolTest) stop() {
- // disable further queries and wait if one is currently running
- s.preNegLock.Lock()
- s.stopping = true
- s.preNegLock.Unlock()
- s.queryWg.Wait()
-
- quit := make(chan struct{})
- s.quit <- quit
- <-quit
- s.sp.Stop()
- s.spi.Close()
- s.preNegLock.Lock()
- s.stopping = false
- s.preNegLock.Unlock()
- for i := range s.testNodes {
- n := &s.testNodes[i]
- if n.connected {
- n.totalConn += s.cycle
- }
- n.connected = false
- n.node = nil
- n.nextConnCycle = 0
- }
- s.conn, s.servedConn = 0, 0
-}
-
-func (s *ServerPoolTest) run() {
- for count := spTestLength; count > 0; count-- {
- if dcList := s.disconnect[s.cycle]; dcList != nil {
- for _, idx := range dcList {
- n := &s.testNodes[idx]
- s.sp.UnregisterNode(n.node)
- n.totalConn += s.cycle
- s.preNegLock.Lock()
- n.connected = false
- s.preNegLock.Unlock()
- n.node = nil
- s.conn--
- if n.service {
- s.servedConn--
- }
- n.nextConnCycle = s.cycle + n.waitCycles
- }
- delete(s.disconnect, s.cycle)
- }
- if s.conn < spTestTarget {
- s.dialCount++
- s.beginWait()
- s.spi.Next()
- s.endWait()
- dial := s.spi.Node()
- id := dial.ID()
- idx := testNodeIndex(id)
- n := &s.testNodes[idx]
- if !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle {
- s.conn++
- if n.service {
- s.servedConn++
- }
- n.totalConn -= s.cycle
- s.preNegLock.Lock()
- n.connected = true
- s.preNegLock.Unlock()
- dc := s.cycle + n.connectCycles
- s.disconnect[dc] = append(s.disconnect[dc], idx)
- n.node = dial
- nv, _ := s.sp.RegisterNode(n.node)
- if n.service {
- nv.Served([]ServedRequest{{ReqType: 0, Amount: 100}}, 0)
- }
- }
- }
- s.serviceCycles += s.servedConn
- s.clock.Run(time.Second)
- s.preNegLock.Lock()
- s.cycle++
- s.preNegLock.Unlock()
- }
-}
-
-func (s *ServerPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) {
- for ; count > 0; count-- {
- idx := rand.Intn(spTestNodes)
- for s.testNodes[idx].connectCycles != 0 || s.testNodes[idx].connected {
- idx = rand.Intn(spTestNodes)
- }
- res = append(res, idx)
- s.preNegLock.Lock()
- s.testNodes[idx] = spTestNode{
- connectCycles: conn,
- waitCycles: wait,
- service: service,
- }
- s.preNegLock.Unlock()
- if trusted {
- s.addTrusted(idx)
- }
- }
- return
-}
-
-func (s *ServerPoolTest) resetNodes() {
- for i, n := range s.testNodes {
- if n.connected {
- n.totalConn += s.cycle
- s.sp.UnregisterNode(n.node)
- }
- s.preNegLock.Lock()
- s.testNodes[i] = spTestNode{totalConn: n.totalConn}
- s.preNegLock.Unlock()
- }
- s.conn, s.servedConn = 0, 0
- s.disconnect = make(map[int][]int)
- s.trusted = nil
-}
-
-func (s *ServerPoolTest) checkNodes(t *testing.T, nodes []int) {
- var sum int
- for _, idx := range nodes {
- n := &s.testNodes[idx]
- if n.connected {
- n.totalConn += s.cycle
- }
- sum += n.totalConn
- n.totalConn = 0
- if n.connected {
- n.totalConn -= s.cycle
- }
- }
- if sum < spMinTotal || sum > spMaxTotal {
- t.Errorf("Total connection amount %d outside expected range %d to %d", sum, spMinTotal, spMaxTotal)
- }
-}
-
-func TestServerPool(t *testing.T) { testServerPool(t, false, false) }
-func TestServerPoolWithPreNeg(t *testing.T) { testServerPool(t, true, false) }
-func TestServerPoolWithPreNegFail(t *testing.T) { testServerPool(t, true, true) }
-func testServerPool(t *testing.T, preNeg, fail bool) {
- s := newServerPoolTest(preNeg, fail)
- nodes := s.setNodes(100, 200, 200, true, false)
- s.setNodes(100, 20, 20, false, false)
- s.start()
- s.run()
- s.stop()
- s.checkNodes(t, nodes)
-}
-
-func TestServerPoolChangedNodes(t *testing.T) { testServerPoolChangedNodes(t, false) }
-func TestServerPoolChangedNodesWithPreNeg(t *testing.T) { testServerPoolChangedNodes(t, true) }
-func testServerPoolChangedNodes(t *testing.T, preNeg bool) {
- s := newServerPoolTest(preNeg, false)
- nodes := s.setNodes(100, 200, 200, true, false)
- s.setNodes(100, 20, 20, false, false)
- s.start()
- s.run()
- s.checkNodes(t, nodes)
- for i := 0; i < 3; i++ {
- s.resetNodes()
- nodes := s.setNodes(100, 200, 200, true, false)
- s.setNodes(100, 20, 20, false, false)
- s.run()
- s.checkNodes(t, nodes)
- }
- s.stop()
-}
-
-func TestServerPoolRestartNoDiscovery(t *testing.T) { testServerPoolRestartNoDiscovery(t, false) }
-func TestServerPoolRestartNoDiscoveryWithPreNeg(t *testing.T) {
- testServerPoolRestartNoDiscovery(t, true)
-}
-func testServerPoolRestartNoDiscovery(t *testing.T, preNeg bool) {
- s := newServerPoolTest(preNeg, false)
- nodes := s.setNodes(100, 200, 200, true, false)
- s.setNodes(100, 20, 20, false, false)
- s.start()
- s.run()
- s.stop()
- s.checkNodes(t, nodes)
- s.input = nil
- s.start()
- s.run()
- s.stop()
- s.checkNodes(t, nodes)
-}
-
-func TestServerPoolTrustedNoDiscovery(t *testing.T) { testServerPoolTrustedNoDiscovery(t, false) }
-func TestServerPoolTrustedNoDiscoveryWithPreNeg(t *testing.T) {
- testServerPoolTrustedNoDiscovery(t, true)
-}
-func testServerPoolTrustedNoDiscovery(t *testing.T, preNeg bool) {
- s := newServerPoolTest(preNeg, false)
- trusted := s.setNodes(200, 200, 200, true, true)
- s.input = nil
- s.start()
- s.run()
- s.stop()
- s.checkNodes(t, trusted)
-}
diff --git a/les/vflux/client/timestats.go b/les/vflux/client/timestats.go
deleted file mode 100644
index 7f1ffdbe2..000000000
--- a/les/vflux/client/timestats.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "io"
- "math"
- "time"
-
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const (
- minResponseTime = time.Millisecond * 50
- maxResponseTime = time.Second * 10
- timeStatLength = 32
- weightScaleFactor = 1000000
-)
-
-// ResponseTimeStats is the response time distribution of a set of answered requests,
-// weighted with request value, either served by a single server or aggregated for
-// multiple servers.
-// It it a fixed length (timeStatLength) distribution vector with linear interpolation.
-// The X axis (the time values) are not linear, they should be transformed with
-// TimeToStatScale and StatScaleToTime.
-type (
- ResponseTimeStats struct {
- stats [timeStatLength]uint64
- exp uint64
- }
- ResponseTimeWeights [timeStatLength]float64
-)
-
-var timeStatsLogFactor = (timeStatLength - 1) / (math.Log(float64(maxResponseTime)/float64(minResponseTime)) + 1)
-
-// TimeToStatScale converts a response time to a distribution vector index. The index
-// is represented by a float64 so that linear interpolation can be applied.
-func TimeToStatScale(d time.Duration) float64 {
- if d < 0 {
- return 0
- }
- r := float64(d) / float64(minResponseTime)
- if r > 1 {
- r = math.Log(r) + 1
- }
- r *= timeStatsLogFactor
- if r > timeStatLength-1 {
- return timeStatLength - 1
- }
- return r
-}
-
-// StatScaleToTime converts a distribution vector index to a response time. The index
-// is represented by a float64 so that linear interpolation can be applied.
-func StatScaleToTime(r float64) time.Duration {
- r /= timeStatsLogFactor
- if r > 1 {
- r = math.Exp(r - 1)
- }
- return time.Duration(r * float64(minResponseTime))
-}
-
-// TimeoutWeights calculates the weight function used for calculating service value
-// based on the response time distribution of the received service.
-// It is based on the request timeout value of the system. It consists of a half cosine
-// function starting with 1, crossing zero at timeout and reaching -1 at 2*timeout.
-// After 2*timeout the weight is constant -1.
-func TimeoutWeights(timeout time.Duration) (res ResponseTimeWeights) {
- for i := range res {
- t := StatScaleToTime(float64(i))
- if t < 2*timeout {
- res[i] = math.Cos(math.Pi / 2 * float64(t) / float64(timeout))
- } else {
- res[i] = -1
- }
- }
- return
-}
-
-// EncodeRLP implements rlp.Encoder
-func (rt *ResponseTimeStats) EncodeRLP(w io.Writer) error {
- enc := struct {
- Stats [timeStatLength]uint64
- Exp uint64
- }{rt.stats, rt.exp}
- return rlp.Encode(w, &enc)
-}
-
-// DecodeRLP implements rlp.Decoder
-func (rt *ResponseTimeStats) DecodeRLP(s *rlp.Stream) error {
- var enc struct {
- Stats [timeStatLength]uint64
- Exp uint64
- }
- if err := s.Decode(&enc); err != nil {
- return err
- }
- rt.stats, rt.exp = enc.Stats, enc.Exp
- return nil
-}
-
-// Add adds a new response time with the given weight to the distribution.
-func (rt *ResponseTimeStats) Add(respTime time.Duration, weight float64, expFactor utils.ExpirationFactor) {
- rt.setExp(expFactor.Exp)
- weight *= expFactor.Factor * weightScaleFactor
- r := TimeToStatScale(respTime)
- i := int(r)
- r -= float64(i)
- rt.stats[i] += uint64(weight * (1 - r))
- if i < timeStatLength-1 {
- rt.stats[i+1] += uint64(weight * r)
- }
-}
-
-// setExp sets the power of 2 exponent of the structure, scaling base values (the vector
-// itself) up or down if necessary.
-func (rt *ResponseTimeStats) setExp(exp uint64) {
- if exp > rt.exp {
- shift := exp - rt.exp
- for i, v := range rt.stats {
- rt.stats[i] = v >> shift
- }
- rt.exp = exp
- }
- if exp < rt.exp {
- shift := rt.exp - exp
- for i, v := range rt.stats {
- rt.stats[i] = v << shift
- }
- rt.exp = exp
- }
-}
-
-// Value calculates the total service value based on the given distribution, using the
-// specified weight function.
-func (rt ResponseTimeStats) Value(weights ResponseTimeWeights, expFactor utils.ExpirationFactor) float64 {
- var v float64
- for i, s := range rt.stats {
- v += float64(s) * weights[i]
- }
- if v < 0 {
- return 0
- }
- return expFactor.Value(v, rt.exp) / weightScaleFactor
-}
-
-// AddStats adds the given ResponseTimeStats to the current one.
-func (rt *ResponseTimeStats) AddStats(s *ResponseTimeStats) {
- rt.setExp(s.exp)
- for i, v := range s.stats {
- rt.stats[i] += v
- }
-}
-
-// SubStats subtracts the given ResponseTimeStats from the current one.
-func (rt *ResponseTimeStats) SubStats(s *ResponseTimeStats) {
- rt.setExp(s.exp)
- for i, v := range s.stats {
- if v < rt.stats[i] {
- rt.stats[i] -= v
- } else {
- rt.stats[i] = 0
- }
- }
-}
-
-// Timeout suggests a timeout value based on the previous distribution. The parameter
-// is the desired rate of timeouts assuming a similar distribution in the future.
-// Note that the actual timeout should have a sensible minimum bound so that operating
-// under ideal working conditions for a long time (for example, using a local server
-// with very low response times) will not make it very hard for the system to accommodate
-// longer response times in the future.
-func (rt ResponseTimeStats) Timeout(failRatio float64) time.Duration {
- var sum uint64
- for _, v := range rt.stats {
- sum += v
- }
- s := uint64(float64(sum) * failRatio)
- i := timeStatLength - 1
- for i > 0 && s >= rt.stats[i] {
- s -= rt.stats[i]
- i--
- }
- r := float64(i) + 0.5
- if rt.stats[i] > 0 {
- r -= float64(s) / float64(rt.stats[i])
- }
- if r < 0 {
- r = 0
- }
- th := StatScaleToTime(r)
- if th > maxResponseTime {
- th = maxResponseTime
- }
- return th
-}
-
-// RtDistribution represents a distribution as a series of (X, Y) chart coordinates,
-// where the X axis is the response time in seconds while the Y axis is the amount of
-// service value received with a response time close to the X coordinate.
-type RtDistribution [timeStatLength][2]float64
-
-// Distribution returns a RtDistribution, optionally normalized to a sum of 1.
-func (rt ResponseTimeStats) Distribution(normalized bool, expFactor utils.ExpirationFactor) (res RtDistribution) {
- var mul float64
- if normalized {
- var sum uint64
- for _, v := range rt.stats {
- sum += v
- }
- if sum > 0 {
- mul = 1 / float64(sum)
- }
- } else {
- mul = expFactor.Value(float64(1)/weightScaleFactor, rt.exp)
- }
- for i, v := range rt.stats {
- res[i][0] = float64(StatScaleToTime(float64(i))) / float64(time.Second)
- res[i][1] = float64(v) * mul
- }
- return
-}
diff --git a/les/vflux/client/timestats_test.go b/les/vflux/client/timestats_test.go
deleted file mode 100644
index a28460171..000000000
--- a/les/vflux/client/timestats_test.go
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "math"
- "math/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/les/utils"
-)
-
-func TestTransition(t *testing.T) {
- var epsilon = 0.01
- var cases = []time.Duration{
- time.Millisecond, minResponseTime,
- time.Second, time.Second * 5, maxResponseTime,
- }
- for _, c := range cases {
- got := StatScaleToTime(TimeToStatScale(c))
- if float64(got)*(1+epsilon) < float64(c) || float64(got)*(1-epsilon) > float64(c) {
- t.Fatalf("Failed to transition back")
- }
- }
- // If the time is too large(exceeds the max response time.
- got := StatScaleToTime(TimeToStatScale(2 * maxResponseTime))
- if float64(got)*(1+epsilon) < float64(maxResponseTime) || float64(got)*(1-epsilon) > float64(maxResponseTime) {
- t.Fatalf("Failed to transition back")
- }
-}
-
-var maxResponseWeights = TimeoutWeights(maxResponseTime)
-
-func TestValue(t *testing.T) {
- noexp := utils.ExpirationFactor{Factor: 1}
- for i := 0; i < 1000; i++ {
- max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime)))
- min := minResponseTime + time.Duration(rand.Int63n(int64(max-minResponseTime)))
- timeout := max/2 + time.Duration(rand.Int63n(int64(maxResponseTime-max/2)))
- s := makeRangeStats(min, max, 1000, noexp)
- value := s.Value(TimeoutWeights(timeout), noexp)
- // calculate the average weight (the average of the given range of the half cosine
- // weight function).
- minx := math.Pi / 2 * float64(min) / float64(timeout)
- maxx := math.Pi / 2 * float64(max) / float64(timeout)
- avgWeight := (math.Sin(maxx) - math.Sin(minx)) / (maxx - minx)
- expv := 1000 * avgWeight
- if expv < 0 {
- expv = 0
- }
- if value < expv-10 || value > expv+10 {
- t.Errorf("Value failed (expected %v, got %v)", expv, value)
- }
- }
-}
-
-func TestAddSubExpire(t *testing.T) {
- var (
- sum1, sum2 ResponseTimeStats
- sum1ValueExp, sum2ValueExp float64
- logOffset utils.Fixed64
- )
- for i := 0; i < 1000; i++ {
- exp := utils.ExpFactor(logOffset)
- max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime)))
- min := minResponseTime + time.Duration(rand.Int63n(int64(max-minResponseTime)))
- s := makeRangeStats(min, max, 1000, exp)
- value := s.Value(maxResponseWeights, exp)
- sum1.AddStats(&s)
- sum1ValueExp += value
- if rand.Intn(2) == 1 {
- sum2.AddStats(&s)
- sum2ValueExp += value
- }
- logOffset += utils.Float64ToFixed64(0.001 / math.Log(2))
- sum1ValueExp -= sum1ValueExp * 0.001
- sum2ValueExp -= sum2ValueExp * 0.001
- }
- exp := utils.ExpFactor(logOffset)
- sum1Value := sum1.Value(maxResponseWeights, exp)
- if sum1Value < sum1ValueExp*0.99 || sum1Value > sum1ValueExp*1.01 {
- t.Errorf("sum1Value failed (expected %v, got %v)", sum1ValueExp, sum1Value)
- }
- sum2Value := sum2.Value(maxResponseWeights, exp)
- if sum2Value < sum2ValueExp*0.99 || sum2Value > sum2ValueExp*1.01 {
- t.Errorf("sum2Value failed (expected %v, got %v)", sum2ValueExp, sum2Value)
- }
- diff := sum1
- diff.SubStats(&sum2)
- diffValue := diff.Value(maxResponseWeights, exp)
- diffValueExp := sum1ValueExp - sum2ValueExp
- if diffValue < diffValueExp*0.99 || diffValue > diffValueExp*1.01 {
- t.Errorf("diffValue failed (expected %v, got %v)", diffValueExp, diffValue)
- }
-}
-
-func TestTimeout(t *testing.T) {
- testTimeoutRange(t, 0, time.Second)
- testTimeoutRange(t, time.Second, time.Second*2)
- testTimeoutRange(t, time.Second, maxResponseTime)
-}
-
-func testTimeoutRange(t *testing.T, min, max time.Duration) {
- s := makeRangeStats(min, max, 1000, utils.ExpirationFactor{Factor: 1})
- for i := 2; i < 9; i++ {
- to := s.Timeout(float64(i) / 10)
- exp := max - (max-min)*time.Duration(i)/10
- tol := (max - min) / 50
- if to < exp-tol || to > exp+tol {
- t.Errorf("Timeout failed (expected %v, got %v)", exp, to)
- }
- }
-}
-
-func makeRangeStats(min, max time.Duration, amount float64, exp utils.ExpirationFactor) ResponseTimeStats {
- var s ResponseTimeStats
- amount /= 1000
- for i := 0; i < 1000; i++ {
- s.Add(min+(max-min)*time.Duration(i)/999, amount, exp)
- }
- return s
-}
diff --git a/les/vflux/client/valuetracker.go b/les/vflux/client/valuetracker.go
deleted file mode 100644
index 806d0c7d7..000000000
--- a/les/vflux/client/valuetracker.go
+++ /dev/null
@@ -1,506 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "bytes"
- "fmt"
- "math"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const (
- vtVersion = 1 // database encoding format for ValueTracker
- nvtVersion = 1 // database encoding format for NodeValueTracker
-)
-
-var (
- vtKey = []byte("vt:")
- vtNodeKey = []byte("vtNode:")
-)
-
-// NodeValueTracker collects service value statistics for a specific server node
-type NodeValueTracker struct {
- lock sync.Mutex
-
- vt *ValueTracker
- rtStats, lastRtStats ResponseTimeStats
- lastTransfer mclock.AbsTime
- basket serverBasket
- reqCosts []uint64
- reqValues []float64
-}
-
-// UpdateCosts updates the node value tracker's request cost table
-func (nv *NodeValueTracker) UpdateCosts(reqCosts []uint64) {
- nv.vt.lock.Lock()
- defer nv.vt.lock.Unlock()
-
- nv.updateCosts(reqCosts, nv.vt.refBasket.reqValues, nv.vt.refBasket.reqValueFactor(reqCosts))
-}
-
-// updateCosts updates the request cost table of the server. The request value factor
-// is also updated based on the given cost table and the current reference basket.
-// Note that the contents of the referenced reqValues slice will not change; a new
-// reference is passed if the values are updated by ValueTracker.
-func (nv *NodeValueTracker) updateCosts(reqCosts []uint64, reqValues []float64, rvFactor float64) {
- nv.lock.Lock()
- defer nv.lock.Unlock()
-
- nv.reqCosts = reqCosts
- nv.reqValues = reqValues
- nv.basket.updateRvFactor(rvFactor)
-}
-
-// transferStats returns request basket and response time statistics that should be
-// added to the global statistics. The contents of the server's own request basket are
-// gradually transferred to the main reference basket and removed from the server basket
-// with the specified transfer rate.
-// The response time statistics are retained at both places and therefore the global
-// distribution is always the sum of the individual server distributions.
-func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float64) (requestBasket, ResponseTimeStats) {
- nv.lock.Lock()
- defer nv.lock.Unlock()
-
- dt := now - nv.lastTransfer
- nv.lastTransfer = now
- if dt < 0 {
- dt = 0
- }
- recentRtStats := nv.rtStats
- recentRtStats.SubStats(&nv.lastRtStats)
- nv.lastRtStats = nv.rtStats
- return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats
-}
-
-type ServedRequest struct {
- ReqType, Amount uint32
-}
-
-// Served adds a served request to the node's statistics. An actual request may be composed
-// of one or more request types (service vector indices).
-func (nv *NodeValueTracker) Served(reqs []ServedRequest, respTime time.Duration) {
- nv.vt.statsExpLock.RLock()
- expFactor := nv.vt.statsExpFactor
- nv.vt.statsExpLock.RUnlock()
-
- nv.lock.Lock()
- defer nv.lock.Unlock()
-
- var value float64
- for _, r := range reqs {
- nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor)
- value += nv.reqValues[r.ReqType] * float64(r.Amount)
- }
- nv.rtStats.Add(respTime, value, expFactor)
-}
-
-// RtStats returns the node's own response time distribution statistics
-func (nv *NodeValueTracker) RtStats() ResponseTimeStats {
- nv.lock.Lock()
- defer nv.lock.Unlock()
-
- return nv.rtStats
-}
-
-// ValueTracker coordinates service value calculation for individual servers and updates
-// global statistics
-type ValueTracker struct {
- clock mclock.Clock
- lock sync.Mutex
- quit chan chan struct{}
- db ethdb.KeyValueStore
- connected map[enode.ID]*NodeValueTracker
- reqTypeCount int
-
- refBasket referenceBasket
- mappings [][]string
- currentMapping int
- initRefBasket requestBasket
- rtStats ResponseTimeStats
-
- transferRate float64
- statsExpLock sync.RWMutex
- statsExpRate, offlineExpRate float64
- statsExpirer utils.Expirer
- statsExpFactor utils.ExpirationFactor
-}
-
-type valueTrackerEncV1 struct {
- Mappings [][]string
- RefBasketMapping uint
- RefBasket requestBasket
- RtStats ResponseTimeStats
- ExpOffset, SavedAt uint64
-}
-
-type nodeValueTrackerEncV1 struct {
- RtStats ResponseTimeStats
- ServerBasketMapping uint
- ServerBasket requestBasket
-}
-
-// RequestInfo is an initializer structure for the service vector.
-type RequestInfo struct {
- // Name identifies the request type and is used for re-mapping the service vector if necessary
- Name string
- // InitAmount and InitValue are used to initialize the reference basket
- InitAmount, InitValue float64
-}
-
-// NewValueTracker creates a new ValueTracker and loads its previously saved state from
-// the database if possible.
-func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []RequestInfo, updatePeriod time.Duration, transferRate, statsExpRate, offlineExpRate float64) *ValueTracker {
- now := clock.Now()
-
- initRefBasket := requestBasket{items: make([]basketItem, len(reqInfo))}
- mapping := make([]string, len(reqInfo))
-
- var sumAmount, sumValue float64
- for _, req := range reqInfo {
- sumAmount += req.InitAmount
- sumValue += req.InitAmount * req.InitValue
- }
- scaleValues := sumAmount * basketFactor / sumValue
- for i, req := range reqInfo {
- mapping[i] = req.Name
- initRefBasket.items[i].amount = uint64(req.InitAmount * basketFactor)
- initRefBasket.items[i].value = uint64(req.InitAmount * req.InitValue * scaleValues)
- }
-
- vt := &ValueTracker{
- clock: clock,
- connected: make(map[enode.ID]*NodeValueTracker),
- quit: make(chan chan struct{}),
- db: db,
- reqTypeCount: len(initRefBasket.items),
- initRefBasket: initRefBasket,
- transferRate: transferRate,
- statsExpRate: statsExpRate,
- offlineExpRate: offlineExpRate,
- }
- if vt.loadFromDb(mapping) != nil {
- // previous state not saved or invalid, init with default values
- vt.refBasket.basket = initRefBasket
- vt.mappings = [][]string{mapping}
- vt.currentMapping = 0
- }
- vt.statsExpirer.SetRate(now, statsExpRate)
- vt.refBasket.init(vt.reqTypeCount)
- vt.periodicUpdate()
-
- go func() {
- for {
- select {
- case <-clock.After(updatePeriod):
- vt.lock.Lock()
- vt.periodicUpdate()
- vt.lock.Unlock()
- case quit := <-vt.quit:
- close(quit)
- return
- }
- }
- }()
- return vt
-}
-
-// StatsExpirer returns the statistics expirer so that other values can be expired
-// with the same rate as the service value statistics.
-func (vt *ValueTracker) StatsExpirer() *utils.Expirer {
- return &vt.statsExpirer
-}
-
-// StatsExpFactor returns the current expiration factor so that other values can be expired
-// with the same rate as the service value statistics.
-func (vt *ValueTracker) StatsExpFactor() utils.ExpirationFactor {
- vt.statsExpLock.RLock()
- defer vt.statsExpLock.RUnlock()
-
- return vt.statsExpFactor
-}
-
-// loadFromDb loads the value tracker's state from the database and converts saved
-// request basket index mapping if it does not match the specified index to name mapping.
-func (vt *ValueTracker) loadFromDb(mapping []string) error {
- enc, err := vt.db.Get(vtKey)
- if err != nil {
- return err
- }
- r := bytes.NewReader(enc)
- var version uint
- if err := rlp.Decode(r, &version); err != nil {
- log.Error("Decoding value tracker state failed", "err", err)
- return err
- }
- if version != vtVersion {
- log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion)
- return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion)
- }
- var vte valueTrackerEncV1
- if err := rlp.Decode(r, &vte); err != nil {
- log.Error("Decoding value tracker state failed", "err", err)
- return err
- }
- logOffset := utils.Fixed64(vte.ExpOffset)
- dt := time.Now().UnixNano() - int64(vte.SavedAt)
- if dt > 0 {
- logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2))
- }
- vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset)
- vt.rtStats = vte.RtStats
- vt.mappings = vte.Mappings
- vt.currentMapping = -1
-loop:
- for i, m := range vt.mappings {
- if len(m) != len(mapping) {
- continue loop
- }
- for j, s := range mapping {
- if m[j] != s {
- continue loop
- }
- }
- vt.currentMapping = i
- break
- }
- if vt.currentMapping == -1 {
- vt.currentMapping = len(vt.mappings)
- vt.mappings = append(vt.mappings, mapping)
- }
- if int(vte.RefBasketMapping) == vt.currentMapping {
- vt.refBasket.basket = vte.RefBasket
- } else {
- if vte.RefBasketMapping >= uint(len(vt.mappings)) {
- log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping)
- return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping)
- }
- vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket)
- }
- return nil
-}
-
-// saveToDb saves the value tracker's state to the database
-func (vt *ValueTracker) saveToDb() {
- vte := valueTrackerEncV1{
- Mappings: vt.mappings,
- RefBasketMapping: uint(vt.currentMapping),
- RefBasket: vt.refBasket.basket,
- RtStats: vt.rtStats,
- ExpOffset: uint64(vt.statsExpirer.LogOffset(vt.clock.Now())),
- SavedAt: uint64(time.Now().UnixNano()),
- }
- enc1, err := rlp.EncodeToBytes(uint(vtVersion))
- if err != nil {
- log.Error("Encoding value tracker state failed", "err", err)
- return
- }
- enc2, err := rlp.EncodeToBytes(&vte)
- if err != nil {
- log.Error("Encoding value tracker state failed", "err", err)
- return
- }
- if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil {
- log.Error("Saving value tracker state failed", "err", err)
- }
-}
-
-// Stop saves the value tracker's state and each loaded node's individual state and
-// returns after shutting the internal goroutines down.
-func (vt *ValueTracker) Stop() {
- quit := make(chan struct{})
- vt.quit <- quit
- <-quit
- vt.lock.Lock()
- vt.periodicUpdate()
- for id, nv := range vt.connected {
- vt.saveNode(id, nv)
- }
- vt.connected = nil
- vt.saveToDb()
- vt.lock.Unlock()
-}
-
-// Register adds a server node to the value tracker
-func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker {
- vt.lock.Lock()
- defer vt.lock.Unlock()
-
- if vt.connected == nil {
- // ValueTracker has already been stopped
- return nil
- }
- nv := vt.loadOrNewNode(id)
- reqTypeCount := len(vt.refBasket.reqValues)
- nv.reqCosts = make([]uint64, reqTypeCount)
- nv.lastTransfer = vt.clock.Now()
- nv.reqValues = vt.refBasket.reqValues
- nv.basket.init(reqTypeCount)
-
- vt.connected[id] = nv
- return nv
-}
-
-// Unregister removes a server node from the value tracker
-func (vt *ValueTracker) Unregister(id enode.ID) {
- vt.lock.Lock()
- defer vt.lock.Unlock()
-
- if nv := vt.connected[id]; nv != nil {
- vt.saveNode(id, nv)
- delete(vt.connected, id)
- }
-}
-
-// GetNode returns an individual server node's value tracker. If it did not exist before
-// then a new node is created.
-func (vt *ValueTracker) GetNode(id enode.ID) *NodeValueTracker {
- vt.lock.Lock()
- defer vt.lock.Unlock()
-
- return vt.loadOrNewNode(id)
-}
-
-// loadOrNewNode returns an individual server node's value tracker. If it did not exist before
-// then a new node is created.
-func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker {
- if nv, ok := vt.connected[id]; ok {
- return nv
- }
- nv := &NodeValueTracker{vt: vt, lastTransfer: vt.clock.Now()}
- enc, err := vt.db.Get(append(vtNodeKey, id[:]...))
- if err != nil {
- return nv
- }
- r := bytes.NewReader(enc)
- var version uint
- if err := rlp.Decode(r, &version); err != nil {
- log.Error("Failed to decode node value tracker", "id", id, "err", err)
- return nv
- }
- if version != nvtVersion {
- log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion)
- return nv
- }
- var nve nodeValueTrackerEncV1
- if err := rlp.Decode(r, &nve); err != nil {
- log.Error("Failed to decode node value tracker", "id", id, "err", err)
- return nv
- }
- nv.rtStats = nve.RtStats
- nv.lastRtStats = nve.RtStats
- if int(nve.ServerBasketMapping) == vt.currentMapping {
- nv.basket.basket = nve.ServerBasket
- } else {
- if nve.ServerBasketMapping >= uint(len(vt.mappings)) {
- log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping)
- return nv
- }
- nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket)
- }
- return nv
-}
-
-// saveNode saves a server node's value tracker to the database
-func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) {
- recentRtStats := nv.rtStats
- recentRtStats.SubStats(&nv.lastRtStats)
- vt.rtStats.AddStats(&recentRtStats)
- nv.lastRtStats = nv.rtStats
-
- nve := nodeValueTrackerEncV1{
- RtStats: nv.rtStats,
- ServerBasketMapping: uint(vt.currentMapping),
- ServerBasket: nv.basket.basket,
- }
- enc1, err := rlp.EncodeToBytes(uint(nvtVersion))
- if err != nil {
- log.Error("Failed to encode service value information", "id", id, "err", err)
- return
- }
- enc2, err := rlp.EncodeToBytes(&nve)
- if err != nil {
- log.Error("Failed to encode service value information", "id", id, "err", err)
- return
- }
- if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil {
- log.Error("Failed to save service value information", "id", id, "err", err)
- }
-}
-
-// RtStats returns the global response time distribution statistics
-func (vt *ValueTracker) RtStats() ResponseTimeStats {
- vt.lock.Lock()
- defer vt.lock.Unlock()
-
- vt.periodicUpdate()
- return vt.rtStats
-}
-
-// periodicUpdate transfers individual node data to the global statistics, normalizes
-// the reference basket and updates request values. The global state is also saved to
-// the database with each update.
-func (vt *ValueTracker) periodicUpdate() {
- now := vt.clock.Now()
- vt.statsExpLock.Lock()
- vt.statsExpFactor = utils.ExpFactor(vt.statsExpirer.LogOffset(now))
- vt.statsExpLock.Unlock()
-
- for _, nv := range vt.connected {
- basket, rtStats := nv.transferStats(now, vt.transferRate)
- vt.refBasket.add(basket)
- vt.rtStats.AddStats(&rtStats)
- }
- vt.refBasket.normalize()
- vt.refBasket.updateReqValues()
- for _, nv := range vt.connected {
- nv.updateCosts(nv.reqCosts, vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts))
- }
- vt.saveToDb()
-}
-
-type RequestStatsItem struct {
- Name string
- ReqAmount, ReqValue float64
-}
-
-// RequestStats returns the current contents of the reference request basket, with
-// request values meaning average per request rather than total.
-func (vt *ValueTracker) RequestStats() []RequestStatsItem {
- vt.statsExpLock.RLock()
- expFactor := vt.statsExpFactor
- vt.statsExpLock.RUnlock()
- vt.lock.Lock()
- defer vt.lock.Unlock()
-
- vt.periodicUpdate()
- res := make([]RequestStatsItem, len(vt.refBasket.basket.items))
- for i, item := range vt.refBasket.basket.items {
- res[i].Name = vt.mappings[vt.currentMapping][i]
- res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp)
- res[i].ReqValue = vt.refBasket.reqValues[i]
- }
- return res
-}
diff --git a/les/vflux/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go
deleted file mode 100644
index 87a337be8..000000000
--- a/les/vflux/client/valuetracker_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "math"
- "math/rand"
- "strconv"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb/memorydb"
- "github.com/ethereum/go-ethereum/p2p/enode"
-
- "github.com/ethereum/go-ethereum/les/utils"
-)
-
-const (
- testReqTypes = 3
- testNodeCount = 5
- testReqCount = 10000
- testRounds = 10
-)
-
-func TestValueTracker(t *testing.T) {
- db := memorydb.New()
- clock := &mclock.Simulated{}
- requestList := make([]RequestInfo, testReqTypes)
- relPrices := make([]float64, testReqTypes)
- totalAmount := make([]uint64, testReqTypes)
- for i := range requestList {
- requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1}
- totalAmount[i] = 1
- relPrices[i] = rand.Float64() + 0.1
- }
- nodes := make([]*NodeValueTracker, testNodeCount)
- for round := 0; round < testRounds; round++ {
- makeRequests := round < testRounds-2
- useExpiration := round == testRounds-1
- var expRate float64
- if useExpiration {
- expRate = math.Log(2) / float64(time.Hour*100)
- }
-
- vt := NewValueTracker(db, clock, requestList, time.Minute, 1/float64(time.Hour), expRate, expRate)
- updateCosts := func(i int) {
- costList := make([]uint64, testReqTypes)
- baseCost := rand.Float64()*10000000 + 100000
- for j := range costList {
- costList[j] = uint64(baseCost * relPrices[j])
- }
- nodes[i].UpdateCosts(costList)
- }
- for i := range nodes {
- nodes[i] = vt.Register(enode.ID{byte(i)})
- updateCosts(i)
- }
- if makeRequests {
- for i := 0; i < testReqCount; i++ {
- reqType := rand.Intn(testReqTypes)
- reqAmount := rand.Intn(10) + 1
- node := rand.Intn(testNodeCount)
- respTime := time.Duration((rand.Float64() + 1) * float64(time.Second) * float64(node+1) / testNodeCount)
- totalAmount[reqType] += uint64(reqAmount)
- nodes[node].Served([]ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime)
- clock.Run(time.Second)
- }
- } else {
- clock.Run(time.Hour * 100)
- if useExpiration {
- for i, a := range totalAmount {
- totalAmount[i] = a / 2
- }
- }
- }
- vt.Stop()
- var sumrp, sumrv float64
- for i, rp := range relPrices {
- sumrp += rp
- sumrv += vt.refBasket.reqValues[i]
- }
- for i, rp := range relPrices {
- ratio := vt.refBasket.reqValues[i] * sumrp / (rp * sumrv)
- if ratio < 0.99 || ratio > 1.01 {
- t.Errorf("reqValues (%v) does not match relPrices (%v)", vt.refBasket.reqValues, relPrices)
- break
- }
- }
- exp := utils.ExpFactor(vt.StatsExpirer().LogOffset(clock.Now()))
- basketAmount := make([]uint64, testReqTypes)
- for i, bi := range vt.refBasket.basket.items {
- basketAmount[i] += uint64(exp.Value(float64(bi.amount), vt.refBasket.basket.exp))
- }
- if makeRequests {
- // if we did not make requests in this round then we expect all amounts to be
- // in the reference basket
- for _, node := range nodes {
- for i, bi := range node.basket.basket.items {
- basketAmount[i] += uint64(exp.Value(float64(bi.amount), node.basket.basket.exp))
- }
- }
- }
- for i, a := range basketAmount {
- amount := a / basketFactor
- if amount+10 < totalAmount[i] || amount > totalAmount[i]+10 {
- t.Errorf("totalAmount[%d] mismatch in round %d (expected %d, got %d)", i, round, totalAmount[i], amount)
- }
- }
- var sumValue float64
- for _, node := range nodes {
- s := node.RtStats()
- sumValue += s.Value(maxResponseWeights, exp)
- }
- s := vt.RtStats()
- mainValue := s.Value(maxResponseWeights, exp)
- if sumValue < mainValue-10 || sumValue > mainValue+10 {
- t.Errorf("Main rtStats value does not match sum of node rtStats values in round %d (main %v, sum %v)", round, mainValue, sumValue)
- }
- }
-}
diff --git a/les/vflux/client/wrsiterator.go b/les/vflux/client/wrsiterator.go
deleted file mode 100644
index 1b37cba6e..000000000
--- a/les/vflux/client/wrsiterator.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "sync"
-
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-// WrsIterator returns nodes from the specified selectable set with a weighted random
-// selection. Selection weights are provided by a callback function.
-type WrsIterator struct {
- lock sync.Mutex
- cond *sync.Cond
-
- ns *nodestate.NodeStateMachine
- wrs *utils.WeightedRandomSelect
- nextNode *enode.Node
- closed bool
-}
-
-// NewWrsIterator creates a new WrsIterator. Nodes are selectable if they have all the required
-// and none of the disabled flags set. When a node is selected the selectedFlag is set which also
-// disables further selectability until it is removed or times out.
-func NewWrsIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags nodestate.Flags, weightField nodestate.Field) *WrsIterator {
- wfn := func(i interface{}) uint64 {
- n := ns.GetNode(i.(enode.ID))
- if n == nil {
- return 0
- }
- wt, _ := ns.GetField(n, weightField).(uint64)
- return wt
- }
-
- w := &WrsIterator{
- ns: ns,
- wrs: utils.NewWeightedRandomSelect(wfn),
- }
- w.cond = sync.NewCond(&w.lock)
-
- ns.SubscribeField(weightField, func(n *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
- if state.HasAll(requireFlags) && state.HasNone(disableFlags) {
- w.lock.Lock()
- w.wrs.Update(n.ID())
- w.lock.Unlock()
- w.cond.Signal()
- }
- })
-
- ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) {
- oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags)
- newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags)
- if newMatch == oldMatch {
- return
- }
-
- w.lock.Lock()
- if newMatch {
- w.wrs.Update(n.ID())
- } else {
- w.wrs.Remove(n.ID())
- }
- w.lock.Unlock()
- w.cond.Signal()
- })
- return w
-}
-
-// Next selects the next node.
-func (w *WrsIterator) Next() bool {
- w.nextNode = w.chooseNode()
- return w.nextNode != nil
-}
-
-func (w *WrsIterator) chooseNode() *enode.Node {
- w.lock.Lock()
- defer w.lock.Unlock()
-
- for {
- for !w.closed && w.wrs.IsEmpty() {
- w.cond.Wait()
- }
- if w.closed {
- return nil
- }
- // Choose the next node at random. Even though w.wrs is guaranteed
- // non-empty here, Choose might return nil if all items have weight
- // zero.
- if c := w.wrs.Choose(); c != nil {
- id := c.(enode.ID)
- w.wrs.Remove(id)
- return w.ns.GetNode(id)
- }
- }
-}
-
-// Close ends the iterator.
-func (w *WrsIterator) Close() {
- w.lock.Lock()
- w.closed = true
- w.lock.Unlock()
- w.cond.Signal()
-}
-
-// Node returns the current node.
-func (w *WrsIterator) Node() *enode.Node {
- w.lock.Lock()
- defer w.lock.Unlock()
- return w.nextNode
-}
diff --git a/les/vflux/client/wrsiterator_test.go b/les/vflux/client/wrsiterator_test.go
deleted file mode 100644
index 77bb5ee0c..000000000
--- a/les/vflux/client/wrsiterator_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2020 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 .
-
-package client
-
-import (
- "reflect"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-var (
- testSetup = &nodestate.Setup{}
- sfTest1 = testSetup.NewFlag("test1")
- sfTest2 = testSetup.NewFlag("test2")
- sfTest3 = testSetup.NewFlag("test3")
- sfTest4 = testSetup.NewFlag("test4")
- sfiTestWeight = testSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0)))
-)
-
-const iterTestNodeCount = 6
-
-func TestWrsIterator(t *testing.T) {
- ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup)
- w := NewWrsIterator(ns, sfTest2, sfTest3.Or(sfTest4), sfiTestWeight)
- ns.Start()
- for i := 1; i <= iterTestNodeCount; i++ {
- ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0)
- ns.SetField(testNode(i), sfiTestWeight, uint64(1))
- }
- next := func() int {
- ch := make(chan struct{})
- go func() {
- w.Next()
- close(ch)
- }()
- select {
- case <-ch:
- case <-time.After(time.Second * 5):
- t.Fatalf("Iterator.Next() timeout")
- }
- node := w.Node()
- ns.SetState(node, sfTest4, nodestate.Flags{}, 0)
- return testNodeIndex(node.ID())
- }
- set := make(map[int]bool)
- expset := func() {
- for len(set) > 0 {
- n := next()
- if !set[n] {
- t.Errorf("Item returned by iterator not in the expected set (got %d)", n)
- }
- delete(set, n)
- }
- }
-
- ns.SetState(testNode(1), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(2), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(3), sfTest2, nodestate.Flags{}, 0)
- set[1] = true
- set[2] = true
- set[3] = true
- expset()
- ns.SetState(testNode(4), sfTest2, nodestate.Flags{}, 0)
- ns.SetState(testNode(5), sfTest2.Or(sfTest3), nodestate.Flags{}, 0)
- ns.SetState(testNode(6), sfTest2, nodestate.Flags{}, 0)
- set[4] = true
- set[6] = true
- expset()
- ns.SetField(testNode(2), sfiTestWeight, uint64(0))
- ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0)
- ns.SetState(testNode(2), nodestate.Flags{}, sfTest4, 0)
- ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0)
- set[1] = true
- set[3] = true
- expset()
- ns.SetField(testNode(2), sfiTestWeight, uint64(1))
- ns.SetState(testNode(2), nodestate.Flags{}, sfTest2, 0)
- ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0)
- ns.SetState(testNode(2), sfTest2, sfTest4, 0)
- ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0)
- set[1] = true
- set[2] = true
- set[3] = true
- expset()
- ns.Stop()
-}
diff --git a/les/vflux/requests.go b/les/vflux/requests.go
deleted file mode 100644
index 5abae2f53..000000000
--- a/les/vflux/requests.go
+++ /dev/null
@@ -1,180 +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 .
-
-package vflux
-
-import (
- "errors"
- "math"
- "math/big"
-
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var ErrNoReply = errors.New("no reply for given request")
-
-const (
- MaxRequestLength = 16 // max number of individual requests in a batch
- CapacityQueryName = "cq"
- CapacityQueryMaxLen = 16
-)
-
-type (
- // Request describes a single vflux request inside a batch. Service and request
- // type are identified by strings, parameters are RLP encoded.
- Request struct {
- Service, Name string
- Params []byte
- }
- // Requests are a batch of vflux requests
- Requests []Request
-
- // Replies are the replies to a batch of requests
- Replies [][]byte
-
- // CapacityQueryReq is the encoding format of the capacity query
- CapacityQueryReq struct {
- Bias uint64 // seconds
- AddTokens []IntOrInf
- }
- // CapacityQueryReply is the encoding format of the response to the capacity query
- CapacityQueryReply []uint64
-)
-
-// Add encodes and adds a new request to the batch
-func (r *Requests) Add(service, name string, val interface{}) (int, error) {
- enc, err := rlp.EncodeToBytes(val)
- if err != nil {
- return -1, err
- }
- *r = append(*r, Request{
- Service: service,
- Name: name,
- Params: enc,
- })
- return len(*r) - 1, nil
-}
-
-// Get decodes the reply to the i-th request in the batch
-func (r Replies) Get(i int, val interface{}) error {
- if i < 0 || i >= len(r) {
- return ErrNoReply
- }
- return rlp.DecodeBytes(r[i], val)
-}
-
-const (
- IntNonNegative = iota
- IntNegative
- IntPlusInf
- IntMinusInf
-)
-
-// IntOrInf is the encoding format for arbitrary length signed integers that can also
-// hold the values of +Inf or -Inf
-type IntOrInf struct {
- Type uint8
- Value big.Int
-}
-
-// BigInt returns the value as a big.Int or panics if the value is infinity
-func (i *IntOrInf) BigInt() *big.Int {
- switch i.Type {
- case IntNonNegative:
- return new(big.Int).Set(&i.Value)
- case IntNegative:
- return new(big.Int).Neg(&i.Value)
- case IntPlusInf:
- panic(nil) // caller should check Inf() before trying to convert to big.Int
- case IntMinusInf:
- panic(nil)
- }
- return &big.Int{} // invalid type decodes to 0 value
-}
-
-// Inf returns 1 if the value is +Inf, -1 if it is -Inf, 0 otherwise
-func (i *IntOrInf) Inf() int {
- switch i.Type {
- case IntPlusInf:
- return 1
- case IntMinusInf:
- return -1
- }
- return 0 // invalid type decodes to 0 value
-}
-
-// Int64 limits the value between MinInt64 and MaxInt64 (even if it is +-Inf) and returns an int64 type
-func (i *IntOrInf) Int64() int64 {
- switch i.Type {
- case IntNonNegative:
- if i.Value.IsInt64() {
- return i.Value.Int64()
- } else {
- return math.MaxInt64
- }
- case IntNegative:
- if i.Value.IsInt64() {
- return -i.Value.Int64()
- } else {
- return math.MinInt64
- }
- case IntPlusInf:
- return math.MaxInt64
- case IntMinusInf:
- return math.MinInt64
- }
- return 0 // invalid type decodes to 0 value
-}
-
-// SetBigInt sets the value to the given big.Int
-func (i *IntOrInf) SetBigInt(v *big.Int) {
- if v.Sign() >= 0 {
- i.Type = IntNonNegative
- i.Value.Set(v)
- } else {
- i.Type = IntNegative
- i.Value.Neg(v)
- }
-}
-
-// SetInt64 sets the value to the given int64. Note that MaxInt64 translates to +Inf
-// while MinInt64 translates to -Inf.
-func (i *IntOrInf) SetInt64(v int64) {
- if v >= 0 {
- if v == math.MaxInt64 {
- i.Type = IntPlusInf
- } else {
- i.Type = IntNonNegative
- i.Value.SetInt64(v)
- }
- } else {
- if v == math.MinInt64 {
- i.Type = IntMinusInf
- } else {
- i.Type = IntNegative
- i.Value.SetInt64(-v)
- }
- }
-}
-
-// SetInf sets the value to +Inf or -Inf
-func (i *IntOrInf) SetInf(sign int) {
- if sign == 1 {
- i.Type = IntPlusInf
- } else {
- i.Type = IntMinusInf
- }
-}
diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go
deleted file mode 100644
index b09f7bb50..000000000
--- a/les/vflux/server/balance.go
+++ /dev/null
@@ -1,693 +0,0 @@
-// Copyright 2020 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 .
-
-package server
-
-import (
- "errors"
- "math"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-var errBalanceOverflow = errors.New("balance overflow")
-
-const maxBalance = math.MaxInt64 // maximum allowed balance value
-
-const (
- balanceCallbackUpdate = iota // called when priority drops below the last minimum estimate
- balanceCallbackZero // called when priority drops to zero (positive balance exhausted)
- balanceCallbackCount // total number of balance callbacks
-)
-
-// PriceFactors determine the pricing policy (may apply either to positive or
-// negative balances which may have different factors).
-// - TimeFactor is cost unit per nanosecond of connection time
-// - CapacityFactor is cost unit per nanosecond of connection time per 1000000 capacity
-// - RequestFactor is cost unit per request "realCost" unit
-type PriceFactors struct {
- TimeFactor, CapacityFactor, RequestFactor float64
-}
-
-// connectionPrice returns the price of connection per nanosecond at the given capacity
-// and the estimated average request cost.
-func (p PriceFactors) connectionPrice(cap uint64, avgReqCost float64) float64 {
- return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 + p.RequestFactor*avgReqCost
-}
-
-type (
- // nodePriority interface provides current and estimated future priorities on demand
- nodePriority interface {
- // priority should return the current priority of the node (higher is better)
- priority(cap uint64) int64
- // estimatePriority should return a lower estimate for the minimum of the node priority
- // value starting from the current moment until the given time. If the priority goes
- // under the returned estimate before the specified moment then it is the caller's
- // responsibility to signal with updateFlag.
- estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64
- }
-
- // ReadOnlyBalance provides read-only operations on the node balance
- ReadOnlyBalance interface {
- nodePriority
- GetBalance() (uint64, uint64)
- GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue)
- GetPriceFactors() (posFactor, negFactor PriceFactors)
- }
-
- // ConnectedBalance provides operations permitted on connected nodes (non-read-only
- // operations are not permitted inside a BalanceOperation)
- ConnectedBalance interface {
- ReadOnlyBalance
- SetPriceFactors(posFactor, negFactor PriceFactors)
- RequestServed(cost uint64) uint64
- }
-
- // AtomicBalanceOperator provides operations permitted in an atomic BalanceOperation
- AtomicBalanceOperator interface {
- ReadOnlyBalance
- AddBalance(amount int64) (uint64, uint64, error)
- SetBalance(pos, neg uint64) error
- }
-)
-
-// nodeBalance keeps track of the positive and negative balances of a connected
-// client and calculates actual and projected future priority values.
-// Implements nodePriority interface.
-type nodeBalance struct {
- bt *balanceTracker
- lock sync.RWMutex
- node *enode.Node
- connAddress string
- active, hasPriority, setFlags bool
- capacity uint64
- balance balance
- posFactor, negFactor PriceFactors
- sumReqCost uint64
- lastUpdate, nextUpdate, initTime mclock.AbsTime
- updateEvent mclock.Timer
- // since only a limited and fixed number of callbacks are needed, they are
- // stored in a fixed size array ordered by priority threshold.
- callbacks [balanceCallbackCount]balanceCallback
- // callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active)
- callbackIndex [balanceCallbackCount]int
- callbackCount int // number of active callbacks
-}
-
-// balance represents a pair of positive and negative balances
-type balance struct {
- pos, neg utils.ExpiredValue
- posExp, negExp utils.ValueExpirer
-}
-
-// posValue returns the value of positive balance at a given timestamp.
-func (b balance) posValue(now mclock.AbsTime) uint64 {
- return b.pos.Value(b.posExp.LogOffset(now))
-}
-
-// negValue returns the value of negative balance at a given timestamp.
-func (b balance) negValue(now mclock.AbsTime) uint64 {
- return b.neg.Value(b.negExp.LogOffset(now))
-}
-
-// addValue adds the value of a given amount to the balance. The original value and
-// updated value will also be returned if the addition is successful.
-// Returns the error if the given value is too large and the value overflows.
-func (b *balance) addValue(now mclock.AbsTime, amount int64, pos bool, force bool) (uint64, uint64, int64, error) {
- var (
- val utils.ExpiredValue
- offset utils.Fixed64
- )
- if pos {
- offset, val = b.posExp.LogOffset(now), b.pos
- } else {
- offset, val = b.negExp.LogOffset(now), b.neg
- }
- old := val.Value(offset)
- if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) {
- if !force {
- return old, 0, 0, errBalanceOverflow
- }
- val = utils.ExpiredValue{}
- amount = maxBalance
- }
- net := val.Add(amount, offset)
- if pos {
- b.pos = val
- } else {
- b.neg = val
- }
- return old, val.Value(offset), net, nil
-}
-
-// setValue sets the internal balance amount to the given values. Returns the
-// error if the given value is too large.
-func (b *balance) setValue(now mclock.AbsTime, pos uint64, neg uint64) error {
- if pos > maxBalance || neg > maxBalance {
- return errBalanceOverflow
- }
- var pb, nb utils.ExpiredValue
- pb.Add(int64(pos), b.posExp.LogOffset(now))
- nb.Add(int64(neg), b.negExp.LogOffset(now))
- b.pos = pb
- b.neg = nb
- return nil
-}
-
-// balanceCallback represents a single callback that is activated when client priority
-// reaches the given threshold
-type balanceCallback struct {
- id int
- threshold int64
- callback func()
-}
-
-// GetBalance returns the current positive and negative balance.
-func (n *nodeBalance) GetBalance() (uint64, uint64) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- now := n.bt.clock.Now()
- n.updateBalance(now)
- return n.balance.posValue(now), n.balance.negValue(now)
-}
-
-// GetRawBalance returns the current positive and negative balance
-// but in the raw(expired value) format.
-func (n *nodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- now := n.bt.clock.Now()
- n.updateBalance(now)
- return n.balance.pos, n.balance.neg
-}
-
-// AddBalance adds the given amount to the positive balance and returns the balance
-// before and after the operation. Exceeding maxBalance results in an error (balance is
-// unchanged) while adding a negative amount higher than the current balance results in
-// zero balance.
-// Note: this function should run inside a NodeStateMachine operation
-func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) {
- var (
- err error
- old, new uint64
- now = n.bt.clock.Now()
- callbacks []func()
- setPriority bool
- )
- // Operation with holding the lock
- n.bt.updateTotalBalance(n, func() bool {
- n.updateBalance(now)
- if old, new, _, err = n.balance.addValue(now, amount, true, false); err != nil {
- return false
- }
- callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus()
- n.storeBalance(true, false)
- return true
- })
- if err != nil {
- return old, old, err
- }
- // Operation without holding the lock
- for _, cb := range callbacks {
- cb()
- }
- if n.setFlags {
- if setPriority {
- n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
- }
- // Note: priority flag is automatically removed by the zero priority callback if necessary
- n.signalPriorityUpdate()
- }
- return old, new, nil
-}
-
-// SetBalance sets the positive and negative balance to the given values
-// Note: this function should run inside a NodeStateMachine operation
-func (n *nodeBalance) SetBalance(pos, neg uint64) error {
- var (
- now = n.bt.clock.Now()
- callbacks []func()
- setPriority bool
- )
- // Operation with holding the lock
- n.bt.updateTotalBalance(n, func() bool {
- n.updateBalance(now)
- if err := n.balance.setValue(now, pos, neg); err != nil {
- return false
- }
- callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus()
- n.storeBalance(true, true)
- return true
- })
- // Operation without holding the lock
- for _, cb := range callbacks {
- cb()
- }
- if n.setFlags {
- if setPriority {
- n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
- }
- // Note: priority flag is automatically removed by the zero priority callback if necessary
- n.signalPriorityUpdate()
- }
- return nil
-}
-
-// RequestServed should be called after serving a request for the given peer
-func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) {
- n.lock.Lock()
-
- var (
- check bool
- fcost = float64(cost)
- now = n.bt.clock.Now()
- )
- n.updateBalance(now)
- if !n.balance.pos.IsZero() {
- posCost := -int64(fcost * n.posFactor.RequestFactor)
- if posCost == 0 {
- fcost = 0
- newBalance = n.balance.posValue(now)
- } else {
- var net int64
- _, newBalance, net, _ = n.balance.addValue(now, posCost, true, false)
- if posCost == net {
- fcost = 0
- } else {
- fcost *= 1 - float64(net)/float64(posCost)
- }
- check = true
- }
- }
- if fcost > 0 && n.negFactor.RequestFactor != 0 {
- n.balance.addValue(now, int64(fcost*n.negFactor.RequestFactor), false, false)
- check = true
- }
- n.sumReqCost += cost
-
- var callbacks []func()
- if check {
- callbacks = n.checkCallbacks(now)
- }
- n.lock.Unlock()
-
- if callbacks != nil {
- n.bt.ns.Operation(func() {
- for _, cb := range callbacks {
- cb()
- }
- })
- }
- return
-}
-
-// priority returns the actual priority based on the current balance
-func (n *nodeBalance) priority(capacity uint64) int64 {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- now := n.bt.clock.Now()
- n.updateBalance(now)
- return n.balanceToPriority(now, n.balance, capacity)
-}
-
-// EstMinPriority gives a lower estimate for the priority at a given time in the future.
-// An average request cost per time is assumed that is twice the average cost per time
-// in the current session.
-// If update is true then a priority callback is added that turns updateFlag on and off
-// in case the priority goes below the estimated minimum.
-func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- now := n.bt.clock.Now()
- n.updateBalance(now)
-
- b := n.balance // copy the balance
- if addBalance != 0 {
- b.addValue(now, addBalance, true, true)
- }
- if future > 0 {
- var avgReqCost float64
- dt := time.Duration(n.lastUpdate - n.initTime)
- if dt > time.Second {
- avgReqCost = float64(n.sumReqCost) * 2 / float64(dt)
- }
- b = n.reducedBalance(b, now, future, capacity, avgReqCost)
- }
- if bias > 0 {
- b = n.reducedBalance(b, now.Add(future), bias, capacity, 0)
- }
- pri := n.balanceToPriority(now, b, capacity)
- // Ensure that biased estimates are always lower than actual priorities, even if
- // the bias is very small.
- // This ensures that two nodes will not ping-pong update signals forever if both of
- // them have zero estimated priority drop in the projected future.
- current := n.balanceToPriority(now, n.balance, capacity)
- if pri >= current {
- pri = current - 1
- }
- if update {
- n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate)
- }
- return pri
-}
-
-// SetPriceFactors sets the price factors. TimeFactor is the price of a nanosecond of
-// connection while RequestFactor is the price of a request cost unit.
-func (n *nodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) {
- n.lock.Lock()
- now := n.bt.clock.Now()
- n.updateBalance(now)
- n.posFactor, n.negFactor = posFactor, negFactor
- callbacks := n.checkCallbacks(now)
- n.lock.Unlock()
- if callbacks != nil {
- n.bt.ns.Operation(func() {
- for _, cb := range callbacks {
- cb()
- }
- })
- }
-}
-
-// GetPriceFactors returns the price factors
-func (n *nodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) {
- n.lock.Lock()
- defer n.lock.Unlock()
-
- return n.posFactor, n.negFactor
-}
-
-// activate starts time/capacity cost deduction.
-func (n *nodeBalance) activate() {
- n.bt.updateTotalBalance(n, func() bool {
- if n.active {
- return false
- }
- n.active = true
- n.lastUpdate = n.bt.clock.Now()
- return true
- })
-}
-
-// deactivate stops time/capacity cost deduction and saves the balances in the database
-func (n *nodeBalance) deactivate() {
- n.bt.updateTotalBalance(n, func() bool {
- if !n.active {
- return false
- }
- n.updateBalance(n.bt.clock.Now())
- if n.updateEvent != nil {
- n.updateEvent.Stop()
- n.updateEvent = nil
- }
- n.storeBalance(true, true)
- n.active = false
- return true
- })
-}
-
-// updateBalance updates balance based on the time factor
-func (n *nodeBalance) updateBalance(now mclock.AbsTime) {
- if n.active && now > n.lastUpdate {
- n.balance = n.reducedBalance(n.balance, n.lastUpdate, time.Duration(now-n.lastUpdate), n.capacity, 0)
- n.lastUpdate = now
- }
-}
-
-// storeBalance stores the positive and/or negative balance of the node in the database
-func (n *nodeBalance) storeBalance(pos, neg bool) {
- if pos {
- n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos)
- }
- if neg {
- n.bt.storeBalance([]byte(n.connAddress), true, n.balance.neg)
- }
-}
-
-// addCallback sets up a one-time callback to be called when priority reaches
-// the threshold. If it has already reached the threshold the callback is called
-// immediately.
-// Note: should be called while n.lock is held
-// Note 2: the callback function runs inside a NodeStateMachine operation
-func (n *nodeBalance) addCallback(id int, threshold int64, callback func()) {
- n.removeCallback(id)
- idx := 0
- for idx < n.callbackCount && threshold > n.callbacks[idx].threshold {
- idx++
- }
- for i := n.callbackCount - 1; i >= idx; i-- {
- n.callbackIndex[n.callbacks[i].id]++
- n.callbacks[i+1] = n.callbacks[i]
- }
- n.callbackCount++
- n.callbackIndex[id] = idx
- n.callbacks[idx] = balanceCallback{id, threshold, callback}
- now := n.bt.clock.Now()
- n.updateBalance(now)
- n.scheduleCheck(now)
-}
-
-// removeCallback removes the given callback and returns true if it was active
-// Note: should be called while n.lock is held
-func (n *nodeBalance) removeCallback(id int) bool {
- idx := n.callbackIndex[id]
- if idx == -1 {
- return false
- }
- n.callbackIndex[id] = -1
- for i := idx; i < n.callbackCount-1; i++ {
- n.callbackIndex[n.callbacks[i+1].id]--
- n.callbacks[i] = n.callbacks[i+1]
- }
- n.callbackCount--
- return true
-}
-
-// checkCallbacks checks whether the threshold of any of the active callbacks
-// have been reached and returns triggered callbacks.
-// Note: checkCallbacks assumes that the balance has been recently updated.
-func (n *nodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) {
- if n.callbackCount == 0 || n.capacity == 0 {
- return
- }
- pri := n.balanceToPriority(now, n.balance, n.capacity)
- for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri {
- n.callbackCount--
- n.callbackIndex[n.callbacks[n.callbackCount].id] = -1
- callbacks = append(callbacks, n.callbacks[n.callbackCount].callback)
- }
- n.scheduleCheck(now)
- return
-}
-
-// scheduleCheck sets up or updates a scheduled event to ensure that it will be called
-// again just after the next threshold has been reached.
-func (n *nodeBalance) scheduleCheck(now mclock.AbsTime) {
- if n.callbackCount != 0 {
- d, ok := n.timeUntil(n.callbacks[n.callbackCount-1].threshold)
- if !ok {
- n.nextUpdate = 0
- n.updateAfter(0)
- return
- }
- if n.nextUpdate == 0 || n.nextUpdate > now.Add(d) {
- if d > time.Second {
- // Note: if the scheduled update is not in the very near future then we
- // schedule the update a bit earlier. This way we do need to update a few
- // extra times but don't need to reschedule every time a processed request
- // brings the expected firing time a little bit closer.
- d = ((d - time.Second) * 7 / 8) + time.Second
- }
- n.nextUpdate = now.Add(d)
- n.updateAfter(d)
- }
- } else {
- n.nextUpdate = 0
- n.updateAfter(0)
- }
-}
-
-// updateAfter schedules a balance update and callback check in the future
-func (n *nodeBalance) updateAfter(dt time.Duration) {
- if n.updateEvent == nil || n.updateEvent.Stop() {
- if dt == 0 {
- n.updateEvent = nil
- } else {
- n.updateEvent = n.bt.clock.AfterFunc(dt, func() {
- var callbacks []func()
- n.lock.Lock()
- if n.callbackCount != 0 {
- now := n.bt.clock.Now()
- n.updateBalance(now)
- callbacks = n.checkCallbacks(now)
- }
- n.lock.Unlock()
- if callbacks != nil {
- n.bt.ns.Operation(func() {
- for _, cb := range callbacks {
- cb()
- }
- })
- }
- })
- }
- }
-}
-
-// balanceExhausted should be called when the positive balance is exhausted (priority goes to zero/negative)
-// Note: this function should run inside a NodeStateMachine operation
-func (n *nodeBalance) balanceExhausted() {
- n.lock.Lock()
- n.storeBalance(true, false)
- n.hasPriority = false
- n.lock.Unlock()
- if n.setFlags {
- n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.priorityFlag, 0)
- }
-}
-
-// checkPriorityStatus checks whether the node has gained priority status and sets the priority
-// callback and flag if necessary. It assumes that the balance has been recently updated.
-// Note that the priority flag has to be set by the caller after the mutex has been released.
-func (n *nodeBalance) checkPriorityStatus() bool {
- if !n.hasPriority && !n.balance.pos.IsZero() {
- n.hasPriority = true
- n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() })
- return true
- }
- return false
-}
-
-// signalPriorityUpdate signals that the priority fell below the previous minimum estimate
-// Note: this function should run inside a NodeStateMachine operation
-func (n *nodeBalance) signalPriorityUpdate() {
- n.bt.ns.SetStateSub(n.node, n.bt.setup.updateFlag, nodestate.Flags{}, 0)
- n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.updateFlag, 0)
-}
-
-// setCapacity updates the capacity value used for priority calculation
-// Note: capacity should never be zero
-// Note 2: this function should run inside a NodeStateMachine operation
-func (n *nodeBalance) setCapacity(capacity uint64) {
- n.lock.Lock()
- now := n.bt.clock.Now()
- n.updateBalance(now)
- n.capacity = capacity
- callbacks := n.checkCallbacks(now)
- n.lock.Unlock()
- for _, cb := range callbacks {
- cb()
- }
-}
-
-// balanceToPriority converts a balance to a priority value. Lower priority means
-// first to disconnect. Positive balance translates to positive priority. If positive
-// balance is zero then negative balance translates to a negative priority.
-func (n *nodeBalance) balanceToPriority(now mclock.AbsTime, b balance, capacity uint64) int64 {
- pos := b.posValue(now)
- if pos > 0 {
- return int64(pos / capacity)
- }
- return -int64(b.negValue(now))
-}
-
-// priorityToBalance converts a target priority to a requested balance value.
-// If the priority is negative, then minimal negative balance is returned;
-// otherwise the minimal positive balance is returned.
-func (n *nodeBalance) priorityToBalance(priority int64, capacity uint64) (uint64, uint64) {
- if priority > 0 {
- return uint64(priority) * n.capacity, 0
- }
- return 0, uint64(-priority)
-}
-
-// reducedBalance estimates the reduced balance at a given time in the future based
-// on the given balance, the time factor and an estimated average request cost per time ratio
-func (n *nodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance {
- // since the costs are applied continuously during the dt time period we calculate
- // the expiration offset at the middle of the period
- var (
- at = start.Add(dt / 2)
- dtf = float64(dt)
- )
- if !b.pos.IsZero() {
- factor := n.posFactor.connectionPrice(capacity, avgReqCost)
- diff := -int64(dtf * factor)
- _, _, net, _ := b.addValue(at, diff, true, false)
- if net == diff {
- dtf = 0
- } else {
- dtf += float64(net) / factor
- }
- }
- if dtf > 0 {
- factor := n.negFactor.connectionPrice(capacity, avgReqCost)
- b.addValue(at, int64(dtf*factor), false, false)
- }
- return b
-}
-
-// timeUntil calculates the remaining time needed to reach a given priority level
-// assuming that no requests are processed until then. If the given level is never
-// reached then (0, false) is returned. If it has already been reached then (0, true)
-// is returned.
-// Note: the function assumes that the balance has been recently updated and
-// calculates the time starting from the last update.
-func (n *nodeBalance) timeUntil(priority int64) (time.Duration, bool) {
- var (
- now = n.bt.clock.Now()
- pos = n.balance.posValue(now)
- targetPos, targetNeg = n.priorityToBalance(priority, n.capacity)
- diffTime float64
- )
- if pos > 0 {
- timePrice := n.posFactor.connectionPrice(n.capacity, 0)
- if timePrice < 1e-100 {
- return 0, false
- }
- if targetPos > 0 {
- if targetPos > pos {
- return 0, true
- }
- diffTime = float64(pos-targetPos) / timePrice
- return time.Duration(diffTime), true
- } else {
- diffTime = float64(pos) / timePrice
- }
- } else {
- if targetPos > 0 {
- return 0, true
- }
- }
- neg := n.balance.negValue(now)
- if targetNeg > neg {
- timePrice := n.negFactor.connectionPrice(n.capacity, 0)
- if timePrice < 1e-100 {
- return 0, false
- }
- diffTime += float64(targetNeg-neg) / timePrice
- }
- return time.Duration(diffTime), true
-}
diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go
deleted file mode 100644
index 7c100aab5..000000000
--- a/les/vflux/server/balance_test.go
+++ /dev/null
@@ -1,439 +0,0 @@
-// Copyright 2020 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 .
-
-package server
-
-import (
- "math"
- "math/rand"
- "reflect"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/ethdb/memorydb"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-type zeroExpirer struct{}
-
-func (z zeroExpirer) SetRate(now mclock.AbsTime, rate float64) {}
-func (z zeroExpirer) SetLogOffset(now mclock.AbsTime, logOffset utils.Fixed64) {}
-func (z zeroExpirer) LogOffset(now mclock.AbsTime) utils.Fixed64 { return 0 }
-
-type balanceTestClient struct{}
-
-func (client balanceTestClient) FreeClientId() string { return "" }
-
-type balanceTestSetup struct {
- clock *mclock.Simulated
- db ethdb.KeyValueStore
- ns *nodestate.NodeStateMachine
- setup *serverSetup
- bt *balanceTracker
-}
-
-func newBalanceTestSetup(db ethdb.KeyValueStore, posExp, negExp utils.ValueExpirer) *balanceTestSetup {
- // Initialize and customize the setup for the balance testing
- clock := &mclock.Simulated{}
- setup := newServerSetup()
- setup.clientField = setup.setup.NewField("balanceTestClient", reflect.TypeOf(balanceTestClient{}))
-
- ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
- if posExp == nil {
- posExp = zeroExpirer{}
- }
- if negExp == nil {
- negExp = zeroExpirer{}
- }
- if db == nil {
- db = memorydb.New()
- }
- bt := newBalanceTracker(ns, setup, db, clock, posExp, negExp)
- ns.Start()
- return &balanceTestSetup{
- clock: clock,
- db: db,
- ns: ns,
- setup: setup,
- bt: bt,
- }
-}
-
-func (b *balanceTestSetup) newNode(capacity uint64) *nodeBalance {
- node := enode.SignNull(&enr.Record{}, enode.ID{})
- b.ns.SetField(node, b.setup.clientField, balanceTestClient{})
- if capacity != 0 {
- b.ns.SetField(node, b.setup.capacityField, capacity)
- }
- n, _ := b.ns.GetField(node, b.setup.balanceField).(*nodeBalance)
- return n
-}
-
-func (b *balanceTestSetup) setBalance(node *nodeBalance, pos, neg uint64) (err error) {
- b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) {
- err = balance.SetBalance(pos, neg)
- })
- return
-}
-
-func (b *balanceTestSetup) addBalance(node *nodeBalance, add int64) (old, new uint64, err error) {
- b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) {
- old, new, err = balance.AddBalance(add)
- })
- return
-}
-
-func (b *balanceTestSetup) stop() {
- b.bt.stop()
- b.ns.Stop()
-}
-
-func TestAddBalance(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
-
- node := b.newNode(1000)
- var inputs = []struct {
- delta int64
- expect [2]uint64
- total uint64
- expectErr bool
- }{
- {100, [2]uint64{0, 100}, 100, false},
- {-100, [2]uint64{100, 0}, 0, false},
- {-100, [2]uint64{0, 0}, 0, false},
- {1, [2]uint64{0, 1}, 1, false},
- {maxBalance, [2]uint64{0, 0}, 0, true},
- }
- for _, i := range inputs {
- old, new, err := b.addBalance(node, i.delta)
- if i.expectErr {
- if err == nil {
- t.Fatalf("Expect get error but nil")
- }
- continue
- } else if err != nil {
- t.Fatalf("Expect get no error but %v", err)
- }
- if old != i.expect[0] || new != i.expect[1] {
- t.Fatalf("Positive balance mismatch, got %v -> %v", old, new)
- }
- if b.bt.TotalTokenAmount() != i.total {
- t.Fatalf("Total positive balance mismatch, want %v, got %v", i.total, b.bt.TotalTokenAmount())
- }
- }
-}
-
-func TestSetBalance(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
- node := b.newNode(1000)
-
- var inputs = []struct {
- pos, neg uint64
- }{
- {1000, 0},
- {0, 1000},
- {1000, 1000},
- }
- for _, i := range inputs {
- b.setBalance(node, i.pos, i.neg)
- pos, neg := node.GetBalance()
- if pos != i.pos {
- t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos)
- }
- if neg != i.neg {
- t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg)
- }
- }
-}
-
-func TestBalanceTimeCost(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
- node := b.newNode(1000)
-
- node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
- b.setBalance(node, uint64(time.Minute), 0) // 1 minute time allowance
-
- var inputs = []struct {
- runTime time.Duration
- expPos uint64
- expNeg uint64
- }{
- {time.Second, uint64(time.Second * 59), 0},
- {0, uint64(time.Second * 59), 0},
- {time.Second * 59, 0, 0},
- {time.Second, 0, uint64(time.Second)},
- }
- for _, i := range inputs {
- b.clock.Run(i.runTime)
- if pos, _ := node.GetBalance(); pos != i.expPos {
- t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
- }
- if _, neg := node.GetBalance(); neg != i.expNeg {
- t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
- }
- }
-
- b.setBalance(node, uint64(time.Minute), 0) // Refill 1 minute time allowance
- for _, i := range inputs {
- b.clock.Run(i.runTime)
- if pos, _ := node.GetBalance(); pos != i.expPos {
- t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
- }
- if _, neg := node.GetBalance(); neg != i.expNeg {
- t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
- }
- }
-}
-
-func TestBalanceReqCost(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
- node := b.newNode(1000)
- node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
-
- b.setBalance(node, uint64(time.Minute), 0) // 1 minute time serving time allowance
- var inputs = []struct {
- reqCost uint64
- expPos uint64
- expNeg uint64
- }{
- {uint64(time.Second), uint64(time.Second * 59), 0},
- {0, uint64(time.Second * 59), 0},
- {uint64(time.Second * 59), 0, 0},
- {uint64(time.Second), 0, uint64(time.Second)},
- }
- for _, i := range inputs {
- node.RequestServed(i.reqCost)
- if pos, _ := node.GetBalance(); pos != i.expPos {
- t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
- }
- if _, neg := node.GetBalance(); neg != i.expNeg {
- t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
- }
- }
-}
-
-func TestBalanceToPriority(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
- node := b.newNode(1000)
- node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
-
- var inputs = []struct {
- pos uint64
- neg uint64
- priority int64
- }{
- {1000, 0, 1},
- {2000, 0, 2}, // Higher balance, higher priority value
- {0, 0, 0},
- {0, 1000, -1000},
- }
- for _, i := range inputs {
- b.setBalance(node, i.pos, i.neg)
- priority := node.priority(1000)
- if priority != i.priority {
- t.Fatalf("priority mismatch, want %v, got %v", i.priority, priority)
- }
- }
-}
-
-func TestEstimatedPriority(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
- node := b.newNode(1000000000)
- node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
- b.setBalance(node, uint64(time.Minute), 0)
- var inputs = []struct {
- runTime time.Duration // time cost
- futureTime time.Duration // diff of future time
- reqCost uint64 // single request cost
- priority int64 // expected estimated priority
- }{
- {time.Second, time.Second, 0, 58},
- {0, time.Second, 0, 58},
-
- // 2 seconds time cost, 1 second estimated time cost, 10^9 request cost,
- // 10^9 estimated request cost per second.
- {time.Second, time.Second, 1000000000, 55},
-
- // 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost,
- // 4*10^9 estimated request cost.
- {time.Second, 3 * time.Second, 1000000000, 48},
-
- // All positive balance is used up
- {time.Second * 55, 0, 0, -1},
-
- // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec.
- {0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29},
- }
- for _, i := range inputs {
- b.clock.Run(i.runTime)
- node.RequestServed(i.reqCost)
- priority := node.estimatePriority(1000000000, 0, i.futureTime, 0, false)
- if priority != i.priority {
- t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority)
- }
- }
-}
-
-func TestPositiveBalanceCounting(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
-
- var nodes []*nodeBalance
- for i := 0; i < 100; i += 1 {
- node := b.newNode(1000000)
- node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
- nodes = append(nodes, node)
- }
-
- // Allocate service token
- var sum uint64
- for i := 0; i < 100; i += 1 {
- amount := int64(rand.Intn(100) + 100)
- b.addBalance(nodes[i], amount)
- sum += uint64(amount)
- }
- if b.bt.TotalTokenAmount() != sum {
- t.Fatalf("Invalid token amount")
- }
-
- // Change client status
- for i := 0; i < 100; i += 1 {
- if rand.Intn(2) == 0 {
- b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1))
- }
- }
- if b.bt.TotalTokenAmount() != sum {
- t.Fatalf("Invalid token amount")
- }
- for i := 0; i < 100; i += 1 {
- if rand.Intn(2) == 0 {
- b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1))
- }
- }
- if b.bt.TotalTokenAmount() != sum {
- t.Fatalf("Invalid token amount")
- }
-}
-
-func TestCallbackChecking(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
- node := b.newNode(1000000)
- node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
-
- var inputs = []struct {
- priority int64
- expDiff time.Duration
- }{
- {500, time.Millisecond * 500},
- {0, time.Second},
- {-int64(time.Second), 2 * time.Second},
- }
- b.setBalance(node, uint64(time.Second), 0)
- for _, i := range inputs {
- diff, _ := node.timeUntil(i.priority)
- if diff != i.expDiff {
- t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff)
- }
- }
-}
-
-func TestCallback(t *testing.T) {
- b := newBalanceTestSetup(nil, nil, nil)
- defer b.stop()
- node := b.newNode(1000)
- node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
-
- callCh := make(chan struct{}, 1)
- b.setBalance(node, uint64(time.Minute), 0)
- node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} })
-
- b.clock.Run(time.Minute)
- select {
- case <-callCh:
- case <-time.NewTimer(time.Second).C:
- t.Fatalf("Callback hasn't been called yet")
- }
-
- b.setBalance(node, uint64(time.Minute), 0)
- node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} })
- node.removeCallback(balanceCallbackZero)
-
- b.clock.Run(time.Minute)
- select {
- case <-callCh:
- t.Fatalf("Callback shouldn't be called")
- case <-time.NewTimer(time.Millisecond * 100).C:
- }
-}
-
-func TestBalancePersistence(t *testing.T) {
- posExp := &utils.Expirer{}
- negExp := &utils.Expirer{}
- posExp.SetRate(0, math.Log(2)/float64(time.Hour*2)) // halves every two hours
- negExp.SetRate(0, math.Log(2)/float64(time.Hour)) // halves every hour
- setup := newBalanceTestSetup(nil, posExp, negExp)
-
- exp := func(balance *nodeBalance, expPos, expNeg uint64) {
- pos, neg := balance.GetBalance()
- if pos != expPos {
- t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos)
- }
- if neg != expNeg {
- t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos)
- }
- }
- expTotal := func(expTotal uint64) {
- total := setup.bt.TotalTokenAmount()
- if total != expTotal {
- t.Fatalf("Total token amount incorrect, want %v, got %v", expTotal, total)
- }
- }
-
- expTotal(0)
- balance := setup.newNode(0)
- expTotal(0)
- setup.setBalance(balance, 16000000000, 16000000000)
- exp(balance, 16000000000, 16000000000)
- expTotal(16000000000)
-
- setup.clock.Run(time.Hour * 2)
- exp(balance, 8000000000, 4000000000)
- expTotal(8000000000)
- setup.stop()
-
- // Test the functionalities after restart
- setup = newBalanceTestSetup(setup.db, posExp, negExp)
- expTotal(8000000000)
- balance = setup.newNode(0)
- exp(balance, 8000000000, 4000000000)
- expTotal(8000000000)
- setup.clock.Run(time.Hour * 2)
- exp(balance, 4000000000, 1000000000)
- expTotal(4000000000)
- setup.stop()
-}
diff --git a/les/vflux/server/balance_tracker.go b/les/vflux/server/balance_tracker.go
deleted file mode 100644
index 9695e7963..000000000
--- a/les/vflux/server/balance_tracker.go
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright 2020 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 .
-
-package server
-
-import (
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-const (
- posThreshold = 1000000 // minimum positive balance that is persisted in the database
- negThreshold = 1000000 // minimum negative balance that is persisted in the database
- persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence
-)
-
-// balanceTracker tracks positive and negative balances for connected nodes.
-// After clientField is set externally, a nodeBalance is created and previous
-// balance values are loaded from the database. Both balances are exponentially expired
-// values. Costs are deducted from the positive balance if present, otherwise added to
-// the negative balance. If the capacity is non-zero then a time cost is applied
-// continuously while individual request costs are applied immediately.
-// The two balances are translated into a single priority value that also depends
-// on the actual capacity.
-type balanceTracker struct {
- setup *serverSetup
- clock mclock.Clock
- lock sync.Mutex
- ns *nodestate.NodeStateMachine
- ndb *nodeDB
- posExp, negExp utils.ValueExpirer
-
- posExpTC, negExpTC uint64
- defaultPosFactors, defaultNegFactors PriceFactors
-
- active, inactive utils.ExpiredValue
- balanceTimer *utils.UpdateTimer
- quit chan struct{}
-}
-
-// newBalanceTracker creates a new balanceTracker
-func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db ethdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *balanceTracker {
- ndb := newNodeDB(db, clock)
- bt := &balanceTracker{
- ns: ns,
- setup: setup,
- ndb: ndb,
- clock: clock,
- posExp: posExp,
- negExp: negExp,
- balanceTimer: utils.NewUpdateTimer(clock, time.Second*10),
- quit: make(chan struct{}),
- }
- posOffset, negOffset := bt.ndb.getExpiration()
- posExp.SetLogOffset(clock.Now(), posOffset)
- negExp.SetLogOffset(clock.Now(), negOffset)
-
- // Load all persisted balance entries of priority nodes,
- // calculate the total number of issued service tokens.
- bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool {
- bt.inactive.AddExp(balance)
- return true
- })
-
- ns.SubscribeField(bt.setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
- n, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance)
- if n == nil {
- return
- }
-
- ov, _ := oldValue.(uint64)
- nv, _ := newValue.(uint64)
- if ov == 0 && nv != 0 {
- n.activate()
- }
- if nv != 0 {
- n.setCapacity(nv)
- }
- if ov != 0 && nv == 0 {
- n.deactivate()
- }
- })
- ns.SubscribeField(bt.setup.clientField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
- type peer interface {
- FreeClientId() string
- }
- if newValue != nil {
- n := bt.newNodeBalance(node, newValue.(peer).FreeClientId(), true)
- bt.lock.Lock()
- n.SetPriceFactors(bt.defaultPosFactors, bt.defaultNegFactors)
- bt.lock.Unlock()
- ns.SetFieldSub(node, bt.setup.balanceField, n)
- } else {
- ns.SetStateSub(node, nodestate.Flags{}, bt.setup.priorityFlag, 0)
- if b, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance); b != nil {
- b.deactivate()
- }
- ns.SetFieldSub(node, bt.setup.balanceField, nil)
- }
- })
-
- // The positive and negative balances of clients are stored in database
- // and both of these decay exponentially over time. Delete them if the
- // value is small enough.
- bt.ndb.evictCallBack = bt.canDropBalance
-
- go func() {
- for {
- select {
- case <-clock.After(persistExpirationRefresh):
- now := clock.Now()
- bt.ndb.setExpiration(posExp.LogOffset(now), negExp.LogOffset(now))
- case <-bt.quit:
- return
- }
- }
- }()
- return bt
-}
-
-// Stop saves expiration offset and unsaved node balances and shuts balanceTracker down
-func (bt *balanceTracker) stop() {
- now := bt.clock.Now()
- bt.ndb.setExpiration(bt.posExp.LogOffset(now), bt.negExp.LogOffset(now))
- close(bt.quit)
- bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
- if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok {
- n.lock.Lock()
- n.storeBalance(true, true)
- n.lock.Unlock()
- bt.ns.SetField(node, bt.setup.balanceField, nil)
- }
- })
- bt.ndb.close()
-}
-
-// TotalTokenAmount returns the current total amount of service tokens in existence
-func (bt *balanceTracker) TotalTokenAmount() uint64 {
- bt.lock.Lock()
- defer bt.lock.Unlock()
-
- bt.balanceTimer.Update(func(_ time.Duration) bool {
- bt.active = utils.ExpiredValue{}
- bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
- if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok && n.active {
- pos, _ := n.GetRawBalance()
- bt.active.AddExp(pos)
- }
- })
- return true
- })
- total := bt.active
- total.AddExp(bt.inactive)
- return total.Value(bt.posExp.LogOffset(bt.clock.Now()))
-}
-
-// GetPosBalanceIDs lists node IDs with an associated positive balance
-func (bt *balanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) {
- return bt.ndb.getPosBalanceIDs(start, stop, maxCount)
-}
-
-// SetDefaultFactors sets the default price factors applied to subsequently connected clients
-func (bt *balanceTracker) SetDefaultFactors(posFactors, negFactors PriceFactors) {
- bt.lock.Lock()
- bt.defaultPosFactors = posFactors
- bt.defaultNegFactors = negFactors
- bt.lock.Unlock()
-}
-
-// SetExpirationTCs sets positive and negative token expiration time constants.
-// Specified in seconds, 0 means infinite (no expiration).
-func (bt *balanceTracker) SetExpirationTCs(pos, neg uint64) {
- bt.lock.Lock()
- defer bt.lock.Unlock()
-
- bt.posExpTC, bt.negExpTC = pos, neg
- now := bt.clock.Now()
- if pos > 0 {
- bt.posExp.SetRate(now, 1/float64(pos*uint64(time.Second)))
- } else {
- bt.posExp.SetRate(now, 0)
- }
- if neg > 0 {
- bt.negExp.SetRate(now, 1/float64(neg*uint64(time.Second)))
- } else {
- bt.negExp.SetRate(now, 0)
- }
-}
-
-// GetExpirationTCs returns the current positive and negative token expiration
-// time constants
-func (bt *balanceTracker) GetExpirationTCs() (pos, neg uint64) {
- bt.lock.Lock()
- defer bt.lock.Unlock()
-
- return bt.posExpTC, bt.negExpTC
-}
-
-// BalanceOperation allows atomic operations on the balance of a node regardless of whether
-// it is currently connected or not
-func (bt *balanceTracker) BalanceOperation(id enode.ID, connAddress string, cb func(AtomicBalanceOperator)) {
- bt.ns.Operation(func() {
- var nb *nodeBalance
- if node := bt.ns.GetNode(id); node != nil {
- nb, _ = bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance)
- }
- if nb == nil {
- node := enode.SignNull(&enr.Record{}, id)
- nb = bt.newNodeBalance(node, connAddress, false)
- }
- cb(nb)
- })
-}
-
-// newNodeBalance loads balances from the database and creates a nodeBalance instance
-// for the given node. It also sets the priorityFlag and adds balanceCallbackZero if
-// the node has a positive balance.
-// Note: this function should run inside a NodeStateMachine operation
-func (bt *balanceTracker) newNodeBalance(node *enode.Node, connAddress string, setFlags bool) *nodeBalance {
- pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false)
- nb := bt.ndb.getOrNewBalance([]byte(connAddress), true)
- n := &nodeBalance{
- bt: bt,
- node: node,
- setFlags: setFlags,
- connAddress: connAddress,
- balance: balance{pos: pb, neg: nb, posExp: bt.posExp, negExp: bt.negExp},
- initTime: bt.clock.Now(),
- lastUpdate: bt.clock.Now(),
- }
- for i := range n.callbackIndex {
- n.callbackIndex[i] = -1
- }
- if setFlags && n.checkPriorityStatus() {
- n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
- }
- return n
-}
-
-// storeBalance stores either a positive or a negative balance in the database
-func (bt *balanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) {
- if bt.canDropBalance(bt.clock.Now(), neg, value) {
- bt.ndb.delBalance(id, neg) // balance is small enough, drop it directly.
- } else {
- bt.ndb.setBalance(id, neg, value)
- }
-}
-
-// canDropBalance tells whether a positive or negative balance is below the threshold
-// and therefore can be dropped from the database
-func (bt *balanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool {
- if neg {
- return b.Value(bt.negExp.LogOffset(now)) <= negThreshold
- }
- return b.Value(bt.posExp.LogOffset(now)) <= posThreshold
-}
-
-// updateTotalBalance adjusts the total balance after executing given callback.
-func (bt *balanceTracker) updateTotalBalance(n *nodeBalance, callback func() bool) {
- bt.lock.Lock()
- defer bt.lock.Unlock()
-
- n.lock.Lock()
- defer n.lock.Unlock()
-
- original, active := n.balance.pos, n.active
- if !callback() {
- return
- }
- if active {
- bt.active.SubExp(original)
- } else {
- bt.inactive.SubExp(original)
- }
- if n.active {
- bt.active.AddExp(n.balance.pos)
- } else {
- bt.inactive.AddExp(n.balance.pos)
- }
-}
diff --git a/les/vflux/server/clientdb.go b/les/vflux/server/clientdb.go
deleted file mode 100644
index a39cbec36..000000000
--- a/les/vflux/server/clientdb.go
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright 2020 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 .
-
-package server
-
-import (
- "bytes"
- "encoding/binary"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const (
- balanceCacheLimit = 8192 // the maximum number of cached items in service token balance queue
-
- // nodeDBVersion is the version identifier of the node data in db
- //
- // Changelog:
- // Version 0 => 1
- // * Replace `lastTotal` with `meta` in positive balance: version 0=>1
- //
- // Version 1 => 2
- // * Positive Balance and negative balance is changed:
- // * Cumulative time is replaced with expiration
- nodeDBVersion = 2
-
- // dbCleanupCycle is the cycle of db for useless data cleanup
- dbCleanupCycle = time.Hour
-)
-
-var (
- positiveBalancePrefix = []byte("pb:") // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance
- negativeBalancePrefix = []byte("nb:") // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance
- expirationKey = []byte("expiration:") // dbVersion(uint16 big endian) + expirationKey -> posExp, negExp
-)
-
-type nodeDB struct {
- db ethdb.KeyValueStore
- cache *lru.Cache[string, utils.ExpiredValue]
- auxbuf []byte // 37-byte auxiliary buffer for key encoding
- verbuf [2]byte // 2-byte auxiliary buffer for db version
- evictCallBack func(mclock.AbsTime, bool, utils.ExpiredValue) bool // Callback to determine whether the balance can be evicted.
- clock mclock.Clock
- closeCh chan struct{}
- cleanupHook func() // Test hook used for testing
-}
-
-func newNodeDB(db ethdb.KeyValueStore, clock mclock.Clock) *nodeDB {
- ndb := &nodeDB{
- db: db,
- cache: lru.NewCache[string, utils.ExpiredValue](balanceCacheLimit),
- auxbuf: make([]byte, 37),
- clock: clock,
- closeCh: make(chan struct{}),
- }
- binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion))
- go ndb.expirer()
- return ndb
-}
-
-func (db *nodeDB) close() {
- close(db.closeCh)
-}
-
-func (db *nodeDB) getPrefix(neg bool) []byte {
- prefix := positiveBalancePrefix
- if neg {
- prefix = negativeBalancePrefix
- }
- return append(db.verbuf[:], prefix...)
-}
-
-func (db *nodeDB) key(id []byte, neg bool) []byte {
- prefix := positiveBalancePrefix
- if neg {
- prefix = negativeBalancePrefix
- }
- if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) {
- db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...)
- }
- copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:])
- copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix)
- copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id)
- return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)]
-}
-
-func (db *nodeDB) getExpiration() (utils.Fixed64, utils.Fixed64) {
- blob, err := db.db.Get(append(db.verbuf[:], expirationKey...))
- if err != nil || len(blob) != 16 {
- return 0, 0
- }
- return utils.Fixed64(binary.BigEndian.Uint64(blob[:8])), utils.Fixed64(binary.BigEndian.Uint64(blob[8:16]))
-}
-
-func (db *nodeDB) setExpiration(pos, neg utils.Fixed64) {
- var buff [16]byte
- binary.BigEndian.PutUint64(buff[:8], uint64(pos))
- binary.BigEndian.PutUint64(buff[8:16], uint64(neg))
- db.db.Put(append(db.verbuf[:], expirationKey...), buff[:16])
-}
-
-func (db *nodeDB) getOrNewBalance(id []byte, neg bool) utils.ExpiredValue {
- key := db.key(id, neg)
- item, exist := db.cache.Get(string(key))
- if exist {
- return item
- }
-
- var b utils.ExpiredValue
- enc, err := db.db.Get(key)
- if err != nil || len(enc) == 0 {
- return b
- }
- if err := rlp.DecodeBytes(enc, &b); err != nil {
- log.Crit("Failed to decode positive balance", "err", err)
- }
- db.cache.Add(string(key), b)
- return b
-}
-
-func (db *nodeDB) setBalance(id []byte, neg bool, b utils.ExpiredValue) {
- key := db.key(id, neg)
- enc, err := rlp.EncodeToBytes(&(b))
- if err != nil {
- log.Crit("Failed to encode positive balance", "err", err)
- }
- db.db.Put(key, enc)
- db.cache.Add(string(key), b)
-}
-
-func (db *nodeDB) delBalance(id []byte, neg bool) {
- key := db.key(id, neg)
- db.db.Delete(key)
- db.cache.Remove(string(key))
-}
-
-// getPosBalanceIDs returns a lexicographically ordered list of IDs of accounts
-// with a positive balance
-func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) {
- if maxCount <= 0 {
- return
- }
- prefix := db.getPrefix(false)
- keylen := len(prefix) + len(enode.ID{})
-
- it := db.db.NewIterator(prefix, start.Bytes())
- defer it.Release()
-
- for it.Next() {
- var id enode.ID
- if len(it.Key()) != keylen {
- return
- }
- copy(id[:], it.Key()[keylen-len(id):])
- if bytes.Compare(id.Bytes(), stop.Bytes()) >= 0 {
- return
- }
- result = append(result, id)
- if len(result) == maxCount {
- return
- }
- }
- return
-}
-
-// forEachBalance iterates all balances and passes values to callback.
-func (db *nodeDB) forEachBalance(neg bool, callback func(id enode.ID, balance utils.ExpiredValue) bool) {
- prefix := db.getPrefix(neg)
- keylen := len(prefix) + len(enode.ID{})
-
- it := db.db.NewIterator(prefix, nil)
- defer it.Release()
-
- for it.Next() {
- var id enode.ID
- if len(it.Key()) != keylen {
- return
- }
- copy(id[:], it.Key()[keylen-len(id):])
-
- var b utils.ExpiredValue
- if err := rlp.DecodeBytes(it.Value(), &b); err != nil {
- continue
- }
- if !callback(id, b) {
- return
- }
- }
-}
-
-func (db *nodeDB) expirer() {
- for {
- select {
- case <-db.clock.After(dbCleanupCycle):
- db.expireNodes()
- case <-db.closeCh:
- return
- }
- }
-}
-
-// expireNodes iterates the whole node db and checks whether the
-// token balances can be deleted.
-func (db *nodeDB) expireNodes() {
- var (
- visited int
- deleted int
- start = time.Now()
- )
- for _, neg := range []bool{false, true} {
- iter := db.db.NewIterator(db.getPrefix(neg), nil)
- for iter.Next() {
- visited++
- var balance utils.ExpiredValue
- if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil {
- log.Crit("Failed to decode negative balance", "err", err)
- }
- if db.evictCallBack != nil && db.evictCallBack(db.clock.Now(), neg, balance) {
- deleted++
- db.db.Delete(iter.Key())
- }
- }
- }
- // Invoke testing hook if it's not nil.
- if db.cleanupHook != nil {
- db.cleanupHook()
- }
- log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start)))
-}
diff --git a/les/vflux/server/clientdb_test.go b/les/vflux/server/clientdb_test.go
deleted file mode 100644
index 353d84aea..000000000
--- a/les/vflux/server/clientdb_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2020 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 .
-
-package server
-
-import (
- "reflect"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-func expval(v uint64) utils.ExpiredValue {
- return utils.ExpiredValue{Base: v}
-}
-
-func TestNodeDB(t *testing.T) {
- ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{})
- defer ndb.close()
-
- var cases = []struct {
- id enode.ID
- ip string
- balance utils.ExpiredValue
- positive bool
- }{
- {enode.ID{0x00, 0x01, 0x02}, "", expval(100), true},
- {enode.ID{0x00, 0x01, 0x02}, "", expval(200), true},
- {enode.ID{}, "127.0.0.1", expval(100), false},
- {enode.ID{}, "127.0.0.1", expval(200), false},
- }
- for _, c := range cases {
- if c.positive {
- ndb.setBalance(c.id.Bytes(), false, c.balance)
- if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, c.balance) {
- t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance, pb)
- }
- } else {
- ndb.setBalance([]byte(c.ip), true, c.balance)
- if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, c.balance) {
- t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance, nb)
- }
- }
- }
- for _, c := range cases {
- if c.positive {
- ndb.delBalance(c.id.Bytes(), false)
- if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, utils.ExpiredValue{}) {
- t.Fatalf("Positive balance mismatch, want %v, got %v", utils.ExpiredValue{}, pb)
- }
- } else {
- ndb.delBalance([]byte(c.ip), true)
- if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, utils.ExpiredValue{}) {
- t.Fatalf("Negative balance mismatch, want %v, got %v", utils.ExpiredValue{}, nb)
- }
- }
- }
- posExp, negExp := utils.Fixed64(1000), utils.Fixed64(2000)
- ndb.setExpiration(posExp, negExp)
- if pos, neg := ndb.getExpiration(); pos != posExp || neg != negExp {
- t.Fatalf("Expiration mismatch, want %v / %v, got %v / %v", posExp, negExp, pos, neg)
- }
- /* curBalance := currencyBalance{typ: "ETH", amount: 10000}
- ndb.setCurrencyBalance(enode.ID{0x01, 0x02}, curBalance)
- if got := ndb.getCurrencyBalance(enode.ID{0x01, 0x02}); !reflect.DeepEqual(got, curBalance) {
- t.Fatalf("Currency balance mismatch, want %v, got %v", curBalance, got)
- }*/
-}
-
-func TestNodeDBExpiration(t *testing.T) {
- var (
- iterated int
- done = make(chan struct{}, 1)
- )
- callback := func(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool {
- iterated += 1
- return true
- }
- clock := &mclock.Simulated{}
- ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock)
- defer ndb.close()
- ndb.evictCallBack = callback
- ndb.cleanupHook = func() { done <- struct{}{} }
-
- var cases = []struct {
- id []byte
- neg bool
- balance utils.ExpiredValue
- }{
- {[]byte{0x01, 0x02}, false, expval(1)},
- {[]byte{0x03, 0x04}, false, expval(1)},
- {[]byte{0x05, 0x06}, false, expval(1)},
- {[]byte{0x07, 0x08}, false, expval(1)},
-
- {[]byte("127.0.0.1"), true, expval(1)},
- {[]byte("127.0.0.2"), true, expval(1)},
- {[]byte("127.0.0.3"), true, expval(1)},
- {[]byte("127.0.0.4"), true, expval(1)},
- }
- for _, c := range cases {
- ndb.setBalance(c.id, c.neg, c.balance)
- }
- clock.WaitForTimers(1)
- clock.Run(time.Hour + time.Minute)
- select {
- case <-done:
- case <-time.NewTimer(time.Second).C:
- t.Fatalf("timeout")
- }
- if iterated != 8 {
- t.Fatalf("Failed to evict useless balances, want %v, got %d", 8, iterated)
- }
-
- for _, c := range cases {
- ndb.setBalance(c.id, c.neg, c.balance)
- }
- clock.WaitForTimers(1)
- clock.Run(time.Hour + time.Minute)
- select {
- case <-done:
- case <-time.NewTimer(time.Second).C:
- t.Fatalf("timeout")
- }
- if iterated != 16 {
- t.Fatalf("Failed to evict useless balances, want %v, got %d", 16, iterated)
- }
-}
diff --git a/les/vflux/server/clientpool.go b/les/vflux/server/clientpool.go
deleted file mode 100644
index a525f8636..000000000
--- a/les/vflux/server/clientpool.go
+++ /dev/null
@@ -1,328 +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 .
-
-package server
-
-import (
- "errors"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/les/vflux"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var (
- ErrNotConnected = errors.New("client not connected")
- ErrNoPriority = errors.New("priority too low to raise capacity")
- ErrCantFindMaximum = errors.New("unable to find maximum allowed capacity")
-)
-
-// ClientPool implements a client database that assigns a priority to each client
-// based on a positive and negative balance. Positive balance is externally assigned
-// to prioritized clients and is decreased with connection time and processed
-// requests (unless the price factors are zero). If the positive balance is zero
-// then negative balance is accumulated.
-//
-// Balance tracking and priority calculation for connected clients is done by
-// balanceTracker. PriorityQueue ensures that clients with the lowest positive or
-// highest negative balance get evicted when the total capacity allowance is full
-// and new clients with a better balance want to connect.
-//
-// Already connected nodes receive a small bias in their favor in order to avoid
-// accepting and instantly kicking out clients. In theory, we try to ensure that
-// each client can have several minutes of connection time.
-//
-// Balances of disconnected clients are stored in nodeDB including positive balance
-// and negative balance. Both positive balance and negative balance will decrease
-// exponentially. If the balance is low enough, then the record will be dropped.
-type ClientPool struct {
- *priorityPool
- *balanceTracker
-
- setup *serverSetup
- clock mclock.Clock
- ns *nodestate.NodeStateMachine
- synced func() bool
-
- lock sync.RWMutex
- connectedBias time.Duration
-
- minCap uint64 // the minimal capacity value allowed for any client
- capReqNode *enode.Node // node that is requesting capacity change; only used inside NSM operation
-}
-
-// clientPeer represents a peer in the client pool. None of the callbacks should block.
-type clientPeer interface {
- Node() *enode.Node
- FreeClientId() string // unique id for non-priority clients (typically a prefix of the network address)
- InactiveAllowance() time.Duration // disconnection timeout for inactive non-priority peers
- UpdateCapacity(newCap uint64, requested bool) // signals a capacity update (requested is true if it is a result of a SetCapacity call on the given peer
- Disconnect() // initiates disconnection (Unregister should always be called)
-}
-
-// NewClientPool creates a new client pool
-func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias time.Duration, clock mclock.Clock, synced func() bool) *ClientPool {
- setup := newServerSetup()
- ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
- cp := &ClientPool{
- priorityPool: newPriorityPool(ns, setup, clock, minCap, connectedBias, 4, 100),
- balanceTracker: newBalanceTracker(ns, setup, balanceDb, clock, &utils.Expirer{}, &utils.Expirer{}),
- setup: setup,
- ns: ns,
- clock: clock,
- minCap: minCap,
- connectedBias: connectedBias,
- synced: synced,
- }
-
- ns.SubscribeState(nodestate.MergeFlags(setup.activeFlag, setup.inactiveFlag, setup.priorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
- if newState.Equals(setup.inactiveFlag) {
- // set timeout for non-priority inactive client
- var timeout time.Duration
- if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok {
- timeout = c.InactiveAllowance()
- }
- ns.AddTimeout(node, setup.inactiveFlag, timeout)
- }
- if oldState.Equals(setup.inactiveFlag) && newState.Equals(setup.inactiveFlag.Or(setup.priorityFlag)) {
- ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) // priority gained; remove timeout
- }
- if newState.Equals(setup.activeFlag) {
- // active with no priority; limit capacity to minCap
- cap, _ := ns.GetField(node, setup.capacityField).(uint64)
- if cap > minCap {
- cp.requestCapacity(node, minCap, minCap, 0)
- }
- }
- if newState.Equals(nodestate.Flags{}) {
- if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok {
- c.Disconnect()
- }
- }
- })
-
- ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
- if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok {
- newCap, _ := newValue.(uint64)
- c.UpdateCapacity(newCap, node == cp.capReqNode)
- }
- })
-
- // add metrics
- cp.ns.SubscribeState(nodestate.MergeFlags(cp.setup.activeFlag, cp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
- if oldState.IsEmpty() && !newState.IsEmpty() {
- clientConnectedMeter.Mark(1)
- }
- if !oldState.IsEmpty() && newState.IsEmpty() {
- clientDisconnectedMeter.Mark(1)
- }
- if oldState.HasNone(cp.setup.activeFlag) && oldState.HasAll(cp.setup.activeFlag) {
- clientActivatedMeter.Mark(1)
- }
- if oldState.HasAll(cp.setup.activeFlag) && oldState.HasNone(cp.setup.activeFlag) {
- clientDeactivatedMeter.Mark(1)
- }
- activeCount, activeCap := cp.Active()
- totalActiveCountGauge.Update(int64(activeCount))
- totalActiveCapacityGauge.Update(int64(activeCap))
- totalInactiveCountGauge.Update(int64(cp.Inactive()))
- })
- return cp
-}
-
-// Start starts the client pool. Should be called before Register/Unregister.
-func (cp *ClientPool) Start() {
- cp.ns.Start()
-}
-
-// Stop shuts the client pool down. The clientPeer interface callbacks will not be called
-// after Stop. Register calls will return nil.
-func (cp *ClientPool) Stop() {
- cp.balanceTracker.stop()
- cp.ns.Stop()
-}
-
-// Register registers the peer into the client pool. If the peer has insufficient
-// priority and remains inactive for longer than the allowed timeout then it will be
-// disconnected by calling the Disconnect function of the clientPeer interface.
-func (cp *ClientPool) Register(peer clientPeer) ConnectedBalance {
- cp.ns.SetField(peer.Node(), cp.setup.clientField, peerWrapper{peer})
- balance, _ := cp.ns.GetField(peer.Node(), cp.setup.balanceField).(*nodeBalance)
- return balance
-}
-
-// Unregister removes the peer from the client pool
-func (cp *ClientPool) Unregister(peer clientPeer) {
- cp.ns.SetField(peer.Node(), cp.setup.clientField, nil)
-}
-
-// SetConnectedBias sets the connection bias, which is applied to already connected clients
-// So that already connected client won't be kicked out very soon and we can ensure all
-// connected clients can have enough time to request or sync some data.
-func (cp *ClientPool) SetConnectedBias(bias time.Duration) {
- cp.lock.Lock()
- cp.connectedBias = bias
- cp.setActiveBias(bias)
- cp.lock.Unlock()
-}
-
-// SetCapacity sets the assigned capacity of a connected client
-func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Duration, requested bool) (capacity uint64, err error) {
- cp.lock.RLock()
- if cp.connectedBias > bias {
- bias = cp.connectedBias
- }
- cp.lock.RUnlock()
-
- cp.ns.Operation(func() {
- balance, _ := cp.ns.GetField(node, cp.setup.balanceField).(*nodeBalance)
- if balance == nil {
- err = ErrNotConnected
- return
- }
- capacity, _ = cp.ns.GetField(node, cp.setup.capacityField).(uint64)
- if capacity == 0 {
- // if the client is inactive then it has insufficient priority for the minimal capacity
- // (will be activated automatically with minCap when possible)
- return
- }
- if reqCap < cp.minCap {
- // can't request less than minCap; switching between 0 (inactive state) and minCap is
- // performed by the server automatically as soon as necessary/possible
- reqCap = cp.minCap
- }
- if reqCap > cp.minCap && cp.ns.GetState(node).HasNone(cp.setup.priorityFlag) {
- err = ErrNoPriority
- return
- }
- if reqCap == capacity {
- return
- }
- if requested {
- // mark the requested node so that the UpdateCapacity callback can signal
- // whether the update is the direct result of a SetCapacity call on the given node
- cp.capReqNode = node
- defer func() {
- cp.capReqNode = nil
- }()
- }
-
- var minTarget, maxTarget uint64
- if reqCap > capacity {
- // Estimate maximum available capacity at the current priority level and request
- // the estimated amount.
- // Note: requestCapacity could find the highest available capacity between the
- // current and the requested capacity but it could cost a lot of iterations with
- // fine step adjustment if the requested capacity is very high. By doing a quick
- // estimation of the maximum available capacity based on the capacity curve we
- // can limit the number of required iterations.
- curve := cp.getCapacityCurve().exclude(node.ID())
- maxTarget = curve.maxCapacity(func(capacity uint64) int64 {
- return balance.estimatePriority(capacity, 0, 0, bias, false)
- })
- if maxTarget < reqCap {
- return
- }
- maxTarget = reqCap
-
- // Specify a narrow target range that allows a limited number of fine step
- // iterations
- minTarget = maxTarget - maxTarget/20
- if minTarget < capacity {
- minTarget = capacity
- }
- } else {
- minTarget, maxTarget = reqCap, reqCap
- }
- if newCap := cp.requestCapacity(node, minTarget, maxTarget, bias); newCap >= minTarget && newCap <= maxTarget {
- capacity = newCap
- return
- }
- // we should be able to find the maximum allowed capacity in a few iterations
- log.Error("Unable to find maximum allowed capacity")
- err = ErrCantFindMaximum
- })
- return
-}
-
-// serveCapQuery serves a vflux capacity query. It receives multiple token amount values
-// and a bias time value. For each given token amount it calculates the maximum achievable
-// capacity in case the amount is added to the balance.
-func (cp *ClientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []byte {
- var req vflux.CapacityQueryReq
- if rlp.DecodeBytes(data, &req) != nil {
- return nil
- }
- if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen {
- return nil
- }
- result := make(vflux.CapacityQueryReply, len(req.AddTokens))
- if !cp.synced() {
- capacityQueryZeroMeter.Mark(1)
- reply, _ := rlp.EncodeToBytes(&result)
- return reply
- }
-
- bias := time.Second * time.Duration(req.Bias)
- cp.lock.RLock()
- if cp.connectedBias > bias {
- bias = cp.connectedBias
- }
- cp.lock.RUnlock()
-
- // use capacityCurve to answer request for multiple newly bought token amounts
- curve := cp.getCapacityCurve().exclude(id)
- cp.BalanceOperation(id, freeID, func(balance AtomicBalanceOperator) {
- pb, _ := balance.GetBalance()
- for i, addTokens := range req.AddTokens {
- add := addTokens.Int64()
- result[i] = curve.maxCapacity(func(capacity uint64) int64 {
- return balance.estimatePriority(capacity, add, 0, bias, false) / int64(capacity)
- })
- if add <= 0 && uint64(-add) >= pb && result[i] > cp.minCap {
- result[i] = cp.minCap
- }
- if result[i] < cp.minCap {
- result[i] = 0
- }
- }
- })
- // add first result to metrics (don't care about priority client multi-queries yet)
- if result[0] == 0 {
- capacityQueryZeroMeter.Mark(1)
- } else {
- capacityQueryNonZeroMeter.Mark(1)
- }
- reply, _ := rlp.EncodeToBytes(&result)
- return reply
-}
-
-// Handle implements Service
-func (cp *ClientPool) Handle(id enode.ID, address string, name string, data []byte) []byte {
- switch name {
- case vflux.CapacityQueryName:
- return cp.serveCapQuery(id, address, data)
- default:
- return nil
- }
-}
diff --git a/les/vflux/server/clientpool_test.go b/les/vflux/server/clientpool_test.go
deleted file mode 100644
index f75c70afc..000000000
--- a/les/vflux/server/clientpool_test.go
+++ /dev/null
@@ -1,606 +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 .
-
-package server
-
-import (
- "fmt"
- "math/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-const defaultConnectedBias = time.Minute * 3
-
-func TestClientPoolL10C100Free(t *testing.T) {
- testClientPool(t, 10, 100, 0, true)
-}
-
-func TestClientPoolL40C200Free(t *testing.T) {
- testClientPool(t, 40, 200, 0, true)
-}
-
-func TestClientPoolL100C300Free(t *testing.T) {
- testClientPool(t, 100, 300, 0, true)
-}
-
-func TestClientPoolL10C100P4(t *testing.T) {
- testClientPool(t, 10, 100, 4, false)
-}
-
-func TestClientPoolL40C200P30(t *testing.T) {
- testClientPool(t, 40, 200, 30, false)
-}
-
-func TestClientPoolL100C300P20(t *testing.T) {
- testClientPool(t, 100, 300, 20, false)
-}
-
-const testClientPoolTicks = 100000
-
-type poolTestPeer struct {
- node *enode.Node
- index int
- disconnCh chan int
- cap uint64
- inactiveAllowed bool
-}
-
-func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer {
- return &poolTestPeer{
- index: i,
- disconnCh: disconnCh,
- node: enode.SignNull(&enr.Record{}, enode.ID{byte(i % 256), byte(i >> 8)}),
- }
-}
-
-func (i *poolTestPeer) Node() *enode.Node {
- return i.node
-}
-
-func (i *poolTestPeer) FreeClientId() string {
- return fmt.Sprintf("addr #%d", i.index)
-}
-
-func (i *poolTestPeer) InactiveAllowance() time.Duration {
- if i.inactiveAllowed {
- return time.Second * 10
- }
- return 0
-}
-
-func (i *poolTestPeer) UpdateCapacity(capacity uint64, requested bool) {
- i.cap = capacity
-}
-
-func (i *poolTestPeer) Disconnect() {
- if i.disconnCh == nil {
- return
- }
- id := i.node.ID()
- i.disconnCh <- int(id[0]) + int(id[1])<<8
-}
-
-func getBalance(pool *ClientPool, p *poolTestPeer) (pos, neg uint64) {
- pool.BalanceOperation(p.node.ID(), p.FreeClientId(), func(nb AtomicBalanceOperator) {
- pos, neg = nb.GetBalance()
- })
- return
-}
-
-func addBalance(pool *ClientPool, id enode.ID, amount int64) {
- pool.BalanceOperation(id, "", func(nb AtomicBalanceOperator) {
- nb.AddBalance(amount)
- })
-}
-
-func checkDiff(a, b uint64) bool {
- maxDiff := (a + b) / 2000
- if maxDiff < 1 {
- maxDiff = 1
- }
- return a > b+maxDiff || b > a+maxDiff
-}
-
-func connect(pool *ClientPool, peer *poolTestPeer) uint64 {
- pool.Register(peer)
- return peer.cap
-}
-
-func disconnect(pool *ClientPool, peer *poolTestPeer) {
- pool.Unregister(peer)
-}
-
-func alwaysTrueFn() bool {
- return true
-}
-
-func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, randomDisconnect bool) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- connected = make([]bool, clientCount)
- connTicks = make([]int, clientCount)
- disconnCh = make(chan int, clientCount)
- pool = NewClientPool(db, 1, 0, &clock, alwaysTrueFn)
- )
- pool.Start()
- pool.SetExpirationTCs(0, 1000)
-
- pool.SetLimits(uint64(activeLimit), uint64(activeLimit))
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- // pool should accept new peers up to its connected limit
- for i := 0; i < activeLimit; i++ {
- if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 {
- connected[i] = true
- } else {
- t.Fatalf("Test peer #%d rejected", i)
- }
- }
- // randomly connect and disconnect peers, expect to have a similar total connection time at the end
- for tickCounter := 0; tickCounter < testClientPoolTicks; tickCounter++ {
- clock.Run(1 * time.Second)
-
- if tickCounter == testClientPoolTicks/4 {
- // give a positive balance to some of the peers
- amount := testClientPoolTicks / 2 * int64(time.Second) // enough for half of the simulation period
- for i := 0; i < paidCount; i++ {
- addBalance(pool, newPoolTestPeer(i, disconnCh).node.ID(), amount)
- }
- }
-
- i := rand.Intn(clientCount)
- if connected[i] {
- if randomDisconnect {
- disconnect(pool, newPoolTestPeer(i, disconnCh))
- connected[i] = false
- connTicks[i] += tickCounter
- }
- } else {
- if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 {
- connected[i] = true
- connTicks[i] -= tickCounter
- } else {
- disconnect(pool, newPoolTestPeer(i, disconnCh))
- }
- }
- pollDisconnects:
- for {
- select {
- case i := <-disconnCh:
- disconnect(pool, newPoolTestPeer(i, disconnCh))
- if connected[i] {
- connTicks[i] += tickCounter
- connected[i] = false
- }
- default:
- break pollDisconnects
- }
- }
- }
-
- expTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2*(activeLimit-paidCount)/(clientCount-paidCount)
- expMin := expTicks - expTicks/5
- expMax := expTicks + expTicks/5
- paidTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2
- paidMin := paidTicks - paidTicks/5
- paidMax := paidTicks + paidTicks/5
-
- // check if the total connected time of peers are all in the expected range
- for i, c := range connected {
- if c {
- connTicks[i] += testClientPoolTicks
- }
- min, max := expMin, expMax
- if i < paidCount {
- // expect a higher amount for clients with a positive balance
- min, max = paidMin, paidMax
- }
- if connTicks[i] < min || connTicks[i] > max {
- t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max)
- }
- }
- pool.Stop()
-}
-
-func testPriorityConnect(t *testing.T, pool *ClientPool, p *poolTestPeer, cap uint64, expSuccess bool) {
- if cap := connect(pool, p); cap == 0 {
- if expSuccess {
- t.Fatalf("Failed to connect paid client")
- } else {
- return
- }
- }
- if newCap, _ := pool.SetCapacity(p.node, cap, defaultConnectedBias, true); newCap != cap {
- if expSuccess {
- t.Fatalf("Failed to raise capacity of paid client")
- } else {
- return
- }
- }
- if !expSuccess {
- t.Fatalf("Should reject high capacity paid client")
- }
-}
-
-func TestConnectPaidClient(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10))
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- // Add balance for an external client and mark it as paid client
- addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute))
- testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 10, true)
-}
-
-func TestConnectPaidClientToSmallPool(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- // Add balance for an external client and mark it as paid client
- addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute))
-
- // connect a fat paid client to pool, should reject it.
- testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 100, false)
-}
-
-func TestConnectPaidClientToFullPool(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- for i := 0; i < 10; i++ {
- addBalance(pool, newPoolTestPeer(i, nil).node.ID(), int64(time.Second*20))
- connect(pool, newPoolTestPeer(i, nil))
- }
- addBalance(pool, newPoolTestPeer(11, nil).node.ID(), int64(time.Second*2)) // Add low balance to new paid client
- if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 {
- t.Fatalf("Low balance paid client should be rejected")
- }
- clock.Run(time.Second)
- addBalance(pool, newPoolTestPeer(12, nil).node.ID(), int64(time.Minute*5)) // Add high balance to new paid client
- if cap := connect(pool, newPoolTestPeer(12, nil)); cap == 0 {
- t.Fatalf("High balance paid client should be accepted")
- }
-}
-
-func TestPaidClientKickedOut(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- kickedCh = make(chan int, 100)
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- pool.SetExpirationTCs(0, 0)
- defer pool.Stop()
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- for i := 0; i < 10; i++ {
- addBalance(pool, newPoolTestPeer(i, kickedCh).node.ID(), 10000000000) // 10 second allowance
- connect(pool, newPoolTestPeer(i, kickedCh))
- clock.Run(time.Millisecond)
- }
- clock.Run(defaultConnectedBias + time.Second*11)
- if cap := connect(pool, newPoolTestPeer(11, kickedCh)); cap == 0 {
- t.Fatalf("Free client should be accepted")
- }
- clock.Run(0)
- select {
- case id := <-kickedCh:
- if id != 0 {
- t.Fatalf("Kicked client mismatch, want %v, got %v", 0, id)
- }
- default:
- t.Fatalf("timeout")
- }
-}
-
-func TestConnectFreeClient(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10))
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
- if cap := connect(pool, newPoolTestPeer(0, nil)); cap == 0 {
- t.Fatalf("Failed to connect free client")
- }
- testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 2, false)
-}
-
-func TestConnectFreeClientToFullPool(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- for i := 0; i < 10; i++ {
- connect(pool, newPoolTestPeer(i, nil))
- }
- if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 {
- t.Fatalf("New free client should be rejected")
- }
- clock.Run(time.Minute)
- if cap := connect(pool, newPoolTestPeer(12, nil)); cap != 0 {
- t.Fatalf("New free client should be rejected")
- }
- clock.Run(time.Millisecond)
- clock.Run(4 * time.Minute)
- if cap := connect(pool, newPoolTestPeer(13, nil)); cap == 0 {
- t.Fatalf("Old client connects more than 5min should be kicked")
- }
-}
-
-func TestFreeClientKickedOut(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- kicked = make(chan int, 100)
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- for i := 0; i < 10; i++ {
- connect(pool, newPoolTestPeer(i, kicked))
- clock.Run(time.Millisecond)
- }
- if cap := connect(pool, newPoolTestPeer(10, kicked)); cap != 0 {
- t.Fatalf("New free client should be rejected")
- }
- clock.Run(0)
- select {
- case <-kicked:
- default:
- t.Fatalf("timeout")
- }
- disconnect(pool, newPoolTestPeer(10, kicked))
- clock.Run(5 * time.Minute)
- for i := 0; i < 10; i++ {
- connect(pool, newPoolTestPeer(i+10, kicked))
- }
- clock.Run(0)
-
- for i := 0; i < 10; i++ {
- select {
- case id := <-kicked:
- if id >= 10 {
- t.Fatalf("Old client should be kicked, now got: %d", id)
- }
- default:
- t.Fatalf("timeout")
- }
- }
-}
-
-func TestPositiveBalanceCalculation(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- kicked = make(chan int, 10)
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute*3))
- testPriorityConnect(t, pool, newPoolTestPeer(0, kicked), 10, true)
- clock.Run(time.Minute)
-
- disconnect(pool, newPoolTestPeer(0, kicked))
- pb, _ := getBalance(pool, newPoolTestPeer(0, kicked))
- if checkDiff(pb, uint64(time.Minute*2)) {
- t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb)
- }
-}
-
-func TestDowngradePriorityClient(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- kicked = make(chan int, 10)
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
-
- p := newPoolTestPeer(0, kicked)
- addBalance(pool, p.node.ID(), int64(time.Minute))
- testPriorityConnect(t, pool, p, 10, true)
- if p.cap != 10 {
- t.Fatalf("The capacity of priority peer hasn't been updated, got: %d", p.cap)
- }
-
- clock.Run(time.Minute) // All positive balance should be used up.
- time.Sleep(300 * time.Millisecond) // Ensure the callback is called
- if p.cap != 1 {
- t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap)
- }
- pb, _ := getBalance(pool, newPoolTestPeer(0, kicked))
- if pb != 0 {
- t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb)
- }
-
- addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute))
- pb, _ = getBalance(pool, newPoolTestPeer(0, kicked))
- if checkDiff(pb, uint64(time.Minute)) {
- t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb)
- }
-}
-
-func TestNegativeBalanceCalculation(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetExpirationTCs(0, 3600)
- pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1})
-
- for i := 0; i < 10; i++ {
- connect(pool, newPoolTestPeer(i, nil))
- }
- clock.Run(time.Second)
-
- for i := 0; i < 10; i++ {
- disconnect(pool, newPoolTestPeer(i, nil))
- _, nb := getBalance(pool, newPoolTestPeer(i, nil))
- if nb != 0 {
- t.Fatalf("Short connection shouldn't be recorded")
- }
- }
- for i := 0; i < 10; i++ {
- connect(pool, newPoolTestPeer(i, nil))
- }
- clock.Run(time.Minute)
- for i := 0; i < 10; i++ {
- disconnect(pool, newPoolTestPeer(i, nil))
- _, nb := getBalance(pool, newPoolTestPeer(i, nil))
- exp := uint64(time.Minute) / 1000
- exp -= exp / 120 // correct for negative balance expiration
- if checkDiff(nb, exp) {
- t.Fatalf("Negative balance mismatch, want %v, got %v", exp, nb)
- }
- }
-}
-
-func TestInactiveClient(t *testing.T) {
- var (
- clock mclock.Simulated
- db = rawdb.NewMemoryDatabase()
- )
- pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
- pool.Start()
- defer pool.Stop()
- pool.SetLimits(2, uint64(2))
-
- p1 := newPoolTestPeer(1, nil)
- p1.inactiveAllowed = true
- p2 := newPoolTestPeer(2, nil)
- p2.inactiveAllowed = true
- p3 := newPoolTestPeer(3, nil)
- p3.inactiveAllowed = true
- addBalance(pool, p1.node.ID(), 1000*int64(time.Second))
- addBalance(pool, p3.node.ID(), 2000*int64(time.Second))
- // p1: 1000 p2: 0 p3: 2000
- p1.cap = connect(pool, p1)
- if p1.cap != 1 {
- t.Fatalf("Failed to connect peer #1")
- }
- p2.cap = connect(pool, p2)
- if p2.cap != 1 {
- t.Fatalf("Failed to connect peer #2")
- }
- p3.cap = connect(pool, p3)
- if p3.cap != 1 {
- t.Fatalf("Failed to connect peer #3")
- }
- if p2.cap != 0 {
- t.Fatalf("Failed to deactivate peer #2")
- }
- addBalance(pool, p2.node.ID(), 3000*int64(time.Second))
- // p1: 1000 p2: 3000 p3: 2000
- if p2.cap != 1 {
- t.Fatalf("Failed to activate peer #2")
- }
- if p1.cap != 0 {
- t.Fatalf("Failed to deactivate peer #1")
- }
- addBalance(pool, p2.node.ID(), -2500*int64(time.Second))
- // p1: 1000 p2: 500 p3: 2000
- if p1.cap != 1 {
- t.Fatalf("Failed to activate peer #1")
- }
- if p2.cap != 0 {
- t.Fatalf("Failed to deactivate peer #2")
- }
- pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0})
- p4 := newPoolTestPeer(4, nil)
- addBalance(pool, p4.node.ID(), 1500*int64(time.Second))
- // p1: 1000 p2: 500 p3: 2000 p4: 1500
- p4.cap = connect(pool, p4)
- if p4.cap != 1 {
- t.Fatalf("Failed to activate peer #4")
- }
- if p1.cap != 0 {
- t.Fatalf("Failed to deactivate peer #1")
- }
- clock.Run(time.Second * 600)
- // manually trigger a check to avoid a long real-time wait
- pool.ns.SetState(p1.node, pool.setup.updateFlag, nodestate.Flags{}, 0)
- pool.ns.SetState(p1.node, nodestate.Flags{}, pool.setup.updateFlag, 0)
- // p1: 1000 p2: 500 p3: 2000 p4: 900
- if p1.cap != 1 {
- t.Fatalf("Failed to activate peer #1")
- }
- if p4.cap != 0 {
- t.Fatalf("Failed to deactivate peer #4")
- }
- disconnect(pool, p2)
- disconnect(pool, p4)
- addBalance(pool, p1.node.ID(), -1000*int64(time.Second))
- if p1.cap != 1 {
- t.Fatalf("Should not deactivate peer #1")
- }
- if p2.cap != 0 {
- t.Fatalf("Should not activate peer #2")
- }
-}
diff --git a/les/vflux/server/metrics.go b/les/vflux/server/metrics.go
deleted file mode 100644
index 680aebe2e..000000000
--- a/les/vflux/server/metrics.go
+++ /dev/null
@@ -1,35 +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 .
-
-package server
-
-import (
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-var (
- totalActiveCapacityGauge = metrics.NewRegisteredGauge("vflux/server/active/capacity", nil)
- totalActiveCountGauge = metrics.NewRegisteredGauge("vflux/server/active/count", nil)
- totalInactiveCountGauge = metrics.NewRegisteredGauge("vflux/server/inactive/count", nil)
-
- clientConnectedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/connected", nil)
- clientActivatedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/activated", nil)
- clientDeactivatedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/deactivated", nil)
- clientDisconnectedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/disconnected", nil)
-
- capacityQueryZeroMeter = metrics.NewRegisteredMeter("vflux/server/capQueryZero", nil)
- capacityQueryNonZeroMeter = metrics.NewRegisteredMeter("vflux/server/capQueryNonZero", nil)
-)
diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go
deleted file mode 100644
index 766026a80..000000000
--- a/les/vflux/server/prioritypool.go
+++ /dev/null
@@ -1,695 +0,0 @@
-// Copyright 2020 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 .
-
-package server
-
-import (
- "math"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/common/prque"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-const (
- lazyQueueRefresh = time.Second * 10 // refresh period of the active queue
-)
-
-// priorityPool handles a set of nodes where each node has a capacity (a scalar value)
-// and a priority (which can change over time and can also depend on the capacity).
-// A node is active if it has at least the necessary minimal amount of capacity while
-// inactive nodes have 0 capacity (values between 0 and the minimum are not allowed).
-// The pool ensures that the number and total capacity of all active nodes are limited
-// and the highest priority nodes are active at all times (limits can be changed
-// during operation with immediate effect).
-//
-// When activating clients a priority bias is applied in favor of the already active
-// nodes in order to avoid nodes quickly alternating between active and inactive states
-// when their priorities are close to each other. The bias is specified in terms of
-// duration (time) because priorities are expected to usually get lower over time and
-// therefore a future minimum prediction (see EstMinPriority) should monotonously
-// decrease with the specified time parameter.
-// This time bias can be interpreted as minimum expected active time at the given
-// capacity (if the threshold priority stays the same).
-//
-// Nodes in the pool always have either inactiveFlag or activeFlag set. A new node is
-// added to the pool by externally setting inactiveFlag. priorityPool can switch a node
-// between inactiveFlag and activeFlag at any time. Nodes can be removed from the pool
-// by externally resetting both flags. activeFlag should not be set externally.
-//
-// The highest priority nodes in "inactive" state are moved to "active" state as soon as
-// the minimum capacity can be granted for them. The capacity of lower priority active
-// nodes is reduced or they are demoted to "inactive" state if their priority is
-// insufficient even at minimal capacity.
-type priorityPool struct {
- setup *serverSetup
- ns *nodestate.NodeStateMachine
- clock mclock.Clock
- lock sync.Mutex
- maxCount, maxCap uint64
- minCap uint64
- activeBias time.Duration
- capacityStepDiv, fineStepDiv uint64
-
- // The snapshot of priority pool for query.
- cachedCurve *capacityCurve
- ccUpdatedAt mclock.AbsTime
- ccUpdateForced bool
-
- // Runtime status of prioritypool, represents the
- // temporary state if tempState is not empty
- tempState []*ppNodeInfo
- activeCount, activeCap uint64
- activeQueue *prque.LazyQueue[int64, *ppNodeInfo]
- inactiveQueue *prque.Prque[int64, *ppNodeInfo]
-}
-
-// ppNodeInfo is the internal node descriptor of priorityPool
-type ppNodeInfo struct {
- nodePriority nodePriority
- node *enode.Node
- connected bool
- capacity uint64 // only changed when temporary state is committed
- activeIndex, inactiveIndex int
-
- tempState bool // should only be true while the priorityPool lock is held
- tempCapacity uint64 // equals capacity when tempState is false
-
- // the following fields only affect the temporary state and they are set to their
- // default value when leaving the temp state
- minTarget, stepDiv uint64
- bias time.Duration
-}
-
-// newPriorityPool creates a new priorityPool
-func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock mclock.Clock, minCap uint64, activeBias time.Duration, capacityStepDiv, fineStepDiv uint64) *priorityPool {
- pp := &priorityPool{
- setup: setup,
- ns: ns,
- clock: clock,
- inactiveQueue: prque.New[int64, *ppNodeInfo](inactiveSetIndex),
- minCap: minCap,
- activeBias: activeBias,
- capacityStepDiv: capacityStepDiv,
- fineStepDiv: fineStepDiv,
- }
- if pp.activeBias < time.Duration(1) {
- pp.activeBias = time.Duration(1)
- }
- pp.activeQueue = prque.NewLazyQueue(activeSetIndex, activePriority, pp.activeMaxPriority, clock, lazyQueueRefresh)
-
- ns.SubscribeField(pp.setup.balanceField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
- if newValue != nil {
- c := &ppNodeInfo{
- node: node,
- nodePriority: newValue.(nodePriority),
- activeIndex: -1,
- inactiveIndex: -1,
- }
- ns.SetFieldSub(node, pp.setup.queueField, c)
- ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0)
- } else {
- ns.SetStateSub(node, nodestate.Flags{}, pp.setup.activeFlag.Or(pp.setup.inactiveFlag), 0)
- if n, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); n != nil {
- pp.disconnectNode(n)
- }
- ns.SetFieldSub(node, pp.setup.capacityField, nil)
- ns.SetFieldSub(node, pp.setup.queueField, nil)
- }
- })
- ns.SubscribeState(pp.setup.activeFlag.Or(pp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
- if c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); c != nil {
- if oldState.IsEmpty() {
- pp.connectNode(c)
- }
- if newState.IsEmpty() {
- pp.disconnectNode(c)
- }
- }
- })
- ns.SubscribeState(pp.setup.updateFlag, func(node *enode.Node, oldState, newState nodestate.Flags) {
- if !newState.IsEmpty() {
- pp.updatePriority(node)
- }
- })
- return pp
-}
-
-// requestCapacity tries to set the capacity of a connected node to the highest possible
-// value inside the given target range. If maxTarget is not reachable then the capacity is
-// iteratively reduced in fine steps based on the fineStepDiv parameter until minTarget is reached.
-// The function returns the new capacity if successful and the original capacity otherwise.
-// Note: this function should run inside a NodeStateMachine operation
-func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget uint64, bias time.Duration) uint64 {
- pp.lock.Lock()
- pp.activeQueue.Refresh()
-
- if minTarget < pp.minCap {
- minTarget = pp.minCap
- }
- if maxTarget < minTarget {
- maxTarget = minTarget
- }
- if bias < pp.activeBias {
- bias = pp.activeBias
- }
- c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo)
- if c == nil {
- log.Error("requestCapacity called for unknown node", "id", node.ID())
- pp.lock.Unlock()
- return 0
- }
- pp.setTempState(c)
- if maxTarget > c.capacity {
- pp.setTempStepDiv(c, pp.fineStepDiv)
- pp.setTempBias(c, bias)
- }
- pp.setTempCapacity(c, maxTarget)
- c.minTarget = minTarget
- pp.removeFromQueues(c)
- pp.activeQueue.Push(c)
- pp.enforceLimits()
- updates := pp.finalizeChanges(c.tempCapacity >= minTarget && c.tempCapacity <= maxTarget && c.tempCapacity != c.capacity)
- pp.lock.Unlock()
- pp.updateFlags(updates)
- return c.capacity
-}
-
-// SetLimits sets the maximum number and total capacity of simultaneously active nodes
-func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) {
- pp.lock.Lock()
- pp.activeQueue.Refresh()
- inc := (maxCount > pp.maxCount) || (maxCap > pp.maxCap)
- dec := (maxCount < pp.maxCount) || (maxCap < pp.maxCap)
- pp.maxCount, pp.maxCap = maxCount, maxCap
-
- var updates []capUpdate
- if dec {
- pp.enforceLimits()
- updates = pp.finalizeChanges(true)
- }
- if inc {
- updates = append(updates, pp.tryActivate(false)...)
- }
- pp.lock.Unlock()
- pp.ns.Operation(func() { pp.updateFlags(updates) })
-}
-
-// setActiveBias sets the bias applied when trying to activate inactive nodes
-func (pp *priorityPool) setActiveBias(bias time.Duration) {
- pp.lock.Lock()
- pp.activeBias = bias
- if pp.activeBias < time.Duration(1) {
- pp.activeBias = time.Duration(1)
- }
- updates := pp.tryActivate(false)
- pp.lock.Unlock()
- pp.ns.Operation(func() { pp.updateFlags(updates) })
-}
-
-// Active returns the number and total capacity of currently active nodes
-func (pp *priorityPool) Active() (uint64, uint64) {
- pp.lock.Lock()
- defer pp.lock.Unlock()
-
- return pp.activeCount, pp.activeCap
-}
-
-// Inactive returns the number of currently inactive nodes
-func (pp *priorityPool) Inactive() int {
- pp.lock.Lock()
- defer pp.lock.Unlock()
-
- return pp.inactiveQueue.Size()
-}
-
-// Limits returns the maximum allowed number and total capacity of active nodes
-func (pp *priorityPool) Limits() (uint64, uint64) {
- pp.lock.Lock()
- defer pp.lock.Unlock()
-
- return pp.maxCount, pp.maxCap
-}
-
-// inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue
-func inactiveSetIndex(a *ppNodeInfo, index int) {
- a.inactiveIndex = index
-}
-
-// activeSetIndex callback updates ppNodeInfo item index in activeQueue
-func activeSetIndex(a *ppNodeInfo, index int) {
- a.activeIndex = index
-}
-
-// invertPriority inverts a priority value. The active queue uses inverted priorities
-// because the node on the top is the first to be deactivated.
-func invertPriority(p int64) int64 {
- if p == math.MinInt64 {
- return math.MaxInt64
- }
- return -p
-}
-
-// activePriority callback returns actual priority of ppNodeInfo item in activeQueue
-func activePriority(c *ppNodeInfo) int64 {
- if c.bias == 0 {
- return invertPriority(c.nodePriority.priority(c.tempCapacity))
- } else {
- return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, 0, c.bias, true))
- }
-}
-
-// activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue
-func (pp *priorityPool) activeMaxPriority(c *ppNodeInfo, until mclock.AbsTime) int64 {
- future := time.Duration(until - pp.clock.Now())
- if future < 0 {
- future = 0
- }
- return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, future, c.bias, false))
-}
-
-// inactivePriority callback returns actual priority of ppNodeInfo item in inactiveQueue
-func (pp *priorityPool) inactivePriority(p *ppNodeInfo) int64 {
- return p.nodePriority.priority(pp.minCap)
-}
-
-// removeFromQueues removes the node from the active/inactive queues
-func (pp *priorityPool) removeFromQueues(c *ppNodeInfo) {
- if c.activeIndex >= 0 {
- pp.activeQueue.Remove(c.activeIndex)
- }
- if c.inactiveIndex >= 0 {
- pp.inactiveQueue.Remove(c.inactiveIndex)
- }
-}
-
-// connectNode is called when a new node has been added to the pool (inactiveFlag set)
-// Note: this function should run inside a NodeStateMachine operation
-func (pp *priorityPool) connectNode(c *ppNodeInfo) {
- pp.lock.Lock()
- pp.activeQueue.Refresh()
- if c.connected {
- pp.lock.Unlock()
- return
- }
- c.connected = true
- pp.inactiveQueue.Push(c, pp.inactivePriority(c))
- updates := pp.tryActivate(false)
- pp.lock.Unlock()
- pp.updateFlags(updates)
-}
-
-// disconnectNode is called when a node has been removed from the pool (both inactiveFlag
-// and activeFlag reset)
-// Note: this function should run inside a NodeStateMachine operation
-func (pp *priorityPool) disconnectNode(c *ppNodeInfo) {
- pp.lock.Lock()
- pp.activeQueue.Refresh()
- if !c.connected {
- pp.lock.Unlock()
- return
- }
- c.connected = false
- pp.removeFromQueues(c)
-
- var updates []capUpdate
- if c.capacity != 0 {
- pp.setTempState(c)
- pp.setTempCapacity(c, 0)
- updates = pp.tryActivate(true)
- }
- pp.lock.Unlock()
- pp.updateFlags(updates)
-}
-
-// setTempState internally puts a node in a temporary state that can either be reverted
-// or confirmed later. This temporary state allows changing the capacity of a node and
-// moving it between the active and inactive queue. activeFlag/inactiveFlag and
-// capacityField are not changed while the changes are still temporary.
-func (pp *priorityPool) setTempState(c *ppNodeInfo) {
- if c.tempState {
- return
- }
- c.tempState = true
- if c.tempCapacity != c.capacity { // should never happen
- log.Error("tempCapacity != capacity when entering tempState")
- }
- // Assign all the defaults to the temp state.
- c.minTarget = pp.minCap
- c.stepDiv = pp.capacityStepDiv
- c.bias = 0
- pp.tempState = append(pp.tempState, c)
-}
-
-// unsetTempState revokes the temp status of the node and reset all internal
-// fields to the default value.
-func (pp *priorityPool) unsetTempState(c *ppNodeInfo) {
- if !c.tempState {
- return
- }
- c.tempState = false
- if c.tempCapacity != c.capacity { // should never happen
- log.Error("tempCapacity != capacity when leaving tempState")
- }
- c.minTarget = pp.minCap
- c.stepDiv = pp.capacityStepDiv
- c.bias = 0
-}
-
-// setTempCapacity changes the capacity of a node in the temporary state and adjusts
-// activeCap and activeCount accordingly. Since this change is performed in the temporary
-// state it should be called after setTempState and before finalizeChanges.
-func (pp *priorityPool) setTempCapacity(c *ppNodeInfo, cap uint64) {
- if !c.tempState { // should never happen
- log.Error("Node is not in temporary state")
- return
- }
- pp.activeCap += cap - c.tempCapacity
- if c.tempCapacity == 0 {
- pp.activeCount++
- }
- if cap == 0 {
- pp.activeCount--
- }
- c.tempCapacity = cap
-}
-
-// setTempBias changes the connection bias of a node in the temporary state.
-func (pp *priorityPool) setTempBias(c *ppNodeInfo, bias time.Duration) {
- if !c.tempState { // should never happen
- log.Error("Node is not in temporary state")
- return
- }
- c.bias = bias
-}
-
-// setTempStepDiv changes the capacity divisor of a node in the temporary state.
-func (pp *priorityPool) setTempStepDiv(c *ppNodeInfo, stepDiv uint64) {
- if !c.tempState { // should never happen
- log.Error("Node is not in temporary state")
- return
- }
- c.stepDiv = stepDiv
-}
-
-// enforceLimits enforces active node count and total capacity limits. It returns the
-// lowest active node priority. Note that this function is performed on the temporary
-// internal state.
-func (pp *priorityPool) enforceLimits() (*ppNodeInfo, int64) {
- if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount {
- return nil, math.MinInt64
- }
- var (
- lastNode *ppNodeInfo
- maxActivePriority int64
- )
- pp.activeQueue.MultiPop(func(c *ppNodeInfo, priority int64) bool {
- lastNode = c
- pp.setTempState(c)
- maxActivePriority = priority
- if c.tempCapacity == c.minTarget || pp.activeCount > pp.maxCount {
- pp.setTempCapacity(c, 0)
- } else {
- sub := c.tempCapacity / c.stepDiv
- if sub == 0 {
- sub = 1
- }
- if c.tempCapacity-sub < c.minTarget {
- sub = c.tempCapacity - c.minTarget
- }
- pp.setTempCapacity(c, c.tempCapacity-sub)
- pp.activeQueue.Push(c)
- }
- return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount
- })
- return lastNode, invertPriority(maxActivePriority)
-}
-
-// finalizeChanges either commits or reverts temporary changes. The necessary capacity
-// field and according flag updates are not performed here but returned in a list because
-// they should be performed while the mutex is not held.
-func (pp *priorityPool) finalizeChanges(commit bool) (updates []capUpdate) {
- for _, c := range pp.tempState {
- // always remove and push back in order to update biased priority
- pp.removeFromQueues(c)
- oldCapacity := c.capacity
- if commit {
- c.capacity = c.tempCapacity
- } else {
- pp.setTempCapacity(c, c.capacity) // revert activeCount/activeCap
- }
- pp.unsetTempState(c)
-
- if c.connected {
- if c.capacity != 0 {
- pp.activeQueue.Push(c)
- } else {
- pp.inactiveQueue.Push(c, pp.inactivePriority(c))
- }
- if c.capacity != oldCapacity {
- updates = append(updates, capUpdate{c.node, oldCapacity, c.capacity})
- }
- }
- }
- pp.tempState = nil
- if commit {
- pp.ccUpdateForced = true
- }
- return
-}
-
-// capUpdate describes a capacityField and activeFlag/inactiveFlag update
-type capUpdate struct {
- node *enode.Node
- oldCap, newCap uint64
-}
-
-// updateFlags performs capacityField and activeFlag/inactiveFlag updates while the
-// pool mutex is not held
-// Note: this function should run inside a NodeStateMachine operation
-func (pp *priorityPool) updateFlags(updates []capUpdate) {
- for _, f := range updates {
- if f.oldCap == 0 {
- pp.ns.SetStateSub(f.node, pp.setup.activeFlag, pp.setup.inactiveFlag, 0)
- }
- if f.newCap == 0 {
- pp.ns.SetStateSub(f.node, pp.setup.inactiveFlag, pp.setup.activeFlag, 0)
- pp.ns.SetFieldSub(f.node, pp.setup.capacityField, nil)
- } else {
- pp.ns.SetFieldSub(f.node, pp.setup.capacityField, f.newCap)
- }
- }
-}
-
-// tryActivate tries to activate inactive nodes if possible
-func (pp *priorityPool) tryActivate(commit bool) []capUpdate {
- for pp.inactiveQueue.Size() > 0 {
- c := pp.inactiveQueue.PopItem()
- pp.setTempState(c)
- pp.setTempBias(c, pp.activeBias)
- pp.setTempCapacity(c, pp.minCap)
- pp.activeQueue.Push(c)
- pp.enforceLimits()
- if c.tempCapacity > 0 {
- commit = true
- pp.setTempBias(c, 0)
- } else {
- break
- }
- }
- pp.ccUpdateForced = true
- return pp.finalizeChanges(commit)
-}
-
-// updatePriority gets the current priority value of the given node from the nodePriority
-// interface and performs the necessary changes. It is triggered by updateFlag.
-// Note: this function should run inside a NodeStateMachine operation
-func (pp *priorityPool) updatePriority(node *enode.Node) {
- pp.lock.Lock()
- pp.activeQueue.Refresh()
- c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo)
- if c == nil || !c.connected {
- pp.lock.Unlock()
- return
- }
- pp.removeFromQueues(c)
- if c.capacity != 0 {
- pp.activeQueue.Push(c)
- } else {
- pp.inactiveQueue.Push(c, pp.inactivePriority(c))
- }
- updates := pp.tryActivate(false)
- pp.lock.Unlock()
- pp.updateFlags(updates)
-}
-
-// capacityCurve is a snapshot of the priority pool contents in a format that can efficiently
-// estimate how much capacity could be granted to a given node at a given priority level.
-type capacityCurve struct {
- points []curvePoint // curve points sorted in descending order of priority
- index map[enode.ID][]int // curve point indexes belonging to each node
- excludeList []int // curve point indexes of excluded node
- excludeFirst bool // true if activeCount == maxCount
-}
-
-type curvePoint struct {
- freeCap uint64 // available capacity and node count at the current priority level
- nextPri int64 // next priority level where more capacity will be available
-}
-
-// getCapacityCurve returns a new or recently cached capacityCurve based on the contents of the pool
-func (pp *priorityPool) getCapacityCurve() *capacityCurve {
- pp.lock.Lock()
- defer pp.lock.Unlock()
-
- now := pp.clock.Now()
- dt := time.Duration(now - pp.ccUpdatedAt)
- if !pp.ccUpdateForced && pp.cachedCurve != nil && dt < time.Second*10 {
- return pp.cachedCurve
- }
-
- pp.ccUpdateForced = false
- pp.ccUpdatedAt = now
- curve := &capacityCurve{
- index: make(map[enode.ID][]int),
- }
- pp.cachedCurve = curve
-
- var excludeID enode.ID
- excludeFirst := pp.maxCount == pp.activeCount
- // reduce node capacities or remove nodes until nothing is left in the queue;
- // record the available capacity and the necessary priority after each step
- lastPri := int64(math.MinInt64)
- for pp.activeCap > 0 {
- cp := curvePoint{}
- if pp.activeCap > pp.maxCap {
- log.Error("Active capacity is greater than allowed maximum", "active", pp.activeCap, "maximum", pp.maxCap)
- } else {
- cp.freeCap = pp.maxCap - pp.activeCap
- }
- // temporarily increase activeCap to enforce reducing or removing a node capacity
- tempCap := cp.freeCap + 1
- pp.activeCap += tempCap
- var next *ppNodeInfo
- // enforceLimits removes the lowest priority node if it has minimal capacity,
- // otherwise reduces its capacity
- next, cp.nextPri = pp.enforceLimits()
- if cp.nextPri < lastPri {
- // enforce monotonicity which may be broken by continuously changing priorities
- cp.nextPri = lastPri
- } else {
- lastPri = cp.nextPri
- }
- pp.activeCap -= tempCap
- if next == nil {
- log.Error("getCapacityCurve: cannot remove next element from the priority queue")
- break
- }
- id := next.node.ID()
- if excludeFirst {
- // if the node count limit is already reached then mark the node with the
- // lowest priority for exclusion
- curve.excludeFirst = true
- excludeID = id
- excludeFirst = false
- }
- // multiple curve points and therefore multiple indexes may belong to a node
- // if it was removed in multiple steps (if its capacity was more than the minimum)
- curve.index[id] = append(curve.index[id], len(curve.points))
- curve.points = append(curve.points, cp)
- }
- // restore original state of the queue
- pp.finalizeChanges(false)
- curve.points = append(curve.points, curvePoint{
- freeCap: pp.maxCap,
- nextPri: math.MaxInt64,
- })
- if curve.excludeFirst {
- curve.excludeList = curve.index[excludeID]
- }
- return curve
-}
-
-// exclude returns a capacityCurve with the given node excluded from the original curve
-func (cc *capacityCurve) exclude(id enode.ID) *capacityCurve {
- if excludeList, ok := cc.index[id]; ok {
- // return a new version of the curve (only one excluded node can be selected)
- // Note: if the first node was excluded by default (excludeFirst == true) then
- // we can forget about that and exclude the node with the given id instead.
- return &capacityCurve{
- points: cc.points,
- index: cc.index,
- excludeList: excludeList,
- }
- }
- return cc
-}
-
-func (cc *capacityCurve) getPoint(i int) curvePoint {
- cp := cc.points[i]
- if i == 0 && cc.excludeFirst {
- cp.freeCap = 0
- return cp
- }
- for ii := len(cc.excludeList) - 1; ii >= 0; ii-- {
- ei := cc.excludeList[ii]
- if ei < i {
- break
- }
- e1, e2 := cc.points[ei], cc.points[ei+1]
- cp.freeCap += e2.freeCap - e1.freeCap
- }
- return cp
-}
-
-// maxCapacity calculates the maximum capacity available for a node with a given
-// (monotonically decreasing) priority vs. capacity function. Note that if the requesting
-// node is already in the pool then it should be excluded from the curve in order to get
-// the correct result.
-func (cc *capacityCurve) maxCapacity(priority func(cap uint64) int64) uint64 {
- min, max := 0, len(cc.points)-1 // the curve always has at least one point
- for min < max {
- mid := (min + max) / 2
- cp := cc.getPoint(mid)
- if cp.freeCap == 0 || priority(cp.freeCap) > cp.nextPri {
- min = mid + 1
- } else {
- max = mid
- }
- }
- cp2 := cc.getPoint(min)
- if cp2.freeCap == 0 || min == 0 {
- return cp2.freeCap
- }
- cp1 := cc.getPoint(min - 1)
- if priority(cp2.freeCap) > cp1.nextPri {
- return cp2.freeCap
- }
- minc, maxc := cp1.freeCap, cp2.freeCap-1
- for minc < maxc {
- midc := (minc + maxc + 1) / 2
- if midc == 0 || priority(midc) > cp1.nextPri {
- minc = midc
- } else {
- maxc = midc - 1
- }
- }
- return maxc
-}
diff --git a/les/vflux/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go
deleted file mode 100644
index 515231211..000000000
--- a/les/vflux/server/prioritypool_test.go
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2020 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 .
-
-package server
-
-import (
- "math/rand"
- "reflect"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-const (
- testCapacityStepDiv = 100
- testCapacityToleranceDiv = 10
- testMinCap = 100
-)
-
-type ppTestClient struct {
- node *enode.Node
- balance, cap uint64
-}
-
-func (c *ppTestClient) priority(cap uint64) int64 {
- return int64(c.balance / cap)
-}
-
-func (c *ppTestClient) estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 {
- return int64(c.balance / cap)
-}
-
-func TestPriorityPool(t *testing.T) {
- clock := &mclock.Simulated{}
- setup := newServerSetup()
- setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{}))
- ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
-
- ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
- if n := ns.GetField(node, setup.balanceField); n != nil {
- c := n.(*ppTestClient)
- c.cap = newValue.(uint64)
- }
- })
- pp := newPriorityPool(ns, setup, clock, testMinCap, 0, testCapacityStepDiv, testCapacityStepDiv)
- ns.Start()
- pp.SetLimits(100, 1000000)
- clients := make([]*ppTestClient, 100)
- raise := func(c *ppTestClient) {
- for {
- var ok bool
- ns.Operation(func() {
- newCap := c.cap + c.cap/testCapacityStepDiv
- ok = pp.requestCapacity(c.node, newCap, newCap, 0) == newCap
- })
- if !ok {
- return
- }
- }
- }
- var sumBalance uint64
- check := func(c *ppTestClient) {
- expCap := 1000000 * c.balance / sumBalance
- capTol := expCap / testCapacityToleranceDiv
- if c.cap < expCap-capTol || c.cap > expCap+capTol {
- t.Errorf("Wrong node capacity (expected %d, got %d)", expCap, c.cap)
- }
- }
-
- for i := range clients {
- c := &ppTestClient{
- node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}),
- balance: 100000000000,
- cap: 1000,
- }
- sumBalance += c.balance
- clients[i] = c
- ns.SetField(c.node, setup.balanceField, c)
- ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0)
- raise(c)
- check(c)
- }
-
- for count := 0; count < 100; count++ {
- c := clients[rand.Intn(len(clients))]
- oldBalance := c.balance
- c.balance = uint64(rand.Int63n(100000000000) + 100000000000)
- sumBalance += c.balance - oldBalance
- pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0)
- pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0)
- if c.balance > oldBalance {
- raise(c)
- } else {
- for _, c := range clients {
- raise(c)
- }
- }
- // check whether capacities are proportional to balances
- for _, c := range clients {
- check(c)
- }
- if count%10 == 0 {
- // test available capacity calculation with capacity curve
- c = clients[rand.Intn(len(clients))]
- curve := pp.getCapacityCurve().exclude(c.node.ID())
-
- add := uint64(rand.Int63n(10000000000000))
- c.balance += add
- sumBalance += add
- expCap := curve.maxCapacity(func(cap uint64) int64 {
- return int64(c.balance / cap)
- })
- var ok bool
- expFail := expCap + 10
- if expFail < testMinCap {
- expFail = testMinCap
- }
- ns.Operation(func() {
- ok = pp.requestCapacity(c.node, expFail, expFail, 0) == expFail
- })
- if ok {
- t.Errorf("Request for more than expected available capacity succeeded")
- }
- if expCap >= testMinCap {
- ns.Operation(func() {
- ok = pp.requestCapacity(c.node, expCap, expCap, 0) == expCap
- })
- if !ok {
- t.Errorf("Request for expected available capacity failed")
- }
- }
- c.balance -= add
- sumBalance -= add
- pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0)
- pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0)
- for _, c := range clients {
- raise(c)
- }
- }
- }
-
- ns.Stop()
-}
-
-func TestCapacityCurve(t *testing.T) {
- clock := &mclock.Simulated{}
- setup := newServerSetup()
- setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{}))
- ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
-
- pp := newPriorityPool(ns, setup, clock, 400000, 0, 2, 2)
- ns.Start()
- pp.SetLimits(10, 10000000)
- clients := make([]*ppTestClient, 10)
-
- for i := range clients {
- c := &ppTestClient{
- node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}),
- balance: 100000000000 * uint64(i+1),
- cap: 1000000,
- }
- clients[i] = c
- ns.SetField(c.node, setup.balanceField, c)
- ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0)
- ns.Operation(func() {
- pp.requestCapacity(c.node, c.cap, c.cap, 0)
- })
- }
-
- curve := pp.getCapacityCurve()
- check := func(balance, expCap uint64) {
- cap := curve.maxCapacity(func(cap uint64) int64 {
- return int64(balance / cap)
- })
- var fail bool
- if cap == 0 || expCap == 0 {
- fail = cap != expCap
- } else {
- pri := balance / cap
- expPri := balance / expCap
- fail = pri != expPri && pri != expPri+1
- }
- if fail {
- t.Errorf("Incorrect capacity for %d balance (got %d, expected %d)", balance, cap, expCap)
- }
- }
-
- check(0, 0)
- check(10000000000, 100000)
- check(50000000000, 500000)
- check(100000000000, 1000000)
- check(200000000000, 1000000)
- check(300000000000, 1500000)
- check(450000000000, 1500000)
- check(600000000000, 2000000)
- check(800000000000, 2000000)
- check(1000000000000, 2500000)
-
- pp.SetLimits(11, 10000000)
- curve = pp.getCapacityCurve()
-
- check(0, 0)
- check(10000000000, 100000)
- check(50000000000, 500000)
- check(150000000000, 750000)
- check(200000000000, 1000000)
- check(220000000000, 1100000)
- check(275000000000, 1100000)
- check(375000000000, 1500000)
- check(450000000000, 1500000)
- check(600000000000, 2000000)
- check(800000000000, 2000000)
- check(1000000000000, 2500000)
-
- ns.Stop()
-}
diff --git a/les/vflux/server/service.go b/les/vflux/server/service.go
deleted file mode 100644
index 40515f072..000000000
--- a/les/vflux/server/service.go
+++ /dev/null
@@ -1,120 +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 .
-
-package server
-
-import (
- "net"
- "strings"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/les/utils"
- "github.com/ethereum/go-ethereum/les/vflux"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-type (
- // Server serves vflux requests
- Server struct {
- limiter *utils.Limiter
- lock sync.Mutex
- services map[string]*serviceEntry
- delayPerRequest time.Duration
- }
-
- // Service is a service registered at the Server and identified by a string id
- Service interface {
- Handle(id enode.ID, address string, name string, data []byte) []byte // never called concurrently
- }
-
- serviceEntry struct {
- id, desc string
- backend Service
- }
-)
-
-// NewServer creates a new Server
-func NewServer(delayPerRequest time.Duration) *Server {
- return &Server{
- limiter: utils.NewLimiter(1000),
- delayPerRequest: delayPerRequest,
- services: make(map[string]*serviceEntry),
- }
-}
-
-// Register registers a Service
-func (s *Server) Register(b Service, id, desc string) {
- srv := &serviceEntry{backend: b, id: id, desc: desc}
- if strings.Contains(srv.id, ":") {
- // srv.id + ":" will be used as a service database prefix
- log.Error("Service ID contains ':'", "id", srv.id)
- return
- }
- s.lock.Lock()
- s.services[srv.id] = srv
- s.lock.Unlock()
-}
-
-// Serve serves a vflux request batch
-// Note: requests are served by the Handle functions of the registered services. Serve
-// may be called concurrently but the Handle functions are called sequentially and
-// therefore thread safety is guaranteed.
-func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vflux.Replies {
- reqLen := uint(len(requests))
- if reqLen == 0 || reqLen > vflux.MaxRequestLength {
- return nil
- }
- // Note: the value parameter will be supplied by the token sale module (total amount paid)
- ch := <-s.limiter.Add(id, address, 0, reqLen)
- if ch == nil {
- return nil
- }
- // Note: the limiter ensures that the following section is not running concurrently,
- // the lock only protects against contention caused by new service registration
- s.lock.Lock()
- results := make(vflux.Replies, len(requests))
- for i, req := range requests {
- if service := s.services[req.Service]; service != nil {
- results[i] = service.backend.Handle(id, address, req.Name, req.Params)
- }
- }
- s.lock.Unlock()
- time.Sleep(s.delayPerRequest * time.Duration(reqLen))
- close(ch)
- return results
-}
-
-// ServeEncoded serves an encoded vflux request batch and returns the encoded replies
-func (s *Server) ServeEncoded(id enode.ID, addr *net.UDPAddr, req []byte) []byte {
- var requests vflux.Requests
- if err := rlp.DecodeBytes(req, &requests); err != nil {
- return nil
- }
- results := s.Serve(id, addr.String(), requests)
- if results == nil {
- return nil
- }
- res, _ := rlp.EncodeToBytes(&results)
- return res
-}
-
-// Stop shuts down the server
-func (s *Server) Stop() {
- s.limiter.Stop()
-}
diff --git a/les/vflux/server/status.go b/les/vflux/server/status.go
deleted file mode 100644
index 2d7e25b68..000000000
--- a/les/vflux/server/status.go
+++ /dev/null
@@ -1,59 +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 .
-
-package server
-
-import (
- "reflect"
-
- "github.com/ethereum/go-ethereum/p2p/nodestate"
-)
-
-type peerWrapper struct{ clientPeer } // the NodeStateMachine type system needs this wrapper
-
-// serverSetup is a wrapper of the node state machine setup, which contains
-// all the created flags and fields used in the vflux server side.
-type serverSetup struct {
- setup *nodestate.Setup
- clientField nodestate.Field // Field contains the client peer handler
-
- // Flags and fields controlled by balance tracker. BalanceTracker
- // is responsible for setting/deleting these flags or fields.
- priorityFlag nodestate.Flags // Flag is set if the node has a positive balance
- updateFlag nodestate.Flags // Flag is set whenever the node balance is changed(priority changed)
- balanceField nodestate.Field // Field contains the client balance for priority calculation
-
- // Flags and fields controlled by priority queue. Priority queue
- // is responsible for setting/deleting these flags or fields.
- activeFlag nodestate.Flags // Flag is set if the node is active
- inactiveFlag nodestate.Flags // Flag is set if the node is inactive
- capacityField nodestate.Field // Field contains the capacity of the node
- queueField nodestate.Field // Field contains the information in the priority queue
-}
-
-// newServerSetup initializes the setup for state machine and returns the flags/fields group.
-func newServerSetup() *serverSetup {
- setup := &serverSetup{setup: &nodestate.Setup{}}
- setup.clientField = setup.setup.NewField("client", reflect.TypeOf(peerWrapper{}))
- setup.priorityFlag = setup.setup.NewFlag("priority")
- setup.updateFlag = setup.setup.NewFlag("update")
- setup.balanceField = setup.setup.NewField("balance", reflect.TypeOf(&nodeBalance{}))
- setup.activeFlag = setup.setup.NewFlag("active")
- setup.inactiveFlag = setup.setup.NewFlag("inactive")
- setup.capacityField = setup.setup.NewField("capacity", reflect.TypeOf(uint64(0)))
- setup.queueField = setup.setup.NewField("queue", reflect.TypeOf(&ppNodeInfo{}))
- return setup
-}
diff --git a/light/lightchain.go b/light/lightchain.go
deleted file mode 100644
index 617658b85..000000000
--- a/light/lightchain.go
+++ /dev/null
@@ -1,531 +0,0 @@
-// Copyright 2016 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 .
-
-// Package light implements on-demand retrieval capable state and chain objects
-// for the Ethereum Light Client.
-package light
-
-import (
- "context"
- "errors"
- "math/big"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/consensus"
- "github.com/ethereum/go-ethereum/core"
- "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/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var (
- bodyCacheLimit = 256
- blockCacheLimit = 256
-)
-
-// LightChain represents a canonical chain that by default only handles block
-// headers, downloading block bodies and receipts on demand through an ODR
-// interface. It only does header validation during chain insertion.
-type LightChain struct {
- hc *core.HeaderChain
- indexerConfig *IndexerConfig
- chainDb ethdb.Database
- engine consensus.Engine
- odr OdrBackend
- chainFeed event.Feed
- chainSideFeed event.Feed
- chainHeadFeed event.Feed
- scope event.SubscriptionScope
- genesisBlock *types.Block
- forker *core.ForkChoice
-
- bodyCache *lru.Cache[common.Hash, *types.Body]
- bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
- blockCache *lru.Cache[common.Hash, *types.Block]
-
- chainmu sync.RWMutex // protects header inserts
- quit chan struct{}
- wg sync.WaitGroup
-
- // Atomic boolean switches:
- stopped atomic.Bool // whether LightChain is stopped or running
- procInterrupt atomic.Bool // interrupts chain insert
-}
-
-// NewLightChain returns a fully initialised light chain using information
-// available in the database. It initialises the default Ethereum header
-// validator.
-func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine) (*LightChain, error) {
- bc := &LightChain{
- chainDb: odr.Database(),
- indexerConfig: odr.IndexerConfig(),
- odr: odr,
- quit: make(chan struct{}),
- bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
- bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
- blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
- engine: engine,
- }
- bc.forker = core.NewForkChoice(bc, nil)
- var err error
- bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt)
- if err != nil {
- return nil, err
- }
- bc.genesisBlock, _ = bc.GetBlockByNumber(NoOdr, 0)
- if bc.genesisBlock == nil {
- return nil, core.ErrNoGenesis
- }
- if err := bc.loadLastState(); err != nil {
- return nil, err
- }
- // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
- for hash := range core.BadHashes {
- if header := bc.GetHeaderByHash(hash); header != nil {
- log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
- bc.SetHead(header.Number.Uint64() - 1)
- log.Info("Chain rewind was successful, resuming normal operation")
- }
- }
- return bc, nil
-}
-
-func (lc *LightChain) getProcInterrupt() bool {
- return lc.procInterrupt.Load()
-}
-
-// Odr returns the ODR backend of the chain
-func (lc *LightChain) Odr() OdrBackend {
- return lc.odr
-}
-
-// HeaderChain returns the underlying header chain.
-func (lc *LightChain) HeaderChain() *core.HeaderChain {
- return lc.hc
-}
-
-// loadLastState loads the last known chain state from the database. This method
-// assumes that the chain manager mutex is held.
-func (lc *LightChain) loadLastState() error {
- if head := rawdb.ReadHeadHeaderHash(lc.chainDb); head == (common.Hash{}) {
- // Corrupt or empty database, init from scratch
- lc.Reset()
- } else {
- header := lc.GetHeaderByHash(head)
- if header == nil {
- // Corrupt or empty database, init from scratch
- lc.Reset()
- } else {
- lc.hc.SetCurrentHeader(header)
- }
- }
- // Issue a status log and return
- header := lc.hc.CurrentHeader()
- headerTd := lc.GetTd(header.Hash(), header.Number.Uint64())
- log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(header.Time), 0)))
- return nil
-}
-
-// SetHead rewinds the local chain to a new head. Everything above the new
-// head will be deleted and the new one set.
-func (lc *LightChain) SetHead(head uint64) error {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.hc.SetHead(head, nil, nil)
- return lc.loadLastState()
-}
-
-// SetHeadWithTimestamp rewinds the local chain to a new head that has at max
-// the given timestamp. Everything above the new head will be deleted and the
-// new one set.
-func (lc *LightChain) SetHeadWithTimestamp(timestamp uint64) error {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.hc.SetHeadWithTimestamp(timestamp, nil, nil)
- return lc.loadLastState()
-}
-
-// GasLimit returns the gas limit of the current HEAD block.
-func (lc *LightChain) GasLimit() uint64 {
- return lc.hc.CurrentHeader().GasLimit
-}
-
-// Reset purges the entire blockchain, restoring it to its genesis state.
-func (lc *LightChain) Reset() {
- lc.ResetWithGenesisBlock(lc.genesisBlock)
-}
-
-// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
-// specified genesis state.
-func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) {
- // Dump the entire block chain and purge the caches
- lc.SetHead(0)
-
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- // Prepare the genesis block and reinitialise the chain
- batch := lc.chainDb.NewBatch()
- rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
- rawdb.WriteBlock(batch, genesis)
- rawdb.WriteHeadHeaderHash(batch, genesis.Hash())
- if err := batch.Write(); err != nil {
- log.Crit("Failed to reset genesis block", "err", err)
- }
- lc.genesisBlock = genesis
- lc.hc.SetGenesis(lc.genesisBlock.Header())
- lc.hc.SetCurrentHeader(lc.genesisBlock.Header())
-}
-
-// Accessors
-
-// Engine retrieves the light chain's consensus engine.
-func (lc *LightChain) Engine() consensus.Engine { return lc.engine }
-
-// Genesis returns the genesis block
-func (lc *LightChain) Genesis() *types.Block {
- return lc.genesisBlock
-}
-
-func (lc *LightChain) StateCache() state.Database {
- panic("not implemented")
-}
-
-// GetBody retrieves a block body (transactions and uncles) from the database
-// or ODR service by hash, caching it if found.
-func (lc *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.Body, error) {
- // Short circuit if the body's already in the cache, retrieve otherwise
- if cached, ok := lc.bodyCache.Get(hash); ok {
- return cached, nil
- }
- number := lc.hc.GetBlockNumber(hash)
- if number == nil {
- return nil, errors.New("unknown block")
- }
- body, err := GetBody(ctx, lc.odr, hash, *number)
- if err != nil {
- return nil, err
- }
- // Cache the found body for next time and return
- lc.bodyCache.Add(hash, body)
- return body, nil
-}
-
-// GetBodyRLP retrieves a block body in RLP encoding from the database or
-// ODR service by hash, caching it if found.
-func (lc *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.RawValue, error) {
- // Short circuit if the body's already in the cache, retrieve otherwise
- if cached, ok := lc.bodyRLPCache.Get(hash); ok {
- return cached, nil
- }
- number := lc.hc.GetBlockNumber(hash)
- if number == nil {
- return nil, errors.New("unknown block")
- }
- body, err := GetBodyRLP(ctx, lc.odr, hash, *number)
- if err != nil {
- return nil, err
- }
- // Cache the found body for next time and return
- lc.bodyRLPCache.Add(hash, body)
- return body, nil
-}
-
-// HasBlock checks if a block is fully present in the database or not, caching
-// it if present.
-func (lc *LightChain) HasBlock(hash common.Hash, number uint64) bool {
- blk, _ := lc.GetBlock(NoOdr, hash, number)
- return blk != nil
-}
-
-// GetBlock retrieves a block from the database or ODR service by hash and number,
-// caching it if found.
-func (lc *LightChain) GetBlock(ctx context.Context, hash common.Hash, number uint64) (*types.Block, error) {
- // Short circuit if the block's already in the cache, retrieve otherwise
- if block, ok := lc.blockCache.Get(hash); ok {
- return block, nil
- }
- block, err := GetBlock(ctx, lc.odr, hash, number)
- if err != nil {
- return nil, err
- }
- // Cache the found block for next time and return
- lc.blockCache.Add(block.Hash(), block)
- return block, nil
-}
-
-// GetBlockByHash retrieves a block from the database or ODR service by hash,
-// caching it if found.
-func (lc *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
- number := lc.hc.GetBlockNumber(hash)
- if number == nil {
- return nil, errors.New("unknown block")
- }
- return lc.GetBlock(ctx, hash, *number)
-}
-
-// GetBlockByNumber retrieves a block from the database or ODR service by
-// number, caching it (associated with its hash) if found.
-func (lc *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) {
- hash, err := GetCanonicalHash(ctx, lc.odr, number)
- if hash == (common.Hash{}) || err != nil {
- return nil, err
- }
- return lc.GetBlock(ctx, hash, number)
-}
-
-// Stop stops the blockchain service. If any imports are currently in progress
-// it will abort them using the procInterrupt.
-func (lc *LightChain) Stop() {
- if !lc.stopped.CompareAndSwap(false, true) {
- return
- }
- close(lc.quit)
- lc.StopInsert()
- lc.wg.Wait()
- log.Info("Blockchain stopped")
-}
-
-// StopInsert interrupts all insertion methods, causing them to return
-// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after
-// calling this method.
-func (lc *LightChain) StopInsert() {
- lc.procInterrupt.Store(true)
-}
-
-// Rollback is designed to remove a chain of links from the database that aren't
-// certain enough to be valid.
-func (lc *LightChain) Rollback(chain []common.Hash) {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- batch := lc.chainDb.NewBatch()
- for i := len(chain) - 1; i >= 0; i-- {
- hash := chain[i]
-
- // Degrade the chain markers if they are explicitly reverted.
- // In theory we should update all in-memory markers in the
- // last step, however the direction of rollback is from high
- // to low, so it's safe the update in-memory markers directly.
- if head := lc.hc.CurrentHeader(); head.Hash() == hash {
- rawdb.WriteHeadHeaderHash(batch, head.ParentHash)
- lc.hc.SetCurrentHeader(lc.GetHeader(head.ParentHash, head.Number.Uint64()-1))
- }
- }
- if err := batch.Write(); err != nil {
- log.Crit("Failed to rollback light chain", "error", err)
- }
-}
-
-func (lc *LightChain) InsertHeader(header *types.Header) error {
- // Verify the header first before obtaining the lock
- headers := []*types.Header{header}
- if _, err := lc.hc.ValidateHeaderChain(headers); err != nil {
- return err
- }
- // Make sure only one thread manipulates the chain at once
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.wg.Add(1)
- defer lc.wg.Done()
-
- _, err := lc.hc.WriteHeaders(headers)
- log.Info("Inserted header", "number", header.Number, "hash", header.Hash())
- return err
-}
-
-func (lc *LightChain) SetCanonical(header *types.Header) error {
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.wg.Add(1)
- defer lc.wg.Done()
-
- if err := lc.hc.Reorg([]*types.Header{header}); err != nil {
- return err
- }
- // Emit events
- block := types.NewBlockWithHeader(header)
- lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()})
- lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block})
- log.Info("Set the chain head", "number", block.Number(), "hash", block.Hash())
- return nil
-}
-
-// InsertHeaderChain attempts to insert the given header chain in to the local
-// chain, possibly creating a reorg. If an error is returned, it will return the
-// index number of the failing header as well an error describing what went wrong.
-
-// In the case of a light chain, InsertHeaderChain also creates and posts light
-// chain events when necessary.
-func (lc *LightChain) InsertHeaderChain(chain []*types.Header) (int, error) {
- if len(chain) == 0 {
- return 0, nil
- }
- start := time.Now()
- if i, err := lc.hc.ValidateHeaderChain(chain); err != nil {
- return i, err
- }
-
- // Make sure only one thread manipulates the chain at once
- lc.chainmu.Lock()
- defer lc.chainmu.Unlock()
-
- lc.wg.Add(1)
- defer lc.wg.Done()
-
- status, err := lc.hc.InsertHeaderChain(chain, start, lc.forker)
- if err != nil || len(chain) == 0 {
- return 0, err
- }
-
- // Create chain event for the new head block of this insertion.
- var (
- lastHeader = chain[len(chain)-1]
- block = types.NewBlockWithHeader(lastHeader)
- )
- switch status {
- case core.CanonStatTy:
- lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()})
- lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block})
- case core.SideStatTy:
- lc.chainSideFeed.Send(core.ChainSideEvent{Block: block})
- }
- return 0, err
-}
-
-// CurrentHeader retrieves the current head header of the canonical chain. The
-// header is retrieved from the HeaderChain's internal cache.
-func (lc *LightChain) CurrentHeader() *types.Header {
- return lc.hc.CurrentHeader()
-}
-
-// GetTd retrieves a block's total difficulty in the canonical chain from the
-// database by hash and number, caching it if found.
-func (lc *LightChain) GetTd(hash common.Hash, number uint64) *big.Int {
- return lc.hc.GetTd(hash, number)
-}
-
-// GetTdOdr retrieves the total difficult from the database or
-// network by hash and number, caching it (associated with its hash) if found.
-func (lc *LightChain) GetTdOdr(ctx context.Context, hash common.Hash, number uint64) *big.Int {
- td := lc.GetTd(hash, number)
- if td != nil {
- return td
- }
- td, _ = GetTd(ctx, lc.odr, hash, number)
- return td
-}
-
-// GetHeader retrieves a block header from the database by hash and number,
-// caching it if found.
-func (lc *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header {
- return lc.hc.GetHeader(hash, number)
-}
-
-// GetHeaderByHash retrieves a block header from the database by hash, caching it if
-// found.
-func (lc *LightChain) GetHeaderByHash(hash common.Hash) *types.Header {
- return lc.hc.GetHeaderByHash(hash)
-}
-
-// HasHeader checks if a block header is present in the database or not, caching
-// it if present.
-func (lc *LightChain) HasHeader(hash common.Hash, number uint64) bool {
- return lc.hc.HasHeader(hash, number)
-}
-
-// GetCanonicalHash returns the canonical hash for a given block number
-func (bc *LightChain) GetCanonicalHash(number uint64) common.Hash {
- return bc.hc.GetCanonicalHash(number)
-}
-
-// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or
-// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the
-// number of blocks to be individually checked before we reach the canonical chain.
-//
-// Note: ancestor == 0 returns the same block, 1 returns its parent and so on.
-func (lc *LightChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) {
- return lc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
-}
-
-// GetHeaderByNumber retrieves a block header from the database by number,
-// caching it (associated with its hash) if found.
-func (lc *LightChain) GetHeaderByNumber(number uint64) *types.Header {
- return lc.hc.GetHeaderByNumber(number)
-}
-
-// GetHeaderByNumberOdr retrieves a block header from the database or network
-// by number, caching it (associated with its hash) if found.
-func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) (*types.Header, error) {
- if header := lc.hc.GetHeaderByNumber(number); header != nil {
- return header, nil
- }
- return GetHeaderByNumber(ctx, lc.odr, number)
-}
-
-// Config retrieves the header chain's chain configuration.
-func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() }
-
-// LockChain locks the chain mutex for reading so that multiple canonical hashes can be
-// retrieved while it is guaranteed that they belong to the same version of the chain
-func (lc *LightChain) LockChain() {
- lc.chainmu.RLock()
-}
-
-// UnlockChain unlocks the chain mutex
-func (lc *LightChain) UnlockChain() {
- lc.chainmu.RUnlock()
-}
-
-// SubscribeChainEvent registers a subscription of ChainEvent.
-func (lc *LightChain) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
- return lc.scope.Track(lc.chainFeed.Subscribe(ch))
-}
-
-// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent.
-func (lc *LightChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
- return lc.scope.Track(lc.chainHeadFeed.Subscribe(ch))
-}
-
-// SubscribeChainSideEvent registers a subscription of ChainSideEvent.
-func (lc *LightChain) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
- return lc.scope.Track(lc.chainSideFeed.Subscribe(ch))
-}
-
-// SubscribeLogsEvent implements the interface of filters.Backend
-// LightChain does not send logs events, so return an empty subscription.
-func (lc *LightChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
- return lc.scope.Track(new(event.Feed).Subscribe(ch))
-}
-
-// SubscribeRemovedLogsEvent implements the interface of filters.Backend
-// LightChain does not send core.RemovedLogsEvent, so return an empty subscription.
-func (lc *LightChain) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
- return lc.scope.Track(new(event.Feed).Subscribe(ch))
-}
diff --git a/light/lightchain_test.go b/light/lightchain_test.go
deleted file mode 100644
index 5694ca72c..000000000
--- a/light/lightchain_test.go
+++ /dev/null
@@ -1,358 +0,0 @@
-// Copyright 2016 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 .
-
-package light
-
-import (
- "context"
- "errors"
- "math/big"
- "testing"
-
- "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/ethdb"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-// So we can deterministically seed different blockchains
-var (
- canonicalSeed = 1
- forkSeed = 2
-)
-
-// makeHeaderChain creates a deterministic chain of headers rooted at parent.
-func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header {
- blocks, _ := core.GenerateChain(params.TestChainConfig, types.NewBlockWithHeader(parent), ethash.NewFaker(), db, n, func(i int, b *core.BlockGen) {
- b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
- })
- headers := make([]*types.Header, len(blocks))
- for i, block := range blocks {
- headers[i] = block.Header()
- }
- return headers
-}
-
-// newCanonical creates a chain database, and injects a deterministic canonical
-// chain. Depending on the full flag, if creates either a full block chain or a
-// header only chain.
-func newCanonical(n int) (ethdb.Database, *LightChain, error) {
- db := rawdb.NewMemoryDatabase()
- gspec := core.Genesis{Config: params.TestChainConfig}
- genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
- blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker())
-
- // Create and inject the requested chain
- if n == 0 {
- return db, blockchain, nil
- }
- // Header-only chain requested
- headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed)
- _, err := blockchain.InsertHeaderChain(headers)
- return db, blockchain, err
-}
-
-// newTestLightChain creates a LightChain that doesn't validate anything.
-func newTestLightChain() *LightChain {
- db := rawdb.NewMemoryDatabase()
- gspec := &core.Genesis{
- Difficulty: big.NewInt(1),
- Config: params.TestChainConfig,
- }
- gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
- lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker())
- if err != nil {
- panic(err)
- }
- return lc
-}
-
-// Test fork of length N starting from block i
-func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td1, td2 *big.Int)) {
- // Copy old chain up to #i into a new db
- db, LightChain2, err := newCanonical(i)
- if err != nil {
- t.Fatal("could not make new canonical in testFork", err)
- }
- // Assert the chains have the same header/block at #i
- var hash1, hash2 common.Hash
- hash1 = LightChain.GetHeaderByNumber(uint64(i)).Hash()
- hash2 = LightChain2.GetHeaderByNumber(uint64(i)).Hash()
- if hash1 != hash2 {
- t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1)
- }
- // Extend the newly created chain
- headerChainB := makeHeaderChain(LightChain2.CurrentHeader(), n, db, forkSeed)
- if _, err := LightChain2.InsertHeaderChain(headerChainB); err != nil {
- t.Fatalf("failed to insert forking chain: %v", err)
- }
- // Sanity check that the forked chain can be imported into the original
- var tdPre, tdPost *big.Int
- cur := LightChain.CurrentHeader()
- tdPre = LightChain.GetTd(cur.Hash(), cur.Number.Uint64())
- if err := testHeaderChainImport(headerChainB, LightChain); err != nil {
- t.Fatalf("failed to import forked header chain: %v", err)
- }
- last := headerChainB[len(headerChainB)-1]
- tdPost = LightChain.GetTd(last.Hash(), last.Number.Uint64())
- // Compare the total difficulties of the chains
- comparator(tdPre, tdPost)
-}
-
-// testHeaderChainImport tries to process a chain of header, writing them into
-// the database if successful.
-func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error {
- for _, header := range chain {
- // Try and validate the header
- if err := lightchain.engine.VerifyHeader(lightchain.hc, header); err != nil {
- return err
- }
- // Manually insert the header into the database, but don't reorganize (allows subsequent testing)
- lightchain.chainmu.Lock()
- rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(),
- new(big.Int).Add(header.Difficulty, lightchain.GetTd(header.ParentHash, header.Number.Uint64()-1)))
- rawdb.WriteHeader(lightchain.chainDb, header)
- lightchain.chainmu.Unlock()
- }
- return nil
-}
-
-// Tests that given a starting canonical chain of a given size, it can be extended
-// with various length chains.
-func TestExtendCanonicalHeaders(t *testing.T) {
- length := 5
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- better := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) <= 0 {
- t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
- }
- }
- // Start fork from current height
- testFork(t, processor, length, 1, better)
- testFork(t, processor, length, 2, better)
- testFork(t, processor, length, 5, better)
- testFork(t, processor, length, 10, better)
-}
-
-// Tests that given a starting canonical chain of a given size, creating shorter
-// forks do not take canonical ownership.
-func TestShorterForkHeaders(t *testing.T) {
- length := 10
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- worse := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) >= 0 {
- t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1)
- }
- }
- // Sum of numbers must be less than `length` for this to be a shorter fork
- testFork(t, processor, 0, 3, worse)
- testFork(t, processor, 0, 7, worse)
- testFork(t, processor, 1, 1, worse)
- testFork(t, processor, 1, 7, worse)
- testFork(t, processor, 5, 3, worse)
- testFork(t, processor, 5, 4, worse)
-}
-
-// Tests that given a starting canonical chain of a given size, creating longer
-// forks do take canonical ownership.
-func TestLongerForkHeaders(t *testing.T) {
- length := 10
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- better := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) <= 0 {
- t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
- }
- }
- // Sum of numbers must be greater than `length` for this to be a longer fork
- testFork(t, processor, 0, 11, better)
- testFork(t, processor, 0, 15, better)
- testFork(t, processor, 1, 10, better)
- testFork(t, processor, 1, 12, better)
- testFork(t, processor, 5, 6, better)
- testFork(t, processor, 5, 8, better)
-}
-
-// Tests that given a starting canonical chain of a given size, creating equal
-// forks do take canonical ownership.
-func TestEqualForkHeaders(t *testing.T) {
- length := 10
-
- // Make first chain starting from genesis
- _, processor, err := newCanonical(length)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Define the difficulty comparator
- equal := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) != 0 {
- t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1)
- }
- }
- // Sum of numbers must be equal to `length` for this to be an equal fork
- testFork(t, processor, 0, 10, equal)
- testFork(t, processor, 1, 9, equal)
- testFork(t, processor, 2, 8, equal)
- testFork(t, processor, 5, 5, equal)
- testFork(t, processor, 6, 4, equal)
- testFork(t, processor, 9, 1, equal)
-}
-
-// Tests that chains missing links do not get accepted by the processor.
-func TestBrokenHeaderChain(t *testing.T) {
- // Make chain starting from genesis
- db, LightChain, err := newCanonical(10)
- if err != nil {
- t.Fatalf("failed to make new canonical chain: %v", err)
- }
- // Create a forked chain, and try to insert with a missing link
- chain := makeHeaderChain(LightChain.CurrentHeader(), 5, db, forkSeed)[1:]
- if err := testHeaderChainImport(chain, LightChain); err == nil {
- t.Errorf("broken header chain not reported")
- }
-}
-
-func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header {
- var chain []*types.Header
- for i, difficulty := range d {
- header := &types.Header{
- Coinbase: common.Address{seed},
- Number: big.NewInt(int64(i + 1)),
- Difficulty: big.NewInt(int64(difficulty)),
- UncleHash: types.EmptyUncleHash,
- TxHash: types.EmptyTxsHash,
- ReceiptHash: types.EmptyReceiptsHash,
- }
- if i == 0 {
- header.ParentHash = genesis.Hash()
- } else {
- header.ParentHash = chain[i-1].Hash()
- }
- chain = append(chain, types.CopyHeader(header))
- }
- return chain
-}
-
-type dummyOdr struct {
- OdrBackend
- db ethdb.Database
- indexerConfig *IndexerConfig
-}
-
-func (odr *dummyOdr) Database() ethdb.Database {
- return odr.db
-}
-
-func (odr *dummyOdr) Retrieve(ctx context.Context, req OdrRequest) error {
- return nil
-}
-
-func (odr *dummyOdr) IndexerConfig() *IndexerConfig {
- return odr.indexerConfig
-}
-
-// Tests that reorganizing a long difficult chain after a short easy one
-// overwrites the canonical numbers and links in the database.
-func TestReorgLongHeaders(t *testing.T) {
- testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10)
-}
-
-// Tests that reorganizing a short difficult chain after a long easy one
-// overwrites the canonical numbers and links in the database.
-func TestReorgShortHeaders(t *testing.T) {
- testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11)
-}
-
-func testReorg(t *testing.T, first, second []int, td int64) {
- bc := newTestLightChain()
-
- // Insert an easy and a difficult chain afterwards
- bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11))
- bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22))
- // Check that the chain is valid number and link wise
- prev := bc.CurrentHeader()
- for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) {
- if prev.ParentHash != header.Hash() {
- t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash())
- }
- }
- // Make sure the chain total difficulty is the correct one
- want := new(big.Int).Add(bc.genesisBlock.Difficulty(), big.NewInt(td))
- if have := bc.GetTd(bc.CurrentHeader().Hash(), bc.CurrentHeader().Number.Uint64()); have.Cmp(want) != 0 {
- t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
- }
-}
-
-// Tests that the insertion functions detect banned hashes.
-func TestBadHeaderHashes(t *testing.T) {
- bc := newTestLightChain()
-
- // Create a chain, ban a hash and try to import
- var err error
- headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
- core.BadHashes[headers[2].Hash()] = true
- if _, err = bc.InsertHeaderChain(headers); !errors.Is(err, core.ErrBannedHash) {
- t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBannedHash)
- }
-}
-
-// Tests that bad hashes are detected on boot, and the chan rolled back to a
-// good state prior to the bad hash.
-func TestReorgBadHeaderHashes(t *testing.T) {
- bc := newTestLightChain()
-
- // Create a chain, import and ban afterwards
- headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10)
-
- if _, err := bc.InsertHeaderChain(headers); err != nil {
- t.Fatalf("failed to import headers: %v", err)
- }
- if bc.CurrentHeader().Hash() != headers[3].Hash() {
- t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash())
- }
- core.BadHashes[headers[3].Hash()] = true
- defer func() { delete(core.BadHashes, headers[3].Hash()) }()
-
- // Create a new LightChain and check that it rolled back the state.
- ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker())
- if err != nil {
- t.Fatalf("failed to create new chain manager: %v", err)
- }
- if ncm.CurrentHeader().Hash() != headers[2].Hash() {
- t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
- }
-}
diff --git a/light/odr.go b/light/odr.go
deleted file mode 100644
index 39f626ee2..000000000
--- a/light/odr.go
+++ /dev/null
@@ -1,196 +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 .
-
-package light
-
-import (
- "context"
- "errors"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// NoOdr is the default context passed to an ODR capable function when the ODR
-// service is not required.
-var NoOdr = context.Background()
-
-// ErrNoPeers is returned if no peers capable of serving a queued request are available
-var ErrNoPeers = errors.New("no suitable peers available")
-
-// OdrBackend is an interface to a backend service that handles ODR retrievals type
-type OdrBackend interface {
- Database() ethdb.Database
- ChtIndexer() *core.ChainIndexer
- BloomTrieIndexer() *core.ChainIndexer
- BloomIndexer() *core.ChainIndexer
- Retrieve(ctx context.Context, req OdrRequest) error
- RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error
- IndexerConfig() *IndexerConfig
-}
-
-// OdrRequest is an interface for retrieval requests
-type OdrRequest interface {
- StoreResult(db ethdb.Database)
-}
-
-// TrieID identifies a state or account storage trie
-type TrieID struct {
- BlockHash common.Hash
- BlockNumber uint64
- StateRoot common.Hash
- Root common.Hash
- AccountAddress []byte
-}
-
-// StateTrieID returns a TrieID for a state trie belonging to a certain block
-// header.
-func StateTrieID(header *types.Header) *TrieID {
- return &TrieID{
- BlockHash: header.Hash(),
- BlockNumber: header.Number.Uint64(),
- StateRoot: header.Root,
- Root: header.Root,
- AccountAddress: nil,
- }
-}
-
-// StorageTrieID returns a TrieID for a contract storage trie at a given account
-// of a given state trie. It also requires the root hash of the trie for
-// checking Merkle proofs.
-func StorageTrieID(state *TrieID, address common.Address, root common.Hash) *TrieID {
- return &TrieID{
- BlockHash: state.BlockHash,
- BlockNumber: state.BlockNumber,
- StateRoot: state.StateRoot,
- AccountAddress: address[:],
- Root: root,
- }
-}
-
-// TrieRequest is the ODR request type for state/storage trie entries
-type TrieRequest struct {
- Id *TrieID
- Key []byte
- Proof *trienode.ProofSet
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *TrieRequest) StoreResult(db ethdb.Database) {
- req.Proof.Store(db)
-}
-
-// CodeRequest is the ODR request type for retrieving contract code
-type CodeRequest struct {
- Id *TrieID // references storage trie of the account
- Hash common.Hash
- Data []byte
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *CodeRequest) StoreResult(db ethdb.Database) {
- rawdb.WriteCode(db, req.Hash, req.Data)
-}
-
-// BlockRequest is the ODR request type for retrieving block bodies
-type BlockRequest struct {
- Hash common.Hash
- Number uint64
- Header *types.Header
- Rlp []byte
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *BlockRequest) StoreResult(db ethdb.Database) {
- rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp)
-}
-
-// ReceiptsRequest is the ODR request type for retrieving receipts.
-type ReceiptsRequest struct {
- Hash common.Hash
- Number uint64
- Header *types.Header
- Receipts types.Receipts
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
- rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts)
-}
-
-// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie
-type ChtRequest struct {
- Config *IndexerConfig
- ChtNum, BlockNum uint64
- ChtRoot common.Hash
- Header *types.Header
- Td *big.Int
- Proof *trienode.ProofSet
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *ChtRequest) StoreResult(db ethdb.Database) {
- hash, num := req.Header.Hash(), req.Header.Number.Uint64()
- rawdb.WriteHeader(db, req.Header)
- rawdb.WriteTd(db, hash, num, req.Td)
- rawdb.WriteCanonicalHash(db, hash, num)
-}
-
-// BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure
-type BloomRequest struct {
- OdrRequest
- Config *IndexerConfig
- BloomTrieNum uint64
- BitIdx uint
- SectionIndexList []uint64
- BloomTrieRoot common.Hash
- BloomBits [][]byte
- Proofs *trienode.ProofSet
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *BloomRequest) StoreResult(db ethdb.Database) {
- for i, sectionIdx := range req.SectionIndexList {
- sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*req.Config.BloomTrieSize-1)
- // if we don't have the canonical hash stored for this section head number, we'll still store it under
- // a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical
- // hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the
- // bit vector again from the network.
- rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i])
- }
-}
-
-// TxStatus describes the status of a transaction
-type TxStatus struct {
- Status txpool.TxStatus
- Lookup *rawdb.LegacyTxLookupEntry `rlp:"nil"`
- Error string
-}
-
-// TxStatusRequest is the ODR request type for retrieving transaction status
-type TxStatusRequest struct {
- Hashes []common.Hash
- Status []TxStatus
-}
-
-// StoreResult stores the retrieved data in local database
-func (req *TxStatusRequest) StoreResult(db ethdb.Database) {}
diff --git a/light/odr_test.go b/light/odr_test.go
deleted file mode 100644
index c415d73e7..000000000
--- a/light/odr_test.go
+++ /dev/null
@@ -1,339 +0,0 @@
-// Copyright 2016 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 .
-
-package light
-
-import (
- "bytes"
- "context"
- "errors"
- "math/big"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/math"
- "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/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-var (
- testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
- testBankFunds = big.NewInt(1_000_000_000_000_000_000)
-
- acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
- acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
- acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey)
- acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey)
-
- testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
- testContractAddr common.Address
-)
-
-type testOdr struct {
- OdrBackend
- indexerConfig *IndexerConfig
- sdb, ldb ethdb.Database
- serverState state.Database
- disable bool
-}
-
-func (odr *testOdr) Database() ethdb.Database {
- return odr.ldb
-}
-
-var ErrOdrDisabled = errors.New("ODR disabled")
-
-func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
- if odr.disable {
- return ErrOdrDisabled
- }
- switch req := req.(type) {
- case *BlockRequest:
- number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash)
- if number != nil {
- req.Rlp = rawdb.ReadBodyRLP(odr.sdb, req.Hash, *number)
- }
- case *ReceiptsRequest:
- number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash)
- if number != nil {
- req.Receipts = rawdb.ReadRawReceipts(odr.sdb, req.Hash, *number)
- }
- case *TrieRequest:
- var (
- err error
- t state.Trie
- )
- if len(req.Id.AccountAddress) > 0 {
- t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToAddress(req.Id.AccountAddress), req.Id.Root)
- } else {
- t, err = odr.serverState.OpenTrie(req.Id.Root)
- }
- if err != nil {
- panic(err)
- }
- nodes := trienode.NewProofSet()
- t.Prove(req.Key, nodes)
- req.Proof = nodes
- case *CodeRequest:
- req.Data = rawdb.ReadCode(odr.sdb, req.Hash)
- }
- req.StoreResult(odr.ldb)
- return nil
-}
-
-func (odr *testOdr) IndexerConfig() *IndexerConfig {
- return odr.indexerConfig
-}
-
-type odrTestFn func(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error)
-
-func TestOdrGetBlockLes2(t *testing.T) { testChainOdr(t, 1, odrGetBlock) }
-
-func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- var block *types.Block
- if bc != nil {
- block = bc.GetBlockByHash(bhash)
- } else {
- block, _ = lc.GetBlockByHash(ctx, bhash)
- }
- if block == nil {
- return nil, nil
- }
- rlp, _ := rlp.EncodeToBytes(block)
- return rlp, nil
-}
-
-func TestOdrGetReceiptsLes2(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) }
-
-func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- var receipts types.Receipts
- if bc != nil {
- if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
- if header := rawdb.ReadHeader(db, bhash, *number); header != nil {
- receipts = rawdb.ReadReceipts(db, bhash, *number, header.Time, bc.Config())
- }
- }
- } else {
- number := rawdb.ReadHeaderNumber(db, bhash)
- if number != nil {
- receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, *number)
- }
- }
- if receipts == nil {
- return nil, nil
- }
- rlp, _ := rlp.EncodeToBytes(receipts)
- return rlp, nil
-}
-
-func TestOdrAccountsLes2(t *testing.T) { testChainOdr(t, 1, odrAccounts) }
-
-func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
- acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr}
-
- var st *state.StateDB
- if bc == nil {
- header := lc.GetHeaderByHash(bhash)
- st = NewState(ctx, header, lc.Odr())
- } else {
- header := bc.GetHeaderByHash(bhash)
- st, _ = state.New(header.Root, bc.StateCache(), nil)
- }
-
- var res []byte
- for _, addr := range acc {
- bal := st.GetBalance(addr)
- rlp, _ := rlp.EncodeToBytes(bal)
- res = append(res, rlp...)
- }
- return res, st.Error()
-}
-
-func TestOdrContractCallLes2(t *testing.T) { testChainOdr(t, 1, odrContractCall) }
-
-func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) {
- data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000")
- config := params.TestChainConfig
-
- var res []byte
- for i := 0; i < 3; i++ {
- data[35] = byte(i)
-
- var (
- st *state.StateDB
- header *types.Header
- chain core.ChainContext
- )
- if bc == nil {
- chain = lc
- header = lc.GetHeaderByHash(bhash)
- st = NewState(ctx, header, lc.Odr())
- } else {
- chain = bc
- header = bc.GetHeaderByHash(bhash)
- st, _ = state.New(header.Root, bc.StateCache(), nil)
- }
-
- // Perform read-only call.
- st.SetBalance(testBankAddress, math.MaxBig256)
- msg := &core.Message{
- From: testBankAddress,
- To: &testContractAddr,
- Value: new(big.Int),
- GasLimit: 1000000,
- GasPrice: big.NewInt(params.InitialBaseFee),
- GasFeeCap: big.NewInt(params.InitialBaseFee),
- GasTipCap: new(big.Int),
- Data: data,
- SkipAccountChecks: true,
- }
- txContext := core.NewEVMTxContext(msg)
- context := core.NewEVMBlockContext(header, chain, nil)
- vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
- gp := new(core.GasPool).AddGas(math.MaxUint64)
- result, _ := core.ApplyMessage(vmenv, msg, gp)
- res = append(res, result.Return()...)
- if st.Error() != nil {
- return res, st.Error()
- }
- }
- return res, nil
-}
-
-func testChainGen(i int, block *core.BlockGen) {
- signer := types.HomesteadSigner{}
- switch i {
- case 0:
- // In block 1, the test bank sends account #1 some ether.
- tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey)
- block.AddTx(tx)
- case 1:
- // In block 2, the test bank sends some more ether to account #1.
- // acc1Addr passes it on to account #2.
- // acc1Addr creates a test contract.
- tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey)
- nonce := block.TxNonce(acc1Addr)
- tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, acc1Key)
- nonce++
- tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, block.BaseFee(), testContractCode), signer, acc1Key)
- testContractAddr = crypto.CreateAddress(acc1Addr, nonce)
- block.AddTx(tx1)
- block.AddTx(tx2)
- block.AddTx(tx3)
- case 2:
- // Block 3 is empty but was mined by account #2.
- block.SetCoinbase(acc2Addr)
- block.SetExtra([]byte("yeehaw"))
- data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001")
- tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey)
- block.AddTx(tx)
- case 3:
- // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
- b2 := block.PrevBlock(1).Header()
- b2.Extra = []byte("foo")
- block.AddUncle(b2)
- b3 := block.PrevBlock(2).Header()
- b3.Extra = []byte("foo")
- block.AddUncle(b3)
- data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
- tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey)
- block.AddTx(tx)
- }
-}
-
-func testChainOdr(t *testing.T, protocol int, fn odrTestFn) {
- var (
- sdb = rawdb.NewMemoryDatabase()
- ldb = rawdb.NewMemoryDatabase()
- gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- // Assemble the test environment
- blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
- _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen)
- if _, err := blockchain.InsertChain(gchain); err != nil {
- t.Fatal(err)
- }
-
- gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults))
- odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
- lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker())
- if err != nil {
- t.Fatal(err)
- }
- headers := make([]*types.Header, len(gchain))
- for i, block := range gchain {
- headers[i] = block.Header()
- }
- if _, err := lightchain.InsertHeaderChain(headers); err != nil {
- t.Fatal(err)
- }
-
- test := func(expFail int) {
- for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ {
- bhash := rawdb.ReadCanonicalHash(sdb, i)
- b1, err := fn(NoOdr, sdb, blockchain, nil, bhash)
- if err != nil {
- t.Fatalf("error in full-node test for block %d: %v", i, err)
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
- defer cancel()
-
- exp := i < uint64(expFail)
- b2, err := fn(ctx, ldb, nil, lightchain, bhash)
- if err != nil && exp {
- t.Errorf("error in ODR test for block %d: %v", i, err)
- }
-
- eq := bytes.Equal(b1, b2)
- if exp && !eq {
- t.Errorf("ODR test output for block %d doesn't match full node", i)
- }
- }
- }
-
- // expect retrievals to fail (except genesis block) without a les peer
- t.Log("checking without ODR")
- odr.disable = true
- test(1)
-
- // expect all retrievals to pass with ODR enabled
- t.Log("checking with ODR")
- odr.disable = false
- test(len(gchain))
-
- // still expect all retrievals to pass, now data should be cached locally
- t.Log("checking without ODR, should be cached")
- odr.disable = true
- test(len(gchain))
-}
diff --git a/light/odr_util.go b/light/odr_util.go
deleted file mode 100644
index 9cac7df4f..000000000
--- a/light/odr_util.go
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2016 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 .
-
-package light
-
-import (
- "context"
- "errors"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// errNonCanonicalHash is returned if the requested chain data doesn't belong
-// to the canonical chain. ODR can only retrieve the canonical chain data covered
-// by the CHT or Bloom trie for verification.
-var errNonCanonicalHash = errors.New("hash is not currently canonical")
-
-// GetHeaderByNumber retrieves the canonical block header corresponding to the
-// given number. The returned header is proven by local CHT.
-func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) {
- // Try to find it in the local database first.
- db := odr.Database()
- hash := rawdb.ReadCanonicalHash(db, number)
-
- // If there is a canonical hash, there should have a header too.
- // But if it's pruned, re-fetch from network again.
- if (hash != common.Hash{}) {
- if header := rawdb.ReadHeader(db, hash, number); header != nil {
- return header, nil
- }
- }
- // Retrieve the header via ODR, ensure the requested header is covered
- // by local trusted CHT.
- chts, _, chtHead := odr.ChtIndexer().Sections()
- if number >= chts*odr.IndexerConfig().ChtSize {
- return nil, errNoTrustedCht
- }
- r := &ChtRequest{
- ChtRoot: GetChtRoot(db, chts-1, chtHead),
- ChtNum: chts - 1,
- BlockNum: number,
- Config: odr.IndexerConfig(),
- }
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- return r.Header, nil
-}
-
-// GetCanonicalHash retrieves the canonical block hash corresponding to the number.
-func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) {
- hash := rawdb.ReadCanonicalHash(odr.Database(), number)
- if hash != (common.Hash{}) {
- return hash, nil
- }
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return common.Hash{}, err
- }
- // number -> canonical mapping already be stored in db, get it.
- return header.Hash(), nil
-}
-
-// GetTd retrieves the total difficulty corresponding to the number and hash.
-func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*big.Int, error) {
- td := rawdb.ReadTd(odr.Database(), hash, number)
- if td != nil {
- return td, nil
- }
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, err
- }
- if header.Hash() != hash {
- return nil, errNonCanonicalHash
- }
- // -> td mapping already be stored in db, get it.
- return rawdb.ReadTd(odr.Database(), hash, number), nil
-}
-
-// GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
-func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) {
- if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil {
- return data, nil
- }
- // Retrieve the block header first and pass it for verification.
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, errNoHeader
- }
- if header.Hash() != hash {
- return nil, errNonCanonicalHash
- }
- r := &BlockRequest{Hash: hash, Number: number, Header: header}
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- return r.Rlp, nil
-}
-
-// GetBody retrieves the block body (transactions, uncles) corresponding to the
-// hash.
-func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) {
- data, err := GetBodyRLP(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- body := new(types.Body)
- if err := rlp.DecodeBytes(data, body); err != nil {
- return nil, err
- }
- return body, nil
-}
-
-// GetBlock retrieves an entire block corresponding to the hash, assembling it
-// back from the stored header and body.
-func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) {
- // Retrieve the block header and body contents
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, errNoHeader
- }
- body, err := GetBody(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- // Reassemble the block and return
- return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles), nil
-}
-
-// GetBlockReceipts retrieves the receipts generated by the transactions included
-// in a block given by its hash. Receipts will be filled in with context data.
-func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) {
- // Assume receipts are already stored locally and attempt to retrieve.
- receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number)
- if receipts == nil {
- header, err := GetHeaderByNumber(ctx, odr, number)
- if err != nil {
- return nil, errNoHeader
- }
- if header.Hash() != hash {
- return nil, errNonCanonicalHash
- }
- r := &ReceiptsRequest{Hash: hash, Number: number, Header: header}
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- receipts = r.Receipts
- }
- // If the receipts are incomplete, fill the derived fields
- if len(receipts) > 0 && receipts[0].TxHash == (common.Hash{}) {
- block, err := GetBlock(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- genesis := rawdb.ReadCanonicalHash(odr.Database(), 0)
- config := rawdb.ReadChainConfig(odr.Database(), genesis)
-
- var blobGasPrice *big.Int
- excessBlobGas := block.ExcessBlobGas()
- if excessBlobGas != nil {
- blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas)
- }
-
- if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, block.Transactions()); err != nil {
- return nil, err
- }
- rawdb.WriteReceipts(odr.Database(), hash, number, receipts)
- }
- return receipts, nil
-}
-
-// GetBlockLogs retrieves the logs generated by the transactions included in a
-// block given by its hash. Logs will be filled in with context data.
-func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) {
- receipts, err := GetBlockReceipts(ctx, odr, hash, number)
- if err != nil {
- return nil, err
- }
- logs := make([][]*types.Log, len(receipts))
- for i, receipt := range receipts {
- logs[i] = receipt.Logs
- }
- return logs, nil
-}
-
-// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to
-// the given bit index and section indexes.
-func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) {
- var (
- reqIndex []int
- reqSections []uint64
- db = odr.Database()
- result = make([][]byte, len(sections))
- )
- blooms, _, sectionHead := odr.BloomTrieIndexer().Sections()
- for i, section := range sections {
- sectionHead := rawdb.ReadCanonicalHash(db, (section+1)*odr.IndexerConfig().BloomSize-1)
- // If we don't have the canonical hash stored for this section head number,
- // we'll still look for an entry with a zero sectionHead (we store it with
- // zero section head too if we don't know it at the time of the retrieval)
- if bloomBits, _ := rawdb.ReadBloomBits(db, bit, section, sectionHead); len(bloomBits) != 0 {
- result[i] = bloomBits
- continue
- }
- // TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index
- if section >= blooms {
- return nil, errNoTrustedBloomTrie
- }
- reqSections = append(reqSections, section)
- reqIndex = append(reqIndex, i)
- }
- // Find all bloombits in database, nothing to query via odr, return.
- if reqSections == nil {
- return result, nil
- }
- // Send odr request to retrieve missing bloombits.
- r := &BloomRequest{
- BloomTrieRoot: GetBloomTrieRoot(db, blooms-1, sectionHead),
- BloomTrieNum: blooms - 1,
- BitIdx: bit,
- SectionIndexList: reqSections,
- Config: odr.IndexerConfig(),
- }
- if err := odr.Retrieve(ctx, r); err != nil {
- return nil, err
- }
- for i, idx := range reqIndex {
- result[idx] = r.BloomBits[i]
- }
- return result, nil
-}
-
-// GetTransaction retrieves a canonical transaction by hash and also returns
-// its position in the chain. There is no guarantee in the LES protocol that
-// the mined transaction will be retrieved back for sure because of different
-// reasons(the transaction is unindexed, the malicious server doesn't reply it
-// deliberately, etc). Therefore, unretrieved transactions will receive a certain
-// number of retries, thus giving a weak guarantee.
-func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
- r := &TxStatusRequest{Hashes: []common.Hash{txHash}}
- if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != txpool.TxStatusIncluded {
- return nil, common.Hash{}, 0, 0, err
- }
- pos := r.Status[0].Lookup
- // first ensure that we have the header, otherwise block body retrieval will fail
- // also verify if this is a canonical block by getting the header by number and checking its hash
- if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash {
- return nil, common.Hash{}, 0, 0, err
- }
- body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex)
- if err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash {
- return nil, common.Hash{}, 0, 0, err
- }
- return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil
-}
diff --git a/light/postprocess.go b/light/postprocess.go
deleted file mode 100644
index a317e30b9..000000000
--- a/light/postprocess.go
+++ /dev/null
@@ -1,538 +0,0 @@
-// Copyright 2017 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 .
-
-package light
-
-import (
- "bytes"
- "context"
- "encoding/binary"
- "errors"
- "fmt"
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/bitutil"
- "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/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// IndexerConfig includes a set of configs for chain indexers.
-type IndexerConfig struct {
- // The block frequency for creating CHTs.
- ChtSize uint64
-
- // The number of confirmations needed to generate/accept a canonical hash help trie.
- ChtConfirms uint64
-
- // The block frequency for creating new bloom bits.
- BloomSize uint64
-
- // The number of confirmation needed before a bloom section is considered probably final and its rotated bits
- // are calculated.
- BloomConfirms uint64
-
- // The block frequency for creating BloomTrie.
- BloomTrieSize uint64
-
- // The number of confirmations needed to generate/accept a bloom trie.
- BloomTrieConfirms uint64
-}
-
-var (
- // DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side.
- DefaultServerIndexerConfig = &IndexerConfig{
- ChtSize: params.CHTFrequency,
- ChtConfirms: params.HelperTrieProcessConfirmations,
- BloomSize: params.BloomBitsBlocks,
- BloomConfirms: params.BloomConfirms,
- BloomTrieSize: params.BloomTrieFrequency,
- BloomTrieConfirms: params.HelperTrieProcessConfirmations,
- }
- // DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side.
- DefaultClientIndexerConfig = &IndexerConfig{
- ChtSize: params.CHTFrequency,
- ChtConfirms: params.HelperTrieConfirmations,
- BloomSize: params.BloomBitsBlocksClient,
- BloomConfirms: params.HelperTrieConfirmations,
- BloomTrieSize: params.BloomTrieFrequency,
- BloomTrieConfirms: params.HelperTrieConfirmations,
- }
- // TestServerIndexerConfig wraps a set of configs as a test indexer config for server side.
- TestServerIndexerConfig = &IndexerConfig{
- ChtSize: 128,
- ChtConfirms: 1,
- BloomSize: 16,
- BloomConfirms: 1,
- BloomTrieSize: 128,
- BloomTrieConfirms: 1,
- }
- // TestClientIndexerConfig wraps a set of configs as a test indexer config for client side.
- TestClientIndexerConfig = &IndexerConfig{
- ChtSize: 128,
- ChtConfirms: 8,
- BloomSize: 128,
- BloomConfirms: 8,
- BloomTrieSize: 128,
- BloomTrieConfirms: 8,
- }
-)
-
-var (
- errNoTrustedCht = errors.New("no trusted canonical hash trie")
- errNoTrustedBloomTrie = errors.New("no trusted bloom trie")
- errNoHeader = errors.New("header not found")
-)
-
-// ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format
-type ChtNode struct {
- Hash common.Hash
- Td *big.Int
-}
-
-// GetChtRoot reads the CHT root associated to the given section from the database
-func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- data, _ := db.Get(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...))
- return common.BytesToHash(data)
-}
-
-// StoreChtRoot writes the CHT root associated to the given section into the database
-func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- db.Put(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
-}
-
-// ChtIndexerBackend implements core.ChainIndexerBackend.
-type ChtIndexerBackend struct {
- disablePruning bool
- diskdb, trieTable ethdb.Database
- odr OdrBackend
- triedb *trie.Database
- section, sectionSize uint64
- lastHash common.Hash
- trie *trie.Trie
- originRoot common.Hash
-}
-
-// NewChtIndexer creates a Cht chain indexer
-func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer {
- trieTable := rawdb.NewTable(db, string(rawdb.ChtTablePrefix))
- backend := &ChtIndexerBackend{
- diskdb: db,
- odr: odr,
- trieTable: trieTable,
- triedb: trie.NewDatabase(trieTable, trie.HashDefaults),
- sectionSize: size,
- disablePruning: disablePruning,
- }
- return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.ChtIndexTablePrefix)), backend, size, confirms, time.Millisecond*100, "cht")
-}
-
-// fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the
-// ODR backend in order to be able to add new entries and calculate subsequent root hashes
-func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
- batch := c.trieTable.NewBatch()
- r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()}
- for {
- err := c.odr.Retrieve(ctx, r)
- switch err {
- case nil:
- r.Proof.Store(batch)
- return batch.Write()
- case ErrNoPeers:
- // if there are no peers to serve, retry later
- select {
- case <-ctx.Done():
- return ctx.Err()
- case <-time.After(time.Second * 10):
- // stay in the loop and try again
- }
- default:
- return err
- }
- }
-}
-
-// Reset implements core.ChainIndexerBackend
-func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
- root := types.EmptyRootHash
- if section > 0 {
- root = GetChtRoot(c.diskdb, section-1, lastSectionHead)
- }
- var err error
- c.trie, err = trie.New(trie.TrieID(root), c.triedb)
-
- if err != nil && c.odr != nil {
- err = c.fetchMissingNodes(ctx, section, root)
- if err == nil {
- c.trie, err = trie.New(trie.TrieID(root), c.triedb)
- }
- }
- c.section = section
- c.originRoot = root
- return err
-}
-
-// Process implements core.ChainIndexerBackend
-func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error {
- hash, num := header.Hash(), header.Number.Uint64()
- c.lastHash = hash
-
- td := rawdb.ReadTd(c.diskdb, hash, num)
- if td == nil {
- panic(nil)
- }
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], num)
- data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
- return c.trie.Update(encNumber[:], data)
-}
-
-// Commit implements core.ChainIndexerBackend
-func (c *ChtIndexerBackend) Commit() error {
- root, nodes, err := c.trie.Commit(false)
- if err != nil {
- return err
- }
- // Commit trie changes into trie database in case it's not nil.
- if nodes != nil {
- if err := c.triedb.Update(root, c.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- return err
- }
- if err := c.triedb.Commit(root, false); err != nil {
- return err
- }
- }
- // Re-create trie with newly generated root and updated database.
- c.trie, err = trie.New(trie.TrieID(root), c.triedb)
- if err != nil {
- return err
- }
- // Pruning historical trie nodes if necessary.
- if !c.disablePruning {
- it := c.trieTable.NewIterator(nil, nil)
- defer it.Release()
-
- var (
- deleted int
- batch = c.trieTable.NewBatch()
- t = time.Now()
- )
- hashes := make(map[common.Hash]struct{})
- if nodes != nil {
- for _, hash := range nodes.Hashes() {
- hashes[hash] = struct{}{}
- }
- }
- for it.Next() {
- trimmed := bytes.TrimPrefix(it.Key(), rawdb.ChtTablePrefix)
- if len(trimmed) == common.HashLength {
- if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
- batch.Delete(trimmed)
- deleted += 1
- }
- }
- }
- if err := batch.Write(); err != nil {
- return err
- }
- log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
- }
- log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
- StoreChtRoot(c.diskdb, c.section, c.lastHash, root)
- return nil
-}
-
-// Prune implements core.ChainIndexerBackend which deletes all chain data
-// (except hash<->number mappings) older than the specified threshold.
-func (c *ChtIndexerBackend) Prune(threshold uint64) error {
- // Short circuit if the light pruning is disabled.
- if c.disablePruning {
- return nil
- }
- t := time.Now()
- // Always keep genesis header in database.
- start, end := uint64(1), (threshold+1)*c.sectionSize
-
- var batch = c.diskdb.NewBatch()
- for {
- numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240)
- if len(numbers) == 0 {
- break
- }
- for i := 0; i < len(numbers); i++ {
- // Keep hash<->number mapping in database otherwise the hash based
- // API(e.g. GetReceipt, GetLogs) will be broken.
- //
- // Storage size wise, the size of a mapping is ~41bytes. For one
- // section is about 1.3MB which is acceptable.
- //
- // In order to totally get rid of this index, we need an additional
- // flag to specify how many historical data light client can serve.
- rawdb.DeleteCanonicalHash(batch, numbers[i])
- rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i])
- }
- if batch.ValueSize() > ethdb.IdealBatchSize {
- if err := batch.Write(); err != nil {
- return err
- }
- batch.Reset()
- }
- start = numbers[len(numbers)-1] + 1
- }
- if err := batch.Write(); err != nil {
- return err
- }
- log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t)))
- return nil
-}
-
-// GetBloomTrieRoot reads the BloomTrie root associated to the given section from the database
-func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- data, _ := db.Get(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...))
- return common.BytesToHash(data)
-}
-
-// StoreBloomTrieRoot writes the BloomTrie root associated to the given section into the database
-func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
- var encNumber [8]byte
- binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
- db.Put(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes())
-}
-
-// BloomTrieIndexerBackend implements core.ChainIndexerBackend
-type BloomTrieIndexerBackend struct {
- disablePruning bool
- diskdb, trieTable ethdb.Database
- triedb *trie.Database
- odr OdrBackend
- section uint64
- parentSize uint64
- size uint64
- bloomTrieRatio uint64
- trie *trie.Trie
- originRoot common.Hash
- sectionHeads []common.Hash
-}
-
-// NewBloomTrieIndexer creates a BloomTrie chain indexer
-func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer {
- trieTable := rawdb.NewTable(db, string(rawdb.BloomTrieTablePrefix))
- backend := &BloomTrieIndexerBackend{
- diskdb: db,
- odr: odr,
- trieTable: trieTable,
- triedb: trie.NewDatabase(trieTable, trie.HashDefaults),
- parentSize: parentSize,
- size: size,
- disablePruning: disablePruning,
- }
- backend.bloomTrieRatio = size / parentSize
- backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
- return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.BloomTrieIndexPrefix)), backend, size, 0, time.Millisecond*100, "bloomtrie")
-}
-
-// fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the
-// ODR backend in order to be able to add new entries and calculate subsequent root hashes
-func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
- indexCh := make(chan uint, types.BloomBitLength)
- type res struct {
- nodes *trienode.ProofSet
- err error
- }
- resCh := make(chan res, types.BloomBitLength)
- for i := 0; i < 20; i++ {
- go func() {
- for bitIndex := range indexCh {
- r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()}
- for {
- if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers {
- // if there are no peers to serve, retry later
- select {
- case <-ctx.Done():
- resCh <- res{nil, ctx.Err()}
- return
- case <-time.After(time.Second * 10):
- // stay in the loop and try again
- }
- } else {
- resCh <- res{r.Proofs, err}
- break
- }
- }
- }
- }()
- }
- for i := uint(0); i < types.BloomBitLength; i++ {
- indexCh <- i
- }
- close(indexCh)
- batch := b.trieTable.NewBatch()
- for i := uint(0); i < types.BloomBitLength; i++ {
- res := <-resCh
- if res.err != nil {
- return res.err
- }
- res.nodes.Store(batch)
- }
- return batch.Write()
-}
-
-// Reset implements core.ChainIndexerBackend
-func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
- root := types.EmptyRootHash
- if section > 0 {
- root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead)
- }
- var err error
- b.trie, err = trie.New(trie.TrieID(root), b.triedb)
- if err != nil && b.odr != nil {
- err = b.fetchMissingNodes(ctx, section, root)
- if err == nil {
- b.trie, err = trie.New(trie.TrieID(root), b.triedb)
- }
- }
- b.section = section
- b.originRoot = root
- return err
-}
-
-// Process implements core.ChainIndexerBackend
-func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error {
- num := header.Number.Uint64() - b.section*b.size
- if (num+1)%b.parentSize == 0 {
- b.sectionHeads[num/b.parentSize] = header.Hash()
- }
- return nil
-}
-
-// Commit implements core.ChainIndexerBackend
-func (b *BloomTrieIndexerBackend) Commit() error {
- var compSize, decompSize uint64
-
- for i := uint(0); i < types.BloomBitLength; i++ {
- var encKey [10]byte
- binary.BigEndian.PutUint16(encKey[0:2], uint16(i))
- binary.BigEndian.PutUint64(encKey[2:10], b.section)
- var decomp []byte
- for j := uint64(0); j < b.bloomTrieRatio; j++ {
- data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j])
- if err != nil {
- return err
- }
- decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8))
- if err2 != nil {
- return err2
- }
- decomp = append(decomp, decompData...)
- }
- comp := bitutil.CompressBytes(decomp)
-
- decompSize += uint64(len(decomp))
- compSize += uint64(len(comp))
-
- var terr error
- if len(comp) > 0 {
- terr = b.trie.Update(encKey[:], comp)
- } else {
- terr = b.trie.Delete(encKey[:])
- }
- if terr != nil {
- return terr
- }
- }
- root, nodes, err := b.trie.Commit(false)
- if err != nil {
- return err
- }
- // Commit trie changes into trie database in case it's not nil.
- if nodes != nil {
- if err := b.triedb.Update(root, b.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- return err
- }
- if err := b.triedb.Commit(root, false); err != nil {
- return err
- }
- }
- // Re-create trie with newly generated root and updated database.
- b.trie, err = trie.New(trie.TrieID(root), b.triedb)
- if err != nil {
- return err
- }
- // Pruning historical trie nodes if necessary.
- if !b.disablePruning {
- it := b.trieTable.NewIterator(nil, nil)
- defer it.Release()
-
- var (
- deleted int
- batch = b.trieTable.NewBatch()
- t = time.Now()
- )
- hashes := make(map[common.Hash]struct{})
- if nodes != nil {
- for _, hash := range nodes.Hashes() {
- hashes[hash] = struct{}{}
- }
- }
- for it.Next() {
- trimmed := bytes.TrimPrefix(it.Key(), rawdb.BloomTrieTablePrefix)
- if len(trimmed) == common.HashLength {
- if _, ok := hashes[common.BytesToHash(trimmed)]; !ok {
- batch.Delete(trimmed)
- deleted += 1
- }
- }
- }
- if err := batch.Write(); err != nil {
- return err
- }
- log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t)))
- }
- sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
- StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root)
- log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize))
-
- return nil
-}
-
-// Prune implements core.ChainIndexerBackend which deletes all
-// bloombits which older than the specified threshold.
-func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error {
- // Short circuit if the light pruning is disabled.
- if b.disablePruning {
- return nil
- }
- start := time.Now()
- for i := uint(0); i < types.BloomBitLength; i++ {
- rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio)
- }
- log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start)))
- return nil
-}
diff --git a/light/trie.go b/light/trie.go
deleted file mode 100644
index 1847f1e71..000000000
--- a/light/trie.go
+++ /dev/null
@@ -1,319 +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 .
-
-package light
-
-import (
- "context"
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "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/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-var (
- sha3Nil = crypto.Keccak256Hash(nil)
-)
-
-func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB {
- state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil)
- return state
-}
-
-func NewStateDatabase(ctx context.Context, head *types.Header, odr OdrBackend) state.Database {
- return &odrDatabase{ctx, StateTrieID(head), odr}
-}
-
-type odrDatabase struct {
- ctx context.Context
- id *TrieID
- backend OdrBackend
-}
-
-func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) {
- return &odrTrie{db: db, id: db.id}, nil
-}
-
-func (db *odrDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (state.Trie, error) {
- return &odrTrie{db: db, id: StorageTrieID(db.id, address, root)}, nil
-}
-
-func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie {
- switch t := t.(type) {
- case *odrTrie:
- cpy := &odrTrie{db: t.db, id: t.id}
- if t.trie != nil {
- cpy.trie = t.trie.Copy()
- }
- return cpy
- default:
- panic(fmt.Errorf("unknown trie type %T", t))
- }
-}
-
-func (db *odrDatabase) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) {
- if codeHash == sha3Nil {
- return nil, nil
- }
- code := rawdb.ReadCode(db.backend.Database(), codeHash)
- if len(code) != 0 {
- return code, nil
- }
- id := *db.id
- id.AccountAddress = addr[:]
- req := &CodeRequest{Id: &id, Hash: codeHash}
- err := db.backend.Retrieve(db.ctx, req)
- return req.Data, err
-}
-
-func (db *odrDatabase) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) {
- code, err := db.ContractCode(addr, codeHash)
- return len(code), err
-}
-
-func (db *odrDatabase) TrieDB() *trie.Database {
- return nil
-}
-
-func (db *odrDatabase) DiskDB() ethdb.KeyValueStore {
- panic("not implemented")
-}
-
-type odrTrie struct {
- db *odrDatabase
- id *TrieID
- trie *trie.Trie
-}
-
-func (t *odrTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
- key = crypto.Keccak256(key)
- var enc []byte
- err := t.do(key, func() (err error) {
- enc, err = t.trie.Get(key)
- return err
- })
- if err != nil || len(enc) == 0 {
- return nil, err
- }
- _, content, _, err := rlp.Split(enc)
- return content, err
-}
-
-func (t *odrTrie) GetAccount(address common.Address) (*types.StateAccount, error) {
- var (
- enc []byte
- key = crypto.Keccak256(address.Bytes())
- )
- err := t.do(key, func() (err error) {
- enc, err = t.trie.Get(key)
- return err
- })
- if err != nil || len(enc) == 0 {
- return nil, err
- }
- acct := new(types.StateAccount)
- if err := rlp.DecodeBytes(enc, acct); err != nil {
- return nil, err
- }
- return acct, nil
-}
-
-func (t *odrTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error {
- key := crypto.Keccak256(address.Bytes())
- value, err := rlp.EncodeToBytes(acc)
- if err != nil {
- return fmt.Errorf("decoding error in account update: %w", err)
- }
- return t.do(key, func() error {
- return t.trie.Update(key, value)
- })
-}
-
-func (t *odrTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
- return nil
-}
-
-func (t *odrTrie) UpdateStorage(_ common.Address, key, value []byte) error {
- key = crypto.Keccak256(key)
- v, _ := rlp.EncodeToBytes(value)
- return t.do(key, func() error {
- return t.trie.Update(key, v)
- })
-}
-
-func (t *odrTrie) DeleteStorage(_ common.Address, key []byte) error {
- key = crypto.Keccak256(key)
- return t.do(key, func() error {
- return t.trie.Delete(key)
- })
-}
-
-// DeleteAccount abstracts an account deletion from the trie.
-func (t *odrTrie) DeleteAccount(address common.Address) error {
- key := crypto.Keccak256(address.Bytes())
- return t.do(key, func() error {
- return t.trie.Delete(key)
- })
-}
-
-func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
- if t.trie == nil {
- return t.id.Root, nil, nil
- }
- return t.trie.Commit(collectLeaf)
-}
-
-func (t *odrTrie) Hash() common.Hash {
- if t.trie == nil {
- return t.id.Root
- }
- return t.trie.Hash()
-}
-
-func (t *odrTrie) NodeIterator(startkey []byte) (trie.NodeIterator, error) {
- return newNodeIterator(t, startkey), nil
-}
-
-func (t *odrTrie) GetKey(sha []byte) []byte {
- return nil
-}
-
-func (t *odrTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
- return errors.New("not implemented, needs client/server interface split")
-}
-
-// do tries and retries to execute a function until it returns with no error or
-// an error type other than MissingNodeError
-func (t *odrTrie) do(key []byte, fn func() error) error {
- for {
- var err error
- if t.trie == nil {
- var id *trie.ID
- if len(t.id.AccountAddress) > 0 {
- id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root)
- } else {
- id = trie.StateTrieID(t.id.StateRoot)
- }
- triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults)
- t.trie, err = trie.New(id, triedb)
- }
- if err == nil {
- err = fn()
- }
- if _, ok := err.(*trie.MissingNodeError); !ok {
- return err
- }
- r := &TrieRequest{Id: t.id, Key: key}
- if err := t.db.backend.Retrieve(t.db.ctx, r); err != nil {
- return err
- }
- }
-}
-
-type nodeIterator struct {
- trie.NodeIterator
- t *odrTrie
- err error
-}
-
-func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator {
- it := &nodeIterator{t: t}
- // Open the actual non-ODR trie if that hasn't happened yet.
- if t.trie == nil {
- it.do(func() error {
- var id *trie.ID
- if len(t.id.AccountAddress) > 0 {
- id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root)
- } else {
- id = trie.StateTrieID(t.id.StateRoot)
- }
- triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults)
- t, err := trie.New(id, triedb)
- if err == nil {
- it.t.trie = t
- }
- return err
- })
- }
- it.do(func() error {
- var err error
- it.NodeIterator, err = it.t.trie.NodeIterator(startkey)
- if err != nil {
- return err
- }
- return it.NodeIterator.Error()
- })
- return it
-}
-
-func (it *nodeIterator) Next(descend bool) bool {
- var ok bool
- it.do(func() error {
- ok = it.NodeIterator.Next(descend)
- return it.NodeIterator.Error()
- })
- return ok
-}
-
-// do runs fn and attempts to fill in missing nodes by retrieving.
-func (it *nodeIterator) do(fn func() error) {
- var lasthash common.Hash
- for {
- it.err = fn()
- missing, ok := it.err.(*trie.MissingNodeError)
- if !ok {
- return
- }
- if missing.NodeHash == lasthash {
- it.err = fmt.Errorf("retrieve loop for trie node %x", missing.NodeHash)
- return
- }
- lasthash = missing.NodeHash
- r := &TrieRequest{Id: it.t.id, Key: nibblesToKey(missing.Path)}
- if it.err = it.t.db.backend.Retrieve(it.t.db.ctx, r); it.err != nil {
- return
- }
- }
-}
-
-func (it *nodeIterator) Error() error {
- if it.err != nil {
- return it.err
- }
- return it.NodeIterator.Error()
-}
-
-func nibblesToKey(nib []byte) []byte {
- if len(nib) > 0 && nib[len(nib)-1] == 0x10 {
- nib = nib[:len(nib)-1] // drop terminator
- }
- if len(nib)&1 == 1 {
- nib = append(nib, 0) // make even
- }
- key := make([]byte, len(nib)/2)
- for bi, ni := 0, 0; ni < len(nib); bi, ni = bi+1, ni+2 {
- key[bi] = nib[ni]<<4 | nib[ni+1]
- }
- return key
-}
diff --git a/light/trie_test.go b/light/trie_test.go
deleted file mode 100644
index fe724e9ee..000000000
--- a/light/trie_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2017 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 .
-
-package light
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "math/big"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "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/state"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-func TestNodeIterator(t *testing.T) {
- var (
- fulldb = rawdb.NewMemoryDatabase()
- lightdb = rawdb.NewMemoryDatabase()
- gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- blockchain, _ := core.NewBlockChain(fulldb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
- _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen)
- if _, err := blockchain.InsertChain(gchain); err != nil {
- panic(err)
- }
-
- gspec.MustCommit(lightdb, trie.NewDatabase(lightdb, trie.HashDefaults))
- ctx := context.Background()
- odr := &testOdr{sdb: fulldb, ldb: lightdb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
- head := blockchain.CurrentHeader()
- lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root)
- fullTrie, _ := blockchain.StateCache().OpenTrie(head.Root)
- if err := diffTries(fullTrie, lightTrie); err != nil {
- t.Fatal(err)
- }
-}
-
-func diffTries(t1, t2 state.Trie) error {
- trieIt1, err := t1.NodeIterator(nil)
- if err != nil {
- return err
- }
- trieIt2, err := t2.NodeIterator(nil)
- if err != nil {
- return err
- }
- i1 := trie.NewIterator(trieIt1)
- i2 := trie.NewIterator(trieIt2)
- for i1.Next() && i2.Next() {
- if !bytes.Equal(i1.Key, i2.Key) {
- spew.Dump(i2)
- return fmt.Errorf("tries have different keys %x, %x", i1.Key, i2.Key)
- }
- if !bytes.Equal(i1.Value, i2.Value) {
- return fmt.Errorf("tries differ at key %x", i1.Key)
- }
- }
- switch {
- case i1.Err != nil:
- return fmt.Errorf("full trie iterator error: %v", i1.Err)
- case i2.Err != nil:
- return fmt.Errorf("light trie iterator error: %v", i2.Err)
- case i1.Next():
- return errors.New("full trie iterator has more k/v pairs")
- case i2.Next():
- return errors.New("light trie iterator has more k/v pairs")
- }
- return nil
-}
diff --git a/light/txpool.go b/light/txpool.go
deleted file mode 100644
index b792d70b1..000000000
--- a/light/txpool.go
+++ /dev/null
@@ -1,556 +0,0 @@
-// Copyright 2016 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 .
-
-package light
-
-import (
- "context"
- "fmt"
- "math/big"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
-)
-
-const (
- // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
- chainHeadChanSize = 10
-)
-
-// txPermanent is the number of mined blocks after a mined transaction is
-// considered permanent and no rollback is expected
-var txPermanent = uint64(500)
-
-// TxPool implements the transaction pool for light clients, which keeps track
-// of the status of locally created transactions, detecting if they are included
-// in a block (mined) or rolled back. There are no queued transactions since we
-// always receive all locally signed transactions in the same order as they are
-// created.
-type TxPool struct {
- config *params.ChainConfig
- signer types.Signer
- quit chan bool
- txFeed event.Feed
- scope event.SubscriptionScope
- chainHeadCh chan core.ChainHeadEvent
- chainHeadSub event.Subscription
- mu sync.RWMutex
- chain *LightChain
- odr OdrBackend
- chainDb ethdb.Database
- relay TxRelayBackend
- head common.Hash
- nonce map[common.Address]uint64 // "pending" nonce
- pending map[common.Hash]*types.Transaction // pending transactions by tx hash
- mined map[common.Hash][]*types.Transaction // mined transactions by block hash
- clearIdx uint64 // earliest block nr that can contain mined tx info
-
- istanbul bool // Fork indicator whether we are in the istanbul stage.
- eip2718 bool // Fork indicator whether we are in the eip2718 stage.
- shanghai bool // Fork indicator whether we are in the shanghai stage.
-}
-
-// TxRelayBackend provides an interface to the mechanism that forwards transactions to the
-// ETH network. The implementations of the functions should be non-blocking.
-//
-// Send instructs backend to forward new transactions NewHead notifies backend about a new
-// head after processed by the tx pool, including mined and rolled back transactions since
-// the last event.
-//
-// Discard notifies backend about transactions that should be discarded either because
-// they have been replaced by a re-send or because they have been mined long ago and no
-// rollback is expected.
-type TxRelayBackend interface {
- Send(txs types.Transactions)
- NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash)
- Discard(hashes []common.Hash)
-}
-
-// NewTxPool creates a new light transaction pool
-func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool {
- pool := &TxPool{
- config: config,
- signer: types.LatestSigner(config),
- nonce: make(map[common.Address]uint64),
- pending: make(map[common.Hash]*types.Transaction),
- mined: make(map[common.Hash][]*types.Transaction),
- quit: make(chan bool),
- chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
- chain: chain,
- relay: relay,
- odr: chain.Odr(),
- chainDb: chain.Odr().Database(),
- head: chain.CurrentHeader().Hash(),
- clearIdx: chain.CurrentHeader().Number.Uint64(),
- }
- // Subscribe events from blockchain
- pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
- go pool.eventLoop()
-
- return pool
-}
-
-// currentState returns the light state of the current head header
-func (pool *TxPool) currentState(ctx context.Context) *state.StateDB {
- return NewState(ctx, pool.chain.CurrentHeader(), pool.odr)
-}
-
-// GetNonce returns the "pending" nonce of a given address. It always queries
-// the nonce belonging to the latest header too in order to detect if another
-// client using the same key sent a transaction.
-func (pool *TxPool) GetNonce(ctx context.Context, addr common.Address) (uint64, error) {
- state := pool.currentState(ctx)
- nonce := state.GetNonce(addr)
- if state.Error() != nil {
- return 0, state.Error()
- }
- sn, ok := pool.nonce[addr]
- if ok && sn > nonce {
- nonce = sn
- }
- if !ok || sn < nonce {
- pool.nonce[addr] = nonce
- }
- return nonce, nil
-}
-
-// txStateChanges stores the recent changes between pending/mined states of
-// transactions. True means mined, false means rolled back, no entry means no change
-type txStateChanges map[common.Hash]bool
-
-// setState sets the status of a tx to either recently mined or recently rolled back
-func (txc txStateChanges) setState(txHash common.Hash, mined bool) {
- val, ent := txc[txHash]
- if ent && (val != mined) {
- delete(txc, txHash)
- } else {
- txc[txHash] = mined
- }
-}
-
-// getLists creates lists of mined and rolled back tx hashes
-func (txc txStateChanges) getLists() (mined []common.Hash, rollback []common.Hash) {
- for hash, val := range txc {
- if val {
- mined = append(mined, hash)
- } else {
- rollback = append(rollback, hash)
- }
- }
- return
-}
-
-// checkMinedTxs checks newly added blocks for the currently pending transactions
-// and marks them as mined if necessary. It also stores block position in the db
-// and adds them to the received txStateChanges map.
-func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number uint64, txc txStateChanges) error {
- // If no transactions are pending, we don't care about anything
- if len(pool.pending) == 0 {
- return nil
- }
- block, err := GetBlock(ctx, pool.odr, hash, number)
- if err != nil {
- return err
- }
- // Gather all the local transaction mined in this block
- list := pool.mined[hash]
- for _, tx := range block.Transactions() {
- if _, ok := pool.pending[tx.Hash()]; ok {
- list = append(list, tx)
- }
- }
- // If some transactions have been mined, write the needed data to disk and update
- if list != nil {
- // Retrieve all the receipts belonging to this block and write the lookup table
- if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results
- return err
- }
- rawdb.WriteTxLookupEntriesByBlock(pool.chainDb, block)
-
- // Update the transaction pool's state
- for _, tx := range list {
- delete(pool.pending, tx.Hash())
- txc.setState(tx.Hash(), true)
- }
- pool.mined[hash] = list
- }
- return nil
-}
-
-// rollbackTxs marks the transactions contained in recently rolled back blocks
-// as rolled back. It also removes any positional lookup entries.
-func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) {
- batch := pool.chainDb.NewBatch()
- if list, ok := pool.mined[hash]; ok {
- for _, tx := range list {
- txHash := tx.Hash()
- rawdb.DeleteTxLookupEntry(batch, txHash)
- pool.pending[txHash] = tx
- txc.setState(txHash, false)
- }
- delete(pool.mined, hash)
- }
- batch.Write()
-}
-
-// reorgOnNewHead sets a new head header, processing (and rolling back if necessary)
-// the blocks since the last known head and returns a txStateChanges map containing
-// the recently mined and rolled back transaction hashes. If an error (context
-// timeout) occurs during checking new blocks, it leaves the locally known head
-// at the latest checked block and still returns a valid txStateChanges, making it
-// possible to continue checking the missing blocks at the next chain head event
-func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) {
- txc := make(txStateChanges)
- oldh := pool.chain.GetHeaderByHash(pool.head)
- newh := newHeader
- // find common ancestor, create list of rolled back and new block hashes
- var oldHashes, newHashes []common.Hash
- for oldh.Hash() != newh.Hash() {
- if oldh.Number.Uint64() >= newh.Number.Uint64() {
- oldHashes = append(oldHashes, oldh.Hash())
- oldh = pool.chain.GetHeader(oldh.ParentHash, oldh.Number.Uint64()-1)
- }
- if oldh.Number.Uint64() < newh.Number.Uint64() {
- newHashes = append(newHashes, newh.Hash())
- newh = pool.chain.GetHeader(newh.ParentHash, newh.Number.Uint64()-1)
- if newh == nil {
- // happens when CHT syncing, nothing to do
- newh = oldh
- }
- }
- }
- if oldh.Number.Uint64() < pool.clearIdx {
- pool.clearIdx = oldh.Number.Uint64()
- }
- // roll back old blocks
- for _, hash := range oldHashes {
- pool.rollbackTxs(hash, txc)
- }
- pool.head = oldh.Hash()
- // check mined txs of new blocks (array is in reversed order)
- for i := len(newHashes) - 1; i >= 0; i-- {
- hash := newHashes[i]
- if err := pool.checkMinedTxs(ctx, hash, newHeader.Number.Uint64()-uint64(i), txc); err != nil {
- return txc, err
- }
- pool.head = hash
- }
-
- // clear old mined tx entries of old blocks
- if idx := newHeader.Number.Uint64(); idx > pool.clearIdx+txPermanent {
- idx2 := idx - txPermanent
- if len(pool.mined) > 0 {
- for i := pool.clearIdx; i < idx2; i++ {
- hash := rawdb.ReadCanonicalHash(pool.chainDb, i)
- if list, ok := pool.mined[hash]; ok {
- hashes := make([]common.Hash, len(list))
- for i, tx := range list {
- hashes[i] = tx.Hash()
- }
- pool.relay.Discard(hashes)
- delete(pool.mined, hash)
- }
- }
- }
- pool.clearIdx = idx2
- }
-
- return txc, nil
-}
-
-// blockCheckTimeout is the time limit for checking new blocks for mined
-// transactions. Checking resumes at the next chain head event if timed out.
-const blockCheckTimeout = time.Second * 3
-
-// eventLoop processes chain head events and also notifies the tx relay backend
-// about the new head hash and tx state changes
-func (pool *TxPool) eventLoop() {
- for {
- select {
- case ev := <-pool.chainHeadCh:
- pool.setNewHead(ev.Block.Header())
- // hack in order to avoid hogging the lock; this part will
- // be replaced by a subsequent PR.
- time.Sleep(time.Millisecond)
-
- // System stopped
- case <-pool.chainHeadSub.Err():
- return
- }
- }
-}
-
-func (pool *TxPool) setNewHead(head *types.Header) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- ctx, cancel := context.WithTimeout(context.Background(), blockCheckTimeout)
- defer cancel()
-
- txc, _ := pool.reorgOnNewHead(ctx, head)
- m, r := txc.getLists()
- pool.relay.NewHead(pool.head, m, r)
-
- // Update fork indicator by next pending block number
- next := new(big.Int).Add(head.Number, big.NewInt(1))
- pool.istanbul = pool.config.IsIstanbul(next)
- pool.eip2718 = pool.config.IsBerlin(next)
- pool.shanghai = pool.config.IsShanghai(next, uint64(time.Now().Unix()))
-}
-
-// Stop stops the light transaction pool
-func (pool *TxPool) Stop() {
- // Unsubscribe all subscriptions registered from txpool
- pool.scope.Close()
- // Unsubscribe subscriptions registered from blockchain
- pool.chainHeadSub.Unsubscribe()
- close(pool.quit)
- log.Info("Transaction pool stopped")
-}
-
-// SubscribeNewTxsEvent registers a subscription of core.NewTxsEvent and
-// starts sending event to the given channel.
-func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
- return pool.scope.Track(pool.txFeed.Subscribe(ch))
-}
-
-// Stats returns the number of currently pending (locally created) transactions
-func (pool *TxPool) Stats() (pending int) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- pending = len(pool.pending)
- return
-}
-
-// validateTx checks whether a transaction is valid according to the consensus rules.
-func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error {
- // Validate sender
- var (
- from common.Address
- err error
- )
-
- // Validate the transaction sender and it's sig. Throw
- // if the from fields is invalid.
- if from, err = types.Sender(pool.signer, tx); err != nil {
- return txpool.ErrInvalidSender
- }
- // Last but not least check for nonce errors
- currentState := pool.currentState(ctx)
- if n := currentState.GetNonce(from); n > tx.Nonce() {
- return core.ErrNonceTooLow
- }
-
- // Check the transaction doesn't exceed the current
- // block limit gas.
- header := pool.chain.GetHeaderByHash(pool.head)
- if header.GasLimit < tx.Gas() {
- return txpool.ErrGasLimit
- }
-
- // Transactions can't be negative. This may never happen
- // using RLP decoded transactions but may occur if you create
- // a transaction using the RPC for example.
- if tx.Value().Sign() < 0 {
- return txpool.ErrNegativeValue
- }
-
- // Transactor should have enough funds to cover the costs
- // cost == V + GP * GL
- if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 {
- return core.ErrInsufficientFunds
- }
-
- // Should supply enough intrinsic gas
- gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
- if err != nil {
- return err
- }
- if tx.Gas() < gas {
- return core.ErrIntrinsicGas
- }
- return currentState.Error()
-}
-
-// add validates a new transaction and sets its state pending if processable.
-// It also updates the locally stored nonce if necessary.
-func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error {
- hash := tx.Hash()
-
- if pool.pending[hash] != nil {
- return fmt.Errorf("known transaction (%x)", hash[:4])
- }
- err := pool.validateTx(ctx, tx)
- if err != nil {
- return err
- }
-
- if _, ok := pool.pending[hash]; !ok {
- pool.pending[hash] = tx
-
- nonce := tx.Nonce() + 1
-
- addr, _ := types.Sender(pool.signer, tx)
- if nonce > pool.nonce[addr] {
- pool.nonce[addr] = nonce
- }
-
- // Notify the subscribers. This event is posted in a goroutine
- // because it's possible that somewhere during the post "Remove transaction"
- // gets called which will then wait for the global tx pool lock and deadlock.
- go pool.txFeed.Send(core.NewTxsEvent{Txs: types.Transactions{tx}})
- }
-
- // Print a log message if low enough level is set
- log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(pool.signer, tx); return from }}, "to", tx.To())
- return nil
-}
-
-// Add adds a transaction to the pool if valid and passes it to the tx relay
-// backend
-func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error {
- pool.mu.Lock()
- defer pool.mu.Unlock()
- data, err := tx.MarshalBinary()
- if err != nil {
- return err
- }
-
- if err := pool.add(ctx, tx); err != nil {
- return err
- }
- //fmt.Println("Send", tx.Hash())
- pool.relay.Send(types.Transactions{tx})
-
- pool.chainDb.Put(tx.Hash().Bytes(), data)
- return nil
-}
-
-// AddBatch adds all valid transactions to the pool and passes them to
-// the tx relay backend
-func (pool *TxPool) AddBatch(ctx context.Context, txs []*types.Transaction) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
- var sendTx types.Transactions
-
- for _, tx := range txs {
- if err := pool.add(ctx, tx); err == nil {
- sendTx = append(sendTx, tx)
- }
- }
- if len(sendTx) > 0 {
- pool.relay.Send(sendTx)
- }
-}
-
-// GetTransaction returns a transaction if it is contained in the pool
-// and nil otherwise.
-func (pool *TxPool) GetTransaction(hash common.Hash) *types.Transaction {
- // check the txs first
- if tx, ok := pool.pending[hash]; ok {
- return tx
- }
- return nil
-}
-
-// GetTransactions returns all currently processable transactions.
-// The returned slice may be modified by the caller.
-func (pool *TxPool) GetTransactions() (txs types.Transactions, err error) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- txs = make(types.Transactions, len(pool.pending))
- i := 0
- for _, tx := range pool.pending {
- txs[i] = tx
- i++
- }
- return txs, nil
-}
-
-// Content retrieves the data content of the transaction pool, returning all the
-// pending as well as queued transactions, grouped by account and nonce.
-func (pool *TxPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- // Retrieve all the pending transactions and sort by account and by nonce
- pending := make(map[common.Address][]*types.Transaction)
- for _, tx := range pool.pending {
- account, _ := types.Sender(pool.signer, tx)
- pending[account] = append(pending[account], tx)
- }
- // There are no queued transactions in a light pool, just return an empty map
- queued := make(map[common.Address][]*types.Transaction)
- return pending, queued
-}
-
-// ContentFrom retrieves the data content of the transaction pool, returning the
-// pending as well as queued transactions of this address, grouped by nonce.
-func (pool *TxPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
- pool.mu.RLock()
- defer pool.mu.RUnlock()
-
- // Retrieve the pending transactions and sort by nonce
- var pending []*types.Transaction
- for _, tx := range pool.pending {
- account, _ := types.Sender(pool.signer, tx)
- if account != addr {
- continue
- }
- pending = append(pending, tx)
- }
- // There are no queued transactions in a light pool, just return an empty map
- return pending, []*types.Transaction{}
-}
-
-// RemoveTransactions removes all given transactions from the pool.
-func (pool *TxPool) RemoveTransactions(txs types.Transactions) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
-
- var hashes []common.Hash
- batch := pool.chainDb.NewBatch()
- for _, tx := range txs {
- hash := tx.Hash()
- delete(pool.pending, hash)
- batch.Delete(hash.Bytes())
- hashes = append(hashes, hash)
- }
- batch.Write()
- pool.relay.Discard(hashes)
-}
-
-// RemoveTx removes the transaction with the given hash from the pool.
-func (pool *TxPool) RemoveTx(hash common.Hash) {
- pool.mu.Lock()
- defer pool.mu.Unlock()
- // delete from pending pool
- delete(pool.pending, hash)
- pool.chainDb.Delete(hash[:])
- pool.relay.Discard([]common.Hash{hash})
-}
diff --git a/light/txpool_test.go b/light/txpool_test.go
deleted file mode 100644
index 1eec7bc42..000000000
--- a/light/txpool_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2016 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 .
-
-package light
-
-import (
- "context"
- "math"
- "math/big"
- "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/core/vm"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-type testTxRelay struct {
- send, discard, mined chan int
-}
-
-func (r *testTxRelay) Send(txs types.Transactions) {
- r.send <- len(txs)
-}
-
-func (r *testTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) {
- m := len(mined)
- if m != 0 {
- r.mined <- m
- }
-}
-
-func (r *testTxRelay) Discard(hashes []common.Hash) {
- r.discard <- len(hashes)
-}
-
-const poolTestTxs = 1000
-const poolTestBlocks = 100
-
-// test tx 0..n-1
-var testTx [poolTestTxs]*types.Transaction
-
-// txs sent before block i
-func sentTx(i int) int {
- return int(math.Pow(float64(i)/float64(poolTestBlocks), 0.9) * poolTestTxs)
-}
-
-// txs included in block i or before that (minedTx(i) <= sentTx(i))
-func minedTx(i int) int {
- return int(math.Pow(float64(i)/float64(poolTestBlocks), 1.1) * poolTestTxs)
-}
-
-func txPoolTestChainGen(i int, block *core.BlockGen) {
- s := minedTx(i)
- e := minedTx(i + 1)
- for i := s; i < e; i++ {
- block.AddTx(testTx[i])
- }
-}
-
-func TestTxPool(t *testing.T) {
- for i := range testTx {
- testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey)
- }
-
- var (
- sdb = rawdb.NewMemoryDatabase()
- ldb = rawdb.NewMemoryDatabase()
- gspec = &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
- BaseFee: big.NewInt(params.InitialBaseFee),
- }
- )
- // Assemble the test environment
- blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
- _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), poolTestBlocks, txPoolTestChainGen)
- if _, err := blockchain.InsertChain(gchain); err != nil {
- panic(err)
- }
-
- gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults))
- odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
- relay := &testTxRelay{
- send: make(chan int, 1),
- discard: make(chan int, 1),
- mined: make(chan int, 1),
- }
- lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker())
- txPermanent = 50
- pool := NewTxPool(params.TestChainConfig, lightchain, relay)
- ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
- defer cancel()
-
- for ii, block := range gchain {
- i := ii + 1
- s := sentTx(i - 1)
- e := sentTx(i)
- for i := s; i < e; i++ {
- pool.Add(ctx, testTx[i])
- got := <-relay.send
- exp := 1
- if got != exp {
- t.Errorf("relay.Send expected len = %d, got %d", exp, got)
- }
- }
-
- if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}); err != nil {
- panic(err)
- }
-
- got := <-relay.mined
- exp := minedTx(i) - minedTx(i-1)
- if got != exp {
- t.Errorf("relay.NewHead expected len(mined) = %d, got %d", exp, got)
- }
-
- exp = 0
- if i > int(txPermanent)+1 {
- exp = minedTx(i-int(txPermanent)-1) - minedTx(i-int(txPermanent)-2)
- }
- if exp != 0 {
- got = <-relay.discard
- if got != exp {
- t.Errorf("relay.Discard expected len = %d, got %d", exp, got)
- }
- }
- }
-}
diff --git a/log/CONTRIBUTORS b/log/CONTRIBUTORS
deleted file mode 100644
index a0866713b..000000000
--- a/log/CONTRIBUTORS
+++ /dev/null
@@ -1,11 +0,0 @@
-Contributors to log15:
-
-- Aaron L
-- Alan Shreve
-- Chris Hines
-- Ciaran Downey
-- Dmitry Chestnykh
-- Evan Shaw
-- Péter Szilágyi
-- Trevor Gattis
-- Vincent Vanackere
diff --git a/log/LICENSE b/log/LICENSE
deleted file mode 100644
index 5f0d1fb6a..000000000
--- a/log/LICENSE
+++ /dev/null
@@ -1,13 +0,0 @@
-Copyright 2014 Alan Shreve
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/log/README.md b/log/README.md
deleted file mode 100644
index 47426806d..000000000
--- a/log/README.md
+++ /dev/null
@@ -1,77 +0,0 @@
-![obligatory xkcd](https://imgs.xkcd.com/comics/standards.png)
-
-# log15 [![godoc reference](https://godoc.org/github.com/inconshreveable/log15?status.png)](https://godoc.org/github.com/inconshreveable/log15) [![Build Status](https://travis-ci.org/inconshreveable/log15.svg?branch=master)](https://travis-ci.org/inconshreveable/log15)
-
-Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](https://golang.org/pkg/io/) and [`net/http`](https://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](https://golang.org/pkg/log/) package.
-
-## Features
-- A simple, easy-to-understand API
-- Promotes structured logging by encouraging use of key/value pairs
-- Child loggers which inherit and add their own private context
-- Lazy evaluation of expensive operations
-- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API.
-- Color terminal support
-- Built-in support for logging to files, streams, syslog, and the network
-- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more
-
-## Versioning
-The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API,
-you must vendor the library.
-
-## Importing
-
-```go
-import log "github.com/inconshreveable/log15"
-```
-
-## Examples
-
-```go
-// all loggers can have key/value context
-srvlog := log.New("module", "app/server")
-
-// all log messages can have key/value context
-srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate)
-
-// child loggers with inherited context
-connlog := srvlog.New("raddr", c.RemoteAddr())
-connlog.Info("connection open")
-
-// lazy evaluation
-connlog.Debug("ping remote", "latency", log.Lazy{pingRemote})
-
-// flexible configuration
-srvlog.SetHandler(log.MultiHandler(
- log.StreamHandler(os.Stderr, log.LogfmtFormat()),
- log.LvlFilterHandler(
- log.LvlError,
- log.Must.FileHandler("errors.json", log.JSONFormat()))))
-```
-
-Will result in output that looks like this:
-
-```
-WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800
-INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1
-```
-
-## Breaking API Changes
-The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version
-of log15.
-
-- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler
-- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack`
-- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors
-
-## FAQ
-
-### The varargs style is brittle and error prone! Can I have type safety please?
-Yes. Use `log.Ctx`:
-
-```go
-srvlog := log.New(log.Ctx{"module": "app/server"})
-srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate})
-```
-
-## License
-Apache
diff --git a/log/README_ETHEREUM.md b/log/README_ETHEREUM.md
deleted file mode 100644
index f6c42ccc0..000000000
--- a/log/README_ETHEREUM.md
+++ /dev/null
@@ -1,5 +0,0 @@
-This package is a fork of https://github.com/inconshreveable/log15, with some
-minor modifications required by the go-ethereum codebase:
-
- * Support for log level `trace`
- * Modified behavior to exit on `critical` failure
diff --git a/log/doc.go b/log/doc.go
deleted file mode 100644
index d2e15140e..000000000
--- a/log/doc.go
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
-Package log15 provides an opinionated, simple toolkit for best-practice logging that is
-both human and machine readable. It is modeled after the standard library's io and net/http
-packages.
-
-This package enforces you to only log key/value pairs. Keys must be strings. Values may be
-any type that you like. The default output format is logfmt, but you may also choose to use
-JSON instead if that suits you. Here's how you log:
-
- log.Info("page accessed", "path", r.URL.Path, "user_id", user.id)
-
-This will output a line that looks like:
-
- lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9
-
-# Getting Started
-
-To get started, you'll want to import the library:
-
- import log "github.com/inconshreveable/log15"
-
-Now you're ready to start logging:
-
- func main() {
- log.Info("Program starting", "args", os.Args())
- }
-
-# Convention
-
-Because recording a human-meaningful message is common and good practice, the first argument to every
-logging method is the value to the *implicit* key 'msg'.
-
-Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so
-will the current timestamp with key 't'.
-
-You may supply any additional context as a set of key/value pairs to the logging function. log15 allows
-you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for
-logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate
-in the variadic argument list:
-
- log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val)
-
-If you really do favor your type-safety, you may choose to pass a log.Ctx instead:
-
- log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val})
-
-# Context loggers
-
-Frequently, you want to add context to a logger so that you can track actions associated with it. An http
-request is a good example. You can easily create new loggers that have context that is automatically included
-with each log line:
-
- requestlogger := log.New("path", r.URL.Path)
-
- // later
- requestlogger.Debug("db txn commit", "duration", txnTimer.Finish())
-
-This will output a log line that includes the path context that is attached to the logger:
-
- lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12
-
-# Handlers
-
-The Handler interface defines where log lines are printed to and how they are formatted. Handler is a
-single interface that is inspired by net/http's handler interface:
-
- type Handler interface {
- Log(r *Record) error
- }
-
-Handlers can filter records, format them, or dispatch to multiple other Handlers.
-This package implements a number of Handlers for common logging patterns that are
-easily composed to create flexible, custom logging structures.
-
-Here's an example handler that prints logfmt output to Stdout:
-
- handler := log.StreamHandler(os.Stdout, log.LogfmtFormat())
-
-Here's an example handler that defers to two other handlers. One handler only prints records
-from the rpc package in logfmt to standard out. The other prints records at Error level
-or above in JSON formatted output to the file /var/log/service.json
-
- handler := log.MultiHandler(
- log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JSONFormat())),
- log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler())
- )
-
-# Logging File Names and Line Numbers
-
-This package implements three Handlers that add debugging information to the
-context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's
-an example that adds the source file and line number of each logging call to
-the context.
-
- h := log.CallerFileHandler(log.StdoutHandler)
- log.Root().SetHandler(h)
- ...
- log.Error("open file", "err", err)
-
-This will output a line that looks like:
-
- lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42
-
-Here's an example that logs the call stack rather than just the call site.
-
- h := log.CallerStackHandler("%+v", log.StdoutHandler)
- log.Root().SetHandler(h)
- ...
- log.Error("open file", "err", err)
-
-This will output a line that looks like:
-
- lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]"
-
-The "%+v" format instructs the handler to include the path of the source file
-relative to the compile time GOPATH. The github.com/go-stack/stack package
-documents the full list of formatting verbs and modifiers available.
-
-# Custom Handlers
-
-The Handler interface is so simple that it's also trivial to write your own. Let's create an
-example handler which tries to write to one handler, but if that fails it falls back to
-writing to another handler and includes the error that it encountered when trying to write
-to the primary. This might be useful when trying to log over a network socket, but if that
-fails you want to log those records to a file on disk.
-
- type BackupHandler struct {
- Primary Handler
- Secondary Handler
- }
-
- func (h *BackupHandler) Log (r *Record) error {
- err := h.Primary.Log(r)
- if err != nil {
- r.Ctx = append(ctx, "primary_err", err)
- return h.Secondary.Log(r)
- }
- return nil
- }
-
-This pattern is so useful that a generic version that handles an arbitrary number of Handlers
-is included as part of this library called FailoverHandler.
-
-# Logging Expensive Operations
-
-Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay
-the price of computing them if you haven't turned up your logging level to a high level of detail.
-
-This package provides a simple type to annotate a logging operation that you want to be evaluated
-lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler
-filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example:
-
- func factorRSAKey() (factors []int) {
- // return the factors of a very large number
- }
-
- log.Debug("factors", log.Lazy{factorRSAKey})
-
-If this message is not logged for any reason (like logging at the Error level), then
-factorRSAKey is never evaluated.
-
-# Dynamic context values
-
-The same log.Lazy mechanism can be used to attach context to a logger which you want to be
-evaluated when the message is logged, but not when the logger is created. For example, let's imagine
-a game where you have Player objects:
-
- type Player struct {
- name string
- alive bool
- log.Logger
- }
-
-You always want to log a player's name and whether they're alive or dead, so when you create the player
-object, you might do:
-
- p := &Player{name: name, alive: true}
- p.Logger = log.New("name", p.name, "alive", p.alive)
-
-Only now, even after a player has died, the logger will still report they are alive because the logging
-context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation
-of whether the player is alive or not to each log message, so that the log records will reflect the player's
-current state no matter when the log message is written:
-
- p := &Player{name: name, alive: true}
- isAlive := func() bool { return p.alive }
- player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive})
-
-# Terminal Format
-
-If log15 detects that stdout is a terminal, it will configure the default
-handler for it (which is log.StdoutHandler) to use TerminalFormat. This format
-logs records nicely for your terminal, including color-coded output based
-on log level.
-
-# Error Handling
-
-Becasuse log15 allows you to step around the type system, there are a few ways you can specify
-invalid arguments to the logging functions. You could, for example, wrap something that is not
-a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries
-are typically the mechanism by which errors are reported, it would be onerous for the logging functions
-to return errors. Instead, log15 handles errors by making these guarantees to you:
-
-- Any log record containing an error will still be printed with the error explained to you as part of the log record.
-
-- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily
-(and if you like, automatically) detect if any of your logging calls are passing bad values.
-
-Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers
-are encouraged to return errors only if they fail to write their log records out to an external source like if the
-syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures
-like the FailoverHandler.
-
-# Library Use
-
-log15 is intended to be useful for library authors as a way to provide configurable logging to
-users of their library. Best practice for use in a library is to always disable all output for your logger
-by default and to provide a public Logger instance that consumers of your library can configure. Like so:
-
- package yourlib
-
- import "github.com/inconshreveable/log15"
-
- var Log = log.New()
-
- func init() {
- Log.SetHandler(log.DiscardHandler())
- }
-
-Users of your library may then enable it if they like:
-
- import "github.com/inconshreveable/log15"
- import "example.com/yourlib"
-
- func main() {
- handler := // custom handler setup
- yourlib.Log.SetHandler(handler)
- }
-
-# Best practices attaching logger context
-
-The ability to attach context to a logger is a powerful one. Where should you do it and why?
-I favor embedding a Logger directly into any persistent object in my application and adding
-unique, tracing context keys to it. For instance, imagine I am writing a web browser:
-
- type Tab struct {
- url string
- render *RenderingContext
- // ...
-
- Logger
- }
-
- func NewTab(url string) *Tab {
- return &Tab {
- // ...
- url: url,
-
- Logger: log.New("url", url),
- }
- }
-
-When a new tab is created, I assign a logger to it with the url of
-the tab as context so it can easily be traced through the logs.
-Now, whenever we perform any operation with the tab, we'll log with its
-embedded logger and it will include the tab title automatically:
-
- tab.Debug("moved position", "idx", tab.idx)
-
-There's only one problem. What if the tab url changes? We could
-use log.Lazy to make sure the current url is always written, but that
-would mean that we couldn't trace a tab's full lifetime through our
-logs after the user navigate to a new URL.
-
-Instead, think about what values to attach to your loggers the
-same way you think about what to use as a key in a SQL database schema.
-If it's possible to use a natural key that is unique for the lifetime of the
-object, do so. But otherwise, log15's ext package has a handy RandId
-function to let you generate what you might call "surrogate keys"
-They're just random hex identifiers to use for tracing. Back to our
-Tab example, we would prefer to set up our Logger like so:
-
- import logext "github.com/inconshreveable/log15/ext"
-
- t := &Tab {
- // ...
- url: url,
- }
-
- t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl})
- return t
-
-Now we'll have a unique traceable identifier even across loading new urls, but
-we'll still be able to see the tab's current url in the log messages.
-
-# Must
-
-For all Handler functions which can return an error, there is a version of that
-function which will return no error but panics on failure. They are all available
-on the Must object. For example:
-
- log.Must.FileHandler("/path", log.JSONFormat)
- log.Must.NetHandler("tcp", ":1234", log.JSONFormat)
-
-# Inspiration and Credit
-
-All of the following excellent projects inspired the design of this library:
-
-code.google.com/p/log4go
-
-github.com/op/go-logging
-
-github.com/technoweenie/grohl
-
-github.com/Sirupsen/logrus
-
-github.com/kr/logfmt
-
-github.com/spacemonkeygo/spacelog
-
-golang's stdlib, notably io and net/http
-
-# The Name
-
-https://xkcd.com/927/
-*/
-package log
diff --git a/log/format.go b/log/format.go
index 2fd1f2855..6447f3c1f 100644
--- a/log/format.go
+++ b/log/format.go
@@ -2,85 +2,26 @@ package log
import (
"bytes"
- "encoding/json"
"fmt"
"math/big"
"reflect"
"strconv"
- "strings"
- "sync"
- "sync/atomic"
"time"
"unicode/utf8"
"github.com/holiman/uint256"
+ "golang.org/x/exp/slog"
)
const (
timeFormat = "2006-01-02T15:04:05-0700"
- termTimeFormat = "01-02|15:04:05.000"
floatFormat = 'f'
termMsgJust = 40
termCtxMaxPadding = 40
)
-// ResetGlobalState resets the fieldPadding, which is useful for producing
-// predictable output.
-func ResetGlobalState() {
- fieldPaddingLock.Lock()
- fieldPadding = make(map[string]int)
- fieldPaddingLock.Unlock()
-}
-
-// locationTrims are trimmed for display to avoid unwieldy log lines.
-var locationTrims = []string{
- "github.com/ethereum/go-ethereum/",
-}
-
-// PrintOrigins sets or unsets log location (file:line) printing for terminal
-// format output.
-func PrintOrigins(print bool) {
- locationEnabled.Store(print)
- if print {
- stackEnabled.Store(true)
- }
-}
-
-// stackEnabled is an atomic flag controlling whether the log handler needs
-// to store the callsite stack. This is needed in case any handler wants to
-// print locations (locationEnabled), use vmodule, or print full stacks (BacktraceAt).
-var stackEnabled atomic.Bool
-
-// locationEnabled is an atomic flag controlling whether the terminal formatter
-// should append the log locations too when printing entries.
-var locationEnabled atomic.Bool
-
-// locationLength is the maxmimum path length encountered, which all logs are
-// padded to to aid in alignment.
-var locationLength atomic.Uint32
-
-// fieldPadding is a global map with maximum field value lengths seen until now
-// to allow padding log contexts in a bit smarter way.
-var fieldPadding = make(map[string]int)
-
-// fieldPaddingLock is a global mutex protecting the field padding map.
-var fieldPaddingLock sync.RWMutex
-
-type Format interface {
- Format(r *Record) []byte
-}
-
-// FormatFunc returns a new Format object which uses
-// the given function to perform record formatting.
-func FormatFunc(f func(*Record) []byte) Format {
- return formatFunc(f)
-}
-
-type formatFunc func(*Record) []byte
-
-func (f formatFunc) Format(r *Record) []byte {
- return f(r)
-}
+// 40 spaces
+var spaces = []byte(" ")
// TerminalStringer is an analogous interface to the stdlib stringer, allowing
// own types to have custom shortened serialization formats when printed to the
@@ -89,348 +30,172 @@ type TerminalStringer interface {
TerminalString() string
}
-// TerminalFormat formats log records optimized for human readability on
-// a terminal with color-coded level output and terser human friendly timestamp.
-// This format should only be used for interactive programs or while developing.
-//
-// [LEVEL] [TIME] MESSAGE key=value key=value ...
-//
-// Example:
-//
-// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
-func TerminalFormat(usecolor bool) Format {
- return FormatFunc(func(r *Record) []byte {
- msg := escapeMessage(r.Msg)
- var color = 0
- if usecolor {
- switch r.Lvl {
- case LvlCrit:
- color = 35
- case LvlError:
- color = 31
- case LvlWarn:
- color = 33
- case LvlInfo:
- color = 32
- case LvlDebug:
- color = 36
- case LvlTrace:
- color = 34
- }
- }
-
- b := &bytes.Buffer{}
- lvl := r.Lvl.AlignedString()
- if locationEnabled.Load() {
- // Log origin printing was requested, format the location path and line number
- location := fmt.Sprintf("%+v", r.Call)
- for _, prefix := range locationTrims {
- location = strings.TrimPrefix(location, prefix)
- }
- // Maintain the maximum location length for fancyer alignment
- align := int(locationLength.Load())
- if align < len(location) {
- align = len(location)
- locationLength.Store(uint32(align))
- }
- padding := strings.Repeat(" ", align-len(location))
-
- // Assemble and print the log heading
- if color > 0 {
- fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, msg)
- } else {
- fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, msg)
- }
- } else {
- if color > 0 {
- fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), msg)
- } else {
- fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), msg)
- }
- }
- // try to justify the log output for short messages
- length := utf8.RuneCountInString(msg)
- if len(r.Ctx) > 0 && length < termMsgJust {
- b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length))
- }
- // print the keys logfmt style
- logfmt(b, r.Ctx, color, true)
- return b.Bytes()
- })
-}
-
-// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable
-// format for key/value pairs.
-//
-// For more details see: http://godoc.org/github.com/kr/logfmt
-func LogfmtFormat() Format {
- return FormatFunc(func(r *Record) []byte {
- common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg}
- buf := &bytes.Buffer{}
- logfmt(buf, append(common, r.Ctx...), 0, false)
- return buf.Bytes()
- })
-}
-
-func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) {
- for i := 0; i < len(ctx); i += 2 {
- if i != 0 {
- buf.WriteByte(' ')
- }
-
- k, ok := ctx[i].(string)
- v := formatLogfmtValue(ctx[i+1], term)
- if !ok {
- k, v = errorKey, fmt.Sprintf("%+T is not a string key", ctx[i])
- } else {
- k = escapeString(k)
- }
-
- // XXX: we should probably check that all of your key bytes aren't invalid
- fieldPaddingLock.RLock()
- padding := fieldPadding[k]
- fieldPaddingLock.RUnlock()
-
- length := utf8.RuneCountInString(v)
- if padding < length && length <= termCtxMaxPadding {
- padding = length
-
- fieldPaddingLock.Lock()
- fieldPadding[k] = padding
- fieldPaddingLock.Unlock()
- }
- if color > 0 {
- fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k)
- } else {
- buf.WriteString(k)
- buf.WriteByte('=')
- }
- buf.WriteString(v)
- if i < len(ctx)-2 && padding > length {
- buf.Write(bytes.Repeat([]byte{' '}, padding-length))
+func (h *TerminalHandler) format(buf []byte, r slog.Record, usecolor bool) []byte {
+ msg := escapeMessage(r.Message)
+ var color = ""
+ if usecolor {
+ switch r.Level {
+ case LevelCrit:
+ color = "\x1b[35m"
+ case slog.LevelError:
+ color = "\x1b[31m"
+ case slog.LevelWarn:
+ color = "\x1b[33m"
+ case slog.LevelInfo:
+ color = "\x1b[32m"
+ case slog.LevelDebug:
+ color = "\x1b[36m"
+ case LevelTrace:
+ color = "\x1b[34m"
}
}
+ if buf == nil {
+ buf = make([]byte, 0, 30+termMsgJust)
+ }
+ b := bytes.NewBuffer(buf)
+
+ if color != "" { // Start color
+ b.WriteString(color)
+ b.WriteString(LevelAlignedString(r.Level))
+ b.WriteString("\x1b[0m")
+ } else {
+ b.WriteString(LevelAlignedString(r.Level))
+ }
+ b.WriteString("[")
+ writeTimeTermFormat(b, r.Time)
+ b.WriteString("] ")
+ b.WriteString(msg)
+
+ // try to justify the log output for short messages
+ //length := utf8.RuneCountInString(msg)
+ length := len(msg)
+ if (r.NumAttrs()+len(h.attrs)) > 0 && length < termMsgJust {
+ b.Write(spaces[:termMsgJust-length])
+ }
+ // print the attributes
+ h.formatAttributes(b, r, color)
+
+ return b.Bytes()
+}
+
+func (h *TerminalHandler) formatAttributes(buf *bytes.Buffer, r slog.Record, color string) {
+ // tmp is a temporary buffer we use, until bytes.Buffer.AvailableBuffer() (1.21)
+ // can be used.
+ var tmp = make([]byte, 40)
+ writeAttr := func(attr slog.Attr, first, last bool) {
+ buf.WriteByte(' ')
+
+ if color != "" {
+ buf.WriteString(color)
+ //buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key))
+ buf.Write(appendEscapeString(tmp[:0], attr.Key))
+ buf.WriteString("\x1b[0m=")
+ } else {
+ //buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key))
+ buf.Write(appendEscapeString(tmp[:0], attr.Key))
+ buf.WriteByte('=')
+ }
+ //val := FormatSlogValue(attr.Value, true, buf.AvailableBuffer())
+ val := FormatSlogValue(attr.Value, tmp[:0])
+
+ padding := h.fieldPadding[attr.Key]
+
+ length := utf8.RuneCount(val)
+ if padding < length && length <= termCtxMaxPadding {
+ padding = length
+ h.fieldPadding[attr.Key] = padding
+ }
+ buf.Write(val)
+ if !last && padding > length {
+ buf.Write(spaces[:padding-length])
+ }
+ }
+ var n = 0
+ var nAttrs = len(h.attrs) + r.NumAttrs()
+ for _, attr := range h.attrs {
+ writeAttr(attr, n == 0, n == nAttrs-1)
+ n++
+ }
+ r.Attrs(func(attr slog.Attr) bool {
+ writeAttr(attr, n == 0, n == nAttrs-1)
+ n++
+ return true
+ })
buf.WriteByte('\n')
}
-// JSONFormat formats log records as JSON objects separated by newlines.
-// It is the equivalent of JSONFormatEx(false, true).
-func JSONFormat() Format {
- return JSONFormatEx(false, true)
-}
-
-// JSONFormatOrderedEx formats log records as JSON arrays. If pretty is true,
-// records will be pretty-printed. If lineSeparated is true, records
-// will be logged with a new line between each record.
-func JSONFormatOrderedEx(pretty, lineSeparated bool) Format {
- jsonMarshal := json.Marshal
- if pretty {
- jsonMarshal = func(v interface{}) ([]byte, error) {
- return json.MarshalIndent(v, "", " ")
- }
- }
- return FormatFunc(func(r *Record) []byte {
- props := map[string]interface{}{
- r.KeyNames.Time: r.Time,
- r.KeyNames.Lvl: r.Lvl.String(),
- r.KeyNames.Msg: r.Msg,
- }
-
- ctx := make([]string, len(r.Ctx))
- for i := 0; i < len(r.Ctx); i += 2 {
- if k, ok := r.Ctx[i].(string); ok {
- ctx[i] = k
- ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true)
- } else {
- props[errorKey] = fmt.Sprintf("%+T is not a string key,", r.Ctx[i])
- }
- }
- props[r.KeyNames.Ctx] = ctx
-
- b, err := jsonMarshal(props)
- if err != nil {
- b, _ = jsonMarshal(map[string]string{
- errorKey: err.Error(),
- })
- return b
- }
- if lineSeparated {
- b = append(b, '\n')
- }
- return b
- })
-}
-
-// JSONFormatEx formats log records as JSON objects. If pretty is true,
-// records will be pretty-printed. If lineSeparated is true, records
-// will be logged with a new line between each record.
-func JSONFormatEx(pretty, lineSeparated bool) Format {
- jsonMarshal := json.Marshal
- if pretty {
- jsonMarshal = func(v interface{}) ([]byte, error) {
- return json.MarshalIndent(v, "", " ")
- }
- }
-
- return FormatFunc(func(r *Record) []byte {
- props := map[string]interface{}{
- r.KeyNames.Time: r.Time,
- r.KeyNames.Lvl: r.Lvl.String(),
- r.KeyNames.Msg: r.Msg,
- }
-
- for i := 0; i < len(r.Ctx); i += 2 {
- k, ok := r.Ctx[i].(string)
- if !ok {
- props[errorKey] = fmt.Sprintf("%+T is not a string key", r.Ctx[i])
- } else {
- props[k] = formatJSONValue(r.Ctx[i+1])
- }
- }
-
- b, err := jsonMarshal(props)
- if err != nil {
- b, _ = jsonMarshal(map[string]string{
- errorKey: err.Error(),
- })
- return b
- }
-
- if lineSeparated {
- b = append(b, '\n')
- }
-
- return b
- })
-}
-
-func formatShared(value interface{}) (result interface{}) {
+// FormatSlogValue formats a slog.Value for serialization to terminal.
+func FormatSlogValue(v slog.Value, tmp []byte) (result []byte) {
+ var value any
defer func() {
if err := recover(); err != nil {
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
- result = "nil"
+ result = []byte("")
} else {
panic(err)
}
}
}()
- switch v := value.(type) {
- case time.Time:
- return v.Format(timeFormat)
-
- case error:
- return v.Error()
-
- case fmt.Stringer:
- return v.String()
-
- default:
- return v
- }
-}
-
-func formatJSONValue(value interface{}) interface{} {
- value = formatShared(value)
- switch value.(type) {
- case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
- return value
- default:
- return fmt.Sprintf("%+v", value)
- }
-}
-
-// formatValue formats a value for serialization
-func formatLogfmtValue(value interface{}, term bool) string {
- if value == nil {
- return "nil"
- }
-
- switch v := value.(type) {
- case time.Time:
+ switch v.Kind() {
+ case slog.KindString:
+ return appendEscapeString(tmp, v.String())
+ case slog.KindInt64: // All int-types (int8, int16 etc) wind up here
+ return appendInt64(tmp, v.Int64())
+ case slog.KindUint64: // All uint-types (uint8, uint16 etc) wind up here
+ return appendUint64(tmp, v.Uint64(), false)
+ case slog.KindFloat64:
+ return strconv.AppendFloat(tmp, v.Float64(), floatFormat, 3, 64)
+ case slog.KindBool:
+ return strconv.AppendBool(tmp, v.Bool())
+ case slog.KindDuration:
+ value = v.Duration()
+ case slog.KindTime:
// Performance optimization: No need for escaping since the provided
// timeFormat doesn't have any escape characters, and escaping is
// expensive.
- return v.Format(timeFormat)
-
- case *big.Int:
- // Big ints get consumed by the Stringer clause, so we need to handle
- // them earlier on.
- if v == nil {
- return ""
- }
- return formatLogfmtBigInt(v)
-
- case *uint256.Int:
- // Uint256s get consumed by the Stringer clause, so we need to handle
- // them earlier on.
- if v == nil {
- return ""
- }
- return formatLogfmtUint256(v)
- }
- if term {
- if s, ok := value.(TerminalStringer); ok {
- // Custom terminal stringer provided, use that
- return escapeString(s.TerminalString())
- }
- }
- value = formatShared(value)
- switch v := value.(type) {
- case bool:
- return strconv.FormatBool(v)
- case float32:
- return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
- case float64:
- return strconv.FormatFloat(v, floatFormat, 3, 64)
- case int8:
- return strconv.FormatInt(int64(v), 10)
- case uint8:
- return strconv.FormatInt(int64(v), 10)
- case int16:
- return strconv.FormatInt(int64(v), 10)
- case uint16:
- return strconv.FormatInt(int64(v), 10)
- // Larger integers get thousands separators.
- case int:
- return FormatLogfmtInt64(int64(v))
- case int32:
- return FormatLogfmtInt64(int64(v))
- case int64:
- return FormatLogfmtInt64(v)
- case uint:
- return FormatLogfmtUint64(uint64(v))
- case uint32:
- return FormatLogfmtUint64(uint64(v))
- case uint64:
- return FormatLogfmtUint64(v)
- case string:
- return escapeString(v)
+ return v.Time().AppendFormat(tmp, timeFormat)
default:
- return escapeString(fmt.Sprintf("%+v", value))
+ value = v.Any()
}
+ if value == nil {
+ return []byte("")
+ }
+ switch v := value.(type) {
+ case *big.Int: // Need to be before fmt.Stringer-clause
+ return appendBigInt(tmp, v)
+ case *uint256.Int: // Need to be before fmt.Stringer-clause
+ return appendU256(tmp, v)
+ case error:
+ return appendEscapeString(tmp, v.Error())
+ case TerminalStringer:
+ return appendEscapeString(tmp, v.TerminalString())
+ case fmt.Stringer:
+ return appendEscapeString(tmp, v.String())
+ }
+
+ // We can use the 'tmp' as a scratch-buffer, to first format the
+ // value, and in a second step do escaping.
+ internal := fmt.Appendf(tmp, "%+v", value)
+ return appendEscapeString(tmp, string(internal))
}
-// FormatLogfmtInt64 formats n with thousand separators.
-func FormatLogfmtInt64(n int64) string {
+// appendInt64 formats n with thousand separators and writes into buffer dst.
+func appendInt64(dst []byte, n int64) []byte {
if n < 0 {
- return formatLogfmtUint64(uint64(-n), true)
+ return appendUint64(dst, uint64(-n), true)
}
- return formatLogfmtUint64(uint64(n), false)
+ return appendUint64(dst, uint64(n), false)
}
-// FormatLogfmtUint64 formats n with thousand separators.
-func FormatLogfmtUint64(n uint64) string {
- return formatLogfmtUint64(n, false)
-}
-
-func formatLogfmtUint64(n uint64, neg bool) string {
+// appendUint64 formats n with thousand separators and writes into buffer dst.
+func appendUint64(dst []byte, n uint64, neg bool) []byte {
// Small numbers are fine as is
if n < 100000 {
if neg {
- return strconv.Itoa(-int(n))
+ return strconv.AppendInt(dst, -int64(n), 10)
} else {
- return strconv.Itoa(int(n))
+ return strconv.AppendInt(dst, int64(n), 10)
}
}
// Large numbers should be split
@@ -455,16 +220,21 @@ func formatLogfmtUint64(n uint64, neg bool) string {
out[i] = '-'
i--
}
- return string(out[i+1:])
+ return append(dst, out[i+1:]...)
}
-// formatLogfmtBigInt formats n with thousand separators.
-func formatLogfmtBigInt(n *big.Int) string {
+// FormatLogfmtUint64 formats n with thousand separators.
+func FormatLogfmtUint64(n uint64) string {
+ return string(appendUint64(nil, n, false))
+}
+
+// appendBigInt formats n with thousand separators and writes to dst.
+func appendBigInt(dst []byte, n *big.Int) []byte {
if n.IsUint64() {
- return FormatLogfmtUint64(n.Uint64())
+ return appendUint64(dst, n.Uint64(), false)
}
if n.IsInt64() {
- return FormatLogfmtInt64(n.Int64())
+ return appendInt64(dst, n.Int64())
}
var (
@@ -489,54 +259,48 @@ func formatLogfmtBigInt(n *big.Int) string {
comma++
}
}
- return string(buf[i+1:])
+ return append(dst, buf[i+1:]...)
}
-// formatLogfmtUint256 formats n with thousand separators.
-func formatLogfmtUint256(n *uint256.Int) string {
+// appendU256 formats n with thousand separators.
+func appendU256(dst []byte, n *uint256.Int) []byte {
if n.IsUint64() {
- return FormatLogfmtUint64(n.Uint64())
+ return appendUint64(dst, n.Uint64(), false)
}
- var (
- text = n.Dec()
- buf = make([]byte, len(text)+len(text)/3)
- comma = 0
- i = len(buf) - 1
- )
- for j := len(text) - 1; j >= 0; j, i = j-1, i-1 {
- c := text[j]
-
- switch {
- case c == '-':
- buf[i] = c
- case comma == 3:
- buf[i] = ','
- i--
- comma = 0
- fallthrough
- default:
- buf[i] = c
- comma++
- }
- }
- return string(buf[i+1:])
+ res := []byte(n.PrettyDec(','))
+ return append(dst, res...)
}
-// escapeString checks if the provided string needs escaping/quoting, and
-// calls strconv.Quote if needed
-func escapeString(s string) string {
+// appendEscapeString writes the string s to the given writer, with
+// escaping/quoting if needed.
+func appendEscapeString(dst []byte, s string) []byte {
needsQuoting := false
+ needsEscaping := false
for _, r := range s {
- // We quote everything below " (0x22) and above~ (0x7E), plus equal-sign
- if r <= '"' || r > '~' || r == '=' {
+ // If it contains spaces or equal-sign, we need to quote it.
+ if r == ' ' || r == '=' {
needsQuoting = true
+ continue
+ }
+ // We need to escape it, if it contains
+ // - character " (0x22) and lower (except space)
+ // - characters above ~ (0x7E), plus equal-sign
+ if r <= '"' || r > '~' {
+ needsEscaping = true
break
}
}
- if !needsQuoting {
- return s
+ if needsEscaping {
+ return strconv.AppendQuote(dst, s)
}
- return strconv.Quote(s)
+ // No escaping needed, but we might have to place within quote-marks, in case
+ // it contained a space
+ if needsQuoting {
+ dst = append(dst, '"')
+ dst = append(dst, []byte(s)...)
+ return append(dst, '"')
+ }
+ return append(dst, []byte(s)...)
}
// escapeMessage checks if the provided string needs escaping/quoting, similarly
@@ -561,3 +325,45 @@ func escapeMessage(s string) string {
}
return strconv.Quote(s)
}
+
+// writeTimeTermFormat writes on the format "01-02|15:04:05.000"
+func writeTimeTermFormat(buf *bytes.Buffer, t time.Time) {
+ _, month, day := t.Date()
+ writePosIntWidth(buf, int(month), 2)
+ buf.WriteByte('-')
+ writePosIntWidth(buf, day, 2)
+ buf.WriteByte('|')
+ hour, min, sec := t.Clock()
+ writePosIntWidth(buf, hour, 2)
+ buf.WriteByte(':')
+ writePosIntWidth(buf, min, 2)
+ buf.WriteByte(':')
+ writePosIntWidth(buf, sec, 2)
+ ns := t.Nanosecond()
+ buf.WriteByte('.')
+ writePosIntWidth(buf, ns/1e6, 3)
+}
+
+// writePosIntWidth writes non-negative integer i to the buffer, padded on the left
+// by zeroes to the given width. Use a width of 0 to omit padding.
+// Adapted from golang.org/x/exp/slog/internal/buffer/buffer.go
+func writePosIntWidth(b *bytes.Buffer, i, width int) {
+ // Cheap integer to fixed-width decimal ASCII.
+ // Copied from log/log.go.
+ if i < 0 {
+ panic("negative int")
+ }
+ // Assemble decimal in reverse order.
+ var bb [20]byte
+ bp := len(bb) - 1
+ for i >= 10 || width > 1 {
+ width--
+ q := i / 10
+ bb[bp] = byte('0' + i - q*10)
+ bp--
+ i = q
+ }
+ // i < 10
+ bb[bp] = byte('0' + i)
+ b.Write(bb[bp:])
+}
diff --git a/log/format_test.go b/log/format_test.go
index 41e1809c3..d4c1df4ab 100644
--- a/log/format_test.go
+++ b/log/format_test.go
@@ -5,18 +5,20 @@ import (
"testing"
)
-var sink string
+var sink []byte
func BenchmarkPrettyInt64Logfmt(b *testing.B) {
+ buf := make([]byte, 100)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- sink = FormatLogfmtInt64(rand.Int63())
+ sink = appendInt64(buf, rand.Int63())
}
}
func BenchmarkPrettyUint64Logfmt(b *testing.B) {
+ buf := make([]byte, 100)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
- sink = FormatLogfmtUint64(rand.Uint64())
+ sink = appendUint64(buf, rand.Uint64(), false)
}
}
diff --git a/log/handler.go b/log/handler.go
index 4a0cf578f..7459aad89 100644
--- a/log/handler.go
+++ b/log/handler.go
@@ -1,375 +1,192 @@
package log
import (
+ "context"
"fmt"
"io"
- "net"
- "os"
+ "math/big"
"reflect"
"sync"
- "sync/atomic"
+ "time"
- "github.com/go-stack/stack"
+ "github.com/holiman/uint256"
+ "golang.org/x/exp/slog"
)
-// Handler defines where and how log records are written.
-// A Logger prints its log records by writing to a Handler.
-// Handlers are composable, providing you great flexibility in combining
-// them to achieve the logging structure that suits your applications.
-type Handler interface {
- Log(r *Record) error
+type discardHandler struct{}
+
+// DiscardHandler returns a no-op handler
+func DiscardHandler() slog.Handler {
+ return &discardHandler{}
}
-// FuncHandler returns a Handler that logs records with the given
-// function.
-func FuncHandler(fn func(r *Record) error) Handler {
- return funcHandler(fn)
+func (h *discardHandler) Handle(_ context.Context, r slog.Record) error {
+ return nil
}
-type funcHandler func(r *Record) error
-
-func (h funcHandler) Log(r *Record) error {
- return h(r)
+func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool {
+ return false
}
-// StreamHandler writes log records to an io.Writer
-// with the given format. StreamHandler can be used
-// to easily begin writing log records to other
-// outputs.
+func (h *discardHandler) WithGroup(name string) slog.Handler {
+ panic("not implemented")
+}
+
+func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ return &discardHandler{}
+}
+
+type TerminalHandler struct {
+ mu sync.Mutex
+ wr io.Writer
+ lvl slog.Level
+ useColor bool
+ attrs []slog.Attr
+ // fieldPadding is a map with maximum field value lengths seen until now
+ // to allow padding log contexts in a bit smarter way.
+ fieldPadding map[string]int
+
+ buf []byte
+}
+
+// NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on
+// a terminal with color-coded level output and terser human friendly timestamp.
+// This format should only be used for interactive programs or while developing.
//
-// StreamHandler wraps itself with LazyHandler and SyncHandler
-// to evaluate Lazy objects and perform safe concurrent writes.
-func StreamHandler(wr io.Writer, fmtr Format) Handler {
- h := FuncHandler(func(r *Record) error {
- _, err := wr.Write(fmtr.Format(r))
- return err
- })
- return LazyHandler(SyncHandler(h))
+// [LEVEL] [TIME] MESSAGE key=value key=value ...
+//
+// Example:
+//
+// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
+func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler {
+ return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor)
}
-// SyncHandler can be wrapped around a handler to guarantee that
-// only a single Log operation can proceed at a time. It's necessary
-// for thread-safe concurrent writes.
-func SyncHandler(h Handler) Handler {
- var mu sync.Mutex
- return FuncHandler(func(r *Record) error {
- mu.Lock()
- defer mu.Unlock()
-
- return h.Log(r)
- })
-}
-
-// FileHandler returns a handler which writes log records to the give file
-// using the given format. If the path
-// already exists, FileHandler will append to the given file. If it does not,
-// FileHandler will create the file with mode 0644.
-func FileHandler(path string, fmtr Format) (Handler, error) {
- f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
- if err != nil {
- return nil, err
+// NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs
+// records which are less than or equal to the specified verbosity level.
+func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler {
+ return &TerminalHandler{
+ wr: wr,
+ lvl: lvl,
+ useColor: useColor,
+ fieldPadding: make(map[string]int),
}
- return closingHandler{f, StreamHandler(f, fmtr)}, nil
}
-// NetHandler opens a socket to the given address and writes records
-// over the connection.
-func NetHandler(network, addr string, fmtr Format) (Handler, error) {
- conn, err := net.Dial(network, addr)
- if err != nil {
- return nil, err
+func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ buf := h.format(h.buf, r, h.useColor)
+ h.wr.Write(buf)
+ h.buf = buf[:0]
+ return nil
+}
+
+func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool {
+ return level >= h.lvl
+}
+
+func (h *TerminalHandler) WithGroup(name string) slog.Handler {
+ panic("not implemented")
+}
+
+func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ return &TerminalHandler{
+ wr: h.wr,
+ lvl: h.lvl,
+ useColor: h.useColor,
+ attrs: append(h.attrs, attrs...),
+ fieldPadding: make(map[string]int),
}
-
- return closingHandler{conn, StreamHandler(conn, fmtr)}, nil
}
-// XXX: closingHandler is essentially unused at the moment
-// it's meant for a future time when the Handler interface supports
-// a possible Close() operation
-type closingHandler struct {
- io.WriteCloser
- Handler
+// ResetFieldPadding zeroes the field-padding for all attribute pairs.
+func (t *TerminalHandler) ResetFieldPadding() {
+ t.mu.Lock()
+ t.fieldPadding = make(map[string]int)
+ t.mu.Unlock()
}
-func (h *closingHandler) Close() error {
- return h.WriteCloser.Close()
+type leveler struct{ minLevel slog.Level }
+
+func (l *leveler) Level() slog.Level {
+ return l.minLevel
}
-// CallerFileHandler returns a Handler that adds the line number and file of
-// the calling function to the context with key "caller".
-func CallerFileHandler(h Handler) Handler {
- return FuncHandler(func(r *Record) error {
- r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call))
- return h.Log(r)
+// JSONHandler returns a handler which prints records in JSON format.
+func JSONHandler(wr io.Writer) slog.Handler {
+ return slog.NewJSONHandler(wr, &slog.HandlerOptions{
+ ReplaceAttr: builtinReplaceJSON,
})
}
-// CallerFuncHandler returns a Handler that adds the calling function name to
-// the context with key "fn".
-func CallerFuncHandler(h Handler) Handler {
- return FuncHandler(func(r *Record) error {
- r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call))
- return h.Log(r)
- })
-}
-
-// This function is here to please go vet on Go < 1.8.
-func formatCall(format string, c stack.Call) string {
- return fmt.Sprintf(format, c)
-}
-
-// CallerStackHandler returns a Handler that adds a stack trace to the context
-// with key "stack". The stack trace is formatted as a space separated list of
-// call sites inside matching []'s. The most recent call site is listed first.
-// Each call site is formatted according to format. See the documentation of
-// package github.com/go-stack/stack for the list of supported formats.
-func CallerStackHandler(format string, h Handler) Handler {
- return FuncHandler(func(r *Record) error {
- s := stack.Trace().TrimBelow(r.Call).TrimRuntime()
- if len(s) > 0 {
- r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s))
- }
- return h.Log(r)
- })
-}
-
-// FilterHandler returns a Handler that only writes records to the
-// wrapped Handler if the given function evaluates true. For example,
-// to only log records where the 'err' key is not nil:
+// LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable
+// format for key/value pairs.
//
-// logger.SetHandler(FilterHandler(func(r *Record) bool {
-// for i := 0; i < len(r.Ctx); i += 2 {
-// if r.Ctx[i] == "err" {
-// return r.Ctx[i+1] != nil
-// }
-// }
-// return false
-// }, h))
-func FilterHandler(fn func(r *Record) bool, h Handler) Handler {
- return FuncHandler(func(r *Record) error {
- if fn(r) {
- return h.Log(r)
- }
- return nil
+// For more details see: http://godoc.org/github.com/kr/logfmt
+func LogfmtHandler(wr io.Writer) slog.Handler {
+ return slog.NewTextHandler(wr, &slog.HandlerOptions{
+ ReplaceAttr: builtinReplaceLogfmt,
})
}
-// MatchFilterHandler returns a Handler that only writes records
-// to the wrapped Handler if the given key in the logged
-// context matches the value. For example, to only log records
-// from your ui package:
-//
-// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
-func MatchFilterHandler(key string, value interface{}, h Handler) Handler {
- return FilterHandler(func(r *Record) (pass bool) {
- switch key {
- case r.KeyNames.Lvl:
- return r.Lvl == value
- case r.KeyNames.Time:
- return r.Time == value
- case r.KeyNames.Msg:
- return r.Msg == value
- }
+// LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs
+// records which are less than or equal to the specified verbosity level.
+func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler {
+ return slog.NewTextHandler(wr, &slog.HandlerOptions{
+ ReplaceAttr: builtinReplaceLogfmt,
+ Level: &leveler{level},
+ })
+}
- for i := 0; i < len(r.Ctx); i += 2 {
- if r.Ctx[i] == key {
- return r.Ctx[i+1] == value
+func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr {
+ return builtinReplace(nil, attr, true)
+}
+
+func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr {
+ return builtinReplace(nil, attr, false)
+}
+
+func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr {
+ switch attr.Key {
+ case slog.TimeKey:
+ if attr.Value.Kind() == slog.KindTime {
+ if logfmt {
+ return slog.String("t", attr.Value.Time().Format(timeFormat))
+ } else {
+ return slog.Attr{Key: "t", Value: attr.Value}
}
}
- return false
- }, h)
-}
-
-// LvlFilterHandler returns a Handler that only writes
-// records which are less than the given verbosity
-// level to the wrapped Handler. For example, to only
-// log Error/Crit records:
-//
-// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
-func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
- return FilterHandler(func(r *Record) (pass bool) {
- return r.Lvl <= maxLvl
- }, h)
-}
-
-// MultiHandler dispatches any write to each of its handlers.
-// This is useful for writing different types of log information
-// to different locations. For example, to log to a file and
-// standard error:
-//
-// log.MultiHandler(
-// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
-// log.StderrHandler)
-func MultiHandler(hs ...Handler) Handler {
- return FuncHandler(func(r *Record) error {
- for _, h := range hs {
- // what to do about failures?
- h.Log(r)
+ case slog.LevelKey:
+ if l, ok := attr.Value.Any().(slog.Level); ok {
+ attr = slog.Any("lvl", LevelString(l))
+ return attr
}
- return nil
- })
-}
+ }
-// FailoverHandler writes all log records to the first handler
-// specified, but will failover and write to the second handler if
-// the first handler has failed, and so on for all handlers specified.
-// For example you might want to log to a network socket, but failover
-// to writing to a file if the network fails, and then to
-// standard out if the file write fails:
-//
-// log.FailoverHandler(
-// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
-// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
-// log.StdoutHandler)
-//
-// All writes that do not go to the first handler will add context with keys of
-// the form "failover_err_{idx}" which explain the error encountered while
-// trying to write to the handlers before them in the list.
-func FailoverHandler(hs ...Handler) Handler {
- return FuncHandler(func(r *Record) error {
- var err error
- for i, h := range hs {
- err = h.Log(r)
- if err == nil {
- return nil
- }
- r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err)
+ switch v := attr.Value.Any().(type) {
+ case time.Time:
+ if logfmt {
+ attr = slog.String(attr.Key, v.Format(timeFormat))
}
-
- return err
- })
-}
-
-// ChannelHandler writes all records to the given channel.
-// It blocks if the channel is full. Useful for async processing
-// of log messages, it's used by BufferedHandler.
-func ChannelHandler(recs chan<- *Record) Handler {
- return FuncHandler(func(r *Record) error {
- recs <- r
- return nil
- })
-}
-
-// BufferedHandler writes all records to a buffered
-// channel of the given size which flushes into the wrapped
-// handler whenever it is available for writing. Since these
-// writes happen asynchronously, all writes to a BufferedHandler
-// never return an error and any errors from the wrapped handler are ignored.
-func BufferedHandler(bufSize int, h Handler) Handler {
- recs := make(chan *Record, bufSize)
- go func() {
- for m := range recs {
- _ = h.Log(m)
+ case *big.Int:
+ if v == nil {
+ attr.Value = slog.StringValue("")
+ } else {
+ attr.Value = slog.StringValue(v.String())
}
- }()
- return ChannelHandler(recs)
-}
-
-// LazyHandler writes all values to the wrapped handler after evaluating
-// any lazy functions in the record's context. It is already wrapped
-// around StreamHandler and SyslogHandler in this library, you'll only need
-// it if you write your own Handler.
-func LazyHandler(h Handler) Handler {
- return FuncHandler(func(r *Record) error {
- // go through the values (odd indices) and reassign
- // the values of any lazy fn to the result of its execution
- hadErr := false
- for i := 1; i < len(r.Ctx); i += 2 {
- lz, ok := r.Ctx[i].(Lazy)
- if ok {
- v, err := evaluateLazy(lz)
- if err != nil {
- hadErr = true
- r.Ctx[i] = err
- } else {
- if cs, ok := v.(stack.CallStack); ok {
- v = cs.TrimBelow(r.Call).TrimRuntime()
- }
- r.Ctx[i] = v
- }
- }
+ case *uint256.Int:
+ if v == nil {
+ attr.Value = slog.StringValue("")
+ } else {
+ attr.Value = slog.StringValue(v.Dec())
}
-
- if hadErr {
- r.Ctx = append(r.Ctx, errorKey, "bad lazy")
+ case fmt.Stringer:
+ if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) {
+ attr.Value = slog.StringValue("")
+ } else {
+ attr.Value = slog.StringValue(v.String())
}
-
- return h.Log(r)
- })
-}
-
-func evaluateLazy(lz Lazy) (interface{}, error) {
- t := reflect.TypeOf(lz.Fn)
-
- if t.Kind() != reflect.Func {
- return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn)
}
-
- if t.NumIn() > 0 {
- return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn)
- }
-
- if t.NumOut() == 0 {
- return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn)
- }
-
- value := reflect.ValueOf(lz.Fn)
- results := value.Call([]reflect.Value{})
- if len(results) == 1 {
- return results[0].Interface(), nil
- }
- values := make([]interface{}, len(results))
- for i, v := range results {
- values[i] = v.Interface()
- }
- return values, nil
-}
-
-// DiscardHandler reports success for all writes but does nothing.
-// It is useful for dynamically disabling logging at runtime via
-// a Logger's SetHandler method.
-func DiscardHandler() Handler {
- return FuncHandler(func(r *Record) error {
- return nil
- })
-}
-
-// Must provides the following Handler creation functions
-// which instead of returning an error parameter only return a Handler
-// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
-var Must muster
-
-func must(h Handler, err error) Handler {
- if err != nil {
- panic(err)
- }
- return h
-}
-
-type muster struct{}
-
-func (m muster) FileHandler(path string, fmtr Format) Handler {
- return must(FileHandler(path, fmtr))
-}
-
-func (m muster) NetHandler(network, addr string, fmtr Format) Handler {
- return must(NetHandler(network, addr, fmtr))
-}
-
-// swapHandler wraps another handler that may be swapped out
-// dynamically at runtime in a thread-safe fashion.
-type swapHandler struct {
- handler atomic.Value
-}
-
-func (h *swapHandler) Log(r *Record) error {
- return (*h.handler.Load().(*Handler)).Log(r)
-}
-
-func (h *swapHandler) Swap(newHandler Handler) {
- h.handler.Store(&newHandler)
-}
-
-func (h *swapHandler) Get() Handler {
- return *h.handler.Load().(*Handler)
+ return attr
}
diff --git a/log/handler_glog.go b/log/handler_glog.go
index afca0808b..fb1e03c5b 100644
--- a/log/handler_glog.go
+++ b/log/handler_glog.go
@@ -17,6 +17,7 @@
package log
import (
+ "context"
"errors"
"fmt"
"regexp"
@@ -25,54 +26,47 @@ import (
"strings"
"sync"
"sync/atomic"
+
+ "golang.org/x/exp/slog"
)
// errVmoduleSyntax is returned when a user vmodule pattern is invalid.
var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
-// errTraceSyntax is returned when a user backtrace pattern is invalid.
-var errTraceSyntax = errors.New("expect file.go:234")
-
// GlogHandler is a log handler that mimics the filtering features of Google's
// glog logger: setting global log levels; overriding with callsite pattern
// matches; and requesting backtraces at certain positions.
type GlogHandler struct {
- origin Handler // The origin handler this wraps
+ origin slog.Handler // The origin handler this wraps
- level atomic.Uint32 // Current log level, atomically accessible
- override atomic.Bool // Flag whether overrides are used, atomically accessible
- backtrace atomic.Bool // Flag whether backtrace location is set
+ level atomic.Int32 // Current log level, atomically accessible
+ override atomic.Bool // Flag whether overrides are used, atomically accessible
- patterns []pattern // Current list of patterns to override with
- siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations
- location string // file:line location where to do a stackdump at
- lock sync.RWMutex // Lock protecting the override pattern list
+ patterns []pattern // Current list of patterns to override with
+ siteCache map[uintptr]slog.Level // Cache of callsite pattern evaluations
+ location string // file:line location where to do a stackdump at
+ lock sync.RWMutex // Lock protecting the override pattern list
}
// NewGlogHandler creates a new log handler with filtering functionality similar
// to Google's glog logger. The returned handler implements Handler.
-func NewGlogHandler(h Handler) *GlogHandler {
+func NewGlogHandler(h slog.Handler) *GlogHandler {
return &GlogHandler{
origin: h,
}
}
-// SetHandler updates the handler to write records to the specified sub-handler.
-func (h *GlogHandler) SetHandler(nh Handler) {
- h.origin = nh
-}
-
// pattern contains a filter for the Vmodule option, holding a verbosity level
// and a file pattern to match.
type pattern struct {
pattern *regexp.Regexp
- level Lvl
+ level slog.Level
}
// Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
// and source files can be raised using Vmodule.
-func (h *GlogHandler) Verbosity(level Lvl) {
- h.level.Store(uint32(level))
+func (h *GlogHandler) Verbosity(level slog.Level) {
+ h.level.Store(int32(level))
}
// Vmodule sets the glog verbosity pattern.
@@ -108,11 +102,13 @@ func (h *GlogHandler) Vmodule(ruleset string) error {
return errVmoduleSyntax
}
// Parse the level and if correct, assemble the filter rule
- level, err := strconv.Atoi(parts[1])
+ l, err := strconv.Atoi(parts[1])
if err != nil {
return errVmoduleSyntax
}
- if level <= 0 {
+ level := FromLegacyLevel(l)
+
+ if level == LevelCrit {
continue // Ignore. It's harmless but no point in paying the overhead.
}
// Compile the rule pattern into a regular expression
@@ -130,107 +126,84 @@ func (h *GlogHandler) Vmodule(ruleset string) error {
matcher = matcher + "$"
re, _ := regexp.Compile(matcher)
- filter = append(filter, pattern{re, Lvl(level)})
+ filter = append(filter, pattern{re, level})
}
// Swap out the vmodule pattern for the new filter system
h.lock.Lock()
defer h.lock.Unlock()
h.patterns = filter
- h.siteCache = make(map[uintptr]Lvl)
+ h.siteCache = make(map[uintptr]slog.Level)
h.override.Store(len(filter) != 0)
- // Enable location storage (globally)
- if len(h.patterns) > 0 {
- stackEnabled.Store(true)
- }
+
return nil
}
-// BacktraceAt sets the glog backtrace location. When set to a file and line
-// number holding a logging statement, a stack trace will be written to the Info
-// log whenever execution hits that statement.
-//
-// Unlike with Vmodule, the ".go" must be present.
-func (h *GlogHandler) BacktraceAt(location string) error {
- // Ensure the backtrace location contains two non-empty elements
- parts := strings.Split(location, ":")
- if len(parts) != 2 {
- return errTraceSyntax
- }
- parts[0] = strings.TrimSpace(parts[0])
- parts[1] = strings.TrimSpace(parts[1])
- if len(parts[0]) == 0 || len(parts[1]) == 0 {
- return errTraceSyntax
- }
- // Ensure the .go prefix is present and the line is valid
- if !strings.HasSuffix(parts[0], ".go") {
- return errTraceSyntax
- }
- if _, err := strconv.Atoi(parts[1]); err != nil {
- return errTraceSyntax
- }
- // All seems valid
- h.lock.Lock()
- defer h.lock.Unlock()
+func (h *GlogHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
+ // fast-track skipping logging if override not enabled and the provided verbosity is above configured
+ return h.override.Load() || slog.Level(h.level.Load()) <= lvl
+}
- h.location = location
- h.backtrace.Store(len(location) > 0)
- // Enable location storage (globally)
- stackEnabled.Store(true)
- return nil
+func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ h.lock.RLock()
+ siteCache := make(map[uintptr]slog.Level)
+ for k, v := range h.siteCache {
+ siteCache[k] = v
+ }
+ h.lock.RUnlock()
+
+ patterns := []pattern{}
+ patterns = append(patterns, h.patterns...)
+
+ res := GlogHandler{
+ origin: h.origin.WithAttrs(attrs),
+ patterns: patterns,
+ siteCache: siteCache,
+ location: h.location,
+ }
+
+ res.level.Store(h.level.Load())
+ res.override.Store(h.override.Load())
+ return &res
+}
+
+func (h *GlogHandler) WithGroup(name string) slog.Handler {
+ panic("not implemented")
}
// Log implements Handler.Log, filtering a log record through the global, local
// and backtrace filters, finally emitting it if either allow it through.
-func (h *GlogHandler) Log(r *Record) error {
- // If backtracing is requested, check whether this is the callsite
- if h.backtrace.Load() {
- // Everything below here is slow. Although we could cache the call sites the
- // same way as for vmodule, backtracing is so rare it's not worth the extra
- // complexity.
- h.lock.RLock()
- match := h.location == r.Call.String()
- h.lock.RUnlock()
-
- if match {
- // Callsite matched, raise the log level to info and gather the stacks
- r.Lvl = LvlInfo
-
- buf := make([]byte, 1024*1024)
- buf = buf[:runtime.Stack(buf, true)]
- r.Msg += "\n\n" + string(buf)
- }
- }
+func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error {
// If the global log level allows, fast track logging
- if h.level.Load() >= uint32(r.Lvl) {
- return h.origin.Log(r)
- }
- // If no local overrides are present, fast track skipping
- if !h.override.Load() {
- return nil
+ if slog.Level(h.level.Load()) <= r.Level {
+ return h.origin.Handle(context.Background(), r)
}
+
// Check callsite cache for previously calculated log levels
h.lock.RLock()
- lvl, ok := h.siteCache[r.Call.Frame().PC]
+ lvl, ok := h.siteCache[r.PC]
h.lock.RUnlock()
// If we didn't cache the callsite yet, calculate it
if !ok {
h.lock.Lock()
+
+ fs := runtime.CallersFrames([]uintptr{r.PC})
+ frame, _ := fs.Next()
+
for _, rule := range h.patterns {
- if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) {
- h.siteCache[r.Call.Frame().PC], lvl, ok = rule.level, rule.level, true
- break
+ if rule.pattern.MatchString(fmt.Sprintf("%+s", frame.File)) {
+ h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true
}
}
// If no rule matched, remember to drop log the next time
if !ok {
- h.siteCache[r.Call.Frame().PC] = 0
+ h.siteCache[r.PC] = 0
}
h.lock.Unlock()
}
- if lvl >= r.Lvl {
- return h.origin.Log(r)
+ if lvl <= r.Level {
+ return h.origin.Handle(context.Background(), r)
}
return nil
}
diff --git a/log/logger.go b/log/logger.go
index 42e7e375d..75e364304 100644
--- a/log/logger.go
+++ b/log/logger.go
@@ -1,294 +1,210 @@
package log
import (
- "fmt"
+ "context"
+ "math"
"os"
+ "runtime"
"time"
- "github.com/go-stack/stack"
+ "golang.org/x/exp/slog"
)
-const timeKey = "t"
-const lvlKey = "lvl"
-const msgKey = "msg"
-const ctxKey = "ctx"
-const errorKey = "LOG15_ERROR"
-const skipLevel = 2
-
-type Lvl int
+const errorKey = "LOG_ERROR"
const (
- LvlCrit Lvl = iota
- LvlError
- LvlWarn
- LvlInfo
- LvlDebug
- LvlTrace
+ legacyLevelCrit = iota
+ legacyLevelError
+ legacyLevelWarn
+ legacyLevelInfo
+ legacyLevelDebug
+ legacyLevelTrace
)
-// AlignedString returns a 5-character string containing the name of a Lvl.
-func (l Lvl) AlignedString() string {
+const (
+ levelMaxVerbosity slog.Level = math.MinInt
+ LevelTrace slog.Level = -8
+ LevelDebug = slog.LevelDebug
+ LevelInfo = slog.LevelInfo
+ LevelWarn = slog.LevelWarn
+ LevelError = slog.LevelError
+ LevelCrit slog.Level = 12
+
+ // for backward-compatibility
+ LvlTrace = LevelTrace
+ LvlInfo = LevelInfo
+ LvlDebug = LevelDebug
+)
+
+// convert from old Geth verbosity level constants
+// to levels defined by slog
+func FromLegacyLevel(lvl int) slog.Level {
+ switch lvl {
+ case legacyLevelCrit:
+ return LevelCrit
+ case legacyLevelError:
+ return slog.LevelError
+ case legacyLevelWarn:
+ return slog.LevelWarn
+ case legacyLevelInfo:
+ return slog.LevelInfo
+ case legacyLevelDebug:
+ return slog.LevelDebug
+ case legacyLevelTrace:
+ return LevelTrace
+ default:
+ break
+ }
+
+ // TODO: should we allow use of custom levels or force them to match existing max/min if they fall outside the range as I am doing here?
+ if lvl > legacyLevelTrace {
+ return LevelTrace
+ }
+ return LevelCrit
+}
+
+// LevelAlignedString returns a 5-character string containing the name of a Lvl.
+func LevelAlignedString(l slog.Level) string {
switch l {
- case LvlTrace:
+ case LevelTrace:
return "TRACE"
- case LvlDebug:
+ case slog.LevelDebug:
return "DEBUG"
- case LvlInfo:
+ case slog.LevelInfo:
return "INFO "
- case LvlWarn:
+ case slog.LevelWarn:
return "WARN "
- case LvlError:
+ case slog.LevelError:
return "ERROR"
- case LvlCrit:
+ case LevelCrit:
return "CRIT "
default:
- panic("bad level")
+ return "unknown level"
}
}
-// String returns the name of a Lvl.
-func (l Lvl) String() string {
+// LevelString returns a string containing the name of a Lvl.
+func LevelString(l slog.Level) string {
switch l {
- case LvlTrace:
- return "trce"
- case LvlDebug:
- return "dbug"
- case LvlInfo:
+ case LevelTrace:
+ return "trace"
+ case slog.LevelDebug:
+ return "debug"
+ case slog.LevelInfo:
return "info"
- case LvlWarn:
+ case slog.LevelWarn:
return "warn"
- case LvlError:
- return "eror"
- case LvlCrit:
+ case slog.LevelError:
+ return "error"
+ case LevelCrit:
return "crit"
default:
- panic("bad level")
+ return "unknown"
}
}
-// LvlFromString returns the appropriate Lvl from a string name.
-// Useful for parsing command line args and configuration files.
-func LvlFromString(lvlString string) (Lvl, error) {
- switch lvlString {
- case "trace", "trce":
- return LvlTrace, nil
- case "debug", "dbug":
- return LvlDebug, nil
- case "info":
- return LvlInfo, nil
- case "warn":
- return LvlWarn, nil
- case "error", "eror":
- return LvlError, nil
- case "crit":
- return LvlCrit, nil
- default:
- return LvlDebug, fmt.Errorf("unknown level: %v", lvlString)
- }
-}
-
-// A Record is what a Logger asks its handler to write
-type Record struct {
- Time time.Time
- Lvl Lvl
- Msg string
- Ctx []interface{}
- Call stack.Call
- KeyNames RecordKeyNames
-}
-
-// RecordKeyNames gets stored in a Record when the write function is executed.
-type RecordKeyNames struct {
- Time string
- Msg string
- Lvl string
- Ctx string
-}
-
// A Logger writes key/value pairs to a Handler
type Logger interface {
- // New returns a new Logger that has this logger's context plus the given context
+ // With returns a new Logger that has this logger's attributes plus the given attributes
+ With(ctx ...interface{}) Logger
+
+ // With returns a new Logger that has this logger's attributes plus the given attributes. Identical to 'With'.
New(ctx ...interface{}) Logger
- // GetHandler gets the handler associated with the logger.
- GetHandler() Handler
+ // Log logs a message at the specified level with context key/value pairs
+ Log(level slog.Level, msg string, ctx ...interface{})
- // SetHandler updates the logger to write records to the specified handler.
- SetHandler(h Handler)
-
- // Log a message at the trace level with context key/value pairs
- //
- // # Usage
- //
- // log.Trace("msg")
- // log.Trace("msg", "key1", val1)
- // log.Trace("msg", "key1", val1, "key2", val2)
+ // Trace log a message at the trace level with context key/value pairs
Trace(msg string, ctx ...interface{})
- // Log a message at the debug level with context key/value pairs
- //
- // # Usage Examples
- //
- // log.Debug("msg")
- // log.Debug("msg", "key1", val1)
- // log.Debug("msg", "key1", val1, "key2", val2)
+ // Debug logs a message at the debug level with context key/value pairs
Debug(msg string, ctx ...interface{})
- // Log a message at the info level with context key/value pairs
- //
- // # Usage Examples
- //
- // log.Info("msg")
- // log.Info("msg", "key1", val1)
- // log.Info("msg", "key1", val1, "key2", val2)
+ // Info logs a message at the info level with context key/value pairs
Info(msg string, ctx ...interface{})
- // Log a message at the warn level with context key/value pairs
- //
- // # Usage Examples
- //
- // log.Warn("msg")
- // log.Warn("msg", "key1", val1)
- // log.Warn("msg", "key1", val1, "key2", val2)
+ // Warn logs a message at the warn level with context key/value pairs
Warn(msg string, ctx ...interface{})
- // Log a message at the error level with context key/value pairs
- //
- // # Usage Examples
- //
- // log.Error("msg")
- // log.Error("msg", "key1", val1)
- // log.Error("msg", "key1", val1, "key2", val2)
+ // Error logs a message at the error level with context key/value pairs
Error(msg string, ctx ...interface{})
- // Log a message at the crit level with context key/value pairs, and then exit.
- //
- // # Usage Examples
- //
- // log.Crit("msg")
- // log.Crit("msg", "key1", val1)
- // log.Crit("msg", "key1", val1, "key2", val2)
+ // Crit logs a message at the crit level with context key/value pairs, and exits
Crit(msg string, ctx ...interface{})
+
+ // Write logs a message at the specified level
+ Write(level slog.Level, msg string, attrs ...any)
+
+ // Enabled reports whether l emits log records at the given context and level.
+ Enabled(ctx context.Context, level slog.Level) bool
}
type logger struct {
- ctx []interface{}
- h *swapHandler
+ inner *slog.Logger
}
-func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) {
- record := &Record{
- Time: time.Now(),
- Lvl: lvl,
- Msg: msg,
- Ctx: newContext(l.ctx, ctx),
- KeyNames: RecordKeyNames{
- Time: timeKey,
- Msg: msgKey,
- Lvl: lvlKey,
- Ctx: ctxKey,
- },
+// NewLogger returns a logger with the specified handler set
+func NewLogger(h slog.Handler) Logger {
+ return &logger{
+ slog.New(h),
}
- if stackEnabled.Load() {
- record.Call = stack.Caller(skip)
+}
+
+// write logs a message at the specified level:
+func (l *logger) Write(level slog.Level, msg string, attrs ...any) {
+ if !l.inner.Enabled(context.Background(), level) {
+ return
}
- l.h.Log(record)
+
+ var pcs [1]uintptr
+ runtime.Callers(3, pcs[:])
+
+ if len(attrs)%2 != 0 {
+ attrs = append(attrs, nil, errorKey, "Normalized odd number of arguments by adding nil")
+ }
+ r := slog.NewRecord(time.Now(), level, msg, pcs[0])
+ r.Add(attrs...)
+ l.inner.Handler().Handle(context.Background(), r)
+}
+
+func (l *logger) Log(level slog.Level, msg string, attrs ...any) {
+ l.Write(level, msg, attrs...)
+}
+
+func (l *logger) With(ctx ...interface{}) Logger {
+ return &logger{l.inner.With(ctx...)}
}
func (l *logger) New(ctx ...interface{}) Logger {
- child := &logger{newContext(l.ctx, ctx), new(swapHandler)}
- child.SetHandler(l.h)
- return child
+ return l.With(ctx...)
}
-func newContext(prefix []interface{}, suffix []interface{}) []interface{} {
- normalizedSuffix := normalize(suffix)
- newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix))
- n := copy(newCtx, prefix)
- copy(newCtx[n:], normalizedSuffix)
- return newCtx
+// Enabled reports whether l emits log records at the given context and level.
+func (l *logger) Enabled(ctx context.Context, level slog.Level) bool {
+ return l.inner.Enabled(ctx, level)
}
func (l *logger) Trace(msg string, ctx ...interface{}) {
- l.write(msg, LvlTrace, ctx, skipLevel)
+ l.Write(LevelTrace, msg, ctx...)
}
func (l *logger) Debug(msg string, ctx ...interface{}) {
- l.write(msg, LvlDebug, ctx, skipLevel)
+ l.Write(slog.LevelDebug, msg, ctx...)
}
func (l *logger) Info(msg string, ctx ...interface{}) {
- l.write(msg, LvlInfo, ctx, skipLevel)
+ l.Write(slog.LevelInfo, msg, ctx...)
}
-func (l *logger) Warn(msg string, ctx ...interface{}) {
- l.write(msg, LvlWarn, ctx, skipLevel)
+func (l *logger) Warn(msg string, ctx ...any) {
+ l.Write(slog.LevelWarn, msg, ctx...)
}
func (l *logger) Error(msg string, ctx ...interface{}) {
- l.write(msg, LvlError, ctx, skipLevel)
+ l.Write(slog.LevelError, msg, ctx...)
}
func (l *logger) Crit(msg string, ctx ...interface{}) {
- l.write(msg, LvlCrit, ctx, skipLevel)
+ l.Write(LevelCrit, msg, ctx...)
os.Exit(1)
}
-
-func (l *logger) GetHandler() Handler {
- return l.h.Get()
-}
-
-func (l *logger) SetHandler(h Handler) {
- l.h.Swap(h)
-}
-
-func normalize(ctx []interface{}) []interface{} {
- // if the caller passed a Ctx object, then expand it
- if len(ctx) == 1 {
- if ctxMap, ok := ctx[0].(Ctx); ok {
- ctx = ctxMap.toArray()
- }
- }
-
- // ctx needs to be even because it's a series of key/value pairs
- // no one wants to check for errors on logging functions,
- // so instead of erroring on bad input, we'll just make sure
- // that things are the right length and users can fix bugs
- // when they see the output looks wrong
- if len(ctx)%2 != 0 {
- ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil")
- }
-
- return ctx
-}
-
-// Lazy allows you to defer calculation of a logged value that is expensive
-// to compute until it is certain that it must be evaluated with the given filters.
-//
-// Lazy may also be used in conjunction with a Logger's New() function
-// to generate a child logger which always reports the current value of changing
-// state.
-//
-// You may wrap any function which takes no arguments to Lazy. It may return any
-// number of values of any type.
-type Lazy struct {
- Fn interface{}
-}
-
-// Ctx is a map of key/value pairs to pass as context to a log function
-// Use this only if you really need greater safety around the arguments you pass
-// to the logging functions.
-type Ctx map[string]interface{}
-
-func (c Ctx) toArray() []interface{} {
- arr := make([]interface{}, len(c)*2)
-
- i := 0
- for k, v := range c {
- arr[i] = k
- arr[i+1] = v
- i += 2
- }
-
- return arr
-}
diff --git a/log/logger_test.go b/log/logger_test.go
index 2e59b3fdf..a633f5ad7 100644
--- a/log/logger_test.go
+++ b/log/logger_test.go
@@ -2,51 +2,26 @@ package log
import (
"bytes"
+ "fmt"
+ "io"
+ "math/big"
"os"
"strings"
"testing"
-)
+ "time"
-// TestLoggingWithTrace checks that if BackTraceAt is set, then the
-// gloghandler is capable of spitting out a stacktrace
-func TestLoggingWithTrace(t *testing.T) {
- defer stackEnabled.Store(stackEnabled.Load())
- out := new(bytes.Buffer)
- logger := New()
- {
- glog := NewGlogHandler(StreamHandler(out, TerminalFormat(false)))
- glog.Verbosity(LvlTrace)
- if err := glog.BacktraceAt("logger_test.go:24"); err != nil {
- t.Fatal(err)
- }
- logger.SetHandler(glog)
- }
- logger.Trace("a message", "foo", "bar") // Will be bumped to INFO
- have := out.String()
- if !strings.HasPrefix(have, "INFO") {
- t.Fatalf("backtraceat should bump level to info: %s", have)
- }
- // The timestamp is locale-dependent, so we want to trim that off
- // "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
- have = strings.Split(have, "]")[1]
- wantPrefix := " a message\n\ngoroutine"
- if !strings.HasPrefix(have, wantPrefix) {
- t.Errorf("\nhave: %q\nwant: %q\n", have, wantPrefix)
- }
-}
+ "github.com/holiman/uint256"
+ "golang.org/x/exp/slog"
+)
// TestLoggingWithVmodule checks that vmodule works.
func TestLoggingWithVmodule(t *testing.T) {
- defer stackEnabled.Store(stackEnabled.Load())
out := new(bytes.Buffer)
- logger := New()
- {
- glog := NewGlogHandler(StreamHandler(out, TerminalFormat(false)))
- glog.Verbosity(LvlCrit)
- logger.SetHandler(glog)
- logger.Warn("This should not be seen", "ignored", "true")
- glog.Vmodule("logger_test.go=5")
- }
+ glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false))
+ glog.Verbosity(LevelCrit)
+ logger := NewLogger(glog)
+ logger.Warn("This should not be seen", "ignored", "true")
+ glog.Vmodule("logger_test.go=5")
logger.Trace("a message", "foo", "bar")
have := out.String()
// The timestamp is locale-dependent, so we want to trim that off
@@ -58,10 +33,140 @@ func TestLoggingWithVmodule(t *testing.T) {
}
}
+func TestTerminalHandlerWithAttrs(t *testing.T) {
+ out := new(bytes.Buffer)
+ glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false).WithAttrs([]slog.Attr{slog.String("baz", "bat")}))
+ glog.Verbosity(LevelTrace)
+ logger := NewLogger(glog)
+ logger.Trace("a message", "foo", "bar")
+ have := out.String()
+ // The timestamp is locale-dependent, so we want to trim that off
+ // "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
+ have = strings.Split(have, "]")[1]
+ want := " a message baz=bat foo=bar\n"
+ if have != want {
+ t.Errorf("\nhave: %q\nwant: %q\n", have, want)
+ }
+}
+
func BenchmarkTraceLogging(b *testing.B) {
- Root().SetHandler(LvlFilterHandler(LvlInfo, StreamHandler(os.Stderr, TerminalFormat(true))))
+ SetDefault(NewLogger(NewTerminalHandler(os.Stderr, true)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
Trace("a message", "v", i)
}
}
+
+func BenchmarkTerminalHandler(b *testing.B) {
+ l := NewLogger(NewTerminalHandler(io.Discard, false))
+ benchmarkLogger(b, l)
+}
+func BenchmarkLogfmtHandler(b *testing.B) {
+ l := NewLogger(LogfmtHandler(io.Discard))
+ benchmarkLogger(b, l)
+}
+
+func BenchmarkJSONHandler(b *testing.B) {
+ l := NewLogger(JSONHandler(io.Discard))
+ benchmarkLogger(b, l)
+}
+
+func benchmarkLogger(b *testing.B, l Logger) {
+ var (
+ bb = make([]byte, 10)
+ tt = time.Now()
+ bigint = big.NewInt(100)
+ nilbig *big.Int
+ err = fmt.Errorf("Oh nooes it's crap")
+ )
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ l.Info("This is a message",
+ "foo", int16(i),
+ "bytes", bb,
+ "bonk", "a string with text",
+ "time", tt,
+ "bigint", bigint,
+ "nilbig", nilbig,
+ "err", err)
+ }
+ b.StopTimer()
+}
+
+func TestLoggerOutput(t *testing.T) {
+ type custom struct {
+ A string
+ B int8
+ }
+ var (
+ customA = custom{"Foo", 12}
+ customB = custom{"Foo\nLinebreak", 122}
+ bb = make([]byte, 10)
+ tt = time.Time{}
+ bigint = big.NewInt(100)
+ nilbig *big.Int
+ err = fmt.Errorf("Oh nooes it's crap")
+ smallUint = uint256.NewInt(500_000)
+ bigUint = &uint256.Int{0xff, 0xff, 0xff, 0xff}
+ )
+
+ out := new(bytes.Buffer)
+ glogHandler := NewGlogHandler(NewTerminalHandler(out, false))
+ glogHandler.Verbosity(LevelInfo)
+ NewLogger(glogHandler).Info("This is a message",
+ "foo", int16(123),
+ "bytes", bb,
+ "bonk", "a string with text",
+ "time", tt,
+ "bigint", bigint,
+ "nilbig", nilbig,
+ "err", err,
+ "struct", customA,
+ "struct", customB,
+ "ptrstruct", &customA,
+ "smalluint", smallUint,
+ "bigUint", bigUint)
+
+ have := out.String()
+ t.Logf("output %v", out.String())
+ want := `INFO [11-07|19:14:33.821] This is a message foo=123 bytes="[0 0 0 0 0 0 0 0 0 0]" bonk="a string with text" time=0001-01-01T00:00:00+0000 bigint=100 nilbig= err="Oh nooes it's crap" struct="{A:Foo B:12}" struct="{A:Foo\nLinebreak B:122}" ptrstruct="&{A:Foo B:12}" smalluint=500,000 bigUint=1,600,660,942,523,603,594,864,898,306,482,794,244,293,965,082,972,225,630,372,095
+`
+ if !bytes.Equal([]byte(have)[25:], []byte(want)[25:]) {
+ t.Errorf("Error\nhave: %q\nwant: %q", have, want)
+ }
+}
+
+const termTimeFormat = "01-02|15:04:05.000"
+
+func BenchmarkAppendFormat(b *testing.B) {
+ var now = time.Now()
+ b.Run("fmt time.Format", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ fmt.Fprintf(io.Discard, "%s", now.Format(termTimeFormat))
+ }
+ })
+ b.Run("time.AppendFormat", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ now.AppendFormat(nil, termTimeFormat)
+ }
+ })
+ var buf = new(bytes.Buffer)
+ b.Run("time.Custom", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ writeTimeTermFormat(buf, now)
+ buf.Reset()
+ }
+ })
+}
+
+func TestTermTimeFormat(t *testing.T) {
+ var now = time.Now()
+ want := now.AppendFormat(nil, termTimeFormat)
+ var b = new(bytes.Buffer)
+ writeTimeTermFormat(b, now)
+ have := b.Bytes()
+ if !bytes.Equal(have, want) {
+ t.Errorf("have != want\nhave: %q\nwant: %q\n", have, want)
+ }
+}
diff --git a/log/root.go b/log/root.go
index 5a41723c3..8662d8706 100644
--- a/log/root.go
+++ b/log/root.go
@@ -2,31 +2,32 @@ package log
import (
"os"
+ "sync/atomic"
+
+ "golang.org/x/exp/slog"
)
-var (
- root = &logger{[]interface{}{}, new(swapHandler)}
- StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat())
- StderrHandler = StreamHandler(os.Stderr, LogfmtFormat())
-)
+var root atomic.Value
func init() {
- root.SetHandler(DiscardHandler())
+ root.Store(&logger{slog.New(DiscardHandler())})
}
-// New returns a new logger with the given context.
-// New is a convenient alias for Root().New
-func New(ctx ...interface{}) Logger {
- return root.New(ctx...)
+// SetDefault sets the default global logger
+func SetDefault(l Logger) {
+ root.Store(l)
+ if lg, ok := l.(*logger); ok {
+ slog.SetDefault(lg.inner)
+ }
}
// Root returns the root logger
func Root() Logger {
- return root
+ return root.Load().(Logger)
}
// The following functions bypass the exported logger methods (logger.Debug,
-// etc.) to keep the call depth the same for all paths to logger.write so
+// etc.) to keep the call depth the same for all paths to logger.Write so
// runtime.Caller(2) always refers to the call site in client code.
// Trace is a convenient alias for Root().Trace
@@ -39,7 +40,7 @@ func Root() Logger {
// log.Trace("msg", "key1", val1)
// log.Trace("msg", "key1", val1, "key2", val2)
func Trace(msg string, ctx ...interface{}) {
- root.write(msg, LvlTrace, ctx, skipLevel)
+ Root().Write(LevelTrace, msg, ctx...)
}
// Debug is a convenient alias for Root().Debug
@@ -52,7 +53,7 @@ func Trace(msg string, ctx ...interface{}) {
// log.Debug("msg", "key1", val1)
// log.Debug("msg", "key1", val1, "key2", val2)
func Debug(msg string, ctx ...interface{}) {
- root.write(msg, LvlDebug, ctx, skipLevel)
+ Root().Write(slog.LevelDebug, msg, ctx...)
}
// Info is a convenient alias for Root().Info
@@ -65,7 +66,7 @@ func Debug(msg string, ctx ...interface{}) {
// log.Info("msg", "key1", val1)
// log.Info("msg", "key1", val1, "key2", val2)
func Info(msg string, ctx ...interface{}) {
- root.write(msg, LvlInfo, ctx, skipLevel)
+ Root().Write(slog.LevelInfo, msg, ctx...)
}
// Warn is a convenient alias for Root().Warn
@@ -78,7 +79,7 @@ func Info(msg string, ctx ...interface{}) {
// log.Warn("msg", "key1", val1)
// log.Warn("msg", "key1", val1, "key2", val2)
func Warn(msg string, ctx ...interface{}) {
- root.write(msg, LvlWarn, ctx, skipLevel)
+ Root().Write(slog.LevelWarn, msg, ctx...)
}
// Error is a convenient alias for Root().Error
@@ -91,7 +92,7 @@ func Warn(msg string, ctx ...interface{}) {
// log.Error("msg", "key1", val1)
// log.Error("msg", "key1", val1, "key2", val2)
func Error(msg string, ctx ...interface{}) {
- root.write(msg, LvlError, ctx, skipLevel)
+ Root().Write(slog.LevelError, msg, ctx...)
}
// Crit is a convenient alias for Root().Crit
@@ -104,15 +105,12 @@ func Error(msg string, ctx ...interface{}) {
// log.Crit("msg", "key1", val1)
// log.Crit("msg", "key1", val1, "key2", val2)
func Crit(msg string, ctx ...interface{}) {
- root.write(msg, LvlCrit, ctx, skipLevel)
+ Root().Write(LevelCrit, msg, ctx...)
os.Exit(1)
}
-// Output is a convenient alias for write, allowing for the modification of
-// the calldepth (number of stack frames to skip).
-// calldepth influences the reported line number of the log message.
-// A calldepth of zero reports the immediate caller of Output.
-// Non-zero calldepth skips as many stack frames.
-func Output(msg string, lvl Lvl, calldepth int, ctx ...interface{}) {
- root.write(msg, lvl, ctx, calldepth+skipLevel)
+// New returns a new logger with the given context.
+// New is a convenient alias for Root().New
+func New(ctx ...interface{}) Logger {
+ return Root().With(ctx...)
}
diff --git a/log/syslog.go b/log/syslog.go
deleted file mode 100644
index 451d831b6..000000000
--- a/log/syslog.go
+++ /dev/null
@@ -1,58 +0,0 @@
-//go:build !windows && !plan9
-// +build !windows,!plan9
-
-package log
-
-import (
- "log/syslog"
- "strings"
-)
-
-// SyslogHandler opens a connection to the system syslog daemon by calling
-// syslog.New and writes all records to it.
-func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) {
- wr, err := syslog.New(priority, tag)
- return sharedSyslog(fmtr, wr, err)
-}
-
-// SyslogNetHandler opens a connection to a log daemon over the network and writes
-// all log records to it.
-func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) {
- wr, err := syslog.Dial(net, addr, priority, tag)
- return sharedSyslog(fmtr, wr, err)
-}
-
-func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) {
- if err != nil {
- return nil, err
- }
- h := FuncHandler(func(r *Record) error {
- var syslogFn = sysWr.Info
- switch r.Lvl {
- case LvlCrit:
- syslogFn = sysWr.Crit
- case LvlError:
- syslogFn = sysWr.Err
- case LvlWarn:
- syslogFn = sysWr.Warning
- case LvlInfo:
- syslogFn = sysWr.Info
- case LvlDebug:
- syslogFn = sysWr.Debug
- case LvlTrace:
- syslogFn = func(m string) error { return nil } // There's no syslog level for trace
- }
-
- s := strings.TrimSpace(string(fmtr.Format(r)))
- return syslogFn(s)
- })
- return LazyHandler(&closingHandler{sysWr, h}), nil
-}
-
-func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler {
- return must(SyslogHandler(priority, tag, fmtr))
-}
-
-func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler {
- return must(SyslogNetHandler(net, addr, priority, tag, fmtr))
-}
diff --git a/metrics/disk_nop.go b/metrics/disk_nop.go
index 58fa4e02f..41bbe9adb 100644
--- a/metrics/disk_nop.go
+++ b/metrics/disk_nop.go
@@ -23,5 +23,5 @@ import "errors"
// ReadDiskStats retrieves the disk IO stats belonging to the current process.
func ReadDiskStats(stats *DiskStats) error {
- return errors.New("Not implemented")
+ return errors.New("not implemented")
}
diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go
index f0ac7ea5e..194a18821 100644
--- a/metrics/gauge_float64_test.go
+++ b/metrics/gauge_float64_test.go
@@ -36,7 +36,7 @@ func TestGaugeFloat64Snapshot(t *testing.T) {
g.Update(47.0)
snapshot := g.Snapshot()
g.Update(float64(0))
- if v := snapshot.Value(); 47.0 != v {
+ if v := snapshot.Value(); v != 47.0 {
t.Errorf("g.Value(): 47.0 != %v\n", v)
}
}
@@ -45,7 +45,7 @@ func TestGetOrRegisterGaugeFloat64(t *testing.T) {
r := NewRegistry()
NewRegisteredGaugeFloat64("foo", r).Update(47.0)
t.Logf("registry: %v", r)
- if g := GetOrRegisterGaugeFloat64("foo", r).Snapshot(); 47.0 != g.Value() {
+ if g := GetOrRegisterGaugeFloat64("foo", r).Snapshot(); g.Value() != 47.0 {
t.Fatal(g)
}
}
diff --git a/metrics/timer.go b/metrics/timer.go
index 576ad8aa3..bb8def82f 100644
--- a/metrics/timer.go
+++ b/metrics/timer.go
@@ -106,20 +106,18 @@ func (t *StandardTimer) Time(f func()) {
t.Update(time.Since(ts))
}
-// Record the duration of an event.
+// Record the duration of an event, in nanoseconds.
func (t *StandardTimer) Update(d time.Duration) {
t.mutex.Lock()
defer t.mutex.Unlock()
- t.histogram.Update(int64(d))
+ t.histogram.Update(d.Nanoseconds())
t.meter.Mark(1)
}
// Record the duration of an event that started at a time and ends now.
+// The record uses nanoseconds.
func (t *StandardTimer) UpdateSince(ts time.Time) {
- t.mutex.Lock()
- defer t.mutex.Unlock()
- t.histogram.Update(int64(time.Since(ts)))
- t.meter.Mark(1)
+ t.Update(time.Since(ts))
}
// timerSnapshot is a read-only copy of another Timer.
diff --git a/miner/miner_test.go b/miner/miner_test.go
index 36d5166c6..411d6026c 100644
--- a/miner/miner_test.go
+++ b/miner/miner_test.go
@@ -99,6 +99,7 @@ func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent)
}
func TestMiner(t *testing.T) {
+ t.Parallel()
miner, mux, cleanup := createMiner(t)
defer cleanup(false)
@@ -128,6 +129,7 @@ func TestMiner(t *testing.T) {
// An initial FailedEvent should allow mining to stop on a subsequent
// downloader StartEvent.
func TestMinerDownloaderFirstFails(t *testing.T) {
+ t.Parallel()
miner, mux, cleanup := createMiner(t)
defer cleanup(false)
@@ -161,6 +163,7 @@ func TestMinerDownloaderFirstFails(t *testing.T) {
}
func TestMinerStartStopAfterDownloaderEvents(t *testing.T) {
+ t.Parallel()
miner, mux, cleanup := createMiner(t)
defer cleanup(false)
@@ -185,6 +188,7 @@ func TestMinerStartStopAfterDownloaderEvents(t *testing.T) {
}
func TestStartWhileDownload(t *testing.T) {
+ t.Parallel()
miner, mux, cleanup := createMiner(t)
defer cleanup(false)
waitForMiningState(t, miner, false)
@@ -199,6 +203,7 @@ func TestStartWhileDownload(t *testing.T) {
}
func TestStartStopMiner(t *testing.T) {
+ t.Parallel()
miner, _, cleanup := createMiner(t)
defer cleanup(false)
waitForMiningState(t, miner, false)
@@ -209,6 +214,7 @@ func TestStartStopMiner(t *testing.T) {
}
func TestCloseMiner(t *testing.T) {
+ t.Parallel()
miner, _, cleanup := createMiner(t)
defer cleanup(true)
waitForMiningState(t, miner, false)
@@ -222,6 +228,7 @@ func TestCloseMiner(t *testing.T) {
// TestMinerSetEtherbase checks that etherbase becomes set even if mining isn't
// possible at the moment
func TestMinerSetEtherbase(t *testing.T) {
+ t.Parallel()
miner, mux, cleanup := createMiner(t)
defer cleanup(false)
miner.Start()
diff --git a/miner/ordering_test.go b/miner/ordering_test.go
index 59d478274..e5868d7a0 100644
--- a/miner/ordering_test.go
+++ b/miner/ordering_test.go
@@ -30,10 +30,12 @@ import (
)
func TestTransactionPriceNonceSortLegacy(t *testing.T) {
+ t.Parallel()
testTransactionPriceNonceSort(t, nil)
}
func TestTransactionPriceNonceSort1559(t *testing.T) {
+ t.Parallel()
testTransactionPriceNonceSort(t, big.NewInt(0))
testTransactionPriceNonceSort(t, big.NewInt(5))
testTransactionPriceNonceSort(t, big.NewInt(50))
@@ -138,6 +140,7 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
// Tests that if multiple transactions have the same price, the ones seen earlier
// are prioritized to avoid network spam attacks aiming for a specific ordering.
func TestTransactionTimeSort(t *testing.T) {
+ t.Parallel()
// Generate a batch of accounts to start with
keys := make([]*ecdsa.PrivateKey, 5)
for i := 0; i < len(keys); i++ {
diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go
index 6f5736344..928363522 100644
--- a/miner/payload_building_test.go
+++ b/miner/payload_building_test.go
@@ -30,6 +30,7 @@ import (
)
func TestBuildPayload(t *testing.T) {
+ t.Parallel()
var (
db = rawdb.NewMemoryDatabase()
recipient = common.HexToAddress("0xdeadbeef")
@@ -82,6 +83,7 @@ func TestBuildPayload(t *testing.T) {
}
func TestPayloadId(t *testing.T) {
+ t.Parallel()
ids := make(map[string]int)
for i, tt := range []*BuildPayloadArgs{
{
diff --git a/miner/stress/clique/main.go b/miner/stress/clique/main.go
index 7b29e63df..13336cd83 100644
--- a/miner/stress/clique/main.go
+++ b/miner/stress/clique/main.go
@@ -45,7 +45,7 @@ import (
)
func main() {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
fdlimit.Raise(2048)
// Generate a batch of accounts to seal and fund with
diff --git a/miner/worker.go b/miner/worker.go
index f68070281..2ed91cc18 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -1074,7 +1074,7 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) {
case err == nil:
// The entire block is filled, decrease resubmit interval in case
// of current interval is larger than the user-specified one.
- w.resubmitAdjustCh <- &intervalAdjust{inc: false}
+ w.adjustResubmitInterval(&intervalAdjust{inc: false})
case errors.Is(err, errBlockInterruptedByRecommit):
// Notify resubmit loop to increase resubmitting interval if the
@@ -1084,10 +1084,10 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) {
if ratio < 0.1 {
ratio = 0.1
}
- w.resubmitAdjustCh <- &intervalAdjust{
+ w.adjustResubmitInterval(&intervalAdjust{
ratio: ratio,
inc: true,
- }
+ })
case errors.Is(err, errBlockInterruptedByNewHead):
// If the block building is interrupted by newhead event, discard it
@@ -1169,6 +1169,15 @@ func (w *worker) isTTDReached(header *types.Header) bool {
return td != nil && ttd != nil && td.Cmp(ttd) >= 0
}
+// adjustResubmitInterval adjusts the resubmit interval.
+func (w *worker) adjustResubmitInterval(message *intervalAdjust) {
+ select {
+ case w.resubmitAdjustCh <- message:
+ default:
+ log.Warn("the resubmitAdjustCh is full, discard the message")
+ }
+}
+
// copyReceipts makes a deep copy of the given receipts.
func copyReceipts(receipts []*types.Receipt) []*types.Receipt {
result := make([]*types.Receipt, len(receipts))
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 9c4694c0e..59fbbbcdc 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -167,6 +167,7 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens
}
func TestGenerateAndImportBlock(t *testing.T) {
+ t.Parallel()
var (
db = rawdb.NewMemoryDatabase()
config = *params.AllCliqueProtocolChanges
@@ -210,9 +211,11 @@ func TestGenerateAndImportBlock(t *testing.T) {
}
func TestEmptyWorkEthash(t *testing.T) {
+ t.Parallel()
testEmptyWork(t, ethashChainConfig, ethash.NewFaker())
}
func TestEmptyWorkClique(t *testing.T) {
+ t.Parallel()
testEmptyWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()))
}
@@ -252,10 +255,12 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
}
func TestAdjustIntervalEthash(t *testing.T) {
+ t.Parallel()
testAdjustInterval(t, ethashChainConfig, ethash.NewFaker())
}
func TestAdjustIntervalClique(t *testing.T) {
+ t.Parallel()
testAdjustInterval(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()))
}
@@ -346,14 +351,17 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co
}
func TestGetSealingWorkEthash(t *testing.T) {
+ t.Parallel()
testGetSealingWork(t, ethashChainConfig, ethash.NewFaker())
}
func TestGetSealingWorkClique(t *testing.T) {
+ t.Parallel()
testGetSealingWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()))
}
func TestGetSealingWorkPostMerge(t *testing.T) {
+ t.Parallel()
local := new(params.ChainConfig)
*local = *ethashChainConfig
local.TerminalTotalDifficulty = big.NewInt(0)
diff --git a/oss-fuzz.sh b/oss-fuzz.sh
index 55660d08e..8978de70d 100644
--- a/oss-fuzz.sh
+++ b/oss-fuzz.sh
@@ -48,39 +48,27 @@ DOG
cd -
}
-function build_native_go_fuzzer() {
- fuzzer=$1
- function=$2
- path=$3
- tags="-tags gofuzz"
-
- if [[ $SANITIZER == *coverage* ]]; then
- coverbuild $path $function $fuzzer $coverpkg
- else
- go-118-fuzz-build $tags -o $fuzzer.a -func $function $path
- $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer
- fi
-}
-
function compile_fuzzer() {
- path=$GOPATH/src/github.com/ethereum/go-ethereum/$1
+ package=$1
function=$2
fuzzer=$3
+ file=$4
+
+ path=$GOPATH/src/$package
echo "Building $fuzzer"
cd $path
# Install build dependencies
- go install github.com/AdamKorcz/go-118-fuzz-build@latest
- go get github.com/AdamKorcz/go-118-fuzz-build/testing
+ go mod tidy
+ go get github.com/holiman/gofuzz-shim/testing
- # Test if file contains a line with "func $function(" and "testing.F".
- if [ $(grep -r "func $function(" $path | grep "testing.F" | wc -l) -eq 1 ]
- then
- build_native_go_fuzzer $fuzzer $function $path
- else
- echo "Could not find the function: func ${function}(f *testing.F)"
- fi
+ if [[ $SANITIZER == *coverage* ]]; then
+ coverbuild $path $function $fuzzer $coverpkg
+ else
+ gofuzz-shim --func $function --package $package -f $file -o $fuzzer.a
+ $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer
+ fi
## Check if there exists a seed corpus file
corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip"
@@ -92,42 +80,140 @@ function compile_fuzzer() {
cd -
}
-compile_fuzzer tests/fuzzers/bitutil FuzzEncoder fuzzBitutilEncoder
-compile_fuzzer tests/fuzzers/bitutil FuzzDecoder fuzzBitutilDecoder
-compile_fuzzer tests/fuzzers/bn256 FuzzAdd fuzzBn256Add
-compile_fuzzer tests/fuzzers/bn256 FuzzMul fuzzBn256Mul
-compile_fuzzer tests/fuzzers/bn256 FuzzPair fuzzBn256Pair
-compile_fuzzer tests/fuzzers/runtime Fuzz fuzzVmRuntime
-compile_fuzzer tests/fuzzers/keystore Fuzz fuzzKeystore
-compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher
-compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp
-compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie
-compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie
-compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty
-compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi
-compile_fuzzer tests/fuzzers/les Fuzz fuzzLes
-compile_fuzzer tests/fuzzers/secp256k1 Fuzz fuzzSecp256k1
-compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool
+go install github.com/holiman/gofuzz-shim@latest
+repo=$GOPATH/src/github.com/ethereum/go-ethereum
+compile_fuzzer github.com/ethereum/go-ethereum/accounts/abi \
+ FuzzABI fuzzAbi \
+ $repo/accounts/abi/abifuzzer_test.go
-compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add
-compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul
-compile_fuzzer tests/fuzzers/bls12381 FuzzG1MultiExp fuzz_g1_multiexp
-compile_fuzzer tests/fuzzers/bls12381 FuzzG2Add fuzz_g2_add
-compile_fuzzer tests/fuzzers/bls12381 FuzzG2Mul fuzz_g2_mul
-compile_fuzzer tests/fuzzers/bls12381 FuzzG2MultiExp fuzz_g2_multiexp
-compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing
-compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1
-compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2
+compile_fuzzer github.com/ethereum/go-ethereum/common/bitutil \
+ FuzzEncoder fuzzBitutilEncoder \
+ $repo/common/bitutil/compress_test.go
-compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1Add fuzz_cross_g1_add
-compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1MultiExp fuzz_cross_g1_multiexp
-compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG2Add fuzz_cross_g2_add
-compile_fuzzer tests/fuzzers/bls12381 FuzzCrossPairing fuzz_cross_pairing
+compile_fuzzer github.com/ethereum/go-ethereum/common/bitutil \
+ FuzzDecoder fuzzBitutilDecoder \
+ $repo/common/bitutil/compress_test.go
-compile_fuzzer tests/fuzzers/snap FuzzARange fuzz_account_range
-compile_fuzzer tests/fuzzers/snap FuzzSRange fuzz_storage_range
-compile_fuzzer tests/fuzzers/snap FuzzByteCodes fuzz_byte_codes
-compile_fuzzer tests/fuzzers/snap FuzzTrieNodes fuzz_trie_nodes
+compile_fuzzer github.com/ethereum/go-ethereum/core/vm/runtime \
+ FuzzVmRuntime fuzzVmRuntime\
+ $repo/core/vm/runtime/runtime_fuzz_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/core/vm \
+ FuzzPrecompiledContracts fuzzPrecompiledContracts\
+ $repo/core/vm/contracts_fuzz_test.go,$repo/core/vm/contracts_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/core/types \
+ FuzzRLP fuzzRlp \
+ $repo/core/types/rlp_fuzzer_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/crypto/blake2b \
+ Fuzz fuzzBlake2b \
+ $repo/crypto/blake2b/blake2b_f_fuzz_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/accounts/keystore \
+ FuzzPassword fuzzKeystore \
+ $repo/accounts/keystore/keystore_fuzzing_test.go
+
+pkg=$repo/trie/
+compile_fuzzer github.com/ethereum/go-ethereum/trie \
+ FuzzTrie fuzzTrie \
+ $pkg/trie_test.go,$pkg/database_test.go,$pkg/tracer_test.go,$pkg/proof_test.go,$pkg/iterator_test.go,$pkg/sync_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/trie \
+ FuzzStackTrie fuzzStackTrie \
+ $pkg/stacktrie_fuzzer_test.go,$pkg/iterator_test.go,$pkg/trie_test.go,$pkg/database_test.go,$pkg/tracer_test.go,$pkg/proof_test.go,$pkg/sync_test.go
+
+#compile_fuzzer tests/fuzzers/snap FuzzARange fuzz_account_range
+compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
+ FuzzARange fuzz_account_range \
+ $repo/eth/protocols/snap/handler_fuzzing_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
+ FuzzSRange fuzz_storage_range \
+ $repo/eth/protocols/snap/handler_fuzzing_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
+ FuzzByteCodes fuzz_byte_codes \
+ $repo/eth/protocols/snap/handler_fuzzing_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \
+ FuzzTrieNodes fuzz_trie_nodes\
+ $repo/eth/protocols/snap/handler_fuzzing_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \
+ FuzzAdd fuzzBn256Add\
+ $repo/tests/fuzzers/bn256/bn256_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \
+ FuzzMul fuzzBn256Mul \
+ $repo/tests/fuzzers/bn256/bn256_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \
+ FuzzPair fuzzBn256Pair \
+ $repo/tests/fuzzers/bn256/bn256_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/txfetcher \
+ Fuzz fuzzTxfetcher \
+ $repo/tests/fuzzers/txfetcher/txfetcher_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzG1Add fuzz_g1_add\
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzG1Mul fuzz_g1_mul\
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzG1MultiExp fuzz_g1_multiexp \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzG2Add fuzz_g2_add \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzG2Mul fuzz_g2_mul\
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzG2MultiExp fuzz_g2_multiexp \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzPairing fuzz_pairing \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzMapG1 fuzz_map_g1\
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzMapG2 fuzz_map_g2 \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzCrossG1Add fuzz_cross_g1_add \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzCrossG1MultiExp fuzz_cross_g1_multiexp \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzCrossG2Add fuzz_cross_g2_add \
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \
+ FuzzCrossPairing fuzz_cross_pairing\
+ $repo/tests/fuzzers/bls12381/bls12381_test.go
+
+compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/secp256k1 \
+ Fuzz fuzzSecp256k1\
+ $repo/tests/fuzzers/secp256k1/secp_test.go
+
+
+#compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool
+#compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty
+#compile_fuzzer tests/fuzzers/les Fuzz fuzzLes
-#TODO: move this to tests/fuzzers, if possible
-compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b
diff --git a/p2p/discover/table.go b/p2p/discover/table.go
index f476d2079..2b7a28708 100644
--- a/p2p/discover/table.go
+++ b/p2p/discover/table.go
@@ -23,6 +23,7 @@
package discover
import (
+ "context"
crand "crypto/rand"
"encoding/binary"
"fmt"
@@ -330,8 +331,10 @@ func (tab *Table) loadSeedNodes() {
seeds = append(seeds, tab.nursery...)
for i := range seeds {
seed := seeds[i]
- age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }}
- tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
+ if tab.log.Enabled(context.Background(), log.LevelTrace) {
+ age := time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP()))
+ tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
+ }
tab.addSeenNode(seed)
}
}
@@ -456,6 +459,26 @@ func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *
return nodes
}
+// appendLiveNodes adds nodes at the given distance to the result slice.
+func (tab *Table) appendLiveNodes(dist uint, result []*enode.Node) []*enode.Node {
+ if dist > 256 {
+ return result
+ }
+ if dist == 0 {
+ return append(result, tab.self())
+ }
+
+ tab.mutex.Lock()
+ defer tab.mutex.Unlock()
+ for _, n := range tab.bucketAtDistance(int(dist)).entries {
+ if n.livenessChecks >= 1 {
+ node := n.Node // avoid handing out pointer to struct field
+ result = append(result, &node)
+ }
+ }
+ return result
+}
+
// len returns the number of nodes in the table.
func (tab *Table) len() (n int) {
tab.mutex.Lock()
diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go
index 2781dd422..3ba342225 100644
--- a/p2p/discover/table_test.go
+++ b/p2p/discover/table_test.go
@@ -199,7 +199,7 @@ func TestTable_findnodeByID(t *testing.T) {
tab, db := newTestTable(transport)
defer db.Close()
defer tab.close()
- fillTable(tab, test.All)
+ fillTable(tab, test.All, true)
// check that closest(Target, N) returns nodes
result := tab.findnodeByID(test.Target, test.N, false).entries
diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go
index 8f3813bdc..d6309dfd6 100644
--- a/p2p/discover/table_util_test.go
+++ b/p2p/discover/table_util_test.go
@@ -109,8 +109,11 @@ func fillBucket(tab *Table, n *node) (last *node) {
// fillTable adds nodes the table to the end of their corresponding bucket
// if the bucket is not full. The caller must not hold tab.mutex.
-func fillTable(tab *Table, nodes []*node) {
+func fillTable(tab *Table, nodes []*node, setLive bool) {
for _, n := range nodes {
+ if setLive {
+ n.livenessChecks = 1
+ }
tab.addSeenNode(n)
}
}
diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go
index 1f9ad69d0..8867a5a8a 100644
--- a/p2p/discover/v4_lookup_test.go
+++ b/p2p/discover/v4_lookup_test.go
@@ -40,7 +40,7 @@ func TestUDPv4_Lookup(t *testing.T) {
}
// Seed table with initial node.
- fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))})
+ fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}, true)
// Start the lookup.
resultC := make(chan []*enode.Node, 1)
@@ -74,7 +74,7 @@ func TestUDPv4_LookupIterator(t *testing.T) {
for i := range lookupTestnet.dists[256] {
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
}
- fillTable(test.table, bootnodes)
+ fillTable(test.table, bootnodes, true)
go serveTestnet(test, lookupTestnet)
// Create the iterator and collect the nodes it yields.
@@ -109,7 +109,7 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
for i := range lookupTestnet.dists[256] {
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
}
- fillTable(test.table, bootnodes)
+ fillTable(test.table, bootnodes, true)
go serveTestnet(test, lookupTestnet)
it := test.udp.RandomNodes()
diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go
index 5add9cefa..361e37962 100644
--- a/p2p/discover/v4_udp_test.go
+++ b/p2p/discover/v4_udp_test.go
@@ -269,7 +269,7 @@ func TestUDPv4_findnode(t *testing.T) {
}
nodes.push(n, numCandidates)
}
- fillTable(test.table, nodes.entries)
+ fillTable(test.table, nodes.entries, false)
// ensure there's a bond with the test node,
// findnode won't be accepted otherwise.
@@ -557,12 +557,7 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
// Prefix logs with node ID.
lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
- lfmt := log.TerminalFormat(false)
- cfg.Log = testlog.Logger(t, log.LvlTrace)
- cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error {
- t.Logf("%s %s", lprefix, lfmt.Format(r))
- return nil
- }))
+ cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix)
// Listen.
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go
index 6ba7a9061..8b3e33d37 100644
--- a/p2p/discover/v5_udp.go
+++ b/p2p/discover/v5_udp.go
@@ -851,6 +851,7 @@ func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *ne
// collectTableNodes creates a FINDNODE result set for the given distances.
func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
+ var bn []*enode.Node
var nodes []*enode.Node
var processed = make(map[uint]struct{})
for _, dist := range distances {
@@ -859,21 +860,11 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en
if seen || dist > 256 {
continue
}
-
- // Get the nodes.
- var bn []*enode.Node
- if dist == 0 {
- bn = []*enode.Node{t.Self()}
- } else if dist <= 256 {
- t.tab.mutex.Lock()
- bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries)
- t.tab.mutex.Unlock()
- }
processed[dist] = struct{}{}
- // Apply some pre-checks to avoid sending invalid nodes.
- for _, n := range bn {
- // TODO livenessChecks > 1
+ for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) {
+ // Apply some pre-checks to avoid sending invalid nodes.
+ // Note liveness is checked by appendLiveNodes.
if netutil.CheckRelayIP(rip, n.IP()) != nil {
continue
}
diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go
index 880b71a99..eaa969ea8 100644
--- a/p2p/discover/v5_udp_test.go
+++ b/p2p/discover/v5_udp_test.go
@@ -79,12 +79,7 @@ func startLocalhostV5(t *testing.T, cfg Config) *UDPv5 {
// Prefix logs with node ID.
lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
- lfmt := log.TerminalFormat(false)
- cfg.Log = testlog.Logger(t, log.LvlTrace)
- cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error {
- t.Logf("%s %s", lprefix, lfmt.Format(r))
- return nil
- }))
+ cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix)
// Listen.
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
@@ -164,9 +159,9 @@ func TestUDPv5_findnodeHandling(t *testing.T) {
nodes253 := nodesAtDistance(test.table.self().ID(), 253, 16)
nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4)
nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10)
- fillTable(test.table, wrapNodes(nodes253))
- fillTable(test.table, wrapNodes(nodes249))
- fillTable(test.table, wrapNodes(nodes248))
+ fillTable(test.table, wrapNodes(nodes253), true)
+ fillTable(test.table, wrapNodes(nodes249), true)
+ fillTable(test.table, wrapNodes(nodes248), true)
// Requesting with distance zero should return the node's own record.
test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}})
@@ -594,7 +589,7 @@ func TestUDPv5_lookup(t *testing.T) {
// Seed table with initial node.
initialNode := lookupTestnet.node(256, 0)
- fillTable(test.table, []*node{wrapNode(initialNode)})
+ fillTable(test.table, []*node{wrapNode(initialNode)}, true)
// Start the lookup.
resultC := make(chan []*enode.Node, 1)
diff --git a/p2p/msgrate/msgrate.go b/p2p/msgrate/msgrate.go
index 4f0879224..de1a3177d 100644
--- a/p2p/msgrate/msgrate.go
+++ b/p2p/msgrate/msgrate.go
@@ -18,6 +18,7 @@
package msgrate
import (
+ "context"
"errors"
"math"
"sort"
@@ -410,7 +411,9 @@ func (t *Trackers) tune() {
t.tuned = time.Now()
t.log.Debug("Recalculated msgrate QoS values", "rtt", t.roundtrip, "confidence", t.confidence, "ttl", t.targetTimeout(), "next", t.tuned.Add(t.roundtrip))
- t.log.Trace("Debug dump of mean capacities", "caps", log.Lazy{Fn: t.meanCapacities})
+ if t.log.Enabled(context.Background(), log.LevelTrace) {
+ t.log.Trace("Debug dump of mean capacities", "caps", t.meanCapacities())
+ }
}
// detune reduces the tracker's confidence in order to make fresh measurements
diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go
index 61b692298..2aa1f8558 100644
--- a/p2p/nat/nat.go
+++ b/p2p/nat/nat.go
@@ -61,12 +61,12 @@ type Interface interface {
// "pmp:192.168.0.1" uses NAT-PMP with the given gateway address
func Parse(spec string) (Interface, error) {
var (
- parts = strings.SplitN(spec, ":", 2)
- mech = strings.ToLower(parts[0])
- ip net.IP
+ before, after, found = strings.Cut(spec, ":")
+ mech = strings.ToLower(before)
+ ip net.IP
)
- if len(parts) > 1 {
- ip = net.ParseIP(parts[1])
+ if found {
+ ip = net.ParseIP(after)
if ip == nil {
return nil, errors.New("invalid IP address")
}
@@ -86,7 +86,7 @@ func Parse(spec string) (Interface, error) {
case "pmp", "natpmp", "nat-pmp":
return PMP(ip), nil
default:
- return nil, fmt.Errorf("unknown mechanism %q", parts[0])
+ return nil, fmt.Errorf("unknown mechanism %q", before)
}
}
diff --git a/p2p/rlpx/rlpx_test.go b/p2p/rlpx/rlpx_test.go
index 28759f2b4..136cb1b5b 100644
--- a/p2p/rlpx/rlpx_test.go
+++ b/p2p/rlpx/rlpx_test.go
@@ -421,7 +421,7 @@ func BenchmarkThroughput(b *testing.B) {
}
conn2.SetSnappy(true)
if err := <-handshakeDone; err != nil {
- b.Fatal("server hanshake error:", err)
+ b.Fatal("server handshake error:", err)
}
// Read N messages.
diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go
index 5ac337939..63cc4936c 100644
--- a/p2p/simulations/adapters/exec.go
+++ b/p2p/simulations/adapters/exec.go
@@ -41,6 +41,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/websocket"
+ "golang.org/x/exp/slog"
)
func init() {
@@ -375,9 +376,11 @@ type execNodeConfig struct {
func initLogging() {
// Initialize the logging by default first.
- glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
- glogger.Verbosity(log.LvlInfo)
- log.Root().SetHandler(glogger)
+ var innerHandler slog.Handler
+ innerHandler = slog.NewTextHandler(os.Stderr, nil)
+ glogger := log.NewGlogHandler(innerHandler)
+ glogger.Verbosity(log.LevelInfo)
+ log.SetDefault(log.NewLogger(glogger))
confEnv := os.Getenv(envNodeConfig)
if confEnv == "" {
@@ -395,14 +398,15 @@ func initLogging() {
}
writer = logWriter
}
- var verbosity = log.LvlInfo
- if conf.Node.LogVerbosity <= log.LvlTrace && conf.Node.LogVerbosity >= log.LvlCrit {
- verbosity = conf.Node.LogVerbosity
+ var verbosity = log.LevelInfo
+ if conf.Node.LogVerbosity <= log.LevelTrace && conf.Node.LogVerbosity >= log.LevelCrit {
+ verbosity = log.FromLegacyLevel(int(conf.Node.LogVerbosity))
}
// Reinitialize the logger
- glogger = log.NewGlogHandler(log.StreamHandler(writer, log.TerminalFormat(true)))
+ innerHandler = log.NewTerminalHandler(writer, true)
+ glogger = log.NewGlogHandler(innerHandler)
glogger.Verbosity(verbosity)
- log.Root().SetHandler(glogger)
+ log.SetDefault(log.NewLogger(glogger))
}
// execP2PNode starts a simulation node when the current binary is executed with
diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go
index 098759599..fb8463d22 100644
--- a/p2p/simulations/adapters/types.go
+++ b/p2p/simulations/adapters/types.go
@@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/websocket"
+ "golang.org/x/exp/slog"
)
// Node represents a node in a simulation network which is created by a
@@ -129,7 +130,7 @@ type NodeConfig struct {
// LogVerbosity is the log verbosity of the p2p node at runtime.
//
// The default verbosity is INFO.
- LogVerbosity log.Lvl
+ LogVerbosity slog.Level
}
// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
@@ -197,7 +198,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
n.Port = confJSON.Port
n.EnableMsgEvents = confJSON.EnableMsgEvents
n.LogFile = confJSON.LogFile
- n.LogVerbosity = log.Lvl(confJSON.LogVerbosity)
+ n.LogVerbosity = slog.Level(confJSON.LogVerbosity)
return nil
}
diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go
index f6cf5113a..70b35ad77 100644
--- a/p2p/simulations/examples/ping-pong.go
+++ b/p2p/simulations/examples/ping-pong.go
@@ -41,7 +41,7 @@ func main() {
flag.Parse()
// set the log level to Trace
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false)))
// register a single ping-pong service
services := map[string]adapters.LifecycleConstructor{
diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go
index 7a4f70e9b..34521b477 100644
--- a/p2p/simulations/http.go
+++ b/p2p/simulations/http.go
@@ -479,12 +479,12 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) {
func NewMsgFilters(filterParam string) (MsgFilters, error) {
filters := make(MsgFilters)
for _, filter := range strings.Split(filterParam, "-") {
- protoCodes := strings.SplitN(filter, ":", 2)
- if len(protoCodes) != 2 || protoCodes[0] == "" || protoCodes[1] == "" {
+ proto, codes, found := strings.Cut(filter, ":")
+ if !found || proto == "" || codes == "" {
return nil, fmt.Errorf("invalid message filter: %s", filter)
}
- proto := protoCodes[0]
- for _, code := range strings.Split(protoCodes[1], ",") {
+
+ for _, code := range strings.Split(codes, ",") {
if code == "*" || code == "-1" {
filters[MsgFilter{Proto: proto, Code: -1}] = struct{}{}
continue
diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go
index 05e43238a..c53a49797 100644
--- a/p2p/simulations/http_test.go
+++ b/p2p/simulations/http_test.go
@@ -37,14 +37,14 @@ import (
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rpc"
"github.com/mattn/go-colorable"
+ "golang.org/x/exp/slog"
)
func TestMain(m *testing.M) {
loglevel := flag.Int("loglevel", 2, "verbosity of logs")
flag.Parse()
- log.PrintOrigins(true)
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.Level(*loglevel), true)))
os.Exit(m.Run())
}
diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go
index ab8cf1946..4ed1e4e6c 100644
--- a/p2p/simulations/network_test.go
+++ b/p2p/simulations/network_test.go
@@ -683,7 +683,7 @@ func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, i
}
}
-// \todo: refactor to implement shapshots
+// \todo: refactor to implement snapshots
// and connect configuration methods once these are moved from
// swarm/network/simulations/connect.go
func BenchmarkMinimalService(b *testing.B) {
diff --git a/params/bootnodes.go b/params/bootnodes.go
index a84389691..5e2c7c218 100644
--- a/params/bootnodes.go
+++ b/params/bootnodes.go
@@ -66,20 +66,25 @@ var GoerliBootnodes = []string{
var V5Bootnodes = []string{
// Teku team's bootnode
- "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA",
- "enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA",
+ "enr:-KG4QMOEswP62yzDjSwWS4YEjtTZ5PO6r65CPqYBkgTTkrpaedQ8uEUo1uMALtJIvb2w_WWEVmg5yt1UAuK1ftxUU7QDhGV0aDKQu6TalgMAAAD__________4JpZIJ2NIJpcIQEnfA2iXNlY3AyNTZrMaEDfol8oLr6XJ7FsdAYE7lpJhKMls4G_v6qQOGKJUWGb_uDdGNwgiMog3VkcIIjKA", // # 4.157.240.54 | azure-us-east-virginia
+ "enr:-KG4QF4B5WrlFcRhUU6dZETwY5ZzAXnA0vGC__L1Kdw602nDZwXSTs5RFXFIFUnbQJmhNGVU6OIX7KVrCSTODsz1tK4DhGV0aDKQu6TalgMAAAD__________4JpZIJ2NIJpcIQExNYEiXNlY3AyNTZrMaECQmM9vp7KhaXhI-nqL_R0ovULLCFSFTa9CPPSdb1zPX6DdGNwgiMog3VkcIIjKA", // 4.196.214.4 | azure-au-east-sydney
// Prylab team's bootnodes
- "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg",
- "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA",
- "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg",
+ "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", // 18.223.219.100 | aws-us-east-2-ohio
+ "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", // 18.223.219.100 | aws-us-east-2-ohio
+ "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", // 18.223.219.100 | aws-us-east-2-ohio
// Lighthouse team's bootnodes
- "enr:-IS4QLkKqDMy_ExrpOEWa59NiClemOnor-krjp4qoeZwIw2QduPC-q7Kz4u1IOWf3DDbdxqQIgC4fejavBOuUPy-HE4BgmlkgnY0gmlwhCLzAHqJc2VjcDI1NmsxoQLQSJfEAHZApkm5edTCZ_4qps_1k_ub2CxHFxi-gr2JMIN1ZHCCIyg",
- "enr:-IS4QDAyibHCzYZmIYZCjXwU9BqpotWmv2BsFlIq1V31BwDDMJPFEbox1ijT5c2Ou3kvieOKejxuaCqIcjxBjJ_3j_cBgmlkgnY0gmlwhAMaHiCJc2VjcDI1NmsxoQJIdpj_foZ02MXz4It8xKD7yUHTBx7lVFn3oeRP21KRV4N1ZHCCIyg",
+ "enr:-Le4QPUXJS2BTORXxyx2Ia-9ae4YqA_JWX3ssj4E_J-3z1A-HmFGrU8BpvpqhNabayXeOZ2Nq_sbeDgtzMJpLLnXFgAChGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISsaa0Zg2lwNpAkAIkHAAAAAPA8kv_-awoTiXNlY3AyNTZrMaEDHAD2JKYevx89W0CcFJFiskdcEzkH_Wdv9iW42qLK79ODdWRwgiMohHVkcDaCI4I", // 172.105.173.25 | linode-au-sydney
+ "enr:-Le4QLHZDSvkLfqgEo8IWGG96h6mxwe_PsggC20CL3neLBjfXLGAQFOPSltZ7oP6ol54OvaNqO02Rnvb8YmDR274uq8ChGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLosQxg2lwNpAqAX4AAAAAAPA8kv_-ax65iXNlY3AyNTZrMaEDBJj7_dLFACaxBfaI8KZTh_SSJUjhyAyfshimvSqo22WDdWRwgiMohHVkcDaCI4I", // 139.162.196.49 | linode-uk-london
+ "enr:-Le4QH6LQrusDbAHPjU_HcKOuMeXfdEB5NJyXgHWFadfHgiySqeDyusQMvfphdYWOzuSZO9Uq2AMRJR5O4ip7OvVma8BhGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLY9ncg2lwNpAkAh8AgQIBAAAAAAAAAAmXiXNlY3AyNTZrMaECDYCZTZEksF-kmgPholqgVt8IXr-8L7Nu7YrZ7HUpgxmDdWRwgiMohHVkcDaCI4I", // 139.99.217.220 | ovh-au-sydney
+ "enr:-Le4QIqLuWybHNONr933Lk0dcMmAB5WgvGKRyDihy1wHDIVlNuuztX62W51voT4I8qD34GcTEOTmag1bcdZ_8aaT4NUBhGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLY04ng2lwNpAkAh8AgAIBAAAAAAAAAA-fiXNlY3AyNTZrMaEDscnRV6n1m-D9ID5UsURk0jsoKNXt1TIrj8uKOGW6iluDdWRwgiMohHVkcDaCI4I", // 139.99.78.39 | ovh-singapore
// EF bootnodes
- "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg",
- "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg",
- "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg",
- "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg",
+ "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", // 3.17.30.69 | aws-us-east-2-ohio
+ "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", // 18.216.248.220 | aws-us-east-2-ohio
+ "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", // 54.178.44.198 | aws-ap-northeast-1-tokyo
+ "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", // 54.65.172.253 | aws-ap-northeast-1-tokyo
+ // Nimbus team's bootnodes
+ "enr:-LK4QA8FfhaAjlb_BXsXxSfiysR7R52Nhi9JBt4F8SPssu8hdE1BXQQEtVDC3qStCW60LSO7hEsVHv5zm8_6Vnjhcn0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAN4aBKJc2VjcDI1NmsxoQJerDhsJ-KxZ8sHySMOCmTO6sHM3iCFQ6VMvLTe948MyYN0Y3CCI4yDdWRwgiOM", // 3.120.104.18 | aws-eu-central-1-frankfurt
+ "enr:-LK4QKWrXTpV9T78hNG6s8AM6IO4XH9kFT91uZtFg1GcsJ6dKovDOr1jtAAFPnS2lvNltkOGA9k29BUN7lFh_sjuc9QBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhANAdd-Jc2VjcDI1NmsxoQLQa6ai7y9PMN5hpLe5HmiJSlYzMuzP7ZhwRiwHvqNXdoN0Y3CCI4yDdWRwgiOM", // 3.64.117.223 | aws-eu-central-1-frankfurt}
}
const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@"
diff --git a/params/config.go b/params/config.go
index 6db4702ce..386ce7e23 100644
--- a/params/config.go
+++ b/params/config.go
@@ -127,6 +127,7 @@ var (
TerminalTotalDifficulty: big.NewInt(10_790_000),
TerminalTotalDifficultyPassed: true,
ShanghaiTime: newUint64(1678832736),
+ CancunTime: newUint64(1705473120),
Clique: &CliqueConfig{
Period: 15,
Epoch: 30000,
@@ -180,7 +181,6 @@ var (
ShanghaiTime: newUint64(0),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
- IsDevMode: true,
}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
@@ -329,9 +329,8 @@ type ChainConfig struct {
TerminalTotalDifficultyPassed bool `json:"terminalTotalDifficultyPassed,omitempty"`
// Various consensus engines
- Ethash *EthashConfig `json:"ethash,omitempty"`
- Clique *CliqueConfig `json:"clique,omitempty"`
- IsDevMode bool `json:"isDev,omitempty"`
+ Ethash *EthashConfig `json:"ethash,omitempty"`
+ Clique *CliqueConfig `json:"clique,omitempty"`
}
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
diff --git a/params/version.go b/params/version.go
index 5fb9631f1..e34474109 100644
--- a/params/version.go
+++ b/params/version.go
@@ -23,7 +23,7 @@ import (
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 13 // Minor version component of the current release
- VersionPatch = 5 // Patch version component of the current release
+ VersionPatch = 9 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string
)
diff --git a/plugins/test-plugin/engine.go b/plugins/test-plugin/engine.go
index 76097ba6f..fac7cd505 100644
--- a/plugins/test-plugin/engine.go
+++ b/plugins/test-plugin/engine.go
@@ -17,6 +17,7 @@ var (
backend restricted.Backend
log core.Logger
events core.Feed
+ createEngineCalled bool
)
var httpApiFlagName = "http.api"
@@ -107,6 +108,7 @@ func (e *engine) Close() error {
}
func CreateEngine(chainConfig *params.ChainConfig, db restricted.Database) consensus.Engine {
+ createEngineCalled = true
return &engine{}
}
diff --git a/plugins/test-plugin/hooks.go b/plugins/test-plugin/hooks.go
index b1a11e44a..47f8249ed 100644
--- a/plugins/test-plugin/hooks.go
+++ b/plugins/test-plugin/hooks.go
@@ -6,6 +6,7 @@ import (
"sync"
"github.com/openrelayxyz/plugeth-utils/core"
+ "github.com/openrelayxyz/plugeth-utils/restricted"
)
@@ -37,6 +38,15 @@ func GetAPIs(stack core.Node, backend core.Backend) []core.API {
return apis
}
+func InitializeNode(stack core.Node, b restricted.Backend) {
+ go func() {
+ m := map[string]struct{}{
+ "InitializeNode":struct{}{},
+ }
+ hookChan <- m
+ }()
+}
+
// func OnShutdown(){
// this injection is covered by another test in this package. See documentation for details.
// }
@@ -187,6 +197,17 @@ func OpCodeSelect() []int {
return nil
}
+// eth/ethconfig
+
+func pseudoCreateEngine() {
+ if createEngineCalled {
+ m := map[string]struct{}{
+ "CreateEngine":struct{}{},
+ }
+ hookChan <- m
+ }
+}
+
// rpc/
@@ -239,6 +260,8 @@ func Is160(num *big.Int) bool {
}
var plugins map[string]struct{} = map[string]struct{}{
+ "InitializeNode":struct{}{},
+ "CreateEngine":struct{}{},
"OnShutdown": struct{}{},
"SetTrieFlushIntervalClone":struct{}{},
"StateUpdate": struct{}{},
diff --git a/plugins/test-plugin/main.go b/plugins/test-plugin/main.go
index 75cba1eee..30f272606 100644
--- a/plugins/test-plugin/main.go
+++ b/plugins/test-plugin/main.go
@@ -53,6 +53,10 @@ func BlockChain() {
var ok bool
f := func(key string) bool {_, ok = m[key]; return ok}
switch {
+ case f("InitializeNode"):
+ delete(plugins, "InitializeNode")
+ case f("CreateEngine"):
+ delete(plugins, "CreateEngine")
case f("OnShutdown"):
delete(plugins, "OnShutdown")
case f("StateUpdate"):
@@ -139,6 +143,7 @@ func BlockChain() {
}
}()
+ pseudoCreateEngine()
txFactory()
txTracer()
}
diff --git a/rpc/client_test.go b/rpc/client_test.go
index 7c96b2d66..ac02ad33c 100644
--- a/rpc/client_test.go
+++ b/rpc/client_test.go
@@ -595,7 +595,7 @@ func TestClientSubscriptionChannelClose(t *testing.T) {
for i := 0; i < 100; i++ {
ch := make(chan int, 100)
- sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1)
+ sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 100, 1)
if err != nil {
t.Fatal(err)
}
diff --git a/rpc/json.go b/rpc/json.go
index 8a3b162ca..5557a8076 100644
--- a/rpc/json.go
+++ b/rpc/json.go
@@ -46,6 +46,17 @@ type subscriptionResult struct {
Result json.RawMessage `json:"result,omitempty"`
}
+type subscriptionResultEnc struct {
+ ID string `json:"subscription"`
+ Result any `json:"result"`
+}
+
+type jsonrpcSubscriptionNotification struct {
+ Version string `json:"jsonrpc"`
+ Method string `json:"method"`
+ Params subscriptionResultEnc `json:"params"`
+}
+
// A value of this type can a JSON-RPC request, notification, successful response or
// error response. Which one it is depends on the fields.
type jsonrpcMessage struct {
@@ -86,8 +97,8 @@ func (msg *jsonrpcMessage) isUnsubscribe() bool {
}
func (msg *jsonrpcMessage) namespace() string {
- elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2)
- return elem[0]
+ before, _, _ := strings.Cut(msg.Method, serviceMethodSeparator)
+ return before
}
func (msg *jsonrpcMessage) String() string {
diff --git a/rpc/metrics.go b/rpc/metrics.go
index b1f128453..ef7449ce0 100644
--- a/rpc/metrics.go
+++ b/rpc/metrics.go
@@ -46,5 +46,5 @@ func updateServeTimeHistogram(method string, success bool, elapsed time.Duration
metrics.NewExpDecaySample(1028, 0.015),
)
}
- metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(elapsed.Microseconds())
+ metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(elapsed.Nanoseconds())
}
diff --git a/rpc/service.go b/rpc/service.go
index 9a77d1520..6add5c48f 100644
--- a/rpc/service.go
+++ b/rpc/service.go
@@ -96,13 +96,13 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error {
// callback returns the callback corresponding to the given RPC method name.
func (r *serviceRegistry) callback(method string) *callback {
- elem := strings.SplitN(method, serviceMethodSeparator, 2)
- if len(elem) != 2 {
+ before, after, found := strings.Cut(method, serviceMethodSeparator)
+ if !found {
return nil
}
r.mu.Lock()
defer r.mu.Unlock()
- return r.services[elem[0]].callbacks[elem[1]]
+ return r.services[before].callbacks[after]
}
// subscription returns a subscription callback in the given service.
diff --git a/rpc/subscription.go b/rpc/subscription.go
index 3231c2cee..9cb072754 100644
--- a/rpc/subscription.go
+++ b/rpc/subscription.go
@@ -105,7 +105,7 @@ type Notifier struct {
mu sync.Mutex
sub *Subscription
- buffer []json.RawMessage
+ buffer []any
callReturned bool
activated bool
}
@@ -129,12 +129,7 @@ func (n *Notifier) CreateSubscription() *Subscription {
// Notify sends a notification to the client with the given data as payload.
// If an error occurs the RPC connection is closed and the error is returned.
-func (n *Notifier) Notify(id ID, data interface{}) error {
- enc, err := json.Marshal(data)
- if err != nil {
- return err
- }
-
+func (n *Notifier) Notify(id ID, data any) error {
n.mu.Lock()
defer n.mu.Unlock()
@@ -144,9 +139,9 @@ func (n *Notifier) Notify(id ID, data interface{}) error {
panic("Notify with wrong ID")
}
if n.activated {
- return n.send(n.sub, enc)
+ return n.send(n.sub, data)
}
- n.buffer = append(n.buffer, enc)
+ n.buffer = append(n.buffer, data)
return nil
}
@@ -181,16 +176,16 @@ func (n *Notifier) activate() error {
return nil
}
-func (n *Notifier) send(sub *Subscription, data json.RawMessage) error {
- params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data})
- ctx := context.Background()
-
- msg := &jsonrpcMessage{
+func (n *Notifier) send(sub *Subscription, data any) error {
+ msg := jsonrpcSubscriptionNotification{
Version: vsn,
Method: n.namespace + notificationMethodSuffix,
- Params: params,
+ Params: subscriptionResultEnc{
+ ID: string(sub.ID),
+ Result: data,
+ },
}
- return n.h.conn.writeJSON(ctx, msg, false)
+ return n.h.conn.writeJSON(context.Background(), &msg, false)
}
// A Subscription is created by a notifier and tied to that notifier. The client can use
diff --git a/rpc/subscription_test.go b/rpc/subscription_test.go
index b27045782..3a131c8e6 100644
--- a/rpc/subscription_test.go
+++ b/rpc/subscription_test.go
@@ -17,12 +17,19 @@
package rpc
import (
+ "bytes"
+ "context"
"encoding/json"
"fmt"
+ "io"
+ "math/big"
"net"
"strings"
"testing"
"time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
)
func TestNewID(t *testing.T) {
@@ -218,3 +225,56 @@ func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionRe
return nil, nil, fmt.Errorf("unrecognized message: %v", msg)
}
}
+
+type mockConn struct {
+ enc *json.Encoder
+}
+
+// writeJSON writes a message to the connection.
+func (c *mockConn) writeJSON(ctx context.Context, msg interface{}, isError bool) error {
+ return c.enc.Encode(msg)
+}
+
+// Closed returns a channel which is closed when the connection is closed.
+func (c *mockConn) closed() <-chan interface{} { return nil }
+
+// RemoteAddr returns the peer address of the connection.
+func (c *mockConn) remoteAddr() string { return "" }
+
+// BenchmarkNotify benchmarks the performance of notifying a subscription.
+func BenchmarkNotify(b *testing.B) {
+ id := ID("test")
+ notifier := &Notifier{
+ h: &handler{conn: &mockConn{json.NewEncoder(io.Discard)}},
+ sub: &Subscription{ID: id},
+ activated: true,
+ }
+ msg := &types.Header{
+ ParentHash: common.HexToHash("0x01"),
+ Number: big.NewInt(100),
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ notifier.Notify(id, msg)
+ }
+}
+
+func TestNotify(t *testing.T) {
+ out := new(bytes.Buffer)
+ id := ID("test")
+ notifier := &Notifier{
+ h: &handler{conn: &mockConn{json.NewEncoder(out)}},
+ sub: &Subscription{ID: id},
+ activated: true,
+ }
+ msg := &types.Header{
+ ParentHash: common.HexToHash("0x01"),
+ Number: big.NewInt(100),
+ }
+ notifier.Notify(id, msg)
+ have := strings.TrimSpace(out.String())
+ want := `{"jsonrpc":"2.0","method":"_subscription","params":{"subscription":"test","result":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000001","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":null,"number":"0x64","gasLimit":"0x0","gasUsed":"0x0","timestamp":"0x0","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":null,"withdrawalsRoot":null,"blobGasUsed":null,"excessBlobGas":null,"parentBeaconBlockRoot":null,"hash":"0xe5fb877dde471b45b9742bb4bb4b3d74a761e2fb7cb849a3d2b687eed90fb604"}}}`
+ if have != want {
+ t.Errorf("have:\n%v\nwant:\n%v\n", have, want)
+ }
+}
diff --git a/signer/core/api.go b/signer/core/api.go
index 43eb89ee0..ef8c13662 100644
--- a/signer/core/api.go
+++ b/signer/core/api.go
@@ -65,7 +65,7 @@ type ExternalAPI interface {
EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
// Version info about the APIs
Version(ctx context.Context) (string, error)
- // SignGnosisSafeTransaction signs/confirms a gnosis-safe multisig transaction
+ // SignGnosisSafeTx signs/confirms a gnosis-safe multisig transaction
SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error)
}
diff --git a/signer/core/api_test.go b/signer/core/api_test.go
index 5a9de161b..69229dada 100644
--- a/signer/core/api_test.go
+++ b/signer/core/api_test.go
@@ -169,6 +169,7 @@ func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address,
}
func TestNewAcc(t *testing.T) {
+ t.Parallel()
api, control := setup(t)
verifyNum := func(num int) {
list, err := list(control, api, t)
@@ -235,6 +236,7 @@ func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs {
}
func TestSignTx(t *testing.T) {
+ t.Parallel()
var (
list []common.Address
res, res2 *ethapi.SignTransactionResult
diff --git a/signer/core/apitypes/signed_data_internal_test.go b/signer/core/apitypes/signed_data_internal_test.go
index af7fc93ed..8067893c2 100644
--- a/signer/core/apitypes/signed_data_internal_test.go
+++ b/signer/core/apitypes/signed_data_internal_test.go
@@ -27,6 +27,7 @@ import (
)
func TestBytesPadding(t *testing.T) {
+ t.Parallel()
tests := []struct {
Type string
Input []byte
@@ -87,6 +88,7 @@ func TestBytesPadding(t *testing.T) {
}
func TestParseAddress(t *testing.T) {
+ t.Parallel()
tests := []struct {
Input interface{}
Output []byte // nil => error
@@ -136,6 +138,7 @@ func TestParseAddress(t *testing.T) {
}
func TestParseBytes(t *testing.T) {
+ t.Parallel()
for i, tt := range []struct {
v interface{}
exp []byte
@@ -170,6 +173,7 @@ func TestParseBytes(t *testing.T) {
}
func TestParseInteger(t *testing.T) {
+ t.Parallel()
for i, tt := range []struct {
t string
v interface{}
@@ -200,6 +204,7 @@ func TestParseInteger(t *testing.T) {
}
func TestConvertStringDataToSlice(t *testing.T) {
+ t.Parallel()
slice := []string{"a", "b", "c"}
var it interface{} = slice
_, err := convertDataToSlice(it)
@@ -209,6 +214,7 @@ func TestConvertStringDataToSlice(t *testing.T) {
}
func TestConvertUint256DataToSlice(t *testing.T) {
+ t.Parallel()
slice := []*math.HexOrDecimal256{
math.NewHexOrDecimal256(1),
math.NewHexOrDecimal256(2),
@@ -222,6 +228,7 @@ func TestConvertUint256DataToSlice(t *testing.T) {
}
func TestConvertAddressDataToSlice(t *testing.T) {
+ t.Parallel()
slice := []common.Address{
common.HexToAddress("0x0000000000000000000000000000000000000001"),
common.HexToAddress("0x0000000000000000000000000000000000000002"),
diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go
index 8218e754d..6bfcd2a72 100644
--- a/signer/core/apitypes/types.go
+++ b/signer/core/apitypes/types.go
@@ -62,7 +62,7 @@ func (vs *ValidationMessages) Info(msg string) {
vs.Messages = append(vs.Messages, ValidationInfo{INFO, msg})
}
-// getWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present
+// GetWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present
func (v *ValidationMessages) GetWarnings() error {
var messages []string
for _, msg := range v.Messages {
diff --git a/signer/core/apitypes/types_test.go b/signer/core/apitypes/types_test.go
index eef3cae00..b5aa3d1e9 100644
--- a/signer/core/apitypes/types_test.go
+++ b/signer/core/apitypes/types_test.go
@@ -19,6 +19,7 @@ package apitypes
import "testing"
func TestIsPrimitive(t *testing.T) {
+ t.Parallel()
// Expected positives
for i, tc := range []string{
"int24", "int24[]", "uint88", "uint88[]", "uint", "uint[]", "int256", "int256[]",
diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go
index a0b292bf7..d2207c9eb 100644
--- a/signer/core/auditlog.go
+++ b/signer/core/auditlog.go
@@ -19,12 +19,14 @@ package core
import (
"context"
"encoding/json"
+ "os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
+ "golang.org/x/exp/slog"
)
type AuditLogger struct {
@@ -113,12 +115,13 @@ func (l *AuditLogger) Version(ctx context.Context) (string, error) {
}
func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) {
- l := log.New("api", "signer")
- handler, err := log.FileHandler(path, log.LogfmtFormat())
+ f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
- l.SetHandler(handler)
+
+ handler := slog.NewTextHandler(f, nil)
+ l := log.NewLogger(handler).With("api", "signer")
l.Info("Configured", "audit log", path)
return &AuditLogger{l, api}, nil
}
diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go
index 3e3837cae..1cf8b4bf3 100644
--- a/signer/core/signed_data_test.go
+++ b/signer/core/signed_data_test.go
@@ -183,6 +183,7 @@ var typedData = apitypes.TypedData{
}
func TestSignData(t *testing.T) {
+ t.Parallel()
api, control := setup(t)
//Create two accounts
createAccount(control, api, t)
@@ -248,6 +249,7 @@ func TestSignData(t *testing.T) {
}
func TestDomainChainId(t *testing.T) {
+ t.Parallel()
withoutChainID := apitypes.TypedData{
Types: apitypes.Types{
"EIP712Domain": []apitypes.Type{
@@ -289,6 +291,7 @@ func TestDomainChainId(t *testing.T) {
}
func TestHashStruct(t *testing.T) {
+ t.Parallel()
hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
if err != nil {
t.Fatal(err)
@@ -309,6 +312,7 @@ func TestHashStruct(t *testing.T) {
}
func TestEncodeType(t *testing.T) {
+ t.Parallel()
domainTypeEncoding := string(typedData.EncodeType("EIP712Domain"))
if domainTypeEncoding != "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" {
t.Errorf("Expected different encodeType result (got %s)", domainTypeEncoding)
@@ -321,6 +325,7 @@ func TestEncodeType(t *testing.T) {
}
func TestTypeHash(t *testing.T) {
+ t.Parallel()
mailTypeHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.TypeHash(typedData.PrimaryType)))
if mailTypeHash != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2" {
t.Errorf("Expected different typeHash result (got %s)", mailTypeHash)
@@ -328,6 +333,7 @@ func TestTypeHash(t *testing.T) {
}
func TestEncodeData(t *testing.T) {
+ t.Parallel()
hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0)
if err != nil {
t.Fatal(err)
@@ -339,6 +345,7 @@ func TestEncodeData(t *testing.T) {
}
func TestFormatter(t *testing.T) {
+ t.Parallel()
var d apitypes.TypedData
err := json.Unmarshal([]byte(jsonTypedData), &d)
if err != nil {
@@ -368,6 +375,7 @@ func sign(typedData apitypes.TypedData) ([]byte, []byte, error) {
}
func TestJsonFiles(t *testing.T) {
+ t.Parallel()
testfiles, err := os.ReadDir("testdata/")
if err != nil {
t.Fatalf("failed reading files: %v", err)
@@ -402,6 +410,7 @@ func TestJsonFiles(t *testing.T) {
// TestFuzzerFiles tests some files that have been found by fuzzing to cause
// crashes or hangs.
func TestFuzzerFiles(t *testing.T) {
+ t.Parallel()
corpusdir := path.Join("testdata", "fuzzing")
testfiles, err := os.ReadDir(corpusdir)
if err != nil {
@@ -514,6 +523,7 @@ var gnosisTx = `
// TestGnosisTypedData tests the scenario where a user submits a full EIP-712
// struct without using the gnosis-specific endpoint
func TestGnosisTypedData(t *testing.T) {
+ t.Parallel()
var td apitypes.TypedData
err := json.Unmarshal([]byte(gnosisTypedData), &td)
if err != nil {
@@ -532,6 +542,7 @@ func TestGnosisTypedData(t *testing.T) {
// TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe
// specific data, and we fill the TypedData struct on our side
func TestGnosisCustomData(t *testing.T) {
+ t.Parallel()
var tx core.GnosisSafeTx
err := json.Unmarshal([]byte(gnosisTx), &tx)
if err != nil {
@@ -644,6 +655,7 @@ var gnosisTxWithChainId = `
`
func TestGnosisTypedDataWithChainId(t *testing.T) {
+ t.Parallel()
var td apitypes.TypedData
err := json.Unmarshal([]byte(gnosisTypedDataWithChainId), &td)
if err != nil {
@@ -662,6 +674,7 @@ func TestGnosisTypedDataWithChainId(t *testing.T) {
// TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe
// specific data, and we fill the TypedData struct on our side
func TestGnosisCustomDataWithChainId(t *testing.T) {
+ t.Parallel()
var tx core.GnosisSafeTx
err := json.Unmarshal([]byte(gnosisTxWithChainId), &tx)
if err != nil {
@@ -813,6 +826,7 @@ var complexTypedData = `
`
func TestComplexTypedData(t *testing.T) {
+ t.Parallel()
var td apitypes.TypedData
err := json.Unmarshal([]byte(complexTypedData), &td)
if err != nil {
@@ -829,6 +843,7 @@ func TestComplexTypedData(t *testing.T) {
}
func TestGnosisSafe(t *testing.T) {
+ t.Parallel()
// json missing chain id
js := "{\n \"safe\": \"0x899FcB1437DE65DC6315f5a69C017dd3F2837557\",\n \"to\": \"0x899FcB1437DE65DC6315f5a69C017dd3F2837557\",\n \"value\": \"0\",\n \"data\": \"0x0d582f13000000000000000000000000d3ed2b8756b942c98c851722f3bd507a17b4745f0000000000000000000000000000000000000000000000000000000000000005\",\n \"operation\": 0,\n \"gasToken\": \"0x0000000000000000000000000000000000000000\",\n \"safeTxGas\": 0,\n \"baseGas\": 0,\n \"gasPrice\": \"0\",\n \"refundReceiver\": \"0x0000000000000000000000000000000000000000\",\n \"nonce\": 0,\n \"executionDate\": null,\n \"submissionDate\": \"2022-02-23T14:09:00.018475Z\",\n \"modified\": \"2022-12-01T15:52:21.214357Z\",\n \"blockNumber\": null,\n \"transactionHash\": null,\n \"safeTxHash\": \"0x6f0f5cffee69087c9d2471e477a63cab2ae171cf433e754315d558d8836274f4\",\n \"executor\": null,\n \"isExecuted\": false,\n \"isSuccessful\": null,\n \"ethGasPrice\": null,\n \"maxFeePerGas\": null,\n \"maxPriorityFeePerGas\": null,\n \"gasUsed\": null,\n \"fee\": null,\n \"origin\": \"https://gnosis-safe.io\",\n \"dataDecoded\": {\n \"method\": \"addOwnerWithThreshold\",\n \"parameters\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"value\": \"0xD3Ed2b8756b942c98c851722F3bd507a17B4745F\"\n },\n {\n \"name\": \"_threshold\",\n \"type\": \"uint256\",\n \"value\": \"5\"\n }\n ]\n },\n \"confirmationsRequired\": 4,\n \"confirmations\": [\n {\n \"owner\": \"0x30B714E065B879F5c042A75Bb40a220A0BE27966\",\n \"submissionDate\": \"2022-03-01T14:56:22Z\",\n \"transactionHash\": \"0x6d0a9c83ac7578ef3be1f2afce089fb83b619583dfa779b82f4422fd64ff3ee9\",\n \"signature\": \"0x00000000000000000000000030b714e065b879f5c042a75bb40a220a0be27966000000000000000000000000000000000000000000000000000000000000000001\",\n \"signatureType\": \"APPROVED_HASH\"\n },\n {\n \"owner\": \"0x8300dFEa25Da0eb744fC0D98c23283F86AB8c10C\",\n \"submissionDate\": \"2022-12-01T15:52:21.214357Z\",\n \"transactionHash\": null,\n \"signature\": \"0xbce73de4cc6ee208e933a93c794dcb8ba1810f9848d1eec416b7be4dae9854c07dbf1720e60bbd310d2159197a380c941cfdb55b3ce58f9dd69efd395d7bef881b\",\n \"signatureType\": \"EOA\"\n }\n ],\n \"trusted\": true,\n \"signatures\": null\n}\n"
var gnosisTx core.GnosisSafeTx
@@ -984,6 +999,7 @@ var complexTypedDataLCRefType = `
`
func TestComplexTypedDataWithLowercaseReftype(t *testing.T) {
+ t.Parallel()
var td apitypes.TypedData
err := json.Unmarshal([]byte(complexTypedDataLCRefType), &td)
if err != nil {
diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go
index 4a060147a..b8c3acfb4 100644
--- a/signer/core/uiapi.go
+++ b/signer/core/uiapi.go
@@ -31,7 +31,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)
-// SignerUIAPI implements methods Clef provides for a UI to query, in the bidirectional communication
+// UIServerAPI implements methods Clef provides for a UI to query, in the bidirectional communication
// channel.
// This API is considered secure, since a request can only
// ever arrive from the UI -- and the UI is capable of approving any action, thus we can consider these
diff --git a/signer/core/validation_test.go b/signer/core/validation_test.go
index 6adaa21af..7f733b0bb 100644
--- a/signer/core/validation_test.go
+++ b/signer/core/validation_test.go
@@ -19,6 +19,7 @@ package core
import "testing"
func TestPasswordValidation(t *testing.T) {
+ t.Parallel()
testcases := []struct {
pw string
shouldFail bool
diff --git a/signer/fourbyte/abi_test.go b/signer/fourbyte/abi_test.go
index 68c027ece..9656732df 100644
--- a/signer/fourbyte/abi_test.go
+++ b/signer/fourbyte/abi_test.go
@@ -52,6 +52,7 @@ func verify(t *testing.T, jsondata, calldata string, exp []interface{}) {
}
func TestNewUnpacker(t *testing.T) {
+ t.Parallel()
type unpackTest struct {
jsondata string
calldata string
@@ -97,6 +98,7 @@ func TestNewUnpacker(t *testing.T) {
}
func TestCalldataDecoding(t *testing.T) {
+ t.Parallel()
// send(uint256) : a52c101e
// compareAndApprove(address,uint256,uint256) : 751e1079
// issue(address[],uint256) : 42958b54
@@ -159,6 +161,7 @@ func TestCalldataDecoding(t *testing.T) {
}
func TestMaliciousABIStrings(t *testing.T) {
+ t.Parallel()
tests := []string{
"func(uint256,uint256,[]uint256)",
"func(uint256,uint256,uint256,)",
diff --git a/signer/fourbyte/fourbyte_test.go b/signer/fourbyte/fourbyte_test.go
index 017001f97..a3dc3b511 100644
--- a/signer/fourbyte/fourbyte_test.go
+++ b/signer/fourbyte/fourbyte_test.go
@@ -17,8 +17,8 @@
package fourbyte
import (
+ "encoding/json"
"fmt"
- "strings"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
@@ -27,18 +27,19 @@ import (
// Tests that all the selectors contained in the 4byte database are valid.
func TestEmbeddedDatabase(t *testing.T) {
+ t.Parallel()
db, err := New()
if err != nil {
t.Fatal(err)
}
+ var abistruct abi.ABI
for id, selector := range db.embedded {
abistring, err := parseSelector(selector)
if err != nil {
t.Errorf("Failed to convert selector to ABI: %v", err)
continue
}
- abistruct, err := abi.JSON(strings.NewReader(string(abistring)))
- if err != nil {
+ if err := json.Unmarshal(abistring, &abistruct); err != nil {
t.Errorf("Failed to parse ABI: %v", err)
continue
}
@@ -55,6 +56,7 @@ func TestEmbeddedDatabase(t *testing.T) {
// Tests that custom 4byte datasets can be handled too.
func TestCustomDatabase(t *testing.T) {
+ t.Parallel()
// Create a new custom 4byte database with no embedded component
tmpdir := t.TempDir()
filename := fmt.Sprintf("%s/4byte_custom.json", tmpdir)
diff --git a/signer/fourbyte/validation_test.go b/signer/fourbyte/validation_test.go
index 1b0ab507a..74fed9fe0 100644
--- a/signer/fourbyte/validation_test.go
+++ b/signer/fourbyte/validation_test.go
@@ -73,6 +73,7 @@ type txtestcase struct {
}
func TestTransactionValidation(t *testing.T) {
+ t.Parallel()
var (
// use empty db, there are other tests for the abi-specific stuff
db = newEmpty()
diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go
index c35da8ecc..d27de22b2 100644
--- a/signer/rules/rules_test.go
+++ b/signer/rules/rules_test.go
@@ -124,6 +124,7 @@ func initRuleEngine(js string) (*rulesetUI, error) {
}
func TestListRequest(t *testing.T) {
+ t.Parallel()
accs := make([]accounts.Account, 5)
for i := range accs {
@@ -152,6 +153,7 @@ func TestListRequest(t *testing.T) {
}
func TestSignTxRequest(t *testing.T) {
+ t.Parallel()
js := `
function ApproveTx(r){
console.log("transaction.from", r.transaction.from);
@@ -244,6 +246,7 @@ func (d *dummyUI) OnSignerStartup(info core.StartupInfo) {
// TestForwarding tests that the rule-engine correctly dispatches requests to the next caller
func TestForwarding(t *testing.T) {
+ t.Parallel()
js := ""
ui := &dummyUI{make([]string, 0)}
jsBackend := storage.NewEphemeralStorage()
@@ -271,6 +274,7 @@ func TestForwarding(t *testing.T) {
}
func TestMissingFunc(t *testing.T) {
+ t.Parallel()
r, err := initRuleEngine(JS)
if err != nil {
t.Errorf("Couldn't create evaluator %v", err)
@@ -293,6 +297,7 @@ func TestMissingFunc(t *testing.T) {
t.Logf("Err %v", err)
}
func TestStorage(t *testing.T) {
+ t.Parallel()
js := `
function testStorage(){
storage.put("mykey", "myvalue")
@@ -455,6 +460,7 @@ func dummySigned(value *big.Int) *types.Transaction {
}
func TestLimitWindow(t *testing.T) {
+ t.Parallel()
r, err := initRuleEngine(ExampleTxWindow)
if err != nil {
t.Errorf("Couldn't create evaluator %v", err)
@@ -540,6 +546,7 @@ func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) {
// if it does, that would be bad since developers may rely on that to store data,
// instead of using the disk-based data storage
func TestContextIsCleared(t *testing.T) {
+ t.Parallel()
js := `
function ApproveTx(){
if (typeof foobar == 'undefined') {
@@ -571,6 +578,7 @@ func TestContextIsCleared(t *testing.T) {
}
func TestSignData(t *testing.T) {
+ t.Parallel()
js := `function ApproveListing(){
return "Approve"
}
diff --git a/signer/storage/aes_gcm_storage_test.go b/signer/storage/aes_gcm_storage_test.go
index e1fea5928..a223b1a6b 100644
--- a/signer/storage/aes_gcm_storage_test.go
+++ b/signer/storage/aes_gcm_storage_test.go
@@ -26,9 +26,11 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/mattn/go-colorable"
+ "golang.org/x/exp/slog"
)
func TestEncryption(t *testing.T) {
+ t.Parallel()
// key := []byte("AES256Key-32Characters1234567890")
// plaintext := []byte(value)
key := []byte("AES256Key-32Characters1234567890")
@@ -51,6 +53,7 @@ func TestEncryption(t *testing.T) {
}
func TestFileStorage(t *testing.T) {
+ t.Parallel()
a := map[string]storedCredential{
"secret": {
Iv: common.Hex2Bytes("cdb30036279601aeee60f16b"),
@@ -89,7 +92,8 @@ func TestFileStorage(t *testing.T) {
}
}
func TestEnd2End(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
+ t.Parallel()
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.LevelInfo, true)))
d := t.TempDir()
@@ -109,9 +113,10 @@ func TestEnd2End(t *testing.T) {
}
func TestSwappedKeys(t *testing.T) {
+ t.Parallel()
// It should not be possible to swap the keys/values, so that
// K1:V1, K2:V2 can be swapped into K1:V2, K2:V1
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
+ log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.LevelInfo, true)))
d := t.TempDir()
diff --git a/tests/block_test.go b/tests/block_test.go
index 5764ae33e..aa6f27b8f 100644
--- a/tests/block_test.go
+++ b/tests/block_test.go
@@ -17,6 +17,8 @@
package tests
import (
+ "math/rand"
+ "runtime"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -49,6 +51,9 @@ func TestBlockchain(t *testing.T) {
bt.skipLoad(`.*randomStatetest94.json.*`)
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
+ if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 {
+ t.Skip("test (randomly) skipped on 32-bit windows")
+ }
execBlockTest(t, bt, test)
})
// There is also a LegacyTests folder, containing blockchain tests generated
@@ -69,19 +74,19 @@ func TestExecutionSpec(t *testing.T) {
}
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
- if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil)); err != nil {
+ if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil {
t.Errorf("test in hash mode without snapshotter failed: %v", err)
return
}
- if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil)); err != nil {
+ if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil {
t.Errorf("test in hash mode with snapshotter failed: %v", err)
return
}
- if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil)); err != nil {
+ if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil {
t.Errorf("test in path mode without snapshotter failed: %v", err)
return
}
- if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil)); err != nil {
+ if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil {
t.Errorf("test in path mode with snapshotter failed: %v", err)
return
}
diff --git a/tests/block_test_util.go b/tests/block_test_util.go
index ad1d34fb2..e0130be48 100644
--- a/tests/block_test_util.go
+++ b/tests/block_test_util.go
@@ -108,7 +108,7 @@ type btHeaderMarshaling struct {
ExcessBlobGas *math.HexOrDecimal64
}
-func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) error {
+func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) {
config, ok := Forks[t.json.Network]
if !ok {
return UnsupportedForkError{t.json.Network}
@@ -116,7 +116,9 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) er
// import pre accounts & construct test genesis block & state root
var (
db = rawdb.NewMemoryDatabase()
- tconf = &trie.Config{}
+ tconf = &trie.Config{
+ Preimages: true,
+ }
)
if scheme == rawdb.PathScheme {
tconf.PathDB = pathdb.Defaults
@@ -141,7 +143,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) er
// Wrap the original engine within the beacon-engine
engine := beacon.New(ethash.NewFaker())
- cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme}
+ cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme, Preimages: true}
if snapshotter {
cache.SnapshotLimit = 1
cache.SnapshotWait = true
@@ -158,6 +160,11 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) er
if err != nil {
return err
}
+ // Import succeeded: regardless of whether the _test_ succeeds or not, schedule
+ // the post-check to run
+ if postCheck != nil {
+ defer postCheck(result, chain)
+ }
cmlast := chain.CurrentBlock().Hash()
if common.Hash(t.json.BestBlock) != cmlast {
return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast)
@@ -330,6 +337,12 @@ func (t *BlockTest) validatePostState(statedb *state.StateDB) error {
if nonce2 != acct.Nonce {
return fmt.Errorf("account nonce mismatch for addr: %s want: %d have: %d", addr, acct.Nonce, nonce2)
}
+ for k, v := range acct.Storage {
+ v2 := statedb.GetState(addr, k)
+ if v2 != v {
+ return fmt.Errorf("account storage mismatch for addr: %s, slot: %x, want: %x, have: %x", addr, k, v, v2)
+ }
+ }
}
return nil
}
diff --git a/tests/fuzzers/bitutil/compress_test.go b/tests/fuzzers/bitutil/compress_test.go
deleted file mode 100644
index ed9d27eb3..000000000
--- a/tests/fuzzers/bitutil/compress_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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 .
-
-package bitutil
-
-import (
- "bytes"
- "testing"
-
- "github.com/ethereum/go-ethereum/common/bitutil"
-)
-
-func FuzzEncoder(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzzEncode(data)
- })
-}
-func FuzzDecoder(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzzDecode(data)
- })
-}
-
-// fuzzEncode implements a go-fuzz fuzzer method to test the bitset encoding and
-// decoding algorithm.
-func fuzzEncode(data []byte) {
- proc, _ := bitutil.DecompressBytes(bitutil.CompressBytes(data), len(data))
- if !bytes.Equal(data, proc) {
- panic("content mismatch")
- }
-}
-
-// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and
-// reencoding algorithm.
-func fuzzDecode(data []byte) {
- blob, err := bitutil.DecompressBytes(data, 1024)
- if err != nil {
- return
- }
- // re-compress it (it's OK if the re-compressed differs from the
- // original - the first input may not have been compressed at all)
- comp := bitutil.CompressBytes(blob)
- if len(comp) > len(blob) {
- // After compression, it must be smaller or equal
- panic("bad compression")
- }
- // But decompressing it once again should work
- decomp, err := bitutil.DecompressBytes(data, 1024)
- if err != nil {
- panic(err)
- }
- if !bytes.Equal(decomp, blob) {
- panic("content mismatch")
- }
-}
diff --git a/tests/fuzzers/bls12381/bls12381_fuzz.go b/tests/fuzzers/bls12381/bls12381_fuzz.go
index f04524f76..9a5c56654 100644
--- a/tests/fuzzers/bls12381/bls12381_fuzz.go
+++ b/tests/fuzzers/bls12381/bls12381_fuzz.go
@@ -14,6 +14,9 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
+//go:build cgo
+// +build cgo
+
package bls
import (
diff --git a/tests/fuzzers/bls12381/bls12381_test.go b/tests/fuzzers/bls12381/bls12381_test.go
index 59e4db31d..3e88979d1 100644
--- a/tests/fuzzers/bls12381/bls12381_test.go
+++ b/tests/fuzzers/bls12381/bls12381_test.go
@@ -14,6 +14,9 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
+//go:build cgo
+// +build cgo
+
package bls
import "testing"
diff --git a/tests/fuzzers/keystore/keystore-fuzzer.go b/tests/fuzzers/keystore/keystore-fuzzer.go
deleted file mode 100644
index 07a85d77b..000000000
--- a/tests/fuzzers/keystore/keystore-fuzzer.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2019 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 .
-
-package keystore
-
-import (
- "os"
-
- "github.com/ethereum/go-ethereum/accounts/keystore"
-)
-
-func fuzz(input []byte) int {
- ks := keystore.NewKeyStore("/tmp/ks", keystore.LightScryptN, keystore.LightScryptP)
-
- a, err := ks.NewAccount(string(input))
- if err != nil {
- panic(err)
- }
- if err := ks.Unlock(a, string(input)); err != nil {
- panic(err)
- }
- os.Remove(a.URL.Path)
- return 1
-}
diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go
deleted file mode 100644
index 209dda0bb..000000000
--- a/tests/fuzzers/les/les-fuzzer.go
+++ /dev/null
@@ -1,411 +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 .
-
-package les
-
-import (
- "bytes"
- "encoding/binary"
- "io"
- "math/big"
-
- "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/txpool"
- "github.com/ethereum/go-ethereum/core/txpool/legacypool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/crypto"
- l "github.com/ethereum/go-ethereum/les"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
-)
-
-var (
- bankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- bankAddr = crypto.PubkeyToAddress(bankKey.PublicKey)
- bankFunds = new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether))
-
- testChainLen = 256
- testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
-
- chain *core.BlockChain
- addresses []common.Address
- txHashes []common.Hash
-
- chtTrie *trie.Trie
- bloomTrie *trie.Trie
- chtKeys [][]byte
- bloomKeys [][]byte
-)
-
-func makechain() (bc *core.BlockChain, addresses []common.Address, txHashes []common.Hash) {
- gspec := &core.Genesis{
- Config: params.TestChainConfig,
- Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
- GasLimit: 100000000,
- }
- signer := types.HomesteadSigner{}
- _, blocks, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), testChainLen,
- func(i int, gen *core.BlockGen) {
- var (
- tx *types.Transaction
- addr common.Address
- )
- nonce := uint64(i)
- if i%4 == 0 {
- tx, _ = types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 200000, big.NewInt(params.GWei), testContractCode), signer, bankKey)
- addr = crypto.CreateAddress(bankAddr, nonce)
- } else {
- addr = common.BigToAddress(big.NewInt(int64(i)))
- tx, _ = types.SignTx(types.NewTransaction(nonce, addr, big.NewInt(10000), params.TxGas, big.NewInt(params.GWei), nil), signer, bankKey)
- }
- gen.AddTx(tx)
- addresses = append(addresses, addr)
- txHashes = append(txHashes, tx.Hash())
- })
- bc, _ = core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
- if _, err := bc.InsertChain(blocks); err != nil {
- panic(err)
- }
- return
-}
-
-func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [][]byte) {
- chtTrie = trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), trie.HashDefaults))
- bloomTrie = trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), trie.HashDefaults))
- for i := 0; i < testChainLen; i++ {
- // The element in CHT is ->
- key := make([]byte, 8)
- binary.BigEndian.PutUint64(key, uint64(i+1))
- chtTrie.MustUpdate(key, []byte{0x1, 0xf})
- chtKeys = append(chtKeys, key)
-
- // The element in Bloom trie is <2 byte bit index> + -> bloom
- key2 := make([]byte, 10)
- binary.BigEndian.PutUint64(key2[2:], uint64(i+1))
- bloomTrie.MustUpdate(key2, []byte{0x2, 0xe})
- bloomKeys = append(bloomKeys, key2)
- }
- return
-}
-
-func init() {
- chain, addresses, txHashes = makechain()
- chtTrie, bloomTrie, chtKeys, bloomKeys = makeTries()
-}
-
-type fuzzer struct {
- chain *core.BlockChain
- pool *txpool.TxPool
-
- chainLen int
- addresses []common.Address
- txs []common.Hash
- nonce uint64
-
- chtKeys [][]byte
- bloomKeys [][]byte
- chtTrie *trie.Trie
- bloomTrie *trie.Trie
-
- input io.Reader
- exhausted bool
-}
-
-func newFuzzer(input []byte) *fuzzer {
- pool := legacypool.New(legacypool.DefaultConfig, chain)
- txpool, _ := txpool.New(new(big.Int).SetUint64(legacypool.DefaultConfig.PriceLimit), chain, []txpool.SubPool{pool})
-
- return &fuzzer{
- chain: chain,
- chainLen: testChainLen,
- addresses: addresses,
- txs: txHashes,
- chtTrie: chtTrie,
- bloomTrie: bloomTrie,
- chtKeys: chtKeys,
- bloomKeys: bloomKeys,
- nonce: uint64(len(txHashes)),
- pool: txpool,
- input: bytes.NewReader(input),
- }
-}
-
-func (f *fuzzer) read(size int) []byte {
- out := make([]byte, size)
- if _, err := f.input.Read(out); err != nil {
- f.exhausted = true
- }
- return out
-}
-
-func (f *fuzzer) randomByte() byte {
- d := f.read(1)
- return d[0]
-}
-
-func (f *fuzzer) randomBool() bool {
- d := f.read(1)
- return d[0]&1 == 1
-}
-
-func (f *fuzzer) randomInt(max int) int {
- if max == 0 {
- return 0
- }
- if max <= 256 {
- return int(f.randomByte()) % max
- }
- var a uint16
- if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
- f.exhausted = true
- }
- return int(a % uint16(max))
-}
-
-func (f *fuzzer) randomX(max int) uint64 {
- var a uint16
- if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
- f.exhausted = true
- }
- if a < 0x8000 {
- return uint64(a%uint16(max+1)) - 1
- }
- return (uint64(1)<<(a%64+1) - 1) & (uint64(a) * 343897772345826595)
-}
-
-func (f *fuzzer) randomBlockHash() common.Hash {
- h := f.chain.GetCanonicalHash(uint64(f.randomInt(3 * f.chainLen)))
- if h != (common.Hash{}) {
- return h
- }
- return common.BytesToHash(f.read(common.HashLength))
-}
-
-func (f *fuzzer) randomAddress() []byte {
- i := f.randomInt(3 * len(f.addresses))
- if i < len(f.addresses) {
- return f.addresses[i].Bytes()
- }
- return f.read(common.AddressLength)
-}
-
-func (f *fuzzer) randomCHTTrieKey() []byte {
- i := f.randomInt(3 * len(f.chtKeys))
- if i < len(f.chtKeys) {
- return f.chtKeys[i]
- }
- return f.read(8)
-}
-
-func (f *fuzzer) randomBloomTrieKey() []byte {
- i := f.randomInt(3 * len(f.bloomKeys))
- if i < len(f.bloomKeys) {
- return f.bloomKeys[i]
- }
- return f.read(10)
-}
-
-func (f *fuzzer) randomTxHash() common.Hash {
- i := f.randomInt(3 * len(f.txs))
- if i < len(f.txs) {
- return f.txs[i]
- }
- return common.BytesToHash(f.read(common.HashLength))
-}
-
-func (f *fuzzer) BlockChain() *core.BlockChain {
- return f.chain
-}
-
-func (f *fuzzer) TxPool() *txpool.TxPool {
- return f.pool
-}
-
-func (f *fuzzer) ArchiveMode() bool {
- return false
-}
-
-func (f *fuzzer) AddTxsSync() bool {
- return false
-}
-
-func (f *fuzzer) GetHelperTrie(typ uint, index uint64) *trie.Trie {
- if typ == 0 {
- return f.chtTrie
- } else if typ == 1 {
- return f.bloomTrie
- }
- return nil
-}
-
-type dummyMsg struct {
- data []byte
-}
-
-func (d dummyMsg) Decode(val interface{}) error {
- return rlp.DecodeBytes(d.data, val)
-}
-
-func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) {
- enc, err := rlp.EncodeToBytes(packet)
- if err != nil {
- panic(err)
- }
- version := f.randomInt(3) + 2 // [LES2, LES3, LES4]
- peer, closeFn := l.NewFuzzerPeer(version)
- defer closeFn()
- fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc})
- if err != nil {
- panic(err)
- }
- fn(f, peer, func() bool { return true })
-}
-
-func fuzz(input []byte) int {
- // We expect some large inputs
- if len(input) < 100 {
- return -1
- }
- f := newFuzzer(input)
- if f.exhausted {
- return -1
- }
- for !f.exhausted {
- switch f.randomInt(8) {
- case 0:
- req := &l.GetBlockHeadersPacket{
- Query: l.GetBlockHeadersData{
- Amount: f.randomX(l.MaxHeaderFetch + 1),
- Skip: f.randomX(10),
- Reverse: f.randomBool(),
- },
- }
- if f.randomBool() {
- req.Query.Origin.Hash = f.randomBlockHash()
- } else {
- req.Query.Origin.Number = uint64(f.randomInt(f.chainLen * 2))
- }
- f.doFuzz(l.GetBlockHeadersMsg, req)
-
- case 1:
- req := &l.GetBlockBodiesPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxBodyFetch+1))}
- for i := range req.Hashes {
- req.Hashes[i] = f.randomBlockHash()
- }
- f.doFuzz(l.GetBlockBodiesMsg, req)
-
- case 2:
- req := &l.GetCodePacket{Reqs: make([]l.CodeReq, f.randomInt(l.MaxCodeFetch+1))}
- for i := range req.Reqs {
- req.Reqs[i] = l.CodeReq{
- BHash: f.randomBlockHash(),
- AccountAddress: f.randomAddress(),
- }
- }
- f.doFuzz(l.GetCodeMsg, req)
-
- case 3:
- req := &l.GetReceiptsPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxReceiptFetch+1))}
- for i := range req.Hashes {
- req.Hashes[i] = f.randomBlockHash()
- }
- f.doFuzz(l.GetReceiptsMsg, req)
-
- case 4:
- req := &l.GetProofsPacket{Reqs: make([]l.ProofReq, f.randomInt(l.MaxProofsFetch+1))}
- for i := range req.Reqs {
- if f.randomBool() {
- req.Reqs[i] = l.ProofReq{
- BHash: f.randomBlockHash(),
- AccountAddress: f.randomAddress(),
- Key: f.randomAddress(),
- FromLevel: uint(f.randomX(3)),
- }
- } else {
- req.Reqs[i] = l.ProofReq{
- BHash: f.randomBlockHash(),
- Key: f.randomAddress(),
- FromLevel: uint(f.randomX(3)),
- }
- }
- }
- f.doFuzz(l.GetProofsV2Msg, req)
-
- case 5:
- req := &l.GetHelperTrieProofsPacket{Reqs: make([]l.HelperTrieReq, f.randomInt(l.MaxHelperTrieProofsFetch+1))}
- for i := range req.Reqs {
- switch f.randomInt(3) {
- case 0:
- // Canonical hash trie
- req.Reqs[i] = l.HelperTrieReq{
- Type: 0,
- TrieIdx: f.randomX(3),
- Key: f.randomCHTTrieKey(),
- FromLevel: uint(f.randomX(3)),
- AuxReq: uint(2),
- }
- case 1:
- // Bloom trie
- req.Reqs[i] = l.HelperTrieReq{
- Type: 1,
- TrieIdx: f.randomX(3),
- Key: f.randomBloomTrieKey(),
- FromLevel: uint(f.randomX(3)),
- AuxReq: 0,
- }
- default:
- // Random trie
- req.Reqs[i] = l.HelperTrieReq{
- Type: 2,
- TrieIdx: f.randomX(3),
- Key: f.randomCHTTrieKey(),
- FromLevel: uint(f.randomX(3)),
- AuxReq: 0,
- }
- }
- }
- f.doFuzz(l.GetHelperTrieProofsMsg, req)
-
- case 6:
- req := &l.SendTxPacket{Txs: make([]*types.Transaction, f.randomInt(l.MaxTxSend+1))}
- signer := types.HomesteadSigner{}
- for i := range req.Txs {
- var nonce uint64
- if f.randomBool() {
- nonce = uint64(f.randomByte())
- } else {
- nonce = f.nonce
- f.nonce += 1
- }
- req.Txs[i], _ = types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(10000), params.TxGas, big.NewInt(1000000000*int64(f.randomByte())), nil), signer, bankKey)
- }
- f.doFuzz(l.SendTxV2Msg, req)
-
- case 7:
- req := &l.GetTxStatusPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxTxStatus+1))}
- for i := range req.Hashes {
- req.Hashes[i] = f.randomTxHash()
- }
- f.doFuzz(l.GetTxStatusMsg, req)
- }
- }
- return 0
-}
diff --git a/tests/fuzzers/les/les_test.go b/tests/fuzzers/les/les_test.go
deleted file mode 100644
index 53af45ceb..000000000
--- a/tests/fuzzers/les/les_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 .
-
-package les
-
-import "testing"
-
-func Fuzz(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzz(data)
- })
-}
diff --git a/tests/fuzzers/rlp/corpus/block_with_uncle.rlp b/tests/fuzzers/rlp/corpus/block_with_uncle.rlp
deleted file mode 100644
index 1b49fe6a0..000000000
Binary files a/tests/fuzzers/rlp/corpus/block_with_uncle.rlp and /dev/null differ
diff --git a/tests/fuzzers/rlp/corpus/r.bin b/tests/fuzzers/rlp/corpus/r.bin
deleted file mode 100644
index cb98a76a8..000000000
--- a/tests/fuzzers/rlp/corpus/r.bin
+++ /dev/null
@@ -1 +0,0 @@
-Ë€€€À€ÀÃÀÀÀÀ
\ No newline at end of file
diff --git a/tests/fuzzers/rlp/corpus/transaction.rlp b/tests/fuzzers/rlp/corpus/transaction.rlp
deleted file mode 100644
index 80eea1aec..000000000
--- a/tests/fuzzers/rlp/corpus/transaction.rlp
+++ /dev/null
@@ -1,2 +0,0 @@
-øNƒ“à€€€‚
-• aùËåÀP?-'´{ÏЋDY¯³fÆj\ÿE÷ ~ì•ÒçF?1(íij6@Év±LÀÝÚ‘‘
\ No newline at end of file
diff --git a/tests/fuzzers/rlp/rlp_fuzzer.go b/tests/fuzzers/rlp/rlp_fuzzer.go
deleted file mode 100644
index 0da8ccdd7..000000000
--- a/tests/fuzzers/rlp/rlp_fuzzer.go
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2019 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 .
-
-package rlp
-
-import (
- "bytes"
- "fmt"
- "math/big"
-
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/holiman/uint256"
-)
-
-func decodeEncode(input []byte, val interface{}, i int) {
- if err := rlp.DecodeBytes(input, val); err == nil {
- output, err := rlp.EncodeToBytes(val)
- if err != nil {
- panic(err)
- }
- if !bytes.Equal(input, output) {
- panic(fmt.Sprintf("case %d: encode-decode is not equal, \ninput : %x\noutput: %x", i, input, output))
- }
- }
-}
-
-func fuzz(input []byte) int {
- if len(input) == 0 {
- return 0
- }
- if len(input) > 500*1024 {
- return 0
- }
-
- var i int
- {
- rlp.Split(input)
- }
- {
- if elems, _, err := rlp.SplitList(input); err == nil {
- rlp.CountValues(elems)
- }
- }
-
- {
- rlp.NewStream(bytes.NewReader(input), 0).Decode(new(interface{}))
- }
-
- {
- decodeEncode(input, new(interface{}), i)
- i++
- }
- {
- var v struct {
- Int uint
- String string
- Bytes []byte
- }
- decodeEncode(input, &v, i)
- i++
- }
-
- {
- type Types struct {
- Bool bool
- Raw rlp.RawValue
- Slice []*Types
- Iface []interface{}
- }
- var v Types
- decodeEncode(input, &v, i)
- i++
- }
- {
- type AllTypes struct {
- Int uint
- String string
- Bytes []byte
- Bool bool
- Raw rlp.RawValue
- Slice []*AllTypes
- Array [3]*AllTypes
- Iface []interface{}
- }
- var v AllTypes
- decodeEncode(input, &v, i)
- i++
- }
- {
- decodeEncode(input, [10]byte{}, i)
- i++
- }
- {
- var v struct {
- Byte [10]byte
- Rool [10]bool
- }
- decodeEncode(input, &v, i)
- i++
- }
- {
- var h types.Header
- decodeEncode(input, &h, i)
- i++
- var b types.Block
- decodeEncode(input, &b, i)
- i++
- var t types.Transaction
- decodeEncode(input, &t, i)
- i++
- var txs types.Transactions
- decodeEncode(input, &txs, i)
- i++
- var rs types.Receipts
- decodeEncode(input, &rs, i)
- }
- {
- i++
- var v struct {
- AnIntPtr *big.Int
- AnInt big.Int
- AnU256Ptr *uint256.Int
- AnU256 uint256.Int
- NotAnU256 [4]uint64
- }
- decodeEncode(input, &v, i)
- }
- return 1
-}
diff --git a/tests/fuzzers/secp256k1/secp_test.go b/tests/fuzzers/secp256k1/secp_test.go
index fbdd8e6ac..ca3039764 100644
--- a/tests/fuzzers/secp256k1/secp_test.go
+++ b/tests/fuzzers/secp256k1/secp_test.go
@@ -35,7 +35,7 @@ func Fuzz(f *testing.F) {
})
}
-func fuzz(dataP1, dataP2 []byte) int {
+func fuzz(dataP1, dataP2 []byte) {
var (
curveA = secp256k1.S256()
curveB = btcec.S256()
@@ -50,5 +50,4 @@ func fuzz(dataP1, dataP2 []byte) int {
fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2)
panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY))
}
- return 0
}
diff --git a/tests/fuzzers/snap/fuzz_test.go b/tests/fuzzers/snap/fuzz_test.go
deleted file mode 100644
index 1c39f2bb5..000000000
--- a/tests/fuzzers/snap/fuzz_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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 .
-
-package snap
-
-import (
- "testing"
-
- "github.com/ethereum/go-ethereum/eth/protocols/snap"
-)
-
-func FuzzARange(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &snap.GetAccountRangePacket{}, snap.GetAccountRangeMsg)
- })
-}
-
-func FuzzSRange(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &snap.GetStorageRangesPacket{}, snap.GetStorageRangesMsg)
- })
-}
-
-func FuzzByteCodes(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &snap.GetByteCodesPacket{}, snap.GetByteCodesMsg)
- })
-}
-
-func FuzzTrieNodes(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- doFuzz(data, &snap.GetTrieNodesPacket{}, snap.GetTrieNodesMsg)
- })
-}
diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go
deleted file mode 100644
index 9e02176e3..000000000
--- a/tests/fuzzers/stacktrie/trie_fuzzer.go
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright 2020 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 .
-
-package stacktrie
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "hash"
- "io"
-
- "github.com/ethereum/go-ethereum/common"
- "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/ethdb"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "golang.org/x/crypto/sha3"
- "golang.org/x/exp/slices"
-)
-
-type fuzzer struct {
- input io.Reader
- exhausted bool
- debugging bool
-}
-
-func (f *fuzzer) read(size int) []byte {
- out := make([]byte, size)
- if _, err := f.input.Read(out); err != nil {
- f.exhausted = true
- }
- return out
-}
-
-func (f *fuzzer) readSlice(min, max int) []byte {
- var a uint16
- binary.Read(f.input, binary.LittleEndian, &a)
- size := min + int(a)%(max-min)
- out := make([]byte, size)
- if _, err := f.input.Read(out); err != nil {
- f.exhausted = true
- }
- return out
-}
-
-// spongeDb is a dummy db backend which accumulates writes in a sponge
-type spongeDb struct {
- sponge hash.Hash
- debug bool
-}
-
-func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") }
-func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") }
-func (s *spongeDb) Delete(key []byte) error { panic("implement me") }
-func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} }
-func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} }
-func (s *spongeDb) NewSnapshot() (ethdb.Snapshot, error) { panic("implement me") }
-func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") }
-func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") }
-func (s *spongeDb) Close() error { return nil }
-
-func (s *spongeDb) Put(key []byte, value []byte) error {
- if s.debug {
- fmt.Printf("db.Put %x : %x\n", key, value)
- }
- s.sponge.Write(key)
- s.sponge.Write(value)
- return nil
-}
-func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") }
-
-// spongeBatch is a dummy batch which immediately writes to the underlying spongedb
-type spongeBatch struct {
- db *spongeDb
-}
-
-func (b *spongeBatch) Put(key, value []byte) error {
- b.db.Put(key, value)
- return nil
-}
-func (b *spongeBatch) Delete(key []byte) error { panic("implement me") }
-func (b *spongeBatch) ValueSize() int { return 100 }
-func (b *spongeBatch) Write() error { return nil }
-func (b *spongeBatch) Reset() {}
-func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil }
-
-type kv struct {
- k, v []byte
-}
-
-// Fuzz is the fuzzing entry-point.
-// The function must return
-//
-// - 1 if the fuzzer should increase priority of the
-// given input during subsequent fuzzing (for example, the input is lexically
-// correct and was parsed successfully);
-// - -1 if the input must not be added to corpus even if gives new coverage; and
-// - 0 otherwise
-//
-// other values are reserved for future use.
-func fuzz(data []byte) int {
- f := fuzzer{
- input: bytes.NewReader(data),
- exhausted: false,
- }
- return f.fuzz()
-}
-
-func Debug(data []byte) int {
- f := fuzzer{
- input: bytes.NewReader(data),
- exhausted: false,
- debugging: true,
- }
- return f.fuzz()
-}
-
-func (f *fuzzer) fuzz() int {
- // This spongeDb is used to check the sequence of disk-db-writes
- var (
- spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
- dbA = trie.NewDatabase(rawdb.NewDatabase(spongeA), nil)
- trieA = trie.NewEmpty(dbA)
- spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
- dbB = trie.NewDatabase(rawdb.NewDatabase(spongeB), nil)
-
- options = trie.NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
- })
- trieB = trie.NewStackTrie(options)
- vals []kv
- useful bool
- maxElements = 10000
- // operate on unique keys only
- keys = make(map[string]struct{})
- )
- // Fill the trie with elements
- for i := 0; !f.exhausted && i < maxElements; i++ {
- k := f.read(32)
- v := f.readSlice(1, 500)
- if f.exhausted {
- // If it was exhausted while reading, the value may be all zeroes,
- // thus 'deletion' which is not supported on stacktrie
- break
- }
- if _, present := keys[string(k)]; present {
- // This key is a duplicate, ignore it
- continue
- }
- keys[string(k)] = struct{}{}
- vals = append(vals, kv{k: k, v: v})
- trieA.MustUpdate(k, v)
- useful = true
- }
- if !useful {
- return 0
- }
- // Flush trie -> database
- rootA, nodes, err := trieA.Commit(false)
- if err != nil {
- panic(err)
- }
- if nodes != nil {
- dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- }
- // Flush memdb -> disk (sponge)
- dbA.Commit(rootA, false)
-
- // Stacktrie requires sorted insertion
- slices.SortFunc(vals, func(a, b kv) int {
- return bytes.Compare(a.k, b.k)
- })
- for _, kv := range vals {
- if f.debugging {
- fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v)
- }
- trieB.MustUpdate(kv.k, kv.v)
- }
- rootB := trieB.Hash()
- trieB.Commit()
- if rootA != rootB {
- panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
- }
- sumA := spongeA.sponge.Sum(nil)
- sumB := spongeB.sponge.Sum(nil)
- if !bytes.Equal(sumA, sumB) {
- panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
- }
-
- // Ensure all the nodes are persisted correctly
- var (
- nodeset = make(map[string][]byte) // path -> blob
- optionsC = trie.NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- if crypto.Keccak256Hash(blob) != hash {
- panic("invalid node blob")
- }
- nodeset[string(path)] = common.CopyBytes(blob)
- })
- trieC = trie.NewStackTrie(optionsC)
- checked int
- )
- for _, kv := range vals {
- trieC.MustUpdate(kv.k, kv.v)
- }
- rootC := trieC.Commit()
- if rootA != rootC {
- panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC))
- }
- trieA, _ = trie.New(trie.TrieID(rootA), dbA)
- iterA := trieA.MustNodeIterator(nil)
- for iterA.Next(true) {
- if iterA.Hash() == (common.Hash{}) {
- if _, present := nodeset[string(iterA.Path())]; present {
- panic("unexpected tiny node")
- }
- continue
- }
- nodeBlob, present := nodeset[string(iterA.Path())]
- if !present {
- panic("missing node")
- }
- if !bytes.Equal(nodeBlob, iterA.NodeBlob()) {
- panic("node blob is not matched")
- }
- checked += 1
- }
- if checked != len(nodeset) {
- panic("node number is not matched")
- }
- return 1
-}
diff --git a/tests/fuzzers/stacktrie/trie_test.go b/tests/fuzzers/stacktrie/trie_test.go
deleted file mode 100644
index f6f755f76..000000000
--- a/tests/fuzzers/stacktrie/trie_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 .
-
-package stacktrie
-
-import "testing"
-
-func Fuzz(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzz(data)
- })
-}
diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go
deleted file mode 100644
index a505fa78a..000000000
--- a/tests/fuzzers/trie/trie-fuzzer.go
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2019 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 .
-
-package trie
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// randTest performs random trie operations.
-// Instances of this test are created by Generate.
-type randTest []randTestStep
-
-type randTestStep struct {
- op int
- key []byte // for opUpdate, opDelete, opGet
- value []byte // for opUpdate
- err error // for debugging
-}
-
-type proofDb struct{}
-
-func (proofDb) Put(key []byte, value []byte) error {
- return nil
-}
-
-func (proofDb) Delete(key []byte) error {
- return nil
-}
-
-const (
- opUpdate = iota
- opDelete
- opGet
- opHash
- opCommit
- opItercheckhash
- opProve
- opMax // boundary value, not an actual op
-)
-
-type dataSource struct {
- input []byte
- reader *bytes.Reader
-}
-
-func newDataSource(input []byte) *dataSource {
- return &dataSource{
- input, bytes.NewReader(input),
- }
-}
-func (ds *dataSource) readByte() byte {
- if b, err := ds.reader.ReadByte(); err != nil {
- return 0
- } else {
- return b
- }
-}
-func (ds *dataSource) Read(buf []byte) (int, error) {
- return ds.reader.Read(buf)
-}
-func (ds *dataSource) Ended() bool {
- return ds.reader.Len() == 0
-}
-
-func Generate(input []byte) randTest {
- var allKeys [][]byte
- r := newDataSource(input)
- genKey := func() []byte {
- if len(allKeys) < 2 || r.readByte() < 0x0f {
- // new key
- key := make([]byte, r.readByte()%50)
- r.Read(key)
- allKeys = append(allKeys, key)
- return key
- }
- // use existing key
- return allKeys[int(r.readByte())%len(allKeys)]
- }
-
- var steps randTest
-
- for i := 0; !r.Ended(); i++ {
- step := randTestStep{op: int(r.readByte()) % opMax}
- switch step.op {
- case opUpdate:
- step.key = genKey()
- step.value = make([]byte, 8)
- binary.BigEndian.PutUint64(step.value, uint64(i))
- case opGet, opDelete, opProve:
- step.key = genKey()
- }
- steps = append(steps, step)
- if len(steps) > 500 {
- break
- }
- }
-
- return steps
-}
-
-// Fuzz is the fuzzing entry-point.
-// The function must return
-//
-// - 1 if the fuzzer should increase priority of the
-// given input during subsequent fuzzing (for example, the input is lexically
-// correct and was parsed successfully);
-// - -1 if the input must not be added to corpus even if gives new coverage; and
-// - 0 otherwise
-//
-// other values are reserved for future use.
-func fuzz(input []byte) int {
- program := Generate(input)
- if len(program) == 0 {
- return 0
- }
- if err := runRandTest(program); err != nil {
- panic(err)
- }
- return 1
-}
-
-func runRandTest(rt randTest) error {
- var (
- triedb = trie.NewDatabase(rawdb.NewMemoryDatabase(), nil)
- tr = trie.NewEmpty(triedb)
- origin = types.EmptyRootHash
- values = make(map[string]string) // tracks content of the trie
- )
- for i, step := range rt {
- switch step.op {
- case opUpdate:
- tr.MustUpdate(step.key, step.value)
- values[string(step.key)] = string(step.value)
- case opDelete:
- tr.MustDelete(step.key)
- delete(values, string(step.key))
- case opGet:
- v := tr.MustGet(step.key)
- want := values[string(step.key)]
- if string(v) != want {
- rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want)
- }
- case opHash:
- tr.Hash()
- case opCommit:
- hash, nodes, err := tr.Commit(false)
- if err != nil {
- return err
- }
- if nodes != nil {
- if err := triedb.Update(hash, origin, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- return err
- }
- }
- newtr, err := trie.New(trie.TrieID(hash), triedb)
- if err != nil {
- return err
- }
- tr = newtr
- origin = hash
- case opItercheckhash:
- checktr := trie.NewEmpty(triedb)
- it := trie.NewIterator(tr.MustNodeIterator(nil))
- for it.Next() {
- checktr.MustUpdate(it.Key, it.Value)
- }
- if tr.Hash() != checktr.Hash() {
- return errors.New("hash mismatch in opItercheckhash")
- }
- case opProve:
- rt[i].err = tr.Prove(step.key, proofDb{})
- }
- // Abort the test on error.
- if rt[i].err != nil {
- return rt[i].err
- }
- }
- return nil
-}
diff --git a/tests/fuzzers/trie/trie_test.go b/tests/fuzzers/trie/trie_test.go
deleted file mode 100644
index a7d28a806..000000000
--- a/tests/fuzzers/trie/trie_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 .
-
-package trie
-
-import "testing"
-
-func Fuzz(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzz(data)
- })
-}
diff --git a/tests/fuzzers/vflux/clientpool-fuzzer.go b/tests/fuzzers/vflux/clientpool-fuzzer.go
deleted file mode 100644
index de694a7b3..000000000
--- a/tests/fuzzers/vflux/clientpool-fuzzer.go
+++ /dev/null
@@ -1,333 +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 .
-
-package vflux
-
-import (
- "bytes"
- "encoding/binary"
- "io"
- "math"
- "math/big"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb/memorydb"
- "github.com/ethereum/go-ethereum/les/vflux"
- vfs "github.com/ethereum/go-ethereum/les/vflux/server"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var (
- debugMode = false
- doLog = func(msg string, ctx ...interface{}) {
- if !debugMode {
- return
- }
- log.Info(msg, ctx...)
- }
-)
-
-type fuzzer struct {
- peers [256]*clientPeer
- disconnectList []*clientPeer
- input io.Reader
- exhausted bool
- activeCount, activeCap uint64
- maxCount, maxCap uint64
-}
-
-type clientPeer struct {
- fuzzer *fuzzer
- node *enode.Node
- freeID string
- timeout time.Duration
-
- balance vfs.ConnectedBalance
- capacity uint64
-}
-
-func (p *clientPeer) Node() *enode.Node {
- return p.node
-}
-
-func (p *clientPeer) FreeClientId() string {
- return p.freeID
-}
-
-func (p *clientPeer) InactiveAllowance() time.Duration {
- return p.timeout
-}
-
-func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) {
- origin, originTotal := p.capacity, p.fuzzer.activeCap
- p.fuzzer.activeCap -= p.capacity
- if p.capacity != 0 {
- p.fuzzer.activeCount--
- }
- p.capacity = newCap
- p.fuzzer.activeCap += p.capacity
- if p.capacity != 0 {
- p.fuzzer.activeCount++
- }
- doLog("Update capacity", "peer", p.node.ID(), "origin", origin, "cap", newCap, "origintotal", originTotal, "total", p.fuzzer.activeCap, "requested", requested)
-}
-
-func (p *clientPeer) Disconnect() {
- origin, originTotal := p.capacity, p.fuzzer.activeCap
- p.fuzzer.disconnectList = append(p.fuzzer.disconnectList, p)
- p.fuzzer.activeCap -= p.capacity
- if p.capacity != 0 {
- p.fuzzer.activeCount--
- }
- p.capacity = 0
- p.balance = nil
- doLog("Disconnect", "peer", p.node.ID(), "origin", origin, "origintotal", originTotal, "total", p.fuzzer.activeCap)
-}
-
-func newFuzzer(input []byte) *fuzzer {
- f := &fuzzer{
- input: bytes.NewReader(input),
- }
- for i := range f.peers {
- f.peers[i] = &clientPeer{
- fuzzer: f,
- node: enode.SignNull(new(enr.Record), enode.ID{byte(i)}),
- freeID: string([]byte{byte(i)}),
- timeout: f.randomDelay(),
- }
- }
- return f
-}
-
-func (f *fuzzer) read(size int) []byte {
- out := make([]byte, size)
- if _, err := f.input.Read(out); err != nil {
- f.exhausted = true
- }
- return out
-}
-
-func (f *fuzzer) randomByte() byte {
- d := f.read(1)
- return d[0]
-}
-
-func (f *fuzzer) randomBool() bool {
- d := f.read(1)
- return d[0]&1 == 1
-}
-
-func (f *fuzzer) randomInt(max int) int {
- if max == 0 {
- return 0
- }
- if max <= 256 {
- return int(f.randomByte()) % max
- }
- var a uint16
- if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
- f.exhausted = true
- }
- return int(a % uint16(max))
-}
-
-func (f *fuzzer) randomTokenAmount(signed bool) int64 {
- x := uint64(f.randomInt(65000))
- x = x * x * x * x
-
- if signed && (x&1) == 1 {
- if x <= math.MaxInt64 {
- return -int64(x)
- }
- return math.MinInt64
- }
- if x <= math.MaxInt64 {
- return int64(x)
- }
- return math.MaxInt64
-}
-
-func (f *fuzzer) randomDelay() time.Duration {
- delay := f.randomByte()
- if delay < 128 {
- return time.Duration(delay) * time.Second
- }
- return 0
-}
-
-func (f *fuzzer) randomFactors() vfs.PriceFactors {
- return vfs.PriceFactors{
- TimeFactor: float64(f.randomByte()) / 25500,
- CapacityFactor: float64(f.randomByte()) / 255,
- RequestFactor: float64(f.randomByte()) / 255,
- }
-}
-
-func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance, id enode.ID) {
- switch f.randomInt(3) {
- case 0:
- cost := uint64(f.randomTokenAmount(false))
- balance.RequestServed(cost)
- doLog("Serve request cost", "id", id, "amount", cost)
- case 1:
- posFactor, negFactor := f.randomFactors(), f.randomFactors()
- balance.SetPriceFactors(posFactor, negFactor)
- doLog("Set price factor", "pos", posFactor, "neg", negFactor)
- case 2:
- balance.GetBalance()
- balance.GetRawBalance()
- balance.GetPriceFactors()
- }
-}
-
-func (f *fuzzer) atomicBalanceOp(balance vfs.AtomicBalanceOperator, id enode.ID) {
- switch f.randomInt(3) {
- case 0:
- amount := f.randomTokenAmount(true)
- balance.AddBalance(amount)
- doLog("Add balance", "id", id, "amount", amount)
- case 1:
- pos, neg := uint64(f.randomTokenAmount(false)), uint64(f.randomTokenAmount(false))
- balance.SetBalance(pos, neg)
- doLog("Set balance", "id", id, "pos", pos, "neg", neg)
- case 2:
- balance.GetBalance()
- balance.GetRawBalance()
- balance.GetPriceFactors()
- }
-}
-
-func fuzzClientPool(input []byte) int {
- if len(input) > 10000 {
- return -1
- }
- f := newFuzzer(input)
- if f.exhausted {
- return 0
- }
- clock := &mclock.Simulated{}
- db := memorydb.New()
- pool := vfs.NewClientPool(db, 10, f.randomDelay(), clock, func() bool { return true })
- pool.Start()
- defer pool.Stop()
-
- count := 0
- for !f.exhausted && count < 1000 {
- count++
- switch f.randomInt(11) {
- case 0:
- i := int(f.randomByte())
- f.peers[i].balance = pool.Register(f.peers[i])
- doLog("Register peer", "id", f.peers[i].node.ID())
- case 1:
- i := int(f.randomByte())
- f.peers[i].Disconnect()
- doLog("Disconnect peer", "id", f.peers[i].node.ID())
- case 2:
- f.maxCount = uint64(f.randomByte())
- f.maxCap = uint64(f.randomByte())
- f.maxCap *= f.maxCap
-
- count, cap := pool.Limits()
- pool.SetLimits(f.maxCount, f.maxCap)
- doLog("Set limits", "maxcount", f.maxCount, "maxcap", f.maxCap, "origincount", count, "oricap", cap)
- case 3:
- bias := f.randomDelay()
- pool.SetConnectedBias(f.randomDelay())
- doLog("Set connection bias", "bias", bias)
- case 4:
- pos, neg := f.randomFactors(), f.randomFactors()
- pool.SetDefaultFactors(pos, neg)
- doLog("Set default factors", "pos", pos, "neg", neg)
- case 5:
- pos, neg := uint64(f.randomInt(50000)), uint64(f.randomInt(50000))
- pool.SetExpirationTCs(pos, neg)
- doLog("Set expiration constants", "pos", pos, "neg", neg)
- case 6:
- var (
- index = f.randomByte()
- reqCap = uint64(f.randomByte())
- bias = f.randomDelay()
- requested = f.randomBool()
- )
- pool.SetCapacity(f.peers[index].node, reqCap, bias, requested)
- doLog("Set capacity", "id", f.peers[index].node.ID(), "reqcap", reqCap, "bias", bias, "requested", requested)
- case 7:
- index := f.randomByte()
- if balance := f.peers[index].balance; balance != nil {
- f.connectedBalanceOp(balance, f.peers[index].node.ID())
- }
- case 8:
- index := f.randomByte()
- pool.BalanceOperation(f.peers[index].node.ID(), f.peers[index].freeID, func(balance vfs.AtomicBalanceOperator) {
- count := f.randomInt(4)
- for i := 0; i < count; i++ {
- f.atomicBalanceOp(balance, f.peers[index].node.ID())
- }
- })
- case 9:
- pool.TotalTokenAmount()
- pool.GetExpirationTCs()
- pool.Active()
- pool.Limits()
- pool.GetPosBalanceIDs(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].node.ID(), f.randomInt(100))
- case 10:
- req := vflux.CapacityQueryReq{
- Bias: uint64(f.randomByte()),
- AddTokens: make([]vflux.IntOrInf, f.randomInt(vflux.CapacityQueryMaxLen+1)),
- }
- for i := range req.AddTokens {
- v := vflux.IntOrInf{Type: uint8(f.randomInt(4))}
- if v.Type < 2 {
- v.Value = *big.NewInt(f.randomTokenAmount(false))
- }
- req.AddTokens[i] = v
- }
- reqEnc, err := rlp.EncodeToBytes(&req)
- if err != nil {
- panic(err)
- }
- p := int(f.randomByte())
- if p < len(reqEnc) {
- reqEnc[p] = f.randomByte()
- }
- pool.Handle(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, vflux.CapacityQueryName, reqEnc)
- }
-
- for _, peer := range f.disconnectList {
- pool.Unregister(peer)
- doLog("Unregister peer", "id", peer.node.ID())
- }
- f.disconnectList = nil
- if d := f.randomDelay(); d > 0 {
- clock.Run(d)
- }
- doLog("Clientpool stats in fuzzer", "count", f.activeCap, "maxcount", f.maxCount, "cap", f.activeCap, "maxcap", f.maxCap)
- activeCount, activeCap := pool.Active()
- doLog("Clientpool stats in pool", "count", activeCount, "cap", activeCap)
- if activeCount != f.activeCount || activeCap != f.activeCap {
- panic(nil)
- }
- if f.activeCount > f.maxCount || f.activeCap > f.maxCap {
- panic(nil)
- }
- }
- return 0
-}
diff --git a/tests/fuzzers/vflux/clientpool_test.go b/tests/fuzzers/vflux/clientpool_test.go
deleted file mode 100644
index 40c5f2290..000000000
--- a/tests/fuzzers/vflux/clientpool_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 .
-
-package vflux
-
-import "testing"
-
-func FuzzClientPool(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzzClientPool(data)
- })
-}
diff --git a/tests/gen_stenv.go b/tests/gen_stenv.go
index 71f006317..a5bd0d5fc 100644
--- a/tests/gen_stenv.go
+++ b/tests/gen_stenv.go
@@ -16,13 +16,14 @@ var _ = (*stEnvMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (s stEnv) MarshalJSON() ([]byte, error) {
type stEnv struct {
- Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
- Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"optional"`
- Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"`
- GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
- Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
- Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
- BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"`
+ Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
+ Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"optional"`
+ Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"`
+ GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
+ Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
+ Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
+ BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"`
+ ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"`
}
var enc stEnv
enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
@@ -32,19 +33,21 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
enc.Number = math.HexOrDecimal64(s.Number)
enc.Timestamp = math.HexOrDecimal64(s.Timestamp)
enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee)
+ enc.ExcessBlobGas = (*math.HexOrDecimal64)(s.ExcessBlobGas)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (s *stEnv) UnmarshalJSON(input []byte) error {
type stEnv struct {
- Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
- Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"optional"`
- Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"`
- GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
- Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
- Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
- BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"`
+ Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"`
+ Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"optional"`
+ Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"`
+ GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"`
+ Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"`
+ Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"`
+ BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"`
+ ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"`
}
var dec stEnv
if err := json.Unmarshal(input, &dec); err != nil {
@@ -75,5 +78,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
if dec.BaseFee != nil {
s.BaseFee = (*big.Int)(dec.BaseFee)
}
+ if dec.ExcessBlobGas != nil {
+ s.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
+ }
return nil
}
diff --git a/tests/state_test.go b/tests/state_test.go
index 094dafcaf..ae78a53a7 100644
--- a/tests/state_test.go
+++ b/tests/state_test.go
@@ -21,9 +21,11 @@ import (
"bytes"
"fmt"
"math/big"
+ "math/rand"
"os"
"path/filepath"
"reflect"
+ "runtime"
"strings"
"testing"
"time"
@@ -76,6 +78,10 @@ func TestState(t *testing.T) {
benchmarksDir,
} {
st.walk(t, dir, func(t *testing.T, name string, test *StateTest) {
+ if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 {
+ t.Skip("test (randomly) skipped on 32-bit windows")
+ return
+ }
for _, subtest := range test.Subtests() {
subtest := subtest
key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index)
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 745a3c6b2..19387b539 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
@@ -83,23 +84,25 @@ type stPostState struct {
//go:generate go run github.com/fjl/gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go
type stEnv struct {
- Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
- Difficulty *big.Int `json:"currentDifficulty" gencodec:"optional"`
- Random *big.Int `json:"currentRandom" gencodec:"optional"`
- GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
- Number uint64 `json:"currentNumber" gencodec:"required"`
- Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
- BaseFee *big.Int `json:"currentBaseFee" gencodec:"optional"`
+ Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
+ Difficulty *big.Int `json:"currentDifficulty" gencodec:"optional"`
+ Random *big.Int `json:"currentRandom" gencodec:"optional"`
+ GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
+ Number uint64 `json:"currentNumber" gencodec:"required"`
+ Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
+ BaseFee *big.Int `json:"currentBaseFee" gencodec:"optional"`
+ ExcessBlobGas *uint64 `json:"currentExcessBlobGas" gencodec:"optional"`
}
type stEnvMarshaling struct {
- Coinbase common.UnprefixedAddress
- Difficulty *math.HexOrDecimal256
- Random *math.HexOrDecimal256
- GasLimit math.HexOrDecimal64
- Number math.HexOrDecimal64
- Timestamp math.HexOrDecimal64
- BaseFee *math.HexOrDecimal256
+ Coinbase common.UnprefixedAddress
+ Difficulty *math.HexOrDecimal256
+ Random *math.HexOrDecimal256
+ GasLimit math.HexOrDecimal64
+ Number math.HexOrDecimal64
+ Timestamp math.HexOrDecimal64
+ BaseFee *math.HexOrDecimal256
+ ExcessBlobGas *math.HexOrDecimal64
}
//go:generate go run github.com/fjl/gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go
@@ -283,6 +286,9 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
context.Random = &rnd
context.Difficulty = big.NewInt(0)
}
+ if config.IsCancun(new(big.Int), block.Time()) && t.json.Env.ExcessBlobGas != nil {
+ context.BlobBaseFee = eip4844.CalcBlobFee(*t.json.Env.ExcessBlobGas)
+ }
evm := vm.NewEVM(context, txContext, statedb, config, vmconfig)
// Execute the message.
diff --git a/trie/database.go b/trie/database.go
index 1e59f0908..e20f7ef90 100644
--- a/trie/database.go
+++ b/trie/database.go
@@ -31,6 +31,7 @@ import (
// Config defines all necessary options for database.
type Config struct {
Preimages bool // Flag whether the preimage of node key is recorded
+ IsVerkle bool // Flag whether the db is holding a verkle tree
HashDB *hashdb.Config // Configs for hash-based scheme
PathDB *pathdb.Config // Configs for experimental path-based scheme
}
@@ -239,17 +240,6 @@ func (db *Database) Dereference(root common.Hash) error {
return nil
}
-// Node retrieves the rlp-encoded node blob with provided node hash. It's
-// only supported by hash-based database and will return an error for others.
-// Note, this function should be deprecated once ETH66 is deprecated.
-func (db *Database) Node(hash common.Hash) ([]byte, error) {
- hdb, ok := db.backend.(*hashdb.Database)
- if !ok {
- return nil, errors.New("not supported")
- }
- return hdb.Node(hash)
-}
-
// Recover rollbacks the database to a specified historical point. The state is
// supported as the rollback destination only if it's canonical state and the
// corresponding trie histories are existent. It's only supported by path-based
@@ -318,3 +308,8 @@ func (db *Database) SetBufferSize(size int) error {
}
return pdb.SetBufferSize(size)
}
+
+// IsVerkle returns the indicator if the database is holding a verkle tree.
+func (db *Database) IsVerkle() bool {
+ return db.config.IsVerkle
+}
diff --git a/trie/hasher.go b/trie/hasher.go
index e594d6d6b..1e063d802 100644
--- a/trie/hasher.go
+++ b/trie/hasher.go
@@ -84,20 +84,19 @@ func (h *hasher) hash(n node, force bool) (hashed node, cached node) {
}
return hashed, cached
default:
- // Value and hash nodes don't have children so they're left as were
+ // Value and hash nodes don't have children, so they're left as were
return n, n
}
}
// hashShortNodeChildren collapses the short node. The returned collapsed node
// holds a live reference to the Key, and must not be modified.
-// The cached
func (h *hasher) hashShortNodeChildren(n *shortNode) (collapsed, cached *shortNode) {
// Hash the short node's child, caching the newly hashed subtree
collapsed, cached = n.copy(), n.copy()
// Previously, we did copy this one. We don't seem to need to actually
// do that, since we don't overwrite/reuse keys
- //cached.Key = common.CopyBytes(n.Key)
+ // cached.Key = common.CopyBytes(n.Key)
collapsed.Key = hexToCompact(n.Key)
// Unless the child is a valuenode or hashnode, hash it
switch n.Val.(type) {
@@ -153,7 +152,7 @@ func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
return h.hashData(enc)
}
-// shortnodeToHash is used to creates a hashNode from a set of hashNodes, (which
+// fullnodeToHash is used to create a hashNode from a fullNode, (which
// may contain nil values)
func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
n.encode(h.encbuf)
@@ -203,7 +202,7 @@ func (h *hasher) proofHash(original node) (collapsed, hashed node) {
fn, _ := h.hashFullNodeChildren(n)
return fn, h.fullnodeToHash(fn, false)
default:
- // Value and hash nodes don't have children so they're left as were
+ // Value and hash nodes don't have children, so they're left as were
return n, n
}
}
diff --git a/trie/iterator.go b/trie/iterator.go
index 6f054a724..83ccc0740 100644
--- a/trie/iterator.go
+++ b/trie/iterator.go
@@ -144,7 +144,8 @@ type nodeIterator struct {
path []byte // Path to the current node
err error // Failure set in case of an internal error in the iterator
- resolver NodeResolver // optional node resolver for avoiding disk hits
+ resolver NodeResolver // optional node resolver for avoiding disk hits
+ pool []*nodeIteratorState // local pool for iteratorstates
}
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
@@ -172,6 +173,24 @@ func newNodeIterator(trie *Trie, start []byte) NodeIterator {
return it
}
+func (it *nodeIterator) putInPool(item *nodeIteratorState) {
+ if len(it.pool) < 40 {
+ item.node = nil
+ it.pool = append(it.pool, item)
+ }
+}
+
+func (it *nodeIterator) getFromPool() *nodeIteratorState {
+ idx := len(it.pool) - 1
+ if idx < 0 {
+ return new(nodeIteratorState)
+ }
+ el := it.pool[idx]
+ it.pool[idx] = nil
+ it.pool = it.pool[:idx]
+ return el
+}
+
func (it *nodeIterator) AddResolver(resolver NodeResolver) {
it.resolver = resolver
}
@@ -423,8 +442,9 @@ func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
return nil
}
-func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) {
+func (it *nodeIterator) findChild(n *fullNode, index int, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) {
var (
+ path = it.path
child node
state *nodeIteratorState
childPath []byte
@@ -433,13 +453,12 @@ func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node,
if n.Children[index] != nil {
child = n.Children[index]
hash, _ := child.cache()
- state = &nodeIteratorState{
- hash: common.BytesToHash(hash),
- node: child,
- parent: ancestor,
- index: -1,
- pathlen: len(path),
- }
+ state = it.getFromPool()
+ state.hash = common.BytesToHash(hash)
+ state.node = child
+ state.parent = ancestor
+ state.index = -1
+ state.pathlen = len(path)
childPath = append(childPath, path...)
childPath = append(childPath, byte(index))
return child, state, childPath, index
@@ -452,7 +471,7 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has
switch node := parent.node.(type) {
case *fullNode:
// Full node, move to the first non-nil child.
- if child, state, path, index := findChild(node, parent.index+1, it.path, ancestor); child != nil {
+ if child, state, path, index := it.findChild(node, parent.index+1, ancestor); child != nil {
parent.index = index - 1
return state, path, true
}
@@ -460,13 +479,12 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has
// Short node, return the pointer singleton child
if parent.index < 0 {
hash, _ := node.Val.cache()
- state := &nodeIteratorState{
- hash: common.BytesToHash(hash),
- node: node.Val,
- parent: ancestor,
- index: -1,
- pathlen: len(it.path),
- }
+ state := it.getFromPool()
+ state.hash = common.BytesToHash(hash)
+ state.node = node.Val
+ state.parent = ancestor
+ state.index = -1
+ state.pathlen = len(it.path)
path := append(it.path, node.Key...)
return state, path, true
}
@@ -480,7 +498,7 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H
switch n := parent.node.(type) {
case *fullNode:
// Full node, move to the first non-nil child before the desired key position
- child, state, path, index := findChild(n, parent.index+1, it.path, ancestor)
+ child, state, path, index := it.findChild(n, parent.index+1, ancestor)
if child == nil {
// No more children in this fullnode
return parent, it.path, false
@@ -492,7 +510,7 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H
}
// The child is before the seek position. Try advancing
for {
- nextChild, nextState, nextPath, nextIndex := findChild(n, index+1, it.path, ancestor)
+ nextChild, nextState, nextPath, nextIndex := it.findChild(n, index+1, ancestor)
// If we run out of children, or skipped past the target, return the
// previous one
if nextChild == nil || bytes.Compare(nextPath, key) >= 0 {
@@ -506,13 +524,12 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H
// Short node, return the pointer singleton child
if parent.index < 0 {
hash, _ := n.Val.cache()
- state := &nodeIteratorState{
- hash: common.BytesToHash(hash),
- node: n.Val,
- parent: ancestor,
- index: -1,
- pathlen: len(it.path),
- }
+ state := it.getFromPool()
+ state.hash = common.BytesToHash(hash)
+ state.node = n.Val
+ state.parent = ancestor
+ state.index = -1
+ state.pathlen = len(it.path)
path := append(it.path, n.Key...)
return state, path, true
}
@@ -533,6 +550,8 @@ func (it *nodeIterator) pop() {
it.path = it.path[:last.pathlen]
it.stack[len(it.stack)-1] = nil
it.stack = it.stack[:len(it.stack)-1]
+ // last is now unused
+ it.putInPool(last)
}
func compareNodes(a, b NodeIterator) int {
diff --git a/trie/iterator_test.go b/trie/iterator_test.go
index 57d1f06a1..9679b49ca 100644
--- a/trie/iterator_test.go
+++ b/trie/iterator_test.go
@@ -616,3 +616,15 @@ func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) {
}
return true, path, hash
}
+
+func BenchmarkIterator(b *testing.B) {
+ diskDb, srcDb, tr, _ := makeTestTrie(rawdb.HashScheme)
+ root := tr.Hash()
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ if err := checkTrieConsistency(diskDb, srcDb.Scheme(), root, false); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
diff --git a/trie/stacktrie_fuzzer_test.go b/trie/stacktrie_fuzzer_test.go
new file mode 100644
index 000000000..1b3f9dbe9
--- /dev/null
+++ b/trie/stacktrie_fuzzer_test.go
@@ -0,0 +1,155 @@
+// Copyright 2020 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 .
+
+package trie
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "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/trie/trienode"
+ "golang.org/x/crypto/sha3"
+ "golang.org/x/exp/slices"
+)
+
+func FuzzStackTrie(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ fuzz(data, false)
+ })
+}
+
+func fuzz(data []byte, debugging bool) {
+ // This spongeDb is used to check the sequence of disk-db-writes
+ var (
+ input = bytes.NewReader(data)
+ spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
+ dbA = NewDatabase(rawdb.NewDatabase(spongeA), nil)
+ trieA = NewEmpty(dbA)
+ spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
+ dbB = NewDatabase(rawdb.NewDatabase(spongeB), nil)
+
+ options = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
+ rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
+ })
+ trieB = NewStackTrie(options)
+ vals []*kv
+ maxElements = 10000
+ // operate on unique keys only
+ keys = make(map[string]struct{})
+ )
+ // Fill the trie with elements
+ for i := 0; input.Len() > 0 && i < maxElements; i++ {
+ k := make([]byte, 32)
+ input.Read(k)
+ var a uint16
+ binary.Read(input, binary.LittleEndian, &a)
+ a = 1 + a%100
+ v := make([]byte, a)
+ input.Read(v)
+ if input.Len() == 0 {
+ // If it was exhausted while reading, the value may be all zeroes,
+ // thus 'deletion' which is not supported on stacktrie
+ break
+ }
+ if _, present := keys[string(k)]; present {
+ // This key is a duplicate, ignore it
+ continue
+ }
+ keys[string(k)] = struct{}{}
+ vals = append(vals, &kv{k: k, v: v})
+ trieA.MustUpdate(k, v)
+ }
+ if len(vals) == 0 {
+ return
+ }
+ // Flush trie -> database
+ rootA, nodes, err := trieA.Commit(false)
+ if err != nil {
+ panic(err)
+ }
+ if nodes != nil {
+ dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
+ }
+ // Flush memdb -> disk (sponge)
+ dbA.Commit(rootA, false)
+
+ // Stacktrie requires sorted insertion
+ slices.SortFunc(vals, (*kv).cmp)
+
+ for _, kv := range vals {
+ if debugging {
+ fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v)
+ }
+ trieB.MustUpdate(kv.k, kv.v)
+ }
+ rootB := trieB.Hash()
+ trieB.Commit()
+ if rootA != rootB {
+ panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
+ }
+ sumA := spongeA.sponge.Sum(nil)
+ sumB := spongeB.sponge.Sum(nil)
+ if !bytes.Equal(sumA, sumB) {
+ panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
+ }
+
+ // Ensure all the nodes are persisted correctly
+ var (
+ nodeset = make(map[string][]byte) // path -> blob
+ optionsC = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
+ if crypto.Keccak256Hash(blob) != hash {
+ panic("invalid node blob")
+ }
+ nodeset[string(path)] = common.CopyBytes(blob)
+ })
+ trieC = NewStackTrie(optionsC)
+ checked int
+ )
+ for _, kv := range vals {
+ trieC.MustUpdate(kv.k, kv.v)
+ }
+ rootC := trieC.Commit()
+ if rootA != rootC {
+ panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC))
+ }
+ trieA, _ = New(TrieID(rootA), dbA)
+ iterA := trieA.MustNodeIterator(nil)
+ for iterA.Next(true) {
+ if iterA.Hash() == (common.Hash{}) {
+ if _, present := nodeset[string(iterA.Path())]; present {
+ panic("unexpected tiny node")
+ }
+ continue
+ }
+ nodeBlob, present := nodeset[string(iterA.Path())]
+ if !present {
+ panic("missing node")
+ }
+ if !bytes.Equal(nodeBlob, iterA.NodeBlob()) {
+ panic("node blob is not matched")
+ }
+ checked += 1
+ }
+ if checked != len(nodeset) {
+ panic("node number is not matched")
+ }
+}
diff --git a/trie/sync.go b/trie/sync.go
index 8eaed9f21..589d28364 100644
--- a/trie/sync.go
+++ b/trie/sync.go
@@ -116,10 +116,9 @@ type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Ha
// nodeRequest represents a scheduled or already in-flight trie node retrieval request.
type nodeRequest struct {
- hash common.Hash // Hash of the trie node to retrieve
- path []byte // Merkle path leading to this node for prioritization
- data []byte // Data content of the node, cached until all subtrees complete
- deletes [][]byte // List of internal path segments for trie nodes to delete
+ hash common.Hash // Hash of the trie node to retrieve
+ path []byte // Merkle path leading to this node for prioritization
+ data []byte // Data content of the node, cached until all subtrees complete
parent *nodeRequest // Parent state node referencing this entry
deps int // Number of dependencies before allowed to commit this node
@@ -146,38 +145,85 @@ type CodeSyncResult struct {
Data []byte // Data content of the retrieved bytecode
}
+// nodeOp represents an operation upon the trie node. It can either represent a
+// deletion to the specific node or a node write for persisting retrieved node.
+type nodeOp struct {
+ owner common.Hash // identifier of the trie (empty for account trie)
+ path []byte // path from the root to the specified node.
+ blob []byte // the content of the node (nil for deletion)
+ hash common.Hash // hash of the node content (empty for node deletion)
+}
+
+// isDelete indicates if the operation is a database deletion.
+func (op *nodeOp) isDelete() bool {
+ return len(op.blob) == 0
+}
+
// syncMemBatch is an in-memory buffer of successfully downloaded but not yet
// persisted data items.
type syncMemBatch struct {
- nodes map[string][]byte // In-memory membatch of recently completed nodes
- hashes map[string]common.Hash // Hashes of recently completed nodes
- deletes map[string]struct{} // List of paths for trie node to delete
- codes map[common.Hash][]byte // In-memory membatch of recently completed codes
- size uint64 // Estimated batch-size of in-memory data.
+ scheme string // State scheme identifier
+ codes map[common.Hash][]byte // In-memory batch of recently completed codes
+ nodes []nodeOp // In-memory batch of recently completed/deleted nodes
+ size uint64 // Estimated batch-size of in-memory data.
}
// newSyncMemBatch allocates a new memory-buffer for not-yet persisted trie nodes.
-func newSyncMemBatch() *syncMemBatch {
+func newSyncMemBatch(scheme string) *syncMemBatch {
return &syncMemBatch{
- nodes: make(map[string][]byte),
- hashes: make(map[string]common.Hash),
- deletes: make(map[string]struct{}),
- codes: make(map[common.Hash][]byte),
+ scheme: scheme,
+ codes: make(map[common.Hash][]byte),
}
}
-// hasNode reports the trie node with specific path is already cached.
-func (batch *syncMemBatch) hasNode(path []byte) bool {
- _, ok := batch.nodes[string(path)]
- return ok
-}
-
// hasCode reports the contract code with specific hash is already cached.
func (batch *syncMemBatch) hasCode(hash common.Hash) bool {
_, ok := batch.codes[hash]
return ok
}
+// addCode caches a contract code database write operation.
+func (batch *syncMemBatch) addCode(hash common.Hash, code []byte) {
+ batch.codes[hash] = code
+ batch.size += common.HashLength + uint64(len(code))
+}
+
+// addNode caches a node database write operation.
+func (batch *syncMemBatch) addNode(owner common.Hash, path []byte, blob []byte, hash common.Hash) {
+ if batch.scheme == rawdb.PathScheme {
+ if owner == (common.Hash{}) {
+ batch.size += uint64(len(path) + len(blob))
+ } else {
+ batch.size += common.HashLength + uint64(len(path)+len(blob))
+ }
+ } else {
+ batch.size += common.HashLength + uint64(len(blob))
+ }
+ batch.nodes = append(batch.nodes, nodeOp{
+ owner: owner,
+ path: path,
+ blob: blob,
+ hash: hash,
+ })
+}
+
+// delNode caches a node database delete operation.
+func (batch *syncMemBatch) delNode(owner common.Hash, path []byte) {
+ if batch.scheme != rawdb.PathScheme {
+ log.Error("Unexpected node deletion", "owner", owner, "path", path, "scheme", batch.scheme)
+ return // deletion is not supported in hash mode.
+ }
+ if owner == (common.Hash{}) {
+ batch.size += uint64(len(path))
+ } else {
+ batch.size += common.HashLength + uint64(len(path))
+ }
+ batch.nodes = append(batch.nodes, nodeOp{
+ owner: owner,
+ path: path,
+ })
+}
+
// Sync is the main state trie synchronisation scheduler, which provides yet
// unknown trie hashes to retrieve, accepts node data associated with said hashes
// and reconstructs the trie step by step until all is done.
@@ -196,7 +242,7 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb
ts := &Sync{
scheme: scheme,
database: database,
- membatch: newSyncMemBatch(),
+ membatch: newSyncMemBatch(scheme),
nodeReqs: make(map[string]*nodeRequest),
codeReqs: make(map[common.Hash]*codeRequest),
queue: prque.New[int64, any](nil), // Ugh, can contain both string and hash, whyyy
@@ -210,16 +256,17 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb
// parent for completion tracking. The given path is a unique node path in
// hex format and contain all the parent path if it's layered trie node.
func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, parentPath []byte, callback LeafCallback) {
- // Short circuit if the trie is empty or already known
if root == types.EmptyRootHash {
return
}
- if s.membatch.hasNode(path) {
- return
- }
owner, inner := ResolvePath(path)
- if rawdb.HasTrieNode(s.database, owner, inner, root, s.scheme) {
+ exist, inconsistent := s.hasNode(owner, inner, root)
+ if exist {
+ // The entire subtrie is already present in the database.
return
+ } else if inconsistent {
+ // There is a pre-existing node with the wrong hash in DB, remove it.
+ s.membatch.delNode(owner, inner)
}
// Assemble the new sub-trie sync request
req := &nodeRequest{
@@ -371,39 +418,42 @@ func (s *Sync) ProcessNode(result NodeSyncResult) error {
}
// Commit flushes the data stored in the internal membatch out to persistent
-// storage, returning any occurred error.
+// storage, returning any occurred error. The whole data set will be flushed
+// in an atomic database batch.
func (s *Sync) Commit(dbw ethdb.Batch) error {
// Flush the pending node writes into database batch.
var (
account int
storage int
)
- for path, value := range s.membatch.nodes {
- owner, inner := ResolvePath([]byte(path))
- if owner == (common.Hash{}) {
- account += 1
+ for _, op := range s.membatch.nodes {
+ if op.isDelete() {
+ // node deletion is only supported in path mode.
+ if op.owner == (common.Hash{}) {
+ rawdb.DeleteAccountTrieNode(dbw, op.path)
+ } else {
+ rawdb.DeleteStorageTrieNode(dbw, op.owner, op.path)
+ }
+ deletionGauge.Inc(1)
} else {
- storage += 1
+ if op.owner == (common.Hash{}) {
+ account += 1
+ } else {
+ storage += 1
+ }
+ rawdb.WriteTrieNode(dbw, op.owner, op.path, op.hash, op.blob, s.scheme)
}
- rawdb.WriteTrieNode(dbw, owner, inner, s.membatch.hashes[path], value, s.scheme)
}
accountNodeSyncedGauge.Inc(int64(account))
storageNodeSyncedGauge.Inc(int64(storage))
- // Flush the pending node deletes into the database batch.
- // Please note that each written and deleted node has a
- // unique path, ensuring no duplication occurs.
- for path := range s.membatch.deletes {
- owner, inner := ResolvePath([]byte(path))
- rawdb.DeleteTrieNode(dbw, owner, inner, common.Hash{} /* unused */, s.scheme)
- }
// Flush the pending code writes into database batch.
for hash, value := range s.membatch.codes {
rawdb.WriteCode(dbw, hash, value)
}
codeSyncedGauge.Inc(int64(len(s.membatch.codes)))
- s.membatch = newSyncMemBatch() // reset the batch
+ s.membatch = newSyncMemBatch(s.scheme) // reset the batch
return nil
}
@@ -476,12 +526,15 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
// child as invalid. This is essential in the case of path mode
// scheme; otherwise, state healing might overwrite existing child
// nodes silently while leaving a dangling parent node within the
- // range of this internal path on disk. This would break the
- // guarantee for state healing.
+ // range of this internal path on disk and the persistent state
+ // ends up with a very weird situation that nodes on the same path
+ // are not inconsistent while they all present in disk. This property
+ // would break the guarantee for state healing.
//
// While it's possible for this shortNode to overwrite a previously
// existing full node, the other branches of the fullNode can be
- // retained as they remain untouched and complete.
+ // retained as they are not accessible with the new shortNode, and
+ // also the whole sub-trie is still untouched and complete.
//
// This step is only necessary for path mode, as there is no deletion
// in hash mode at all.
@@ -498,8 +551,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
exists = rawdb.ExistsStorageTrieNode(s.database, owner, append(inner, key[:i]...))
}
if exists {
- req.deletes = append(req.deletes, key[:i])
- deletionGauge.Inc(1)
+ s.membatch.delNode(owner, append(inner, key[:i]...))
log.Debug("Detected dangling node", "owner", owner, "path", append(inner, key[:i]...))
}
}
@@ -521,6 +573,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
var (
missing = make(chan *nodeRequest, len(children))
pending sync.WaitGroup
+ batchMu sync.Mutex
)
for _, child := range children {
// Notify any external watcher of a new key/value node
@@ -538,34 +591,32 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
}
}
}
- // If the child references another node, resolve or schedule
+ // If the child references another node, resolve or schedule.
+ // We check all children concurrently.
if node, ok := (child.node).(hashNode); ok {
- // Try to resolve the node from the local database
- if s.membatch.hasNode(child.path) {
- continue
- }
- // Check the presence of children concurrently
+ path := child.path
+ hash := common.BytesToHash(node)
pending.Add(1)
- go func(child childNode) {
+ go func() {
defer pending.Done()
-
- // If database says duplicate, then at least the trie node is present
- // and we hold the assumption that it's NOT legacy contract code.
- var (
- chash = common.BytesToHash(node)
- owner, inner = ResolvePath(child.path)
- )
- if rawdb.HasTrieNode(s.database, owner, inner, chash, s.scheme) {
+ owner, inner := ResolvePath(path)
+ exist, inconsistent := s.hasNode(owner, inner, hash)
+ if exist {
return
+ } else if inconsistent {
+ // There is a pre-existing node with the wrong hash in DB, remove it.
+ batchMu.Lock()
+ s.membatch.delNode(owner, inner)
+ batchMu.Unlock()
}
// Locally unknown node, schedule for retrieval
missing <- &nodeRequest{
- path: child.path,
- hash: chash,
+ path: path,
+ hash: hash,
parent: req,
callback: req.callback,
}
- }(child)
+ }()
}
}
pending.Wait()
@@ -587,21 +638,10 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
// committed themselves.
func (s *Sync) commitNodeRequest(req *nodeRequest) error {
// Write the node content to the membatch
- s.membatch.nodes[string(req.path)] = req.data
- s.membatch.hashes[string(req.path)] = req.hash
+ owner, path := ResolvePath(req.path)
+ s.membatch.addNode(owner, path, req.data, req.hash)
- // The size tracking refers to the db-batch, not the in-memory data.
- if s.scheme == rawdb.PathScheme {
- s.membatch.size += uint64(len(req.path) + len(req.data))
- } else {
- s.membatch.size += common.HashLength + uint64(len(req.data))
- }
- // Delete the internal nodes which are marked as invalid
- for _, segment := range req.deletes {
- path := append(req.path, segment...)
- s.membatch.deletes[string(path)] = struct{}{}
- s.membatch.size += uint64(len(path))
- }
+ // Removed the completed node request
delete(s.nodeReqs, string(req.path))
s.fetches[len(req.path)]--
@@ -622,8 +662,9 @@ func (s *Sync) commitNodeRequest(req *nodeRequest) error {
// committed themselves.
func (s *Sync) commitCodeRequest(req *codeRequest) error {
// Write the node content to the membatch
- s.membatch.codes[req.hash] = req.data
- s.membatch.size += common.HashLength + uint64(len(req.data))
+ s.membatch.addCode(req.hash, req.data)
+
+ // Removed the completed code request
delete(s.codeReqs, req.hash)
s.fetches[len(req.path)]--
@@ -639,6 +680,28 @@ func (s *Sync) commitCodeRequest(req *codeRequest) error {
return nil
}
+// hasNode reports whether the specified trie node is present in the database.
+// 'exists' is true when the node exists in the database and matches the given root
+// hash. The 'inconsistent' return value is true when the node exists but does not
+// match the expected hash.
+func (s *Sync) hasNode(owner common.Hash, path []byte, hash common.Hash) (exists bool, inconsistent bool) {
+ // If node is running with hash scheme, check the presence with node hash.
+ if s.scheme == rawdb.HashScheme {
+ return rawdb.HasLegacyTrieNode(s.database, hash), false
+ }
+ // If node is running with path scheme, check the presence with node path.
+ var blob []byte
+ var dbHash common.Hash
+ if owner == (common.Hash{}) {
+ blob, dbHash = rawdb.ReadAccountTrieNode(s.database, path)
+ } else {
+ blob, dbHash = rawdb.ReadStorageTrieNode(s.database, owner, path)
+ }
+ exists = hash == dbHash
+ inconsistent = !exists && len(blob) != 0
+ return exists, inconsistent
+}
+
// ResolvePath resolves the provided composite node path by separating the
// path in account trie if it's existent.
func ResolvePath(path []byte) (common.Hash, []byte) {
diff --git a/trie/sync_test.go b/trie/sync_test.go
index 3b7986ef6..585181b48 100644
--- a/trie/sync_test.go
+++ b/trie/sync_test.go
@@ -19,6 +19,7 @@ package trie
import (
"bytes"
"fmt"
+ "math/rand"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -571,7 +572,7 @@ func testIncompleteSync(t *testing.T, scheme string) {
hash := crypto.Keccak256Hash(result.Data)
if hash != root {
addedKeys = append(addedKeys, result.Path)
- addedHashes = append(addedHashes, crypto.Keccak256Hash(result.Data))
+ addedHashes = append(addedHashes, hash)
}
}
// Fetch the next batch to retrieve
@@ -587,6 +588,10 @@ func testIncompleteSync(t *testing.T, scheme string) {
}
// Sanity check that removing any node from the database is detected
for i, path := range addedKeys {
+ if rand.Int31n(100) > 5 {
+ // Only check 5 percent of added keys as a sanity check
+ continue
+ }
owner, inner := ResolvePath([]byte(path))
nodeHash := addedHashes[i]
value := rawdb.ReadTrieNode(diskdb, owner, inner, nodeHash, scheme)
@@ -679,8 +684,11 @@ func testSyncOrdering(t *testing.T, scheme string) {
}
}
}
-
func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database) {
+ syncWithHookWriter(t, root, db, srcDb, nil)
+}
+
+func syncWithHookWriter(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database, hookWriter ethdb.KeyValueWriter) {
// Create a destination trie and sync with the scheduler
sched := NewSync(root, db, nil, srcDb.Scheme())
@@ -718,8 +726,11 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database
if err := sched.Commit(batch); err != nil {
t.Fatalf("failed to commit data: %v", err)
}
- batch.Write()
-
+ if hookWriter != nil {
+ batch.Replay(hookWriter)
+ } else {
+ batch.Write()
+ }
paths, nodes, _ = sched.Missing(0)
elements = elements[:0]
for i := 0; i < len(paths); i++ {
@@ -889,3 +900,116 @@ func testPivotMove(t *testing.T, scheme string, tiny bool) {
syncWith(t, rootC, destDisk, srcTrieDB)
checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateC, true)
}
+
+func TestSyncAbort(t *testing.T) {
+ testSyncAbort(t, rawdb.PathScheme)
+ testSyncAbort(t, rawdb.HashScheme)
+}
+
+type hookWriter struct {
+ db ethdb.KeyValueStore
+ filter func(key []byte, value []byte) bool
+}
+
+// Put inserts the given value into the key-value data store.
+func (w *hookWriter) Put(key []byte, value []byte) error {
+ if w.filter != nil && w.filter(key, value) {
+ return nil
+ }
+ return w.db.Put(key, value)
+}
+
+// Delete removes the key from the key-value data store.
+func (w *hookWriter) Delete(key []byte) error {
+ return w.db.Delete(key)
+}
+
+func testSyncAbort(t *testing.T, scheme string) {
+ var (
+ srcDisk = rawdb.NewMemoryDatabase()
+ srcTrieDB = newTestDatabase(srcDisk, scheme)
+ srcTrie, _ = New(TrieID(types.EmptyRootHash), srcTrieDB)
+
+ deleteFn = func(key []byte, tr *Trie, states map[string][]byte) {
+ tr.Delete(key)
+ delete(states, string(key))
+ }
+ writeFn = func(key []byte, val []byte, tr *Trie, states map[string][]byte) {
+ if val == nil {
+ val = randBytes(32)
+ }
+ tr.Update(key, val)
+ states[string(key)] = common.CopyBytes(val)
+ }
+ copyStates = func(states map[string][]byte) map[string][]byte {
+ cpy := make(map[string][]byte)
+ for k, v := range states {
+ cpy[k] = v
+ }
+ return cpy
+ }
+ )
+ var (
+ stateA = make(map[string][]byte)
+ key = randBytes(32)
+ val = randBytes(32)
+ )
+ for i := 0; i < 256; i++ {
+ writeFn(randBytes(32), nil, srcTrie, stateA)
+ }
+ writeFn(key, val, srcTrie, stateA)
+
+ rootA, nodesA, _ := srcTrie.Commit(false)
+ if err := srcTrieDB.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil); err != nil {
+ panic(err)
+ }
+ if err := srcTrieDB.Commit(rootA, false); err != nil {
+ panic(err)
+ }
+ // Create a destination trie and sync with the scheduler
+ destDisk := rawdb.NewMemoryDatabase()
+ syncWith(t, rootA, destDisk, srcTrieDB)
+ checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateA, true)
+
+ // Delete the element from the trie
+ stateB := copyStates(stateA)
+ srcTrie, _ = New(TrieID(rootA), srcTrieDB)
+ deleteFn(key, srcTrie, stateB)
+
+ rootB, nodesB, _ := srcTrie.Commit(false)
+ if err := srcTrieDB.Update(rootB, rootA, 0, trienode.NewWithNodeSet(nodesB), nil); err != nil {
+ panic(err)
+ }
+ if err := srcTrieDB.Commit(rootB, false); err != nil {
+ panic(err)
+ }
+
+ // Sync the new state, but never persist the new root node. Before the
+ // fix #28595, the original old root node will still be left in database
+ // which breaks the next healing cycle.
+ syncWithHookWriter(t, rootB, destDisk, srcTrieDB, &hookWriter{db: destDisk, filter: func(key []byte, value []byte) bool {
+ if scheme == rawdb.HashScheme {
+ return false
+ }
+ if len(value) == 0 {
+ return false
+ }
+ ok, path := rawdb.ResolveAccountTrieNodeKey(key)
+ return ok && len(path) == 0
+ }})
+
+ // Add elements to expand trie
+ stateC := copyStates(stateB)
+ srcTrie, _ = New(TrieID(rootB), srcTrieDB)
+
+ writeFn(key, val, srcTrie, stateC)
+ rootC, nodesC, _ := srcTrie.Commit(false)
+ if err := srcTrieDB.Update(rootC, rootB, 0, trienode.NewWithNodeSet(nodesC), nil); err != nil {
+ panic(err)
+ }
+ if err := srcTrieDB.Commit(rootC, false); err != nil {
+ panic(err)
+ }
+ syncWith(t, rootC, destDisk, srcTrieDB)
+ checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateC, true)
+}
diff --git a/trie/trie_test.go b/trie/trie_test.go
index 431575354..c5bd3faf5 100644
--- a/trie/trie_test.go
+++ b/trie/trie_test.go
@@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"hash"
+ "io"
"math/big"
"math/rand"
"reflect"
@@ -362,13 +363,18 @@ func TestRandomCases(t *testing.T) {
{op: 1, key: common.Hex2Bytes("980c393656413a15c8da01978ed9f89feb80b502f58f2d640e3a2f5f7a99a7018f1b573befd92053ac6f78fca4a87268"), value: common.Hex2Bytes("")}, // step 24
{op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25
}
- runRandTest(rt)
+ if err := runRandTest(rt); err != nil {
+ t.Fatal(err)
+ }
}
// randTest performs random trie operations.
// Instances of this test are created by Generate.
type randTest []randTestStep
+// compile-time interface check
+var _ quick.Generator = (randTest)(nil)
+
type randTestStep struct {
op int
key []byte // for opUpdate, opDelete, opGet
@@ -389,33 +395,45 @@ const (
)
func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
+ var finishedFn = func() bool {
+ size--
+ return size == 0
+ }
+ return reflect.ValueOf(generateSteps(finishedFn, r))
+}
+
+func generateSteps(finished func() bool, r io.Reader) randTest {
var allKeys [][]byte
+ var one = []byte{0}
genKey := func() []byte {
- if len(allKeys) < 2 || r.Intn(100) < 10 {
+ r.Read(one)
+ if len(allKeys) < 2 || one[0]%100 > 90 {
// new key
- key := make([]byte, r.Intn(50))
+ size := one[0] % 50
+ key := make([]byte, size)
r.Read(key)
allKeys = append(allKeys, key)
return key
}
// use existing key
- return allKeys[r.Intn(len(allKeys))]
+ idx := int(one[0]) % len(allKeys)
+ return allKeys[idx]
}
-
var steps randTest
- for i := 0; i < size; i++ {
- step := randTestStep{op: r.Intn(opMax)}
+ for !finished() {
+ r.Read(one)
+ step := randTestStep{op: int(one[0]) % opMax}
switch step.op {
case opUpdate:
step.key = genKey()
step.value = make([]byte, 8)
- binary.BigEndian.PutUint64(step.value, uint64(i))
+ binary.BigEndian.PutUint64(step.value, uint64(len(steps)))
case opGet, opDelete, opProve:
step.key = genKey()
}
steps = append(steps, step)
}
- return reflect.ValueOf(steps)
+ return steps
}
func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error {
@@ -460,7 +478,12 @@ func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error {
return nil
}
-func runRandTest(rt randTest) bool {
+// runRandTestBool coerces error to boolean, for use in quick.Check
+func runRandTestBool(rt randTest) bool {
+ return runRandTest(rt) == nil
+}
+
+func runRandTest(rt randTest) error {
var scheme = rawdb.HashScheme
if rand.Intn(2) == 0 {
scheme = rawdb.PathScheme
@@ -513,12 +536,12 @@ func runRandTest(rt randTest) bool {
newtr, err := New(TrieID(root), triedb)
if err != nil {
rt[i].err = err
- return false
+ return err
}
if nodes != nil {
if err := verifyAccessList(origTrie, newtr, nodes); err != nil {
rt[i].err = err
- return false
+ return err
}
}
tr = newtr
@@ -587,14 +610,14 @@ func runRandTest(rt randTest) bool {
}
// Abort the test on error.
if rt[i].err != nil {
- return false
+ return rt[i].err
}
}
- return true
+ return nil
}
func TestRandom(t *testing.T) {
- if err := quick.Check(runRandTest, nil); err != nil {
+ if err := quick.Check(runRandTestBool, nil); err != nil {
if cerr, ok := err.(*quick.CheckError); ok {
t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In))
}
@@ -1185,3 +1208,17 @@ func TestDecodeNode(t *testing.T) {
decodeNode(hash, elems)
}
}
+
+func FuzzTrie(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ var steps = 500
+ var input = bytes.NewReader(data)
+ var finishedFn = func() bool {
+ steps--
+ return steps < 0 || input.Len() == 0
+ }
+ if err := runRandTest(generateSteps(finishedFn, input)); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
diff --git a/trie/triedb/hashdb/database.go b/trie/triedb/hashdb/database.go
index 6ae5eaa3f..31fb51a68 100644
--- a/trie/triedb/hashdb/database.go
+++ b/trie/triedb/hashdb/database.go
@@ -82,11 +82,6 @@ var Defaults = &Config{
// Database is an intermediate write layer between the trie data structures and
// the disk database. The aim is to accumulate trie writes in-memory and only
// periodically flush a couple tries to disk, garbage collecting the remainder.
-//
-// Note, the trie Database is **not** thread safe in its mutations, but it **is**
-// thread safe in providing individual, independent node access. The rationale
-// behind this split design is to provide read access to RPC handlers and sync
-// servers even while the trie is executing expensive garbage collection.
type Database struct {
diskdb ethdb.Database // Persistent storage for matured trie nodes
resolver ChildResolver // The handler to resolve children of nodes
@@ -113,7 +108,7 @@ type Database struct {
// cachedNode is all the information we know about a single cached trie node
// in the memory database write layer.
type cachedNode struct {
- node []byte // Encoded node blob
+ node []byte // Encoded node blob, immutable
parents uint32 // Number of live nodes referencing this one
external map[common.Hash]struct{} // The set of external children
flushPrev common.Hash // Previous node in the flush-list
@@ -152,9 +147,9 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas
}
}
-// insert inserts a simplified trie node into the memory database.
-// All nodes inserted by this function will be reference tracked
-// and in theory should only used for **trie nodes** insertion.
+// insert inserts a trie node into the memory database. All nodes inserted by
+// this function will be reference tracked. This function assumes the lock is
+// already held.
func (db *Database) insert(hash common.Hash, node []byte) {
// If the node's already cached, skip
if _, ok := db.dirties[hash]; ok {
@@ -183,9 +178,9 @@ func (db *Database) insert(hash common.Hash, node []byte) {
db.dirtiesSize += common.StorageSize(common.HashLength + len(node))
}
-// Node retrieves an encoded cached trie node from memory. If it cannot be found
+// node retrieves an encoded cached trie node from memory. If it cannot be found
// cached, the method queries the persistent database for the content.
-func (db *Database) Node(hash common.Hash) ([]byte, error) {
+func (db *Database) node(hash common.Hash) ([]byte, error) {
// It doesn't make sense to retrieve the metaroot
if hash == (common.Hash{}) {
return nil, errors.New("not found")
@@ -198,11 +193,14 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
return enc, nil
}
}
- // Retrieve the node from the dirty cache if available
+ // Retrieve the node from the dirty cache if available.
db.lock.RLock()
dirty := db.dirties[hash]
db.lock.RUnlock()
+ // Return the cached node if it's found in the dirty set.
+ // The dirty.node field is immutable and safe to read it
+ // even without lock guard.
if dirty != nil {
memcacheDirtyHitMeter.Mark(1)
memcacheDirtyReadMeter.Mark(int64(len(dirty.node)))
@@ -223,20 +221,6 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
return nil, errors.New("not found")
}
-// Nodes retrieves the hashes of all the nodes cached within the memory database.
-// This method is extremely expensive and should only be used to validate internal
-// states in test code.
-func (db *Database) Nodes() []common.Hash {
- db.lock.RLock()
- defer db.lock.RUnlock()
-
- var hashes = make([]common.Hash, 0, len(db.dirties))
- for hash := range db.dirties {
- hashes = append(hashes, hash)
- }
- return hashes
-}
-
// Reference adds a new reference from a parent node to a child node.
// This function is used to add reference between internal trie node
// and external node(e.g. storage trie root), all internal trie nodes
@@ -344,16 +328,16 @@ func (db *Database) dereference(hash common.Hash) {
// Cap iteratively flushes old but still referenced trie nodes until the total
// memory usage goes below the given threshold.
-//
-// Note, this method is a non-synchronized mutator. It is unsafe to call this
-// concurrently with other mutators.
func (db *Database) Cap(limit common.StorageSize) error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
// Create a database batch to flush persistent data out. It is important that
// outside code doesn't see an inconsistent state (referenced data removed from
// memory cache during commit but not yet in persistent storage). This is ensured
// by only uncaching existing data when the database write finalizes.
- nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
batch := db.diskdb.NewBatch()
+ nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
// db.dirtiesSize only contains the useful data in the cache, but when reporting
// the total memory consumption, the maintenance metadata is also needed to be
@@ -391,9 +375,6 @@ func (db *Database) Cap(limit common.StorageSize) error {
return err
}
// Write successful, clear out the flushed data
- db.lock.Lock()
- defer db.lock.Unlock()
-
for db.oldest != oldest {
node := db.dirties[db.oldest]
delete(db.dirties, db.oldest)
@@ -424,10 +405,10 @@ func (db *Database) Cap(limit common.StorageSize) error {
// Commit iterates over all the children of a particular node, writes them out
// to disk, forcefully tearing down all references in both directions. As a side
// effect, all pre-images accumulated up to this point are also written.
-//
-// Note, this method is a non-synchronized mutator. It is unsafe to call this
-// concurrently with other mutators.
func (db *Database) Commit(node common.Hash, report bool) error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
// Create a database batch to flush persistent data out. It is important that
// outside code doesn't see an inconsistent state (referenced data removed from
// memory cache during commit but not yet in persistent storage). This is ensured
@@ -454,8 +435,6 @@ func (db *Database) Commit(node common.Hash, report bool) error {
return err
}
// Uncache any leftovers in the last batch
- db.lock.Lock()
- defer db.lock.Unlock()
if err := batch.Replay(uncacher); err != nil {
return err
}
@@ -508,13 +487,11 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane
if err := batch.Write(); err != nil {
return err
}
- db.lock.Lock()
err := batch.Replay(uncacher)
- batch.Reset()
- db.lock.Unlock()
if err != nil {
return err
}
+ batch.Reset()
}
return nil
}
@@ -583,7 +560,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
// Ensure the parent state is present and signal a warning if not.
if parent != types.EmptyRootHash {
- if blob, _ := db.Node(parent); len(blob) == 0 {
+ if blob, _ := db.node(parent); len(blob) == 0 {
log.Error("parent state is not present")
}
}
@@ -664,7 +641,7 @@ func (db *Database) Scheme() string {
// Reader retrieves a node reader belonging to the given state root.
// An error will be returned if the requested state is not available.
func (db *Database) Reader(root common.Hash) (*reader, error) {
- if _, err := db.Node(root); err != nil {
+ if _, err := db.node(root); err != nil {
return nil, fmt.Errorf("state %#x is not available, %v", root, err)
}
return &reader{db: db}, nil
@@ -675,9 +652,9 @@ type reader struct {
db *Database
}
-// Node retrieves the trie node with the given node hash.
-// No error will be returned if the node is not found.
+// Node retrieves the trie node with the given node hash. No error will be
+// returned if the node is not found.
func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
- blob, _ := reader.db.Node(hash)
+ blob, _ := reader.db.node(hash)
return blob, nil
}
diff --git a/trie/triedb/pathdb/database.go b/trie/triedb/pathdb/database.go
index dc64414e9..f2d6cea63 100644
--- a/trie/triedb/pathdb/database.go
+++ b/trie/triedb/pathdb/database.go
@@ -170,14 +170,31 @@ func New(diskdb ethdb.Database, config *Config) *Database {
}
db.freezer = freezer
- // Truncate the extra state histories above in freezer in case
- // it's not aligned with the disk layer.
- pruned, err := truncateFromHead(db.diskdb, freezer, db.tree.bottom().stateID())
- if err != nil {
- log.Crit("Failed to truncate extra state histories", "err", err)
- }
- if pruned != 0 {
- log.Warn("Truncated extra state histories", "number", pruned)
+ diskLayerID := db.tree.bottom().stateID()
+ if diskLayerID == 0 {
+ // Reset the entire state histories in case the trie database is
+ // not initialized yet, as these state histories are not expected.
+ frozen, err := db.freezer.Ancients()
+ if err != nil {
+ log.Crit("Failed to retrieve head of state history", "err", err)
+ }
+ if frozen != 0 {
+ err := db.freezer.Reset()
+ if err != nil {
+ log.Crit("Failed to reset state histories", "err", err)
+ }
+ log.Info("Truncated extraneous state history")
+ }
+ } else {
+ // Truncate the extra state histories above in freezer in case
+ // it's not aligned with the disk layer.
+ pruned, err := truncateFromHead(db.diskdb, freezer, diskLayerID)
+ if err != nil {
+ log.Crit("Failed to truncate extra state histories", "err", err)
+ }
+ if pruned != 0 {
+ log.Warn("Truncated extra state histories", "number", pruned)
+ }
}
}
// Disable database in case node is still in the initial state sync stage.
@@ -431,6 +448,9 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
inited = true
}
})
+ if !inited {
+ inited = rawdb.ReadSnapSyncStatusFlag(db.diskdb) != rawdb.StateSyncUnknown
+ }
return inited
}
diff --git a/trie/trienode/node.go b/trie/trienode/node.go
index 98d5588b6..95315c2e9 100644
--- a/trie/trienode/node.go
+++ b/trie/trienode/node.go
@@ -39,7 +39,7 @@ func (n *Node) Size() int {
// IsDeleted returns the indicator if the node is marked as deleted.
func (n *Node) IsDeleted() bool {
- return n.Hash == (common.Hash{})
+ return len(n.Blob) == 0
}
// New constructs a node with provided node information.
diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go
new file mode 100644
index 000000000..ce059edc6
--- /dev/null
+++ b/trie/utils/verkle.go
@@ -0,0 +1,342 @@
+// Copyright 2023 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 .
+
+package utils
+
+import (
+ "encoding/binary"
+ "sync"
+
+ "github.com/crate-crypto/go-ipa/bandersnatch/fr"
+ "github.com/ethereum/go-ethereum/common/lru"
+ "github.com/ethereum/go-ethereum/metrics"
+ "github.com/gballet/go-verkle"
+ "github.com/holiman/uint256"
+)
+
+const (
+ // The spec of verkle key encoding can be found here.
+ // https://notes.ethereum.org/@vbuterin/verkle_tree_eip#Tree-embedding
+ VersionLeafKey = 0
+ BalanceLeafKey = 1
+ NonceLeafKey = 2
+ CodeKeccakLeafKey = 3
+ CodeSizeLeafKey = 4
+)
+
+var (
+ zero = uint256.NewInt(0)
+ verkleNodeWidthLog2 = 8
+ headerStorageOffset = uint256.NewInt(64)
+ mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(256), 31-uint(verkleNodeWidthLog2))
+ codeOffset = uint256.NewInt(128)
+ verkleNodeWidth = uint256.NewInt(256)
+ codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset)
+
+ index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64]
+
+ // cacheHitGauge is the metric to track how many cache hit occurred.
+ cacheHitGauge = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil)
+
+ // cacheMissGauge is the metric to track how many cache miss occurred.
+ cacheMissGauge = metrics.NewRegisteredGauge("trie/verkle/cache/miss", nil)
+)
+
+func init() {
+ // The byte array is the Marshalled output of the point computed as such:
+ //
+ // var (
+ // config = verkle.GetConfig()
+ // fr verkle.Fr
+ // )
+ // verkle.FromLEBytes(&fr, []byte{2, 64})
+ // point := config.CommitToPoly([]verkle.Fr{fr}, 1)
+ index0Point = new(verkle.Point)
+ err := index0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191})
+ if err != nil {
+ panic(err)
+ }
+}
+
+// PointCache is the LRU cache for storing evaluated address commitment.
+type PointCache struct {
+ lru lru.BasicLRU[string, *verkle.Point]
+ lock sync.RWMutex
+}
+
+// NewPointCache returns the cache with specified size.
+func NewPointCache(maxItems int) *PointCache {
+ return &PointCache{
+ lru: lru.NewBasicLRU[string, *verkle.Point](maxItems),
+ }
+}
+
+// Get returns the cached commitment for the specified address, or computing
+// it on the flight.
+func (c *PointCache) Get(addr []byte) *verkle.Point {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ p, ok := c.lru.Get(string(addr))
+ if ok {
+ cacheHitGauge.Inc(1)
+ return p
+ }
+ cacheMissGauge.Inc(1)
+ p = evaluateAddressPoint(addr)
+ c.lru.Add(string(addr), p)
+ return p
+}
+
+// GetStem returns the first 31 bytes of the tree key as the tree stem. It only
+// works for the account metadata whose treeIndex is 0.
+func (c *PointCache) GetStem(addr []byte) []byte {
+ p := c.Get(addr)
+ return pointToHash(p, 0)[:31]
+}
+
+// GetTreeKey performs both the work of the spec's get_tree_key function, and that
+// of pedersen_hash: it builds the polynomial in pedersen_hash without having to
+// create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte
+// array. Since at most the first 5 coefficients of the polynomial will be non-zero,
+// these 5 coefficients are created directly.
+func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte {
+ if len(address) < 32 {
+ var aligned [32]byte
+ address = append(aligned[:32-len(address)], address...)
+ }
+ // poly = [2+256*64, address_le_low, address_le_high, tree_index_le_low, tree_index_le_high]
+ var poly [5]fr.Element
+
+ // 32-byte address, interpreted as two little endian
+ // 16-byte numbers.
+ verkle.FromLEBytes(&poly[1], address[:16])
+ verkle.FromLEBytes(&poly[2], address[16:])
+
+ // treeIndex must be interpreted as a 32-byte aligned little-endian integer.
+ // e.g: if treeIndex is 0xAABBCC, we need the byte representation to be 0xCCBBAA00...00.
+ // poly[3] = LE({CC,BB,AA,00...0}) (16 bytes), poly[4]=LE({00,00,...}) (16 bytes).
+ //
+ // To avoid unnecessary endianness conversions for go-ipa, we do some trick:
+ // - poly[3]'s byte representation is the same as the *top* 16 bytes (trieIndexBytes[16:]) of
+ // 32-byte aligned big-endian representation (BE({00,...,AA,BB,CC})).
+ // - poly[4]'s byte representation is the same as the *low* 16 bytes (trieIndexBytes[:16]) of
+ // the 32-byte aligned big-endian representation (BE({00,00,...}).
+ trieIndexBytes := treeIndex.Bytes32()
+ verkle.FromBytes(&poly[3], trieIndexBytes[16:])
+ verkle.FromBytes(&poly[4], trieIndexBytes[:16])
+
+ cfg := verkle.GetConfig()
+ ret := cfg.CommitToPoly(poly[:], 0)
+
+ // add a constant point corresponding to poly[0]=[2+256*64].
+ ret.Add(ret, index0Point)
+
+ return pointToHash(ret, subIndex)
+}
+
+// GetTreeKeyWithEvaluatedAddress is basically identical to GetTreeKey, the only
+// difference is a part of polynomial is already evaluated.
+//
+// Specifically, poly = [2+256*64, address_le_low, address_le_high] is already
+// evaluated.
+func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte {
+ var poly [5]fr.Element
+
+ poly[0].SetZero()
+ poly[1].SetZero()
+ poly[2].SetZero()
+
+ // little-endian, 32-byte aligned treeIndex
+ var index [32]byte
+ for i := 0; i < len(treeIndex); i++ {
+ binary.LittleEndian.PutUint64(index[i*8:(i+1)*8], treeIndex[i])
+ }
+ verkle.FromLEBytes(&poly[3], index[:16])
+ verkle.FromLEBytes(&poly[4], index[16:])
+
+ cfg := verkle.GetConfig()
+ ret := cfg.CommitToPoly(poly[:], 0)
+
+ // add the pre-evaluated address
+ ret.Add(ret, evaluated)
+
+ return pointToHash(ret, subIndex)
+}
+
+// VersionKey returns the verkle tree key of the version field for the specified account.
+func VersionKey(address []byte) []byte {
+ return GetTreeKey(address, zero, VersionLeafKey)
+}
+
+// BalanceKey returns the verkle tree key of the balance field for the specified account.
+func BalanceKey(address []byte) []byte {
+ return GetTreeKey(address, zero, BalanceLeafKey)
+}
+
+// NonceKey returns the verkle tree key of the nonce field for the specified account.
+func NonceKey(address []byte) []byte {
+ return GetTreeKey(address, zero, NonceLeafKey)
+}
+
+// CodeKeccakKey returns the verkle tree key of the code keccak field for
+// the specified account.
+func CodeKeccakKey(address []byte) []byte {
+ return GetTreeKey(address, zero, CodeKeccakLeafKey)
+}
+
+// CodeSizeKey returns the verkle tree key of the code size field for the
+// specified account.
+func CodeSizeKey(address []byte) []byte {
+ return GetTreeKey(address, zero, CodeSizeLeafKey)
+}
+
+func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) {
+ var (
+ chunkOffset = new(uint256.Int).Add(codeOffset, chunk)
+ treeIndex = new(uint256.Int).Div(chunkOffset, verkleNodeWidth)
+ subIndexMod = new(uint256.Int).Mod(chunkOffset, verkleNodeWidth)
+ )
+ var subIndex byte
+ if len(subIndexMod) != 0 {
+ subIndex = byte(subIndexMod[0])
+ }
+ return treeIndex, subIndex
+}
+
+// CodeChunkKey returns the verkle tree key of the code chunk for the
+// specified account.
+func CodeChunkKey(address []byte, chunk *uint256.Int) []byte {
+ treeIndex, subIndex := codeChunkIndex(chunk)
+ return GetTreeKey(address, treeIndex, subIndex)
+}
+
+func storageIndex(bytes []byte) (*uint256.Int, byte) {
+ // If the storage slot is in the header, we need to add the header offset.
+ var key uint256.Int
+ key.SetBytes(bytes)
+ if key.Cmp(codeStorageDelta) < 0 {
+ // This addition is always safe; it can't ever overflow since pos
+
+package utils
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/gballet/go-verkle"
+ "github.com/holiman/uint256"
+)
+
+func TestTreeKey(t *testing.T) {
+ var (
+ address = []byte{0x01}
+ addressEval = evaluateAddressPoint(address)
+ smallIndex = uint256.NewInt(1)
+ largeIndex = uint256.NewInt(10000)
+ smallStorage = []byte{0x1}
+ largeStorage = bytes.Repeat([]byte{0xff}, 16)
+ )
+ if !bytes.Equal(VersionKey(address), VersionKeyWithEvaluatedAddress(addressEval)) {
+ t.Fatal("Unmatched version key")
+ }
+ if !bytes.Equal(BalanceKey(address), BalanceKeyWithEvaluatedAddress(addressEval)) {
+ t.Fatal("Unmatched balance key")
+ }
+ if !bytes.Equal(NonceKey(address), NonceKeyWithEvaluatedAddress(addressEval)) {
+ t.Fatal("Unmatched nonce key")
+ }
+ if !bytes.Equal(CodeKeccakKey(address), CodeKeccakKeyWithEvaluatedAddress(addressEval)) {
+ t.Fatal("Unmatched code keccak key")
+ }
+ if !bytes.Equal(CodeSizeKey(address), CodeSizeKeyWithEvaluatedAddress(addressEval)) {
+ t.Fatal("Unmatched code size key")
+ }
+ if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) {
+ t.Fatal("Unmatched code chunk key")
+ }
+ if !bytes.Equal(CodeChunkKey(address, largeIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, largeIndex)) {
+ t.Fatal("Unmatched code chunk key")
+ }
+ if !bytes.Equal(StorageSlotKey(address, smallStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, smallStorage)) {
+ t.Fatal("Unmatched storage slot key")
+ }
+ if !bytes.Equal(StorageSlotKey(address, largeStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, largeStorage)) {
+ t.Fatal("Unmatched storage slot key")
+ }
+}
+
+// goos: darwin
+// goarch: amd64
+// pkg: github.com/ethereum/go-ethereum/trie/utils
+// cpu: VirtualApple @ 2.50GHz
+// BenchmarkTreeKey
+// BenchmarkTreeKey-8 398731 2961 ns/op 32 B/op 1 allocs/op
+func BenchmarkTreeKey(b *testing.B) {
+ // Initialize the IPA settings which can be pretty expensive.
+ verkle.GetConfig()
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ BalanceKey([]byte{0x01})
+ }
+}
+
+// goos: darwin
+// goarch: amd64
+// pkg: github.com/ethereum/go-ethereum/trie/utils
+// cpu: VirtualApple @ 2.50GHz
+// BenchmarkTreeKeyWithEvaluation
+// BenchmarkTreeKeyWithEvaluation-8 513855 2324 ns/op 32 B/op 1 allocs/op
+func BenchmarkTreeKeyWithEvaluation(b *testing.B) {
+ // Initialize the IPA settings which can be pretty expensive.
+ verkle.GetConfig()
+
+ addr := []byte{0x01}
+ eval := evaluateAddressPoint(addr)
+
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ BalanceKeyWithEvaluatedAddress(eval)
+ }
+}
+
+// goos: darwin
+// goarch: amd64
+// pkg: github.com/ethereum/go-ethereum/trie/utils
+// cpu: VirtualApple @ 2.50GHz
+// BenchmarkStorageKey
+// BenchmarkStorageKey-8 230516 4584 ns/op 96 B/op 3 allocs/op
+func BenchmarkStorageKey(b *testing.B) {
+ // Initialize the IPA settings which can be pretty expensive.
+ verkle.GetConfig()
+
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ StorageSlotKey([]byte{0x01}, bytes.Repeat([]byte{0xff}, 32))
+ }
+}
+
+// goos: darwin
+// goarch: amd64
+// pkg: github.com/ethereum/go-ethereum/trie/utils
+// cpu: VirtualApple @ 2.50GHz
+// BenchmarkStorageKeyWithEvaluation
+// BenchmarkStorageKeyWithEvaluation-8 320125 3753 ns/op 96 B/op 3 allocs/op
+func BenchmarkStorageKeyWithEvaluation(b *testing.B) {
+ // Initialize the IPA settings which can be pretty expensive.
+ verkle.GetConfig()
+
+ addr := []byte{0x01}
+ eval := evaluateAddressPoint(addr)
+
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ StorageSlotKeyWithEvaluatedAddress(eval, bytes.Repeat([]byte{0xff}, 32))
+ }
+}
diff --git a/trie/verkle.go b/trie/verkle.go
new file mode 100644
index 000000000..89e2e5340
--- /dev/null
+++ b/trie/verkle.go
@@ -0,0 +1,375 @@
+// Copyright 2023 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 .
+
+package trie
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/trie/trienode"
+ "github.com/ethereum/go-ethereum/trie/utils"
+ "github.com/gballet/go-verkle"
+ "github.com/holiman/uint256"
+)
+
+var (
+ zero [32]byte
+ errInvalidRootType = errors.New("invalid node type for root")
+)
+
+// VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie
+// interface so that Verkle trees can be reused verbatim.
+type VerkleTrie struct {
+ root verkle.VerkleNode
+ db *Database
+ cache *utils.PointCache
+ reader *trieReader
+}
+
+// NewVerkleTrie constructs a verkle tree based on the specified root hash.
+func NewVerkleTrie(root common.Hash, db *Database, cache *utils.PointCache) (*VerkleTrie, error) {
+ reader, err := newTrieReader(root, common.Hash{}, db)
+ if err != nil {
+ return nil, err
+ }
+ // Parse the root verkle node if it's not empty.
+ node := verkle.New()
+ if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
+ blob, err := reader.node(nil, common.Hash{})
+ if err != nil {
+ return nil, err
+ }
+ node, err = verkle.ParseNode(blob, 0)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return &VerkleTrie{
+ root: node,
+ db: db,
+ cache: cache,
+ reader: reader,
+ }, nil
+}
+
+// GetKey returns the sha3 preimage of a hashed key that was previously used
+// to store a value.
+func (t *VerkleTrie) GetKey(key []byte) []byte {
+ return key
+}
+
+// GetAccount implements state.Trie, retrieving the account with the specified
+// account address. If the specified account is not in the verkle tree, nil will
+// be returned. If the tree is corrupted, an error will be returned.
+func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
+ var (
+ acc = &types.StateAccount{}
+ values [][]byte
+ err error
+ )
+ switch n := t.root.(type) {
+ case *verkle.InternalNode:
+ values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver)
+ if err != nil {
+ return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err)
+ }
+ default:
+ return nil, errInvalidRootType
+ }
+ if values == nil {
+ return nil, nil
+ }
+ // Decode nonce in little-endian
+ if len(values[utils.NonceLeafKey]) > 0 {
+ acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey])
+ }
+ // Decode balance in little-endian
+ var balance [32]byte
+ copy(balance[:], values[utils.BalanceLeafKey])
+ for i := 0; i < len(balance)/2; i++ {
+ balance[len(balance)-i-1], balance[i] = balance[i], balance[len(balance)-i-1]
+ }
+ acc.Balance = new(big.Int).SetBytes(balance[:])
+
+ // Decode codehash
+ acc.CodeHash = values[utils.CodeKeccakLeafKey]
+
+ // TODO account.Root is leave as empty. How should we handle the legacy account?
+ return acc, nil
+}
+
+// GetStorage implements state.Trie, retrieving the storage slot with the specified
+// account address and storage key. If the specified slot is not in the verkle tree,
+// nil will be returned. If the tree is corrupted, an error will be returned.
+func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
+ k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
+ val, err := t.root.Get(k, t.nodeResolver)
+ if err != nil {
+ return nil, err
+ }
+ return common.TrimLeftZeroes(val), nil
+}
+
+// UpdateAccount implements state.Trie, writing the provided account into the tree.
+// If the tree is corrupted, an error will be returned.
+func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) error {
+ var (
+ err error
+ nonce, balance [32]byte
+ values = make([][]byte, verkle.NodeWidth)
+ )
+ values[utils.VersionLeafKey] = zero[:]
+ values[utils.CodeKeccakLeafKey] = acc.CodeHash[:]
+
+ // Encode nonce in little-endian
+ binary.LittleEndian.PutUint64(nonce[:], acc.Nonce)
+ values[utils.NonceLeafKey] = nonce[:]
+
+ // Encode balance in little-endian
+ bytes := acc.Balance.Bytes()
+ if len(bytes) > 0 {
+ for i, b := range bytes {
+ balance[len(bytes)-i-1] = b
+ }
+ }
+ values[utils.BalanceLeafKey] = balance[:]
+
+ switch n := t.root.(type) {
+ case *verkle.InternalNode:
+ err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver)
+ if err != nil {
+ return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err)
+ }
+ default:
+ return errInvalidRootType
+ }
+ // TODO figure out if the code size needs to be updated, too
+ return nil
+}
+
+// UpdateStorage implements state.Trie, writing the provided storage slot into
+// the tree. If the tree is corrupted, an error will be returned.
+func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error {
+ // Left padding the slot value to 32 bytes.
+ var v [32]byte
+ if len(value) >= 32 {
+ copy(v[:], value[:32])
+ } else {
+ copy(v[32-len(value):], value[:])
+ }
+ k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(address.Bytes()), key)
+ return t.root.Insert(k, v[:], t.nodeResolver)
+}
+
+// DeleteAccount implements state.Trie, deleting the specified account from the
+// trie. If the account was not existent in the trie, no error will be returned.
+// If the trie is corrupted, an error will be returned.
+func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
+ var (
+ err error
+ values = make([][]byte, verkle.NodeWidth)
+ )
+ for i := 0; i < verkle.NodeWidth; i++ {
+ values[i] = zero[:]
+ }
+ switch n := t.root.(type) {
+ case *verkle.InternalNode:
+ err = n.InsertValuesAtStem(t.cache.GetStem(addr.Bytes()), values, t.nodeResolver)
+ if err != nil {
+ return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err)
+ }
+ default:
+ return errInvalidRootType
+ }
+ return nil
+}
+
+// DeleteStorage implements state.Trie, deleting the specified storage slot from
+// the trie. If the storage slot was not existent in the trie, no error will be
+// returned. If the trie is corrupted, an error will be returned.
+func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error {
+ var zero [32]byte
+ k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
+ return t.root.Insert(k, zero[:], t.nodeResolver)
+}
+
+// Hash returns the root hash of the tree. It does not write to the database and
+// can be used even if the tree doesn't have one.
+func (t *VerkleTrie) Hash() common.Hash {
+ return t.root.Commit().Bytes()
+}
+
+// Commit writes all nodes to the tree's memory database.
+func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
+ root, ok := t.root.(*verkle.InternalNode)
+ if !ok {
+ return common.Hash{}, nil, errors.New("unexpected root node type")
+ }
+ nodes, err := root.BatchSerialize()
+ if err != nil {
+ return common.Hash{}, nil, fmt.Errorf("serializing tree nodes: %s", err)
+ }
+ nodeset := trienode.NewNodeSet(common.Hash{})
+ for _, node := range nodes {
+ // hash parameter is not used in pathdb
+ nodeset.AddNode(node.Path, trienode.New(common.Hash{}, node.SerializedBytes))
+ }
+ // Serialize root commitment form
+ return t.Hash(), nodeset, nil
+}
+
+// NodeIterator implements state.Trie, returning an iterator that returns
+// nodes of the trie. Iteration starts at the key after the given start key.
+//
+// TODO(gballet, rjl493456442) implement it.
+func (t *VerkleTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
+ panic("not implemented")
+}
+
+// Prove implements state.Trie, constructing a Merkle proof for key. The result
+// contains all encoded nodes on the path to the value at key. The value itself
+// is also included in the last node and can be retrieved by verifying the proof.
+//
+// If the trie does not contain a value for key, the returned proof contains all
+// nodes of the longest existing prefix of the key (at least the root), ending
+// with the node that proves the absence of the key.
+//
+// TODO(gballet, rjl493456442) implement it.
+func (t *VerkleTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
+ panic("not implemented")
+}
+
+// Copy returns a deep-copied verkle tree.
+func (t *VerkleTrie) Copy() *VerkleTrie {
+ return &VerkleTrie{
+ root: t.root.Copy(),
+ db: t.db,
+ cache: t.cache,
+ reader: t.reader,
+ }
+}
+
+// IsVerkle indicates if the trie is a Verkle trie.
+func (t *VerkleTrie) IsVerkle() bool {
+ return true
+}
+
+// ChunkedCode represents a sequence of 32-bytes chunks of code (31 bytes of which
+// are actual code, and 1 byte is the pushdata offset).
+type ChunkedCode []byte
+
+// Copy the values here so as to avoid an import cycle
+const (
+ PUSH1 = byte(0x60)
+ PUSH32 = byte(0x7f)
+)
+
+// ChunkifyCode generates the chunked version of an array representing EVM bytecode
+func ChunkifyCode(code []byte) ChunkedCode {
+ var (
+ chunkOffset = 0 // offset in the chunk
+ chunkCount = len(code) / 31
+ codeOffset = 0 // offset in the code
+ )
+ if len(code)%31 != 0 {
+ chunkCount++
+ }
+ chunks := make([]byte, chunkCount*32)
+ for i := 0; i < chunkCount; i++ {
+ // number of bytes to copy, 31 unless the end of the code has been reached.
+ end := 31 * (i + 1)
+ if len(code) < end {
+ end = len(code)
+ }
+ copy(chunks[i*32+1:], code[31*i:end]) // copy the code itself
+
+ // chunk offset = taken from the last chunk.
+ if chunkOffset > 31 {
+ // skip offset calculation if push data covers the whole chunk
+ chunks[i*32] = 31
+ chunkOffset = 1
+ continue
+ }
+ chunks[32*i] = byte(chunkOffset)
+ chunkOffset = 0
+
+ // Check each instruction and update the offset it should be 0 unless
+ // a PUSH-N overflows.
+ for ; codeOffset < end; codeOffset++ {
+ if code[codeOffset] >= PUSH1 && code[codeOffset] <= PUSH32 {
+ codeOffset += int(code[codeOffset] - PUSH1 + 1)
+ if codeOffset+1 >= 31*(i+1) {
+ codeOffset++
+ chunkOffset = codeOffset - 31*(i+1)
+ break
+ }
+ }
+ }
+ }
+ return chunks
+}
+
+// UpdateContractCode implements state.Trie, writing the provided contract code
+// into the trie.
+func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
+ var (
+ chunks = ChunkifyCode(code)
+ values [][]byte
+ key []byte
+ err error
+ )
+ for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
+ groupOffset := (chunknr + 128) % 256
+ if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
+ values = make([][]byte, verkle.NodeWidth)
+ key = utils.CodeChunkKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), uint256.NewInt(chunknr))
+ }
+ values[groupOffset] = chunks[i : i+32]
+
+ // Reuse the calculated key to also update the code size.
+ if i == 0 {
+ cs := make([]byte, 32)
+ binary.LittleEndian.PutUint64(cs, uint64(len(code)))
+ values[utils.CodeSizeLeafKey] = cs
+ }
+ if groupOffset == 255 || len(chunks)-i <= 32 {
+ switch root := t.root.(type) {
+ case *verkle.InternalNode:
+ err = root.InsertValuesAtStem(key[:31], values, t.nodeResolver)
+ if err != nil {
+ return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
+ }
+ default:
+ return errInvalidRootType
+ }
+ }
+ }
+ return nil
+}
+
+func (t *VerkleTrie) ToDot() string {
+ return verkle.ToDot(t.root)
+}
+
+func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
+ return t.reader.node(path, common.Hash{})
+}
diff --git a/trie/verkle_test.go b/trie/verkle_test.go
new file mode 100644
index 000000000..bd31ea387
--- /dev/null
+++ b/trie/verkle_test.go
@@ -0,0 +1,97 @@
+// Copyright 2023 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 .
+
+package trie
+
+import (
+ "bytes"
+ "math/big"
+ "reflect"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/trie/triedb/pathdb"
+ "github.com/ethereum/go-ethereum/trie/utils"
+)
+
+var (
+ accounts = map[common.Address]*types.StateAccount{
+ {1}: {
+ Nonce: 100,
+ Balance: big.NewInt(100),
+ CodeHash: common.Hash{0x1}.Bytes(),
+ },
+ {2}: {
+ Nonce: 200,
+ Balance: big.NewInt(200),
+ CodeHash: common.Hash{0x2}.Bytes(),
+ },
+ }
+ storages = map[common.Address]map[common.Hash][]byte{
+ {1}: {
+ common.Hash{10}: []byte{10},
+ common.Hash{11}: []byte{11},
+ common.MaxHash: []byte{0xff},
+ },
+ {2}: {
+ common.Hash{20}: []byte{20},
+ common.Hash{21}: []byte{21},
+ common.MaxHash: []byte{0xff},
+ },
+ }
+)
+
+func TestVerkleTreeReadWrite(t *testing.T) {
+ db := NewDatabase(rawdb.NewMemoryDatabase(), &Config{
+ IsVerkle: true,
+ PathDB: pathdb.Defaults,
+ })
+ defer db.Close()
+
+ tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
+
+ for addr, acct := range accounts {
+ if err := tr.UpdateAccount(addr, acct); err != nil {
+ t.Fatalf("Failed to update account, %v", err)
+ }
+ for key, val := range storages[addr] {
+ if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil {
+ t.Fatalf("Failed to update account, %v", err)
+ }
+ }
+ }
+
+ for addr, acct := range accounts {
+ stored, err := tr.GetAccount(addr)
+ if err != nil {
+ t.Fatalf("Failed to get account, %v", err)
+ }
+ if !reflect.DeepEqual(stored, acct) {
+ t.Fatal("account is not matched")
+ }
+ for key, val := range storages[addr] {
+ stored, err := tr.GetStorage(addr, key.Bytes())
+ if err != nil {
+ t.Fatalf("Failed to get storage, %v", err)
+ }
+ if !bytes.Equal(stored, val) {
+ t.Fatal("storage is not matched")
+ }
+ }
+ }
+}