diff --git a/cmd/faucet/README.md b/cmd/faucet/README.md
deleted file mode 100644
index 7e857fa0ee..0000000000
--- 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 8f4127216e..0000000000
--- 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 dad5ad84f2..0000000000
--- a/cmd/faucet/faucet.html
+++ /dev/null
@@ -1,233 +0,0 @@
-
-
-
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 39b62c4939..0000000000
--- a/cmd/faucet/faucet_test.go
+++ /dev/null
@@ -1,46 +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) {
- t.Parallel()
- // 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/config.go b/cmd/geth/config.go
index 027dac7bd6..5f52f1df54 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/les_test.go b/cmd/geth/les_test.go
deleted file mode 100644
index 98c8a12dc6..0000000000
--- a/cmd/geth/les_test.go
+++ /dev/null
@@ -1,206 +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) {
- t.Parallel()
- 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/main.go b/cmd/geth/main.go
index d1b14b81cd..e5a17e45cf 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -62,7 +62,7 @@ var (
utils.MinFreeDiskSpaceFlag,
utils.KeyStoreDirFlag,
utils.ExternalSignerFlag,
- utils.NoUSBFlag,
+ utils.NoUSBFlag, // deprecated
utils.USBFlag,
utils.SmartCardDaemonPathFlag,
utils.OverrideCancun,
@@ -87,24 +87,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,
@@ -127,7 +127,7 @@ var (
utils.NoDiscoverFlag,
utils.DiscoveryV4Flag,
utils.DiscoveryV5Flag,
- utils.LegacyDiscoveryV5Flag,
+ utils.LegacyDiscoveryV5Flag, // deprecated
utils.NetrestrictFlag,
utils.NodeKeyFileFlag,
utils.NodeKeyHexFlag,
@@ -306,7 +306,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) &&
@@ -317,11 +317,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 2e03dc5eaa..1d32880325 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/utils/flags.go b/cmd/utils/flags.go
index 72a56e9c28..b49c7c36d5 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"
@@ -255,7 +254,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,
}
@@ -282,41 +281,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",
@@ -1224,25 +1188,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)
}
}
@@ -1340,58 +1304,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 {
@@ -1496,12 +1426,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)
}
@@ -1650,12 +1575,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)
@@ -1734,9 +1658,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
}
@@ -1913,9 +1834,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
@@ -1923,27 +1841,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
}
@@ -1965,13 +1868,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
}
diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go
index 6669ff176f..00237fecaf 100644
--- a/cmd/utils/flags_legacy.go
+++ b/cmd/utils/flags_legacy.go
@@ -39,6 +39,12 @@ var DeprecatedFlags = []cli.Flag{
CacheTrieRejournalFlag,
LegacyDiscoveryV5Flag,
TxLookupLimitFlag,
+ LightServeFlag,
+ LightIngressFlag,
+ LightEgressFlag,
+ LightMaxPeersFlag,
+ LightNoPruneFlag,
+ LightNoSyncServeFlag,
}
var (
@@ -77,6 +83,41 @@ 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,
+ }
)
// showDeprecated displays deprecated flags that will be soon removed from the codebase.
diff --git a/eth/backend.go b/eth/backend.go
index 09559f0ac1..774ffaf248 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/ethconfig/config.go b/eth/ethconfig/config.go
index 5e8f58efdb..ad664afb5b 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -46,16 +46,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/ethstats/ethstats.go b/ethstats/ethstats.go
index 84a6722806..75d0faac54 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/les/api.go b/les/api.go
deleted file mode 100644
index e8490f7b0f..0000000000
--- 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 3e9dbadce8..0000000000
--- 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 484c95504c..0000000000
--- 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 d1efa2f5d3..0000000000
--- 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 a98524ce2e..0000000000
--- 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 be5e9fd564..0000000000
--- 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 50f6dce879..0000000000
--- 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 cb3fc430b7..0000000000
--- 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 695d54e141..0000000000
--- 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 a0319c67f7..0000000000
--- 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 9a93dba145..0000000000
--- 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 307313fb10..0000000000
--- 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 76a241fa5a..0000000000
--- 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 428d7fbf22..0000000000
--- 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 b7cc9bd903..0000000000
--- 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 3afc31272f..0000000000
--- 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 c803a5ddb3..0000000000
--- 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 07d3133c95..0000000000
--- 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 943b05fdfc..0000000000
--- 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 c907018590..0000000000
--- 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 69824a92dd..0000000000
--- 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 b38a393d4c..0000000000
--- 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 d6ca0eac7c..0000000000
--- 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 cfebdbfb9a..0000000000
--- 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 5e354b7efd..0000000000
--- 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 728f960a54..0000000000
--- 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 d84856c7fb..0000000000
--- 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 5b3505064b..0000000000
--- 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 cc5b601713..0000000000
--- 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, nil)
- 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 b258fc3caf..0000000000
--- 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 9a8214ac2f..0000000000
--- 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 6be13eaecd..0000000000
--- 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 40a51fb76f..0000000000
--- 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 5942b06ec0..0000000000
--- 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 98601c4486..0000000000
--- 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 099b61d053..0000000000
--- 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 1c751d8cc6..0000000000
--- 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 70b7ff64f7..0000000000
--- 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 c031b21de5..0000000000
--- 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 62a4285d15..0000000000
--- 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 b219d0439d..0000000000
--- 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 486b00820a..0000000000
--- 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 3e1c0ad987..0000000000
--- 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 135273ef96..0000000000
--- 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 0da850bcac..0000000000
--- 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 9a5a2a98a8..0000000000
--- a/les/vflux/client/fillset_test.go
+++ /dev/null
@@ -1,119 +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) {
- t.Parallel()
-
- 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 ad3f8df5bb..0000000000
--- 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 c7cb649082..0000000000
--- a/les/vflux/client/queueiterator_test.go
+++ /dev/null
@@ -1,99 +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) {
- t.Parallel()
-
- testQueueIterator(t, true)
-}
-
-func TestQueueIteratorLIFO(t *testing.T) {
- t.Parallel()
-
- 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 55d4b165df..0000000000
--- 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 320d1b4b3e..0000000000
--- a/les/vflux/client/requestbasket_test.go
+++ /dev/null
@@ -1,171 +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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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 271d6e0224..0000000000
--- 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 19d4fe6630..0000000000
--- a/les/vflux/client/serverpool_test.go
+++ /dev/null
@@ -1,424 +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) {
- t.Parallel()
-
- testServerPool(t, false, false)
-}
-func TestServerPoolWithPreNeg(t *testing.T) {
- t.Parallel()
-
- testServerPool(t, true, false)
-}
-func TestServerPoolWithPreNegFail(t *testing.T) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- testServerPoolChangedNodes(t, false)
-}
-func TestServerPoolChangedNodesWithPreNeg(t *testing.T) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- testServerPoolRestartNoDiscovery(t, false)
-}
-func TestServerPoolRestartNoDiscoveryWithPreNeg(t *testing.T) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- testServerPoolTrustedNoDiscovery(t, false)
-}
-func TestServerPoolTrustedNoDiscoveryWithPreNeg(t *testing.T) {
- t.Parallel()
-
- 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 7f1ffdbe26..0000000000
--- 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 80ea2047c6..0000000000
--- a/les/vflux/client/timestats_test.go
+++ /dev/null
@@ -1,145 +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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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 e0d1010ffe..0000000000
--- 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 332d65ee51..0000000000
--- a/les/vflux/client/valuetracker_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"
- "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) {
- t.Parallel()
-
- 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 1b37cba6e5..0000000000
--- 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 f6eb2d8813..0000000000
--- a/les/vflux/client/wrsiterator_test.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 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) {
- t.Parallel()
-
- 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 5abae2f537..0000000000
--- 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 b09f7bb501..0000000000
--- 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 e1ff7bf4e9..0000000000
--- a/les/vflux/server/balance_test.go
+++ /dev/null
@@ -1,459 +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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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 9695e79638..0000000000
--- 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 a39cbec36a..0000000000
--- 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 caa4384e19..0000000000
--- a/les/vflux/server/clientdb_test.go
+++ /dev/null
@@ -1,148 +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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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 a525f86368..0000000000
--- 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 7319be0824..0000000000
--- a/les/vflux/server/clientpool_test.go
+++ /dev/null
@@ -1,640 +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) {
- t.Parallel()
-
- testClientPool(t, 10, 100, 0, true)
-}
-
-func TestClientPoolL40C200Free(t *testing.T) {
- t.Parallel()
-
- testClientPool(t, 40, 200, 0, true)
-}
-
-func TestClientPoolL100C300Free(t *testing.T) {
- t.Parallel()
-
- testClientPool(t, 100, 300, 0, true)
-}
-
-func TestClientPoolL10C100P4(t *testing.T) {
- t.Parallel()
-
- testClientPool(t, 10, 100, 4, false)
-}
-
-func TestClientPoolL40C200P30(t *testing.T) {
- t.Parallel()
-
- testClientPool(t, 40, 200, 30, false)
-}
-
-func TestClientPoolL100C300P20(t *testing.T) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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 680aebe2ea..0000000000
--- 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 766026a808..0000000000
--- 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 60b7b83bbc..0000000000
--- a/les/vflux/server/prioritypool_test.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 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) {
- t.Parallel()
-
- 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) {
- t.Parallel()
-
- 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 40515f072e..0000000000
--- 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 2d7e25b684..0000000000
--- 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/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go
deleted file mode 100644
index 209dda0bb9..0000000000
--- 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 53af45ceb4..0000000000
--- 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/vflux/clientpool-fuzzer.go b/tests/fuzzers/vflux/clientpool-fuzzer.go
deleted file mode 100644
index de694a7b3f..0000000000
--- 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 40c5f22905..0000000000
--- 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)
- })
-}