Compare commits
43 Commits
v1.3.7
...
bc-fusion-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bba0e11e9 | ||
|
|
7ade1d2a5d | ||
|
|
e2e2ac750c | ||
|
|
85c6750592 | ||
|
|
7466f0a075 | ||
|
|
64a57a3e2a | ||
|
|
e5822640c6 | ||
|
|
73d19c00cd | ||
|
|
5fa1755329 | ||
|
|
23a8d00334 | ||
|
|
3349a24333 | ||
|
|
7f2ef5987f | ||
|
|
8d51cec12e | ||
|
|
de1a126ec5 | ||
|
|
124939aaa4 | ||
|
|
a25a2143ce | ||
|
|
7c8fa2b880 | ||
|
|
a6befb5078 | ||
|
|
8e56d6b875 | ||
|
|
0bdd0d20e5 | ||
|
|
267c5c028c | ||
|
|
c25594257d | ||
|
|
ad09930bdf | ||
|
|
432085ea62 | ||
|
|
eb4ea42196 | ||
|
|
0ba5816cc7 | ||
|
|
354c0d7180 | ||
|
|
9badb15e80 | ||
|
|
aab5ad94b8 | ||
|
|
c0df5e7000 | ||
|
|
167da21801 | ||
|
|
08f75ca23f | ||
|
|
6685f68995 | ||
|
|
d4f7313760 | ||
|
|
94b68156c8 | ||
|
|
ab8793ae9f | ||
|
|
6744d7c15f | ||
|
|
3414e5672a | ||
|
|
8f3c525adc | ||
|
|
3e9e6423c0 | ||
|
|
5743b067ba | ||
|
|
d3f882d799 | ||
|
|
030e41607e |
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -47,5 +47,3 @@ jobs:
|
||||
run: |
|
||||
go mod download
|
||||
make geth
|
||||
|
||||
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- run: |
|
||||
go mod download
|
||||
go mod tidy
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
|
||||
1
.github/workflows/unit-test.yml
vendored
1
.github/workflows/unit-test.yml
vendored
@@ -52,4 +52,3 @@ jobs:
|
||||
git submodule update --init --depth 1 --recursive
|
||||
go mod download
|
||||
make test
|
||||
|
||||
|
||||
@@ -149,15 +149,8 @@ unzip testnet.zip
|
||||
#### 3. Download snapshot
|
||||
Download latest chaindata snapshot from [here](https://github.com/bnb-chain/bsc-snapshots). Follow the guide to structure your files.
|
||||
|
||||
Note: if you can not download the chaindata snapshot and want to sync from genesis, you have to generate the genesis block first, you have already get the genesis.json in Step 2.
|
||||
So just run:
|
||||
``` shell
|
||||
## It will init genesis with Hash-Base Storage Scheme by default.
|
||||
geth --datadir <datadir> init ./genesis.json
|
||||
Note: If you encounter difficulties downloading the chaindata snapshot and prefer to synchronize from the genesis block on the Chapel testnet, remember to include the additional flag `--chapel` when initially launching Geth.
|
||||
|
||||
## It will init genesis with Path-Base Storage Scheme.
|
||||
geth --datadir <datadir> --state.scheme path init ./genesis.json
|
||||
```
|
||||
#### 4. Start a full node
|
||||
```shell
|
||||
./geth --config ./config.toml --datadir ./node --cache 8000 --rpc.allow-unprotected-txs --history.transactions 0
|
||||
|
||||
@@ -94,7 +94,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis
|
||||
|
||||
filterBackend := &filterBackend{database, blockchain, backend}
|
||||
backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{})
|
||||
backend.events = filters.NewEventSystem(backend.filterSystem, false)
|
||||
backend.events = filters.NewEventSystem(backend.filterSystem)
|
||||
|
||||
header := backend.blockchain.CurrentBlock()
|
||||
block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64())
|
||||
|
||||
@@ -2127,7 +2127,7 @@ func TestGolangBindings(t *testing.T) {
|
||||
t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
replacer = exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/tendermint/tendermint@v0.0.0", "-replace", "github.com/tendermint/tendermint=github.com/bnb-chain/tendermint@v0.31.15") // Repo root
|
||||
replacer = exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/tendermint/tendermint@v0.0.0", "-replace", "github.com/tendermint/tendermint=github.com/bnb-chain/tendermint@v0.31.16") // Repo root
|
||||
replacer.Dir = pkg
|
||||
if out, err := replacer.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to replace tendermint dependency to bnb-chain source: %v\n%s", err, out)
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -530,11 +530,11 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error {
|
||||
for i, key := range hashes {
|
||||
keys[i] = common.CopyBytes(key[:])
|
||||
}
|
||||
nodes := make(light.NodeList, len(proof))
|
||||
nodes := make(trienode.ProofList, len(proof))
|
||||
for i, node := range proof {
|
||||
nodes[i] = node
|
||||
}
|
||||
proofdb := nodes.NodeSet()
|
||||
proofdb := nodes.Set()
|
||||
|
||||
var end []byte
|
||||
if len(keys) > 0 {
|
||||
|
||||
@@ -11,10 +11,7 @@ The `faucet` is a single binary app (everything included) with all configuration
|
||||
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:
|
||||
- `-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).
|
||||
- `-ws` is ws endpoint to what faucet will connect to
|
||||
|
||||
## Funding
|
||||
|
||||
|
||||
@@ -42,21 +42,11 @@ import (
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"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"
|
||||
)
|
||||
@@ -64,10 +54,7 @@ import (
|
||||
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")
|
||||
wsEndpoint = flag.String("ws", "http://127.0.0.1:7777/", "Url to ws endpoint")
|
||||
|
||||
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")
|
||||
@@ -163,15 +150,6 @@ func main() {
|
||||
if err != nil {
|
||||
log.Crit("Failed to read genesis block contents", "genesis", *genesisFlag, "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 {
|
||||
@@ -191,7 +169,7 @@ func main() {
|
||||
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(), bep2eInfos)
|
||||
faucet, err := newFaucet(genesis, *wsEndpoint, ks, website.Bytes(), bep2eInfos)
|
||||
if err != nil {
|
||||
log.Crit("Failed to start faucet", "err", err)
|
||||
}
|
||||
@@ -219,7 +197,6 @@ type bep2eInfo struct {
|
||||
// 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
|
||||
|
||||
@@ -248,65 +225,18 @@ type wsConn struct {
|
||||
wlock sync.Mutex
|
||||
}
|
||||
|
||||
func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte, bep2eInfos map[string]bep2eInfo) (*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"),
|
||||
NoUSB: true,
|
||||
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
|
||||
}
|
||||
func newFaucet(genesis *core.Genesis, url string, ks *keystore.KeyStore, index []byte, bep2eInfos map[string]bep2eInfo) (*faucet, error) {
|
||||
bep2eAbi, err := abi.JSON(strings.NewReader(bep2eAbiJson))
|
||||
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)
|
||||
client, err := ethclient.Dial(url)
|
||||
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,
|
||||
@@ -319,8 +249,8 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui
|
||||
}
|
||||
|
||||
// close terminates the Ethereum connection and tears down the faucet.
|
||||
func (f *faucet) close() error {
|
||||
return f.stack.Close()
|
||||
func (f *faucet) close() {
|
||||
f.client.Close()
|
||||
}
|
||||
|
||||
// listenAndServe registers the HTTP handlers for the faucet and boots it up
|
||||
@@ -342,7 +272,7 @@ func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// apiHandler handles requests for Ether grants and transaction statuses.
|
||||
func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
upgrader := websocket.Upgrader{}
|
||||
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -406,7 +336,6 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
@@ -694,13 +623,11 @@ func (f *faucet) loop() {
|
||||
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)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/prompt"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/iface"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/petnames"
|
||||
@@ -26,6 +28,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/signer/core"
|
||||
)
|
||||
@@ -47,6 +50,10 @@ var (
|
||||
Usage: "Password file path for the imported BLS account , which contains the password to get the private key by decrypting the keystore file",
|
||||
Category: flags.AccountCategory,
|
||||
}
|
||||
chainIdFlag = &cli.Int64Flag{
|
||||
Name: "chain-id",
|
||||
Usage: "The chain id of the network that the validator will be created at",
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -189,6 +196,22 @@ Print summary of existing BLS accounts in the current BLS wallet.`,
|
||||
|
||||
Delete the selected BLS account from the BLS wallet.`,
|
||||
},
|
||||
{
|
||||
Name: "generate-proof",
|
||||
Usage: "Generate ownership proof for the selected BLS account from the BLS wallet",
|
||||
Action: blsAccountGenerateProof,
|
||||
ArgsUsage: "<BLS pubkey>",
|
||||
Category: "BLS ACCOUNT COMMANDS",
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.BLSPasswordFileFlag,
|
||||
chainIdFlag,
|
||||
},
|
||||
Description: `
|
||||
geth bls account generate-proof
|
||||
|
||||
Generate ownership proof for the selected BLS account from the BLS wallet. The proof is used to prove the ownership of the BLS account when creating validator on BSC after feynman upgrade.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -608,3 +631,79 @@ func blsAccountDelete(ctx *cli.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// blsAccountGenerateProof generate ownership proof for a selected BLS account.
|
||||
func blsAccountGenerateProof(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 0 {
|
||||
utils.Fatalf("No BLS account specified.")
|
||||
}
|
||||
var filteredPubKeys []bls.PublicKey
|
||||
for _, str := range ctx.Args().Slice() {
|
||||
pkString := str
|
||||
if strings.Contains(pkString, "0x") {
|
||||
pkString = pkString[2:]
|
||||
}
|
||||
pubKeyBytes, err := hex.DecodeString(pkString)
|
||||
if err != nil {
|
||||
utils.Fatalf("Could not decode string %s as hex.", pkString)
|
||||
}
|
||||
blsPublicKey, err := bls.PublicKeyFromBytes(pubKeyBytes)
|
||||
if err != nil {
|
||||
utils.Fatalf("%#x is not a valid BLS public key.", pubKeyBytes)
|
||||
}
|
||||
filteredPubKeys = append(filteredPubKeys, blsPublicKey)
|
||||
}
|
||||
if len(filteredPubKeys) > 1 {
|
||||
utils.Fatalf("Only support one BLS account specified.")
|
||||
}
|
||||
pubkeyBz := filteredPubKeys[0].Marshal()
|
||||
|
||||
cfg := gethConfig{Node: defaultNodeConfig()}
|
||||
// Load config file.
|
||||
if file := ctx.String(configFileFlag.Name); file != "" {
|
||||
if err := loadConfig(file, &cfg); err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
utils.SetNodeConfig(ctx, &cfg.Node)
|
||||
|
||||
walletDir := filepath.Join(cfg.Node.DataDir, BLSWalletPath)
|
||||
dirExists, err := wallet.Exists(walletDir)
|
||||
if err != nil || !dirExists {
|
||||
utils.Fatalf("BLS wallet not exists.")
|
||||
}
|
||||
|
||||
walletPassword := utils.GetPassPhraseWithList("Enter the password for your BLS wallet.", false, 0, utils.MakePasswordListFromPath(ctx.String(utils.BLSPasswordFileFlag.Name)))
|
||||
w, err := wallet.OpenWallet(context.Background(), &wallet.Config{
|
||||
WalletDir: walletDir,
|
||||
WalletPassword: walletPassword,
|
||||
})
|
||||
if err != nil {
|
||||
utils.Fatalf("Open BLS wallet failed: %v.", err)
|
||||
}
|
||||
km, err := w.InitializeKeymanager(context.Background(), iface.InitKeymanagerConfig{ListenForChanges: false})
|
||||
if err != nil {
|
||||
utils.Fatalf("Initialize key manager failed: %v.", err)
|
||||
}
|
||||
|
||||
chainIdInt64 := ctx.Int64(chainIdFlag.Name)
|
||||
if chainIdInt64 == 0 {
|
||||
utils.Fatalf("Chain id is required.")
|
||||
}
|
||||
chainId := new(big.Int).SetInt64(chainIdInt64)
|
||||
paddedChainIdBytes := make([]byte, 32)
|
||||
copy(paddedChainIdBytes[32-len(chainId.Bytes()):], chainId.Bytes())
|
||||
msgHash := crypto.Keccak256(append(pubkeyBz, paddedChainIdBytes...))
|
||||
|
||||
req := &validatorpb.SignRequest{
|
||||
PublicKey: pubkeyBz,
|
||||
SigningRoot: msgHash,
|
||||
}
|
||||
sig, err := km.Sign(context.Background(), req)
|
||||
if err != nil {
|
||||
utils.Fatalf("Generate signature failed: %v.", err)
|
||||
}
|
||||
fmt.Printf("Proof: %#x\n", sig.Marshal())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func initGenesis(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to write genesis block: %v", err)
|
||||
}
|
||||
log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
|
||||
log.Info(fmt.Sprintf("Successfully wrote genesis state database=%v hash=%v", name, hash))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
|
||||
"github.com/naoina/toml"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
@@ -32,6 +33,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
||||
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
@@ -40,7 +43,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/naoina/toml"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -54,8 +56,9 @@ var (
|
||||
}
|
||||
|
||||
configFileFlag = &cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "TOML configuration file",
|
||||
Name: "config",
|
||||
Usage: "TOML configuration file",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -134,8 +137,12 @@ func loadBaseConfig(ctx *cli.Context) gethConfig {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
if !utils.ValidateStateScheme(cfg.Eth.StateScheme) {
|
||||
utils.Fatalf("invalid state scheme param in config: %s", cfg.Eth.StateScheme)
|
||||
|
||||
scheme := cfg.Eth.StateScheme
|
||||
if scheme != "" {
|
||||
if !rawdb.ValidateStateScheme(scheme) {
|
||||
utils.Fatalf("Invalid state scheme param in config: %s", scheme)
|
||||
}
|
||||
}
|
||||
if cfg.Eth.Genesis != nil && cfg.Eth.Genesis.Config != nil {
|
||||
log.Warn("Chain config in the configuration file is ignored!")
|
||||
@@ -170,6 +177,19 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||
// makeFullNode loads geth configuration and creates the Ethereum backend.
|
||||
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
|
||||
stack, cfg := makeConfigNode(ctx)
|
||||
if ctx.IsSet(utils.RialtoHash.Name) {
|
||||
v := ctx.String(utils.RialtoHash.Name)
|
||||
params.RialtoGenesisHash = common.HexToHash(v)
|
||||
}
|
||||
|
||||
if ctx.IsSet(utils.OverrideShanghai.Name) {
|
||||
v := ctx.Uint64(utils.OverrideShanghai.Name)
|
||||
cfg.Eth.OverrideShanghai = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideKepler.Name) {
|
||||
v := ctx.Uint64(utils.OverrideKepler.Name)
|
||||
cfg.Eth.OverrideKepler = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideCancun.Name) {
|
||||
v := ctx.Uint64(utils.OverrideCancun.Name)
|
||||
cfg.Eth.OverrideCancun = &v
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/triedb/pathdb"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -63,6 +64,7 @@ Remove blockchain and state databases`,
|
||||
dbCompactCmd,
|
||||
dbGetCmd,
|
||||
dbDeleteCmd,
|
||||
dbInspectTrieCmd,
|
||||
dbPutCmd,
|
||||
dbGetSlotsCmd,
|
||||
dbDumpFreezerIndex,
|
||||
@@ -88,6 +90,17 @@ Remove blockchain and state databases`,
|
||||
Usage: "Inspect the storage size for each type of data in the database",
|
||||
Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`,
|
||||
}
|
||||
dbInspectTrieCmd = &cli.Command{
|
||||
Action: inspectTrie,
|
||||
Name: "inspect-trie",
|
||||
ArgsUsage: "<blocknum> <jobnum>",
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.SyncModeFlag,
|
||||
},
|
||||
Usage: "Inspect the MPT tree of the account and contract.",
|
||||
Description: `This commands iterates the entrie WorldState.`,
|
||||
}
|
||||
dbCheckStateContentCmd = &cli.Command{
|
||||
Action: checkStateContent,
|
||||
Name: "check-state-content",
|
||||
@@ -120,6 +133,7 @@ a data corruption.`,
|
||||
utils.DataDirFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.BSCMainnetFlag,
|
||||
utils.ChapelFlag,
|
||||
utils.StateSchemeFlag,
|
||||
},
|
||||
Description: "This command looks up the specified trie node key from the database.",
|
||||
@@ -133,6 +147,7 @@ a data corruption.`,
|
||||
utils.DataDirFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.BSCMainnetFlag,
|
||||
utils.ChapelFlag,
|
||||
utils.StateSchemeFlag,
|
||||
},
|
||||
Description: "This command delete the specify trie node from the database.",
|
||||
@@ -312,6 +327,92 @@ func confirmAndRemoveDB(database string, kind string) {
|
||||
}
|
||||
}
|
||||
|
||||
func inspectTrie(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
|
||||
}
|
||||
|
||||
if ctx.NArg() > 3 {
|
||||
return fmt.Errorf("Max 3 arguments: %v", ctx.Command.ArgsUsage)
|
||||
}
|
||||
|
||||
var (
|
||||
blockNumber uint64
|
||||
trieRootHash common.Hash
|
||||
jobnum uint64
|
||||
)
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
db := utils.MakeChainDatabase(ctx, stack, true, false)
|
||||
defer db.Close()
|
||||
|
||||
var headerBlockHash common.Hash
|
||||
if ctx.NArg() >= 1 {
|
||||
if ctx.Args().Get(0) == "latest" {
|
||||
headerHash := rawdb.ReadHeadHeaderHash(db)
|
||||
blockNumber = *(rawdb.ReadHeaderNumber(db, headerHash))
|
||||
} else if ctx.Args().Get(0) == "snapshot" {
|
||||
trieRootHash = rawdb.ReadSnapshotRoot(db)
|
||||
blockNumber = math.MaxUint64
|
||||
} else {
|
||||
var err error
|
||||
blockNumber, err = strconv.ParseUint(ctx.Args().Get(0), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to Parse blocknum, Args[0]: %v, err: %v", ctx.Args().Get(0), err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.NArg() == 1 {
|
||||
jobnum = 1000
|
||||
} else {
|
||||
var err error
|
||||
jobnum, err = strconv.ParseUint(ctx.Args().Get(1), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to Parse jobnum, Args[1]: %v, err: %v", ctx.Args().Get(1), err)
|
||||
}
|
||||
}
|
||||
|
||||
if blockNumber != math.MaxUint64 {
|
||||
headerBlockHash = rawdb.ReadCanonicalHash(db, blockNumber)
|
||||
if headerBlockHash == (common.Hash{}) {
|
||||
return fmt.Errorf("ReadHeadBlockHash empry hash")
|
||||
}
|
||||
blockHeader := rawdb.ReadHeader(db, headerBlockHash, blockNumber)
|
||||
trieRootHash = blockHeader.Root
|
||||
}
|
||||
if (trieRootHash == common.Hash{}) {
|
||||
log.Error("Empty root hash")
|
||||
}
|
||||
fmt.Printf("ReadBlockHeader, root: %v, blocknum: %v\n", trieRootHash, blockNumber)
|
||||
|
||||
dbScheme := rawdb.ReadStateScheme(db)
|
||||
var config *trie.Config
|
||||
if dbScheme == rawdb.PathScheme {
|
||||
config = &trie.Config{
|
||||
PathDB: pathdb.ReadOnly,
|
||||
}
|
||||
} else if dbScheme == rawdb.HashScheme {
|
||||
config = trie.HashDefaults
|
||||
}
|
||||
|
||||
triedb := trie.NewDatabase(db, config)
|
||||
theTrie, err := trie.New(trie.TrieID(trieRootHash), triedb)
|
||||
if err != nil {
|
||||
fmt.Printf("fail to new trie tree, err: %v, rootHash: %v\n", err, trieRootHash.String())
|
||||
return err
|
||||
}
|
||||
theInspect, err := trie.NewInspector(theTrie, triedb, trieRootHash, blockNumber, jobnum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
theInspect.Run()
|
||||
theInspect.DisplayResult()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inspect(ctx *cli.Context) error {
|
||||
var (
|
||||
prefix []byte
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestCustomGenesis(t *testing.T) {
|
||||
|
||||
// Query the custom genesis block
|
||||
geth := runGeth(t, "--networkid", "1337", "--syncmode=full", "--cache", "16",
|
||||
"--datadir", datadir, "--maxpeers", "0", "--port", "0", "--authrpc.port", "0",
|
||||
"--datadir", datadir, "--maxpeers", "0", "--port", "0",
|
||||
"--nodiscover", "--nat", "none", "--ipcdisable",
|
||||
"--exec", tt.query, "console")
|
||||
geth.ExpectRegexp(tt.result)
|
||||
@@ -141,7 +141,7 @@ func TestCustomBackend(t *testing.T) {
|
||||
}
|
||||
{ // Exec + query
|
||||
args := append(tt.execArgs, "--networkid", "1337", "--syncmode=full", "--cache", "16",
|
||||
"--datadir", datadir, "--maxpeers", "0", "--port", "0", "--authrpc.port", "0",
|
||||
"--datadir", datadir, "--maxpeers", "0", "--port", "0",
|
||||
"--nodiscover", "--nat", "none", "--ipcdisable",
|
||||
"--exec", "eth.getBlock(0).nonce", "console")
|
||||
geth := runGeth(t, args...)
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
type gethrpc struct {
|
||||
name string
|
||||
rpc *rpc.Client
|
||||
geth *testgeth
|
||||
nodeInfo *p2p.NodeInfo
|
||||
}
|
||||
|
||||
func (g *gethrpc) killAndWait() {
|
||||
g.geth.Kill()
|
||||
g.geth.WaitExit()
|
||||
}
|
||||
|
||||
func (g *gethrpc) callRPC(result interface{}, method string, args ...interface{}) {
|
||||
if err := g.rpc.Call(&result, method, args...); err != nil {
|
||||
g.geth.Fatalf("callRPC %v: %v", method, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gethrpc) addPeer(peer *gethrpc) {
|
||||
g.geth.Logf("%v.addPeer(%v)", g.name, peer.name)
|
||||
enode := peer.getNodeInfo().Enode
|
||||
peerCh := make(chan *p2p.PeerEvent)
|
||||
sub, err := g.rpc.Subscribe(context.Background(), "admin", peerCh, "peerEvents")
|
||||
if err != nil {
|
||||
g.geth.Fatalf("subscribe %v: %v", g.name, err)
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
g.callRPC(nil, "admin_addPeer", enode)
|
||||
dur := 14 * time.Second
|
||||
timeout := time.After(dur)
|
||||
select {
|
||||
case ev := <-peerCh:
|
||||
g.geth.Logf("%v received event: type=%v, peer=%v", g.name, ev.Type, ev.Peer)
|
||||
case err := <-sub.Err():
|
||||
g.geth.Fatalf("%v sub error: %v", g.name, err)
|
||||
case <-timeout:
|
||||
g.geth.Error("timeout adding peer after", dur)
|
||||
}
|
||||
}
|
||||
|
||||
// Use this function instead of `g.nodeInfo` directly
|
||||
func (g *gethrpc) getNodeInfo() *p2p.NodeInfo {
|
||||
if g.nodeInfo != nil {
|
||||
return g.nodeInfo
|
||||
}
|
||||
g.nodeInfo = &p2p.NodeInfo{}
|
||||
g.callRPC(&g.nodeInfo, "admin_nodeInfo")
|
||||
return g.nodeInfo
|
||||
}
|
||||
|
||||
// ipcEndpoint resolves an IPC endpoint based on a configured value, taking into
|
||||
// account the set data folders as well as the designated platform we're currently
|
||||
// running on.
|
||||
func ipcEndpoint(ipcPath, datadir string) string {
|
||||
// On windows we can only use plain top-level pipes
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.HasPrefix(ipcPath, `\\.\pipe\`) {
|
||||
return ipcPath
|
||||
}
|
||||
return `\\.\pipe\` + ipcPath
|
||||
}
|
||||
// Resolve names into the data directory full paths otherwise
|
||||
if filepath.Base(ipcPath) == ipcPath {
|
||||
if datadir == "" {
|
||||
return filepath.Join(os.TempDir(), ipcPath)
|
||||
}
|
||||
return filepath.Join(datadir, ipcPath)
|
||||
}
|
||||
return ipcPath
|
||||
}
|
||||
|
||||
// nextIPC ensures that each ipc pipe gets a unique name.
|
||||
// On linux, it works well to use ipc pipes all over the filesystem (in datadirs),
|
||||
// but windows require pipes to sit in "\\.\pipe\". Therefore, to run several
|
||||
// nodes simultaneously, we need to distinguish between them, which we do by
|
||||
// the pipe filename instead of folder.
|
||||
var nextIPC atomic.Uint32
|
||||
|
||||
func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc {
|
||||
ipcName := fmt.Sprintf("geth-%d.ipc", nextIPC.Add(1))
|
||||
args = append([]string{"--networkid=42", "--port=0", "--authrpc.port", "0", "--ipcpath", ipcName}, args...)
|
||||
t.Logf("Starting %v with rpc: %v", name, args)
|
||||
|
||||
g := &gethrpc{
|
||||
name: name,
|
||||
geth: runGeth(t, args...),
|
||||
}
|
||||
ipcpath := ipcEndpoint(ipcName, g.geth.Datadir)
|
||||
// We can't know exactly how long geth will take to start, so we try 10
|
||||
// times over a 5 second period.
|
||||
var err error
|
||||
for i := 0; i < 10; i++ {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if g.rpc, err = rpc.Dial(ipcpath); err == nil {
|
||||
return g
|
||||
}
|
||||
}
|
||||
t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
func initGeth(t *testing.T) string {
|
||||
args := []string{"--networkid=42", "init", "./testdata/clique.json"}
|
||||
t.Logf("Initializing geth: %v ", args)
|
||||
g := runGeth(t, args...)
|
||||
datadir := g.Datadir
|
||||
g.WaitExit()
|
||||
return datadir
|
||||
}
|
||||
|
||||
func startLightServer(t *testing.T) *gethrpc {
|
||||
datadir := initGeth(t)
|
||||
t.Logf("Importing keys to geth")
|
||||
runGeth(t, "account", "import", "--datadir", datadir, "--password", "./testdata/password.txt", "--lightkdf", "./testdata/key.prv").WaitExit()
|
||||
account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105"
|
||||
server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--miner.etherbase=0x02f0d131f1f97aef08aec6e3291b957d9efe7105", "--mine", "--light.serve=100", "--light.maxpeers=1", "--discv4=false", "--nat=extip:127.0.0.1", "--verbosity=4")
|
||||
return server
|
||||
}
|
||||
|
||||
func startClient(t *testing.T, name string) *gethrpc {
|
||||
datadir := initGeth(t)
|
||||
return startGethWithIpc(t, name, "--datadir", datadir, "--discv4=false", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4")
|
||||
}
|
||||
|
||||
func TestPriorityClient(t *testing.T) {
|
||||
lightServer := startLightServer(t)
|
||||
defer lightServer.killAndWait()
|
||||
|
||||
// Start client and add lightServer as peer
|
||||
freeCli := startClient(t, "freeCli")
|
||||
defer freeCli.killAndWait()
|
||||
freeCli.addPeer(lightServer)
|
||||
|
||||
var peers []*p2p.PeerInfo
|
||||
freeCli.callRPC(&peers, "admin_peers")
|
||||
if len(peers) != 1 {
|
||||
t.Errorf("Expected: # of client peers == 1, actual: %v", len(peers))
|
||||
return
|
||||
}
|
||||
|
||||
// Set up priority client, get its nodeID, increase its balance on the lightServer
|
||||
prioCli := startClient(t, "prioCli")
|
||||
defer prioCli.killAndWait()
|
||||
// 3_000_000_000 once we move to Go 1.13
|
||||
tokens := uint64(3000000000)
|
||||
lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens)
|
||||
prioCli.addPeer(lightServer)
|
||||
|
||||
// Check if priority client is actually syncing and the regular client got kicked out
|
||||
prioCli.callRPC(&peers, "admin_peers")
|
||||
if len(peers) != 1 {
|
||||
t.Errorf("Expected: # of prio peers == 1, actual: %v", len(peers))
|
||||
}
|
||||
|
||||
nodes := map[string]*gethrpc{
|
||||
lightServer.getNodeInfo().ID: lightServer,
|
||||
freeCli.getNodeInfo().ID: freeCli,
|
||||
prioCli.getNodeInfo().ID: prioCli,
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
lightServer.callRPC(&peers, "admin_peers")
|
||||
peersWithNames := make(map[string]string)
|
||||
for _, p := range peers {
|
||||
peersWithNames[nodes[p.ID].name] = p.ID
|
||||
}
|
||||
if _, freeClientFound := peersWithNames[freeCli.name]; freeClientFound {
|
||||
t.Error("client is still a peer of lightServer", peersWithNames)
|
||||
}
|
||||
if _, prioClientFound := peersWithNames[prioCli.name]; !prioClientFound {
|
||||
t.Error("prio client is not among lightServer peers", peersWithNames)
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ var (
|
||||
utils.MinFreeDiskSpaceFlag,
|
||||
utils.KeyStoreDirFlag,
|
||||
utils.ExternalSignerFlag,
|
||||
utils.NoUSBFlag,
|
||||
utils.NoUSBFlag, // deprecated
|
||||
utils.DirectBroadcastFlag,
|
||||
utils.DisableSnapProtocolFlag,
|
||||
utils.EnableTrustProtocolFlag,
|
||||
@@ -69,6 +69,9 @@ var (
|
||||
utils.RangeLimitFlag,
|
||||
utils.USBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.RialtoHash,
|
||||
utils.OverrideShanghai,
|
||||
utils.OverrideKepler,
|
||||
utils.OverrideCancun,
|
||||
utils.OverrideVerkle,
|
||||
utils.EnablePersonal,
|
||||
@@ -93,29 +96,30 @@ var (
|
||||
utils.ExitWhenSyncedFlag,
|
||||
utils.GCModeFlag,
|
||||
utils.SnapshotFlag,
|
||||
utils.TxLookupLimitFlag,
|
||||
utils.TxLookupLimitFlag, // deprecated
|
||||
utils.TransactionHistoryFlag,
|
||||
utils.StateSchemeFlag,
|
||||
utils.StateHistoryFlag,
|
||||
utils.PathDBSyncFlag,
|
||||
utils.LightServeFlag,
|
||||
utils.LightIngressFlag,
|
||||
utils.LightEgressFlag,
|
||||
utils.LightMaxPeersFlag,
|
||||
utils.LightNoPruneFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.LightNoSyncServeFlag,
|
||||
utils.LightServeFlag, // deprecated
|
||||
utils.LightIngressFlag, // deprecated
|
||||
utils.LightEgressFlag, // deprecated
|
||||
utils.LightMaxPeersFlag, // deprecated
|
||||
utils.LightNoPruneFlag, // deprecated
|
||||
utils.LightKDFFlag, // deprecated
|
||||
utils.LightNoSyncServeFlag, // deprecated
|
||||
utils.EthRequiredBlocksFlag,
|
||||
utils.LegacyWhitelistFlag,
|
||||
utils.LegacyWhitelistFlag, // deprecated
|
||||
utils.BloomFilterSizeFlag,
|
||||
utils.TriesInMemoryFlag,
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheTrieFlag,
|
||||
utils.CacheTrieJournalFlag,
|
||||
utils.CacheTrieRejournalFlag,
|
||||
utils.CacheTrieJournalFlag, // deprecated
|
||||
utils.CacheTrieRejournalFlag, // deprecated
|
||||
utils.CacheGCFlag,
|
||||
utils.CacheSnapshotFlag,
|
||||
// utils.CacheNoPrefetchFlag,
|
||||
utils.CachePreimagesFlag,
|
||||
utils.PersistDiffFlag,
|
||||
utils.DiffBlockFlag,
|
||||
@@ -135,12 +139,12 @@ var (
|
||||
utils.MinerExtraDataFlag,
|
||||
utils.MinerRecommitIntervalFlag,
|
||||
utils.MinerDelayLeftoverFlag,
|
||||
utils.MinerNewPayloadTimeout,
|
||||
// utils.MinerNewPayloadTimeout,
|
||||
utils.NATFlag,
|
||||
utils.NoDiscoverFlag,
|
||||
utils.DiscoveryV4Flag,
|
||||
utils.DiscoveryV5Flag,
|
||||
utils.LegacyDiscoveryV5Flag,
|
||||
utils.LegacyDiscoveryV5Flag, // deprecated
|
||||
utils.NetrestrictFlag,
|
||||
utils.NodeKeyFileFlag,
|
||||
utils.NodeKeyHexFlag,
|
||||
@@ -173,10 +177,10 @@ var (
|
||||
utils.HTTPListenAddrFlag,
|
||||
utils.HTTPPortFlag,
|
||||
utils.HTTPCORSDomainFlag,
|
||||
utils.AuthListenFlag,
|
||||
utils.AuthPortFlag,
|
||||
utils.AuthVirtualHostsFlag,
|
||||
utils.JWTSecretFlag,
|
||||
// utils.AuthListenFlag,
|
||||
// utils.AuthPortFlag,
|
||||
// utils.AuthVirtualHostsFlag,
|
||||
// utils.JWTSecretFlag,
|
||||
utils.HTTPVirtualHostsFlag,
|
||||
utils.GraphQLEnabledFlag,
|
||||
utils.GraphQLCORSDomainFlag,
|
||||
@@ -292,6 +296,9 @@ func main() {
|
||||
func prepare(ctx *cli.Context) {
|
||||
// If we're running a known preset, log it for convenience.
|
||||
switch {
|
||||
case ctx.IsSet(utils.ChapelFlag.Name):
|
||||
log.Info("Starting BSC on Chapel testnet...")
|
||||
|
||||
case ctx.IsSet(utils.DeveloperFlag.Name):
|
||||
log.Info("Starting Geth in ephemeral dev mode...")
|
||||
log.Warn(`You are running Geth in --dev mode. Please note the following:
|
||||
@@ -314,19 +321,15 @@ func prepare(ctx *cli.Context) {
|
||||
log.Info("Starting Geth on BSC 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.DeveloperFlag.Name) {
|
||||
if !ctx.IsSet(utils.DeveloperFlag.Name) &&
|
||||
!ctx.IsSet(utils.ChapelFlag.Name) {
|
||||
// Nope, we're really on mainnet. Bump that cache up!
|
||||
log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// geth is the main entry point into the system if no special subcommand is run.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
@@ -62,7 +63,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"
|
||||
@@ -164,15 +164,20 @@ var (
|
||||
}
|
||||
NetworkIdFlag = &cli.Uint64Flag{
|
||||
Name: "networkid",
|
||||
Usage: "Explicitly set network id (integer)(For testnets: use --goerli, --sepolia, --holesky instead)",
|
||||
Usage: "Explicitly set network id (integer)(For testnets: use --chapel instead)",
|
||||
Value: ethconfig.Defaults.NetworkId,
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
BSCMainnetFlag = &cli.BoolFlag{
|
||||
Name: "bsc",
|
||||
Name: "mainnet",
|
||||
Usage: "BSC mainnet",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
ChapelFlag = &cli.BoolFlag{
|
||||
Name: "chapel",
|
||||
Usage: "Chapel network: pre-configured Proof-of-Stake-Authority BSC test network",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
DeveloperFlag = &cli.BoolFlag{
|
||||
Name: "dev",
|
||||
Usage: "Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled",
|
||||
@@ -285,6 +290,21 @@ var (
|
||||
Value: &defaultVerifyMode,
|
||||
Category: flags.FastNodeCategory,
|
||||
}
|
||||
RialtoHash = &cli.StringFlag{
|
||||
Name: "rialtohash",
|
||||
Usage: "Manually specify the Rialto Genesis Hash, to trigger builtin network logic",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideShanghai = &cli.Uint64Flag{
|
||||
Name: "override.shanghai",
|
||||
Usage: "Manually specify the Shanghai fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideKepler = &cli.Uint64Flag{
|
||||
Name: "override.kepler",
|
||||
Usage: "Manually specify the Kepler fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideCancun = &cli.Uint64Flag{
|
||||
Name: "override.cancun",
|
||||
Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting",
|
||||
@@ -297,7 +317,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,
|
||||
}
|
||||
@@ -310,7 +330,6 @@ var (
|
||||
StateSchemeFlag = &cli.StringFlag{
|
||||
Name: "state.scheme",
|
||||
Usage: "Scheme to use for storing ethereum state ('hash' or 'path')",
|
||||
Value: rawdb.HashScheme,
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
PathDBSyncFlag = &cli.BoolFlag{
|
||||
@@ -331,41 +350,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",
|
||||
@@ -432,9 +416,10 @@ var (
|
||||
Category: flags.TxPoolCategory,
|
||||
}
|
||||
TxPoolReannounceTimeFlag = &cli.DurationFlag{
|
||||
Name: "txpool.reannouncetime",
|
||||
Usage: "Duration for announcing local pending transactions again (default = 10 years, minimum = 1 minute)",
|
||||
Value: ethconfig.Defaults.TxPool.ReannounceTime,
|
||||
Name: "txpool.reannouncetime",
|
||||
Usage: "Duration for announcing local pending transactions again (default = 10 years, minimum = 1 minute)",
|
||||
Value: ethconfig.Defaults.TxPool.ReannounceTime,
|
||||
Category: flags.TxPoolCategory,
|
||||
}
|
||||
// Blob transaction pool settings
|
||||
BlobPoolDataDirFlag = &cli.StringFlag{
|
||||
@@ -510,7 +495,7 @@ var (
|
||||
PruneAncientDataFlag = &cli.BoolFlag{
|
||||
Name: "pruneancient",
|
||||
Usage: "Prune ancient data, is an optional config and disabled by default. Only keep the latest 9w blocks' data,the older blocks' data will be permanently pruned. Notice:the geth/chaindata/ancient dir will be removed, if restart without the flag, the ancient data will start with the previous point that the oldest unpruned block number. Recommends to the user who don't care about the ancient data.",
|
||||
Category: flags.HistoryCategory,
|
||||
Category: flags.BlockHistoryCategory,
|
||||
}
|
||||
CacheLogSizeFlag = &cli.IntFlag{
|
||||
Name: "cache.blocklogs",
|
||||
@@ -571,10 +556,10 @@ var (
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
MinerNewPayloadTimeout = &cli.DurationFlag{
|
||||
Name: "miner.newpayload-timeout",
|
||||
Usage: "Specify the maximum time allowance for creating a new payload",
|
||||
Value: ethconfig.Defaults.Miner.NewPayloadTimeout,
|
||||
// Category: flags.MinerCategory,
|
||||
Name: "miner.newpayload-timeout",
|
||||
Usage: "Specify the maximum time allowance for creating a new payload",
|
||||
Value: ethconfig.Defaults.Miner.NewPayloadTimeout,
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
|
||||
// Account settings
|
||||
@@ -1056,7 +1041,7 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
||||
BlockAmountReserved = &cli.Uint64Flag{
|
||||
Name: "block-amount-reserved",
|
||||
Usage: "Sets the expected remained amount of blocks for offline block prune",
|
||||
Category: flags.HistoryCategory,
|
||||
Category: flags.BlockHistoryCategory,
|
||||
}
|
||||
|
||||
CheckSnapshotWithMPT = &cli.BoolFlag{
|
||||
@@ -1068,7 +1053,7 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
||||
EnableDoubleSignMonitorFlag = &cli.BoolFlag{
|
||||
Name: "monitor.doublesign",
|
||||
Usage: "Enable double sign monitor to check whether any validator signs multiple blocks",
|
||||
Category: flags.FastFinalityCategory,
|
||||
Category: flags.MinerCategory,
|
||||
}
|
||||
|
||||
VotingEnabledFlag = &cli.BoolFlag{
|
||||
@@ -1110,7 +1095,9 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
||||
|
||||
var (
|
||||
// TestnetFlags is the flag group of all built-in supported testnets.
|
||||
TestnetFlags = []cli.Flag{}
|
||||
TestnetFlags = []cli.Flag{
|
||||
ChapelFlag,
|
||||
}
|
||||
// NetworkFlags is the flag group of all built-in supported networks.
|
||||
NetworkFlags = append([]cli.Flag{BSCMainnetFlag}, TestnetFlags...)
|
||||
|
||||
@@ -1357,25 +1344,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1486,27 +1473,8 @@ 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 max peers per ip is not set, use max peers
|
||||
if cfg.MaxPeersPerIP <= 0 {
|
||||
@@ -1517,36 +1485,21 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||
cfg.MaxPeersPerIP = ctx.Int(MaxPeersPerIPFlag.Name)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -1678,12 +1631,7 @@ func setBLSWalletDir(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)
|
||||
}
|
||||
@@ -1844,12 +1792,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, BSCMainnetFlag, DeveloperFlag)
|
||||
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)
|
||||
@@ -1939,7 +1886,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
}
|
||||
// Parse state scheme, abort the process if it's not compatible.
|
||||
chaindb := tryMakeReadOnlyDatabase(ctx, stack)
|
||||
scheme, err := ResolveStateScheme(ctx, cfg.StateScheme, chaindb)
|
||||
scheme, err := ParseStateScheme(ctx, chaindb)
|
||||
chaindb.Close()
|
||||
if err != nil {
|
||||
Fatalf("%v", err)
|
||||
@@ -1963,9 +1910,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
|
||||
}
|
||||
@@ -2038,6 +1982,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
}
|
||||
cfg.Genesis = core.DefaultBSCGenesisBlock()
|
||||
SetDNSDiscoveryDefaults(cfg, params.BSCGenesisHash)
|
||||
case ctx.Bool(ChapelFlag.Name):
|
||||
if !ctx.IsSet(NetworkIdFlag.Name) {
|
||||
cfg.NetworkId = 97
|
||||
}
|
||||
cfg.Genesis = core.DefaultChapelGenesisBlock()
|
||||
SetDNSDiscoveryDefaults(cfg, params.ChapelGenesisHash)
|
||||
case ctx.Bool(DeveloperFlag.Name):
|
||||
if !ctx.IsSet(NetworkIdFlag.Name) {
|
||||
cfg.NetworkId = 1337
|
||||
@@ -2128,9 +2078,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
|
||||
@@ -2140,27 +2087,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
|
||||
}
|
||||
@@ -2209,13 +2141,12 @@ func EnableMinerInfo(ctx *cli.Context, minerConfig miner.Config) SetupMetricsOpt
|
||||
|
||||
// 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, ethcfg.RangeLimit),
|
||||
Service: filters.NewFilterAPI(filterSystem, ethcfg.RangeLimit),
|
||||
}})
|
||||
return filterSystem
|
||||
}
|
||||
@@ -2399,6 +2330,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis {
|
||||
switch {
|
||||
case ctx.Bool(BSCMainnetFlag.Name):
|
||||
genesis = core.DefaultBSCGenesisBlock()
|
||||
case ctx.Bool(ChapelFlag.Name):
|
||||
genesis = core.DefaultChapelGenesisBlock()
|
||||
case ctx.Bool(DeveloperFlag.Name):
|
||||
Fatalf("Developer chains are ephemeral")
|
||||
}
|
||||
@@ -2485,88 +2418,44 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
|
||||
return preloads
|
||||
}
|
||||
|
||||
// ResolveStateScheme resolve state scheme from CLI flag, config file and persistent state.
|
||||
// The differences between ResolveStateScheme and ParseStateScheme are:
|
||||
// - ResolveStateScheme adds config to compare with CLI and persistent state to ensure correctness.
|
||||
// - ResolveStateScheme is only used in SetEthConfig function.
|
||||
// ParseStateScheme checks if the specified state scheme is compatible with
|
||||
// the stored state.
|
||||
//
|
||||
// 1. If config isn't provided, write hash mode to config by default, so in current function, config is nonempty.
|
||||
// 2. If persistent state and cli is empty, use config param.
|
||||
// 3. If persistent state is empty, provide CLI flag and config, choose CLI to return.
|
||||
// 4. If persistent state is nonempty and CLI isn't provided, persistent state should be equal to config.
|
||||
// 5. If all three items are provided: if any two of the three are not equal, return error.
|
||||
func ResolveStateScheme(ctx *cli.Context, stateSchemeCfg string, disk ethdb.Database) (string, error) {
|
||||
stored := rawdb.ReadStateScheme(disk)
|
||||
if stored == "" {
|
||||
// there is no persistent state data in disk db(e.g. geth init)
|
||||
if !ctx.IsSet(StateSchemeFlag.Name) {
|
||||
log.Info("State scheme set by config", "scheme", stateSchemeCfg)
|
||||
return stateSchemeCfg, nil
|
||||
}
|
||||
// if both CLI flag and config are set, choose CLI
|
||||
scheme := ctx.String(StateSchemeFlag.Name)
|
||||
if !ValidateStateScheme(scheme) {
|
||||
return "", fmt.Errorf("invalid state scheme param in CLI: %s", scheme)
|
||||
}
|
||||
log.Info("State scheme set by CLI", "scheme", scheme)
|
||||
return scheme, nil
|
||||
}
|
||||
if !ctx.IsSet(StateSchemeFlag.Name) {
|
||||
if stored != stateSchemeCfg {
|
||||
return "", fmt.Errorf("incompatible state scheme, stored: %s, config: %s", stored, stateSchemeCfg)
|
||||
}
|
||||
log.Info("State scheme set to already existing", "scheme", stored)
|
||||
return stored, nil
|
||||
}
|
||||
scheme := ctx.String(StateSchemeFlag.Name)
|
||||
if !ValidateStateScheme(scheme) {
|
||||
return "", fmt.Errorf("invalid state scheme param in CLI: %s", scheme)
|
||||
}
|
||||
// if there is persistent state data in disk db, and CLI flag, config are set,
|
||||
// when they all are different, return error
|
||||
if scheme != stored || scheme != stateSchemeCfg || stored != stateSchemeCfg {
|
||||
return "", fmt.Errorf("incompatible state scheme, stored: %s, config: %s, CLI: %s", stored, stateSchemeCfg, scheme)
|
||||
}
|
||||
log.Info("All are provided, state scheme set to already existing", "scheme", stored)
|
||||
return stored, nil
|
||||
}
|
||||
|
||||
// ParseStateScheme resolves scheme identifier from CLI flag. If the provided
|
||||
// state scheme is not compatible with the one of persistent scheme, an error
|
||||
// will be returned.
|
||||
// - If the provided scheme is none, use the scheme consistent with persistent
|
||||
// state, or fallback to hash-based scheme if state is empty.
|
||||
//
|
||||
// - none: use the scheme consistent with persistent state, or fallback
|
||||
// to hash-based scheme if state is empty.
|
||||
// - hash: use hash-based scheme or error out if not compatible with
|
||||
// persistent state scheme.
|
||||
// - path: use path-based scheme or error out if not compatible with
|
||||
// persistent state scheme.
|
||||
// - If the provided scheme is hash, use hash-based scheme or error out if not
|
||||
// compatible with persistent state scheme.
|
||||
//
|
||||
// - If the provided scheme is path: use path-based scheme or error out if not
|
||||
// compatible with persistent state scheme.
|
||||
func ParseStateScheme(ctx *cli.Context, disk ethdb.Database) (string, error) {
|
||||
// If state scheme is not specified, use the scheme consistent
|
||||
// with persistent state, or fallback to hash mode if database
|
||||
// is empty.
|
||||
provided, err := compareCLIWithConfig(ctx)
|
||||
if err != nil {
|
||||
log.Error("failed to compare CLI with config", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
stored := rawdb.ReadStateScheme(disk)
|
||||
if !ctx.IsSet(StateSchemeFlag.Name) {
|
||||
if provided == "" {
|
||||
if stored == "" {
|
||||
// use default scheme for empty database, flip it when
|
||||
// path mode is chosen as default
|
||||
log.Info("State scheme set to default", "scheme", "hash")
|
||||
log.Info("State scheme set to default", "scheme", rawdb.HashScheme)
|
||||
return rawdb.HashScheme, nil
|
||||
}
|
||||
log.Info("State scheme set to already existing", "scheme", stored)
|
||||
log.Info("State scheme set to already existing disk db", "scheme", stored)
|
||||
return stored, nil // reuse scheme of persistent scheme
|
||||
}
|
||||
// If state scheme is specified, ensure it's compatible with
|
||||
// persistent state.
|
||||
scheme := ctx.String(StateSchemeFlag.Name)
|
||||
if !ValidateStateScheme(scheme) {
|
||||
return "", fmt.Errorf("invalid state scheme param in CLI: %s", scheme)
|
||||
// If state scheme is specified, ensure it's compatible with persistent state.
|
||||
if stored == "" || provided == stored {
|
||||
log.Info("State scheme set by user", "scheme", provided)
|
||||
return provided, nil
|
||||
}
|
||||
if stored == "" || scheme == stored {
|
||||
log.Info("State scheme set by user", "scheme", scheme)
|
||||
return scheme, nil
|
||||
}
|
||||
return "", fmt.Errorf("incompatible state scheme, stored: %s, provided: %s", stored, scheme)
|
||||
return "", fmt.Errorf("incompatible state scheme, db stored: %s, user provided: %s", stored, provided)
|
||||
}
|
||||
|
||||
// MakeTrieDatabase constructs a trie database based on the configured scheme.
|
||||
@@ -2593,11 +2482,64 @@ func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, read
|
||||
return trie.NewDatabase(disk, config)
|
||||
}
|
||||
|
||||
// ValidateStateScheme used to check state scheme whether is valid.
|
||||
// Valid state scheme: hash and path.
|
||||
func ValidateStateScheme(stateScheme string) bool {
|
||||
if stateScheme == rawdb.HashScheme || stateScheme == rawdb.PathScheme {
|
||||
return true
|
||||
func compareCLIWithConfig(ctx *cli.Context) (string, error) {
|
||||
var (
|
||||
cfgScheme string
|
||||
err error
|
||||
)
|
||||
if file := ctx.String("config"); file != "" {
|
||||
// we don't validate cfgScheme because it's already checked in cmd/geth/loadBaseConfig
|
||||
if cfgScheme, err = scanConfigForStateScheme(file); err != nil {
|
||||
log.Error("Failed to parse config file", "error", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return false
|
||||
if !ctx.IsSet(StateSchemeFlag.Name) {
|
||||
if cfgScheme != "" {
|
||||
log.Info("Use config state scheme", "config", cfgScheme)
|
||||
}
|
||||
return cfgScheme, nil
|
||||
}
|
||||
|
||||
cliScheme := ctx.String(StateSchemeFlag.Name)
|
||||
if !rawdb.ValidateStateScheme(cliScheme) {
|
||||
return "", fmt.Errorf("invalid state scheme in CLI: %s", cliScheme)
|
||||
}
|
||||
if cfgScheme == "" || cliScheme == cfgScheme {
|
||||
log.Info("Use CLI state scheme", "CLI", cliScheme)
|
||||
return cliScheme, nil
|
||||
}
|
||||
return "", fmt.Errorf("incompatible state scheme, CLI: %s, config: %s", cliScheme, cfgScheme)
|
||||
}
|
||||
|
||||
func scanConfigForStateScheme(file string) (string, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
targetStr := "StateScheme"
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.Contains(line, targetStr) {
|
||||
return indexStateScheme(line), nil
|
||||
}
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func indexStateScheme(str string) string {
|
||||
i1 := strings.Index(str, "\"")
|
||||
i2 := strings.LastIndex(str, "\"")
|
||||
|
||||
if i1 != -1 && i2 != -1 && i1 < i2 {
|
||||
return str[i1+1 : i2]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -18,9 +18,12 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
)
|
||||
|
||||
@@ -65,32 +68,124 @@ func Test_SplitTagsFlag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStateScheme(t *testing.T) {
|
||||
func Test_parseConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
wantResult bool
|
||||
name string
|
||||
fn func() string
|
||||
wantedResult string
|
||||
wantedIsErr bool
|
||||
wantedErrStr string
|
||||
}{
|
||||
{
|
||||
name: "hash scheme",
|
||||
arg: rawdb.HashScheme,
|
||||
wantResult: true,
|
||||
name: "path",
|
||||
fn: func() string {
|
||||
tomlString := `[Eth]NetworkId = 56StateScheme = "path"`
|
||||
return createTempTomlFile(t, tomlString)
|
||||
},
|
||||
wantedResult: rawdb.PathScheme,
|
||||
wantedIsErr: false,
|
||||
wantedErrStr: "",
|
||||
},
|
||||
{
|
||||
name: "path scheme",
|
||||
arg: rawdb.PathScheme,
|
||||
wantResult: true,
|
||||
name: "hash",
|
||||
fn: func() string {
|
||||
tomlString := `[Eth]NetworkId = 56StateScheme = "hash"`
|
||||
return createTempTomlFile(t, tomlString)
|
||||
},
|
||||
wantedResult: rawdb.HashScheme,
|
||||
wantedIsErr: false,
|
||||
wantedErrStr: "",
|
||||
},
|
||||
{
|
||||
name: "invalid scheme",
|
||||
arg: "mockScheme",
|
||||
wantResult: false,
|
||||
name: "empty state scheme",
|
||||
fn: func() string {
|
||||
tomlString := `[Eth]NetworkId = 56StateScheme = ""`
|
||||
return createTempTomlFile(t, tomlString)
|
||||
},
|
||||
wantedResult: "",
|
||||
wantedIsErr: false,
|
||||
wantedErrStr: "",
|
||||
},
|
||||
{
|
||||
name: "unset state scheme",
|
||||
fn: func() string {
|
||||
tomlString := `[Eth]NetworkId = 56`
|
||||
return createTempTomlFile(t, tomlString)
|
||||
},
|
||||
wantedResult: "",
|
||||
wantedIsErr: false,
|
||||
wantedErrStr: "",
|
||||
},
|
||||
{
|
||||
name: "failed to open file",
|
||||
fn: func() string { return "" },
|
||||
wantedResult: "",
|
||||
wantedIsErr: true,
|
||||
wantedErrStr: "open : no such file or directory",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ValidateStateScheme(tt.arg); got != tt.wantResult {
|
||||
t.Errorf("ValidateStateScheme() = %v, want %v", got, tt.wantResult)
|
||||
result, err := scanConfigForStateScheme(tt.fn())
|
||||
if tt.wantedIsErr {
|
||||
assert.Contains(t, err.Error(), tt.wantedErrStr)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.wantedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// createTempTomlFile is a helper function to create a temp file with the provided TOML content
|
||||
func createTempTomlFile(t *testing.T, content string) string {
|
||||
t.Helper()
|
||||
|
||||
dir := t.TempDir()
|
||||
file, err := os.CreateTemp(dir, "config.toml")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create temporary file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(content)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to write to temporary file: %v", err)
|
||||
}
|
||||
return file.Name()
|
||||
}
|
||||
|
||||
func Test_parseString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
wantResult string
|
||||
}{
|
||||
{
|
||||
name: "hash string",
|
||||
arg: "\"hash\"",
|
||||
wantResult: rawdb.HashScheme,
|
||||
},
|
||||
{
|
||||
name: "path string",
|
||||
arg: "\"path\"",
|
||||
wantResult: rawdb.PathScheme,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
arg: "",
|
||||
wantResult: "",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
arg: "\"\"",
|
||||
wantResult: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := indexStateScheme(tt.arg); got != tt.wantResult {
|
||||
t.Errorf("parseString() = %v, want %v", got, tt.wantResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ type PoSA interface {
|
||||
IsSystemContract(to *common.Address) bool
|
||||
EnoughDistance(chain ChainReader, header *types.Header) bool
|
||||
IsLocalBlock(header *types.Header) bool
|
||||
GetJustifiedNumberAndHash(chain ChainHeaderReader, header *types.Header) (uint64, common.Hash, error)
|
||||
GetJustifiedNumberAndHash(chain ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error)
|
||||
GetFinalizedHeader(chain ChainHeaderReader, header *types.Header) *types.Header
|
||||
VerifyVote(chain ChainHeaderReader, vote *types.VoteEnvelope) error
|
||||
IsActiveValidatorAt(chain ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
237
consensus/parlia/feynmanfork.go
Normal file
237
consensus/parlia/feynmanfork.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package parlia
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/systemcontracts"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// TODO: SecondsPerDay represents the seconds in a day, it should be 86400
|
||||
// We set it to 60 for testing purpose and we will change it back to 86400 when launching
|
||||
// const SecondsPerDay uint64 = 86400
|
||||
const SecondsPerDay uint64 = 60
|
||||
|
||||
// the params should be two blocks' time(timestamp)
|
||||
func sameDayInUTC(first, second uint64) bool {
|
||||
return first/SecondsPerDay == second/SecondsPerDay
|
||||
}
|
||||
|
||||
func isBreatheBlock(lastBlockTime, blockTime uint64) bool {
|
||||
return lastBlockTime != 0 && !sameDayInUTC(lastBlockTime, blockTime)
|
||||
}
|
||||
|
||||
// initializeFeynmanContract initialize new contracts of Feynman fork
|
||||
func (p *Parlia) initializeFeynmanContract(state *state.StateDB, header *types.Header, chain core.ChainContext,
|
||||
txs *[]*types.Transaction, receipts *[]*types.Receipt, receivedTxs *[]*types.Transaction, usedGas *uint64, mining bool,
|
||||
) error {
|
||||
// method
|
||||
method := "initialize"
|
||||
|
||||
// initialize contracts
|
||||
contracts := []string{
|
||||
systemcontracts.StakeHubContract,
|
||||
systemcontracts.GovernorContract,
|
||||
systemcontracts.GovTokenContract,
|
||||
systemcontracts.TimelockContract,
|
||||
}
|
||||
// get packed data
|
||||
data, err := p.stakeHubABI.Pack(method)
|
||||
if err != nil {
|
||||
log.Error("Unable to pack tx for initialize feynman contracts", "error", err)
|
||||
return err
|
||||
}
|
||||
for _, c := range contracts {
|
||||
msg := p.getSystemMessage(header.Coinbase, common.HexToAddress(c), data, common.Big0)
|
||||
// apply message
|
||||
log.Info("initialize feynman contract", "block number", header.Number.Uint64(), "contract", c)
|
||||
err = p.applyTransaction(msg, state, header, chain, txs, receipts, receivedTxs, usedGas, mining)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ValidatorItem struct {
|
||||
address common.Address
|
||||
votingPower *big.Int
|
||||
voteAddress []byte
|
||||
}
|
||||
|
||||
// An ValidatorHeap is a max-heap of validator's votingPower.
|
||||
type ValidatorHeap []ValidatorItem
|
||||
|
||||
func (h *ValidatorHeap) Len() int { return len(*h) }
|
||||
|
||||
func (h *ValidatorHeap) Less(i, j int) bool {
|
||||
// We want topK validators with max voting power, so we need a max-heap
|
||||
if (*h)[i].votingPower.Cmp((*h)[j].votingPower) == 0 {
|
||||
return (*h)[i].address.Hex() < (*h)[j].address.Hex()
|
||||
} else {
|
||||
return (*h)[i].votingPower.Cmp((*h)[j].votingPower) == 1
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ValidatorHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] }
|
||||
|
||||
func (h *ValidatorHeap) Push(x interface{}) {
|
||||
*h = append(*h, x.(ValidatorItem))
|
||||
}
|
||||
|
||||
func (h *ValidatorHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*h = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
func (p *Parlia) updateValidatorSetV2(state *state.StateDB, header *types.Header, chain core.ChainContext,
|
||||
txs *[]*types.Transaction, receipts *[]*types.Receipt, receivedTxs *[]*types.Transaction, usedGas *uint64, mining bool,
|
||||
) error {
|
||||
// 1. get all validators and its voting power
|
||||
blockNr := rpc.BlockNumberOrHashWithHash(header.ParentHash, false)
|
||||
validatorItems, err := p.getValidatorElectionInfo(blockNr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxElectedValidators, err := p.getMaxElectedValidators(blockNr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. sort by voting power
|
||||
eValidators, eVotingPowers, eVoteAddrs := getTopValidatorsByVotingPower(validatorItems, maxElectedValidators)
|
||||
|
||||
// 3. update validator set to system contract
|
||||
method := "updateValidatorSetV2"
|
||||
data, err := p.validatorSetABI.Pack(method, eValidators, eVotingPowers, eVoteAddrs)
|
||||
if err != nil {
|
||||
log.Error("Unable to pack tx for updateValidatorSetV2", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// get system message
|
||||
msg := p.getSystemMessage(header.Coinbase, common.HexToAddress(systemcontracts.ValidatorContract), data, common.Big0)
|
||||
// apply message
|
||||
return p.applyTransaction(msg, state, header, chain, txs, receipts, receivedTxs, usedGas, mining)
|
||||
}
|
||||
|
||||
func (p *Parlia) getValidatorElectionInfo(blockNr rpc.BlockNumberOrHash) ([]ValidatorItem, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
method := "getValidatorElectionInfo"
|
||||
toAddress := common.HexToAddress(systemcontracts.StakeHubContract)
|
||||
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
|
||||
|
||||
data, err := p.stakeHubABI.Pack(method, big.NewInt(0), big.NewInt(0))
|
||||
if err != nil {
|
||||
log.Error("Unable to pack tx for getValidatorElectionInfo", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
msgData := (hexutil.Bytes)(data)
|
||||
|
||||
result, err := p.ethAPI.Call(ctx, ethapi.TransactionArgs{
|
||||
Gas: &gas,
|
||||
To: &toAddress,
|
||||
Data: &msgData,
|
||||
}, blockNr, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var validators []common.Address
|
||||
var votingPowers []*big.Int
|
||||
var voteAddrs [][]byte
|
||||
var totalLength *big.Int
|
||||
if err := p.stakeHubABI.UnpackIntoInterface(&[]interface{}{&validators, &votingPowers, &voteAddrs, &totalLength}, method, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if totalLength.Int64() != int64(len(validators)) || totalLength.Int64() != int64(len(votingPowers)) || totalLength.Int64() != int64(len(voteAddrs)) {
|
||||
return nil, fmt.Errorf("validator length not match")
|
||||
}
|
||||
|
||||
validatorItems := make([]ValidatorItem, len(validators))
|
||||
for i := 0; i < len(validators); i++ {
|
||||
validatorItems[i] = ValidatorItem{
|
||||
address: validators[i],
|
||||
votingPower: votingPowers[i],
|
||||
voteAddress: voteAddrs[i],
|
||||
}
|
||||
}
|
||||
|
||||
return validatorItems, nil
|
||||
}
|
||||
|
||||
func (p *Parlia) getMaxElectedValidators(blockNr rpc.BlockNumberOrHash) (maxElectedValidators *big.Int, err error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
method := "maxElectedValidators"
|
||||
toAddress := common.HexToAddress(systemcontracts.StakeHubContract)
|
||||
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
|
||||
|
||||
data, err := p.stakeHubABI.Pack(method)
|
||||
if err != nil {
|
||||
log.Error("Unable to pack tx for maxElectedValidators", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
msgData := (hexutil.Bytes)(data)
|
||||
|
||||
result, err := p.ethAPI.Call(ctx, ethapi.TransactionArgs{
|
||||
Gas: &gas,
|
||||
To: &toAddress,
|
||||
Data: &msgData,
|
||||
}, blockNr, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := p.stakeHubABI.UnpackIntoInterface(&maxElectedValidators, method, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return maxElectedValidators, nil
|
||||
}
|
||||
|
||||
func getTopValidatorsByVotingPower(validatorItems []ValidatorItem, maxElectedValidators *big.Int) ([]common.Address, []uint64, [][]byte) {
|
||||
var validatorHeap ValidatorHeap
|
||||
for i := 0; i < len(validatorItems); i++ {
|
||||
// only keep validators with voting power > 0
|
||||
if validatorItems[i].votingPower.Cmp(big.NewInt(0)) == 1 {
|
||||
validatorHeap = append(validatorHeap, validatorItems[i])
|
||||
}
|
||||
}
|
||||
hp := &validatorHeap
|
||||
heap.Init(hp)
|
||||
|
||||
topN := int(maxElectedValidators.Int64())
|
||||
if topN > len(validatorHeap) {
|
||||
topN = len(validatorHeap)
|
||||
}
|
||||
eValidators := make([]common.Address, topN)
|
||||
eVotingPowers := make([]uint64, topN)
|
||||
eVoteAddrs := make([][]byte, topN)
|
||||
for i := 0; i < topN; i++ {
|
||||
item := heap.Pop(hp).(ValidatorItem)
|
||||
eValidators[i] = item.address
|
||||
// as the decimal in BNB Beacon Chain is 1e8 and in BNB Smart Chain is 1e18, we need to divide it by 1e10
|
||||
eVotingPowers[i] = new(big.Int).Div(item.votingPower, big.NewInt(1e10)).Uint64()
|
||||
eVoteAddrs[i] = item.voteAddress
|
||||
}
|
||||
|
||||
return eValidators, eVotingPowers, eVoteAddrs
|
||||
}
|
||||
166
consensus/parlia/feynmanfork_test.go
Normal file
166
consensus/parlia/feynmanfork_test.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package parlia
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestValidatorHeap(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
k int64
|
||||
validators []ValidatorItem
|
||||
expected []common.Address
|
||||
}{
|
||||
{
|
||||
description: "normal case",
|
||||
k: 2,
|
||||
validators: []ValidatorItem{
|
||||
{
|
||||
address: common.HexToAddress("0x1"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(300), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x1"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x2"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(200), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x2"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x3"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(100), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x3"),
|
||||
},
|
||||
},
|
||||
expected: []common.Address{
|
||||
common.HexToAddress("0x1"),
|
||||
common.HexToAddress("0x2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "same voting power",
|
||||
k: 2,
|
||||
validators: []ValidatorItem{
|
||||
{
|
||||
address: common.HexToAddress("0x1"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(300), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x1"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x2"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(100), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x2"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x3"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(100), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x3"),
|
||||
},
|
||||
},
|
||||
expected: []common.Address{
|
||||
common.HexToAddress("0x1"),
|
||||
common.HexToAddress("0x2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "zero voting power and k > len(validators)",
|
||||
k: 5,
|
||||
validators: []ValidatorItem{
|
||||
{
|
||||
address: common.HexToAddress("0x1"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(300), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x1"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x2"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x2"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x3"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x3"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x4"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x4"),
|
||||
},
|
||||
},
|
||||
expected: []common.Address{
|
||||
common.HexToAddress("0x1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "zero voting power and k < len(validators)",
|
||||
k: 2,
|
||||
validators: []ValidatorItem{
|
||||
{
|
||||
address: common.HexToAddress("0x1"),
|
||||
votingPower: new(big.Int).Mul(big.NewInt(300), big.NewInt(1e10)),
|
||||
voteAddress: []byte("0x1"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x2"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x2"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x3"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x3"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x4"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x4"),
|
||||
},
|
||||
},
|
||||
expected: []common.Address{
|
||||
common.HexToAddress("0x1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "all zero voting power",
|
||||
k: 2,
|
||||
validators: []ValidatorItem{
|
||||
{
|
||||
address: common.HexToAddress("0x1"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x1"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x2"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x2"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x3"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x3"),
|
||||
},
|
||||
{
|
||||
address: common.HexToAddress("0x4"),
|
||||
votingPower: big.NewInt(0),
|
||||
voteAddress: []byte("0x4"),
|
||||
},
|
||||
},
|
||||
expected: []common.Address{},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
eligibleValidators, _, _ := getTopValidatorsByVotingPower(tc.validators, big.NewInt(tc.k))
|
||||
|
||||
// check
|
||||
if len(eligibleValidators) != len(tc.expected) {
|
||||
t.Errorf("expected %d, got %d", len(tc.expected), len(eligibleValidators))
|
||||
}
|
||||
for i := 0; i < len(tc.expected); i++ {
|
||||
if eligibleValidators[i] != tc.expected[i] {
|
||||
t.Errorf("expected %s, got %s", tc.expected[i].Hex(), eligibleValidators[i].Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
@@ -91,6 +90,10 @@ var (
|
||||
common.HexToAddress(systemcontracts.TokenHubContract): true,
|
||||
common.HexToAddress(systemcontracts.RelayerIncentivizeContract): true,
|
||||
common.HexToAddress(systemcontracts.CrossChainContract): true,
|
||||
common.HexToAddress(systemcontracts.StakeHubContract): true,
|
||||
common.HexToAddress(systemcontracts.GovernorContract): true,
|
||||
common.HexToAddress(systemcontracts.GovTokenContract): true,
|
||||
common.HexToAddress(systemcontracts.TimelockContract): true,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -180,7 +183,7 @@ func ecrecover(header *types.Header, sigCache *lru.ARCCache, chainId *big.Int) (
|
||||
signature := header.Extra[len(header.Extra)-extraSeal:]
|
||||
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(SealHash(header, chainId).Bytes(), signature)
|
||||
pubkey, err := crypto.Ecrecover(types.SealHash(header, chainId).Bytes(), signature)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
@@ -200,7 +203,7 @@ func ecrecover(header *types.Header, sigCache *lru.ARCCache, chainId *big.Int) (
|
||||
// or not), which could be abused to produce different hashes for the same header.
|
||||
func ParliaRLP(header *types.Header, chainId *big.Int) []byte {
|
||||
b := new(bytes.Buffer)
|
||||
encodeSigHeader(b, header, chainId)
|
||||
types.EncodeSigHeader(b, header, chainId)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
@@ -227,6 +230,7 @@ type Parlia struct {
|
||||
validatorSetABIBeforeLuban abi.ABI
|
||||
validatorSetABI abi.ABI
|
||||
slashABI abi.ABI
|
||||
stakeHubABI abi.ABI
|
||||
|
||||
// The fields below are for testing only
|
||||
fakeDiff bool // Skip difficulty verifications
|
||||
@@ -269,6 +273,10 @@ func New(
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stABI, err := abi.JSON(strings.NewReader(stakeABI))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c := &Parlia{
|
||||
chainConfig: chainConfig,
|
||||
config: parliaConfig,
|
||||
@@ -280,6 +288,7 @@ func New(
|
||||
validatorSetABIBeforeLuban: vABIBeforeLuban,
|
||||
validatorSetABI: vABI,
|
||||
slashABI: sABI,
|
||||
stakeHubABI: stABI,
|
||||
signer: types.LatestSigner(chainConfig),
|
||||
}
|
||||
|
||||
@@ -447,7 +456,11 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header
|
||||
// The source block should be the highest justified block.
|
||||
sourceNumber := attestation.Data.SourceNumber
|
||||
sourceHash := attestation.Data.SourceHash
|
||||
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, parent)
|
||||
headers := []*types.Header{parent}
|
||||
if len(parents) > 0 {
|
||||
headers = parents
|
||||
}
|
||||
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, headers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
|
||||
}
|
||||
@@ -871,7 +884,7 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head
|
||||
|
||||
// Prepare vote attestation
|
||||
// Prepare vote data
|
||||
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, parent)
|
||||
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{parent})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
|
||||
}
|
||||
@@ -904,7 +917,7 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head
|
||||
// Prepare vote address bitset.
|
||||
for _, valInfo := range snap.Validators {
|
||||
if _, ok := voteAddrSet[valInfo.VoteAddress]; ok {
|
||||
attestation.VoteAddressSet |= 1 << (valInfo.Index - 1) //Index is offset by 1
|
||||
attestation.VoteAddressSet |= 1 << (valInfo.Index - 1) // Index is offset by 1
|
||||
}
|
||||
}
|
||||
validatorsBitSet := bitset.From([]uint64{uint64(attestation.VoteAddressSet)})
|
||||
@@ -1154,6 +1167,31 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
parent := chain.GetHeaderByHash(header.ParentHash)
|
||||
if parent == nil {
|
||||
return errors.New("parent not found")
|
||||
}
|
||||
|
||||
log.Info("!!! DEBUG Finalize block time", "number", header.Number, "time", header.Time, "parent time", parent.Time)
|
||||
if p.chainConfig.IsOnFeynman(header.Number, parent.Time, header.Time) {
|
||||
err := p.initializeFeynmanContract(state, header, cx, txs, receipts, systemTxs, usedGas, false)
|
||||
if err != nil {
|
||||
log.Error("init feynman contract failed", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update validators every day
|
||||
if p.chainConfig.IsFeynman(header.Number, header.Time) && isBreatheBlock(parent.Time, header.Time) {
|
||||
// we should avoid update validators in the Feynman upgrade block
|
||||
if !p.chainConfig.IsOnFeynman(header.Number, parent.Time, header.Time) {
|
||||
if err := p.updateValidatorSetV2(state, header, cx, txs, receipts, systemTxs, usedGas, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(*systemTxs) > 0 {
|
||||
return errors.New("the length of systemTxs do not match")
|
||||
}
|
||||
@@ -1207,15 +1245,41 @@ func (p *Parlia) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
|
||||
|
||||
err := p.distributeIncoming(p.val, state, header, cx, &txs, &receipts, nil, &header.GasUsed, true)
|
||||
if err != nil {
|
||||
log.Error("p.distributeIncoming failed", "err", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if p.chainConfig.IsPlato(header.Number) {
|
||||
if err := p.distributeFinalityReward(chain, state, header, cx, &txs, &receipts, nil, &header.GasUsed, true); err != nil {
|
||||
log.Error("p.p.distributeFinalityReward after Plato failed", "err", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
parent := chain.GetHeaderByHash(header.ParentHash)
|
||||
if parent == nil {
|
||||
return nil, nil, errors.New("parent not found")
|
||||
}
|
||||
log.Info("!!! DEBUG FinalizeAndAssemble block time", "number", header.Number, "time", header.Time, "parent time", parent.Time)
|
||||
if p.chainConfig.IsOnFeynman(header.Number, parent.Time, header.Time) {
|
||||
err := p.initializeFeynmanContract(state, header, cx, &txs, &receipts, nil, &header.GasUsed, true)
|
||||
if err != nil {
|
||||
log.Error("init feynman contract failed", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// update validators every day
|
||||
if p.chainConfig.IsFeynman(header.Number, header.Time) && isBreatheBlock(parent.Time, header.Time) {
|
||||
// we should avoid update validators in the Feynman upgrade block
|
||||
if !p.chainConfig.IsOnFeynman(header.Number, parent.Time, header.Time) {
|
||||
if err := p.updateValidatorSetV2(state, header, cx, &txs, &receipts, nil, &header.GasUsed, true); err != nil {
|
||||
log.Error("p.updateValidatorSetV2 after Feynman failed", "err", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// should not happen. Once happen, stop the node is better than broadcast the block
|
||||
if header.GasLimit < header.GasUsed {
|
||||
return nil, nil, errors.New("gas consumption of system txs exceed the gas limit")
|
||||
@@ -1266,7 +1330,7 @@ func (p *Parlia) VerifyVote(chain consensus.ChainHeaderReader, vote *types.VoteE
|
||||
return fmt.Errorf("target number mismatch")
|
||||
}
|
||||
|
||||
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, header)
|
||||
justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{header})
|
||||
if err != nil {
|
||||
log.Error("failed to get the highest justified number and hash", "headerNumber", header.Number, "headerHash", header.Hash())
|
||||
return fmt.Errorf("unexpected error when getting the highest justified number and hash")
|
||||
@@ -1418,7 +1482,7 @@ func (p *Parlia) Seal(chain consensus.ChainHeaderReader, block *types.Block, res
|
||||
select {
|
||||
case results <- block.WithSeal(header):
|
||||
default:
|
||||
log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header, p.chainConfig.ChainID))
|
||||
log.Warn("Sealing result is not read by miner", "sealhash", types.SealHash(header, p.chainConfig.ChainID))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -1492,7 +1556,7 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
|
||||
// So it's not the real hash of a block, just used as unique id to distinguish task
|
||||
func (p *Parlia) SealHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
encodeSigHeaderWithoutVoteAttestation(hasher, header, p.chainConfig.ChainID)
|
||||
types.EncodeSigHeaderWithoutVoteAttestation(hasher, header, p.chainConfig.ChainID)
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
}
|
||||
@@ -1550,16 +1614,15 @@ func (p *Parlia) getCurrentValidators(blockHash common.Hash, blockNum *big.Int)
|
||||
|
||||
var valSet []common.Address
|
||||
var voteAddrSet []types.BLSPublicKey
|
||||
|
||||
if err := p.validatorSetABI.UnpackIntoInterface(&[]interface{}{&valSet, &voteAddrSet}, method, result); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
voteAddrmap := make(map[common.Address]*types.BLSPublicKey, len(valSet))
|
||||
voteAddrMap := make(map[common.Address]*types.BLSPublicKey, len(valSet))
|
||||
for i := 0; i < len(valSet); i++ {
|
||||
voteAddrmap[valSet[i]] = &(voteAddrSet)[i]
|
||||
voteAddrMap[valSet[i]] = &(voteAddrSet)[i]
|
||||
}
|
||||
return valSet, voteAddrmap, nil
|
||||
return valSet, voteAddrMap, nil
|
||||
}
|
||||
|
||||
// slash spoiled validators
|
||||
@@ -1576,7 +1639,7 @@ func (p *Parlia) distributeIncoming(val common.Address, state *state.StateDB, he
|
||||
doDistributeSysReward := !p.chainConfig.IsKepler(header.Number, header.Time) &&
|
||||
state.GetBalance(common.HexToAddress(systemcontracts.SystemRewardContract)).Cmp(maxSystemBalance) < 0
|
||||
if doDistributeSysReward {
|
||||
var rewards = new(big.Int)
|
||||
rewards := new(big.Int)
|
||||
rewards = rewards.Rsh(balance, systemRewardPercent)
|
||||
if rewards.Cmp(common.Big0) > 0 {
|
||||
err := p.distributeToSystem(rewards, state, header, chain, txs, receipts, receivedTxs, usedGas, mining)
|
||||
@@ -1751,20 +1814,22 @@ func (p *Parlia) applyTransaction(
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetJustifiedNumberAndHash returns the highest justified block's number and hash on the branch including and before `header`
|
||||
func (p *Parlia) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, header *types.Header) (uint64, common.Hash, error) {
|
||||
if chain == nil || header == nil {
|
||||
// GetJustifiedNumberAndHash retrieves the number and hash of the highest justified block
|
||||
// within the branch including `headers` and utilizing the latest element as the head.
|
||||
func (p *Parlia) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error) {
|
||||
if chain == nil || len(headers) == 0 || headers[len(headers)-1] == nil {
|
||||
return 0, common.Hash{}, fmt.Errorf("illegal chain or header")
|
||||
}
|
||||
snap, err := p.snapshot(chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
head := headers[len(headers)-1]
|
||||
snap, err := p.snapshot(chain, head.Number.Uint64(), head.Hash(), headers)
|
||||
if err != nil {
|
||||
log.Error("Unexpected error when getting snapshot",
|
||||
"error", err, "blockNumber", header.Number.Uint64(), "blockHash", header.Hash())
|
||||
"error", err, "blockNumber", head.Number.Uint64(), "blockHash", head.Hash())
|
||||
return 0, common.Hash{}, err
|
||||
}
|
||||
|
||||
if snap.Attestation == nil {
|
||||
if p.chainConfig.IsLuban(header.Number) {
|
||||
if p.chainConfig.IsLuban(head.Number) {
|
||||
log.Debug("once one attestation generated, attestation of snap would not be nil forever basically")
|
||||
}
|
||||
return 0, chain.GetHeaderByNumber(0).Hash(), nil
|
||||
@@ -1796,62 +1861,6 @@ func (p *Parlia) GetFinalizedHeader(chain consensus.ChainHeaderReader, header *t
|
||||
}
|
||||
|
||||
// =========================== utility function ==========================
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
func SealHash(header *types.Header, chainId *big.Int) (hash common.Hash) {
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
encodeSigHeader(hasher, header, chainId)
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
}
|
||||
|
||||
func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) {
|
||||
err := rlp.Encode(w, []interface{}{
|
||||
chainId,
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:len(header.Extra)-extraSeal], // this will panic if extra is too short, should check before calling encodeSigHeader
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
panic("can't encode: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func encodeSigHeaderWithoutVoteAttestation(w io.Writer, header *types.Header, chainId *big.Int) {
|
||||
err := rlp.Encode(w, []interface{}{
|
||||
chainId,
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:extraVanity], // this will panic if extra is too short, should check before calling encodeSigHeaderWithoutVoteAttestation
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
panic("can't encode: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Address) uint64 {
|
||||
if snap.inturn(val) {
|
||||
return 0
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
exlru "github.com/hashicorp/golang-lru"
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
@@ -41,6 +42,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/systemcontracts"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
@@ -58,6 +60,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
badBlockRecords = mapset.NewSet[common.Hash]()
|
||||
badBlockRecordslimit = 1000
|
||||
badBlockGauge = metrics.NewRegisteredGauge("chain/insert/badBlock", nil)
|
||||
|
||||
headBlockGauge = metrics.NewRegisteredGauge("chain/head/block", nil)
|
||||
headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil)
|
||||
headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil)
|
||||
@@ -323,6 +329,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
||||
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
|
||||
return nil, genesisErr
|
||||
}
|
||||
systemcontracts.GenesisHash = genesisHash
|
||||
log.Info("Initialised chain configuration", "config", chainConfig)
|
||||
// Description of chainConfig is empty now
|
||||
/*
|
||||
@@ -632,7 +639,7 @@ func (bc *BlockChain) empty() bool {
|
||||
// GetJustifiedNumber returns the highest justified blockNumber on the branch including and before `header`.
|
||||
func (bc *BlockChain) GetJustifiedNumber(header *types.Header) uint64 {
|
||||
if p, ok := bc.engine.(consensus.PoSA); ok {
|
||||
justifiedBlockNumber, _, err := p.GetJustifiedNumberAndHash(bc, header)
|
||||
justifiedBlockNumber, _, err := p.GetJustifiedNumberAndHash(bc, []*types.Header{header})
|
||||
if err == nil {
|
||||
return justifiedBlockNumber
|
||||
}
|
||||
@@ -2918,15 +2925,22 @@ func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *pa
|
||||
if vcs != "" {
|
||||
vcs = fmt.Sprintf("\nVCS: %s", vcs)
|
||||
}
|
||||
|
||||
if badBlockRecords.Cardinality() < badBlockRecordslimit {
|
||||
badBlockRecords.Add(block.Hash())
|
||||
badBlockGauge.Update(int64(badBlockRecords.Cardinality()))
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`
|
||||
########## BAD BLOCK #########
|
||||
Block: %v (%#x)
|
||||
Miner: %v
|
||||
Error: %v
|
||||
Platform: %v%v
|
||||
Chain config: %#v
|
||||
Receipts: %v
|
||||
##############################
|
||||
`, block.Number(), block.Hash(), err, platform, vcs, config, receiptString)
|
||||
`, block.Number(), block.Hash(), block.Coinbase(), err, platform, vcs, config, receiptString)
|
||||
}
|
||||
|
||||
// InsertHeaderChain attempts to insert the given header chain in to the local
|
||||
|
||||
@@ -71,7 +71,7 @@ func (bc *BlockChain) CurrentSafeBlock() *types.Header {
|
||||
if currentHeader == nil {
|
||||
return nil
|
||||
}
|
||||
_, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(bc, currentHeader)
|
||||
_, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(bc, []*types.Header{currentHeader})
|
||||
if err == nil {
|
||||
return bc.GetHeaderByHash(justifiedBlockHash)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/systemcontracts"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
@@ -63,9 +62,9 @@ type Genesis struct {
|
||||
Number uint64 `json:"number"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas,omitempty" toml:",omitempty"` // EIP-4844, TODO(Nathan): remove tag `omitempty` after cancun fork
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed,omitempty" toml:",omitempty"` // EIP-4844, TODO(Nathan): remove tag `omitempty` after cancun fork
|
||||
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas,omitempty"` // EIP-4844, TODO(Nathan): remove tag `omitempty` after cancun fork
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed,omitempty"` // EIP-4844, TODO(Nathan): remove tag `omitempty` after cancun fork
|
||||
}
|
||||
|
||||
func ReadGenesis(db ethdb.Database) (*Genesis, error) {
|
||||
@@ -274,10 +273,13 @@ func (e *GenesisMismatchError) Error() string {
|
||||
return fmt.Sprintf("database contains incompatible genesis (have %x, new %x)", e.Stored, e.New)
|
||||
}
|
||||
|
||||
// ChainOverrides contains the changes to chain config.
|
||||
// ChainOverrides contains the changes to chain config
|
||||
// Typically, these modifications involve hardforks that are not enabled on the BSC mainnet, intended for testing purposes.
|
||||
type ChainOverrides struct {
|
||||
OverrideCancun *uint64
|
||||
OverrideVerkle *uint64
|
||||
OverrideShanghai *uint64
|
||||
OverrideKepler *uint64
|
||||
OverrideCancun *uint64
|
||||
OverrideVerkle *uint64
|
||||
}
|
||||
|
||||
// SetupGenesisBlock writes or updates the genesis block in db.
|
||||
@@ -303,6 +305,12 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
|
||||
}
|
||||
applyOverrides := func(config *params.ChainConfig) {
|
||||
if config != nil {
|
||||
if overrides != nil && overrides.OverrideShanghai != nil {
|
||||
config.ShanghaiTime = overrides.OverrideShanghai
|
||||
}
|
||||
if overrides != nil && overrides.OverrideKepler != nil {
|
||||
config.KeplerTime = overrides.OverrideKepler
|
||||
}
|
||||
if overrides != nil && overrides.OverrideCancun != nil {
|
||||
config.CancunTime = overrides.OverrideCancun
|
||||
}
|
||||
@@ -313,7 +321,6 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
|
||||
}
|
||||
// Just commit the new block if there is no stored genesis block.
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
systemcontracts.GenesisHash = stored
|
||||
if (stored == common.Hash{}) {
|
||||
if genesis == nil {
|
||||
log.Info("Writing default BSC mainnet genesis block")
|
||||
@@ -572,6 +579,22 @@ func DefaultBSCGenesisBlock() *Genesis {
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultChapelGenesisBlock returns the BSC mainnet genesis block.
|
||||
func DefaultChapelGenesisBlock() *Genesis {
|
||||
alloc := decodePrealloc(bscChapelAllocData)
|
||||
return &Genesis{
|
||||
Config: params.ChapelChainConfig,
|
||||
Nonce: 0,
|
||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001284214b9b9c85549ab3d2b972df0deef66ac2c9b71b214cb885500844365e95cd9942c7276e7fd8a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0980a75ecd1309ea12fa2ed87a8744fbfc9b863d535552c16704d214347f29fa77f77da6d75d7c752f474cf03cceff28abc65c9cbae594f725c80e12d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
GasLimit: 40000000,
|
||||
Difficulty: big.NewInt(1),
|
||||
Mixhash: common.Hash(hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000000")),
|
||||
Coinbase: common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE"),
|
||||
Timestamp: 0x5e9da7ce,
|
||||
Alloc: alloc,
|
||||
}
|
||||
}
|
||||
|
||||
// DeveloperGenesisBlock returns the 'geth --dev' genesis block.
|
||||
func DeveloperGenesisBlock(gasLimit uint64, faucet common.Address) *Genesis {
|
||||
// Override the default period to the user requested one
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -113,7 +113,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
// GetJustifiedNumber returns the highest justified blockNumber on the branch including and before `header`.
|
||||
func (hc *HeaderChain) GetJustifiedNumber(header *types.Header) uint64 {
|
||||
if p, ok := hc.engine.(consensus.PoSA); ok {
|
||||
justifiedBlockNumber, _, err := p.GetJustifiedNumberAndHash(hc, header)
|
||||
justifiedBlockNumber, _, err := p.GetJustifiedNumberAndHash(hc, []*types.Header{header})
|
||||
if err == nil {
|
||||
return justifiedBlockNumber
|
||||
}
|
||||
|
||||
@@ -305,3 +305,12 @@ func ReadStateScheme(db ethdb.Reader) string {
|
||||
}
|
||||
return HashScheme
|
||||
}
|
||||
|
||||
// ValidateStateScheme used to check state scheme whether is valid.
|
||||
// Valid state scheme: hash and path.
|
||||
func ValidateStateScheme(stateScheme string) bool {
|
||||
if stateScheme == HashScheme || stateScheme == PathScheme {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
34
core/rawdb/accessors_trie_test.go
Normal file
34
core/rawdb/accessors_trie_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package rawdb
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateStateScheme(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
wantResult bool
|
||||
}{
|
||||
{
|
||||
name: "hash scheme",
|
||||
arg: HashScheme,
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path scheme",
|
||||
arg: PathScheme,
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "invalid scheme",
|
||||
arg: "mockScheme",
|
||||
wantResult: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ValidateStateScheme(tt.arg); got != tt.wantResult {
|
||||
t.Errorf("ValidateStateScheme() = %v, want %v", got, tt.wantResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
@@ -134,6 +135,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
err := p.engine.Finalize(p.bc, header, statedb, &commonTxs, block.Uncles(), withdrawals, &receipts, &systemTxs, usedGas)
|
||||
if err != nil {
|
||||
log.Error("!!!DEBUG!!! Failed to p.engine.Finalize block", "err", err)
|
||||
return statedb, receipts, allLogs, *usedGas, err
|
||||
}
|
||||
for _, receipt := range receipts {
|
||||
|
||||
@@ -13,4 +13,10 @@ const (
|
||||
TokenManagerContract = "0x0000000000000000000000000000000000001008"
|
||||
CrossChainContract = "0x0000000000000000000000000000000000002000"
|
||||
StakingContract = "0x0000000000000000000000000000000000002001"
|
||||
StakeHubContract = "0x0000000000000000000000000000000000002002"
|
||||
StakeCreditContract = "0x0000000000000000000000000000000000002003"
|
||||
GovernorContract = "0x0000000000000000000000000000000000002004"
|
||||
GovTokenContract = "0x0000000000000000000000000000000000002005"
|
||||
TimelockContract = "0x0000000000000000000000000000000000002006"
|
||||
TokenRecoverPortalContract = "0x0000000000000000000000000000000000003000"
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -26,6 +26,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
@@ -560,8 +562,7 @@ func (d *DiffLayer) DecodeRLP(s *rlp.Stream) error {
|
||||
if err := s.Decode(&ed); err != nil {
|
||||
return err
|
||||
}
|
||||
d.BlockHash, d.Number, d.Codes, d.Destructs, d.Accounts, d.Storages =
|
||||
ed.BlockHash, ed.Number, ed.Codes, ed.Destructs, ed.Accounts, ed.Storages
|
||||
d.BlockHash, d.Number, d.Codes, d.Destructs, d.Accounts, d.Storages = ed.BlockHash, ed.Number, ed.Codes, ed.Destructs, ed.Accounts, ed.Storages
|
||||
|
||||
d.Receipts = make([]*Receipt, len(ed.Receipts))
|
||||
for i, storageReceipt := range ed.Receipts {
|
||||
@@ -608,6 +609,7 @@ func (storage *DiffStorage) Swap(i, j int) {
|
||||
storage.Keys[i], storage.Keys[j] = storage.Keys[j], storage.Keys[i]
|
||||
storage.Vals[i], storage.Vals[j] = storage.Vals[j], storage.Vals[i]
|
||||
}
|
||||
|
||||
func (storage *DiffStorage) Less(i, j int) bool {
|
||||
return string(storage.Keys[i][:]) < string(storage.Keys[j][:])
|
||||
}
|
||||
@@ -622,3 +624,64 @@ type DiffAccountsInBlock struct {
|
||||
BlockHash common.Hash
|
||||
Transactions []DiffAccountsInTx
|
||||
}
|
||||
|
||||
var (
|
||||
extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity
|
||||
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
||||
)
|
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
func SealHash(header *Header, chainId *big.Int) (hash common.Hash) {
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
EncodeSigHeader(hasher, header, chainId)
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
}
|
||||
|
||||
func EncodeSigHeader(w io.Writer, header *Header, chainId *big.Int) {
|
||||
err := rlp.Encode(w, []interface{}{
|
||||
chainId,
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:len(header.Extra)-extraSeal], // this will panic if extra is too short, should check before calling encodeSigHeader
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
panic("can't encode: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func EncodeSigHeaderWithoutVoteAttestation(w io.Writer, header *Header, chainId *big.Int) {
|
||||
err := rlp.Encode(w, []interface{}{
|
||||
chainId,
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:extraVanity], // this will panic if extra is too short, should check before calling encodeSigHeaderWithoutVoteAttestation
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
panic("can't encode: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,23 +17,28 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/blake2b"
|
||||
"github.com/ethereum/go-ethereum/crypto/bls12381"
|
||||
"github.com/ethereum/go-ethereum/crypto/bn256"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// PrecompiledContract is the basic interface for native Go contracts. The implementation
|
||||
@@ -219,6 +224,27 @@ var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
|
||||
common.BytesToAddress([]byte{103}): &cometBFTLightBlockValidate{},
|
||||
}
|
||||
|
||||
// PrecompiledContractsFeynman contains the default set of pre-compiled Ethereum
|
||||
// contracts used in the Feynman release.
|
||||
var PrecompiledContractsFeynman = map[common.Address]PrecompiledContract{
|
||||
common.BytesToAddress([]byte{1}): &ecrecover{},
|
||||
common.BytesToAddress([]byte{2}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{3}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{4}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{5}): &bigModExp{},
|
||||
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
|
||||
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
|
||||
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
|
||||
common.BytesToAddress([]byte{9}): &blake2F{},
|
||||
|
||||
common.BytesToAddress([]byte{100}): &tmHeaderValidate{},
|
||||
common.BytesToAddress([]byte{101}): &iavlMerkleProofValidatePlato{},
|
||||
common.BytesToAddress([]byte{102}): &blsSignatureVerify{},
|
||||
common.BytesToAddress([]byte{103}): &cometBFTLightBlockValidate{},
|
||||
common.BytesToAddress([]byte{104}): &verifyDoubleSignEvidence{},
|
||||
common.BytesToAddress([]byte{105}): &secp256k1SignatureRecover{},
|
||||
}
|
||||
|
||||
// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
|
||||
// contracts specified in EIP-2537. These are exported for testing purposes.
|
||||
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
|
||||
@@ -245,6 +271,7 @@ var (
|
||||
PrecompiledAddressesIstanbul []common.Address
|
||||
PrecompiledAddressesByzantium []common.Address
|
||||
PrecompiledAddressesHomestead []common.Address
|
||||
PrecompiledAddressesFeynman []common.Address
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -281,11 +308,16 @@ func init() {
|
||||
for k := range PrecompiledContractsCancun {
|
||||
PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k)
|
||||
}
|
||||
for k := range PrecompiledContractsFeynman {
|
||||
PrecompiledAddressesFeynman = append(PrecompiledAddressesFeynman, k)
|
||||
}
|
||||
}
|
||||
|
||||
// ActivePrecompiles returns the precompiles enabled with the current configuration.
|
||||
func ActivePrecompiles(rules params.Rules) []common.Address {
|
||||
switch {
|
||||
case rules.IsFeynman:
|
||||
return PrecompiledAddressesFeynman
|
||||
case rules.IsCancun:
|
||||
return PrecompiledAddressesCancun
|
||||
case rules.IsHertz:
|
||||
@@ -561,7 +593,7 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) {
|
||||
// Modulo 0 is undefined, return zero
|
||||
return common.LeftPadBytes([]byte{}, int(modLen)), nil
|
||||
case base.BitLen() == 1: // a bit length of 1 means it's 1 (or -1).
|
||||
//If base == 1, then we can just return base % mod (if mod >= 1, which it is)
|
||||
// If base == 1, then we can just return base % mod (if mod >= 1, which it is)
|
||||
v = base.Mod(base, mod).Bytes()
|
||||
default:
|
||||
v = base.Exp(base, exp, mod).Bytes()
|
||||
@@ -1355,3 +1387,88 @@ func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash {
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// verifyDoubleSignEvidence implements bsc header verification precompile.
|
||||
type verifyDoubleSignEvidence struct{}
|
||||
|
||||
// RequiredGas returns the gas required to execute the pre-compiled contract.
|
||||
func (c *verifyDoubleSignEvidence) RequiredGas(input []byte) uint64 {
|
||||
return params.DoubleSignEvidenceVerifyGas
|
||||
}
|
||||
|
||||
var (
|
||||
extraSeal = 65
|
||||
)
|
||||
|
||||
type DoubleSignEvidence struct {
|
||||
ChainId *big.Int
|
||||
HeaderBytes1 []byte
|
||||
HeaderBytes2 []byte
|
||||
}
|
||||
|
||||
// Run input: rlp encoded DoubleSignEvidence
|
||||
// return:
|
||||
// signer address| evidence height|
|
||||
// 20 bytes | 32 bytes |
|
||||
func (c *verifyDoubleSignEvidence) Run(input []byte) ([]byte, error) {
|
||||
evidence := &DoubleSignEvidence{}
|
||||
err := rlp.DecodeBytes(input, evidence)
|
||||
if err != nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
header1 := &types.Header{}
|
||||
err = rlp.DecodeBytes(evidence.HeaderBytes1, header1)
|
||||
if err != nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
header2 := &types.Header{}
|
||||
err = rlp.DecodeBytes(evidence.HeaderBytes2, header2)
|
||||
if err != nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
// basic check
|
||||
if header1.Number.Cmp(header2.Number) != 0 {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
if header1.ParentHash != header2.ParentHash {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
if len(header1.Extra) < extraSeal || len(header2.Extra) < extraSeal {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
sig1 := header1.Extra[len(header1.Extra)-extraSeal:]
|
||||
sig2 := header2.Extra[len(header2.Extra)-extraSeal:]
|
||||
if bytes.Equal(sig1, sig2) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
// check sig
|
||||
msgHash1 := types.SealHash(header1, evidence.ChainId)
|
||||
msgHash2 := types.SealHash(header2, evidence.ChainId)
|
||||
if bytes.Equal(msgHash1.Bytes(), msgHash2.Bytes()) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
pubkey1, err := secp256k1.RecoverPubkey(msgHash1.Bytes(), sig1)
|
||||
if err != nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
pubkey2, err := secp256k1.RecoverPubkey(msgHash2.Bytes(), sig2)
|
||||
if err != nil {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
if !bytes.Equal(pubkey1, pubkey2) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
|
||||
returnBz := make([]byte, 52) // 20 + 32
|
||||
signerAddr := crypto.Keccak256(pubkey1[1:])[12:]
|
||||
evidenceHeightBz := header1.Number.Bytes()
|
||||
copy(returnBz[:20], signerAddr)
|
||||
copy(returnBz[52-len(evidenceHeightBz):], evidenceHeightBz)
|
||||
|
||||
return returnBz, nil
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@ import (
|
||||
|
||||
"github.com/tendermint/iavl"
|
||||
"github.com/tendermint/tendermint/crypto/merkle"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
//nolint:staticcheck
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
v1 "github.com/ethereum/go-ethereum/core/vm/lightclient/v1"
|
||||
v2 "github.com/ethereum/go-ethereum/core/vm/lightclient/v2"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
@@ -104,7 +108,7 @@ func (c *tmHeaderValidate) Run(input []byte) (result []byte, err error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// iavlMerkleProofValidate implemented as a native contract.
|
||||
type iavlMerkleProofValidate struct {
|
||||
@@ -397,3 +401,43 @@ type cometBFTLightBlockValidateHertz struct {
|
||||
func (c *cometBFTLightBlockValidateHertz) Run(input []byte) (result []byte, err error) {
|
||||
return c.run(input, true)
|
||||
}
|
||||
|
||||
// secp256k1SignatureRecover implemented as a native contract.
|
||||
type secp256k1SignatureRecover struct{}
|
||||
|
||||
func (c *secp256k1SignatureRecover) RequiredGas(input []byte) uint64 {
|
||||
return params.EcrecoverGas
|
||||
}
|
||||
|
||||
const (
|
||||
tmPubKeyLength uint8 = 33
|
||||
tmSignatureLength uint8 = 64
|
||||
tmSignatureMsgHashLength uint8 = 32
|
||||
)
|
||||
|
||||
// input:
|
||||
// | tmPubKey | tmSignature | tmSignatureMsgHash |
|
||||
// | 33 bytes | 64 bytes | 32 bytes |
|
||||
func (c *secp256k1SignatureRecover) Run(input []byte) (result []byte, err error) {
|
||||
if len(input) != int(tmPubKeyLength)+int(tmSignatureLength)+int(tmSignatureMsgHashLength) {
|
||||
return nil, fmt.Errorf("invalid input")
|
||||
}
|
||||
|
||||
log.Warn("!!!DEBUG!! secp256k1SignatureRecover", "input", hexutil.Encode(input))
|
||||
return c.runTMSecp256k1Signature(
|
||||
input[:tmPubKeyLength],
|
||||
input[tmPubKeyLength:tmPubKeyLength+tmSignatureLength],
|
||||
input[tmPubKeyLength+tmSignatureLength:],
|
||||
)
|
||||
}
|
||||
|
||||
func (c *secp256k1SignatureRecover) runTMSecp256k1Signature(pubkey, signatureStr, msgHash []byte) (result []byte, err error) {
|
||||
tmPubKey := secp256k1.PubKeySecp256k1(pubkey)
|
||||
ok := tmPubKey.VerifyBytesWithMsgHash(msgHash, signatureStr)
|
||||
log.Warn("!!!DEBUG!! secp256k1SignatureRecover", "ok", ok)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid signature")
|
||||
}
|
||||
log.Warn("!!!DEBUG!! secp256k1SignatureRecover", "address", hexutil.Encode(tmPubKey.Address().Bytes()))
|
||||
return tmPubKey.Address().Bytes(), nil
|
||||
}
|
||||
|
||||
@@ -373,3 +373,42 @@ func TestCometBFTLightBlockValidateHertz(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectOutputStr, hex.EncodeToString(res))
|
||||
}
|
||||
|
||||
func TestSecp256k1SignatureRecover(t *testing.T) {
|
||||
// local key
|
||||
{
|
||||
pubKey, err := hex.DecodeString("0278caa4d6321aa856d6341dd3e8bcdfe0b55901548871c63c3f5cec43c2ae88a9")
|
||||
require.NoError(t, err)
|
||||
sig, err := hex.DecodeString("0cb78be0d8eaeab991907b06c61240c04f4ca83f54b7799ce77cf029b837988038c4b3b7f5df231695b0d14499b716e1fd6504860eb3c9244ecb4e569d44c062")
|
||||
require.NoError(t, err)
|
||||
msghash, err := hex.DecodeString("b6ac827edff4bbbf23579720782dbef40b65780af292cc66849e7e5944f1230f")
|
||||
require.NoError(t, err)
|
||||
expectedAddr, err := hex.DecodeString("fa3B227adFf8EA1706098928715076D76959Ae6c")
|
||||
require.NoError(t, err)
|
||||
|
||||
input := append(append(pubKey, sig...), msghash...)
|
||||
contract := &secp256k1SignatureRecover{}
|
||||
res, err := contract.Run(input)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expectedAddr, res)
|
||||
}
|
||||
// ledger
|
||||
{
|
||||
pubKey, err := hex.DecodeString("02d63ee39adb1779353b4393dd5ea9d6d2b6df63b71d168571803cc7b9a0a20e98")
|
||||
require.NoError(t, err)
|
||||
sig, err := hex.DecodeString("66bdb5d381b2773c0f569858c7ee143959522d7c1f46dc656c325cb7353ec40c28ec22dff3650b34c096c5b12e702d7237d409f1ebaaa6dd1128a8f2d401fd5b")
|
||||
require.NoError(t, err)
|
||||
msghash, err := hex.DecodeString("c45e8f0dc7c054c31912beeffd6f10f1c585606d61e252e97968cd66661c2571")
|
||||
require.NoError(t, err)
|
||||
expectedAddr, err := hex.DecodeString("65a284146b84210a01add088954bb52d88b230af")
|
||||
require.NoError(t, err)
|
||||
|
||||
input := append(append(pubKey, sig...), msghash...)
|
||||
contract := &secp256k1SignatureRecover{}
|
||||
res, err := contract.Run(input)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expectedAddr, res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
|
||||
common.BytesToAddress([]byte{0x0f, 0x11}): &bls12381MapG1{},
|
||||
common.BytesToAddress([]byte{0x0f, 0x12}): &bls12381MapG2{},
|
||||
common.BytesToAddress([]byte{102}): &blsSignatureVerify{},
|
||||
common.BytesToAddress([]byte{104}): &verifyDoubleSignEvidence{},
|
||||
}
|
||||
|
||||
// EIP-152 test vectors
|
||||
@@ -405,3 +406,14 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
|
||||
}
|
||||
benchmarkPrecompiled("0f", testcase, b)
|
||||
}
|
||||
|
||||
func TestDoubleSignSlash(t *testing.T) {
|
||||
tc := precompiledTest{
|
||||
Input: "f906278202cab9030ff9030ca01062d3d5015b9242bc193a9b0769f3d3780ecb55f97f40a752ae26d0b68cd0d8a0fae1a05fcb14bfd9b8a9f2b65007a9b6c2000de0627a73be644dd993d32342c494976ea74026e726554db657fa54763abd0c3a0aa9a0f385cc58ed297ff0d66eb5580b02853d3478ba418b1819ac659ee05df49b9794a0bf88464af369ed6b8cf02db00f0b9556ffa8d49cd491b00952a7f83431446638a00a6d0870e586a76278fbfdcedf76ef6679af18fc1f9137cfad495f434974ea81b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001820cdf830f4240830f4240846555fa64b90111d983010301846765746888676f312e32302e378664617277696e00007abd731ef8ae07b86091cb8836d58f5444b883422a18825d899035d3e6ea39ad1a50069bf0b86da8b5573dde1cb4a0a34f19ce94e0ef78ff7518c80265b8a3ca56e3c60167523590d4e8dcc324900559465fc0fa403774096614e135de280949b58a45cc96f2ba9e17f848820d41a08429d0d8b33ee72a84f750fefea846cbca54e487129c7961c680bb72309ca888820d42a08c9db14d938b19f9e2261bbeca2679945462be2b58103dfff73665d0d150fb8a804ae755e0fe64b59753f4db6308a1f679747bce186aa2c62b95fa6eeff3fbd08f3b0667e45428a54ade15bad19f49641c499b431b36f65803ea71b379e6b61de501a0232c9ba2d41b40d36ed794c306747bcbc49bf61a0f37409c18bfe2b5bef26a2d880000000000000000b9030ff9030ca01062d3d5015b9242bc193a9b0769f3d3780ecb55f97f40a752ae26d0b68cd0d8a0b2789a5357827ed838335283e15c4dcc42b9bebcbf2919a18613246787e2f96094976ea74026e726554db657fa54763abd0c3a0aa9a071ce4c09ee275206013f0063761bc19c93c13990582f918cc57333634c94ce89a00e095703e5c9b149f253fe89697230029e32484a410b4b1f2c61442d73c3095aa0d317ae19ede7c8a2d3ac9ef98735b049bcb7278d12f48c42b924538b60a25e12b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001820cdf830f4240830f4240846555fa64b90111d983010301846765746888676f312e32302e378664617277696e00007abd731ef8ae07b86091cb8836d58f5444b883422a18825d899035d3e6ea39ad1a50069bf0b86da8b5573dde1cb4a0a34f19ce94e0ef78ff7518c80265b8a3ca56e3c60167523590d4e8dcc324900559465fc0fa403774096614e135de280949b58a45cc96f2ba9e17f848820d41a08429d0d8b33ee72a84f750fefea846cbca54e487129c7961c680bb72309ca888820d42a08c9db14d938b19f9e2261bbeca2679945462be2b58103dfff73665d0d150fb8a80c0b17bfe88534296ff064cb7156548f6deba2d6310d5044ed6485f087dc6ef232e051c28e1909c2b50a3b4f29345d66681c319bef653e52e5d746480d5a3983b00a0b56228685be711834d0f154292d07826dea42a0fad3e4f56c31470b7fbfbea26880000000000000000",
|
||||
Expected: "15d34aaf54267db7d7c367839aaf71a00a2c6a650000000000000000000000000000000000000000000000000000000000000cdf",
|
||||
Gas: 1000,
|
||||
Name: "",
|
||||
}
|
||||
|
||||
testPrecompiled("68", tc, t)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ type (
|
||||
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
||||
var precompiles map[common.Address]PrecompiledContract
|
||||
switch {
|
||||
case evm.chainRules.IsFeynman:
|
||||
precompiles = PrecompiledContractsFeynman
|
||||
case evm.chainRules.IsCancun:
|
||||
precompiles = PrecompiledContractsCancun
|
||||
case evm.chainRules.IsHertz:
|
||||
|
||||
@@ -218,7 +218,7 @@ func (voteManager *VoteManager) loop() {
|
||||
// A validator must not vote within the span of its other votes . (Rule 2)
|
||||
// Validators always vote for their canonical chain’s latest block. (Rule 3)
|
||||
func (voteManager *VoteManager) UnderRules(header *types.Header) (bool, uint64, common.Hash) {
|
||||
sourceNumber, sourceHash, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, header)
|
||||
sourceNumber, sourceHash, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, []*types.Header{header})
|
||||
if err != nil {
|
||||
log.Error("failed to get the highest justified number and hash at cur header", "curHeader's BlockNumber", header.Number, "curHeader's BlockHash", header.Hash())
|
||||
return false, 0, common.Hash{}
|
||||
|
||||
@@ -78,15 +78,15 @@ func newTestBackend() *testBackend {
|
||||
func (b *testBackend) IsMining() bool { return true }
|
||||
func (b *testBackend) EventMux() *event.TypeMux { return b.eventMux }
|
||||
|
||||
func (p *mockPOSA) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, header *types.Header) (uint64, common.Hash, error) {
|
||||
parentHeader := chain.GetHeaderByHash(header.ParentHash)
|
||||
func (p *mockPOSA) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error) {
|
||||
parentHeader := chain.GetHeaderByHash(headers[len(headers)-1].ParentHash)
|
||||
if parentHeader == nil {
|
||||
return 0, common.Hash{}, fmt.Errorf("unexpected error")
|
||||
}
|
||||
return parentHeader.Number.Uint64(), parentHeader.Hash(), nil
|
||||
}
|
||||
|
||||
func (p *mockInvalidPOSA) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, header *types.Header) (uint64, common.Hash, error) {
|
||||
func (p *mockInvalidPOSA) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error) {
|
||||
return 0, common.Hash{}, fmt.Errorf("not supported")
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,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)
|
||||
@@ -165,6 +165,24 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Override the chain config with provided settings.
|
||||
var overrides core.ChainOverrides
|
||||
if config.OverrideShanghai != nil {
|
||||
chainConfig.ShanghaiTime = config.OverrideShanghai
|
||||
overrides.OverrideShanghai = config.OverrideShanghai
|
||||
}
|
||||
if config.OverrideKepler != nil {
|
||||
chainConfig.KeplerTime = config.OverrideKepler
|
||||
overrides.OverrideKepler = config.OverrideKepler
|
||||
}
|
||||
if config.OverrideCancun != nil {
|
||||
chainConfig.CancunTime = config.OverrideCancun
|
||||
overrides.OverrideCancun = config.OverrideCancun
|
||||
}
|
||||
if config.OverrideVerkle != nil {
|
||||
chainConfig.VerkleTime = config.OverrideVerkle
|
||||
overrides.OverrideVerkle = config.OverrideVerkle
|
||||
}
|
||||
|
||||
eth := &Ethereum{
|
||||
config: config,
|
||||
@@ -241,14 +259,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
|
||||
peers := newPeerSet()
|
||||
bcOps = append(bcOps, core.EnableBlockValidator(chainConfig, eth.engine, config.TriesVerifyMode, peers))
|
||||
// Override the chain config with provided settings.
|
||||
var overrides core.ChainOverrides
|
||||
if config.OverrideCancun != nil {
|
||||
overrides.OverrideCancun = config.OverrideCancun
|
||||
}
|
||||
if config.OverrideVerkle != nil {
|
||||
overrides.OverrideVerkle = config.OverrideVerkle
|
||||
}
|
||||
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory, bcOps...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -403,7 +413,7 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.eventMux),
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Service: filters.NewFilterAPI(filters.NewFilterSystem(s.APIBackend, filters.Config{}), false, s.config.RangeLimit),
|
||||
Service: filters.NewFilterAPI(filters.NewFilterSystem(s.APIBackend, filters.Config{}), s.config.RangeLimit),
|
||||
}, {
|
||||
Namespace: "admin",
|
||||
Service: NewAdminAPI(s),
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/consensus/parlia"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
@@ -48,14 +47,6 @@ var FullNodeGPO = gasprice.Config{
|
||||
IgnorePrice: gasprice.DefaultIgnorePrice,
|
||||
}
|
||||
|
||||
// LightClientGPO contains default gasprice oracle settings for light client.
|
||||
var LightClientGPO = gasprice.Config{
|
||||
Blocks: 2,
|
||||
Percentile: 60,
|
||||
MaxPrice: gasprice.DefaultMaxPrice,
|
||||
IgnorePrice: gasprice.DefaultIgnorePrice,
|
||||
}
|
||||
|
||||
// Defaults contains default settings for use on the BSC main net.
|
||||
var Defaults = Config{
|
||||
SyncMode: downloader.SnapSync,
|
||||
@@ -63,7 +54,6 @@ var Defaults = Config{
|
||||
TxLookupLimit: 2350000,
|
||||
TransactionHistory: 2350000,
|
||||
StateHistory: params.FullImmutabilityThreshold,
|
||||
StateScheme: rawdb.HashScheme,
|
||||
LightPeers: 100,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 154,
|
||||
@@ -113,8 +103,8 @@ type Config struct {
|
||||
NoPruning bool // Whether to disable pruning and flush everything to disk
|
||||
NoPrefetch bool
|
||||
DirectBroadcast bool
|
||||
DisableSnapProtocol bool //Whether disable snap protocol
|
||||
EnableTrustProtocol bool //Whether enable trust protocol
|
||||
DisableSnapProtocol bool // Whether disable snap protocol
|
||||
EnableTrustProtocol bool // Whether enable trust protocol
|
||||
PipeCommit bool
|
||||
RangeLimit bool
|
||||
|
||||
@@ -192,6 +182,12 @@ type Config struct {
|
||||
// send-transaction variants. The unit is ether.
|
||||
RPCTxFeeCap float64
|
||||
|
||||
// OverrideShanghai (TODO: remove after the fork)
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
|
||||
// OverrideKepler (TODO: remove after the fork)
|
||||
OverrideKepler *uint64 `toml:",omitempty"`
|
||||
|
||||
// OverrideCancun (TODO: remove after the fork)
|
||||
OverrideCancun *uint64 `toml:",omitempty"`
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
TransactionHistory uint64 `toml:",omitempty"`
|
||||
StateHistory uint64 `toml:",omitempty"`
|
||||
StateScheme string `toml:",omitempty"`
|
||||
PathSyncFlush bool `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
LightServ int `toml:",omitempty"`
|
||||
LightIngress int `toml:",omitempty"`
|
||||
@@ -68,6 +69,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
RPCGasCap uint64
|
||||
RPCEVMTimeout time.Duration
|
||||
RPCTxFeeCap float64
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
OverrideKepler *uint64 `toml:",omitempty"`
|
||||
OverrideCancun *uint64 `toml:",omitempty"`
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
}
|
||||
@@ -91,6 +94,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.TransactionHistory = c.TransactionHistory
|
||||
enc.StateHistory = c.StateHistory
|
||||
enc.StateScheme = c.StateScheme
|
||||
enc.PathSyncFlush = c.PathSyncFlush
|
||||
enc.RequiredBlocks = c.RequiredBlocks
|
||||
enc.LightServ = c.LightServ
|
||||
enc.LightIngress = c.LightIngress
|
||||
@@ -123,6 +127,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.RPCGasCap = c.RPCGasCap
|
||||
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
||||
enc.RPCTxFeeCap = c.RPCTxFeeCap
|
||||
enc.OverrideShanghai = c.OverrideShanghai
|
||||
enc.OverrideKepler = c.OverrideKepler
|
||||
enc.OverrideCancun = c.OverrideCancun
|
||||
enc.OverrideVerkle = c.OverrideVerkle
|
||||
return &enc, nil
|
||||
@@ -150,6 +156,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
TransactionHistory *uint64 `toml:",omitempty"`
|
||||
StateHistory *uint64 `toml:",omitempty"`
|
||||
StateScheme *string `toml:",omitempty"`
|
||||
PathSyncFlush *bool `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
LightServ *int `toml:",omitempty"`
|
||||
LightIngress *int `toml:",omitempty"`
|
||||
@@ -182,6 +189,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
RPCGasCap *uint64
|
||||
RPCEVMTimeout *time.Duration
|
||||
RPCTxFeeCap *float64
|
||||
OverrideShanghai *uint64 `toml:",omitempty"`
|
||||
OverrideKepler *uint64 `toml:",omitempty"`
|
||||
OverrideCancun *uint64 `toml:",omitempty"`
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
}
|
||||
@@ -246,6 +255,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.StateScheme != nil {
|
||||
c.StateScheme = *dec.StateScheme
|
||||
}
|
||||
if dec.PathSyncFlush != nil {
|
||||
c.PathSyncFlush = *dec.PathSyncFlush
|
||||
}
|
||||
if dec.RequiredBlocks != nil {
|
||||
c.RequiredBlocks = dec.RequiredBlocks
|
||||
}
|
||||
@@ -342,6 +354,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.RPCTxFeeCap != nil {
|
||||
c.RPCTxFeeCap = *dec.RPCTxFeeCap
|
||||
}
|
||||
if dec.OverrideShanghai != nil {
|
||||
c.OverrideShanghai = dec.OverrideShanghai
|
||||
}
|
||||
if dec.OverrideKepler != nil {
|
||||
c.OverrideKepler = dec.OverrideKepler
|
||||
}
|
||||
if dec.OverrideCancun != nil {
|
||||
c.OverrideCancun = dec.OverrideCancun
|
||||
}
|
||||
|
||||
@@ -64,10 +64,10 @@ type FilterAPI struct {
|
||||
}
|
||||
|
||||
// NewFilterAPI returns a new FilterAPI instance.
|
||||
func NewFilterAPI(system *FilterSystem, lightMode bool, rangeLimit bool) *FilterAPI {
|
||||
func NewFilterAPI(system *FilterSystem, rangeLimit bool) *FilterAPI {
|
||||
api := &FilterAPI{
|
||||
sys: system,
|
||||
events: NewEventSystem(system, lightMode),
|
||||
events: NewEventSystem(system),
|
||||
filters: make(map[rpc.ID]*filter),
|
||||
timeout: system.cfg.Timeout,
|
||||
rangeLimit: rangeLimit,
|
||||
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"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/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
@@ -205,10 +204,8 @@ type subscription struct {
|
||||
// EventSystem creates subscriptions, processes events and broadcasts them to the
|
||||
// subscription which match the subscription criteria.
|
||||
type EventSystem struct {
|
||||
backend Backend
|
||||
sys *FilterSystem
|
||||
lightMode bool
|
||||
lastHead *types.Header
|
||||
backend Backend
|
||||
sys *FilterSystem
|
||||
|
||||
// Subscriptions
|
||||
txsSub event.Subscription // Subscription for new transaction event
|
||||
@@ -237,11 +234,10 @@ type EventSystem struct {
|
||||
//
|
||||
// The returned manager has a loop that needs to be stopped with the Stop function
|
||||
// or by stopping the given mux.
|
||||
func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem {
|
||||
func NewEventSystem(sys *FilterSystem) *EventSystem {
|
||||
m := &EventSystem{
|
||||
sys: sys,
|
||||
backend: sys.backend,
|
||||
lightMode: lightMode,
|
||||
install: make(chan *subscription),
|
||||
uninstall: make(chan *subscription),
|
||||
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
||||
@@ -523,21 +519,6 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent)
|
||||
for _, f := range filters[BlocksSubscription] {
|
||||
f.headers <- ev.Block.Header()
|
||||
}
|
||||
if es.lightMode && len(filters[LogsSubscription]) > 0 {
|
||||
es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) {
|
||||
for _, f := range filters[LogsSubscription] {
|
||||
if f.logsCrit.FromBlock != nil && header.Number.Cmp(f.logsCrit.FromBlock) < 0 {
|
||||
continue
|
||||
}
|
||||
if f.logsCrit.ToBlock != nil && header.Number.Cmp(f.logsCrit.ToBlock) > 0 {
|
||||
continue
|
||||
}
|
||||
if matchedLogs := es.lightFilterLogs(header, f.logsCrit.Addresses, f.logsCrit.Topics, remove); len(matchedLogs) > 0 {
|
||||
f.logs <- matchedLogs
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (es *EventSystem) handleFinalizedHeaderEvent(filters filterIndex, ev core.FinalizedHeaderEvent) {
|
||||
@@ -546,76 +527,6 @@ func (es *EventSystem) handleFinalizedHeaderEvent(filters filterIndex, ev core.F
|
||||
}
|
||||
}
|
||||
|
||||
func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func(*types.Header, bool)) {
|
||||
oldh := es.lastHead
|
||||
es.lastHead = newHeader
|
||||
if oldh == nil {
|
||||
return
|
||||
}
|
||||
newh := newHeader
|
||||
// find common ancestor, create list of rolled back and new block hashes
|
||||
var oldHeaders, newHeaders []*types.Header
|
||||
for oldh.Hash() != newh.Hash() {
|
||||
if oldh.Number.Uint64() >= newh.Number.Uint64() {
|
||||
oldHeaders = append(oldHeaders, oldh)
|
||||
oldh = rawdb.ReadHeader(es.backend.ChainDb(), oldh.ParentHash, oldh.Number.Uint64()-1)
|
||||
}
|
||||
if oldh.Number.Uint64() < newh.Number.Uint64() {
|
||||
newHeaders = append(newHeaders, newh)
|
||||
newh = rawdb.ReadHeader(es.backend.ChainDb(), newh.ParentHash, newh.Number.Uint64()-1)
|
||||
if newh == nil {
|
||||
// happens when CHT syncing, nothing to do
|
||||
newh = oldh
|
||||
}
|
||||
}
|
||||
}
|
||||
// roll back old blocks
|
||||
for _, h := range oldHeaders {
|
||||
callBack(h, true)
|
||||
}
|
||||
// check new blocks (array is in reverse order)
|
||||
for i := len(newHeaders) - 1; i >= 0; i-- {
|
||||
callBack(newHeaders[i], false)
|
||||
}
|
||||
}
|
||||
|
||||
// filter logs of a single header in light client mode
|
||||
func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []*types.Log {
|
||||
if !bloomFilter(header.Bloom, addresses, topics) {
|
||||
return nil
|
||||
}
|
||||
// Get the logs of the block
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
cached, err := es.sys.cachedLogElem(ctx, header.Hash(), header.Number.Uint64())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
unfiltered := append([]*types.Log{}, cached.logs...)
|
||||
for i, log := range unfiltered {
|
||||
// Don't modify in-cache elements
|
||||
logcopy := *log
|
||||
logcopy.Removed = remove
|
||||
// Swap copy in-place
|
||||
unfiltered[i] = &logcopy
|
||||
}
|
||||
logs := filterLogs(unfiltered, nil, nil, addresses, topics)
|
||||
// Txhash is already resolved
|
||||
if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) {
|
||||
return logs
|
||||
}
|
||||
// Resolve txhash
|
||||
body, err := es.sys.cachedGetBody(ctx, cached, header.Hash(), header.Number.Uint64())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, log := range logs {
|
||||
// logs are already copied, safe to modify
|
||||
log.TxHash = body.Transactions[log.TxIndex].Hash()
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
// eventLoop (un)installs filters and processes mux events.
|
||||
func (es *EventSystem) eventLoop() {
|
||||
// Ensure all subscriptions get cleaned up
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"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/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
@@ -202,7 +201,7 @@ func TestBlockSubscription(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
genesis = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
@@ -257,7 +256,7 @@ func TestPendingTxFilter(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
|
||||
transactions = []*types.Transaction{
|
||||
types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
||||
@@ -313,7 +312,7 @@ func TestPendingTxFilterFullTx(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
|
||||
transactions = []*types.Transaction{
|
||||
types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
||||
@@ -369,7 +368,7 @@ func TestLogFilterCreation(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
_, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
|
||||
testCases = []struct {
|
||||
crit FilterCriteria
|
||||
@@ -416,7 +415,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
_, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
)
|
||||
|
||||
// different situations where log filter creation should fail.
|
||||
@@ -438,7 +437,7 @@ func TestInvalidGetLogsRequest(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
_, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
|
||||
)
|
||||
|
||||
@@ -463,7 +462,7 @@ func TestLogFilter(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
|
||||
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
@@ -577,7 +576,7 @@ func TestPendingLogsSubscription(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
|
||||
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
@@ -751,143 +750,6 @@ func TestPendingLogsSubscription(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLightFilterLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
||||
api = NewFilterAPI(sys, true, false)
|
||||
signer = types.HomesteadSigner{}
|
||||
|
||||
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333")
|
||||
notUsedAddress = common.HexToAddress("0x9999999999999999999999999999999999999999")
|
||||
firstTopic = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
|
||||
secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222")
|
||||
|
||||
// posted twice, once as regular logs and once as pending logs.
|
||||
allLogs = []*types.Log{
|
||||
// Block 1
|
||||
{Address: firstAddr, Topics: []common.Hash{}, Data: []byte{}, BlockNumber: 2, Index: 0},
|
||||
// Block 2
|
||||
{Address: firstAddr, Topics: []common.Hash{firstTopic}, Data: []byte{}, BlockNumber: 3, Index: 0},
|
||||
{Address: secondAddr, Topics: []common.Hash{firstTopic}, Data: []byte{}, BlockNumber: 3, Index: 1},
|
||||
{Address: thirdAddress, Topics: []common.Hash{secondTopic}, Data: []byte{}, BlockNumber: 3, Index: 2},
|
||||
// Block 3
|
||||
{Address: thirdAddress, Topics: []common.Hash{secondTopic}, Data: []byte{}, BlockNumber: 4, Index: 0},
|
||||
}
|
||||
|
||||
testCases = []struct {
|
||||
crit FilterCriteria
|
||||
expected []*types.Log
|
||||
id rpc.ID
|
||||
}{
|
||||
// match all
|
||||
0: {FilterCriteria{}, allLogs, ""},
|
||||
// match none due to no matching addresses
|
||||
1: {FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, ""},
|
||||
// match logs based on addresses, ignore topics
|
||||
2: {FilterCriteria{Addresses: []common.Address{firstAddr}}, allLogs[:2], ""},
|
||||
// match logs based on addresses and topics
|
||||
3: {FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, allLogs[3:5], ""},
|
||||
// all logs with block num >= 3
|
||||
4: {FilterCriteria{FromBlock: big.NewInt(3), ToBlock: big.NewInt(5)}, allLogs[1:], ""},
|
||||
// all logs
|
||||
5: {FilterCriteria{FromBlock: big.NewInt(0), ToBlock: big.NewInt(5)}, allLogs, ""},
|
||||
// all logs with 1>= block num <=2 and topic secondTopic
|
||||
6: {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(3), Topics: [][]common.Hash{{secondTopic}}}, allLogs[3:4], ""},
|
||||
}
|
||||
|
||||
key, _ = crypto.GenerateKey()
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
genesis = &core.Genesis{Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{
|
||||
addr: {Balance: big.NewInt(params.Ether)},
|
||||
},
|
||||
}
|
||||
receipts = []*types.Receipt{{
|
||||
Logs: []*types.Log{allLogs[0]},
|
||||
}, {
|
||||
Logs: []*types.Log{allLogs[1], allLogs[2], allLogs[3]},
|
||||
}, {
|
||||
Logs: []*types.Log{allLogs[4]},
|
||||
}}
|
||||
)
|
||||
|
||||
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 4, func(i int, b *core.BlockGen) {
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
receipts[i-1].Bloom = types.CreateBloom(types.Receipts{receipts[i-1]})
|
||||
b.AddUncheckedReceipt(receipts[i-1])
|
||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i - 1), To: &common.Address{}, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, key)
|
||||
b.AddTx(tx)
|
||||
})
|
||||
for i, block := range blocks {
|
||||
rawdb.WriteBlock(db, block)
|
||||
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
|
||||
rawdb.WriteHeadBlockHash(db, block.Hash())
|
||||
if i > 0 {
|
||||
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), []*types.Receipt{receipts[i-1]})
|
||||
}
|
||||
}
|
||||
// create all filters
|
||||
for i := range testCases {
|
||||
id, err := api.NewFilter(testCases[i].crit)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testCases[i].id = id
|
||||
}
|
||||
|
||||
// raise events
|
||||
time.Sleep(1 * time.Second)
|
||||
for _, block := range blocks {
|
||||
backend.chainFeed.Send(core.ChainEvent{Block: block, Hash: common.Hash{}, Logs: allLogs})
|
||||
}
|
||||
|
||||
for i, tt := range testCases {
|
||||
var fetched []*types.Log
|
||||
timeout := time.Now().Add(1 * time.Second)
|
||||
for { // fetch all expected logs
|
||||
results, err := api.GetFilterChanges(tt.id)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to fetch logs: %v", err)
|
||||
}
|
||||
fetched = append(fetched, results.([]*types.Log)...)
|
||||
if len(fetched) >= len(tt.expected) {
|
||||
break
|
||||
}
|
||||
// check timeout
|
||||
if time.Now().After(timeout) {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
if len(fetched) != len(tt.expected) {
|
||||
t.Errorf("invalid number of logs for case %d, want %d log(s), got %d", i, len(tt.expected), len(fetched))
|
||||
return
|
||||
}
|
||||
|
||||
for l := range fetched {
|
||||
if fetched[l].Removed {
|
||||
t.Errorf("expected log not to be removed for log %d in case %d", l, i)
|
||||
}
|
||||
expected := *tt.expected[l]
|
||||
blockNum := expected.BlockNumber - 1
|
||||
expected.BlockHash = blocks[blockNum].Hash()
|
||||
expected.TxHash = blocks[blockNum].Transactions()[0].Hash()
|
||||
if !reflect.DeepEqual(fetched[l], &expected) {
|
||||
t.Errorf("invalid log on index %d for case %d", l, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPendingTxFilterDeadlock tests if the event loop hangs when pending
|
||||
// txes arrive at the same time that one of multiple filters is timing out.
|
||||
// Please refer to #22131 for more details.
|
||||
@@ -898,7 +760,7 @@ func TestPendingTxFilterDeadlock(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{Timeout: timeout})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
done = make(chan struct{})
|
||||
)
|
||||
|
||||
@@ -969,7 +831,7 @@ func TestVoteSubscription(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
backend, sys = newTestFilterSystem(t, db, Config{Timeout: 5 * time.Minute})
|
||||
api = NewFilterAPI(sys, false, false)
|
||||
api = NewFilterAPI(sys, false)
|
||||
votes = []*types.VoteEnvelope{
|
||||
&types.VoteEnvelope{
|
||||
VoteAddress: types.BLSPublicKey{},
|
||||
|
||||
@@ -24,13 +24,13 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"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/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -321,7 +321,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac
|
||||
it.Release()
|
||||
|
||||
// Generate the Merkle proofs for the first and last account
|
||||
proof := light.NewNodeSet()
|
||||
proof := trienode.NewProofSet()
|
||||
if err := tr.Prove(req.Origin[:], proof); err != nil {
|
||||
log.Warn("Failed to prove account range", "origin", req.Origin, "err", err)
|
||||
return nil, nil
|
||||
@@ -333,7 +333,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac
|
||||
}
|
||||
}
|
||||
var proofs [][]byte
|
||||
for _, blob := range proof.NodeList() {
|
||||
for _, blob := range proof.List() {
|
||||
proofs = append(proofs, blob)
|
||||
}
|
||||
return accounts, proofs
|
||||
@@ -427,7 +427,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
proof := light.NewNodeSet()
|
||||
proof := trienode.NewProofSet()
|
||||
if err := stTrie.Prove(origin[:], proof); err != nil {
|
||||
log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err)
|
||||
return nil, nil
|
||||
@@ -438,7 +438,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
for _, blob := range proof.NodeList() {
|
||||
for _, blob := range proof.List() {
|
||||
proofs = append(proofs, blob)
|
||||
}
|
||||
// Proof terminates the reply as proofs are only added if a node
|
||||
|
||||
@@ -38,11 +38,11 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/msgrate"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -2499,11 +2499,11 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco
|
||||
for i, key := range hashes {
|
||||
keys[i] = common.CopyBytes(key[:])
|
||||
}
|
||||
nodes := make(light.NodeList, len(proof))
|
||||
nodes := make(trienode.ProofList, len(proof))
|
||||
for i, node := range proof {
|
||||
nodes[i] = node
|
||||
}
|
||||
proofdb := nodes.NodeSet()
|
||||
proofdb := nodes.Set()
|
||||
|
||||
var end []byte
|
||||
if len(keys) > 0 {
|
||||
@@ -2751,7 +2751,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
|
||||
for j, key := range hashes[i] {
|
||||
keys[j] = common.CopyBytes(key[:])
|
||||
}
|
||||
nodes := make(light.NodeList, 0, len(proof))
|
||||
nodes := make(trienode.ProofList, 0, len(proof))
|
||||
if i == len(hashes)-1 {
|
||||
for _, node := range proof {
|
||||
nodes = append(nodes, node)
|
||||
@@ -2770,7 +2770,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
|
||||
} else {
|
||||
// A proof was attached, the response is only partial, check that the
|
||||
// returned data is indeed part of the storage trie
|
||||
proofdb := nodes.NodeSet()
|
||||
proofdb := nodes.Set()
|
||||
|
||||
var end []byte
|
||||
if len(keys) > 0 {
|
||||
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
"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"
|
||||
@@ -275,7 +274,7 @@ func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.H
|
||||
// Unless we send the entire trie, we need to supply proofs
|
||||
// Actually, we need to supply proofs either way! This seems to be an implementation
|
||||
// quirk in go-ethereum
|
||||
proof := light.NewNodeSet()
|
||||
proof := trienode.NewProofSet()
|
||||
if err := t.accountTrie.Prove(origin[:], proof); err != nil {
|
||||
t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err)
|
||||
}
|
||||
@@ -285,7 +284,7 @@ func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.H
|
||||
t.logger.Error("Could not prove last item", "error", err)
|
||||
}
|
||||
}
|
||||
for _, blob := range proof.NodeList() {
|
||||
for _, blob := range proof.List() {
|
||||
proofs = append(proofs, blob)
|
||||
}
|
||||
return keys, vals, proofs
|
||||
@@ -355,7 +354,7 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm
|
||||
if originHash != (common.Hash{}) || (abort && len(keys) > 0) {
|
||||
// If we're aborting, we need to prove the first and last item
|
||||
// This terminates the response (and thus the loop)
|
||||
proof := light.NewNodeSet()
|
||||
proof := trienode.NewProofSet()
|
||||
stTrie := t.storageTries[account]
|
||||
|
||||
// Here's a potential gotcha: when constructing the proof, we cannot
|
||||
@@ -370,7 +369,7 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm
|
||||
t.logger.Error("Could not prove last item", "error", err)
|
||||
}
|
||||
}
|
||||
for _, blob := range proof.NodeList() {
|
||||
for _, blob := range proof.List() {
|
||||
proofs = append(proofs, blob)
|
||||
}
|
||||
break
|
||||
@@ -413,7 +412,7 @@ func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, acco
|
||||
if exit {
|
||||
// If we're aborting, we need to prove the first and last item
|
||||
// This terminates the response (and thus the loop)
|
||||
proof := light.NewNodeSet()
|
||||
proof := trienode.NewProofSet()
|
||||
stTrie := t.storageTries[account]
|
||||
|
||||
// Here's a potential gotcha: when constructing the proof, we cannot
|
||||
@@ -429,7 +428,7 @@ func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, acco
|
||||
t.logger.Error("Could not prove last item", "error", err)
|
||||
}
|
||||
}
|
||||
for _, blob := range proof.NodeList() {
|
||||
for _, blob := range proof.List() {
|
||||
proofs = append(proofs, blob)
|
||||
}
|
||||
break
|
||||
@@ -601,9 +600,10 @@ func testSyncBloatedProof(t *testing.T, scheme string) {
|
||||
vals = append(vals, entry.v)
|
||||
}
|
||||
// The proofs
|
||||
proof := light.NewNodeSet()
|
||||
proof := trienode.NewProofSet()
|
||||
if err := t.accountTrie.Prove(origin[:], proof); err != nil {
|
||||
t.logger.Error("Could not prove origin", "origin", origin, "error", err)
|
||||
t.logger.Error("Could not prove origin", "origin", origin, "error", err)
|
||||
}
|
||||
// The bloat: add proof of every single element
|
||||
for _, entry := range t.accountValues {
|
||||
@@ -616,7 +616,7 @@ func testSyncBloatedProof(t *testing.T, scheme string) {
|
||||
keys = append(keys[:1], keys[2:]...)
|
||||
vals = append(vals[:1], vals[2:]...)
|
||||
}
|
||||
for _, blob := range proof.NodeList() {
|
||||
for _, blob := range proof.List() {
|
||||
proofs = append(proofs, blob)
|
||||
}
|
||||
if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil {
|
||||
|
||||
@@ -63,7 +63,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
||||
filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{})
|
||||
n.RegisterAPIs([]rpc.API{{
|
||||
Namespace: "eth",
|
||||
Service: filters.NewFilterAPI(filterSystem, false, false),
|
||||
Service: filters.NewFilterAPI(filterSystem, false),
|
||||
}})
|
||||
|
||||
// Import the test chain.
|
||||
|
||||
@@ -156,8 +156,15 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
|
||||
// including a frozen memory table and another live one.
|
||||
memTableLimit := 2
|
||||
memTableSize := cache * 1024 * 1024 / 2 / memTableLimit
|
||||
if memTableSize > maxMemTableSize {
|
||||
memTableSize = maxMemTableSize
|
||||
|
||||
// The memory table size is currently capped at maxMemTableSize-1 due to a
|
||||
// known bug in the pebble where maxMemTableSize is not recognized as a
|
||||
// valid size.
|
||||
//
|
||||
// TODO use the maxMemTableSize as the maximum table size once the issue
|
||||
// in pebble is fixed.
|
||||
if memTableSize >= maxMemTableSize {
|
||||
memTableSize = maxMemTableSize - 1
|
||||
}
|
||||
|
||||
logger.Info("Allocated cache and file handles", "cache", common.StorageSize(cache*1024*1024),
|
||||
|
||||
@@ -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"
|
||||
@@ -480,7 +479,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,
|
||||
|
||||
12
go.mod
12
go.mod
@@ -20,7 +20,7 @@ require (
|
||||
github.com/crate-crypto/go-kzg-4844 v0.3.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/deckarep/golang-set/v2 v2.1.0
|
||||
github.com/docker/docker v20.10.24+incompatible
|
||||
github.com/docker/docker v24.0.7+incompatible
|
||||
github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127
|
||||
github.com/ethereum/c-kzg-4844 v0.3.1
|
||||
github.com/fatih/color v1.13.0
|
||||
@@ -79,11 +79,11 @@ require (
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3
|
||||
github.com/willf/bitset v1.1.3
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.11.0
|
||||
golang.org/x/text v0.12.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.9.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
@@ -270,7 +270,7 @@ require (
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/oauth2 v0.5.0 // indirect
|
||||
golang.org/x/term v0.11.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
google.golang.org/api v0.44.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
@@ -295,5 +295,5 @@ replace (
|
||||
github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-tendermint v0.0.0-20230417032003-4cda1f296fb2
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/prysmaticlabs/grpc-gateway/v2 v2.3.1-0.20210702154020-550e1cd83ec1
|
||||
github.com/syndtr/goleveldb v1.0.1 => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
github.com/tendermint/tendermint => github.com/bnb-chain/tendermint v0.31.15
|
||||
github.com/tendermint/tendermint => github.com/bnb-chain/tendermint v0.31.16
|
||||
)
|
||||
|
||||
23
go.sum
23
go.sum
@@ -188,8 +188,8 @@ github.com/bnb-chain/greenfield-tendermint v0.0.0-20230417032003-4cda1f296fb2 h1
|
||||
github.com/bnb-chain/greenfield-tendermint v0.0.0-20230417032003-4cda1f296fb2/go.mod h1:9q11eHNRY9FDwFH+4pompzPNGv//Z3VcfvkELaHJPMs=
|
||||
github.com/bnb-chain/ics23 v0.1.0 h1:DvjGOts2FBfbxB48384CYD1LbcrfjThFz8kowY/7KxU=
|
||||
github.com/bnb-chain/ics23 v0.1.0/go.mod h1:cU6lTGolbbLFsGCgceNB2AzplH1xecLp6+KXvxM32nI=
|
||||
github.com/bnb-chain/tendermint v0.31.15 h1:Xyn/Hifb/7X4E1zSuMdnZdMSoM2Fx6cZuKCNnqIxbNU=
|
||||
github.com/bnb-chain/tendermint v0.31.15/go.mod h1:cmt8HHmQUSVaWQ/hoTefRxsh5X3ERaM1zCUIR0DPbFU=
|
||||
github.com/bnb-chain/tendermint v0.31.16 h1:rOO6WG61JDAuRCCL8TKnGhorJftQDVygq0mqR7A0ck4=
|
||||
github.com/bnb-chain/tendermint v0.31.16/go.mod h1:cmt8HHmQUSVaWQ/hoTefRxsh5X3ERaM1zCUIR0DPbFU=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
@@ -344,8 +344,8 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE=
|
||||
github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
@@ -1757,8 +1757,8 @@ golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5
|
||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -2028,13 +2028,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -2046,8 +2047,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -87,8 +87,9 @@ var (
|
||||
Category: flags.LoggingCategory,
|
||||
}
|
||||
logRotateFlag = &cli.BoolFlag{
|
||||
Name: "log.rotate",
|
||||
Usage: "Enables log file rotation",
|
||||
Name: "log.rotate",
|
||||
Usage: "Enables log file rotation",
|
||||
Category: flags.LoggingCategory,
|
||||
}
|
||||
logMaxSizeMBsFlag = &cli.IntFlag{
|
||||
Name: "log.maxsize",
|
||||
|
||||
@@ -39,7 +39,7 @@ const (
|
||||
DeprecatedCategory = "ALIASED (deprecated)"
|
||||
FastNodeCategory = "FAST NODE"
|
||||
FastFinalityCategory = "FAST FINALITY"
|
||||
HistoryCategory = "HISTORY"
|
||||
BlockHistoryCategory = "BLOCK HISTORY MANAGEMENT"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -28,8 +28,6 @@ var Modules = map[string]string{
|
||||
"personal": PersonalJs,
|
||||
"rpc": RpcJs,
|
||||
"txpool": TxpoolJs,
|
||||
"les": LESJs,
|
||||
"vflux": VfluxJs,
|
||||
"dev": DevJs,
|
||||
}
|
||||
|
||||
@@ -802,91 +800,6 @@ web3._extend({
|
||||
});
|
||||
`
|
||||
|
||||
const LESJs = `
|
||||
web3._extend({
|
||||
property: 'les',
|
||||
methods:
|
||||
[
|
||||
new web3._extend.Method({
|
||||
name: 'getCheckpoint',
|
||||
call: 'les_getCheckpoint',
|
||||
params: 1
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'clientInfo',
|
||||
call: 'les_clientInfo',
|
||||
params: 1
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'priorityClientInfo',
|
||||
call: 'les_priorityClientInfo',
|
||||
params: 3
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'setClientParams',
|
||||
call: 'les_setClientParams',
|
||||
params: 2
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'setDefaultParams',
|
||||
call: 'les_setDefaultParams',
|
||||
params: 1
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'addBalance',
|
||||
call: 'les_addBalance',
|
||||
params: 2
|
||||
}),
|
||||
],
|
||||
properties:
|
||||
[
|
||||
new web3._extend.Property({
|
||||
name: 'latestCheckpoint',
|
||||
getter: 'les_latestCheckpoint'
|
||||
}),
|
||||
new web3._extend.Property({
|
||||
name: 'checkpointContractAddress',
|
||||
getter: 'les_getCheckpointContractAddress'
|
||||
}),
|
||||
new web3._extend.Property({
|
||||
name: 'serverInfo',
|
||||
getter: 'les_serverInfo'
|
||||
}),
|
||||
]
|
||||
});
|
||||
`
|
||||
|
||||
const VfluxJs = `
|
||||
web3._extend({
|
||||
property: 'vflux',
|
||||
methods:
|
||||
[
|
||||
new web3._extend.Method({
|
||||
name: 'distribution',
|
||||
call: 'vflux_distribution',
|
||||
params: 2
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'timeout',
|
||||
call: 'vflux_timeout',
|
||||
params: 2
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'value',
|
||||
call: 'vflux_value',
|
||||
params: 2
|
||||
}),
|
||||
],
|
||||
properties:
|
||||
[
|
||||
new web3._extend.Property({
|
||||
name: 'requestStats',
|
||||
getter: 'vflux_requestStats'
|
||||
}),
|
||||
]
|
||||
});
|
||||
`
|
||||
|
||||
const DevJs = `
|
||||
web3._extend({
|
||||
property: 'dev',
|
||||
|
||||
349
les/api.go
349
les/api.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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[:])
|
||||
}
|
||||
}
|
||||
@@ -1,351 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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/log"
|
||||
"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) SubscribeNewVoteEvent(ch chan<- core.NewVoteEvent) event.Subscription {
|
||||
log.Error("light ethereum does not support SubscribeNewVoteEvent")
|
||||
return nil
|
||||
}
|
||||
|
||||
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) SubscribeFinalizedHeaderEvent(ch chan<- core.FinalizedHeaderEvent) event.Subscription {
|
||||
return b.eth.blockchain.SubscribeFinalizedHeaderEvent(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) Chain() *core.BlockChain {
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
513
les/api_test.go
513
les/api_test.go
@@ -1,513 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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([]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] = 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 {
|
||||
count := atomic.AddUint64(&reqCount[i], 1)
|
||||
if count%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] = atomic.LoadUint64(&reqCount[i])
|
||||
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, count := range reqCount {
|
||||
t.Log("client", i, "processed", count)
|
||||
}
|
||||
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
|
||||
}
|
||||
351
les/benchmark.go
351
les/benchmark.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
384
les/client.go
384
les/client.go
@@ -1,384 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package les implements the Light Ethereum Subprotocol.
|
||||
package les
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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/filters"
|
||||
"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, nil, genesisHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Info("Initialised chain configuration for les.client", "config", chainConfig)
|
||||
// Description of chainConfig is empty now
|
||||
/*
|
||||
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: "eth",
|
||||
Service: filters.NewFilterAPI(filters.NewFilterSystem(s.ApiBackend, filters.Config{}), true, s.config.RangeLimit),
|
||||
}, {
|
||||
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
|
||||
}
|
||||
@@ -1,308 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// 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.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 light.NodeList
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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][]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][]uint64)
|
||||
for code := range reqAvgTimeCost {
|
||||
ct.stats[code] = make([]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
|
||||
}
|
||||
atomic.AddUint64(&ct.stats[code][l], 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], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9])
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,753 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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: 128 + 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 := light.NewNodeSet()
|
||||
|
||||
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.NodeList()); 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: 128 + 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 := light.NewNodeSet()
|
||||
t, _ := trie.New(trie.StateTrieID(header.Root), server.backend.Blockchain().TrieDB())
|
||||
t.Prove(account, proofsV2)
|
||||
expected = proofsV2.NodeList()
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
151
les/metrics.go
151
les/metrics.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
237
les/odr.go
237
les/odr.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,536 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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.(light.NodeList)
|
||||
// Verify the proof and store if checks out
|
||||
nodeSet := proofs.NodeSet()
|
||||
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 light.NodeList
|
||||
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.NodeSet()
|
||||
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.NodeSet()
|
||||
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
|
||||
}
|
||||
458
les/odr_test.go
458
les/odr_test.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
*/
|
||||
1360
les/peer.go
1360
les/peer.go
File diff suppressed because it is too large
Load Diff
166
les/peer_test.go
166
les/peer_test.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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().Hash(), chain1.CurrentHeader().Number.Uint64(), chain1.CurrentHeader().Time)
|
||||
forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
327
les/protocol.go
327
les/protocol.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
*/
|
||||
421
les/retrieve.go
421
les/retrieve.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
281
les/server.go
281
les/server.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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().Hash(), 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,565 +0,0 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package 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"
|
||||
)
|
||||
|
||||
// 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()+bc.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 := light.NewNodeSet()
|
||||
|
||||
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()+bc.TriesInMemory() <= local {
|
||||
p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local)
|
||||
p.bumpInvalid()
|
||||
continue
|
||||
}
|
||||
root = header.Root
|
||||
}
|
||||
// If a header lookup failed (non existent), ignore subsequent requests for the same header
|
||||
if root == (common.Hash{}) {
|
||||
p.bumpInvalid()
|
||||
continue
|
||||
}
|
||||
// Open the account or storage trie for the request
|
||||
statedb := bc.StateCache()
|
||||
|
||||
var trie state.Trie
|
||||
switch len(request.AccountAddress) {
|
||||
case 0:
|
||||
// No account key specified, open an account trie
|
||||
trie, err = statedb.OpenTrie(root)
|
||||
if trie == nil || err != nil {
|
||||
p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err)
|
||||
continue
|
||||
}
|
||||
default:
|
||||
// Account key specified, open a storage trie
|
||||
address := common.BytesToAddress(request.AccountAddress)
|
||||
account, err := getAccount(bc.TrieDB(), root, address)
|
||||
if err != nil {
|
||||
p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", address, "err", err)
|
||||
p.bumpInvalid()
|
||||
continue
|
||||
}
|
||||
trie, err = statedb.OpenStorageTrie(root, address, account.Root)
|
||||
if trie == nil || err != nil {
|
||||
p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", address, "root", account.Root, "err", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Prove the user's request from the account or storage trie
|
||||
if err := trie.Prove(request.Key, nodes); err != nil {
|
||||
p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err)
|
||||
continue
|
||||
}
|
||||
if nodes.DataSize() >= softResponseLimit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return p.replyProofsV2(r.ReqID, nodes.NodeList())
|
||||
}, 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 := light.NewNodeSet()
|
||||
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.NodeList(), 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([]*txpool.Transaction{{Tx: 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
|
||||
}
|
||||
@@ -1,364 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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, servingTimeDiff 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
|
||||
atomic.AddUint64(&t.sq.servingTimeDiff, 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 := atomic.SwapUint64(&sq.servingTimeDiff, 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()
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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())
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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(128-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.Hash(), 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() }
|
||||
}
|
||||
179
les/txrelay.go
179
les/txrelay.go
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user