Compare commits
71 Commits
v1.3.5
...
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 | ||
|
|
01a4b00a2b | ||
|
|
474860ef77 | ||
|
|
4d2bd1253d | ||
|
|
ca058b7a69 | ||
|
|
f4b7cdfe38 | ||
|
|
f6fb2e9116 | ||
|
|
6685f68995 | ||
|
|
e44de3ab27 | ||
|
|
07c46abcbe | ||
|
|
8a440e753f | ||
|
|
8205fdc525 | ||
|
|
0b632d97f3 | ||
|
|
84b268f6fb | ||
|
|
f628e32ba5 | ||
|
|
e4910b9540 | ||
|
|
2e69dcb342 | ||
|
|
64be7df9e2 | ||
|
|
1ebf2a4376 | ||
|
|
8ce9cdaae3 | ||
|
|
b3df096358 | ||
|
|
d4f7313760 | ||
|
|
4b107c5303 | ||
|
|
94b68156c8 | ||
|
|
ab8793ae9f | ||
|
|
6744d7c15f | ||
|
|
3414e5672a | ||
|
|
8f3c525adc | ||
|
|
3e9e6423c0 | ||
|
|
3a5ec36c54 | ||
|
|
7891b210e0 | ||
|
|
3076ad2ab9 | ||
|
|
d9873bbf38 | ||
|
|
3e6c16afd3 | ||
|
|
1ddd337d1c | ||
|
|
a182557ac3 | ||
|
|
4b32d400ee | ||
|
|
5743b067ba | ||
|
|
d3f882d799 | ||
|
|
030e41607e |
4
.github/workflows/build-test.yml
vendored
4
.github/workflows/build-test.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- develop
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
@@ -47,5 +47,3 @@ jobs:
|
||||
run: |
|
||||
go mod download
|
||||
make geth
|
||||
|
||||
|
||||
|
||||
2
.github/workflows/commit-lint.yml
vendored
2
.github/workflows/commit-lint.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- develop
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
|
||||
2
.github/workflows/integration-test.yml
vendored
2
.github/workflows/integration-test.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- develop
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- develop
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- run: |
|
||||
go mod download
|
||||
go mod tidy
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
|
||||
3
.github/workflows/unit-test.yml
vendored
3
.github/workflows/unit-test.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- develop
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
@@ -52,4 +52,3 @@ jobs:
|
||||
git submodule update --init --depth 1 --recursive
|
||||
go mod download
|
||||
make test
|
||||
|
||||
|
||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,4 +1,32 @@
|
||||
# Changelog
|
||||
## v1.3.7
|
||||
FEATURE
|
||||
* [\#2067](https://github.com/bnb-chain/bsc/pull/2067) cmd/geth: add check func to validate state scheme
|
||||
* [\#2068](https://github.com/bnb-chain/bsc/pull/2068) internal/ethapi: implement eth_getBlockReceipts
|
||||
|
||||
BUGFIX
|
||||
* [\#2035](https://github.com/bnb-chain/bsc/pull/2035) all: pull snap sync PRs from upstream v1.13.5
|
||||
* [\#2072](https://github.com/bnb-chain/bsc/pull/2072) fix: fix the pebble config of level option
|
||||
* [\#2078](https://github.com/bnb-chain/bsc/pull/2078) core: LoadChainConfig return the predefined config for built-in networks firstly
|
||||
|
||||
## v1.3.6
|
||||
FEATURE
|
||||
* [\#2012](https://github.com/bnb-chain/bsc/pull/2012) cmd, core, ethdb: enable Pebble on 32 bits and OpenBSD
|
||||
* [\#2063](https://github.com/bnb-chain/bsc/pull/2063) log: support to disable log rotate by hours
|
||||
* [\#2064](https://github.com/bnb-chain/bsc/pull/2064) log: limit rotateHours in range [0,23]
|
||||
|
||||
BUGFIX
|
||||
* [\#2058](https://github.com/bnb-chain/bsc/pull/2058) params: set default hardfork times
|
||||
|
||||
IMPROVEMENT
|
||||
* [\#2015](https://github.com/bnb-chain/bsc/pull/2015) cmd, core, eth: change default network from ETH to BSC
|
||||
* [\#2036](https://github.com/bnb-chain/bsc/pull/2036) cmd/jsutils: add 2 tools get validator version and block txs number
|
||||
* [\#2037](https://github.com/bnb-chain/bsc/pull/2037) core/txpool/legacypool: respect nolocals-setting
|
||||
* [\#2042](https://github.com/bnb-chain/bsc/pull/2042) core/systemcontracts: update CommitUrl for keplerUpgrade
|
||||
* [\#2043](https://github.com/bnb-chain/bsc/pull/2043) tests/truffle: adapt changes in bsc-genesis-contracts
|
||||
* [\#2051](https://github.com/bnb-chain/bsc/pull/2051) core/vote: wait some blocks before voting since mining begin
|
||||
* [\#2060](https://github.com/bnb-chain/bsc/pull/2060) cmd/utils: allow HTTPHost and WSHost flags precede
|
||||
|
||||
## v1.3.5
|
||||
FEATURE
|
||||
* [\#1970](https://github.com/bnb-chain/bsc/pull/1970) core: enable Shanghai EIPs
|
||||
|
||||
15
README.md
15
README.md
@@ -110,15 +110,15 @@ on how you can run your own `geth` instance.
|
||||
|
||||
The hardware must meet certain requirements to run a full node on mainnet:
|
||||
- VPS running recent versions of Mac OS X, Linux, or Windows.
|
||||
- IMPORTANT 2.5 TB(May 2023) of free disk space, solid-state drive(SSD), gp3, 8k IOPS, 250 MB/S throughput, read latency <1ms. (if node is started with snap sync, it will need NVMe SSD)
|
||||
- IMPORTANT 3 TB(Dec 2023) of free disk space, solid-state drive(SSD), gp3, 8k IOPS, 500 MB/S throughput, read latency <1ms. (if node is started with snap sync, it will need NVMe SSD)
|
||||
- 16 cores of CPU and 64 GB of memory (RAM)
|
||||
- Suggest m5zn.3xlarge instance type on AWS, c2-standard-16 on Google cloud.
|
||||
- Suggest m5zn.6xlarge or r7iz.4xlarge instance type on AWS, c2-standard-16 on Google cloud.
|
||||
- A broadband Internet connection with upload/download speeds of 5 MB/S
|
||||
|
||||
The requirement for testnet:
|
||||
- VPS running recent versions of Mac OS X, Linux, or Windows.
|
||||
- 500G of storage for testnet.
|
||||
- 4 cores of CPU and 8 gigabytes of memory (RAM).
|
||||
- 4 cores of CPU and 16 gigabytes of memory (RAM).
|
||||
|
||||
### Steps to Run a Fullnode
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1206,7 +1206,7 @@ func GenDoc(ctx *cli.Context) error {
|
||||
URL: accounts.URL{Path: ".. ignored .."},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
|
||||
Address: common.MaxAddress,
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ set to standard output. The following filters are supported:
|
||||
- `-limit <N>` limits the output set to N entries, taking the top N nodes by score
|
||||
- `-ip <CIDR>` filters nodes by IP subnet
|
||||
- `-min-age <duration>` filters nodes by 'first seen' time
|
||||
- `-eth-network <mainnet/goerli/sepolia>` filters nodes by "eth" ENR entry
|
||||
- `-eth-network <mainnet/goerli/sepolia/holesky>` filters nodes by "eth" ENR entry
|
||||
- `-les-server` filters nodes by LES server support
|
||||
- `-snap` filters nodes by snap protocol support
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ type accRangeTest struct {
|
||||
func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
||||
var (
|
||||
root = s.chain.RootAt(999)
|
||||
ffHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
ffHash = common.MaxHash
|
||||
zero = common.Hash{}
|
||||
firstKeyMinus1 = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf29")
|
||||
firstKey = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a")
|
||||
@@ -125,7 +125,7 @@ type stRangesTest struct {
|
||||
// TestSnapGetStorageRanges various forms of GetStorageRanges requests.
|
||||
func (s *Suite) TestSnapGetStorageRanges(t *utesting.T) {
|
||||
var (
|
||||
ffHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
ffHash = common.MaxHash
|
||||
zero = common.Hash{}
|
||||
firstKey = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a")
|
||||
secondKey = common.HexToHash("0x09e47cd5056a689e708f22fe1f932709a320518e444f5f7d8d46a3da523d6606")
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -135,6 +138,16 @@ func loadBaseConfig(ctx *cli.Context) gethConfig {
|
||||
}
|
||||
}
|
||||
|
||||
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!")
|
||||
}
|
||||
|
||||
// Apply flags.
|
||||
utils.SetNodeConfig(ctx, &cfg.Node)
|
||||
return cfg
|
||||
@@ -164,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
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestConsoleWelcome(t *testing.T) {
|
||||
geth.SetTemplateFunc("gover", runtime.Version)
|
||||
geth.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
||||
geth.SetTemplateFunc("niltime", func() string {
|
||||
return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
||||
return time.Unix(0x5e9da7ce, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
||||
})
|
||||
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||
|
||||
@@ -131,7 +131,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
|
||||
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
||||
attach.SetTemplateFunc("niltime", func() string {
|
||||
return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
||||
return time.Unix(0x5e9da7ce, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
|
||||
})
|
||||
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||
attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
|
||||
|
||||
@@ -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",
|
||||
@@ -119,7 +132,8 @@ a data corruption.`,
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.MainnetFlag,
|
||||
utils.BSCMainnetFlag,
|
||||
utils.ChapelFlag,
|
||||
utils.StateSchemeFlag,
|
||||
},
|
||||
Description: "This command looks up the specified trie node key from the database.",
|
||||
@@ -132,7 +146,8 @@ a data corruption.`,
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.MainnetFlag,
|
||||
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...)
|
||||
@@ -188,7 +188,7 @@ func TestCustomBackend(t *testing.T) {
|
||||
initExpect: `Fatal: Invalid choice for db.engine 'mssql', allowed 'leveldb' or 'pebble'`,
|
||||
// Since the init fails, this will return the (default) mainnet genesis
|
||||
// block nonce
|
||||
execExpect: `0x0000000000000042`,
|
||||
execExpect: `0x0000000000000000`,
|
||||
},
|
||||
} {
|
||||
if err := testfunc(t, tt); err != nil {
|
||||
|
||||
@@ -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:
|
||||
@@ -311,22 +318,18 @@ func prepare(ctx *cli.Context) {
|
||||
`)
|
||||
|
||||
case !ctx.IsSet(utils.NetworkIdFlag.Name):
|
||||
log.Info("Starting Geth on Ethereum mainnet...")
|
||||
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"
|
||||
@@ -51,7 +52,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"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/eth/filters"
|
||||
@@ -63,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"
|
||||
@@ -165,13 +164,18 @@ var (
|
||||
}
|
||||
NetworkIdFlag = &cli.Uint64Flag{
|
||||
Name: "networkid",
|
||||
Usage: "Explicitly set network id (integer)(For testnets: use --goerli, --sepolia instead)",
|
||||
Usage: "Explicitly set network id (integer)(For testnets: use --chapel instead)",
|
||||
Value: ethconfig.Defaults.NetworkId,
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
MainnetFlag = &cli.BoolFlag{
|
||||
BSCMainnetFlag = &cli.BoolFlag{
|
||||
Name: "mainnet",
|
||||
Usage: "Ethereum 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{
|
||||
@@ -286,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",
|
||||
@@ -298,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,
|
||||
}
|
||||
@@ -311,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{
|
||||
@@ -332,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",
|
||||
@@ -433,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{
|
||||
@@ -511,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",
|
||||
@@ -572,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
|
||||
@@ -1057,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{
|
||||
@@ -1069,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{
|
||||
@@ -1111,25 +1095,22 @@ 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{MainnetFlag}, TestnetFlags...)
|
||||
NetworkFlags = append([]cli.Flag{BSCMainnetFlag}, TestnetFlags...)
|
||||
|
||||
// DatabasePathFlags is the flag group of all database path flags.
|
||||
DatabasePathFlags = []cli.Flag{
|
||||
DataDirFlag,
|
||||
AncientFlag,
|
||||
RemoteDBFlag,
|
||||
DBEngineFlag,
|
||||
HttpHeaderFlag,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
if rawdb.PebbleEnabled {
|
||||
DatabasePathFlags = append(DatabasePathFlags, DBEngineFlag)
|
||||
}
|
||||
}
|
||||
|
||||
// MakeDataDir retrieves the currently requested data directory, terminating
|
||||
// if none (or the empty string) is specified. If the node is starting a testnet,
|
||||
// then a subdirectory of the specified datadir will be used.
|
||||
@@ -1259,8 +1240,10 @@ func SplitAndTrim(input string) (ret []string) {
|
||||
// setHTTP creates the HTTP RPC listener interface string from the set
|
||||
// command line flags, returning empty if the HTTP endpoint is disabled.
|
||||
func setHTTP(ctx *cli.Context, cfg *node.Config) {
|
||||
if ctx.Bool(HTTPEnabledFlag.Name) && cfg.HTTPHost == "" {
|
||||
cfg.HTTPHost = "127.0.0.1"
|
||||
if ctx.Bool(HTTPEnabledFlag.Name) {
|
||||
if cfg.HTTPHost == "" {
|
||||
cfg.HTTPHost = "127.0.0.1"
|
||||
}
|
||||
if ctx.IsSet(HTTPListenAddrFlag.Name) {
|
||||
cfg.HTTPHost = ctx.String(HTTPListenAddrFlag.Name)
|
||||
}
|
||||
@@ -1324,8 +1307,10 @@ func setGraphQL(ctx *cli.Context, cfg *node.Config) {
|
||||
// setWS creates the WebSocket RPC listener interface string from the set
|
||||
// command line flags, returning empty if the HTTP endpoint is disabled.
|
||||
func setWS(ctx *cli.Context, cfg *node.Config) {
|
||||
if ctx.Bool(WSEnabledFlag.Name) && cfg.WSHost == "" {
|
||||
cfg.WSHost = "127.0.0.1"
|
||||
if ctx.Bool(WSEnabledFlag.Name) {
|
||||
if cfg.WSHost == "" {
|
||||
cfg.WSHost = "127.0.0.1"
|
||||
}
|
||||
if ctx.IsSet(WSListenAddrFlag.Name) {
|
||||
cfg.WSHost = ctx.String(WSListenAddrFlag.Name)
|
||||
}
|
||||
@@ -1359,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1488,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 {
|
||||
@@ -1519,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 {
|
||||
@@ -1680,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)
|
||||
}
|
||||
@@ -1845,13 +1791,12 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) {
|
||||
// SetEthConfig applies eth-related command line flags to the config.
|
||||
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
// Avoid conflicting network flags
|
||||
CheckExclusive(ctx, MainnetFlag, DeveloperFlag)
|
||||
CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light")
|
||||
CheckExclusive(ctx, BSCMainnetFlag, DeveloperFlag)
|
||||
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)
|
||||
@@ -1965,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
|
||||
}
|
||||
@@ -2034,12 +1976,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
}
|
||||
// Override any default configs for hard coded networks.
|
||||
switch {
|
||||
case ctx.Bool(MainnetFlag.Name):
|
||||
case ctx.Bool(BSCMainnetFlag.Name):
|
||||
if !ctx.IsSet(NetworkIdFlag.Name) {
|
||||
cfg.NetworkId = 1
|
||||
cfg.NetworkId = 56
|
||||
}
|
||||
cfg.Genesis = core.DefaultGenesisBlock()
|
||||
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
|
||||
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
|
||||
@@ -2130,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
|
||||
@@ -2142,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
|
||||
}
|
||||
@@ -2211,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,8 +2328,10 @@ func DialRPCWithHeaders(endpoint string, headers []string) (*rpc.Client, error)
|
||||
func MakeGenesis(ctx *cli.Context) *core.Genesis {
|
||||
var genesis *core.Genesis
|
||||
switch {
|
||||
case ctx.Bool(MainnetFlag.Name):
|
||||
genesis = core.DefaultGenesisBlock()
|
||||
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")
|
||||
}
|
||||
@@ -2487,39 +2418,44 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
|
||||
return preloads
|
||||
}
|
||||
|
||||
// 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.
|
||||
// ParseStateScheme checks if the specified state scheme is compatible with
|
||||
// the stored state.
|
||||
//
|
||||
// - 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 none, use the scheme consistent with persistent
|
||||
// state, or fallback to hash-based scheme if state is empty.
|
||||
//
|
||||
// - 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 schema 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 stored == "" || scheme == stored {
|
||||
log.Info("State scheme set by user", "scheme", scheme)
|
||||
return scheme, nil
|
||||
// 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
|
||||
}
|
||||
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.
|
||||
@@ -2545,3 +2481,65 @@ func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, read
|
||||
}
|
||||
return trie.NewDatabase(disk, config)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
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,8 +18,13 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
)
|
||||
|
||||
func Test_SplitTagsFlag(t *testing.T) {
|
||||
@@ -62,3 +67,126 @@ func Test_SplitTagsFlag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func() string
|
||||
wantedResult string
|
||||
wantedIsErr bool
|
||||
wantedErrStr string
|
||||
}{
|
||||
{
|
||||
name: "path",
|
||||
fn: func() string {
|
||||
tomlString := `[Eth]NetworkId = 56StateScheme = "path"`
|
||||
return createTempTomlFile(t, tomlString)
|
||||
},
|
||||
wantedResult: rawdb.PathScheme,
|
||||
wantedIsErr: false,
|
||||
wantedErrStr: "",
|
||||
},
|
||||
{
|
||||
name: "hash",
|
||||
fn: func() string {
|
||||
tomlString := `[Eth]NetworkId = 56StateScheme = "hash"`
|
||||
return createTempTomlFile(t, tomlString)
|
||||
},
|
||||
wantedResult: rawdb.HashScheme,
|
||||
wantedIsErr: false,
|
||||
wantedErrStr: "",
|
||||
},
|
||||
{
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ const (
|
||||
var (
|
||||
hashT = reflect.TypeOf(Hash{})
|
||||
addressT = reflect.TypeOf(Address{})
|
||||
|
||||
// MaxAddress represents the maximum possible address value.
|
||||
MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")
|
||||
|
||||
// MaxHash represents the maximum possible hash value.
|
||||
MaxHash = HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
)
|
||||
|
||||
// Hash represents the 32 byte Keccak256 hash of arbitrary data.
|
||||
|
||||
@@ -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
|
||||
@@ -241,6 +245,7 @@ func New(
|
||||
) *Parlia {
|
||||
// get parlia config
|
||||
parliaConfig := chainConfig.Parlia
|
||||
log.Info("Parlia", "chainConfig", chainConfig)
|
||||
|
||||
// Set any missing consensus parameters to their defaults
|
||||
if parliaConfig != nil && parliaConfig.Epoch == 0 {
|
||||
@@ -268,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,
|
||||
@@ -279,6 +288,7 @@ func New(
|
||||
validatorSetABIBeforeLuban: vABIBeforeLuban,
|
||||
validatorSetABI: vABI,
|
||||
slashABI: sABI,
|
||||
stakeHubABI: stABI,
|
||||
signer: types.LatestSigner(chainConfig),
|
||||
}
|
||||
|
||||
@@ -446,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")
|
||||
}
|
||||
@@ -870,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")
|
||||
}
|
||||
@@ -903,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)})
|
||||
@@ -1153,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")
|
||||
}
|
||||
@@ -1206,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")
|
||||
@@ -1265,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")
|
||||
@@ -1417,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))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -1491,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
|
||||
}
|
||||
@@ -1549,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
|
||||
@@ -1575,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)
|
||||
@@ -1750,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
|
||||
@@ -1795,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)
|
||||
}
|
||||
|
||||
170
core/genesis.go
170
core/genesis.go
@@ -23,8 +23,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -32,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"
|
||||
@@ -65,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) {
|
||||
@@ -202,8 +199,8 @@ func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash comm
|
||||
// - private network, can't recover
|
||||
var genesis *Genesis
|
||||
switch blockhash {
|
||||
case params.MainnetGenesisHash:
|
||||
genesis = DefaultGenesisBlock()
|
||||
case params.BSCGenesisHash:
|
||||
genesis = DefaultBSCGenesisBlock()
|
||||
}
|
||||
if genesis != nil {
|
||||
alloc = genesis.Alloc
|
||||
@@ -276,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.
|
||||
@@ -305,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
|
||||
}
|
||||
@@ -315,11 +321,10 @@ 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 main-net genesis block")
|
||||
genesis = DefaultGenesisBlock()
|
||||
log.Info("Writing default BSC mainnet genesis block")
|
||||
genesis = DefaultBSCGenesisBlock()
|
||||
} else {
|
||||
log.Info("Writing custom genesis block")
|
||||
}
|
||||
@@ -328,6 +333,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
|
||||
return genesis.Config, common.Hash{}, err
|
||||
}
|
||||
applyOverrides(genesis.Config)
|
||||
log.Info("genesis block hash", "hash", block.Hash())
|
||||
return genesis.Config, block.Hash(), nil
|
||||
}
|
||||
// The genesis block is present(perhaps in ancient database) while the
|
||||
@@ -337,7 +343,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
|
||||
header := rawdb.ReadHeader(db, stored, 0)
|
||||
if header.Root != types.EmptyRootHash && !triedb.Initialized(header.Root) {
|
||||
if genesis == nil {
|
||||
genesis = DefaultGenesisBlock()
|
||||
genesis = DefaultBSCGenesisBlock()
|
||||
}
|
||||
// Ensure the stored genesis matches with the given one.
|
||||
hash := genesis.ToBlock().Hash()
|
||||
@@ -398,14 +404,19 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
|
||||
return newcfg, stored, nil
|
||||
}
|
||||
|
||||
// LoadChainConfig loads the stored chain config if it is already present in
|
||||
// database, otherwise, return the config in the provided genesis specification.
|
||||
// LoadChainConfig retrieves the predefined chain configuration for the built-in network.
|
||||
// For non-built-in networks, it first attempts to load the stored chain configuration from the database.
|
||||
// If the configuration is not present, it returns the configuration specified in the provided genesis specification.
|
||||
func LoadChainConfig(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
|
||||
// Load the stored chain config from the database. It can be nil
|
||||
// in case the database is empty. Notably, we only care about the
|
||||
// chain config corresponds to the canonical chain.
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
if stored != (common.Hash{}) {
|
||||
builtInConf := params.GetBuiltInChainConfig(stored)
|
||||
if builtInConf != nil {
|
||||
return builtInConf, stored, nil
|
||||
}
|
||||
storedcfg := rawdb.ReadChainConfig(db, stored)
|
||||
if storedcfg != nil {
|
||||
return storedcfg, stored, nil
|
||||
@@ -431,71 +442,15 @@ func LoadChainConfig(db ethdb.Database, genesis *Genesis) (*params.ChainConfig,
|
||||
return params.BSCChainConfig, params.BSCGenesisHash, nil
|
||||
}
|
||||
|
||||
// For any block in g.Config which is nil but the same block in defaultConfig is not
|
||||
// set the block in genesis config to the block in defaultConfig.
|
||||
// Reflection is used to avoid a long series of if statements with hardcoded block names.
|
||||
func (g *Genesis) setDefaultBlockValues(defaultConfig *params.ChainConfig) {
|
||||
// Regex to match block names
|
||||
blockRegex := regexp.MustCompile(`.*Block$`)
|
||||
|
||||
// Get reflect values
|
||||
gConfigElem := reflect.ValueOf(g.Config).Elem()
|
||||
defaultConfigElem := reflect.ValueOf(defaultConfig).Elem()
|
||||
|
||||
// Iterate over fields in config
|
||||
for i := 0; i < gConfigElem.NumField(); i++ {
|
||||
gConfigField := gConfigElem.Field(i)
|
||||
defaultConfigField := defaultConfigElem.Field(i)
|
||||
fieldName := gConfigElem.Type().Field(i).Name
|
||||
|
||||
// Use the regex to check if the field is a Block field
|
||||
if gConfigField.Kind() == reflect.Ptr && blockRegex.MatchString(fieldName) {
|
||||
if gConfigField.IsNil() {
|
||||
gConfigField.Set(defaultConfigField)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hard fork block height specified in config.toml has higher priority, but
|
||||
// if it is not specified in config.toml, use the default height in code.
|
||||
func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||
var defaultConfig *params.ChainConfig
|
||||
switch {
|
||||
case ghash == params.MainnetGenesisHash:
|
||||
defaultConfig = params.MainnetChainConfig
|
||||
case ghash == params.BSCGenesisHash:
|
||||
defaultConfig = params.BSCChainConfig
|
||||
case ghash == params.ChapelGenesisHash:
|
||||
defaultConfig = params.ChapelChainConfig
|
||||
case ghash == params.RialtoGenesisHash:
|
||||
defaultConfig = params.RialtoChainConfig
|
||||
default:
|
||||
if g != nil {
|
||||
// it could be a custom config for QA test, just return
|
||||
return g.Config
|
||||
}
|
||||
defaultConfig = params.AllEthashProtocolChanges
|
||||
conf := params.GetBuiltInChainConfig(ghash)
|
||||
if conf != nil {
|
||||
return conf
|
||||
}
|
||||
if g == nil || g.Config == nil {
|
||||
return defaultConfig
|
||||
if g != nil {
|
||||
return g.Config // it could be a custom config for QA test, just return
|
||||
}
|
||||
|
||||
g.setDefaultBlockValues(defaultConfig)
|
||||
|
||||
// BSC Parlia set up
|
||||
if g.Config.Parlia == nil {
|
||||
g.Config.Parlia = defaultConfig.Parlia
|
||||
} else {
|
||||
if g.Config.Parlia.Period == 0 {
|
||||
g.Config.Parlia.Period = defaultConfig.Parlia.Period
|
||||
}
|
||||
if g.Config.Parlia.Epoch == 0 {
|
||||
g.Config.Parlia.Epoch = defaultConfig.Parlia.Epoch
|
||||
}
|
||||
}
|
||||
|
||||
return g.Config
|
||||
return params.AllEthashProtocolChanges
|
||||
}
|
||||
|
||||
// ToBlock returns the genesis block according to genesis specification.
|
||||
@@ -608,6 +563,38 @@ func DefaultGenesisBlock() *Genesis {
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultBSCGenesisBlock returns the BSC mainnet genesis block.
|
||||
func DefaultBSCGenesisBlock() *Genesis {
|
||||
alloc := decodePrealloc(bscMainnetAllocData)
|
||||
return &Genesis{
|
||||
Config: params.BSCChainConfig,
|
||||
Nonce: 0,
|
||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000002a7cdd959bfe8d9487b2a43b33565295a698f7e26488aa4d1955ee33403f8ccb1d4de5fb97c7ade29ef9f4360c606c7ab4db26b016007d3ad0ab86a0ee01c3b1283aa067c58eab4709f85e99d46de5fe685b1ded8013785d6623cc18d214320b6bb6475978f3adfc719c99674c072166708589033e2d9afec2be4ec20253b8642161bc3f444f53679c1f3d472f7be8361c80a4c1e7e9aaf001d0877f1cfde218ce2fd7544e0b2cc94692d4a704debef7bcb61328b8f7166496996a7da21cf1f1b04d9b3e26a3d0772d4c407bbe49438ed859fe965b140dcf1aab71a96bbad7cf34b5fa511d8e963dbba288b1960e75d64430b3230294d12c6ab2aac5c2cd68e80b16b581ea0a6e3c511bbd10f4519ece37dc24887e11b55d7ae2f5b9e386cd1b50a4550696d957cb4900f03a82012708dafc9e1b880fd083b32182b869be8e0922b81f8e175ffde54d797fe11eb03f9e3bf75f1d68bf0b8b6fb4e317a0f9d6f03eaf8ce6675bc60d8c4d90829ce8f72d0163c1d5cf348a862d55063035e7a025f4da968de7e4d7e4004197917f4070f1d6caa02bbebaebb5d7e581e4b66559e635f805ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
GasLimit: 40000000,
|
||||
Difficulty: big.NewInt(1),
|
||||
Mixhash: common.Hash(hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000000")),
|
||||
Coinbase: common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE"),
|
||||
Timestamp: 0x5e9da7ce,
|
||||
Alloc: alloc,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -635,13 +622,34 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet common.Address) *Genesis {
|
||||
}
|
||||
|
||||
func decodePrealloc(data string) GenesisAlloc {
|
||||
var p []struct{ Addr, Balance *big.Int }
|
||||
var p []struct {
|
||||
Addr *big.Int
|
||||
Balance *big.Int
|
||||
Misc *struct {
|
||||
Nonce uint64
|
||||
Code []byte
|
||||
Slots []struct {
|
||||
Key common.Hash
|
||||
Val common.Hash
|
||||
}
|
||||
} `rlp:"optional"`
|
||||
}
|
||||
if err := rlp.NewStream(strings.NewReader(data), 0).Decode(&p); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ga := make(GenesisAlloc, len(p))
|
||||
for _, account := range p {
|
||||
ga[common.BigToAddress(account.Addr)] = GenesisAccount{Balance: account.Balance}
|
||||
acc := GenesisAccount{Balance: account.Balance}
|
||||
if account.Misc != nil {
|
||||
acc.Nonce = account.Misc.Nonce
|
||||
acc.Code = account.Misc.Code
|
||||
|
||||
acc.Storage = make(map[common.Hash]common.Hash)
|
||||
for _, slot := range account.Misc.Slots {
|
||||
acc.Storage[slot.Key] = slot.Val
|
||||
}
|
||||
}
|
||||
ga[common.BigToAddress(account.Addr)] = acc
|
||||
}
|
||||
return ga
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -71,8 +71,8 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db, newDbConfig(scheme)), nil)
|
||||
},
|
||||
wantHash: params.MainnetGenesisHash,
|
||||
wantConfig: params.MainnetChainConfig,
|
||||
wantHash: params.BSCGenesisHash,
|
||||
wantConfig: params.BSCChainConfig,
|
||||
},
|
||||
{
|
||||
name: "mainnet block in DB, genesis == nil",
|
||||
@@ -241,12 +241,12 @@ func TestConfigOrDefault(t *testing.T) {
|
||||
gHash := params.BSCGenesisHash
|
||||
config := defaultGenesis.configOrDefault(gHash)
|
||||
|
||||
if config.ChainID.Cmp(params.MainnetChainConfig.ChainID) != 0 {
|
||||
if config.ChainID.Cmp(params.BSCChainConfig.ChainID) != 0 {
|
||||
t.Errorf("ChainID of resulting config should be %v, but is %v instead", params.BSCChainConfig.ChainID, config.ChainID)
|
||||
}
|
||||
|
||||
if config.HomesteadBlock.Cmp(params.MainnetChainConfig.HomesteadBlock) != 0 {
|
||||
t.Errorf("resulting config should have HomesteadBlock = %v, but instead is %v", params.MainnetChainConfig, config.HomesteadBlock)
|
||||
if config.HomesteadBlock.Cmp(params.BSCChainConfig.HomesteadBlock) != 0 {
|
||||
t.Errorf("resulting config should have HomesteadBlock = %v, but instead is %v", params.BSCChainConfig, config.HomesteadBlock)
|
||||
}
|
||||
|
||||
if config.PlanckBlock == nil {
|
||||
@@ -258,34 +258,6 @@ func TestConfigOrDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultBlockValues(t *testing.T) {
|
||||
genesis := &Genesis{Config: ¶ms.ChainConfig{ChainID: big.NewInt(66), HomesteadBlock: big.NewInt(11)}}
|
||||
genesis.setDefaultBlockValues(params.BSCChainConfig)
|
||||
|
||||
// Make sure the non-nil block was not modified
|
||||
if genesis.Config.HomesteadBlock.Cmp(big.NewInt(11)) != 0 {
|
||||
t.Errorf("Homestead block should not have been modified. HomesteadBlock = %v", genesis.Config.HomesteadBlock)
|
||||
}
|
||||
|
||||
// Spot check a few blocks
|
||||
if genesis.Config.NielsBlock.Cmp(params.BSCChainConfig.NielsBlock) != 0 {
|
||||
t.Errorf("Niels block not matching: in genesis = %v , in defaultConfig = %v", genesis.Config.NielsBlock, params.BSCChainConfig.NielsBlock)
|
||||
}
|
||||
|
||||
if genesis.Config.NanoBlock.Cmp(params.BSCChainConfig.NanoBlock) != 0 {
|
||||
t.Errorf("Nano block not matching: in genesis = %v , in defaultConfig = %v", genesis.Config.NanoBlock, params.BSCChainConfig.NanoBlock)
|
||||
}
|
||||
|
||||
if genesis.Config.PlanckBlock.Cmp(params.BSCChainConfig.PlanckBlock) != 0 {
|
||||
t.Errorf("Nano block not matching: in genesis = %v , in defaultConfig = %v", genesis.Config.PlanckBlock, params.BSCChainConfig.PlanckBlock)
|
||||
}
|
||||
|
||||
// Lastly make sure non-block fields such as ChainID have not been modified
|
||||
if genesis.Config.ChainID.Cmp(big.NewInt(66)) != 0 {
|
||||
t.Errorf("ChainID should not have been modified. ChainID = %v", genesis.Config.ChainID)
|
||||
}
|
||||
}
|
||||
|
||||
func newDbConfig(scheme string) *trie.Config {
|
||||
if scheme == rawdb.HashScheme {
|
||||
return trie.HashDefaults
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -32,24 +32,51 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type allocItem struct{ Addr, Balance *big.Int }
|
||||
type allocItem struct {
|
||||
Addr *big.Int
|
||||
Balance *big.Int
|
||||
Misc *allocItemMisc `rlp:"optional"`
|
||||
}
|
||||
|
||||
type allocItemMisc struct {
|
||||
Nonce uint64
|
||||
Code []byte
|
||||
Slots []allocItemStorageItem
|
||||
}
|
||||
|
||||
type allocItemStorageItem struct {
|
||||
Key common.Hash
|
||||
Val common.Hash
|
||||
}
|
||||
|
||||
func makelist(g *core.Genesis) []allocItem {
|
||||
items := make([]allocItem, 0, len(g.Alloc))
|
||||
for addr, account := range g.Alloc {
|
||||
var misc *allocItemMisc
|
||||
if len(account.Storage) > 0 || len(account.Code) > 0 || account.Nonce != 0 {
|
||||
panic(fmt.Sprintf("can't encode account %x", addr))
|
||||
misc = &allocItemMisc{
|
||||
Nonce: account.Nonce,
|
||||
Code: account.Code,
|
||||
Slots: make([]allocItemStorageItem, 0, len(account.Storage)),
|
||||
}
|
||||
for key, val := range account.Storage {
|
||||
misc.Slots = append(misc.Slots, allocItemStorageItem{key, val})
|
||||
}
|
||||
slices.SortFunc(misc.Slots, func(a, b allocItemStorageItem) int {
|
||||
return a.Key.Cmp(b.Key)
|
||||
})
|
||||
}
|
||||
bigAddr := new(big.Int).SetBytes(addr.Bytes())
|
||||
items = append(items, allocItem{bigAddr, account.Balance})
|
||||
items = append(items, allocItem{bigAddr, account.Balance, misc})
|
||||
}
|
||||
slices.SortFunc(items, func(a, b allocItem) bool {
|
||||
return a.Addr.Cmp(b.Addr) < 0
|
||||
slices.SortFunc(items, func(a, b allocItem) int {
|
||||
return a.Addr.Cmp(b.Addr)
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/leveldb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/pebble"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
@@ -425,6 +426,16 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, ancient
|
||||
return frdb, nil
|
||||
}
|
||||
|
||||
// NewPebbleDBDatabase creates a persistent key-value database without a freezer
|
||||
// moving immutable chain segments into cold storage.
|
||||
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
db, err := pebble.New(file, cache, handles, namespace, readonly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewDatabase(db), nil
|
||||
}
|
||||
|
||||
const (
|
||||
dbPebble = "pebble"
|
||||
dbLeveldb = "leveldb"
|
||||
@@ -480,12 +491,8 @@ func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) {
|
||||
return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb)
|
||||
}
|
||||
if o.Type == dbPebble || existingDb == dbPebble {
|
||||
if PebbleEnabled {
|
||||
log.Info("Using pebble as the backing database")
|
||||
return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
|
||||
} else {
|
||||
return nil, errors.New("db.engine 'pebble' not supported on this platform")
|
||||
}
|
||||
log.Info("Using pebble as the backing database")
|
||||
return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
|
||||
}
|
||||
if o.Type == dbLeveldb || existingDb == dbLeveldb {
|
||||
log.Info("Using leveldb as the backing database")
|
||||
@@ -493,10 +500,8 @@ func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) {
|
||||
}
|
||||
// No pre-existing database, no user-requested one either. Default to Pebble
|
||||
// on supported platforms and LevelDB on anything else.
|
||||
// if PebbleEnabled {
|
||||
// log.Info("Defaulting to pebble as the backing database")
|
||||
// return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
|
||||
// }
|
||||
log.Info("Defaulting to leveldb as the backing database")
|
||||
return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
//go:build (arm64 || amd64) && !openbsd
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/pebble"
|
||||
)
|
||||
|
||||
// Pebble is unsuported on 32bit architecture
|
||||
const PebbleEnabled = true
|
||||
|
||||
// NewPebbleDBDatabase creates a persistent key-value database without a freezer
|
||||
// moving immutable chain segments into cold storage.
|
||||
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
db, err := pebble.New(file, cache, handles, namespace, readonly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewDatabase(db), nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !((arm64 || amd64) && !openbsd)
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
// Pebble is unsuported on 32bit architecture
|
||||
const PebbleEnabled = false
|
||||
|
||||
// NewPebbleDBDatabase creates a persistent key-value database without a freezer
|
||||
// moving immutable chain segments into cold storage.
|
||||
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
return nil, errors.New("pebble is not supported on this platform")
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const tmpSuffix = ".tmp"
|
||||
@@ -240,6 +241,7 @@ func cleanup(path string) error {
|
||||
}
|
||||
for _, name := range names {
|
||||
if name == filepath.Base(path)+tmpSuffix {
|
||||
log.Info("Removed leftover freezer directory", "name", name)
|
||||
return os.RemoveAll(filepath.Join(parent, name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,12 @@ func (t *freezerTable) repair() error {
|
||||
t.index.ReadAt(buffer, offsetsSize-indexEntrySize)
|
||||
lastIndex.unmarshalBinary(buffer)
|
||||
}
|
||||
// Print an error log if the index is corrupted due to an incorrect
|
||||
// last index item. While it is theoretically possible to have a zero offset
|
||||
// by storing all zero-size items, it is highly unlikely to occur in practice.
|
||||
if lastIndex.offset == 0 && offsetsSize%indexEntrySize > 1 {
|
||||
log.Error("Corrupted index file detected", "lastOffset", lastIndex.offset, "items", offsetsSize%indexEntrySize-1)
|
||||
}
|
||||
if t.readonly {
|
||||
t.head, err = t.openFile(lastIndex.filenum, openFreezerFileForReadOnly)
|
||||
} else {
|
||||
@@ -357,7 +363,7 @@ func (t *freezerTable) repair() error {
|
||||
return err
|
||||
}
|
||||
if verbose {
|
||||
t.logger.Info("Chain freezer table opened", "items", t.items.Load(), "size", t.headBytes)
|
||||
t.logger.Info("Chain freezer table opened", "items", t.items.Load(), "deleted", t.itemOffset.Load(), "hidden", t.itemHidden.Load(), "tailId", t.tailId, "headId", t.headId, "size", t.headBytes)
|
||||
} else {
|
||||
t.logger.Debug("Chain freezer table opened", "items", t.items.Load(), "size", common.StorageSize(t.headBytes))
|
||||
}
|
||||
@@ -530,6 +536,10 @@ func (t *freezerTable) truncateTail(items uint64) error {
|
||||
if err := t.meta.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Close the index file before shorten it.
|
||||
if err := t.index.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Truncate the deleted index entries from the index file.
|
||||
err = copyFrom(t.index.Name(), t.index.Name(), indexEntrySize*(newDeleted-deleted+1), func(f *os.File) error {
|
||||
tailIndex := indexEntry{
|
||||
@@ -543,13 +553,14 @@ func (t *freezerTable) truncateTail(items uint64) error {
|
||||
return err
|
||||
}
|
||||
// Reopen the modified index file to load the changes
|
||||
if err := t.index.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.index, err = openFreezerFileForAppend(t.index.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Sync the file to ensure changes are flushed to disk
|
||||
if err := t.index.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Release any files before the current tail
|
||||
t.tailId = newTailId
|
||||
t.itemOffset.Store(newDeleted)
|
||||
@@ -782,7 +793,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
|
||||
return fmt.Errorf("missing data file %d", fileId)
|
||||
}
|
||||
if _, err := dataFile.ReadAt(output[len(output)-length:], int64(start)); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w, fileid: %d, start: %d, length: %d", err, fileId, start, length)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -365,21 +365,15 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou
|
||||
}
|
||||
|
||||
func stackTrieGenerate(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan trieKV, out chan common.Hash) {
|
||||
var nodeWriter trie.NodeWriteFunc
|
||||
options := trie.NewStackTrieOptions()
|
||||
if db != nil {
|
||||
nodeWriter = func(owner common.Hash, path []byte, hash common.Hash, blob []byte) {
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(db, owner, path, hash, blob, scheme)
|
||||
}
|
||||
})
|
||||
}
|
||||
t := trie.NewStackTrieWithOwner(nodeWriter, owner)
|
||||
t := trie.NewStackTrie(options)
|
||||
for leaf := range in {
|
||||
t.Update(leaf.key[:], leaf.value)
|
||||
}
|
||||
var root common.Hash
|
||||
if db == nil {
|
||||
root = t.Hash()
|
||||
} else {
|
||||
root, _ = t.Commit()
|
||||
}
|
||||
out <- root
|
||||
out <- t.Commit()
|
||||
}
|
||||
|
||||
@@ -1363,10 +1363,12 @@ func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (boo
|
||||
nodes = trienode.NewNodeSet(addrHash)
|
||||
slots = make(map[common.Hash][]byte)
|
||||
)
|
||||
stack := trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, blob []byte) {
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
nodes.AddNode(path, trienode.NewDeleted())
|
||||
size += common.StorageSize(len(path))
|
||||
})
|
||||
stack := trie.NewStackTrie(options)
|
||||
for iter.Next() {
|
||||
if size > storageDeleteLimit {
|
||||
return true, size, nil, nil, nil
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
)
|
||||
|
||||
defer blockchain.Stop()
|
||||
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
|
||||
bigNumber := new(big.Int).SetBytes(common.MaxHash.Bytes())
|
||||
tooBigNumber := new(big.Int).Set(bigNumber)
|
||||
tooBigNumber.Add(tooBigNumber, common.Big1)
|
||||
for i, tt := range []struct {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
const blocksNumberSinceMining = 5 // the number of blocks need to wait before voting, counting from the validator begin to mine
|
||||
|
||||
var votesManagerCounter = metrics.NewRegisteredCounter("votesManager/local", nil)
|
||||
|
||||
// Backend wraps all methods required for voting.
|
||||
@@ -95,6 +97,7 @@ func (voteManager *VoteManager) loop() {
|
||||
dlEventCh := events.Chan()
|
||||
|
||||
startVote := true
|
||||
blockCountSinceMining := 0
|
||||
var once sync.Once
|
||||
for {
|
||||
select {
|
||||
@@ -120,9 +123,15 @@ func (voteManager *VoteManager) loop() {
|
||||
continue
|
||||
}
|
||||
if !voteManager.eth.IsMining() {
|
||||
blockCountSinceMining = 0
|
||||
log.Debug("skip voting because mining is disabled, continue")
|
||||
continue
|
||||
}
|
||||
blockCountSinceMining++
|
||||
if blockCountSinceMining <= blocksNumberSinceMining {
|
||||
log.Debug("skip voting", "blockCountSinceMining", blockCountSinceMining, "blocksNumberSinceMining", blocksNumberSinceMining)
|
||||
continue
|
||||
}
|
||||
|
||||
if cHead.Block == nil {
|
||||
log.Debug("cHead.Block is nil, continue")
|
||||
@@ -209,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")
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func testVotePool(t *testing.T, isValidRules bool) {
|
||||
if _, err := chain.InsertChain(bs); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := 0; i < 10+blocksNumberSinceMining; i++ {
|
||||
bs, _ = core.GenerateChain(params.TestChainConfig, bs[len(bs)-1], ethash.NewFaker(), db, 1, nil)
|
||||
if _, err := chain.InsertChain(bs); err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -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,22 +47,13 @@ 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 Ethereum main net.
|
||||
// Defaults contains default settings for use on the BSC main net.
|
||||
var Defaults = Config{
|
||||
SyncMode: downloader.SnapSync,
|
||||
NetworkId: 1,
|
||||
NetworkId: 56,
|
||||
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
|
||||
@@ -367,7 +367,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP
|
||||
if len(req.Origin) > 0 {
|
||||
origin, req.Origin = common.BytesToHash(req.Origin), nil
|
||||
}
|
||||
var limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
var limit = common.MaxHash
|
||||
if len(req.Limit) > 0 {
|
||||
limit, req.Limit = common.BytesToHash(req.Limit), nil
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -26,4 +26,32 @@ var (
|
||||
|
||||
IngressRegistrationErrorMeter = metrics.NewRegisteredMeter(ingressRegistrationErrorName, nil)
|
||||
EgressRegistrationErrorMeter = metrics.NewRegisteredMeter(egressRegistrationErrorName, nil)
|
||||
|
||||
// deletionGauge is the metric to track how many trie node deletions
|
||||
// are performed in total during the sync process.
|
||||
deletionGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/delete", nil)
|
||||
|
||||
// lookupGauge is the metric to track how many trie node lookups are
|
||||
// performed to determine if node needs to be deleted.
|
||||
lookupGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/lookup", nil)
|
||||
|
||||
// boundaryAccountNodesGauge is the metric to track how many boundary trie
|
||||
// nodes in account trie are met.
|
||||
boundaryAccountNodesGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/boundary/account", nil)
|
||||
|
||||
// boundaryAccountNodesGauge is the metric to track how many boundary trie
|
||||
// nodes in storage tries are met.
|
||||
boundaryStorageNodesGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/boundary/storage", nil)
|
||||
|
||||
// smallStorageGauge is the metric to track how many storages are small enough
|
||||
// to retrieved in one or two request.
|
||||
smallStorageGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/small", nil)
|
||||
|
||||
// largeStorageGauge is the metric to track how many storages are large enough
|
||||
// to retrieved concurrently.
|
||||
largeStorageGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/large", nil)
|
||||
|
||||
// skipStorageHealingGauge is the metric to track how many storages are retrieved
|
||||
// in multiple requests but healing is not necessary.
|
||||
skipStorageHealingGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/noheal", nil)
|
||||
)
|
||||
|
||||
@@ -67,7 +67,7 @@ func (r *hashRange) End() common.Hash {
|
||||
// If the end overflows (non divisible range), return a shorter interval
|
||||
next, overflow := new(uint256.Int).AddOverflow(r.current, r.step)
|
||||
if overflow {
|
||||
return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
return common.MaxHash
|
||||
}
|
||||
return next.SubUint64(next, 1).Bytes32()
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestHashRanges(t *testing.T) {
|
||||
common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.MaxHash,
|
||||
},
|
||||
},
|
||||
// Split a divisible part of the hash range up into 2 chunks
|
||||
@@ -58,7 +58,7 @@ func TestHashRanges(t *testing.T) {
|
||||
},
|
||||
ends: []common.Hash{
|
||||
common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.MaxHash,
|
||||
},
|
||||
},
|
||||
// Split the entire hash range into a non divisible 3 chunks
|
||||
@@ -73,7 +73,7 @@ func TestHashRanges(t *testing.T) {
|
||||
ends: []common.Hash{
|
||||
common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"),
|
||||
common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
|
||||
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.MaxHash,
|
||||
},
|
||||
},
|
||||
// Split a part of hash range into a non divisible 3 chunks
|
||||
@@ -88,7 +88,7 @@ func TestHashRanges(t *testing.T) {
|
||||
ends: []common.Hash{
|
||||
common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"),
|
||||
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.MaxHash,
|
||||
},
|
||||
},
|
||||
// Split a part of hash range into a non divisible 3 chunks, but with a
|
||||
@@ -108,7 +108,7 @@ func TestHashRanges(t *testing.T) {
|
||||
ends: []common.Hash{
|
||||
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
|
||||
common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"),
|
||||
common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
common.MaxHash,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -717,6 +717,19 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
// cleanPath is used to remove the dangling nodes in the stackTrie.
|
||||
func (s *Syncer) cleanPath(batch ethdb.Batch, owner common.Hash, path []byte) {
|
||||
if owner == (common.Hash{}) && rawdb.ExistsAccountTrieNode(s.db, path) {
|
||||
rawdb.DeleteAccountTrieNode(batch, path)
|
||||
deletionGauge.Inc(1)
|
||||
}
|
||||
if owner != (common.Hash{}) && rawdb.ExistsStorageTrieNode(s.db, owner, path) {
|
||||
rawdb.DeleteStorageTrieNode(batch, owner, path)
|
||||
deletionGauge.Inc(1)
|
||||
}
|
||||
lookupGauge.Inc(1)
|
||||
}
|
||||
|
||||
// loadSyncStatus retrieves a previously aborted sync status from the database,
|
||||
// or generates a fresh one if none is available.
|
||||
func (s *Syncer) loadSyncStatus() {
|
||||
@@ -739,9 +752,22 @@ func (s *Syncer) loadSyncStatus() {
|
||||
s.accountBytes += common.StorageSize(len(key) + len(value))
|
||||
},
|
||||
}
|
||||
task.genTrie = trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, val []byte) {
|
||||
rawdb.WriteTrieNode(task.genBatch, owner, path, hash, val, s.scheme)
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(task.genBatch, common.Hash{}, path, hash, blob, s.scheme)
|
||||
})
|
||||
if s.scheme == rawdb.PathScheme {
|
||||
// Configure the dangling node cleaner and also filter out boundary nodes
|
||||
// only in the context of the path scheme. Deletion is forbidden in the
|
||||
// hash scheme, as it can disrupt state completeness.
|
||||
options = options.WithCleaner(func(path []byte) {
|
||||
s.cleanPath(task.genBatch, common.Hash{}, path)
|
||||
})
|
||||
// Skip the left boundary if it's not the first range.
|
||||
// Skip the right boundary if it's not the last range.
|
||||
options = options.WithSkipBoundary(task.Next != (common.Hash{}), task.Last != common.MaxHash, boundaryAccountNodesGauge)
|
||||
}
|
||||
task.genTrie = trie.NewStackTrie(options)
|
||||
for accountHash, subtasks := range task.SubTasks {
|
||||
for _, subtask := range subtasks {
|
||||
subtask := subtask // closure for subtask.genBatch in the stacktrie writer callback
|
||||
@@ -752,9 +778,23 @@ func (s *Syncer) loadSyncStatus() {
|
||||
s.storageBytes += common.StorageSize(len(key) + len(value))
|
||||
},
|
||||
}
|
||||
subtask.genTrie = trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) {
|
||||
rawdb.WriteTrieNode(subtask.genBatch, owner, path, hash, val, s.scheme)
|
||||
}, accountHash)
|
||||
owner := accountHash // local assignment for stacktrie writer closure
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(subtask.genBatch, owner, path, hash, blob, s.scheme)
|
||||
})
|
||||
if s.scheme == rawdb.PathScheme {
|
||||
// Configure the dangling node cleaner and also filter out boundary nodes
|
||||
// only in the context of the path scheme. Deletion is forbidden in the
|
||||
// hash scheme, as it can disrupt state completeness.
|
||||
options = options.WithCleaner(func(path []byte) {
|
||||
s.cleanPath(subtask.genBatch, owner, path)
|
||||
})
|
||||
// Skip the left boundary if it's not the first range.
|
||||
// Skip the right boundary if it's not the last range.
|
||||
options = options.WithSkipBoundary(subtask.Next != common.Hash{}, subtask.Last != common.MaxHash, boundaryStorageNodesGauge)
|
||||
}
|
||||
subtask.genTrie = trie.NewStackTrie(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -798,7 +838,7 @@ func (s *Syncer) loadSyncStatus() {
|
||||
last := common.BigToHash(new(big.Int).Add(next.Big(), step))
|
||||
if i == accountConcurrency-1 {
|
||||
// Make sure we don't overflow if the step is not a proper divisor
|
||||
last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
last = common.MaxHash
|
||||
}
|
||||
batch := ethdb.HookedBatch{
|
||||
Batch: s.db.NewBatch(),
|
||||
@@ -806,14 +846,27 @@ func (s *Syncer) loadSyncStatus() {
|
||||
s.accountBytes += common.StorageSize(len(key) + len(value))
|
||||
},
|
||||
}
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(batch, common.Hash{}, path, hash, blob, s.scheme)
|
||||
})
|
||||
if s.scheme == rawdb.PathScheme {
|
||||
// Configure the dangling node cleaner and also filter out boundary nodes
|
||||
// only in the context of the path scheme. Deletion is forbidden in the
|
||||
// hash scheme, as it can disrupt state completeness.
|
||||
options = options.WithCleaner(func(path []byte) {
|
||||
s.cleanPath(batch, common.Hash{}, path)
|
||||
})
|
||||
// Skip the left boundary if it's not the first range.
|
||||
// Skip the right boundary if it's not the last range.
|
||||
options = options.WithSkipBoundary(next != common.Hash{}, last != common.MaxHash, boundaryAccountNodesGauge)
|
||||
}
|
||||
s.tasks = append(s.tasks, &accountTask{
|
||||
Next: next,
|
||||
Last: last,
|
||||
SubTasks: make(map[common.Hash][]*storageTask),
|
||||
genBatch: batch,
|
||||
genTrie: trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, val []byte) {
|
||||
rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme)
|
||||
}),
|
||||
genTrie: trie.NewStackTrie(options),
|
||||
})
|
||||
log.Debug("Created account sync task", "from", next, "last", last)
|
||||
next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
|
||||
@@ -1877,7 +1930,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
|
||||
return
|
||||
}
|
||||
// Some accounts are incomplete, leave as is for the storage and contract
|
||||
// task assigners to pick up and fill.
|
||||
// task assigners to pick up and fill
|
||||
}
|
||||
|
||||
// processBytecodeResponse integrates an already validated bytecode response
|
||||
@@ -1965,6 +2018,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) {
|
||||
if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) {
|
||||
res.mainTask.needState[j] = false
|
||||
res.mainTask.pend--
|
||||
smallStorageGauge.Inc(1)
|
||||
}
|
||||
// If the last contract was chunked, mark it as needing healing
|
||||
// to avoid writing it out to disk prematurely.
|
||||
@@ -2000,7 +2054,11 @@ func (s *Syncer) processStorageResponse(res *storageResponse) {
|
||||
log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "chunks", chunks)
|
||||
}
|
||||
r := newHashRange(lastKey, chunks)
|
||||
|
||||
if chunks == 1 {
|
||||
smallStorageGauge.Inc(1)
|
||||
} else {
|
||||
largeStorageGauge.Inc(1)
|
||||
}
|
||||
// Our first task is the one that was just filled by this response.
|
||||
batch := ethdb.HookedBatch{
|
||||
Batch: s.db.NewBatch(),
|
||||
@@ -2008,14 +2066,25 @@ func (s *Syncer) processStorageResponse(res *storageResponse) {
|
||||
s.storageBytes += common.StorageSize(len(key) + len(value))
|
||||
},
|
||||
}
|
||||
owner := account // local assignment for stacktrie writer closure
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(batch, owner, path, hash, blob, s.scheme)
|
||||
})
|
||||
if s.scheme == rawdb.PathScheme {
|
||||
options = options.WithCleaner(func(path []byte) {
|
||||
s.cleanPath(batch, owner, path)
|
||||
})
|
||||
// Keep the left boundary as it's the first range.
|
||||
// Skip the right boundary if it's not the last range.
|
||||
options = options.WithSkipBoundary(false, r.End() != common.MaxHash, boundaryStorageNodesGauge)
|
||||
}
|
||||
tasks = append(tasks, &storageTask{
|
||||
Next: common.Hash{},
|
||||
Last: r.End(),
|
||||
root: acc.Root,
|
||||
genBatch: batch,
|
||||
genTrie: trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) {
|
||||
rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme)
|
||||
}, account),
|
||||
genTrie: trie.NewStackTrie(options),
|
||||
})
|
||||
for r.Next() {
|
||||
batch := ethdb.HookedBatch{
|
||||
@@ -2024,14 +2093,27 @@ func (s *Syncer) processStorageResponse(res *storageResponse) {
|
||||
s.storageBytes += common.StorageSize(len(key) + len(value))
|
||||
},
|
||||
}
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(batch, owner, path, hash, blob, s.scheme)
|
||||
})
|
||||
if s.scheme == rawdb.PathScheme {
|
||||
// Configure the dangling node cleaner and also filter out boundary nodes
|
||||
// only in the context of the path scheme. Deletion is forbidden in the
|
||||
// hash scheme, as it can disrupt state completeness.
|
||||
options = options.WithCleaner(func(path []byte) {
|
||||
s.cleanPath(batch, owner, path)
|
||||
})
|
||||
// Skip the left boundary as it's not the first range
|
||||
// Skip the right boundary if it's not the last range.
|
||||
options = options.WithSkipBoundary(true, r.End() != common.MaxHash, boundaryStorageNodesGauge)
|
||||
}
|
||||
tasks = append(tasks, &storageTask{
|
||||
Next: r.Start(),
|
||||
Last: r.End(),
|
||||
root: acc.Root,
|
||||
genBatch: batch,
|
||||
genTrie: trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) {
|
||||
rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme)
|
||||
}, account),
|
||||
genTrie: trie.NewStackTrie(options),
|
||||
})
|
||||
}
|
||||
for _, task := range tasks {
|
||||
@@ -2076,9 +2158,23 @@ func (s *Syncer) processStorageResponse(res *storageResponse) {
|
||||
slots += len(res.hashes[i])
|
||||
|
||||
if i < len(res.hashes)-1 || res.subTask == nil {
|
||||
tr := trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) {
|
||||
rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme)
|
||||
}, account)
|
||||
// no need to make local reassignment of account: this closure does not outlive the loop
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(batch, account, path, hash, blob, s.scheme)
|
||||
})
|
||||
if s.scheme == rawdb.PathScheme {
|
||||
// Configure the dangling node cleaner only in the context of the
|
||||
// path scheme. Deletion is forbidden in the hash scheme, as it can
|
||||
// disrupt state completeness.
|
||||
//
|
||||
// Notably, boundary nodes can be also kept because the whole storage
|
||||
// trie is complete.
|
||||
options = options.WithCleaner(func(path []byte) {
|
||||
s.cleanPath(batch, account, path)
|
||||
})
|
||||
}
|
||||
tr := trie.NewStackTrie(options)
|
||||
for j := 0; j < len(res.hashes[i]); j++ {
|
||||
tr.Update(res.hashes[i][j][:], res.slots[i][j])
|
||||
}
|
||||
@@ -2100,18 +2196,25 @@ func (s *Syncer) processStorageResponse(res *storageResponse) {
|
||||
// Large contracts could have generated new trie nodes, flush them to disk
|
||||
if res.subTask != nil {
|
||||
if res.subTask.done {
|
||||
if root, err := res.subTask.genTrie.Commit(); err != nil {
|
||||
log.Error("Failed to commit stack slots", "err", err)
|
||||
} else if root == res.subTask.root {
|
||||
// If the chunk's root is an overflown but full delivery, clear the heal request
|
||||
root := res.subTask.genTrie.Commit()
|
||||
if err := res.subTask.genBatch.Write(); err != nil {
|
||||
log.Error("Failed to persist stack slots", "err", err)
|
||||
}
|
||||
res.subTask.genBatch.Reset()
|
||||
|
||||
// If the chunk's root is an overflown but full delivery,
|
||||
// clear the heal request.
|
||||
accountHash := res.accounts[len(res.accounts)-1]
|
||||
if root == res.subTask.root && rawdb.HasStorageTrieNode(s.db, accountHash, nil, root) {
|
||||
for i, account := range res.mainTask.res.hashes {
|
||||
if account == res.accounts[len(res.accounts)-1] {
|
||||
if account == accountHash {
|
||||
res.mainTask.needHeal[i] = false
|
||||
skipStorageHealingGauge.Inc(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize || res.subTask.done {
|
||||
if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize {
|
||||
if err := res.subTask.genBatch.Write(); err != nil {
|
||||
log.Error("Failed to persist stack slots", "err", err)
|
||||
}
|
||||
@@ -2318,9 +2421,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) {
|
||||
// flush after finalizing task.done. It's fine even if we crash and lose this
|
||||
// write as it will only cause more data to be downloaded during heal.
|
||||
if task.done {
|
||||
if _, err := task.genTrie.Commit(); err != nil {
|
||||
log.Error("Failed to commit stack account", "err", err)
|
||||
}
|
||||
task.genTrie.Commit()
|
||||
}
|
||||
if task.genBatch.ValueSize() > ethdb.IdealBatchSize || task.done {
|
||||
if err := task.genBatch.Write(); err != nil {
|
||||
@@ -2398,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 {
|
||||
@@ -2625,7 +2726,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
|
||||
// the requested data. For storage range queries that means the state being
|
||||
// retrieved was either already pruned remotely, or the peer is not yet
|
||||
// synced to our head.
|
||||
if len(hashes) == 0 {
|
||||
if len(hashes) == 0 && len(proof) == 0 {
|
||||
logger.Debug("Peer rejected storage request")
|
||||
s.statelessPeers[peer.ID()] = struct{}{}
|
||||
s.lock.Unlock()
|
||||
@@ -2637,13 +2738,20 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo
|
||||
// Reconstruct the partial tries from the response and verify them
|
||||
var cont bool
|
||||
|
||||
// If a proof was attached while the response is empty, it indicates that the
|
||||
// requested range specified with 'origin' is empty. Construct an empty state
|
||||
// response locally to finalize the range.
|
||||
if len(hashes) == 0 && len(proof) > 0 {
|
||||
hashes = append(hashes, []common.Hash{})
|
||||
slots = append(slots, [][]byte{})
|
||||
}
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
// Convert the keys and proofs into an internal format
|
||||
keys := make([][]byte, len(hashes[i]))
|
||||
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)
|
||||
@@ -2662,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 {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -31,10 +32,10 @@ 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"
|
||||
"github.com/ethereum/go-ethereum/trie/testutil"
|
||||
"github.com/ethereum/go-ethereum/trie/triedb/pathdb"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"golang.org/x/crypto/sha3"
|
||||
@@ -254,7 +255,7 @@ func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, orig
|
||||
func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) {
|
||||
var size uint64
|
||||
if limit == (common.Hash{}) {
|
||||
limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
limit = common.MaxHash
|
||||
}
|
||||
for _, entry := range t.accountValues {
|
||||
if size > cap {
|
||||
@@ -273,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)
|
||||
}
|
||||
@@ -283,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
|
||||
@@ -319,7 +320,7 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm
|
||||
if len(origin) > 0 {
|
||||
originHash = common.BytesToHash(origin)
|
||||
}
|
||||
var limitHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
var limitHash = common.MaxHash
|
||||
if len(limit) > 0 {
|
||||
limitHash = common.BytesToHash(limit)
|
||||
}
|
||||
@@ -353,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
|
||||
@@ -368,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
|
||||
@@ -411,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
|
||||
@@ -427,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
|
||||
@@ -599,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 {
|
||||
@@ -614,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 {
|
||||
@@ -762,7 +764,7 @@ func testSyncWithStorage(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 3000, true, false)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 3000, true, false, false)
|
||||
|
||||
mkSource := func(name string) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -772,7 +774,7 @@ func testSyncWithStorage(t *testing.T, scheme string) {
|
||||
source.storageValues = storageElems
|
||||
return source
|
||||
}
|
||||
syncer := setupSyncer(nodeScheme, mkSource("sourceA"))
|
||||
syncer := setupSyncer(scheme, mkSource("sourceA"))
|
||||
done := checkStall(t, term)
|
||||
if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
|
||||
t.Fatalf("sync failed: %v", err)
|
||||
@@ -799,7 +801,7 @@ func testMultiSyncManyUseless(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
|
||||
|
||||
mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -821,7 +823,7 @@ func testMultiSyncManyUseless(t *testing.T, scheme string) {
|
||||
}
|
||||
|
||||
syncer := setupSyncer(
|
||||
nodeScheme,
|
||||
scheme,
|
||||
mkSource("full", true, true, true),
|
||||
mkSource("noAccounts", false, true, true),
|
||||
mkSource("noStorage", true, false, true),
|
||||
@@ -853,7 +855,7 @@ func testMultiSyncManyUselessWithLowTimeout(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
|
||||
|
||||
mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -875,7 +877,7 @@ func testMultiSyncManyUselessWithLowTimeout(t *testing.T, scheme string) {
|
||||
}
|
||||
|
||||
syncer := setupSyncer(
|
||||
nodeScheme,
|
||||
scheme,
|
||||
mkSource("full", true, true, true),
|
||||
mkSource("noAccounts", false, true, true),
|
||||
mkSource("noStorage", true, false, true),
|
||||
@@ -912,7 +914,7 @@ func testMultiSyncManyUnresponsive(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
|
||||
|
||||
mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -934,7 +936,7 @@ func testMultiSyncManyUnresponsive(t *testing.T, scheme string) {
|
||||
}
|
||||
|
||||
syncer := setupSyncer(
|
||||
nodeScheme,
|
||||
scheme,
|
||||
mkSource("full", true, true, true),
|
||||
mkSource("noAccounts", false, true, true),
|
||||
mkSource("noStorage", true, false, true),
|
||||
@@ -1215,7 +1217,7 @@ func testSyncBoundaryStorageTrie(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 10, 1000, false, true)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 10, 1000, false, true, false)
|
||||
|
||||
mkSource := func(name string) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -1226,7 +1228,7 @@ func testSyncBoundaryStorageTrie(t *testing.T, scheme string) {
|
||||
return source
|
||||
}
|
||||
syncer := setupSyncer(
|
||||
nodeScheme,
|
||||
scheme,
|
||||
mkSource("peer-a"),
|
||||
mkSource("peer-b"),
|
||||
)
|
||||
@@ -1257,7 +1259,7 @@ func testSyncWithStorageAndOneCappedPeer(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 300, 1000, false, false)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 300, 1000, false, false, false)
|
||||
|
||||
mkSource := func(name string, slow bool) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -1273,7 +1275,7 @@ func testSyncWithStorageAndOneCappedPeer(t *testing.T, scheme string) {
|
||||
}
|
||||
|
||||
syncer := setupSyncer(
|
||||
nodeScheme,
|
||||
scheme,
|
||||
mkSource("nice-a", false),
|
||||
mkSource("slow", true),
|
||||
)
|
||||
@@ -1304,7 +1306,7 @@ func testSyncWithStorageAndCorruptPeer(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
|
||||
|
||||
mkSource := func(name string, handler storageHandlerFunc) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -1317,7 +1319,7 @@ func testSyncWithStorageAndCorruptPeer(t *testing.T, scheme string) {
|
||||
}
|
||||
|
||||
syncer := setupSyncer(
|
||||
nodeScheme,
|
||||
scheme,
|
||||
mkSource("nice-a", defaultStorageRequestHandler),
|
||||
mkSource("nice-b", defaultStorageRequestHandler),
|
||||
mkSource("nice-c", defaultStorageRequestHandler),
|
||||
@@ -1348,7 +1350,7 @@ func testSyncWithStorageAndNonProvingPeer(t *testing.T, scheme string) {
|
||||
})
|
||||
}
|
||||
)
|
||||
nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false)
|
||||
sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false)
|
||||
|
||||
mkSource := func(name string, handler storageHandlerFunc) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
@@ -1360,7 +1362,7 @@ func testSyncWithStorageAndNonProvingPeer(t *testing.T, scheme string) {
|
||||
return source
|
||||
}
|
||||
syncer := setupSyncer(
|
||||
nodeScheme,
|
||||
scheme,
|
||||
mkSource("nice-a", defaultStorageRequestHandler),
|
||||
mkSource("nice-b", defaultStorageRequestHandler),
|
||||
mkSource("nice-c", defaultStorageRequestHandler),
|
||||
@@ -1413,6 +1415,45 @@ func testSyncWithStorageMisbehavingProve(t *testing.T, scheme string) {
|
||||
verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t)
|
||||
}
|
||||
|
||||
// TestSyncWithUnevenStorage tests sync where the storage trie is not even
|
||||
// and with a few empty ranges.
|
||||
func TestSyncWithUnevenStorage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testSyncWithUnevenStorage(t, rawdb.HashScheme)
|
||||
testSyncWithUnevenStorage(t, rawdb.PathScheme)
|
||||
}
|
||||
|
||||
func testSyncWithUnevenStorage(t *testing.T, scheme string) {
|
||||
var (
|
||||
once sync.Once
|
||||
cancel = make(chan struct{})
|
||||
term = func() {
|
||||
once.Do(func() {
|
||||
close(cancel)
|
||||
})
|
||||
}
|
||||
)
|
||||
accountTrie, accounts, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 256, false, false, true)
|
||||
|
||||
mkSource := func(name string) *testPeer {
|
||||
source := newTestPeer(name, t, term)
|
||||
source.accountTrie = accountTrie.Copy()
|
||||
source.accountValues = accounts
|
||||
source.setStorageTries(storageTries)
|
||||
source.storageValues = storageElems
|
||||
source.storageRequestHandler = func(t *testPeer, reqId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
|
||||
return defaultStorageRequestHandler(t, reqId, root, accounts, origin, limit, 128) // retrieve storage in large mode
|
||||
}
|
||||
return source
|
||||
}
|
||||
syncer := setupSyncer(scheme, mkSource("source"))
|
||||
if err := syncer.Sync(accountTrie.Hash(), cancel); err != nil {
|
||||
t.Fatalf("sync failed: %v", err)
|
||||
}
|
||||
verifyTrie(scheme, syncer.db, accountTrie.Hash(), t)
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k, v []byte
|
||||
}
|
||||
@@ -1511,7 +1552,7 @@ func makeBoundaryAccountTrie(scheme string, n int) (string, *trie.Trie, []*kv) {
|
||||
for i := 0; i < accountConcurrency; i++ {
|
||||
last := common.BigToHash(new(big.Int).Add(next.Big(), step))
|
||||
if i == accountConcurrency-1 {
|
||||
last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
last = common.MaxHash
|
||||
}
|
||||
boundaries = append(boundaries, last)
|
||||
next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
|
||||
@@ -1608,7 +1649,7 @@ func makeAccountTrieWithStorageWithUniqueStorage(scheme string, accounts, slots
|
||||
}
|
||||
|
||||
// makeAccountTrieWithStorage spits out a trie, along with the leafs
|
||||
func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, boundary bool) (string, *trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) {
|
||||
func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, boundary bool, uneven bool) (*trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) {
|
||||
var (
|
||||
db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme))
|
||||
accTrie = trie.NewEmpty(db)
|
||||
@@ -1633,6 +1674,8 @@ func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, bounda
|
||||
)
|
||||
if boundary {
|
||||
stRoot, stNodes, stEntries = makeBoundaryStorageTrie(common.BytesToHash(key), slots, db)
|
||||
} else if uneven {
|
||||
stRoot, stNodes, stEntries = makeUnevenStorageTrie(common.BytesToHash(key), slots, db)
|
||||
} else {
|
||||
stRoot, stNodes, stEntries = makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), 0, db)
|
||||
}
|
||||
@@ -1675,7 +1718,7 @@ func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, bounda
|
||||
}
|
||||
storageTries[common.BytesToHash(key)] = trie
|
||||
}
|
||||
return db.Scheme(), accTrie, entries, storageTries, storageEntries
|
||||
return accTrie, entries, storageTries, storageEntries
|
||||
}
|
||||
|
||||
// makeStorageTrieWithSeed fills a storage trie with n items, returning the
|
||||
@@ -1721,7 +1764,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo
|
||||
for i := 0; i < accountConcurrency; i++ {
|
||||
last := common.BigToHash(new(big.Int).Add(next.Big(), step))
|
||||
if i == accountConcurrency-1 {
|
||||
last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
||||
last = common.MaxHash
|
||||
}
|
||||
boundaries = append(boundaries, last)
|
||||
next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
|
||||
@@ -1752,6 +1795,38 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo
|
||||
return root, nodes, entries
|
||||
}
|
||||
|
||||
// makeUnevenStorageTrie constructs a storage tries will states distributed in
|
||||
// different range unevenly.
|
||||
func makeUnevenStorageTrie(owner common.Hash, slots int, db *trie.Database) (common.Hash, *trienode.NodeSet, []*kv) {
|
||||
var (
|
||||
entries []*kv
|
||||
tr, _ = trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db)
|
||||
chosen = make(map[byte]struct{})
|
||||
)
|
||||
for i := 0; i < 3; i++ {
|
||||
var n int
|
||||
for {
|
||||
n = mrand.Intn(15) // the last range is set empty deliberately
|
||||
if _, ok := chosen[byte(n)]; ok {
|
||||
continue
|
||||
}
|
||||
chosen[byte(n)] = struct{}{}
|
||||
break
|
||||
}
|
||||
for j := 0; j < slots/3; j++ {
|
||||
key := append([]byte{byte(n)}, testutil.RandBytes(31)...)
|
||||
val, _ := rlp.EncodeToBytes(testutil.RandBytes(32))
|
||||
|
||||
elem := &kv{key, val}
|
||||
tr.MustUpdate(elem.k, elem.v)
|
||||
entries = append(entries, elem)
|
||||
}
|
||||
}
|
||||
slices.SortFunc(entries, (*kv).cmp)
|
||||
root, nodes, _ := tr.Commit(false)
|
||||
return root, nodes, entries
|
||||
}
|
||||
|
||||
func verifyTrie(scheme string, db ethdb.KeyValueStore, root common.Hash, t *testing.T) {
|
||||
t.Helper()
|
||||
triedb := trie.NewDatabase(rawdb.NewDatabase(db), newDbConfig(scheme))
|
||||
|
||||
@@ -110,6 +110,16 @@ func (ec *Client) PeerCount(ctx context.Context) (uint64, error) {
|
||||
return uint64(result), err
|
||||
}
|
||||
|
||||
// BlockReceipts returns the receipts of a given block number or hash.
|
||||
func (ec *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*types.Receipt, error) {
|
||||
var r []*types.Receipt
|
||||
err := ec.c.CallContext(ctx, &r, "eth_getBlockReceipts", blockNrOrHash.String())
|
||||
if err == nil && r == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
type rpcBlock struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
Transactions []rpcTransaction `json:"transactions"`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
// 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/>.
|
||||
|
||||
//go:build (arm64 || amd64) && !openbsd
|
||||
|
||||
// Package pebble implements the key-value database layer based on pebble.
|
||||
package pebble
|
||||
|
||||
@@ -48,6 +46,9 @@ const (
|
||||
// metricsGatheringInterval specifies the interval to retrieve pebble database
|
||||
// compaction, io and pause stats to report to the user.
|
||||
metricsGatheringInterval = 3 * time.Second
|
||||
|
||||
// numLevels is the level number of pebble sst files
|
||||
numLevels = 7
|
||||
)
|
||||
|
||||
// Database is a persistent key-value store based on the pebble storage engine.
|
||||
@@ -141,15 +142,29 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
|
||||
|
||||
// The max memtable size is limited by the uint32 offsets stored in
|
||||
// internal/arenaskl.node, DeferredBatchOp, and flushableBatchEntry.
|
||||
// Taken from https://github.com/cockroachdb/pebble/blob/master/open.go#L38
|
||||
maxMemTableSize := 4<<30 - 1 // Capped by 4 GB
|
||||
//
|
||||
// - MaxUint32 on 64-bit platforms;
|
||||
// - MaxInt on 32-bit platforms.
|
||||
//
|
||||
// It is used when slices are limited to Uint32 on 64-bit platforms (the
|
||||
// length limit for slices is naturally MaxInt on 32-bit platforms).
|
||||
//
|
||||
// Taken from https://github.com/cockroachdb/pebble/blob/master/internal/constants/constants.go
|
||||
maxMemTableSize := (1<<31)<<(^uint(0)>>63) - 1
|
||||
|
||||
// Two memory tables is configured which is identical to leveldb,
|
||||
// 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),
|
||||
@@ -191,6 +206,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
|
||||
WriteStallBegin: db.onWriteStallBegin,
|
||||
WriteStallEnd: db.onWriteStallEnd,
|
||||
},
|
||||
Levels: make([]pebble.LevelOptions, numLevels),
|
||||
Logger: panicLogger{}, // TODO(karalabe): Delete when this is upstreamed in Pebble
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
// 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/>.
|
||||
|
||||
//go:build (arm64 || amd64) && !openbsd
|
||||
|
||||
package pebble
|
||||
|
||||
import (
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -964,6 +964,34 @@ func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address
|
||||
return res[:], state.Error()
|
||||
}
|
||||
|
||||
// GetBlockReceipts returns the block receipts for the given block hash or number or tag.
|
||||
func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||||
block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||||
if block == nil || err != nil {
|
||||
// When the block doesn't exist, the RPC method should return JSON null
|
||||
// as per specification.
|
||||
return nil, nil
|
||||
}
|
||||
receipts, err := s.b.GetReceipts(ctx, block.Hash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txs := block.Transactions()
|
||||
if len(txs) != len(receipts) {
|
||||
return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(receipts))
|
||||
}
|
||||
|
||||
// Derive the sender.
|
||||
signer := types.MakeSigner(s.b.ChainConfig(), block.Number(), block.Time())
|
||||
|
||||
result := make([]map[string]interface{}, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// OverrideAccount indicates the overriding fields of account during the execution
|
||||
// of a message call.
|
||||
// Note, state and stateDiff can't be specified at the same time. If state is
|
||||
@@ -2123,13 +2151,18 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.
|
||||
|
||||
// Derive the sender.
|
||||
signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time)
|
||||
return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil
|
||||
}
|
||||
|
||||
// marshalReceipt marshals a transaction receipt into a JSON object.
|
||||
func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} {
|
||||
from, _ := types.Sender(signer, tx)
|
||||
|
||||
fields := map[string]interface{}{
|
||||
"blockHash": blockHash,
|
||||
"blockNumber": hexutil.Uint64(blockNumber),
|
||||
"transactionHash": hash,
|
||||
"transactionIndex": hexutil.Uint64(index),
|
||||
"transactionHash": tx.Hash(),
|
||||
"transactionIndex": hexutil.Uint64(txIndex),
|
||||
"from": from,
|
||||
"to": tx.To(),
|
||||
"gasUsed": hexutil.Uint64(receipt.GasUsed),
|
||||
@@ -2155,7 +2188,7 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.
|
||||
if receipt.ContractAddress != (common.Address{}) {
|
||||
fields["contractAddress"] = receipt.ContractAddress
|
||||
}
|
||||
return fields, nil
|
||||
return fields
|
||||
}
|
||||
|
||||
// sign is a helper function that signs a transaction with the private key of the given address.
|
||||
|
||||
@@ -1781,9 +1781,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) {
|
||||
}
|
||||
*/
|
||||
|
||||
func TestRPCGetTransactionReceipt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Hash) {
|
||||
// Initialize test accounts
|
||||
var (
|
||||
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
@@ -1809,10 +1807,9 @@ func TestRPCGetTransactionReceipt(t *testing.T) {
|
||||
contract: {Balance: big.NewInt(params.Ether), Code: common.FromHex("0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a9059cbb14610030575b600080fd5b61004a6004803603810190610045919061016a565b610060565b60405161005791906101c5565b60405180910390f35b60008273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516100bf91906101ef565b60405180910390a36001905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610101826100d6565b9050919050565b610111816100f6565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b6000819050919050565b61014781610134565b811461015257600080fd5b50565b6000813590506101648161013e565b92915050565b60008060408385031215610181576101806100d1565b5b600061018f8582860161011f565b92505060206101a085828601610155565b9150509250929050565b60008115159050919050565b6101bf816101aa565b82525050565b60006020820190506101da60008301846101b6565b92915050565b6101e981610134565b82525050565b600060208201905061020460008301846101e0565b9291505056fea2646970667358221220b469033f4b77b9565ee84e0a2f04d496b18160d26034d54f9487e57788fd36d564736f6c63430008120033")},
|
||||
},
|
||||
}
|
||||
genBlocks = 5
|
||||
signer = types.LatestSignerForChainID(params.TestChainConfig.ChainID)
|
||||
txHashes = make([]common.Hash, genBlocks)
|
||||
gasPrice = big.NewInt(3e9) // 3Gwei
|
||||
signer = types.LatestSignerForChainID(params.TestChainConfig.ChainID)
|
||||
txHashes = make([]common.Hash, genBlocks)
|
||||
gasPrice = big.NewInt(3e9) // 3Gwei
|
||||
)
|
||||
backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
|
||||
var (
|
||||
@@ -1854,16 +1851,16 @@ func TestRPCGetTransactionReceipt(t *testing.T) {
|
||||
txHashes[i] = tx.Hash()
|
||||
}
|
||||
})
|
||||
api := NewTransactionAPI(backend, new(AddrLocker))
|
||||
blockHashes := make([]common.Hash, genBlocks+1)
|
||||
ctx := context.Background()
|
||||
for i := 0; i <= genBlocks; i++ {
|
||||
header, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(i))
|
||||
if err != nil {
|
||||
t.Errorf("failed to get block: %d err: %v", i, err)
|
||||
}
|
||||
blockHashes[i] = header.Hash()
|
||||
}
|
||||
return backend, txHashes
|
||||
}
|
||||
|
||||
func TestRPCGetTransactionReceipt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
backend, txHashes = setupReceiptBackend(t, 5)
|
||||
api = NewTransactionAPI(backend, new(AddrLocker))
|
||||
)
|
||||
|
||||
var testSuite = []struct {
|
||||
txHash common.Hash
|
||||
@@ -2016,3 +2013,102 @@ func TestRPCGetTransactionReceipt(t *testing.T) {
|
||||
require.JSONEqf(t, want, have, "test %d: json not match, want: %s, have: %s", i, want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRPCGetBlockReceipts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
genBlocks = 5
|
||||
backend, _ = setupReceiptBackend(t, genBlocks)
|
||||
api = NewBlockChainAPI(backend)
|
||||
)
|
||||
blockHashes := make([]common.Hash, genBlocks+1)
|
||||
ctx := context.Background()
|
||||
for i := 0; i <= genBlocks; i++ {
|
||||
header, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(i))
|
||||
if err != nil {
|
||||
t.Errorf("failed to get block: %d err: %v", i, err)
|
||||
}
|
||||
blockHashes[i] = header.Hash()
|
||||
}
|
||||
|
||||
var testSuite = []struct {
|
||||
test rpc.BlockNumberOrHash
|
||||
want string
|
||||
}{
|
||||
// 0. block without any txs(hash)
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithHash(blockHashes[0], false),
|
||||
want: `[]`,
|
||||
},
|
||||
// 1. block without any txs(number)
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithNumber(0),
|
||||
want: `[]`,
|
||||
},
|
||||
// 2. earliest tag
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithNumber(rpc.EarliestBlockNumber),
|
||||
want: `[]`,
|
||||
},
|
||||
// 3. latest tag
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber),
|
||||
want: `[{"blockHash":"0xde2f10e5c44cb6158aa7fbc70e98da70f51ee72f29c7a28fc30bf37e992655f7","blockNumber":"0x5","contractAddress":"0xfdaa97661a584d977b4d3abb5370766ff5b86a18","cumulativeGasUsed":"0xe01a","effectiveGasPrice":"0xb2d05e00","from":"0x703c4b2bd70c169f5717101caee543299fc946c7","gasUsed":"0xe01a","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":null,"transactionHash":"0x1b420e5f43f9620364d175d798acbd61d6f76ed8ea8ed7e0f93e4332ab8399b2","transactionIndex":"0x0","type":"0x1"}]`,
|
||||
},
|
||||
// 4. block with legacy transfer tx(hash)
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithHash(blockHashes[1], false),
|
||||
want: `[{"blockHash":"0xcf5e82a62028debbeecba9a6a7cbeaed67b431800a4250a60943101a611d179a","blockNumber":"0x1","contractAddress":null,"cumulativeGasUsed":"0x5208","effectiveGasPrice":"0xb2d05e00","from":"0x703c4b2bd70c169f5717101caee543299fc946c7","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e","transactionHash":"0xf875edfe4579e2a5e1ad45c4d802c1e5abbca19561398e9c58c41dabd86a3aa6","transactionIndex":"0x0","type":"0x0"}]`,
|
||||
},
|
||||
// 5. block with contract create tx(number)
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(2)),
|
||||
want: `[{"blockHash":"0x739ed5e516e6b2b0fa16f22a335a371a1fac616bc394b7454fdf07a0fd10db30","blockNumber":"0x2","contractAddress":"0xae9bea628c4ce503dcfd7e305cab4e29e7476592","cumulativeGasUsed":"0xcf4e","effectiveGasPrice":"0xb2d05e00","from":"0x703c4b2bd70c169f5717101caee543299fc946c7","gasUsed":"0xcf4e","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":null,"transactionHash":"0xd7a2d56946b13872c0064d0af803fa7b2a7f6be74023cf9ce6337c0dc5b06813","transactionIndex":"0x0","type":"0x0"}]`,
|
||||
},
|
||||
// 6. block with legacy contract call tx(hash)
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithHash(blockHashes[3], false),
|
||||
want: `[{"blockHash":"0x07545649b5df1cd84de57265fa5acdb473a1a033bf51c43d61c6183b13487f19","blockNumber":"0x3","contractAddress":null,"cumulativeGasUsed":"0x5e28","effectiveGasPrice":"0xb2d05e00","from":"0x703c4b2bd70c169f5717101caee543299fc946c7","gasUsed":"0x5e28","logs":[{"address":"0x0000000000000000000000000000000000031ec7","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000703c4b2bd70c169f5717101caee543299fc946c7","0x0000000000000000000000000000000000000000000000000000000000000003"],"data":"0x000000000000000000000000000000000000000000000000000000000000000d","blockNumber":"0x3","transactionHash":"0x2c660ba194f0e2de5bbb4e2f38a15fe9f263dd5e5c524d45be4834755b2c2a8c","transactionIndex":"0x0","blockHash":"0x07545649b5df1cd84de57265fa5acdb473a1a033bf51c43d61c6183b13487f19","logIndex":"0x0","removed":false}],"logsBloom":"0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000800000000000000008000000000000000000000000000000000020000000080000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000400000000002000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000","status":"0x1","to":"0x0000000000000000000000000000000000031ec7","transactionHash":"0x2c660ba194f0e2de5bbb4e2f38a15fe9f263dd5e5c524d45be4834755b2c2a8c","transactionIndex":"0x0","type":"0x0"}]`,
|
||||
},
|
||||
// 7. block with dynamic fee tx(number)
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(4)),
|
||||
want: `[{"blockHash":"0x2a78cbbc361402d352baf99afcd8d57b34df182ed44819ca6bf0a1ceedc94a1e","blockNumber":"0x4","contractAddress":null,"cumulativeGasUsed":"0x538d","effectiveGasPrice":"0xb2d05e00","from":"0x703c4b2bd70c169f5717101caee543299fc946c7","gasUsed":"0x538d","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x0","to":"0x0000000000000000000000000000000000031ec7","transactionHash":"0x395e5326a196a53e961b8f406b706049288d8f92e467f95677ced981fa3a40ce","transactionIndex":"0x0","type":"0x2"}]`,
|
||||
},
|
||||
// 8. block is empty
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithHash(common.Hash{}, false),
|
||||
want: `null`,
|
||||
},
|
||||
// 9. block is not found
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithHash(common.HexToHash("deadbeef"), false),
|
||||
want: `null`,
|
||||
},
|
||||
// 10. block is not found
|
||||
{
|
||||
test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(genBlocks + 1)),
|
||||
want: `null`,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range testSuite {
|
||||
var (
|
||||
result interface{}
|
||||
err error
|
||||
)
|
||||
result, err = api.GetBlockReceipts(context.Background(), tt.test)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: want no error, have %v", i, err)
|
||||
continue
|
||||
}
|
||||
data, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: json marshal error", i)
|
||||
continue
|
||||
}
|
||||
want, have := tt.want, string(data)
|
||||
require.JSONEqf(t, want, have, "test %d: json not match, want: %s, have: %s", i, want, have)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -611,6 +609,11 @@ web3._extend({
|
||||
params: 4,
|
||||
inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null],
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'getBlockReceipts',
|
||||
call: 'eth_getBlockReceipts',
|
||||
params: 1,
|
||||
}),
|
||||
],
|
||||
properties: [
|
||||
new web3._extend.Property({
|
||||
@@ -797,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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user