Geth v1.9.11 #17
@ -93,7 +93,7 @@ jobs:
|
||||
- python-paramiko
|
||||
script:
|
||||
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||
- go run build/ci.go debsrc -goversion 1.13.6 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
- go run build/ci.go debsrc -goversion 1.13.8 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
|
||||
# This builder does the Linux Azure uploads
|
||||
- stage: build
|
||||
@ -183,7 +183,7 @@ jobs:
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
before_install:
|
||||
- curl https://dl.google.com/go/go1.13.6.linux-amd64.tar.gz | tar -xz
|
||||
- curl https://dl.google.com/go/go1.13.8.linux-amd64.tar.gz | tar -xz
|
||||
- export PATH=`pwd`/go/bin:$PATH
|
||||
- export GOROOT=`pwd`/go
|
||||
- export GOPATH=$HOME/go
|
||||
|
@ -39,7 +39,7 @@ directory.
|
||||
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
||||
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
||||
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). |
|
||||
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
||||
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
||||
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
|
||||
|
@ -127,15 +127,28 @@ func (b *SimulatedBackend) rollback() {
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||
}
|
||||
|
||||
// stateByBlockNumber retrieves a state by a given blocknumber.
|
||||
func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) {
|
||||
if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) == 0 {
|
||||
return b.blockchain.State()
|
||||
}
|
||||
block, err := b.BlockByNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.blockchain.StateAt(block.Hash())
|
||||
}
|
||||
|
||||
// CodeAt returns the code associated with a certain account in the blockchain.
|
||||
func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
return statedb.GetCode(contract), nil
|
||||
}
|
||||
|
||||
@ -144,10 +157,11 @@ func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Addres
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
return statedb.GetBalance(contract), nil
|
||||
}
|
||||
|
||||
@ -156,10 +170,11 @@ func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address,
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return 0, errBlockNumberUnsupported
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
return statedb.GetNonce(contract), nil
|
||||
}
|
||||
|
||||
@ -168,10 +183,11 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
statedb, err := b.stateByBlockNumber(ctx, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
val := statedb.GetState(contract, key)
|
||||
return val[:], nil
|
||||
}
|
||||
|
@ -141,6 +141,11 @@ func (am *Manager) Wallets() []Wallet {
|
||||
am.lock.RLock()
|
||||
defer am.lock.RUnlock()
|
||||
|
||||
return am.walletsNoLock()
|
||||
}
|
||||
|
||||
// walletsNoLock returns all registered wallets. Callers must hold am.lock.
|
||||
func (am *Manager) walletsNoLock() []Wallet {
|
||||
cpy := make([]Wallet, len(am.wallets))
|
||||
copy(cpy, am.wallets)
|
||||
return cpy
|
||||
@ -155,7 +160,7 @@ func (am *Manager) Wallet(url string) (Wallet, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, wallet := range am.Wallets() {
|
||||
for _, wallet := range am.walletsNoLock() {
|
||||
if wallet.URL() == parsed {
|
||||
return wallet, nil
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ environment:
|
||||
install:
|
||||
- git submodule update --init
|
||||
- rmdir C:\go /s /q
|
||||
- appveyor DownloadFile https://dl.google.com/go/go1.13.6.windows-%GETH_ARCH%.zip
|
||||
- 7z x go1.13.6.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||
- appveyor DownloadFile https://dl.google.com/go/go1.13.8.windows-%GETH_ARCH%.zip
|
||||
- 7z x go1.13.8.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||
- go version
|
||||
- gcc --version
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# This file contains sha256 checksums of optional build dependencies.
|
||||
|
||||
aae5be954bdc40bcf8006eb77e8d8a5dde412722bc8effcdaf9772620d06420c go1.13.6.src.tar.gz
|
||||
b13bf04633d4d8cf53226ebeaace8d4d2fd07ae6fa676d0844a688339debec34 go1.13.8.src.tar.gz
|
||||
|
||||
478994633b0f5121a7a8d4f368078093e21014fdc7fb2c0ceeae63668c13c5b6 golangci-lint-1.22.2-freebsd-amd64.tar.gz
|
||||
fcf80824c21567eb0871055711bf9bdca91cf9a081122e2a45f1d11fed754600 golangci-lint-1.22.2-darwin-amd64.tar.gz
|
||||
|
@ -171,7 +171,7 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e
|
||||
}
|
||||
|
||||
prevRecords, exists := existing[path]
|
||||
prevValue := combineTXT(prevRecords.values)
|
||||
prevValue := strings.Join(prevRecords.values, "")
|
||||
if !exists {
|
||||
// Entry is unknown, push a new one
|
||||
log.Info(fmt.Sprintf("Creating %s = %q", path, val))
|
||||
@ -191,8 +191,8 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e
|
||||
continue
|
||||
}
|
||||
// Stale entry, nuke it.
|
||||
log.Info(fmt.Sprintf("Deleting %s = %q", path, combineTXT(set.values)))
|
||||
changes = append(changes, newTXTChange("DELETE", path, set.ttl, set.values))
|
||||
log.Info(fmt.Sprintf("Deleting %s = %q", path, strings.Join(set.values, "")))
|
||||
changes = append(changes, newTXTChange("DELETE", path, set.ttl, set.values...))
|
||||
}
|
||||
|
||||
sortChanges(changes)
|
||||
@ -263,7 +263,7 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error
|
||||
}
|
||||
|
||||
// newTXTChange creates a change to a TXT record.
|
||||
func newTXTChange(action, name string, ttl int64, values []string) *route53.Change {
|
||||
func newTXTChange(action, name string, ttl int64, values ...string) *route53.Change {
|
||||
var c route53.Change
|
||||
var r route53.ResourceRecordSet
|
||||
var rrs []*route53.ResourceRecord
|
||||
@ -288,28 +288,16 @@ func isSubdomain(name, domain string) bool {
|
||||
return strings.HasSuffix("."+name, "."+domain)
|
||||
}
|
||||
|
||||
// combineTXT concatenates the given quoted strings into a single unquoted string.
|
||||
func combineTXT(values []string) string {
|
||||
result := ""
|
||||
for _, v := range values {
|
||||
if v[0] == '"' {
|
||||
v = v[1 : len(v)-1]
|
||||
}
|
||||
result += v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// splitTXT splits value into a list of quoted 255-character strings.
|
||||
func splitTXT(value string) []string {
|
||||
var result []string
|
||||
func splitTXT(value string) string {
|
||||
var result strings.Builder
|
||||
for len(value) > 0 {
|
||||
rlen := len(value)
|
||||
if rlen > 253 {
|
||||
rlen = 253
|
||||
}
|
||||
result = append(result, strconv.Quote(value[:rlen]))
|
||||
result.WriteString(strconv.Quote(value[:rlen]))
|
||||
value = value[rlen:]
|
||||
}
|
||||
return result
|
||||
return result.String()
|
||||
}
|
||||
|
@ -28,8 +28,7 @@ import (
|
||||
func TestRoute53ChangeSort(t *testing.T) {
|
||||
testTree0 := map[string]recordSet{
|
||||
"2kfjogvxdqtxxugbh7gs7naaai.n": {ttl: 3333, values: []string{
|
||||
`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-"`,
|
||||
`"vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`,
|
||||
`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-""vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`,
|
||||
}},
|
||||
"fdxn3sn67na5dka4j2gok7bvqi.n": {ttl: treeNodeTTL, values: []string{`"enrtree-branch:"`}},
|
||||
"n": {ttl: rootTTL, values: []string{`"enrtree-root:v1 e=2KFJOGVXDQTXXUGBH7GS7NAAAI l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=0 sig=v_-J_q_9ICQg5ztExFvLQhDBGMb0lZPJLhe3ts9LAcgqhOhtT3YFJsl8BWNDSwGtamUdR-9xl88_w-X42SVpjwE"`}},
|
||||
@ -116,8 +115,7 @@ func TestRoute53ChangeSort(t *testing.T) {
|
||||
ResourceRecordSet: &route53.ResourceRecordSet{
|
||||
Name: sp("2kfjogvxdqtxxugbh7gs7naaai.n"),
|
||||
ResourceRecords: []*route53.ResourceRecord{
|
||||
{Value: sp(`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-"`)},
|
||||
{Value: sp(`"vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`)},
|
||||
{Value: sp(`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-""vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`)},
|
||||
},
|
||||
TTL: ip(3333),
|
||||
Type: sp("TXT"),
|
||||
|
@ -52,6 +52,10 @@ If you want to encrypt an existing private key, it can be specified by setting
|
||||
Name: "privatekey",
|
||||
Usage: "file containing a raw private key to encrypt",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "lightkdf",
|
||||
Usage: "use less secure scrypt parameters",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
// Check if keyfile path given and make sure it doesn't already exist.
|
||||
@ -91,7 +95,11 @@ If you want to encrypt an existing private key, it can be specified by setting
|
||||
|
||||
// Encrypt key with passphrase.
|
||||
passphrase := promptPassphrase(true)
|
||||
keyjson, err := keystore.EncryptKey(key, passphrase, keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
scryptN, scryptP := keystore.StandardScryptN, keystore.StandardScryptP
|
||||
if ctx.Bool("lightkdf") {
|
||||
scryptN, scryptP = keystore.LightScryptN, keystore.LightScryptP
|
||||
}
|
||||
keyjson, err := keystore.EncryptKey(key, passphrase, scryptN, scryptP)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error encrypting key: %v", err)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func TestMessageSignVerify(t *testing.T) {
|
||||
message := "test message"
|
||||
|
||||
// Create the key.
|
||||
generate := runEthkey(t, "generate", keyfile)
|
||||
generate := runEthkey(t, "generate", "--lightkdf", keyfile)
|
||||
generate.Expect(`
|
||||
!! Unsupported terminal, password will be echoed.
|
||||
Password: {{.InputLine "foobar"}}
|
||||
|
@ -360,11 +360,14 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
// Send over the initial stats and the latest header
|
||||
f.lock.RLock()
|
||||
reqs := f.reqs
|
||||
f.lock.RUnlock()
|
||||
if err = send(conn, map[string]interface{}{
|
||||
"funds": new(big.Int).Div(balance, ether),
|
||||
"funded": nonce,
|
||||
"peers": f.stack.Server().PeerCount(),
|
||||
"requests": f.reqs,
|
||||
"requests": reqs,
|
||||
}, 3*time.Second); err != nil {
|
||||
log.Warn("Failed to send initial stats to client", "err", err)
|
||||
return
|
||||
|
@ -56,6 +56,18 @@ This is a destructive action and changes the network in which you will be
|
||||
participating.
|
||||
|
||||
It expects the genesis file as argument.`,
|
||||
}
|
||||
dumpGenesisCommand = cli.Command{
|
||||
Action: utils.MigrateFlags(dumpGenesis),
|
||||
Name: "dumpgenesis",
|
||||
Usage: "Dumps genesis block JSON configuration to stdout",
|
||||
ArgsUsage: "",
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
The dumpgenesis command dumps the genesis block configuration in JSON format to stdout.`,
|
||||
}
|
||||
importCommand = cli.Command{
|
||||
Action: utils.MigrateFlags(importChain),
|
||||
@ -227,6 +239,17 @@ func initGenesis(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpGenesis(ctx *cli.Context) error {
|
||||
genesis := utils.MakeGenesis(ctx)
|
||||
if genesis == nil {
|
||||
genesis = core.DefaultGenesisBlock()
|
||||
}
|
||||
if err := json.NewEncoder(os.Stdout).Encode(genesis); err != nil {
|
||||
utils.Fatalf("could not encode genesis")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func importChain(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) < 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
|
@ -155,6 +155,11 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
}
|
||||
utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
|
||||
cfg.Eth.StateDiff = true
|
||||
utils.RegisterStateDiffService(stack, ctx)
|
||||
}
|
||||
|
||||
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
|
||||
shhEnabled := enableWhisper(ctx)
|
||||
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
|
||||
|
@ -131,6 +131,7 @@ var (
|
||||
utils.NetrestrictFlag,
|
||||
utils.NodeKeyFileFlag,
|
||||
utils.NodeKeyHexFlag,
|
||||
utils.DNSDiscoveryFlag,
|
||||
utils.DeveloperFlag,
|
||||
utils.DeveloperPeriodFlag,
|
||||
utils.TestnetFlag,
|
||||
@ -145,6 +146,11 @@ var (
|
||||
utils.GpoPercentileFlag,
|
||||
utils.EWASMInterpreterFlag,
|
||||
utils.EVMInterpreterFlag,
|
||||
utils.StateDiffFlag,
|
||||
utils.StateDiffPathsAndProofs,
|
||||
utils.StateDiffIntermediateNodes,
|
||||
utils.StateDiffStreamBlock,
|
||||
utils.StateDiffWatchedAddresses,
|
||||
configFileFlag,
|
||||
}
|
||||
|
||||
@ -205,6 +211,7 @@ func init() {
|
||||
copydbCommand,
|
||||
removedbCommand,
|
||||
dumpCommand,
|
||||
dumpGenesisCommand,
|
||||
inspectCommand,
|
||||
// See accountcmd.go:
|
||||
accountCommand,
|
||||
|
@ -80,6 +80,7 @@ type RetestethEthAPI interface {
|
||||
SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error)
|
||||
BlockNumber(ctx context.Context) (uint64, error)
|
||||
GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error)
|
||||
GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error)
|
||||
GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error)
|
||||
GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error)
|
||||
GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error)
|
||||
@ -618,6 +619,20 @@ func (api *RetestethAPI) GetBlockByNumber(ctx context.Context, blockNr math.HexO
|
||||
return nil, fmt.Errorf("block %d not found", blockNr)
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
||||
block := api.blockchain.GetBlockByHash(blockHash)
|
||||
if block != nil {
|
||||
response, err := RPCMarshalBlock(block, true, fullTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response["author"] = response["miner"]
|
||||
response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), block.Number().Uint64()))
|
||||
return response, err
|
||||
}
|
||||
return nil, fmt.Errorf("block 0x%x not found", blockHash)
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) AccountRange(ctx context.Context,
|
||||
blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
|
||||
addressHash *math.HexOrDecimal256, maxResults uint64,
|
||||
|
@ -182,6 +182,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.BootnodesFlag,
|
||||
utils.BootnodesV4Flag,
|
||||
utils.BootnodesV5Flag,
|
||||
utils.DNSDiscoveryFlag,
|
||||
utils.ListenPortFlag,
|
||||
utils.MaxPeersFlag,
|
||||
utils.MaxPendingPeersFlag,
|
||||
@ -250,6 +251,16 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.MinerLegacyExtraDataFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "STATE DIFF",
|
||||
Flags: []cli.Flag{
|
||||
utils.StateDiffFlag,
|
||||
utils.StateDiffPathsAndProofs,
|
||||
utils.StateDiffIntermediateNodes,
|
||||
utils.StateDiffStreamBlock,
|
||||
utils.StateDiffWatchedAddresses,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "MISC",
|
||||
},
|
||||
|
@ -32,6 +32,8 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -61,9 +63,10 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/statediff"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
|
||||
pcsclite "github.com/gballet/go-libpcsclite"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -658,6 +661,10 @@ var (
|
||||
Name: "netrestrict",
|
||||
Usage: "Restricts network communication to the given IP networks (CIDR masks)",
|
||||
}
|
||||
DNSDiscoveryFlag = cli.StringFlag{
|
||||
Name: "discovery.dns",
|
||||
Usage: "Sets DNS discovery entry points (use \"\" to disable DNS)",
|
||||
}
|
||||
|
||||
// ATM the url is left to the user and deployment to
|
||||
JSpathFlag = cli.StringFlag{
|
||||
@ -749,6 +756,27 @@ var (
|
||||
Usage: "External EVM configuration (default = built-in interpreter)",
|
||||
Value: "",
|
||||
}
|
||||
|
||||
StateDiffFlag = cli.BoolFlag{
|
||||
Name: "statediff",
|
||||
Usage: "Enables the processing of state diffs between each block",
|
||||
}
|
||||
StateDiffPathsAndProofs = cli.BoolFlag{
|
||||
Name: "statediff.pathsandproofs",
|
||||
Usage: "Set to true to generate paths and proof sets for diffed state and storage trie leaf nodes",
|
||||
}
|
||||
StateDiffIntermediateNodes = cli.BoolFlag{
|
||||
Name: "statediff.intermediatenodes",
|
||||
Usage: "Set to include intermediate (branch and extension) nodes; default (false) processes leaf nodes only",
|
||||
}
|
||||
StateDiffStreamBlock = cli.BoolFlag{
|
||||
Name: "statediff.streamblock",
|
||||
Usage: "Set to true to stream the block data alongside state diff data in the same subscription payload",
|
||||
}
|
||||
StateDiffWatchedAddresses = cli.StringSliceFlag{
|
||||
Name: "statediff.watchedaddresses",
|
||||
Usage: "If provided, state diffing process is restricted to these addresses",
|
||||
}
|
||||
)
|
||||
|
||||
// MakeDataDir retrieves the currently requested data directory, terminating
|
||||
@ -811,9 +839,9 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
|
||||
switch {
|
||||
case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(BootnodesV4Flag.Name):
|
||||
if ctx.GlobalIsSet(BootnodesV4Flag.Name) {
|
||||
urls = strings.Split(ctx.GlobalString(BootnodesV4Flag.Name), ",")
|
||||
urls = splitAndTrim(ctx.GlobalString(BootnodesV4Flag.Name))
|
||||
} else {
|
||||
urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",")
|
||||
urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name))
|
||||
}
|
||||
case ctx.GlobalBool(TestnetFlag.Name):
|
||||
urls = params.TestnetBootnodes
|
||||
@ -845,9 +873,9 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) {
|
||||
switch {
|
||||
case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(BootnodesV5Flag.Name):
|
||||
if ctx.GlobalIsSet(BootnodesV5Flag.Name) {
|
||||
urls = strings.Split(ctx.GlobalString(BootnodesV5Flag.Name), ",")
|
||||
urls = splitAndTrim(ctx.GlobalString(BootnodesV5Flag.Name))
|
||||
} else {
|
||||
urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",")
|
||||
urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name))
|
||||
}
|
||||
case ctx.GlobalBool(RinkebyFlag.Name):
|
||||
urls = params.RinkebyBootnodes
|
||||
@ -958,6 +986,9 @@ func setWS(ctx *cli.Context, cfg *node.Config) {
|
||||
if ctx.GlobalIsSet(WSApiFlag.Name) {
|
||||
cfg.WSModules = splitAndTrim(ctx.GlobalString(WSApiFlag.Name))
|
||||
}
|
||||
if ctx.GlobalBool(StateDiffFlag.Name) {
|
||||
cfg.WSModules = append(cfg.WSModules, "statediff")
|
||||
}
|
||||
}
|
||||
|
||||
// setIPC creates an IPC path configuration from the set command line flags,
|
||||
@ -1477,6 +1508,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
if ctx.GlobalIsSet(RPCGlobalGasCap.Name) {
|
||||
cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name))
|
||||
}
|
||||
if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) {
|
||||
urls := ctx.GlobalString(DNSDiscoveryFlag.Name)
|
||||
if urls == "" {
|
||||
cfg.DiscoveryURLs = []string{}
|
||||
} else {
|
||||
cfg.DiscoveryURLs = splitAndTrim(urls)
|
||||
}
|
||||
}
|
||||
|
||||
// Override any default configs for hard coded networks.
|
||||
switch {
|
||||
@ -1485,16 +1524,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
cfg.NetworkId = 3
|
||||
}
|
||||
cfg.Genesis = core.DefaultTestnetGenesisBlock()
|
||||
setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.TestnetGenesisHash])
|
||||
case ctx.GlobalBool(RinkebyFlag.Name):
|
||||
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
||||
cfg.NetworkId = 4
|
||||
}
|
||||
cfg.Genesis = core.DefaultRinkebyGenesisBlock()
|
||||
setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.RinkebyGenesisHash])
|
||||
case ctx.GlobalBool(GoerliFlag.Name):
|
||||
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
||||
cfg.NetworkId = 5
|
||||
}
|
||||
cfg.Genesis = core.DefaultGoerliGenesisBlock()
|
||||
setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.GoerliGenesisHash])
|
||||
case ctx.GlobalBool(DeveloperFlag.Name):
|
||||
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
||||
cfg.NetworkId = 1337
|
||||
@ -1521,9 +1563,22 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
|
||||
cfg.Miner.GasPrice = big.NewInt(1)
|
||||
}
|
||||
default:
|
||||
if cfg.NetworkId == 1 {
|
||||
setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.MainnetGenesisHash])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setDNSDiscoveryDefaults configures DNS discovery with the given URL if
|
||||
// no URLs are set.
|
||||
func setDNSDiscoveryDefaults(cfg *eth.Config, url string) {
|
||||
if cfg.DiscoveryURLs != nil {
|
||||
return
|
||||
}
|
||||
cfg.DiscoveryURLs = []string{url}
|
||||
}
|
||||
|
||||
// RegisterEthService adds an Ethereum client to the stack.
|
||||
func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
||||
var err error
|
||||
@ -1593,6 +1648,25 @@ func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []st
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterStateDiffService configures and registers a service to stream state diff data over RPC
|
||||
func RegisterStateDiffService(stack *node.Node, ctx *cli.Context) {
|
||||
config := statediff.Config{
|
||||
PathsAndProofs: ctx.GlobalBool(StateDiffPathsAndProofs.Name),
|
||||
IntermediateNodes: ctx.GlobalBool(StateDiffIntermediateNodes.Name),
|
||||
StreamBlock: ctx.GlobalBool(StateDiffStreamBlock.Name),
|
||||
WatchedAddresses: ctx.GlobalStringSlice(StateDiffWatchedAddresses.Name),
|
||||
}
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var ethServ *eth.Ethereum
|
||||
ctx.Service(ðServ)
|
||||
chainDb := ethServ.ChainDb()
|
||||
blockChain := ethServ.BlockChain()
|
||||
return statediff.NewStateDiffService(chainDb, blockChain, config)
|
||||
}); err != nil {
|
||||
Fatalf("Failed to register State Diff Service", err)
|
||||
}
|
||||
}
|
||||
|
||||
func SetupMetrics(ctx *cli.Context) {
|
||||
if metrics.Enabled {
|
||||
log.Info("Enabling metrics collection")
|
||||
|
@ -31,44 +31,93 @@ func Now() AbsTime {
|
||||
return AbsTime(monotime.Now())
|
||||
}
|
||||
|
||||
// Add returns t + d.
|
||||
// Add returns t + d as absolute time.
|
||||
func (t AbsTime) Add(d time.Duration) AbsTime {
|
||||
return t + AbsTime(d)
|
||||
}
|
||||
|
||||
// Sub returns t - t2 as a duration.
|
||||
func (t AbsTime) Sub(t2 AbsTime) time.Duration {
|
||||
return time.Duration(t - t2)
|
||||
}
|
||||
|
||||
// The Clock interface makes it possible to replace the monotonic system clock with
|
||||
// a simulated clock.
|
||||
type Clock interface {
|
||||
Now() AbsTime
|
||||
Sleep(time.Duration)
|
||||
After(time.Duration) <-chan time.Time
|
||||
NewTimer(time.Duration) ChanTimer
|
||||
After(time.Duration) <-chan AbsTime
|
||||
AfterFunc(d time.Duration, f func()) Timer
|
||||
}
|
||||
|
||||
// Timer represents a cancellable event returned by AfterFunc
|
||||
// Timer is a cancellable event created by AfterFunc.
|
||||
type Timer interface {
|
||||
// Stop cancels the timer. It returns false if the timer has already
|
||||
// expired or been stopped.
|
||||
Stop() bool
|
||||
}
|
||||
|
||||
// ChanTimer is a cancellable event created by NewTimer.
|
||||
type ChanTimer interface {
|
||||
Timer
|
||||
|
||||
// The channel returned by C receives a value when the timer expires.
|
||||
C() <-chan AbsTime
|
||||
// Reset reschedules the timer with a new timeout.
|
||||
// It should be invoked only on stopped or expired timers with drained channels.
|
||||
Reset(time.Duration)
|
||||
}
|
||||
|
||||
// System implements Clock using the system clock.
|
||||
type System struct{}
|
||||
|
||||
// Now returns the current monotonic time.
|
||||
func (System) Now() AbsTime {
|
||||
func (c System) Now() AbsTime {
|
||||
return AbsTime(monotime.Now())
|
||||
}
|
||||
|
||||
// Sleep blocks for the given duration.
|
||||
func (System) Sleep(d time.Duration) {
|
||||
func (c System) Sleep(d time.Duration) {
|
||||
time.Sleep(d)
|
||||
}
|
||||
|
||||
// NewTimer creates a timer which can be rescheduled.
|
||||
func (c System) NewTimer(d time.Duration) ChanTimer {
|
||||
ch := make(chan AbsTime, 1)
|
||||
t := time.AfterFunc(d, func() {
|
||||
// This send is non-blocking because that's how time.Timer
|
||||
// behaves. It doesn't matter in the happy case, but does
|
||||
// when Reset is misused.
|
||||
select {
|
||||
case ch <- c.Now():
|
||||
default:
|
||||
}
|
||||
})
|
||||
return &systemTimer{t, ch}
|
||||
}
|
||||
|
||||
// After returns a channel which receives the current time after d has elapsed.
|
||||
func (System) After(d time.Duration) <-chan time.Time {
|
||||
return time.After(d)
|
||||
func (c System) After(d time.Duration) <-chan AbsTime {
|
||||
ch := make(chan AbsTime, 1)
|
||||
time.AfterFunc(d, func() { ch <- c.Now() })
|
||||
return ch
|
||||
}
|
||||
|
||||
// AfterFunc runs f on a new goroutine after the duration has elapsed.
|
||||
func (System) AfterFunc(d time.Duration, f func()) Timer {
|
||||
func (c System) AfterFunc(d time.Duration, f func()) Timer {
|
||||
return time.AfterFunc(d, f)
|
||||
}
|
||||
|
||||
type systemTimer struct {
|
||||
*time.Timer
|
||||
ch <-chan AbsTime
|
||||
}
|
||||
|
||||
func (st *systemTimer) Reset(d time.Duration) {
|
||||
st.Timer.Reset(d)
|
||||
}
|
||||
|
||||
func (st *systemTimer) C() <-chan AbsTime {
|
||||
return st.ch
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package mclock
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -32,18 +33,24 @@ import (
|
||||
// the timeout using a channel or semaphore.
|
||||
type Simulated struct {
|
||||
now AbsTime
|
||||
scheduled []*simTimer
|
||||
scheduled simTimerHeap
|
||||
mu sync.RWMutex
|
||||
cond *sync.Cond
|
||||
lastId uint64
|
||||
}
|
||||
|
||||
// simTimer implements Timer on the virtual clock.
|
||||
// simTimer implements ChanTimer on the virtual clock.
|
||||
type simTimer struct {
|
||||
do func()
|
||||
at AbsTime
|
||||
id uint64
|
||||
s *Simulated
|
||||
at AbsTime
|
||||
index int // position in s.scheduled
|
||||
s *Simulated
|
||||
do func()
|
||||
ch <-chan AbsTime
|
||||
}
|
||||
|
||||
func (s *Simulated) init() {
|
||||
if s.cond == nil {
|
||||
s.cond = sync.NewCond(&s.mu)
|
||||
}
|
||||
}
|
||||
|
||||
// Run moves the clock by the given duration, executing all timers before that duration.
|
||||
@ -53,14 +60,9 @@ func (s *Simulated) Run(d time.Duration) {
|
||||
|
||||
end := s.now + AbsTime(d)
|
||||
var do []func()
|
||||
for len(s.scheduled) > 0 {
|
||||
ev := s.scheduled[0]
|
||||
if ev.at > end {
|
||||
break
|
||||
}
|
||||
s.now = ev.at
|
||||
for len(s.scheduled) > 0 && s.scheduled[0].at <= end {
|
||||
ev := heap.Pop(&s.scheduled).(*simTimer)
|
||||
do = append(do, ev.do)
|
||||
s.scheduled = s.scheduled[1:]
|
||||
}
|
||||
s.now = end
|
||||
s.mu.Unlock()
|
||||
@ -102,14 +104,22 @@ func (s *Simulated) Sleep(d time.Duration) {
|
||||
<-s.After(d)
|
||||
}
|
||||
|
||||
// NewTimer creates a timer which fires when the clock has advanced by d.
|
||||
func (s *Simulated) NewTimer(d time.Duration) ChanTimer {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
ch := make(chan AbsTime, 1)
|
||||
var timer *simTimer
|
||||
timer = s.schedule(d, func() { ch <- timer.at })
|
||||
timer.ch = ch
|
||||
return timer
|
||||
}
|
||||
|
||||
// After returns a channel which receives the current time after the clock
|
||||
// has advanced by d.
|
||||
func (s *Simulated) After(d time.Duration) <-chan time.Time {
|
||||
after := make(chan time.Time, 1)
|
||||
s.AfterFunc(d, func() {
|
||||
after <- (time.Time{}).Add(time.Duration(s.now))
|
||||
})
|
||||
return after
|
||||
func (s *Simulated) After(d time.Duration) <-chan AbsTime {
|
||||
return s.NewTimer(d).C()
|
||||
}
|
||||
|
||||
// AfterFunc runs fn after the clock has advanced by d. Unlike with the system
|
||||
@ -117,46 +127,83 @@ func (s *Simulated) After(d time.Duration) <-chan time.Time {
|
||||
func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.schedule(d, fn)
|
||||
}
|
||||
|
||||
func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer {
|
||||
s.init()
|
||||
|
||||
at := s.now + AbsTime(d)
|
||||
s.lastId++
|
||||
id := s.lastId
|
||||
l, h := 0, len(s.scheduled)
|
||||
ll := h
|
||||
for l != h {
|
||||
m := (l + h) / 2
|
||||
if (at < s.scheduled[m].at) || ((at == s.scheduled[m].at) && (id < s.scheduled[m].id)) {
|
||||
h = m
|
||||
} else {
|
||||
l = m + 1
|
||||
}
|
||||
}
|
||||
ev := &simTimer{do: fn, at: at, s: s}
|
||||
s.scheduled = append(s.scheduled, nil)
|
||||
copy(s.scheduled[l+1:], s.scheduled[l:ll])
|
||||
s.scheduled[l] = ev
|
||||
heap.Push(&s.scheduled, ev)
|
||||
s.cond.Broadcast()
|
||||
return ev
|
||||
}
|
||||
|
||||
func (ev *simTimer) Stop() bool {
|
||||
s := ev.s
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
ev.s.mu.Lock()
|
||||
defer ev.s.mu.Unlock()
|
||||
|
||||
for i := 0; i < len(s.scheduled); i++ {
|
||||
if s.scheduled[i] == ev {
|
||||
s.scheduled = append(s.scheduled[:i], s.scheduled[i+1:]...)
|
||||
s.cond.Broadcast()
|
||||
return true
|
||||
}
|
||||
if ev.index < 0 {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
heap.Remove(&ev.s.scheduled, ev.index)
|
||||
ev.s.cond.Broadcast()
|
||||
ev.index = -1
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Simulated) init() {
|
||||
if s.cond == nil {
|
||||
s.cond = sync.NewCond(&s.mu)
|
||||
func (ev *simTimer) Reset(d time.Duration) {
|
||||
if ev.ch == nil {
|
||||
panic("mclock: Reset() on timer created by AfterFunc")
|
||||
}
|
||||
|
||||
ev.s.mu.Lock()
|
||||
defer ev.s.mu.Unlock()
|
||||
ev.at = ev.s.now.Add(d)
|
||||
if ev.index < 0 {
|
||||
heap.Push(&ev.s.scheduled, ev) // already expired
|
||||
} else {
|
||||
heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule
|
||||
}
|
||||
ev.s.cond.Broadcast()
|
||||
}
|
||||
|
||||
func (ev *simTimer) C() <-chan AbsTime {
|
||||
if ev.ch == nil {
|
||||
panic("mclock: C() on timer created by AfterFunc")
|
||||
}
|
||||
return ev.ch
|
||||
}
|
||||
|
||||
type simTimerHeap []*simTimer
|
||||
|
||||
func (h *simTimerHeap) Len() int {
|
||||
return len(*h)
|
||||
}
|
||||
|
||||
func (h *simTimerHeap) Less(i, j int) bool {
|
||||
return (*h)[i].at < (*h)[j].at
|
||||
}
|
||||
|
||||
func (h *simTimerHeap) Swap(i, j int) {
|
||||
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
|
||||
(*h)[i].index = i
|
||||
(*h)[j].index = j
|
||||
}
|
||||
|
||||
func (h *simTimerHeap) Push(x interface{}) {
|
||||
t := x.(*simTimer)
|
||||
t.index = len(*h)
|
||||
*h = append(*h, t)
|
||||
}
|
||||
|
||||
func (h *simTimerHeap) Pop() interface{} {
|
||||
end := len(*h) - 1
|
||||
t := (*h)[end]
|
||||
t.index = -1
|
||||
(*h)[end] = nil
|
||||
*h = (*h)[:end]
|
||||
return t
|
||||
}
|
||||
|
@ -25,14 +25,16 @@ var _ Clock = System{}
|
||||
var _ Clock = new(Simulated)
|
||||
|
||||
func TestSimulatedAfter(t *testing.T) {
|
||||
const timeout = 30 * time.Minute
|
||||
const adv = time.Minute
|
||||
|
||||
var (
|
||||
c Simulated
|
||||
end = c.Now().Add(timeout)
|
||||
ch = c.After(timeout)
|
||||
timeout = 30 * time.Minute
|
||||
offset = 99 * time.Hour
|
||||
adv = 11 * time.Minute
|
||||
c Simulated
|
||||
)
|
||||
c.Run(offset)
|
||||
|
||||
end := c.Now().Add(timeout)
|
||||
ch := c.After(timeout)
|
||||
for c.Now() < end.Add(-adv) {
|
||||
c.Run(adv)
|
||||
select {
|
||||
@ -45,8 +47,8 @@ func TestSimulatedAfter(t *testing.T) {
|
||||
c.Run(adv)
|
||||
select {
|
||||
case stamp := <-ch:
|
||||
want := time.Time{}.Add(timeout)
|
||||
if !stamp.Equal(want) {
|
||||
want := AbsTime(0).Add(offset).Add(timeout)
|
||||
if stamp != want {
|
||||
t.Errorf("Wrong time sent on timer channel: got %v, want %v", stamp, want)
|
||||
}
|
||||
default:
|
||||
@ -94,7 +96,7 @@ func TestSimulatedSleep(t *testing.T) {
|
||||
var (
|
||||
c Simulated
|
||||
timeout = 1 * time.Hour
|
||||
done = make(chan AbsTime)
|
||||
done = make(chan AbsTime, 1)
|
||||
)
|
||||
go func() {
|
||||
c.Sleep(timeout)
|
||||
@ -113,3 +115,48 @@ func TestSimulatedSleep(t *testing.T) {
|
||||
t.Fatal("Sleep didn't return in time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimulatedTimerReset(t *testing.T) {
|
||||
var (
|
||||
c Simulated
|
||||
timeout = 1 * time.Hour
|
||||
)
|
||||
timer := c.NewTimer(timeout)
|
||||
c.Run(2 * timeout)
|
||||
select {
|
||||
case ftime := <-timer.C():
|
||||
if ftime != AbsTime(timeout) {
|
||||
t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(timeout))
|
||||
}
|
||||
default:
|
||||
t.Fatal("timer didn't fire")
|
||||
}
|
||||
|
||||
timer.Reset(timeout)
|
||||
c.Run(2 * timeout)
|
||||
select {
|
||||
case ftime := <-timer.C():
|
||||
if ftime != AbsTime(3*timeout) {
|
||||
t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(3*timeout))
|
||||
}
|
||||
default:
|
||||
t.Fatal("timer didn't fire again")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimulatedTimerStop(t *testing.T) {
|
||||
var (
|
||||
c Simulated
|
||||
timeout = 1 * time.Hour
|
||||
)
|
||||
timer := c.NewTimer(timeout)
|
||||
c.Run(2 * timeout)
|
||||
if timer.Stop() {
|
||||
t.Errorf("Stop returned true for fired timer")
|
||||
}
|
||||
select {
|
||||
case <-timer.C():
|
||||
default:
|
||||
t.Fatal("timer didn't fire")
|
||||
}
|
||||
}
|
||||
|
@ -110,11 +110,12 @@ const (
|
||||
// CacheConfig contains the configuration values for the trie caching/pruning
|
||||
// that's resident in a blockchain.
|
||||
type CacheConfig struct {
|
||||
TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
TrieCleanNoPrefetch bool // Whether to disable heuristic state prefetching for followup blocks
|
||||
TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
|
||||
TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node)
|
||||
TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
|
||||
TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
TrieCleanNoPrefetch bool // Whether to disable heuristic state prefetching for followup blocks
|
||||
TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
|
||||
TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node)
|
||||
TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
|
||||
ProcessingStateDiffs bool // Whether statediffs processing should be taken into a account before a trie is pruned
|
||||
}
|
||||
|
||||
// BlockChain represents the canonical chain given a database with a genesis
|
||||
@ -177,6 +178,8 @@ type BlockChain struct {
|
||||
badBlocks *lru.Cache // Bad block cache
|
||||
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
|
||||
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.
|
||||
|
||||
stateDiffsProcessed map[common.Hash]int
|
||||
}
|
||||
|
||||
// NewBlockChain returns a fully initialised block chain using information
|
||||
@ -197,24 +200,25 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
txLookupCache, _ := lru.New(txLookupCacheLimit)
|
||||
futureBlocks, _ := lru.New(maxFutureBlocks)
|
||||
badBlocks, _ := lru.New(badBlockLimit)
|
||||
|
||||
stateDiffsProcessed := make(map[common.Hash]int)
|
||||
bc := &BlockChain{
|
||||
chainConfig: chainConfig,
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
|
||||
quit: make(chan struct{}),
|
||||
shouldPreserve: shouldPreserve,
|
||||
bodyCache: bodyCache,
|
||||
bodyRLPCache: bodyRLPCache,
|
||||
receiptsCache: receiptsCache,
|
||||
blockCache: blockCache,
|
||||
txLookupCache: txLookupCache,
|
||||
futureBlocks: futureBlocks,
|
||||
engine: engine,
|
||||
vmConfig: vmConfig,
|
||||
badBlocks: badBlocks,
|
||||
chainConfig: chainConfig,
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
|
||||
quit: make(chan struct{}),
|
||||
shouldPreserve: shouldPreserve,
|
||||
bodyCache: bodyCache,
|
||||
bodyRLPCache: bodyRLPCache,
|
||||
receiptsCache: receiptsCache,
|
||||
blockCache: blockCache,
|
||||
txLookupCache: txLookupCache,
|
||||
futureBlocks: futureBlocks,
|
||||
engine: engine,
|
||||
vmConfig: vmConfig,
|
||||
badBlocks: badBlocks,
|
||||
stateDiffsProcessed: stateDiffsProcessed,
|
||||
}
|
||||
bc.validator = NewBlockValidator(chainConfig, bc, engine)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
|
||||
@ -1290,6 +1294,11 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BlockChain) AddToStateDiffProcessedCollection(hash common.Hash) {
|
||||
count := bc.stateDiffsProcessed[hash]
|
||||
bc.stateDiffsProcessed[hash] = count + 1
|
||||
}
|
||||
|
||||
// WriteBlockWithState writes the block and all associated state to the database.
|
||||
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
|
||||
bc.chainmu.Lock()
|
||||
@ -1381,6 +1390,19 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
bc.triegc.Push(root, number)
|
||||
break
|
||||
}
|
||||
if bc.cacheConfig.ProcessingStateDiffs {
|
||||
if !bc.rootAllowedToBeDereferenced(root.(common.Hash)) {
|
||||
bc.triegc.Push(root, number)
|
||||
break
|
||||
} else {
|
||||
log.Debug("Current root found in stateDiffsProcessed collection with a count of 2, okay to dereference",
|
||||
"root", root.(common.Hash).Hex(),
|
||||
"blockNumber", uint64(-number),
|
||||
"size of stateDiffsProcessed", len(bc.stateDiffsProcessed))
|
||||
delete(bc.stateDiffsProcessed, root.(common.Hash))
|
||||
}
|
||||
}
|
||||
log.Debug("Dereferencing", "root", root.(common.Hash).Hex())
|
||||
triedb.Dereference(root.(common.Hash))
|
||||
}
|
||||
}
|
||||
@ -1439,6 +1461,15 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// since we need the state tries of the current block and its parent in-memory
|
||||
// in order to process statediffs, we should avoid dereferencing roots until
|
||||
// its statediff and its child have been processed
|
||||
func (bc *BlockChain) rootAllowedToBeDereferenced(root common.Hash) bool {
|
||||
diffProcessedForSelfAndChildCount := 2
|
||||
count := bc.stateDiffsProcessed[root]
|
||||
return count >= diffProcessedForSelfAndChildCount
|
||||
}
|
||||
|
||||
// addFutureBlock checks if the block is within the max allowed window to get
|
||||
// accepted for future processing, and returns an error if the block is too far
|
||||
// ahead and was not added.
|
||||
@ -1654,18 +1685,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
// If we have a followup block, run that against the current state to pre-cache
|
||||
// transactions and probabilistically some of the account/storage trie nodes.
|
||||
var followupInterrupt uint32
|
||||
|
||||
if !bc.cacheConfig.TrieCleanNoPrefetch {
|
||||
if followup, err := it.peek(); followup != nil && err == nil {
|
||||
go func(start time.Time) {
|
||||
throwaway, _ := state.New(parent.Root, bc.stateCache)
|
||||
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt)
|
||||
throwaway, _ := state.New(parent.Root, bc.stateCache)
|
||||
go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) {
|
||||
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, interrupt)
|
||||
|
||||
blockPrefetchExecuteTimer.Update(time.Since(start))
|
||||
if atomic.LoadUint32(&followupInterrupt) == 1 {
|
||||
if atomic.LoadUint32(interrupt) == 1 {
|
||||
blockPrefetchInterruptMeter.Mark(1)
|
||||
}
|
||||
}(time.Now())
|
||||
}(time.Now(), followup, throwaway, &followupInterrupt)
|
||||
}
|
||||
}
|
||||
// Process block using the parent state as reference point
|
||||
|
@ -2288,6 +2288,90 @@ func TestSideImportPrunedBlocks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessingStateDiffs(t *testing.T) {
|
||||
defaultTrieCleanCache := 256
|
||||
defaultTrieDirtyCache := 256
|
||||
defaultTrieTimeout := 60 * time.Minute
|
||||
cacheConfig := &CacheConfig{
|
||||
TrieDirtyDisabled: false,
|
||||
TrieCleanLimit: defaultTrieCleanCache,
|
||||
TrieDirtyLimit: defaultTrieDirtyCache,
|
||||
TrieTimeLimit: defaultTrieTimeout,
|
||||
ProcessingStateDiffs: true,
|
||||
}
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
genesis := new(Genesis).MustCommit(db)
|
||||
numberOfBlocks := TriesInMemory
|
||||
engine := ethash.NewFaker()
|
||||
blockchain, _ := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil)
|
||||
blocks := makeBlockChain(genesis, numberOfBlocks+1, engine, db, canonicalSeed)
|
||||
_, err := blockchain.InsertChain(blocks)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create pristine chain: %v", err)
|
||||
}
|
||||
defer blockchain.Stop()
|
||||
|
||||
//when adding a root hash to the collection, it will increment the count
|
||||
firstStateRoot := blocks[0].Root()
|
||||
blockchain.AddToStateDiffProcessedCollection(firstStateRoot)
|
||||
value, ok := blockchain.stateDiffsProcessed[firstStateRoot]
|
||||
if !ok {
|
||||
t.Error("state root not found in collection")
|
||||
}
|
||||
if value != 1 {
|
||||
t.Error("state root count not correct", "want", 1, "got", value)
|
||||
}
|
||||
|
||||
blockchain.AddToStateDiffProcessedCollection(firstStateRoot)
|
||||
value, ok = blockchain.stateDiffsProcessed[firstStateRoot]
|
||||
if !ok {
|
||||
t.Error("state root not found in collection")
|
||||
}
|
||||
if value != 2 {
|
||||
t.Error("state root count not correct", "want", 2, "got", value)
|
||||
}
|
||||
|
||||
moreBlocks := makeBlockChain(blocks[len(blocks)-1], 1, engine, db, canonicalSeed)
|
||||
_, err = blockchain.InsertChain(moreBlocks)
|
||||
if err != nil {
|
||||
t.Error("error inserting test chain")
|
||||
}
|
||||
|
||||
//a root hash can be dereferenced when it's state diff and it's child's state diff have been processed
|
||||
//(i.e. it has a count of 2 in stateDiffsProcessed)
|
||||
nodes := blockchain.stateCache.TrieDB().Nodes()
|
||||
if containsRootHash(nodes, firstStateRoot) {
|
||||
t.Errorf("stateRoot %s in nodes, want: %t, got: %t", firstStateRoot.Hex(), false, true)
|
||||
}
|
||||
|
||||
//a root hash should still be in the in-mem db if it's child's state diff hasn't yet been processed
|
||||
//(i.e. it has a count of 1 stateDiffsProcessed)
|
||||
secondStateRoot := blocks[1].Root()
|
||||
blockchain.AddToStateDiffProcessedCollection(secondStateRoot)
|
||||
if !containsRootHash(nodes, secondStateRoot) {
|
||||
t.Errorf("stateRoot %s in nodes, want: %t, got: %t", secondStateRoot.Hex(), true, false)
|
||||
}
|
||||
|
||||
//the stateDiffsProcessed collection is cleaned up once a hash has been dereferenced
|
||||
_, ok = blockchain.stateDiffsProcessed[firstStateRoot]
|
||||
if ok {
|
||||
t.Errorf("stateRoot %s in stateDiffsProcessed collection, want: %t, got: %t",
|
||||
firstStateRoot.Hex(),
|
||||
false,
|
||||
ok,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func containsRootHash(collection []common.Hash, hash common.Hash) bool {
|
||||
for _, n := range collection {
|
||||
if n == hash {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestDeleteCreateRevert tests a weird state transition corner case that we hit
|
||||
// while changing the internals of statedb. The workflow is that a contract is
|
||||
// self destructed, then in a followup transaction (but same block) it's created
|
||||
|
36
core/evm.go
36
core/evm.go
@ -60,24 +60,32 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
|
||||
|
||||
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
|
||||
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
|
||||
var cache map[uint64]common.Hash
|
||||
// Cache will initially contain [refHash.parent],
|
||||
// Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...]
|
||||
var cache []common.Hash
|
||||
|
||||
return func(n uint64) common.Hash {
|
||||
// If there's no hash cache yet, make one
|
||||
if cache == nil {
|
||||
cache = map[uint64]common.Hash{
|
||||
ref.Number.Uint64() - 1: ref.ParentHash,
|
||||
if len(cache) == 0 {
|
||||
cache = append(cache, ref.ParentHash)
|
||||
}
|
||||
if idx := ref.Number.Uint64() - n - 1; idx < uint64(len(cache)) {
|
||||
return cache[idx]
|
||||
}
|
||||
// No luck in the cache, but we can start iterating from the last element we already know
|
||||
lastKnownHash := cache[len(cache)-1]
|
||||
lastKnownNumber := ref.Number.Uint64() - uint64(len(cache))
|
||||
|
||||
for {
|
||||
header := chain.GetHeader(lastKnownHash, lastKnownNumber)
|
||||
if header == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Try to fulfill the request from the cache
|
||||
if hash, ok := cache[n]; ok {
|
||||
return hash
|
||||
}
|
||||
// Not cached, iterate the blocks and cache the hashes
|
||||
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
|
||||
cache[header.Number.Uint64()-1] = header.ParentHash
|
||||
if n == header.Number.Uint64()-1 {
|
||||
return header.ParentHash
|
||||
cache = append(cache, header.ParentHash)
|
||||
lastKnownHash = header.ParentHash
|
||||
lastKnownNumber = header.Number.Uint64() - 1
|
||||
if n == lastKnownNumber {
|
||||
return lastKnownHash
|
||||
}
|
||||
}
|
||||
return common.Hash{}
|
||||
|
@ -272,10 +272,13 @@ func (s *stateObject) finalise() {
|
||||
}
|
||||
|
||||
// updateTrie writes cached storage modifications into the object's storage trie.
|
||||
// It will return nil if the trie has not been loaded and no changes have been made
|
||||
func (s *stateObject) updateTrie(db Database) Trie {
|
||||
// Make sure all dirty slots are finalized into the pending storage area
|
||||
s.finalise()
|
||||
|
||||
if len(s.pendingStorage) == 0 {
|
||||
return s.trie
|
||||
}
|
||||
// Track the amount of time wasted on updating the storge trie
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
|
||||
@ -305,8 +308,10 @@ func (s *stateObject) updateTrie(db Database) Trie {
|
||||
|
||||
// UpdateRoot sets the trie root to the current root hash of
|
||||
func (s *stateObject) updateRoot(db Database) {
|
||||
s.updateTrie(db)
|
||||
|
||||
// If nothing changed, don't bother with hashing anything
|
||||
if s.updateTrie(db) == nil {
|
||||
return
|
||||
}
|
||||
// Track the amount of time wasted on hashing the storge trie
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
|
||||
@ -317,7 +322,10 @@ func (s *stateObject) updateRoot(db Database) {
|
||||
// CommitTrie the storage trie of the object to db.
|
||||
// This updates the trie root.
|
||||
func (s *stateObject) CommitTrie(db Database) error {
|
||||
s.updateTrie(db)
|
||||
// If nothing changed, don't bother with hashing anything
|
||||
if s.updateTrie(db) == nil {
|
||||
return nil
|
||||
}
|
||||
if s.dbErr != nil {
|
||||
return s.dbErr
|
||||
}
|
||||
|
@ -330,7 +330,8 @@ func (s *StateDB) StorageTrie(addr common.Address) Trie {
|
||||
return nil
|
||||
}
|
||||
cpy := stateObject.deepCopy(s)
|
||||
return cpy.updateTrie(s.db)
|
||||
cpy.updateTrie(s.db)
|
||||
return cpy.getTrie(s.db)
|
||||
}
|
||||
|
||||
func (s *StateDB) HasSuicided(addr common.Address) bool {
|
||||
@ -750,8 +751,10 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now())
|
||||
}
|
||||
// The onleaf func is called _serially_, so we can reuse the same account
|
||||
// for unmarshalling every time.
|
||||
var account Account
|
||||
return s.trie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var account Account
|
||||
if err := rlp.DecodeBytes(leaf, &account); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
@ -53,6 +52,10 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAlreadyKnown is returned if the transactions is already contained
|
||||
// within the pool.
|
||||
ErrAlreadyKnown = errors.New("already known")
|
||||
|
||||
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
||||
ErrInvalidSender = errors.New("invalid sender")
|
||||
|
||||
@ -579,7 +582,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
|
||||
if pool.all.Get(hash) != nil {
|
||||
log.Trace("Discarding already known transaction", "hash", hash)
|
||||
knownTxMeter.Mark(1)
|
||||
return false, fmt.Errorf("known transaction: %x", hash)
|
||||
return false, ErrAlreadyKnown
|
||||
}
|
||||
// If the transaction fails basic validation, discard it
|
||||
if err := pool.validateTx(tx, local); err != nil {
|
||||
@ -786,7 +789,7 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
|
||||
for i, tx := range txs {
|
||||
// If the transaction is known, pre-set the error slot
|
||||
if pool.all.Get(tx.Hash()) != nil {
|
||||
errs[i] = fmt.Errorf("known transaction: %x", tx.Hash())
|
||||
errs[i] = ErrAlreadyKnown
|
||||
knownTxMeter.Mark(1)
|
||||
continue
|
||||
}
|
||||
@ -864,6 +867,12 @@ func (pool *TxPool) Get(hash common.Hash) *types.Transaction {
|
||||
return pool.all.Get(hash)
|
||||
}
|
||||
|
||||
// Has returns an indicator whether txpool has a transaction cached with the
|
||||
// given hash.
|
||||
func (pool *TxPool) Has(hash common.Hash) bool {
|
||||
return pool.all.Get(hash) != nil
|
||||
}
|
||||
|
||||
// removeTx removes a single transaction from the queue, moving all subsequent
|
||||
// transactions back to the future queue.
|
||||
func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
|
||||
|
@ -17,7 +17,6 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
@ -26,8 +25,7 @@ func NewEnv(cfg *Config) *vm.EVM {
|
||||
context := vm.Context{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
GetHash: func(uint64) common.Hash { return common.Hash{} },
|
||||
|
||||
GetHash: cfg.GetHashFn,
|
||||
Origin: cfg.Origin,
|
||||
Coinbase: cfg.Coinbase,
|
||||
BlockNumber: cfg.BlockNumber,
|
||||
|
@ -23,8 +23,11 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
@ -203,3 +206,113 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
|
||||
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
|
||||
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
|
||||
}
|
||||
|
||||
func fakeHeader(n uint64, parentHash common.Hash) *types.Header {
|
||||
header := types.Header{
|
||||
Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"),
|
||||
Number: big.NewInt(int64(n)),
|
||||
ParentHash: parentHash,
|
||||
Time: 1000,
|
||||
Nonce: types.BlockNonce{0x1},
|
||||
Extra: []byte{},
|
||||
Difficulty: big.NewInt(0),
|
||||
GasLimit: 100000,
|
||||
}
|
||||
return &header
|
||||
}
|
||||
|
||||
type dummyChain struct {
|
||||
counter int
|
||||
}
|
||||
|
||||
// Engine retrieves the chain's consensus engine.
|
||||
func (d *dummyChain) Engine() consensus.Engine {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHeader returns the hash corresponding to their hash.
|
||||
func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header {
|
||||
d.counter++
|
||||
parentHash := common.Hash{}
|
||||
s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32)
|
||||
copy(parentHash[:], s)
|
||||
|
||||
//parentHash := common.Hash{byte(n - 1)}
|
||||
//fmt.Printf("GetHeader(%x, %d) => header with parent %x\n", h, n, parentHash)
|
||||
return fakeHeader(n, parentHash)
|
||||
}
|
||||
|
||||
// TestBlockhash tests the blockhash operation. It's a bit special, since it internally
|
||||
// requires access to a chain reader.
|
||||
func TestBlockhash(t *testing.T) {
|
||||
// Current head
|
||||
n := uint64(1000)
|
||||
parentHash := common.Hash{}
|
||||
s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32)
|
||||
copy(parentHash[:], s)
|
||||
header := fakeHeader(n, parentHash)
|
||||
|
||||
// This is the contract we're using. It requests the blockhash for current num (should be all zeroes),
|
||||
// then iteratively fetches all blockhashes back to n-260.
|
||||
// It returns
|
||||
// 1. the first (should be zero)
|
||||
// 2. the second (should be the parent hash)
|
||||
// 3. the last non-zero hash
|
||||
// By making the chain reader return hashes which correlate to the number, we can
|
||||
// verify that it obtained the right hashes where it should
|
||||
|
||||
/*
|
||||
|
||||
pragma solidity ^0.5.3;
|
||||
contract Hasher{
|
||||
|
||||
function test() public view returns (bytes32, bytes32, bytes32){
|
||||
uint256 x = block.number;
|
||||
bytes32 first;
|
||||
bytes32 last;
|
||||
bytes32 zero;
|
||||
zero = blockhash(x); // Should be zeroes
|
||||
first = blockhash(x-1);
|
||||
for(uint256 i = 2 ; i < 260; i++){
|
||||
bytes32 hash = blockhash(x - i);
|
||||
if (uint256(hash) != 0){
|
||||
last = hash;
|
||||
}
|
||||
}
|
||||
return (zero, first, last);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
// The contract above
|
||||
data := common.Hex2Bytes("6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d14604a575b600080fd5b60506074565b60405180848152602001838152602001828152602001935050505060405180910390f35b600080600080439050600080600083409050600184034092506000600290505b61010481101560c35760008186034090506000816001900414151560b6578093505b5080806001019150506094565b508083839650965096505050505090919256fea165627a7a72305820462d71b510c1725ff35946c20b415b0d50b468ea157c8c77dff9466c9cb85f560029")
|
||||
// The method call to 'test()'
|
||||
input := common.Hex2Bytes("f8a8fd6d")
|
||||
chain := &dummyChain{}
|
||||
ret, _, err := Execute(data, input, &Config{
|
||||
GetHashFn: core.GetHashFn(header, chain),
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if len(ret) != 96 {
|
||||
t.Fatalf("expected returndata to be 96 bytes, got %d", len(ret))
|
||||
}
|
||||
|
||||
zero := new(big.Int).SetBytes(ret[0:32])
|
||||
first := new(big.Int).SetBytes(ret[32:64])
|
||||
last := new(big.Int).SetBytes(ret[64:96])
|
||||
if zero.BitLen() != 0 {
|
||||
t.Fatalf("expected zeroes, got %x", ret[0:32])
|
||||
}
|
||||
if first.Uint64() != 999 {
|
||||
t.Fatalf("second block should be 999, got %d (%x)", first, ret[32:64])
|
||||
}
|
||||
if last.Uint64() != 744 {
|
||||
t.Fatalf("last block should be 744, got %d (%x)", last, ret[64:96])
|
||||
}
|
||||
if exp, got := 255, chain.counter; exp != got {
|
||||
t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"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"
|
||||
@ -74,6 +75,7 @@ type Ethereum struct {
|
||||
blockchain *core.BlockChain
|
||||
protocolManager *ProtocolManager
|
||||
lesServer LesServer
|
||||
dialCandiates enode.Iterator
|
||||
|
||||
// DB interfaces
|
||||
chainDb ethdb.Database // Block chain database
|
||||
@ -177,11 +179,12 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
||||
EVMInterpreter: config.EVMInterpreter,
|
||||
}
|
||||
cacheConfig = &core.CacheConfig{
|
||||
TrieCleanLimit: config.TrieCleanCache,
|
||||
TrieCleanNoPrefetch: config.NoPrefetch,
|
||||
TrieDirtyLimit: config.TrieDirtyCache,
|
||||
TrieDirtyDisabled: config.NoPruning,
|
||||
TrieTimeLimit: config.TrieTimeout,
|
||||
TrieCleanLimit: config.TrieCleanCache,
|
||||
TrieCleanNoPrefetch: config.NoPrefetch,
|
||||
TrieDirtyLimit: config.TrieDirtyCache,
|
||||
TrieDirtyDisabled: config.NoPruning,
|
||||
TrieTimeLimit: config.TrieTimeout,
|
||||
ProcessingStateDiffs: config.StateDiff,
|
||||
}
|
||||
)
|
||||
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve)
|
||||
@ -220,6 +223,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
||||
}
|
||||
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
||||
|
||||
eth.dialCandiates, err = eth.setupDiscovery(&ctx.Config.P2P)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return eth, nil
|
||||
}
|
||||
|
||||
@ -510,6 +518,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol {
|
||||
for i, vsn := range ProtocolVersions {
|
||||
protos[i] = s.protocolManager.makeProtocol(vsn)
|
||||
protos[i].Attributes = []enr.Entry{s.currentEthEntry()}
|
||||
protos[i].DialCandidates = s.dialCandiates
|
||||
}
|
||||
if s.lesServer != nil {
|
||||
protos = append(protos, s.lesServer.Protocols()...)
|
||||
|
@ -61,6 +61,8 @@ var DefaultConfig = Config{
|
||||
Blocks: 20,
|
||||
Percentile: 60,
|
||||
},
|
||||
|
||||
StateDiff: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -95,6 +97,10 @@ type Config struct {
|
||||
NetworkId uint64 // Network ID to use for selecting peers to connect to
|
||||
SyncMode downloader.SyncMode
|
||||
|
||||
// This can be set to list of enrtree:// URLs which will be queried for
|
||||
// for nodes to connect to.
|
||||
DiscoveryURLs []string
|
||||
|
||||
NoPruning bool // Whether to disable pruning and flush everything to disk
|
||||
NoPrefetch bool // Whether to disable prefetching and only load state on demand
|
||||
|
||||
@ -156,8 +162,10 @@ type Config struct {
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
|
||||
// Istanbul block override (TODO: remove after the fork)
|
||||
OverrideIstanbul *big.Int
|
||||
OverrideIstanbul *big.Int `toml:",omitempty"`
|
||||
|
||||
// MuirGlacier block override (TODO: remove after the fork)
|
||||
OverrideMuirGlacier *big.Int
|
||||
OverrideMuirGlacier *big.Int `toml:",omitempty"`
|
||||
|
||||
StateDiff bool
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ package eth
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/dnsdisc"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
@ -37,6 +39,7 @@ func (e ethEntry) ENRKey() string {
|
||||
return "eth"
|
||||
}
|
||||
|
||||
// startEthEntryUpdate starts the ENR updater loop.
|
||||
func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) {
|
||||
var newHead = make(chan core.ChainHeadEvent, 10)
|
||||
sub := eth.blockchain.SubscribeChainHeadEvent(newHead)
|
||||
@ -59,3 +62,12 @@ func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) {
|
||||
func (eth *Ethereum) currentEthEntry() *ethEntry {
|
||||
return ðEntry{ForkID: forkid.NewID(eth.blockchain)}
|
||||
}
|
||||
|
||||
// setupDiscovery creates the node discovery source for the eth protocol.
|
||||
func (eth *Ethereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) {
|
||||
if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
client := dnsdisc.NewClient(dnsdisc.Config{})
|
||||
return client.NewIterator(eth.config.DiscoveryURLs...)
|
||||
}
|
@ -470,7 +470,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.headerThroughput
|
||||
}
|
||||
return ps.idlePeers(62, 64, idle, throughput)
|
||||
return ps.idlePeers(62, 65, idle, throughput)
|
||||
}
|
||||
|
||||
// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
|
||||
@ -484,7 +484,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.blockThroughput
|
||||
}
|
||||
return ps.idlePeers(62, 64, idle, throughput)
|
||||
return ps.idlePeers(62, 65, idle, throughput)
|
||||
}
|
||||
|
||||
// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
|
||||
@ -498,7 +498,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.receiptThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 64, idle, throughput)
|
||||
return ps.idlePeers(63, 65, idle, throughput)
|
||||
}
|
||||
|
||||
// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
|
||||
@ -512,7 +512,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
|
||||
defer p.lock.RUnlock()
|
||||
return p.stateThroughput
|
||||
}
|
||||
return ps.idlePeers(63, 64, idle, throughput)
|
||||
return ps.idlePeers(63, 65, idle, throughput)
|
||||
}
|
||||
|
||||
// idlePeers retrieves a flat list of all currently idle peers satisfying the
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package fetcher contains the block announcement based synchronisation.
|
||||
// Package fetcher contains the announcement based blocks or transaction synchronisation.
|
||||
package fetcher
|
||||
|
||||
import (
|
||||
@ -27,16 +27,40 @@ import (
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block is explicitly requested
|
||||
arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block/transaction is explicitly requested
|
||||
gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches
|
||||
fetchTimeout = 5 * time.Second // Maximum allotted time to return an explicitly requested block
|
||||
maxUncleDist = 7 // Maximum allowed backward distance from the chain head
|
||||
maxQueueDist = 32 // Maximum allowed distance from the chain head to queue
|
||||
hashLimit = 256 // Maximum number of unique blocks a peer may have announced
|
||||
blockLimit = 64 // Maximum number of unique blocks a peer may have delivered
|
||||
fetchTimeout = 5 * time.Second // Maximum allotted time to return an explicitly requested block/transaction
|
||||
)
|
||||
|
||||
const (
|
||||
maxUncleDist = 7 // Maximum allowed backward distance from the chain head
|
||||
maxQueueDist = 32 // Maximum allowed distance from the chain head to queue
|
||||
hashLimit = 256 // Maximum number of unique blocks a peer may have announced
|
||||
blockLimit = 64 // Maximum number of unique blocks a peer may have delivered
|
||||
)
|
||||
|
||||
var (
|
||||
blockAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/in", nil)
|
||||
blockAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/announces/out", nil)
|
||||
blockAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/drop", nil)
|
||||
blockAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/dos", nil)
|
||||
|
||||
blockBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/in", nil)
|
||||
blockBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/broadcasts/out", nil)
|
||||
blockBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/drop", nil)
|
||||
blockBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/dos", nil)
|
||||
|
||||
headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/headers", nil)
|
||||
bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/bodies", nil)
|
||||
|
||||
headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/in", nil)
|
||||
headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/out", nil)
|
||||
bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/in", nil)
|
||||
bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/out", nil)
|
||||
)
|
||||
|
||||
var (
|
||||
@ -67,9 +91,9 @@ type chainInsertFn func(types.Blocks) (int, error)
|
||||
// peerDropFn is a callback type for dropping a peer detected as malicious.
|
||||
type peerDropFn func(id string)
|
||||
|
||||
// announce is the hash notification of the availability of a new block in the
|
||||
// blockAnnounce is the hash notification of the availability of a new block in the
|
||||
// network.
|
||||
type announce struct {
|
||||
type blockAnnounce struct {
|
||||
hash common.Hash // Hash of the block being announced
|
||||
number uint64 // Number of the block being announced (0 = unknown | old protocol)
|
||||
header *types.Header // Header of the block partially reassembled (new protocol)
|
||||
@ -97,18 +121,18 @@ type bodyFilterTask struct {
|
||||
time time.Time // Arrival time of the blocks' contents
|
||||
}
|
||||
|
||||
// inject represents a schedules import operation.
|
||||
type inject struct {
|
||||
// blockInject represents a schedules import operation.
|
||||
type blockInject struct {
|
||||
origin string
|
||||
block *types.Block
|
||||
}
|
||||
|
||||
// Fetcher is responsible for accumulating block announcements from various peers
|
||||
// BlockFetcher is responsible for accumulating block announcements from various peers
|
||||
// and scheduling them for retrieval.
|
||||
type Fetcher struct {
|
||||
type BlockFetcher struct {
|
||||
// Various event channels
|
||||
notify chan *announce
|
||||
inject chan *inject
|
||||
notify chan *blockAnnounce
|
||||
inject chan *blockInject
|
||||
|
||||
headerFilter chan chan *headerFilterTask
|
||||
bodyFilter chan chan *bodyFilterTask
|
||||
@ -117,16 +141,16 @@ type Fetcher struct {
|
||||
quit chan struct{}
|
||||
|
||||
// Announce states
|
||||
announces map[string]int // Per peer announce counts to prevent memory exhaustion
|
||||
announced map[common.Hash][]*announce // Announced blocks, scheduled for fetching
|
||||
fetching map[common.Hash]*announce // Announced blocks, currently fetching
|
||||
fetched map[common.Hash][]*announce // Blocks with headers fetched, scheduled for body retrieval
|
||||
completing map[common.Hash]*announce // Blocks with headers, currently body-completing
|
||||
announces map[string]int // Per peer blockAnnounce counts to prevent memory exhaustion
|
||||
announced map[common.Hash][]*blockAnnounce // Announced blocks, scheduled for fetching
|
||||
fetching map[common.Hash]*blockAnnounce // Announced blocks, currently fetching
|
||||
fetched map[common.Hash][]*blockAnnounce // Blocks with headers fetched, scheduled for body retrieval
|
||||
completing map[common.Hash]*blockAnnounce // Blocks with headers, currently body-completing
|
||||
|
||||
// Block cache
|
||||
queue *prque.Prque // Queue containing the import operations (block number sorted)
|
||||
queues map[string]int // Per peer block counts to prevent memory exhaustion
|
||||
queued map[common.Hash]*inject // Set of already queued blocks (to dedupe imports)
|
||||
queue *prque.Prque // Queue containing the import operations (block number sorted)
|
||||
queues map[string]int // Per peer block counts to prevent memory exhaustion
|
||||
queued map[common.Hash]*blockInject // Set of already queued blocks (to dedupe imports)
|
||||
|
||||
// Callbacks
|
||||
getBlock blockRetrievalFn // Retrieves a block from the local chain
|
||||
@ -137,30 +161,30 @@ type Fetcher struct {
|
||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||
|
||||
// Testing hooks
|
||||
announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the announce list
|
||||
announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the blockAnnounce list
|
||||
queueChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a block from the import queue
|
||||
fetchingHook func([]common.Hash) // Method to call upon starting a block (eth/61) or header (eth/62) fetch
|
||||
completingHook func([]common.Hash) // Method to call upon starting a block body fetch (eth/62)
|
||||
importedHook func(*types.Block) // Method to call upon successful block import (both eth/61 and eth/62)
|
||||
}
|
||||
|
||||
// New creates a block fetcher to retrieve blocks based on hash announcements.
|
||||
func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertChain chainInsertFn, dropPeer peerDropFn) *Fetcher {
|
||||
return &Fetcher{
|
||||
notify: make(chan *announce),
|
||||
inject: make(chan *inject),
|
||||
// NewBlockFetcher creates a block fetcher to retrieve blocks based on hash announcements.
|
||||
func NewBlockFetcher(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher {
|
||||
return &BlockFetcher{
|
||||
notify: make(chan *blockAnnounce),
|
||||
inject: make(chan *blockInject),
|
||||
headerFilter: make(chan chan *headerFilterTask),
|
||||
bodyFilter: make(chan chan *bodyFilterTask),
|
||||
done: make(chan common.Hash),
|
||||
quit: make(chan struct{}),
|
||||
announces: make(map[string]int),
|
||||
announced: make(map[common.Hash][]*announce),
|
||||
fetching: make(map[common.Hash]*announce),
|
||||
fetched: make(map[common.Hash][]*announce),
|
||||
completing: make(map[common.Hash]*announce),
|
||||
announced: make(map[common.Hash][]*blockAnnounce),
|
||||
fetching: make(map[common.Hash]*blockAnnounce),
|
||||
fetched: make(map[common.Hash][]*blockAnnounce),
|
||||
completing: make(map[common.Hash]*blockAnnounce),
|
||||
queue: prque.New(nil),
|
||||
queues: make(map[string]int),
|
||||
queued: make(map[common.Hash]*inject),
|
||||
queued: make(map[common.Hash]*blockInject),
|
||||
getBlock: getBlock,
|
||||
verifyHeader: verifyHeader,
|
||||
broadcastBlock: broadcastBlock,
|
||||
@ -172,21 +196,21 @@ func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBloc
|
||||
|
||||
// Start boots up the announcement based synchroniser, accepting and processing
|
||||
// hash notifications and block fetches until termination requested.
|
||||
func (f *Fetcher) Start() {
|
||||
func (f *BlockFetcher) Start() {
|
||||
go f.loop()
|
||||
}
|
||||
|
||||
// Stop terminates the announcement based synchroniser, canceling all pending
|
||||
// operations.
|
||||
func (f *Fetcher) Stop() {
|
||||
func (f *BlockFetcher) Stop() {
|
||||
close(f.quit)
|
||||
}
|
||||
|
||||
// Notify announces the fetcher of the potential availability of a new block in
|
||||
// the network.
|
||||
func (f *Fetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time,
|
||||
func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time,
|
||||
headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error {
|
||||
block := &announce{
|
||||
block := &blockAnnounce{
|
||||
hash: hash,
|
||||
number: number,
|
||||
time: time,
|
||||
@ -203,8 +227,8 @@ func (f *Fetcher) Notify(peer string, hash common.Hash, number uint64, time time
|
||||
}
|
||||
|
||||
// Enqueue tries to fill gaps the fetcher's future import queue.
|
||||
func (f *Fetcher) Enqueue(peer string, block *types.Block) error {
|
||||
op := &inject{
|
||||
func (f *BlockFetcher) Enqueue(peer string, block *types.Block) error {
|
||||
op := &blockInject{
|
||||
origin: peer,
|
||||
block: block,
|
||||
}
|
||||
@ -218,7 +242,7 @@ func (f *Fetcher) Enqueue(peer string, block *types.Block) error {
|
||||
|
||||
// FilterHeaders extracts all the headers that were explicitly requested by the fetcher,
|
||||
// returning those that should be handled differently.
|
||||
func (f *Fetcher) FilterHeaders(peer string, headers []*types.Header, time time.Time) []*types.Header {
|
||||
func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time time.Time) []*types.Header {
|
||||
log.Trace("Filtering headers", "peer", peer, "headers", len(headers))
|
||||
|
||||
// Send the filter channel to the fetcher
|
||||
@ -246,7 +270,7 @@ func (f *Fetcher) FilterHeaders(peer string, headers []*types.Header, time time.
|
||||
|
||||
// FilterBodies extracts all the block bodies that were explicitly requested by
|
||||
// the fetcher, returning those that should be handled differently.
|
||||
func (f *Fetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) {
|
||||
func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) {
|
||||
log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles))
|
||||
|
||||
// Send the filter channel to the fetcher
|
||||
@ -274,7 +298,7 @@ func (f *Fetcher) FilterBodies(peer string, transactions [][]*types.Transaction,
|
||||
|
||||
// Loop is the main fetcher loop, checking and processing various notification
|
||||
// events.
|
||||
func (f *Fetcher) loop() {
|
||||
func (f *BlockFetcher) loop() {
|
||||
// Iterate the block fetching until a quit is requested
|
||||
fetchTimer := time.NewTimer(0)
|
||||
completeTimer := time.NewTimer(0)
|
||||
@ -289,7 +313,7 @@ func (f *Fetcher) loop() {
|
||||
// Import any queued blocks that could potentially fit
|
||||
height := f.chainHeight()
|
||||
for !f.queue.Empty() {
|
||||
op := f.queue.PopItem().(*inject)
|
||||
op := f.queue.PopItem().(*blockInject)
|
||||
hash := op.block.Hash()
|
||||
if f.queueChangeHook != nil {
|
||||
f.queueChangeHook(hash, false)
|
||||
@ -313,24 +337,24 @@ func (f *Fetcher) loop() {
|
||||
// Wait for an outside event to occur
|
||||
select {
|
||||
case <-f.quit:
|
||||
// Fetcher terminating, abort all operations
|
||||
// BlockFetcher terminating, abort all operations
|
||||
return
|
||||
|
||||
case notification := <-f.notify:
|
||||
// A block was announced, make sure the peer isn't DOSing us
|
||||
propAnnounceInMeter.Mark(1)
|
||||
blockAnnounceInMeter.Mark(1)
|
||||
|
||||
count := f.announces[notification.origin] + 1
|
||||
if count > hashLimit {
|
||||
log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit)
|
||||
propAnnounceDOSMeter.Mark(1)
|
||||
blockAnnounceDOSMeter.Mark(1)
|
||||
break
|
||||
}
|
||||
// If we have a valid block number, check that it's potentially useful
|
||||
if notification.number > 0 {
|
||||
if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
|
||||
log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist)
|
||||
propAnnounceDropMeter.Mark(1)
|
||||
blockAnnounceDropMeter.Mark(1)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -352,7 +376,7 @@ func (f *Fetcher) loop() {
|
||||
|
||||
case op := <-f.inject:
|
||||
// A direct block insertion was requested, try and fill any pending gaps
|
||||
propBroadcastInMeter.Mark(1)
|
||||
blockBroadcastInMeter.Mark(1)
|
||||
f.enqueue(op.origin, op.block)
|
||||
|
||||
case hash := <-f.done:
|
||||
@ -439,7 +463,7 @@ func (f *Fetcher) loop() {
|
||||
|
||||
// Split the batch of headers into unknown ones (to return to the caller),
|
||||
// known incomplete ones (requiring body retrievals) and completed blocks.
|
||||
unknown, incomplete, complete := []*types.Header{}, []*announce{}, []*types.Block{}
|
||||
unknown, incomplete, complete := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}
|
||||
for _, header := range task.headers {
|
||||
hash := header.Hash()
|
||||
|
||||
@ -475,7 +499,7 @@ func (f *Fetcher) loop() {
|
||||
f.forgetHash(hash)
|
||||
}
|
||||
} else {
|
||||
// Fetcher doesn't know about it, add to the return list
|
||||
// BlockFetcher doesn't know about it, add to the return list
|
||||
unknown = append(unknown, header)
|
||||
}
|
||||
}
|
||||
@ -562,8 +586,8 @@ func (f *Fetcher) loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// rescheduleFetch resets the specified fetch timer to the next announce timeout.
|
||||
func (f *Fetcher) rescheduleFetch(fetch *time.Timer) {
|
||||
// rescheduleFetch resets the specified fetch timer to the next blockAnnounce timeout.
|
||||
func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) {
|
||||
// Short circuit if no blocks are announced
|
||||
if len(f.announced) == 0 {
|
||||
return
|
||||
@ -579,7 +603,7 @@ func (f *Fetcher) rescheduleFetch(fetch *time.Timer) {
|
||||
}
|
||||
|
||||
// rescheduleComplete resets the specified completion timer to the next fetch timeout.
|
||||
func (f *Fetcher) rescheduleComplete(complete *time.Timer) {
|
||||
func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) {
|
||||
// Short circuit if no headers are fetched
|
||||
if len(f.fetched) == 0 {
|
||||
return
|
||||
@ -596,27 +620,27 @@ func (f *Fetcher) rescheduleComplete(complete *time.Timer) {
|
||||
|
||||
// enqueue schedules a new future import operation, if the block to be imported
|
||||
// has not yet been seen.
|
||||
func (f *Fetcher) enqueue(peer string, block *types.Block) {
|
||||
func (f *BlockFetcher) enqueue(peer string, block *types.Block) {
|
||||
hash := block.Hash()
|
||||
|
||||
// Ensure the peer isn't DOSing us
|
||||
count := f.queues[peer] + 1
|
||||
if count > blockLimit {
|
||||
log.Debug("Discarded propagated block, exceeded allowance", "peer", peer, "number", block.Number(), "hash", hash, "limit", blockLimit)
|
||||
propBroadcastDOSMeter.Mark(1)
|
||||
blockBroadcastDOSMeter.Mark(1)
|
||||
f.forgetHash(hash)
|
||||
return
|
||||
}
|
||||
// Discard any past or too distant blocks
|
||||
if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
|
||||
log.Debug("Discarded propagated block, too far away", "peer", peer, "number", block.Number(), "hash", hash, "distance", dist)
|
||||
propBroadcastDropMeter.Mark(1)
|
||||
blockBroadcastDropMeter.Mark(1)
|
||||
f.forgetHash(hash)
|
||||
return
|
||||
}
|
||||
// Schedule the block for future importing
|
||||
if _, ok := f.queued[hash]; !ok {
|
||||
op := &inject{
|
||||
op := &blockInject{
|
||||
origin: peer,
|
||||
block: block,
|
||||
}
|
||||
@ -633,7 +657,7 @@ func (f *Fetcher) enqueue(peer string, block *types.Block) {
|
||||
// insert spawns a new goroutine to run a block insertion into the chain. If the
|
||||
// block's number is at the same height as the current import phase, it updates
|
||||
// the phase states accordingly.
|
||||
func (f *Fetcher) insert(peer string, block *types.Block) {
|
||||
func (f *BlockFetcher) insert(peer string, block *types.Block) {
|
||||
hash := block.Hash()
|
||||
|
||||
// Run the import on a new thread
|
||||
@ -651,7 +675,7 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
|
||||
switch err := f.verifyHeader(block.Header()); err {
|
||||
case nil:
|
||||
// All ok, quickly propagate to our peers
|
||||
propBroadcastOutTimer.UpdateSince(block.ReceivedAt)
|
||||
blockBroadcastOutTimer.UpdateSince(block.ReceivedAt)
|
||||
go f.broadcastBlock(block, true)
|
||||
|
||||
case consensus.ErrFutureBlock:
|
||||
@ -669,7 +693,7 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
|
||||
return
|
||||
}
|
||||
// If import succeeded, broadcast the block
|
||||
propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
|
||||
blockAnnounceOutTimer.UpdateSince(block.ReceivedAt)
|
||||
go f.broadcastBlock(block, false)
|
||||
|
||||
// Invoke the testing hook if needed
|
||||
@ -681,7 +705,7 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
|
||||
|
||||
// forgetHash removes all traces of a block announcement from the fetcher's
|
||||
// internal state.
|
||||
func (f *Fetcher) forgetHash(hash common.Hash) {
|
||||
func (f *BlockFetcher) forgetHash(hash common.Hash) {
|
||||
// Remove all pending announces and decrement DOS counters
|
||||
for _, announce := range f.announced[hash] {
|
||||
f.announces[announce.origin]--
|
||||
@ -723,7 +747,7 @@ func (f *Fetcher) forgetHash(hash common.Hash) {
|
||||
|
||||
// forgetBlock removes all traces of a queued block from the fetcher's internal
|
||||
// state.
|
||||
func (f *Fetcher) forgetBlock(hash common.Hash) {
|
||||
func (f *BlockFetcher) forgetBlock(hash common.Hash) {
|
||||
if insert := f.queued[hash]; insert != nil {
|
||||
f.queues[insert.origin]--
|
||||
if f.queues[insert.origin] == 0 {
|
@ -76,7 +76,7 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common
|
||||
|
||||
// fetcherTester is a test simulator for mocking out local block chain.
|
||||
type fetcherTester struct {
|
||||
fetcher *Fetcher
|
||||
fetcher *BlockFetcher
|
||||
|
||||
hashes []common.Hash // Hash chain belonging to the tester
|
||||
blocks map[common.Hash]*types.Block // Blocks belonging to the tester
|
||||
@ -92,7 +92,7 @@ func newTester() *fetcherTester {
|
||||
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
|
||||
drops: make(map[string]bool),
|
||||
}
|
||||
tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertChain, tester.dropPeer)
|
||||
tester.fetcher = NewBlockFetcher(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertChain, tester.dropPeer)
|
||||
tester.fetcher.Start()
|
||||
|
||||
return tester
|
@ -1,43 +0,0 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the metrics collected by the fetcher.
|
||||
|
||||
package fetcher
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
propAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/in", nil)
|
||||
propAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/announces/out", nil)
|
||||
propAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/drop", nil)
|
||||
propAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/announces/dos", nil)
|
||||
|
||||
propBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/in", nil)
|
||||
propBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/prop/broadcasts/out", nil)
|
||||
propBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/drop", nil)
|
||||
propBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/prop/broadcasts/dos", nil)
|
||||
|
||||
headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/headers", nil)
|
||||
bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/fetch/bodies", nil)
|
||||
|
||||
headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/in", nil)
|
||||
headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/headers/out", nil)
|
||||
bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/in", nil)
|
||||
bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/filter/bodies/out", nil)
|
||||
)
|
894
eth/fetcher/tx_fetcher.go
Normal file
894
eth/fetcher/tx_fetcher.go
Normal file
@ -0,0 +1,894 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package fetcher
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxTxAnnounces is the maximum number of unique transaction a peer
|
||||
// can announce in a short time.
|
||||
maxTxAnnounces = 4096
|
||||
|
||||
// maxTxRetrievals is the maximum transaction number can be fetched in one
|
||||
// request. The rationale to pick 256 is:
|
||||
// - In eth protocol, the softResponseLimit is 2MB. Nowadays according to
|
||||
// Etherscan the average transaction size is around 200B, so in theory
|
||||
// we can include lots of transaction in a single protocol packet.
|
||||
// - However the maximum size of a single transaction is raised to 128KB,
|
||||
// so pick a middle value here to ensure we can maximize the efficiency
|
||||
// of the retrieval and response size overflow won't happen in most cases.
|
||||
maxTxRetrievals = 256
|
||||
|
||||
// maxTxUnderpricedSetSize is the size of the underpriced transaction set that
|
||||
// is used to track recent transactions that have been dropped so we don't
|
||||
// re-request them.
|
||||
maxTxUnderpricedSetSize = 32768
|
||||
|
||||
// txArriveTimeout is the time allowance before an announced transaction is
|
||||
// explicitly requested.
|
||||
txArriveTimeout = 500 * time.Millisecond
|
||||
|
||||
// txGatherSlack is the interval used to collate almost-expired announces
|
||||
// with network fetches.
|
||||
txGatherSlack = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
// txFetchTimeout is the maximum allotted time to return an explicitly
|
||||
// requested transaction.
|
||||
txFetchTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
txAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/in", nil)
|
||||
txAnnounceKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/known", nil)
|
||||
txAnnounceUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/underpriced", nil)
|
||||
txAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/dos", nil)
|
||||
|
||||
txBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/in", nil)
|
||||
txBroadcastKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/known", nil)
|
||||
txBroadcastUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/underpriced", nil)
|
||||
txBroadcastOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/otherreject", nil)
|
||||
|
||||
txRequestOutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/out", nil)
|
||||
txRequestFailMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/fail", nil)
|
||||
txRequestDoneMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/done", nil)
|
||||
txRequestTimeoutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/timeout", nil)
|
||||
|
||||
txReplyInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/in", nil)
|
||||
txReplyKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/known", nil)
|
||||
txReplyUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/underpriced", nil)
|
||||
txReplyOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/otherreject", nil)
|
||||
|
||||
txFetcherWaitingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/peers", nil)
|
||||
txFetcherWaitingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/hashes", nil)
|
||||
txFetcherQueueingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/peers", nil)
|
||||
txFetcherQueueingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/hashes", nil)
|
||||
txFetcherFetchingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/peers", nil)
|
||||
txFetcherFetchingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/hashes", nil)
|
||||
)
|
||||
|
||||
// txAnnounce is the notification of the availability of a batch
|
||||
// of new transactions in the network.
|
||||
type txAnnounce struct {
|
||||
origin string // Identifier of the peer originating the notification
|
||||
hashes []common.Hash // Batch of transaction hashes being announced
|
||||
}
|
||||
|
||||
// txRequest represents an in-flight transaction retrieval request destined to
|
||||
// a specific peers.
|
||||
type txRequest struct {
|
||||
hashes []common.Hash // Transactions having been requested
|
||||
stolen map[common.Hash]struct{} // Deliveries by someone else (don't re-request)
|
||||
time mclock.AbsTime // Timestamp of the request
|
||||
}
|
||||
|
||||
// txDelivery is the notification that a batch of transactions have been added
|
||||
// to the pool and should be untracked.
|
||||
type txDelivery struct {
|
||||
origin string // Identifier of the peer originating the notification
|
||||
hashes []common.Hash // Batch of transaction hashes having been delivered
|
||||
direct bool // Whether this is a direct reply or a broadcast
|
||||
}
|
||||
|
||||
// txDrop is the notiication that a peer has disconnected.
|
||||
type txDrop struct {
|
||||
peer string
|
||||
}
|
||||
|
||||
// TxFetcher is responsible for retrieving new transaction based on announcements.
|
||||
//
|
||||
// The fetcher operates in 3 stages:
|
||||
// - Transactions that are newly discovered are moved into a wait list.
|
||||
// - After ~500ms passes, transactions from the wait list that have not been
|
||||
// broadcast to us in whole are moved into a queueing area.
|
||||
// - When a connected peer doesn't have in-flight retrieval requests, any
|
||||
// transaction queued up (and announced by the peer) are allocated to the
|
||||
// peer and moved into a fetching status until it's fulfilled or fails.
|
||||
//
|
||||
// The invariants of the fetcher are:
|
||||
// - Each tracked transaction (hash) must only be present in one of the
|
||||
// three stages. This ensures that the fetcher operates akin to a finite
|
||||
// state automata and there's do data leak.
|
||||
// - Each peer that announced transactions may be scheduled retrievals, but
|
||||
// only ever one concurrently. This ensures we can immediately know what is
|
||||
// missing from a reply and reschedule it.
|
||||
type TxFetcher struct {
|
||||
notify chan *txAnnounce
|
||||
cleanup chan *txDelivery
|
||||
drop chan *txDrop
|
||||
quit chan struct{}
|
||||
|
||||
underpriced mapset.Set // Transactions discarded as too cheap (don't re-fetch)
|
||||
|
||||
// Stage 1: Waiting lists for newly discovered transactions that might be
|
||||
// broadcast without needing explicit request/reply round trips.
|
||||
waitlist map[common.Hash]map[string]struct{} // Transactions waiting for an potential broadcast
|
||||
waittime map[common.Hash]mclock.AbsTime // Timestamps when transactions were added to the waitlist
|
||||
waitslots map[string]map[common.Hash]struct{} // Waiting announcement sgroupped by peer (DoS protection)
|
||||
|
||||
// Stage 2: Queue of transactions that waiting to be allocated to some peer
|
||||
// to be retrieved directly.
|
||||
announces map[string]map[common.Hash]struct{} // Set of announced transactions, grouped by origin peer
|
||||
announced map[common.Hash]map[string]struct{} // Set of download locations, grouped by transaction hash
|
||||
|
||||
// Stage 3: Set of transactions currently being retrieved, some which may be
|
||||
// fulfilled and some rescheduled. Note, this step shares 'announces' from the
|
||||
// previous stage to avoid having to duplicate (need it for DoS checks).
|
||||
fetching map[common.Hash]string // Transaction set currently being retrieved
|
||||
requests map[string]*txRequest // In-flight transaction retrievals
|
||||
alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails
|
||||
|
||||
// Callbacks
|
||||
hasTx func(common.Hash) bool // Retrieves a tx from the local txpool
|
||||
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
|
||||
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
|
||||
|
||||
step chan struct{} // Notification channel when the fetcher loop iterates
|
||||
clock mclock.Clock // Time wrapper to simulate in tests
|
||||
rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random)
|
||||
}
|
||||
|
||||
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
||||
// based on hash announcements.
|
||||
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error) *TxFetcher {
|
||||
return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, mclock.System{}, nil)
|
||||
}
|
||||
|
||||
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
||||
// a simulated version and the internal randomness with a deterministic one.
|
||||
func NewTxFetcherForTests(
|
||||
hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error,
|
||||
clock mclock.Clock, rand *mrand.Rand) *TxFetcher {
|
||||
return &TxFetcher{
|
||||
notify: make(chan *txAnnounce),
|
||||
cleanup: make(chan *txDelivery),
|
||||
drop: make(chan *txDrop),
|
||||
quit: make(chan struct{}),
|
||||
waitlist: make(map[common.Hash]map[string]struct{}),
|
||||
waittime: make(map[common.Hash]mclock.AbsTime),
|
||||
waitslots: make(map[string]map[common.Hash]struct{}),
|
||||
announces: make(map[string]map[common.Hash]struct{}),
|
||||
announced: make(map[common.Hash]map[string]struct{}),
|
||||
fetching: make(map[common.Hash]string),
|
||||
requests: make(map[string]*txRequest),
|
||||
alternates: make(map[common.Hash]map[string]struct{}),
|
||||
underpriced: mapset.NewSet(),
|
||||
hasTx: hasTx,
|
||||
addTxs: addTxs,
|
||||
fetchTxs: fetchTxs,
|
||||
clock: clock,
|
||||
rand: rand,
|
||||
}
|
||||
}
|
||||
|
||||
// Notify announces the fetcher of the potential availability of a new batch of
|
||||
// transactions in the network.
|
||||
func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error {
|
||||
// Keep track of all the announced transactions
|
||||
txAnnounceInMeter.Mark(int64(len(hashes)))
|
||||
|
||||
// Skip any transaction announcements that we already know of, or that we've
|
||||
// previously marked as cheap and discarded. This check is of course racey,
|
||||
// because multiple concurrent notifies will still manage to pass it, but it's
|
||||
// still valuable to check here because it runs concurrent to the internal
|
||||
// loop, so anything caught here is time saved internally.
|
||||
var (
|
||||
unknowns = make([]common.Hash, 0, len(hashes))
|
||||
duplicate, underpriced int64
|
||||
)
|
||||
for _, hash := range hashes {
|
||||
switch {
|
||||
case f.hasTx(hash):
|
||||
duplicate++
|
||||
|
||||
case f.underpriced.Contains(hash):
|
||||
underpriced++
|
||||
|
||||
default:
|
||||
unknowns = append(unknowns, hash)
|
||||
}
|
||||
}
|
||||
txAnnounceKnownMeter.Mark(duplicate)
|
||||
txAnnounceUnderpricedMeter.Mark(underpriced)
|
||||
|
||||
// If anything's left to announce, push it into the internal loop
|
||||
if len(unknowns) == 0 {
|
||||
return nil
|
||||
}
|
||||
announce := &txAnnounce{
|
||||
origin: peer,
|
||||
hashes: unknowns,
|
||||
}
|
||||
select {
|
||||
case f.notify <- announce:
|
||||
return nil
|
||||
case <-f.quit:
|
||||
return errTerminated
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue imports a batch of received transaction into the transaction pool
|
||||
// and the fetcher. This method may be called by both transaction broadcasts and
|
||||
// direct request replies. The differentiation is important so the fetcher can
|
||||
// re-shedule missing transactions as soon as possible.
|
||||
func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) error {
|
||||
// Keep track of all the propagated transactions
|
||||
if direct {
|
||||
txReplyInMeter.Mark(int64(len(txs)))
|
||||
} else {
|
||||
txBroadcastInMeter.Mark(int64(len(txs)))
|
||||
}
|
||||
// Push all the transactions into the pool, tracking underpriced ones to avoid
|
||||
// re-requesting them and dropping the peer in case of malicious transfers.
|
||||
var (
|
||||
added = make([]common.Hash, 0, len(txs))
|
||||
duplicate int64
|
||||
underpriced int64
|
||||
otherreject int64
|
||||
)
|
||||
errs := f.addTxs(txs)
|
||||
for i, err := range errs {
|
||||
if err != nil {
|
||||
// Track the transaction hash if the price is too low for us.
|
||||
// Avoid re-request this transaction when we receive another
|
||||
// announcement.
|
||||
if err == core.ErrUnderpriced || err == core.ErrReplaceUnderpriced {
|
||||
for f.underpriced.Cardinality() >= maxTxUnderpricedSetSize {
|
||||
f.underpriced.Pop()
|
||||
}
|
||||
f.underpriced.Add(txs[i].Hash())
|
||||
}
|
||||
// Track a few interesting failure types
|
||||
switch err {
|
||||
case nil: // Noop, but need to handle to not count these
|
||||
|
||||
case core.ErrAlreadyKnown:
|
||||
duplicate++
|
||||
|
||||
case core.ErrUnderpriced, core.ErrReplaceUnderpriced:
|
||||
underpriced++
|
||||
|
||||
default:
|
||||
otherreject++
|
||||
}
|
||||
}
|
||||
added = append(added, txs[i].Hash())
|
||||
}
|
||||
if direct {
|
||||
txReplyKnownMeter.Mark(duplicate)
|
||||
txReplyUnderpricedMeter.Mark(underpriced)
|
||||
txReplyOtherRejectMeter.Mark(otherreject)
|
||||
} else {
|
||||
txBroadcastKnownMeter.Mark(duplicate)
|
||||
txBroadcastUnderpricedMeter.Mark(underpriced)
|
||||
txBroadcastOtherRejectMeter.Mark(otherreject)
|
||||
}
|
||||
select {
|
||||
case f.cleanup <- &txDelivery{origin: peer, hashes: added, direct: direct}:
|
||||
return nil
|
||||
case <-f.quit:
|
||||
return errTerminated
|
||||
}
|
||||
}
|
||||
|
||||
// Drop should be called when a peer disconnects. It cleans up all the internal
|
||||
// data structures of the given node.
|
||||
func (f *TxFetcher) Drop(peer string) error {
|
||||
select {
|
||||
case f.drop <- &txDrop{peer: peer}:
|
||||
return nil
|
||||
case <-f.quit:
|
||||
return errTerminated
|
||||
}
|
||||
}
|
||||
|
||||
// Start boots up the announcement based synchroniser, accepting and processing
|
||||
// hash notifications and block fetches until termination requested.
|
||||
func (f *TxFetcher) Start() {
|
||||
go f.loop()
|
||||
}
|
||||
|
||||
// Stop terminates the announcement based synchroniser, canceling all pending
|
||||
// operations.
|
||||
func (f *TxFetcher) Stop() {
|
||||
close(f.quit)
|
||||
}
|
||||
|
||||
func (f *TxFetcher) loop() {
|
||||
var (
|
||||
waitTimer = new(mclock.Timer)
|
||||
timeoutTimer = new(mclock.Timer)
|
||||
|
||||
waitTrigger = make(chan struct{}, 1)
|
||||
timeoutTrigger = make(chan struct{}, 1)
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case ann := <-f.notify:
|
||||
// Drop part of the new announcements if there are too many accumulated.
|
||||
// Note, we could but do not filter already known transactions here as
|
||||
// the probability of something arriving between this call and the pre-
|
||||
// filter outside is essentially zero.
|
||||
used := len(f.waitslots[ann.origin]) + len(f.announces[ann.origin])
|
||||
if used >= maxTxAnnounces {
|
||||
// This can happen if a set of transactions are requested but not
|
||||
// all fulfilled, so the remainder are rescheduled without the cap
|
||||
// check. Should be fine as the limit is in the thousands and the
|
||||
// request size in the hundreds.
|
||||
txAnnounceDOSMeter.Mark(int64(len(ann.hashes)))
|
||||
break
|
||||
}
|
||||
want := used + len(ann.hashes)
|
||||
if want > maxTxAnnounces {
|
||||
txAnnounceDOSMeter.Mark(int64(want - maxTxAnnounces))
|
||||
ann.hashes = ann.hashes[:want-maxTxAnnounces]
|
||||
}
|
||||
// All is well, schedule the remainder of the transactions
|
||||
idleWait := len(f.waittime) == 0
|
||||
_, oldPeer := f.announces[ann.origin]
|
||||
|
||||
for _, hash := range ann.hashes {
|
||||
// If the transaction is already downloading, add it to the list
|
||||
// of possible alternates (in case the current retrieval fails) and
|
||||
// also account it for the peer.
|
||||
if f.alternates[hash] != nil {
|
||||
f.alternates[hash][ann.origin] = struct{}{}
|
||||
|
||||
// Stage 2 and 3 share the set of origins per tx
|
||||
if announces := f.announces[ann.origin]; announces != nil {
|
||||
announces[hash] = struct{}{}
|
||||
} else {
|
||||
f.announces[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// If the transaction is not downloading, but is already queued
|
||||
// from a different peer, track it for the new peer too.
|
||||
if f.announced[hash] != nil {
|
||||
f.announced[hash][ann.origin] = struct{}{}
|
||||
|
||||
// Stage 2 and 3 share the set of origins per tx
|
||||
if announces := f.announces[ann.origin]; announces != nil {
|
||||
announces[hash] = struct{}{}
|
||||
} else {
|
||||
f.announces[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// If the transaction is already known to the fetcher, but not
|
||||
// yet downloading, add the peer as an alternate origin in the
|
||||
// waiting list.
|
||||
if f.waitlist[hash] != nil {
|
||||
f.waitlist[hash][ann.origin] = struct{}{}
|
||||
|
||||
if waitslots := f.waitslots[ann.origin]; waitslots != nil {
|
||||
waitslots[hash] = struct{}{}
|
||||
} else {
|
||||
f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Transaction unknown to the fetcher, insert it into the waiting list
|
||||
f.waitlist[hash] = map[string]struct{}{ann.origin: struct{}{}}
|
||||
f.waittime[hash] = f.clock.Now()
|
||||
|
||||
if waitslots := f.waitslots[ann.origin]; waitslots != nil {
|
||||
waitslots[hash] = struct{}{}
|
||||
} else {
|
||||
f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
|
||||
}
|
||||
}
|
||||
// If a new item was added to the waitlist, schedule it into the fetcher
|
||||
if idleWait && len(f.waittime) > 0 {
|
||||
f.rescheduleWait(waitTimer, waitTrigger)
|
||||
}
|
||||
// If this peer is new and announced something already queued, maybe
|
||||
// request transactions from them
|
||||
if !oldPeer && len(f.announces[ann.origin]) > 0 {
|
||||
f.scheduleFetches(timeoutTimer, timeoutTrigger, map[string]struct{}{ann.origin: struct{}{}})
|
||||
}
|
||||
|
||||
case <-waitTrigger:
|
||||
// At least one transaction's waiting time ran out, push all expired
|
||||
// ones into the retrieval queues
|
||||
actives := make(map[string]struct{})
|
||||
for hash, instance := range f.waittime {
|
||||
if time.Duration(f.clock.Now()-instance)+txGatherSlack > txArriveTimeout {
|
||||
// Transaction expired without propagation, schedule for retrieval
|
||||
if f.announced[hash] != nil {
|
||||
panic("announce tracker already contains waitlist item")
|
||||
}
|
||||
f.announced[hash] = f.waitlist[hash]
|
||||
for peer := range f.waitlist[hash] {
|
||||
if announces := f.announces[peer]; announces != nil {
|
||||
announces[hash] = struct{}{}
|
||||
} else {
|
||||
f.announces[peer] = map[common.Hash]struct{}{hash: struct{}{}}
|
||||
}
|
||||
delete(f.waitslots[peer], hash)
|
||||
if len(f.waitslots[peer]) == 0 {
|
||||
delete(f.waitslots, peer)
|
||||
}
|
||||
actives[peer] = struct{}{}
|
||||
}
|
||||
delete(f.waittime, hash)
|
||||
delete(f.waitlist, hash)
|
||||
}
|
||||
}
|
||||
// If transactions are still waiting for propagation, reschedule the wait timer
|
||||
if len(f.waittime) > 0 {
|
||||
f.rescheduleWait(waitTimer, waitTrigger)
|
||||
}
|
||||
// If any peers became active and are idle, request transactions from them
|
||||
if len(actives) > 0 {
|
||||
f.scheduleFetches(timeoutTimer, timeoutTrigger, actives)
|
||||
}
|
||||
|
||||
case <-timeoutTrigger:
|
||||
// Clean up any expired retrievals and avoid re-requesting them from the
|
||||
// same peer (either overloaded or malicious, useless in both cases). We
|
||||
// could also penalize (Drop), but there's nothing to gain, and if could
|
||||
// possibly further increase the load on it.
|
||||
for peer, req := range f.requests {
|
||||
if time.Duration(f.clock.Now()-req.time)+txGatherSlack > txFetchTimeout {
|
||||
txRequestTimeoutMeter.Mark(int64(len(req.hashes)))
|
||||
|
||||
// Reschedule all the not-yet-delivered fetches to alternate peers
|
||||
for _, hash := range req.hashes {
|
||||
// Skip rescheduling hashes already delivered by someone else
|
||||
if req.stolen != nil {
|
||||
if _, ok := req.stolen[hash]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Move the delivery back from fetching to queued
|
||||
if _, ok := f.announced[hash]; ok {
|
||||
panic("announced tracker already contains alternate item")
|
||||
}
|
||||
if f.alternates[hash] != nil { // nil if tx was broadcast during fetch
|
||||
f.announced[hash] = f.alternates[hash]
|
||||
}
|
||||
delete(f.announced[hash], peer)
|
||||
if len(f.announced[hash]) == 0 {
|
||||
delete(f.announced, hash)
|
||||
}
|
||||
delete(f.announces[peer], hash)
|
||||
delete(f.alternates, hash)
|
||||
delete(f.fetching, hash)
|
||||
}
|
||||
if len(f.announces[peer]) == 0 {
|
||||
delete(f.announces, peer)
|
||||
}
|
||||
// Keep track of the request as dangling, but never expire
|
||||
f.requests[peer].hashes = nil
|
||||
}
|
||||
}
|
||||
// Schedule a new transaction retrieval
|
||||
f.scheduleFetches(timeoutTimer, timeoutTrigger, nil)
|
||||
|
||||
// No idea if we sheduled something or not, trigger the timer if needed
|
||||
// TODO(karalabe): this is kind of lame, can't we dump it into scheduleFetches somehow?
|
||||
f.rescheduleTimeout(timeoutTimer, timeoutTrigger)
|
||||
|
||||
case delivery := <-f.cleanup:
|
||||
// Independent if the delivery was direct or broadcast, remove all
|
||||
// traces of the hash from internal trackers
|
||||
for _, hash := range delivery.hashes {
|
||||
if _, ok := f.waitlist[hash]; ok {
|
||||
for peer, txset := range f.waitslots {
|
||||
delete(txset, hash)
|
||||
if len(txset) == 0 {
|
||||
delete(f.waitslots, peer)
|
||||
}
|
||||
}
|
||||
delete(f.waitlist, hash)
|
||||
delete(f.waittime, hash)
|
||||
} else {
|
||||
for peer, txset := range f.announces {
|
||||
delete(txset, hash)
|
||||
if len(txset) == 0 {
|
||||
delete(f.announces, peer)
|
||||
}
|
||||
}
|
||||
delete(f.announced, hash)
|
||||
delete(f.alternates, hash)
|
||||
|
||||
// If a transaction currently being fetched from a different
|
||||
// origin was delivered (delivery stolen), mark it so the
|
||||
// actual delivery won't double schedule it.
|
||||
if origin, ok := f.fetching[hash]; ok && (origin != delivery.origin || !delivery.direct) {
|
||||
stolen := f.requests[origin].stolen
|
||||
if stolen == nil {
|
||||
f.requests[origin].stolen = make(map[common.Hash]struct{})
|
||||
stolen = f.requests[origin].stolen
|
||||
}
|
||||
stolen[hash] = struct{}{}
|
||||
}
|
||||
delete(f.fetching, hash)
|
||||
}
|
||||
}
|
||||
// In case of a direct delivery, also reschedule anything missing
|
||||
// from the original query
|
||||
if delivery.direct {
|
||||
// Mark the reqesting successful (independent of individual status)
|
||||
txRequestDoneMeter.Mark(int64(len(delivery.hashes)))
|
||||
|
||||
// Make sure something was pending, nuke it
|
||||
req := f.requests[delivery.origin]
|
||||
if req == nil {
|
||||
log.Warn("Unexpected transaction delivery", "peer", delivery.origin)
|
||||
break
|
||||
}
|
||||
delete(f.requests, delivery.origin)
|
||||
|
||||
// Anything not delivered should be re-scheduled (with or without
|
||||
// this peer, depending on the response cutoff)
|
||||
delivered := make(map[common.Hash]struct{})
|
||||
for _, hash := range delivery.hashes {
|
||||
delivered[hash] = struct{}{}
|
||||
}
|
||||
cutoff := len(req.hashes) // If nothing is delivered, assume everything is missing, don't retry!!!
|
||||
for i, hash := range req.hashes {
|
||||
if _, ok := delivered[hash]; ok {
|
||||
cutoff = i
|
||||
}
|
||||
}
|
||||
// Reschedule missing hashes from alternates, not-fulfilled from alt+self
|
||||
for i, hash := range req.hashes {
|
||||
// Skip rescheduling hashes already delivered by someone else
|
||||
if req.stolen != nil {
|
||||
if _, ok := req.stolen[hash]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, ok := delivered[hash]; !ok {
|
||||
if i < cutoff {
|
||||
delete(f.alternates[hash], delivery.origin)
|
||||
delete(f.announces[delivery.origin], hash)
|
||||
if len(f.announces[delivery.origin]) == 0 {
|
||||
delete(f.announces, delivery.origin)
|
||||
}
|
||||
}
|
||||
if len(f.alternates[hash]) > 0 {
|
||||
if _, ok := f.announced[hash]; ok {
|
||||
panic(fmt.Sprintf("announced tracker already contains alternate item: %v", f.announced[hash]))
|
||||
}
|
||||
f.announced[hash] = f.alternates[hash]
|
||||
}
|
||||
}
|
||||
delete(f.alternates, hash)
|
||||
delete(f.fetching, hash)
|
||||
}
|
||||
// Something was delivered, try to rechedule requests
|
||||
f.scheduleFetches(timeoutTimer, timeoutTrigger, nil) // Partial delivery may enable others to deliver too
|
||||
}
|
||||
|
||||
case drop := <-f.drop:
|
||||
// A peer was dropped, remove all traces of it
|
||||
if _, ok := f.waitslots[drop.peer]; ok {
|
||||
for hash := range f.waitslots[drop.peer] {
|
||||
delete(f.waitlist[hash], drop.peer)
|
||||
if len(f.waitlist[hash]) == 0 {
|
||||
delete(f.waitlist, hash)
|
||||
delete(f.waittime, hash)
|
||||
}
|
||||
}
|
||||
delete(f.waitslots, drop.peer)
|
||||
if len(f.waitlist) > 0 {
|
||||
f.rescheduleWait(waitTimer, waitTrigger)
|
||||
}
|
||||
}
|
||||
// Clean up any active requests
|
||||
var request *txRequest
|
||||
if request = f.requests[drop.peer]; request != nil {
|
||||
for _, hash := range request.hashes {
|
||||
// Skip rescheduling hashes already delivered by someone else
|
||||
if request.stolen != nil {
|
||||
if _, ok := request.stolen[hash]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Undelivered hash, reschedule if there's an alternative origin available
|
||||
delete(f.alternates[hash], drop.peer)
|
||||
if len(f.alternates[hash]) == 0 {
|
||||
delete(f.alternates, hash)
|
||||
} else {
|
||||
f.announced[hash] = f.alternates[hash]
|
||||
delete(f.alternates, hash)
|
||||
}
|
||||
delete(f.fetching, hash)
|
||||
}
|
||||
delete(f.requests, drop.peer)
|
||||
}
|
||||
// Clean up general announcement tracking
|
||||
if _, ok := f.announces[drop.peer]; ok {
|
||||
for hash := range f.announces[drop.peer] {
|
||||
delete(f.announced[hash], drop.peer)
|
||||
if len(f.announced[hash]) == 0 {
|
||||
delete(f.announced, hash)
|
||||
}
|
||||
}
|
||||
delete(f.announces, drop.peer)
|
||||
}
|
||||
// If a request was cancelled, check if anything needs to be rescheduled
|
||||
if request != nil {
|
||||
f.scheduleFetches(timeoutTimer, timeoutTrigger, nil)
|
||||
f.rescheduleTimeout(timeoutTimer, timeoutTrigger)
|
||||
}
|
||||
|
||||
case <-f.quit:
|
||||
return
|
||||
}
|
||||
// No idea what happened, but bump some sanity metrics
|
||||
txFetcherWaitingPeers.Update(int64(len(f.waitslots)))
|
||||
txFetcherWaitingHashes.Update(int64(len(f.waitlist)))
|
||||
txFetcherQueueingPeers.Update(int64(len(f.announces) - len(f.requests)))
|
||||
txFetcherQueueingHashes.Update(int64(len(f.announced)))
|
||||
txFetcherFetchingPeers.Update(int64(len(f.requests)))
|
||||
txFetcherFetchingHashes.Update(int64(len(f.fetching)))
|
||||
|
||||
// Loop did something, ping the step notifier if needed (tests)
|
||||
if f.step != nil {
|
||||
f.step <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rescheduleWait iterates over all the transactions currently in the waitlist
|
||||
// and schedules the movement into the fetcher for the earliest.
|
||||
//
|
||||
// The method has a granularity of 'gatherSlack', since there's not much point in
|
||||
// spinning over all the transactions just to maybe find one that should trigger
|
||||
// a few ms earlier.
|
||||
func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) {
|
||||
if *timer != nil {
|
||||
(*timer).Stop()
|
||||
}
|
||||
now := f.clock.Now()
|
||||
|
||||
earliest := now
|
||||
for _, instance := range f.waittime {
|
||||
if earliest > instance {
|
||||
earliest = instance
|
||||
if txArriveTimeout-time.Duration(now-earliest) < gatherSlack {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
*timer = f.clock.AfterFunc(txArriveTimeout-time.Duration(now-earliest), func() {
|
||||
trigger <- struct{}{}
|
||||
})
|
||||
}
|
||||
|
||||
// rescheduleTimeout iterates over all the transactions currently in flight and
|
||||
// schedules a cleanup run when the first would trigger.
|
||||
//
|
||||
// The method has a granularity of 'gatherSlack', since there's not much point in
|
||||
// spinning over all the transactions just to maybe find one that should trigger
|
||||
// a few ms earlier.
|
||||
//
|
||||
// This method is a bit "flaky" "by design". In theory the timeout timer only ever
|
||||
// should be rescheduled if some request is pending. In practice, a timeout will
|
||||
// cause the timer to be rescheduled every 5 secs (until the peer comes through or
|
||||
// disconnects). This is a limitation of the fetcher code because we don't trac
|
||||
// pending requests and timed out requests separatey. Without double tracking, if
|
||||
// we simply didn't reschedule the timer on all-timeout then the timer would never
|
||||
// be set again since len(request) > 0 => something's running.
|
||||
func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{}) {
|
||||
if *timer != nil {
|
||||
(*timer).Stop()
|
||||
}
|
||||
now := f.clock.Now()
|
||||
|
||||
earliest := now
|
||||
for _, req := range f.requests {
|
||||
// If this request already timed out, skip it altogether
|
||||
if req.hashes == nil {
|
||||
continue
|
||||
}
|
||||
if earliest > req.time {
|
||||
earliest = req.time
|
||||
if txFetchTimeout-time.Duration(now-earliest) < gatherSlack {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
*timer = f.clock.AfterFunc(txFetchTimeout-time.Duration(now-earliest), func() {
|
||||
trigger <- struct{}{}
|
||||
})
|
||||
}
|
||||
|
||||
// scheduleFetches starts a batch of retrievals for all available idle peers.
|
||||
func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, whitelist map[string]struct{}) {
|
||||
// Gather the set of peers we want to retrieve from (default to all)
|
||||
actives := whitelist
|
||||
if actives == nil {
|
||||
actives = make(map[string]struct{})
|
||||
for peer := range f.announces {
|
||||
actives[peer] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(actives) == 0 {
|
||||
return
|
||||
}
|
||||
// For each active peer, try to schedule some transaction fetches
|
||||
idle := len(f.requests) == 0
|
||||
|
||||
f.forEachPeer(actives, func(peer string) {
|
||||
if f.requests[peer] != nil {
|
||||
return // continue in the for-each
|
||||
}
|
||||
if len(f.announces[peer]) == 0 {
|
||||
return // continue in the for-each
|
||||
}
|
||||
hashes := make([]common.Hash, 0, maxTxRetrievals)
|
||||
f.forEachHash(f.announces[peer], func(hash common.Hash) bool {
|
||||
if _, ok := f.fetching[hash]; !ok {
|
||||
// Mark the hash as fetching and stash away possible alternates
|
||||
f.fetching[hash] = peer
|
||||
|
||||
if _, ok := f.alternates[hash]; ok {
|
||||
panic(fmt.Sprintf("alternate tracker already contains fetching item: %v", f.alternates[hash]))
|
||||
}
|
||||
f.alternates[hash] = f.announced[hash]
|
||||
delete(f.announced, hash)
|
||||
|
||||
// Accumulate the hash and stop if the limit was reached
|
||||
hashes = append(hashes, hash)
|
||||
if len(hashes) >= maxTxRetrievals {
|
||||
return false // break in the for-each
|
||||
}
|
||||
}
|
||||
return true // continue in the for-each
|
||||
})
|
||||
// If any hashes were allocated, request them from the peer
|
||||
if len(hashes) > 0 {
|
||||
f.requests[peer] = &txRequest{hashes: hashes, time: f.clock.Now()}
|
||||
txRequestOutMeter.Mark(int64(len(hashes)))
|
||||
|
||||
go func(peer string, hashes []common.Hash) {
|
||||
// Try to fetch the transactions, but in case of a request
|
||||
// failure (e.g. peer disconnected), reschedule the hashes.
|
||||
if err := f.fetchTxs(peer, hashes); err != nil {
|
||||
txRequestFailMeter.Mark(int64(len(hashes)))
|
||||
f.Drop(peer)
|
||||
}
|
||||
}(peer, hashes)
|
||||
}
|
||||
})
|
||||
// If a new request was fired, schedule a timeout timer
|
||||
if idle && len(f.requests) > 0 {
|
||||
f.rescheduleTimeout(timer, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// forEachPeer does a range loop over a map of peers in production, but during
|
||||
// testing it does a deterministic sorted random to allow reproducing issues.
|
||||
func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string)) {
|
||||
// If we're running production, use whatever Go's map gives us
|
||||
if f.rand == nil {
|
||||
for peer := range peers {
|
||||
do(peer)
|
||||
}
|
||||
return
|
||||
}
|
||||
// We're running the test suite, make iteration deterministic
|
||||
list := make([]string, 0, len(peers))
|
||||
for peer := range peers {
|
||||
list = append(list, peer)
|
||||
}
|
||||
sort.Strings(list)
|
||||
rotateStrings(list, f.rand.Intn(len(list)))
|
||||
for _, peer := range list {
|
||||
do(peer)
|
||||
}
|
||||
}
|
||||
|
||||
// forEachHash does a range loop over a map of hashes in production, but during
|
||||
// testing it does a deterministic sorted random to allow reproducing issues.
|
||||
func (f *TxFetcher) forEachHash(hashes map[common.Hash]struct{}, do func(hash common.Hash) bool) {
|
||||
// If we're running production, use whatever Go's map gives us
|
||||
if f.rand == nil {
|
||||
for hash := range hashes {
|
||||
if !do(hash) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
// We're running the test suite, make iteration deterministic
|
||||
list := make([]common.Hash, 0, len(hashes))
|
||||
for hash := range hashes {
|
||||
list = append(list, hash)
|
||||
}
|
||||
sortHashes(list)
|
||||
rotateHashes(list, f.rand.Intn(len(list)))
|
||||
for _, hash := range list {
|
||||
if !do(hash) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rotateStrings rotates the contents of a slice by n steps. This method is only
|
||||
// used in tests to simulate random map iteration but keep it deterministic.
|
||||
func rotateStrings(slice []string, n int) {
|
||||
orig := make([]string, len(slice))
|
||||
copy(orig, slice)
|
||||
|
||||
for i := 0; i < len(orig); i++ {
|
||||
slice[i] = orig[(i+n)%len(orig)]
|
||||
}
|
||||
}
|
||||
|
||||
// sortHashes sorts a slice of hashes. This method is only used in tests in order
|
||||
// to simulate random map iteration but keep it deterministic.
|
||||
func sortHashes(slice []common.Hash) {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
for j := i + 1; j < len(slice); j++ {
|
||||
if bytes.Compare(slice[i][:], slice[j][:]) > 0 {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rotateHashes rotates the contents of a slice by n steps. This method is only
|
||||
// used in tests to simulate random map iteration but keep it deterministic.
|
||||
func rotateHashes(slice []common.Hash, n int) {
|
||||
orig := make([]common.Hash, len(slice))
|
||||
copy(orig, slice)
|
||||
|
||||
for i := 0; i < len(orig); i++ {
|
||||
slice[i] = orig[(i+n)%len(orig)]
|
||||
}
|
||||
}
|
1528
eth/fetcher/tx_fetcher_test.go
Normal file
1528
eth/fetcher/tx_fetcher_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId uint64
|
||||
SyncMode downloader.SyncMode
|
||||
DiscoveryURLs []string
|
||||
NoPruning bool
|
||||
NoPrefetch bool
|
||||
Whitelist map[uint64]common.Hash `toml:"-"`
|
||||
@ -49,11 +50,14 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
RPCGasCap *big.Int `toml:",omitempty"`
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideIstanbul *big.Int `toml:",omitempty"`
|
||||
OverrideMuirGlacier *big.Int `toml:",omitempty"`
|
||||
}
|
||||
var enc Config
|
||||
enc.Genesis = c.Genesis
|
||||
enc.NetworkId = c.NetworkId
|
||||
enc.SyncMode = c.SyncMode
|
||||
enc.DiscoveryURLs = c.DiscoveryURLs
|
||||
enc.NoPruning = c.NoPruning
|
||||
enc.NoPrefetch = c.NoPrefetch
|
||||
enc.Whitelist = c.Whitelist
|
||||
@ -82,6 +86,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||
enc.RPCGasCap = c.RPCGasCap
|
||||
enc.Checkpoint = c.Checkpoint
|
||||
enc.CheckpointOracle = c.CheckpointOracle
|
||||
enc.OverrideIstanbul = c.OverrideIstanbul
|
||||
enc.OverrideMuirGlacier = c.OverrideMuirGlacier
|
||||
return &enc, nil
|
||||
}
|
||||
|
||||
@ -91,6 +97,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId *uint64
|
||||
SyncMode *downloader.SyncMode
|
||||
DiscoveryURLs []string
|
||||
NoPruning *bool
|
||||
NoPrefetch *bool
|
||||
Whitelist map[uint64]common.Hash `toml:"-"`
|
||||
@ -119,6 +126,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
RPCGasCap *big.Int `toml:",omitempty"`
|
||||
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
OverrideIstanbul *big.Int `toml:",omitempty"`
|
||||
OverrideMuirGlacier *big.Int `toml:",omitempty"`
|
||||
}
|
||||
var dec Config
|
||||
if err := unmarshal(&dec); err != nil {
|
||||
@ -133,6 +142,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.SyncMode != nil {
|
||||
c.SyncMode = *dec.SyncMode
|
||||
}
|
||||
if dec.DiscoveryURLs != nil {
|
||||
c.DiscoveryURLs = dec.DiscoveryURLs
|
||||
}
|
||||
if dec.NoPruning != nil {
|
||||
c.NoPruning = *dec.NoPruning
|
||||
}
|
||||
@ -217,5 +229,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
if dec.CheckpointOracle != nil {
|
||||
c.CheckpointOracle = dec.CheckpointOracle
|
||||
}
|
||||
if dec.OverrideIstanbul != nil {
|
||||
c.OverrideIstanbul = dec.OverrideIstanbul
|
||||
}
|
||||
if dec.OverrideMuirGlacier != nil {
|
||||
c.OverrideMuirGlacier = dec.OverrideMuirGlacier
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
157
eth/handler.go
157
eth/handler.go
@ -50,9 +50,6 @@ const (
|
||||
// txChanSize is the size of channel listening to NewTxsEvent.
|
||||
// The number is referenced from the size of tx pool.
|
||||
txChanSize = 4096
|
||||
|
||||
// minimim number of peers to broadcast new blocks to
|
||||
minBroadcastPeers = 4
|
||||
)
|
||||
|
||||
var (
|
||||
@ -77,9 +74,10 @@ type ProtocolManager struct {
|
||||
blockchain *core.BlockChain
|
||||
maxPeers int
|
||||
|
||||
downloader *downloader.Downloader
|
||||
fetcher *fetcher.Fetcher
|
||||
peers *peerSet
|
||||
downloader *downloader.Downloader
|
||||
blockFetcher *fetcher.BlockFetcher
|
||||
txFetcher *fetcher.TxFetcher
|
||||
peers *peerSet
|
||||
|
||||
eventMux *event.TypeMux
|
||||
txsCh chan core.NewTxsEvent
|
||||
@ -97,6 +95,9 @@ type ProtocolManager struct {
|
||||
// wait group is used for graceful shutdowns during downloading
|
||||
// and processing
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Test fields or hooks
|
||||
broadcastTxAnnouncesOnly bool // Testing field, disable transaction propagation
|
||||
}
|
||||
|
||||
// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
|
||||
@ -187,7 +188,16 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)
|
||||
manager.blockFetcher = fetcher.NewBlockFetcher(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)
|
||||
|
||||
fetchTx := func(peer string, hashes []common.Hash) error {
|
||||
p := manager.peers.Peer(peer)
|
||||
if p == nil {
|
||||
return errors.New("unknown peer")
|
||||
}
|
||||
return p.RequestTxs(hashes)
|
||||
}
|
||||
manager.txFetcher = fetcher.NewTxFetcher(txpool.Has, txpool.AddRemotes, fetchTx)
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
@ -203,7 +213,7 @@ func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol {
|
||||
Version: version,
|
||||
Length: length,
|
||||
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
peer := pm.newPeer(int(version), p, rw)
|
||||
peer := pm.newPeer(int(version), p, rw, pm.txpool.Get)
|
||||
select {
|
||||
case pm.newPeerCh <- peer:
|
||||
pm.wg.Add(1)
|
||||
@ -235,6 +245,8 @@ func (pm *ProtocolManager) removePeer(id string) {
|
||||
|
||||
// Unregister the peer from the downloader and Ethereum peer set
|
||||
pm.downloader.UnregisterPeer(id)
|
||||
pm.txFetcher.Drop(id)
|
||||
|
||||
if err := pm.peers.Unregister(id); err != nil {
|
||||
log.Error("Peer removal failed", "peer", id, "err", err)
|
||||
}
|
||||
@ -258,7 +270,7 @@ func (pm *ProtocolManager) Start(maxPeers int) {
|
||||
|
||||
// start sync handlers
|
||||
go pm.syncer()
|
||||
go pm.txsyncLoop()
|
||||
go pm.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64.
|
||||
}
|
||||
|
||||
func (pm *ProtocolManager) Stop() {
|
||||
@ -286,8 +298,8 @@ func (pm *ProtocolManager) Stop() {
|
||||
log.Info("Ethereum protocol stopped")
|
||||
}
|
||||
|
||||
func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
||||
return newPeer(pv, p, newMeteredMsgWriter(rw))
|
||||
func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer {
|
||||
return newPeer(pv, p, rw, getPooledTx)
|
||||
}
|
||||
|
||||
// handle is the callback invoked to manage the life cycle of an eth peer. When
|
||||
@ -311,9 +323,6 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||
p.Log().Debug("Ethereum handshake failed", "err", err)
|
||||
return err
|
||||
}
|
||||
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
|
||||
rw.Init(p.version)
|
||||
}
|
||||
// Register the peer locally
|
||||
if err := pm.peers.Register(p); err != nil {
|
||||
p.Log().Error("Ethereum peer registration failed", "err", err)
|
||||
@ -514,7 +523,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
p.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want)
|
||||
}
|
||||
// Irrelevant of the fork checks, send the header to the fetcher just in case
|
||||
headers = pm.fetcher.FilterHeaders(p.id, headers, time.Now())
|
||||
headers = pm.blockFetcher.FilterHeaders(p.id, headers, time.Now())
|
||||
}
|
||||
if len(headers) > 0 || !filter {
|
||||
err := pm.downloader.DeliverHeaders(p.id, headers)
|
||||
@ -567,7 +576,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
// Filter out any explicitly requested bodies, deliver the rest to the downloader
|
||||
filter := len(transactions) > 0 || len(uncles) > 0
|
||||
if filter {
|
||||
transactions, uncles = pm.fetcher.FilterBodies(p.id, transactions, uncles, time.Now())
|
||||
transactions, uncles = pm.blockFetcher.FilterBodies(p.id, transactions, uncles, time.Now())
|
||||
}
|
||||
if len(transactions) > 0 || len(uncles) > 0 || !filter {
|
||||
err := pm.downloader.DeliverBodies(p.id, transactions, uncles)
|
||||
@ -678,7 +687,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
}
|
||||
}
|
||||
for _, block := range unknown {
|
||||
pm.fetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies)
|
||||
pm.blockFetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies)
|
||||
}
|
||||
|
||||
case msg.Code == NewBlockMsg:
|
||||
@ -703,7 +712,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
|
||||
// Mark the peer as owning the block and schedule it for import
|
||||
p.MarkBlock(request.Block.Hash())
|
||||
pm.fetcher.Enqueue(p.id, request.Block)
|
||||
pm.blockFetcher.Enqueue(p.id, request.Block)
|
||||
|
||||
// Assuming the block is importable by the peer, but possibly not yet done so,
|
||||
// calculate the head hash and TD that the peer truly must have.
|
||||
@ -724,7 +733,59 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
}
|
||||
}
|
||||
|
||||
case msg.Code == TxMsg:
|
||||
case msg.Code == NewPooledTransactionHashesMsg && p.version >= eth65:
|
||||
// New transaction announcement arrived, make sure we have
|
||||
// a valid and fresh chain to handle them
|
||||
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
|
||||
break
|
||||
}
|
||||
var hashes []common.Hash
|
||||
if err := msg.Decode(&hashes); err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
// Schedule all the unknown hashes for retrieval
|
||||
for _, hash := range hashes {
|
||||
p.MarkTransaction(hash)
|
||||
}
|
||||
pm.txFetcher.Notify(p.id, hashes)
|
||||
|
||||
case msg.Code == GetPooledTransactionsMsg && p.version >= eth65:
|
||||
// Decode the retrieval message
|
||||
msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
|
||||
if _, err := msgStream.List(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Gather transactions until the fetch or network limits is reached
|
||||
var (
|
||||
hash common.Hash
|
||||
bytes int
|
||||
hashes []common.Hash
|
||||
txs []rlp.RawValue
|
||||
)
|
||||
for bytes < softResponseLimit {
|
||||
// Retrieve the hash of the next block
|
||||
if err := msgStream.Decode(&hash); err == rlp.EOL {
|
||||
break
|
||||
} else if err != nil {
|
||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
||||
}
|
||||
// Retrieve the requested transaction, skipping if unknown to us
|
||||
tx := pm.txpool.Get(hash)
|
||||
if tx == nil {
|
||||
continue
|
||||
}
|
||||
// If known, encode and queue for response packet
|
||||
if encoded, err := rlp.EncodeToBytes(tx); err != nil {
|
||||
log.Error("Failed to encode transaction", "err", err)
|
||||
} else {
|
||||
hashes = append(hashes, hash)
|
||||
txs = append(txs, encoded)
|
||||
bytes += len(encoded)
|
||||
}
|
||||
}
|
||||
return p.SendPooledTransactionsRLP(hashes, txs)
|
||||
|
||||
case msg.Code == TransactionMsg || (msg.Code == PooledTransactionsMsg && p.version >= eth65):
|
||||
// Transactions arrived, make sure we have a valid and fresh chain to handle them
|
||||
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
|
||||
break
|
||||
@ -741,7 +802,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||
}
|
||||
p.MarkTransaction(tx.Hash())
|
||||
}
|
||||
pm.txpool.AddRemotes(txs)
|
||||
pm.txFetcher.Enqueue(p.id, txs, msg.Code == PooledTransactionsMsg)
|
||||
|
||||
default:
|
||||
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
|
||||
@ -766,14 +827,7 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
|
||||
return
|
||||
}
|
||||
// Send the block to a subset of our peers
|
||||
transferLen := int(math.Sqrt(float64(len(peers))))
|
||||
if transferLen < minBroadcastPeers {
|
||||
transferLen = minBroadcastPeers
|
||||
}
|
||||
if transferLen > len(peers) {
|
||||
transferLen = len(peers)
|
||||
}
|
||||
transfer := peers[:transferLen]
|
||||
transfer := peers[:int(math.Sqrt(float64(len(peers))))]
|
||||
for _, peer := range transfer {
|
||||
peer.AsyncSendNewBlock(block, td)
|
||||
}
|
||||
@ -789,22 +843,43 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastTxs will propagate a batch of transactions to all peers which are not known to
|
||||
// BroadcastTransactions will propagate a batch of transactions to all peers which are not known to
|
||||
// already have the given transaction.
|
||||
func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) {
|
||||
var txset = make(map[*peer]types.Transactions)
|
||||
|
||||
func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propagate bool) {
|
||||
var (
|
||||
txset = make(map[*peer][]common.Hash)
|
||||
annos = make(map[*peer][]common.Hash)
|
||||
)
|
||||
// Broadcast transactions to a batch of peers not knowing about it
|
||||
if propagate {
|
||||
for _, tx := range txs {
|
||||
peers := pm.peers.PeersWithoutTx(tx.Hash())
|
||||
|
||||
// Send the block to a subset of our peers
|
||||
transfer := peers[:int(math.Sqrt(float64(len(peers))))]
|
||||
for _, peer := range transfer {
|
||||
txset[peer] = append(txset[peer], tx.Hash())
|
||||
}
|
||||
log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
|
||||
}
|
||||
for peer, hashes := range txset {
|
||||
peer.AsyncSendTransactions(hashes)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Otherwise only broadcast the announcement to peers
|
||||
for _, tx := range txs {
|
||||
peers := pm.peers.PeersWithoutTx(tx.Hash())
|
||||
for _, peer := range peers {
|
||||
txset[peer] = append(txset[peer], tx)
|
||||
annos[peer] = append(annos[peer], tx.Hash())
|
||||
}
|
||||
log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
|
||||
}
|
||||
// FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
|
||||
for peer, txs := range txset {
|
||||
peer.AsyncSendTransactions(txs)
|
||||
for peer, hashes := range annos {
|
||||
if peer.version >= eth65 {
|
||||
peer.AsyncSendPooledTransactionHashes(hashes)
|
||||
} else {
|
||||
peer.AsyncSendTransactions(hashes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -823,7 +898,13 @@ func (pm *ProtocolManager) txBroadcastLoop() {
|
||||
for {
|
||||
select {
|
||||
case event := <-pm.txsCh:
|
||||
pm.BroadcastTxs(event.Txs)
|
||||
// For testing purpose only, disable propagation
|
||||
if pm.broadcastTxAnnouncesOnly {
|
||||
pm.BroadcastTransactions(event.Txs, false)
|
||||
continue
|
||||
}
|
||||
pm.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers
|
||||
pm.BroadcastTransactions(event.Txs, false) // Only then announce to the rest
|
||||
|
||||
// Err() channel will be closed when unsubscribing.
|
||||
case <-pm.txsSub.Err():
|
||||
|
@ -495,7 +495,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new blockchain: %v", err)
|
||||
}
|
||||
pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), ethash.NewFaker(), blockchain, db, 1, nil)
|
||||
pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||
}
|
||||
@ -554,12 +554,12 @@ func TestBroadcastBlock(t *testing.T) {
|
||||
broadcastExpected int
|
||||
}{
|
||||
{1, 1},
|
||||
{2, 2},
|
||||
{3, 3},
|
||||
{4, 4},
|
||||
{5, 4},
|
||||
{9, 4},
|
||||
{12, 4},
|
||||
{2, 1},
|
||||
{3, 1},
|
||||
{4, 2},
|
||||
{5, 2},
|
||||
{9, 3},
|
||||
{12, 3},
|
||||
{16, 4},
|
||||
{26, 5},
|
||||
{100, 10},
|
||||
@ -582,7 +582,7 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new blockchain: %v", err)
|
||||
}
|
||||
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, 1, nil)
|
||||
pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db, 1, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start test protocol manager: %v", err)
|
||||
}
|
||||
@ -592,6 +592,7 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
|
||||
for i := 0; i < totalPeers; i++ {
|
||||
peer, _ := newTestPeer(fmt.Sprintf("peer %d", i), eth63, pm, true)
|
||||
defer peer.close()
|
||||
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {})
|
||||
@ -608,31 +609,23 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
|
||||
}
|
||||
}(peer)
|
||||
}
|
||||
timeout := time.After(2 * time.Second)
|
||||
var receivedCount int
|
||||
outer:
|
||||
var received int
|
||||
for {
|
||||
select {
|
||||
case err = <-errCh:
|
||||
break outer
|
||||
case <-doneCh:
|
||||
receivedCount++
|
||||
if receivedCount == totalPeers {
|
||||
break outer
|
||||
received++
|
||||
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
if received != broadcastExpected {
|
||||
t.Errorf("broadcast count mismatch: have %d, want %d", received, broadcastExpected)
|
||||
}
|
||||
case <-timeout:
|
||||
break outer
|
||||
return
|
||||
|
||||
case err = <-errCh:
|
||||
t.Fatalf("broadcast failed: %v", err)
|
||||
}
|
||||
}
|
||||
for _, peer := range peers {
|
||||
peer.app.Close()
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("error matching block by peer: %v", err)
|
||||
}
|
||||
if receivedCount != broadcastExpected {
|
||||
t.Errorf("block broadcast to %d peers, expected %d", receivedCount, broadcastExpected)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Tests that a propagated malformed block (uncles or transactions don't match
|
||||
@ -677,7 +670,7 @@ func TestBroadcastMalformedBlock(t *testing.T) {
|
||||
malformedEverything.TxHash[0]++
|
||||
|
||||
// Keep listening to broadcasts and notify if any arrives
|
||||
notify := make(chan struct{})
|
||||
notify := make(chan struct{}, 1)
|
||||
go func() {
|
||||
if _, err := sink.app.ReadMsg(); err == nil {
|
||||
notify <- struct{}{}
|
||||
|
@ -68,7 +68,7 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
|
||||
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db, 1, nil)
|
||||
pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db, 1, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -91,22 +91,43 @@ func newTestProtocolManagerMust(t *testing.T, mode downloader.SyncMode, blocks i
|
||||
// testTxPool is a fake, helper transaction pool for testing purposes
|
||||
type testTxPool struct {
|
||||
txFeed event.Feed
|
||||
pool []*types.Transaction // Collection of all transactions
|
||||
added chan<- []*types.Transaction // Notification channel for new transactions
|
||||
pool map[common.Hash]*types.Transaction // Hash map of collected transactions
|
||||
added chan<- []*types.Transaction // Notification channel for new transactions
|
||||
|
||||
lock sync.RWMutex // Protects the transaction pool
|
||||
}
|
||||
|
||||
// Has returns an indicator whether txpool has a transaction
|
||||
// cached with the given hash.
|
||||
func (p *testTxPool) Has(hash common.Hash) bool {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return p.pool[hash] != nil
|
||||
}
|
||||
|
||||
// Get retrieves the transaction from local txpool with given
|
||||
// tx hash.
|
||||
func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return p.pool[hash]
|
||||
}
|
||||
|
||||
// AddRemotes appends a batch of transactions to the pool, and notifies any
|
||||
// listeners if the addition channel is non nil
|
||||
func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.pool = append(p.pool, txs...)
|
||||
for _, tx := range txs {
|
||||
p.pool[tx.Hash()] = tx
|
||||
}
|
||||
if p.added != nil {
|
||||
p.added <- txs
|
||||
}
|
||||
p.txFeed.Send(core.NewTxsEvent{Txs: txs})
|
||||
return make([]error, len(txs))
|
||||
}
|
||||
|
||||
@ -153,7 +174,7 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te
|
||||
var id enode.ID
|
||||
rand.Read(id[:])
|
||||
|
||||
peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net)
|
||||
peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get)
|
||||
|
||||
// Start the peer on a new thread
|
||||
errc := make(chan error, 1)
|
||||
@ -191,7 +212,7 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesi
|
||||
CurrentBlock: head,
|
||||
GenesisBlock: genesis,
|
||||
}
|
||||
case p.version == eth64:
|
||||
case p.version >= eth64:
|
||||
msg = &statusData{
|
||||
ProtocolVersion: uint32(p.version),
|
||||
NetworkID: DefaultConfig.NetworkId,
|
||||
|
139
eth/metrics.go
139
eth/metrics.go
@ -1,139 +0,0 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
)
|
||||
|
||||
var (
|
||||
propTxnInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/packets", nil)
|
||||
propTxnInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/traffic", nil)
|
||||
propTxnOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/packets", nil)
|
||||
propTxnOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/traffic", nil)
|
||||
propHashInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/packets", nil)
|
||||
propHashInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/traffic", nil)
|
||||
propHashOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/packets", nil)
|
||||
propHashOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/traffic", nil)
|
||||
propBlockInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/packets", nil)
|
||||
propBlockInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/traffic", nil)
|
||||
propBlockOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/packets", nil)
|
||||
propBlockOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/traffic", nil)
|
||||
reqHeaderInPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/in/packets", nil)
|
||||
reqHeaderInTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/in/traffic", nil)
|
||||
reqHeaderOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/out/packets", nil)
|
||||
reqHeaderOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/out/traffic", nil)
|
||||
reqBodyInPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/packets", nil)
|
||||
reqBodyInTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/traffic", nil)
|
||||
reqBodyOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/packets", nil)
|
||||
reqBodyOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/traffic", nil)
|
||||
reqStateInPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/in/packets", nil)
|
||||
reqStateInTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/in/traffic", nil)
|
||||
reqStateOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/out/packets", nil)
|
||||
reqStateOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/out/traffic", nil)
|
||||
reqReceiptInPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/packets", nil)
|
||||
reqReceiptInTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/traffic", nil)
|
||||
reqReceiptOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/packets", nil)
|
||||
reqReceiptOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/traffic", nil)
|
||||
miscInPacketsMeter = metrics.NewRegisteredMeter("eth/misc/in/packets", nil)
|
||||
miscInTrafficMeter = metrics.NewRegisteredMeter("eth/misc/in/traffic", nil)
|
||||
miscOutPacketsMeter = metrics.NewRegisteredMeter("eth/misc/out/packets", nil)
|
||||
miscOutTrafficMeter = metrics.NewRegisteredMeter("eth/misc/out/traffic", nil)
|
||||
)
|
||||
|
||||
// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
|
||||
// accumulating the above defined metrics based on the data stream contents.
|
||||
type meteredMsgReadWriter struct {
|
||||
p2p.MsgReadWriter // Wrapped message stream to meter
|
||||
version int // Protocol version to select correct meters
|
||||
}
|
||||
|
||||
// newMeteredMsgWriter wraps a p2p MsgReadWriter with metering support. If the
|
||||
// metrics system is disabled, this function returns the original object.
|
||||
func newMeteredMsgWriter(rw p2p.MsgReadWriter) p2p.MsgReadWriter {
|
||||
if !metrics.Enabled {
|
||||
return rw
|
||||
}
|
||||
return &meteredMsgReadWriter{MsgReadWriter: rw}
|
||||
}
|
||||
|
||||
// Init sets the protocol version used by the stream to know which meters to
|
||||
// increment in case of overlapping message ids between protocol versions.
|
||||
func (rw *meteredMsgReadWriter) Init(version int) {
|
||||
rw.version = version
|
||||
}
|
||||
|
||||
func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) {
|
||||
// Read the message and short circuit in case of an error
|
||||
msg, err := rw.MsgReadWriter.ReadMsg()
|
||||
if err != nil {
|
||||
return msg, err
|
||||
}
|
||||
// Account for the data traffic
|
||||
packets, traffic := miscInPacketsMeter, miscInTrafficMeter
|
||||
switch {
|
||||
case msg.Code == BlockHeadersMsg:
|
||||
packets, traffic = reqHeaderInPacketsMeter, reqHeaderInTrafficMeter
|
||||
case msg.Code == BlockBodiesMsg:
|
||||
packets, traffic = reqBodyInPacketsMeter, reqBodyInTrafficMeter
|
||||
|
||||
case rw.version >= eth63 && msg.Code == NodeDataMsg:
|
||||
packets, traffic = reqStateInPacketsMeter, reqStateInTrafficMeter
|
||||
case rw.version >= eth63 && msg.Code == ReceiptsMsg:
|
||||
packets, traffic = reqReceiptInPacketsMeter, reqReceiptInTrafficMeter
|
||||
|
||||
case msg.Code == NewBlockHashesMsg:
|
||||
packets, traffic = propHashInPacketsMeter, propHashInTrafficMeter
|
||||
case msg.Code == NewBlockMsg:
|
||||
packets, traffic = propBlockInPacketsMeter, propBlockInTrafficMeter
|
||||
case msg.Code == TxMsg:
|
||||
packets, traffic = propTxnInPacketsMeter, propTxnInTrafficMeter
|
||||
}
|
||||
packets.Mark(1)
|
||||
traffic.Mark(int64(msg.Size))
|
||||
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error {
|
||||
// Account for the data traffic
|
||||
packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter
|
||||
switch {
|
||||
case msg.Code == BlockHeadersMsg:
|
||||
packets, traffic = reqHeaderOutPacketsMeter, reqHeaderOutTrafficMeter
|
||||
case msg.Code == BlockBodiesMsg:
|
||||
packets, traffic = reqBodyOutPacketsMeter, reqBodyOutTrafficMeter
|
||||
|
||||
case rw.version >= eth63 && msg.Code == NodeDataMsg:
|
||||
packets, traffic = reqStateOutPacketsMeter, reqStateOutTrafficMeter
|
||||
case rw.version >= eth63 && msg.Code == ReceiptsMsg:
|
||||
packets, traffic = reqReceiptOutPacketsMeter, reqReceiptOutTrafficMeter
|
||||
|
||||
case msg.Code == NewBlockHashesMsg:
|
||||
packets, traffic = propHashOutPacketsMeter, propHashOutTrafficMeter
|
||||
case msg.Code == NewBlockMsg:
|
||||
packets, traffic = propBlockOutPacketsMeter, propBlockOutTrafficMeter
|
||||
case msg.Code == TxMsg:
|
||||
packets, traffic = propTxnOutPacketsMeter, propTxnOutTrafficMeter
|
||||
}
|
||||
packets.Mark(1)
|
||||
traffic.Mark(int64(msg.Size))
|
||||
|
||||
// Send the packet to the p2p layer
|
||||
return rw.MsgReadWriter.WriteMsg(msg)
|
||||
}
|
339
eth/peer.go
339
eth/peer.go
@ -41,24 +41,35 @@ const (
|
||||
maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
|
||||
maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS)
|
||||
|
||||
// maxQueuedTxs is the maximum number of transaction lists to queue up before
|
||||
// dropping broadcasts. This is a sensitive number as a transaction list might
|
||||
// contain a single transaction, or thousands.
|
||||
maxQueuedTxs = 128
|
||||
// maxQueuedTxs is the maximum number of transactions to queue up before dropping
|
||||
// older broadcasts.
|
||||
maxQueuedTxs = 4096
|
||||
|
||||
// maxQueuedProps is the maximum number of block propagations to queue up before
|
||||
// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
|
||||
// before dropping older announcements.
|
||||
maxQueuedTxAnns = 4096
|
||||
|
||||
// maxQueuedBlocks is the maximum number of block propagations to queue up before
|
||||
// dropping broadcasts. There's not much point in queueing stale blocks, so a few
|
||||
// that might cover uncles should be enough.
|
||||
maxQueuedProps = 4
|
||||
maxQueuedBlocks = 4
|
||||
|
||||
// maxQueuedAnns is the maximum number of block announcements to queue up before
|
||||
// maxQueuedBlockAnns is the maximum number of block announcements to queue up before
|
||||
// dropping broadcasts. Similarly to block propagations, there's no point to queue
|
||||
// above some healthy uncle limit, so use that.
|
||||
maxQueuedAnns = 4
|
||||
maxQueuedBlockAnns = 4
|
||||
|
||||
handshakeTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// max is a helper function which returns the larger of the two given integers.
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known
|
||||
// about a connected peer.
|
||||
type PeerInfo struct {
|
||||
@ -86,48 +97,48 @@ type peer struct {
|
||||
td *big.Int
|
||||
lock sync.RWMutex
|
||||
|
||||
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
|
||||
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
|
||||
queuedTxs chan []*types.Transaction // Queue of transactions to broadcast to the peer
|
||||
queuedProps chan *propEvent // Queue of blocks to broadcast to the peer
|
||||
queuedAnns chan *types.Block // Queue of blocks to announce to the peer
|
||||
term chan struct{} // Termination channel to stop the broadcaster
|
||||
knownBlocks mapset.Set // Set of block hashes known to be known by this peer
|
||||
queuedBlocks chan *propEvent // Queue of blocks to broadcast to the peer
|
||||
queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer
|
||||
|
||||
knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
|
||||
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
|
||||
txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests
|
||||
getPooledTx func(common.Hash) *types.Transaction // Callback used to retrieve transaction from txpool
|
||||
|
||||
term chan struct{} // Termination channel to stop the broadcaster
|
||||
}
|
||||
|
||||
func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
||||
func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer {
|
||||
return &peer{
|
||||
Peer: p,
|
||||
rw: rw,
|
||||
version: version,
|
||||
id: fmt.Sprintf("%x", p.ID().Bytes()[:8]),
|
||||
knownTxs: mapset.NewSet(),
|
||||
knownBlocks: mapset.NewSet(),
|
||||
queuedTxs: make(chan []*types.Transaction, maxQueuedTxs),
|
||||
queuedProps: make(chan *propEvent, maxQueuedProps),
|
||||
queuedAnns: make(chan *types.Block, maxQueuedAnns),
|
||||
term: make(chan struct{}),
|
||||
Peer: p,
|
||||
rw: rw,
|
||||
version: version,
|
||||
id: fmt.Sprintf("%x", p.ID().Bytes()[:8]),
|
||||
knownTxs: mapset.NewSet(),
|
||||
knownBlocks: mapset.NewSet(),
|
||||
queuedBlocks: make(chan *propEvent, maxQueuedBlocks),
|
||||
queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
|
||||
txBroadcast: make(chan []common.Hash),
|
||||
txAnnounce: make(chan []common.Hash),
|
||||
getPooledTx: getPooledTx,
|
||||
term: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// broadcast is a write loop that multiplexes block propagations, announcements
|
||||
// and transaction broadcasts into the remote peer. The goal is to have an async
|
||||
// writer that does not lock up node internals.
|
||||
func (p *peer) broadcast() {
|
||||
// broadcastBlocks is a write loop that multiplexes blocks and block accouncements
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *peer) broadcastBlocks() {
|
||||
for {
|
||||
select {
|
||||
case txs := <-p.queuedTxs:
|
||||
if err := p.SendTransactions(txs); err != nil {
|
||||
return
|
||||
}
|
||||
p.Log().Trace("Broadcast transactions", "count", len(txs))
|
||||
|
||||
case prop := <-p.queuedProps:
|
||||
case prop := <-p.queuedBlocks:
|
||||
if err := p.SendNewBlock(prop.block, prop.td); err != nil {
|
||||
return
|
||||
}
|
||||
p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
|
||||
|
||||
case block := <-p.queuedAnns:
|
||||
case block := <-p.queuedBlockAnns:
|
||||
if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
|
||||
return
|
||||
}
|
||||
@ -139,6 +150,130 @@ func (p *peer) broadcast() {
|
||||
}
|
||||
}
|
||||
|
||||
// broadcastTransactions is a write loop that schedules transaction broadcasts
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *peer) broadcastTransactions() {
|
||||
var (
|
||||
queue []common.Hash // Queue of hashes to broadcast as full transactions
|
||||
done chan struct{} // Non-nil if background broadcaster is running
|
||||
fail = make(chan error) // Channel used to receive network error
|
||||
)
|
||||
for {
|
||||
// If there's no in-flight broadcast running, check if a new one is needed
|
||||
if done == nil && len(queue) > 0 {
|
||||
// Pile transaction until we reach our allowed network limit
|
||||
var (
|
||||
hashes []common.Hash
|
||||
txs []*types.Transaction
|
||||
size common.StorageSize
|
||||
)
|
||||
for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
|
||||
if tx := p.getPooledTx(queue[i]); tx != nil {
|
||||
txs = append(txs, tx)
|
||||
size += tx.Size()
|
||||
}
|
||||
hashes = append(hashes, queue[i])
|
||||
}
|
||||
queue = queue[:copy(queue, queue[len(hashes):])]
|
||||
|
||||
// If there's anything available to transfer, fire up an async writer
|
||||
if len(txs) > 0 {
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
if err := p.sendTransactions(txs); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
p.Log().Trace("Sent transactions", "count", len(txs))
|
||||
}()
|
||||
}
|
||||
}
|
||||
// Transfer goroutine may or may not have been started, listen for events
|
||||
select {
|
||||
case hashes := <-p.txBroadcast:
|
||||
// New batch of transactions to be broadcast, queue them (with cap)
|
||||
queue = append(queue, hashes...)
|
||||
if len(queue) > maxQueuedTxs {
|
||||
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
|
||||
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
|
||||
}
|
||||
|
||||
case <-done:
|
||||
done = nil
|
||||
|
||||
case <-fail:
|
||||
return
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// announceTransactions is a write loop that schedules transaction broadcasts
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
func (p *peer) announceTransactions() {
|
||||
var (
|
||||
queue []common.Hash // Queue of hashes to announce as transaction stubs
|
||||
done chan struct{} // Non-nil if background announcer is running
|
||||
fail = make(chan error) // Channel used to receive network error
|
||||
)
|
||||
for {
|
||||
// If there's no in-flight announce running, check if a new one is needed
|
||||
if done == nil && len(queue) > 0 {
|
||||
// Pile transaction hashes until we reach our allowed network limit
|
||||
var (
|
||||
hashes []common.Hash
|
||||
pending []common.Hash
|
||||
size common.StorageSize
|
||||
)
|
||||
for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
|
||||
if p.getPooledTx(queue[i]) != nil {
|
||||
pending = append(pending, queue[i])
|
||||
size += common.HashLength
|
||||
}
|
||||
hashes = append(hashes, queue[i])
|
||||
}
|
||||
queue = queue[:copy(queue, queue[len(hashes):])]
|
||||
|
||||
// If there's anything available to transfer, fire up an async writer
|
||||
if len(pending) > 0 {
|
||||
done = make(chan struct{})
|
||||
go func() {
|
||||
if err := p.sendPooledTransactionHashes(pending); err != nil {
|
||||
fail <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
p.Log().Trace("Sent transaction announcements", "count", len(pending))
|
||||
}()
|
||||
}
|
||||
}
|
||||
// Transfer goroutine may or may not have been started, listen for events
|
||||
select {
|
||||
case hashes := <-p.txAnnounce:
|
||||
// New batch of transactions to be broadcast, queue them (with cap)
|
||||
queue = append(queue, hashes...)
|
||||
if len(queue) > maxQueuedTxAnns {
|
||||
// Fancy copy and resize to ensure buffer doesn't grow indefinitely
|
||||
queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
|
||||
}
|
||||
|
||||
case <-done:
|
||||
done = nil
|
||||
|
||||
case <-fail:
|
||||
return
|
||||
|
||||
case <-p.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// close signals the broadcast goroutine to terminate.
|
||||
func (p *peer) close() {
|
||||
close(p.term)
|
||||
@ -194,46 +329,111 @@ func (p *peer) MarkTransaction(hash common.Hash) {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
|
||||
// SendTransactions sends transactions to the peer and includes the hashes
|
||||
// SendTransactions64 sends transactions to the peer and includes the hashes
|
||||
// in its transaction hash set for future reference.
|
||||
func (p *peer) SendTransactions(txs types.Transactions) error {
|
||||
//
|
||||
// This method is legacy support for initial transaction exchange in eth/64 and
|
||||
// prior. For eth/65 and higher use SendPooledTransactionHashes.
|
||||
func (p *peer) SendTransactions64(txs types.Transactions) error {
|
||||
return p.sendTransactions(txs)
|
||||
}
|
||||
|
||||
// sendTransactions sends transactions to the peer and includes the hashes
|
||||
// in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction sender. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
func (p *peer) sendTransactions(txs types.Transactions) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, tx := range txs {
|
||||
p.knownTxs.Add(tx.Hash())
|
||||
}
|
||||
for p.knownTxs.Cardinality() >= maxKnownTxs {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
return p2p.Send(p.rw, TxMsg, txs)
|
||||
return p2p.Send(p.rw, TransactionMsg, txs)
|
||||
}
|
||||
|
||||
// AsyncSendTransactions queues list of transactions propagation to a remote
|
||||
// peer. If the peer's broadcast queue is full, the event is silently dropped.
|
||||
func (p *peer) AsyncSendTransactions(txs []*types.Transaction) {
|
||||
// AsyncSendTransactions queues a list of transactions (by hash) to eventually
|
||||
// propagate to a remote peer. The number of pending sends are capped (new ones
|
||||
// will force old sends to be dropped)
|
||||
func (p *peer) AsyncSendTransactions(hashes []common.Hash) {
|
||||
select {
|
||||
case p.queuedTxs <- txs:
|
||||
case p.txBroadcast <- hashes:
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for _, tx := range txs {
|
||||
p.knownTxs.Add(tx.Hash())
|
||||
}
|
||||
for p.knownTxs.Cardinality() >= maxKnownTxs {
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
default:
|
||||
p.Log().Debug("Dropping transaction propagation", "count", len(txs))
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
case <-p.term:
|
||||
p.Log().Debug("Dropping transaction propagation", "count", len(hashes))
|
||||
}
|
||||
}
|
||||
|
||||
// sendPooledTransactionHashes sends transaction hashes to the peer and includes
|
||||
// them in its transaction hash set for future reference.
|
||||
//
|
||||
// This method is a helper used by the async transaction announcer. Don't call it
|
||||
// directly as the queueing (memory) and transmission (bandwidth) costs should
|
||||
// not be managed directly.
|
||||
func (p *peer) sendPooledTransactionHashes(hashes []common.Hash) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
return p2p.Send(p.rw, NewPooledTransactionHashesMsg, hashes)
|
||||
}
|
||||
|
||||
// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
|
||||
// announce to a remote peer. The number of pending sends are capped (new ones
|
||||
// will force old sends to be dropped)
|
||||
func (p *peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) {
|
||||
select {
|
||||
case p.txAnnounce <- hashes:
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
case <-p.term:
|
||||
p.Log().Debug("Dropping transaction announcement", "count", len(hashes))
|
||||
}
|
||||
}
|
||||
|
||||
// SendPooledTransactionsRLP sends requested transactions to the peer and adds the
|
||||
// hashes in its transaction hash set for future reference.
|
||||
//
|
||||
// Note, the method assumes the hashes are correct and correspond to the list of
|
||||
// transactions being sent.
|
||||
func (p *peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error {
|
||||
// Mark all the transactions as known, but ensure we don't overflow our limits
|
||||
for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
|
||||
p.knownTxs.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownTxs.Add(hash)
|
||||
}
|
||||
return p2p.Send(p.rw, PooledTransactionsMsg, txs)
|
||||
}
|
||||
|
||||
// SendNewBlockHashes announces the availability of a number of blocks through
|
||||
// a hash notification.
|
||||
func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
|
||||
// Mark all the block hashes as known, but ensure we don't overflow our limits
|
||||
for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
p.knownBlocks.Add(hash)
|
||||
}
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
request := make(newBlockHashesData, len(hashes))
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
request[i].Hash = hashes[i]
|
||||
@ -247,12 +447,12 @@ func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error
|
||||
// dropped.
|
||||
func (p *peer) AsyncSendNewBlockHash(block *types.Block) {
|
||||
select {
|
||||
case p.queuedAnns <- block:
|
||||
case p.queuedBlockAnns <- block:
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
default:
|
||||
p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
|
||||
}
|
||||
@ -261,10 +461,10 @@ func (p *peer) AsyncSendNewBlockHash(block *types.Block) {
|
||||
// SendNewBlock propagates an entire block to a remote peer.
|
||||
func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td})
|
||||
}
|
||||
|
||||
@ -272,12 +472,12 @@ func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
|
||||
// the peer's broadcast queue is full, the event is silently dropped.
|
||||
func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
|
||||
select {
|
||||
case p.queuedProps <- &propEvent{block: block, td: td}:
|
||||
case p.queuedBlocks <- &propEvent{block: block, td: td}:
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
for p.knownBlocks.Cardinality() >= maxKnownBlocks {
|
||||
p.knownBlocks.Pop()
|
||||
}
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
default:
|
||||
p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
|
||||
}
|
||||
@ -352,6 +552,12 @@ func (p *peer) RequestReceipts(hashes []common.Hash) error {
|
||||
return p2p.Send(p.rw, GetReceiptsMsg, hashes)
|
||||
}
|
||||
|
||||
// RequestTxs fetches a batch of transactions from a remote node.
|
||||
func (p *peer) RequestTxs(hashes []common.Hash) error {
|
||||
p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
|
||||
return p2p.Send(p.rw, GetPooledTransactionsMsg, hashes)
|
||||
}
|
||||
|
||||
// Handshake executes the eth protocol handshake, negotiating version number,
|
||||
// network IDs, difficulties, head and genesis blocks.
|
||||
func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
|
||||
@ -372,7 +578,7 @@ func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis
|
||||
CurrentBlock: head,
|
||||
GenesisBlock: genesis,
|
||||
})
|
||||
case p.version == eth64:
|
||||
case p.version >= eth64:
|
||||
errc <- p2p.Send(p.rw, StatusMsg, &statusData{
|
||||
ProtocolVersion: uint32(p.version),
|
||||
NetworkID: network,
|
||||
@ -389,7 +595,7 @@ func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis
|
||||
switch {
|
||||
case p.version == eth63:
|
||||
errc <- p.readStatusLegacy(network, &status63, genesis)
|
||||
case p.version == eth64:
|
||||
case p.version >= eth64:
|
||||
errc <- p.readStatus(network, &status, genesis, forkFilter)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
|
||||
@ -410,7 +616,7 @@ func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis
|
||||
switch {
|
||||
case p.version == eth63:
|
||||
p.td, p.head = status63.TD, status63.CurrentBlock
|
||||
case p.version == eth64:
|
||||
case p.version >= eth64:
|
||||
p.td, p.head = status.TD, status.Head
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
|
||||
@ -511,7 +717,10 @@ func (ps *peerSet) Register(p *peer) error {
|
||||
return errAlreadyRegistered
|
||||
}
|
||||
ps.peers[p.id] = p
|
||||
go p.broadcast()
|
||||
|
||||
go p.broadcastBlocks()
|
||||
go p.broadcastTransactions()
|
||||
go p.announceTransactions()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -33,16 +33,17 @@ import (
|
||||
const (
|
||||
eth63 = 63
|
||||
eth64 = 64
|
||||
eth65 = 65
|
||||
)
|
||||
|
||||
// protocolName is the official short name of the protocol used during capability negotiation.
|
||||
const protocolName = "eth"
|
||||
|
||||
// ProtocolVersions are the supported versions of the eth protocol (first is primary).
|
||||
var ProtocolVersions = []uint{eth64, eth63}
|
||||
var ProtocolVersions = []uint{eth65, eth64, eth63}
|
||||
|
||||
// protocolLengths are the number of implemented message corresponding to different protocol versions.
|
||||
var protocolLengths = map[uint]uint64{eth64: 17, eth63: 17}
|
||||
var protocolLengths = map[uint]uint64{eth65: 17, eth64: 17, eth63: 17}
|
||||
|
||||
const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
|
||||
|
||||
@ -50,7 +51,7 @@ const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a prot
|
||||
const (
|
||||
StatusMsg = 0x00
|
||||
NewBlockHashesMsg = 0x01
|
||||
TxMsg = 0x02
|
||||
TransactionMsg = 0x02
|
||||
GetBlockHeadersMsg = 0x03
|
||||
BlockHeadersMsg = 0x04
|
||||
GetBlockBodiesMsg = 0x05
|
||||
@ -60,6 +61,14 @@ const (
|
||||
NodeDataMsg = 0x0e
|
||||
GetReceiptsMsg = 0x0f
|
||||
ReceiptsMsg = 0x10
|
||||
|
||||
// New protocol message codes introduced in eth65
|
||||
//
|
||||
// Previously these message ids were used by some legacy and unsupported
|
||||
// eth protocols, reown them here.
|
||||
NewPooledTransactionHashesMsg = 0x08
|
||||
GetPooledTransactionsMsg = 0x09
|
||||
PooledTransactionsMsg = 0x0a
|
||||
)
|
||||
|
||||
type errCode int
|
||||
@ -94,6 +103,14 @@ var errorToString = map[int]string{
|
||||
}
|
||||
|
||||
type txPool interface {
|
||||
// Has returns an indicator whether txpool has a transaction
|
||||
// cached with the given hash.
|
||||
Has(hash common.Hash) bool
|
||||
|
||||
// Get retrieves the transaction from local txpool with given
|
||||
// tx hash.
|
||||
Get(hash common.Hash) *types.Transaction
|
||||
|
||||
// AddRemotes should add the given transactions to the pool.
|
||||
AddRemotes([]*types.Transaction) []error
|
||||
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -61,7 +62,7 @@ func TestStatusMsgErrors63(t *testing.T) {
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
code: TxMsg, data: []interface{}{},
|
||||
code: TransactionMsg, data: []interface{}{},
|
||||
wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
|
||||
},
|
||||
{
|
||||
@ -113,7 +114,7 @@ func TestStatusMsgErrors64(t *testing.T) {
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
code: TxMsg, data: []interface{}{},
|
||||
code: TransactionMsg, data: []interface{}{},
|
||||
wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
|
||||
},
|
||||
{
|
||||
@ -180,16 +181,16 @@ func TestForkIDSplit(t *testing.T) {
|
||||
blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
|
||||
blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
|
||||
|
||||
ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), new(testTxPool), engine, chainNoFork, dbNoFork, 1, nil)
|
||||
ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), new(testTxPool), engine, chainProFork, dbProFork, 1, nil)
|
||||
ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork, 1, nil)
|
||||
ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork, 1, nil)
|
||||
)
|
||||
ethNoFork.Start(1000)
|
||||
ethProFork.Start(1000)
|
||||
|
||||
// Both nodes should allow the other to connect (same genesis, next fork is the same)
|
||||
p2pNoFork, p2pProFork := p2p.MsgPipe()
|
||||
peerNoFork := newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork)
|
||||
peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork)
|
||||
peerNoFork := newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
|
||||
errc := make(chan error, 2)
|
||||
go func() { errc <- ethNoFork.handle(peerProFork) }()
|
||||
@ -207,8 +208,8 @@ func TestForkIDSplit(t *testing.T) {
|
||||
chainProFork.InsertChain(blocksProFork[:1])
|
||||
|
||||
p2pNoFork, p2pProFork = p2p.MsgPipe()
|
||||
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork)
|
||||
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork)
|
||||
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
|
||||
errc = make(chan error, 2)
|
||||
go func() { errc <- ethNoFork.handle(peerProFork) }()
|
||||
@ -226,8 +227,8 @@ func TestForkIDSplit(t *testing.T) {
|
||||
chainProFork.InsertChain(blocksProFork[1:2])
|
||||
|
||||
p2pNoFork, p2pProFork = p2p.MsgPipe()
|
||||
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork)
|
||||
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork)
|
||||
peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
|
||||
peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
|
||||
|
||||
errc = make(chan error, 2)
|
||||
go func() { errc <- ethNoFork.handle(peerProFork) }()
|
||||
@ -246,6 +247,7 @@ func TestForkIDSplit(t *testing.T) {
|
||||
// This test checks that received transactions are added to the local pool.
|
||||
func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) }
|
||||
func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) }
|
||||
func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) }
|
||||
|
||||
func testRecvTransactions(t *testing.T, protocol int) {
|
||||
txAdded := make(chan []*types.Transaction)
|
||||
@ -256,7 +258,7 @@ func testRecvTransactions(t *testing.T, protocol int) {
|
||||
defer p.close()
|
||||
|
||||
tx := newTestTransaction(testAccount, 0, 0)
|
||||
if err := p2p.Send(p.app, TxMsg, []interface{}{tx}); err != nil {
|
||||
if err := p2p.Send(p.app, TransactionMsg, []interface{}{tx}); err != nil {
|
||||
t.Fatalf("send error: %v", err)
|
||||
}
|
||||
select {
|
||||
@ -274,18 +276,22 @@ func testRecvTransactions(t *testing.T, protocol int) {
|
||||
// This test checks that pending transactions are sent.
|
||||
func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) }
|
||||
func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) }
|
||||
func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) }
|
||||
|
||||
func testSendTransactions(t *testing.T, protocol int) {
|
||||
pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
|
||||
defer pm.Stop()
|
||||
|
||||
// Fill the pool with big transactions.
|
||||
// Fill the pool with big transactions (use a subscription to wait until all
|
||||
// the transactions are announced to avoid spurious events causing extra
|
||||
// broadcasts).
|
||||
const txsize = txsyncPackSize / 10
|
||||
alltxs := make([]*types.Transaction, 100)
|
||||
for nonce := range alltxs {
|
||||
alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize)
|
||||
}
|
||||
pm.txpool.AddRemotes(alltxs)
|
||||
time.Sleep(100 * time.Millisecond) // Wait until new tx even gets out of the system (lame)
|
||||
|
||||
// Connect several peers. They should all receive the pending transactions.
|
||||
var wg sync.WaitGroup
|
||||
@ -297,18 +303,50 @@ func testSendTransactions(t *testing.T, protocol int) {
|
||||
seen[tx.Hash()] = false
|
||||
}
|
||||
for n := 0; n < len(alltxs) && !t.Failed(); {
|
||||
var txs []*types.Transaction
|
||||
msg, err := p.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Errorf("%v: read error: %v", p.Peer, err)
|
||||
} else if msg.Code != TxMsg {
|
||||
t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code)
|
||||
var forAllHashes func(callback func(hash common.Hash))
|
||||
switch protocol {
|
||||
case 63:
|
||||
fallthrough
|
||||
case 64:
|
||||
msg, err := p.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Errorf("%v: read error: %v", p.Peer, err)
|
||||
continue
|
||||
} else if msg.Code != TransactionMsg {
|
||||
t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code)
|
||||
continue
|
||||
}
|
||||
var txs []*types.Transaction
|
||||
if err := msg.Decode(&txs); err != nil {
|
||||
t.Errorf("%v: %v", p.Peer, err)
|
||||
continue
|
||||
}
|
||||
forAllHashes = func(callback func(hash common.Hash)) {
|
||||
for _, tx := range txs {
|
||||
callback(tx.Hash())
|
||||
}
|
||||
}
|
||||
case 65:
|
||||
msg, err := p.app.ReadMsg()
|
||||
if err != nil {
|
||||
t.Errorf("%v: read error: %v", p.Peer, err)
|
||||
continue
|
||||
} else if msg.Code != NewPooledTransactionHashesMsg {
|
||||
t.Errorf("%v: got code %d, want NewPooledTransactionHashesMsg", p.Peer, msg.Code)
|
||||
continue
|
||||
}
|
||||
var hashes []common.Hash
|
||||
if err := msg.Decode(&hashes); err != nil {
|
||||
t.Errorf("%v: %v", p.Peer, err)
|
||||
continue
|
||||
}
|
||||
forAllHashes = func(callback func(hash common.Hash)) {
|
||||
for _, h := range hashes {
|
||||
callback(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := msg.Decode(&txs); err != nil {
|
||||
t.Errorf("%v: %v", p.Peer, err)
|
||||
}
|
||||
for _, tx := range txs {
|
||||
hash := tx.Hash()
|
||||
forAllHashes(func(hash common.Hash) {
|
||||
seentx, want := seen[hash]
|
||||
if seentx {
|
||||
t.Errorf("%v: got tx more than once: %x", p.Peer, hash)
|
||||
@ -318,7 +356,7 @@ func testSendTransactions(t *testing.T, protocol int) {
|
||||
}
|
||||
seen[hash] = true
|
||||
n++
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
@ -329,6 +367,53 @@ func testSendTransactions(t *testing.T, protocol int) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestTransactionPropagation(t *testing.T) { testSyncTransaction(t, true) }
|
||||
func TestTransactionAnnouncement(t *testing.T) { testSyncTransaction(t, false) }
|
||||
|
||||
func testSyncTransaction(t *testing.T, propagtion bool) {
|
||||
// Create a protocol manager for transaction fetcher and sender
|
||||
pmFetcher, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
|
||||
defer pmFetcher.Stop()
|
||||
pmSender, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil)
|
||||
pmSender.broadcastTxAnnouncesOnly = !propagtion
|
||||
defer pmSender.Stop()
|
||||
|
||||
// Sync up the two peers
|
||||
io1, io2 := p2p.MsgPipe()
|
||||
|
||||
go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get))
|
||||
go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get))
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
pmFetcher.synchronise(pmFetcher.peers.BestPeer())
|
||||
atomic.StoreUint32(&pmFetcher.acceptTxs, 1)
|
||||
|
||||
newTxs := make(chan core.NewTxsEvent, 1024)
|
||||
sub := pmFetcher.txpool.SubscribeNewTxsEvent(newTxs)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Fill the pool with new transactions
|
||||
alltxs := make([]*types.Transaction, 1024)
|
||||
for nonce := range alltxs {
|
||||
alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), 0)
|
||||
}
|
||||
pmSender.txpool.AddRemotes(alltxs)
|
||||
|
||||
var got int
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case ev := <-newTxs:
|
||||
got += len(ev.Txs)
|
||||
if got == 1024 {
|
||||
break loop
|
||||
}
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Fatal("Failed to retrieve all transaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the custom union field encoder and decoder works correctly.
|
||||
func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
|
||||
// Create a "random" hash for testing
|
||||
|
36
eth/sync.go
36
eth/sync.go
@ -44,6 +44,12 @@ type txsync struct {
|
||||
|
||||
// syncTransactions starts sending all currently pending transactions to the given peer.
|
||||
func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
// Assemble the set of transaction to broadcast or announce to the remote
|
||||
// peer. Fun fact, this is quite an expensive operation as it needs to sort
|
||||
// the transactions if the sorting is not cached yet. However, with a random
|
||||
// order, insertions could overflow the non-executable queues and get dropped.
|
||||
//
|
||||
// TODO(karalabe): Figure out if we could get away with random order somehow
|
||||
var txs types.Transactions
|
||||
pending, _ := pm.txpool.Pending()
|
||||
for _, batch := range pending {
|
||||
@ -52,26 +58,40 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
|
||||
if len(txs) == 0 {
|
||||
return
|
||||
}
|
||||
// The eth/65 protocol introduces proper transaction announcements, so instead
|
||||
// of dripping transactions across multiple peers, just send the entire list as
|
||||
// an announcement and let the remote side decide what they need (likely nothing).
|
||||
if p.version >= eth65 {
|
||||
hashes := make([]common.Hash, len(txs))
|
||||
for i, tx := range txs {
|
||||
hashes[i] = tx.Hash()
|
||||
}
|
||||
p.AsyncSendPooledTransactionHashes(hashes)
|
||||
return
|
||||
}
|
||||
// Out of luck, peer is running legacy protocols, drop the txs over
|
||||
select {
|
||||
case pm.txsyncCh <- &txsync{p, txs}:
|
||||
case pm.txsyncCh <- &txsync{p: p, txs: txs}:
|
||||
case <-pm.quitSync:
|
||||
}
|
||||
}
|
||||
|
||||
// txsyncLoop takes care of the initial transaction sync for each new
|
||||
// txsyncLoop64 takes care of the initial transaction sync for each new
|
||||
// connection. When a new peer appears, we relay all currently pending
|
||||
// transactions. In order to minimise egress bandwidth usage, we send
|
||||
// the transactions in small packs to one peer at a time.
|
||||
func (pm *ProtocolManager) txsyncLoop() {
|
||||
func (pm *ProtocolManager) txsyncLoop64() {
|
||||
var (
|
||||
pending = make(map[enode.ID]*txsync)
|
||||
sending = false // whether a send is active
|
||||
pack = new(txsync) // the pack that is being sent
|
||||
done = make(chan error, 1) // result of the send
|
||||
)
|
||||
|
||||
// send starts a sending a pack of transactions from the sync.
|
||||
send := func(s *txsync) {
|
||||
if s.p.version >= eth65 {
|
||||
panic("initial transaction syncer running on eth/65+")
|
||||
}
|
||||
// Fill pack with transactions up to the target size.
|
||||
size := common.StorageSize(0)
|
||||
pack.p = s.p
|
||||
@ -88,7 +108,7 @@ func (pm *ProtocolManager) txsyncLoop() {
|
||||
// Send the pack in the background.
|
||||
s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size)
|
||||
sending = true
|
||||
go func() { done <- pack.p.SendTransactions(pack.txs) }()
|
||||
go func() { done <- pack.p.SendTransactions64(pack.txs) }()
|
||||
}
|
||||
|
||||
// pick chooses the next pending sync.
|
||||
@ -133,8 +153,10 @@ func (pm *ProtocolManager) txsyncLoop() {
|
||||
// downloading hashes and blocks as well as handling the announcement handler.
|
||||
func (pm *ProtocolManager) syncer() {
|
||||
// Start and ensure cleanup of sync mechanisms
|
||||
pm.fetcher.Start()
|
||||
defer pm.fetcher.Stop()
|
||||
pm.blockFetcher.Start()
|
||||
pm.txFetcher.Start()
|
||||
defer pm.blockFetcher.Stop()
|
||||
defer pm.txFetcher.Stop()
|
||||
defer pm.downloader.Terminate()
|
||||
|
||||
// Wait for different events to fire synchronisation operations
|
||||
|
@ -26,9 +26,13 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
func TestFastSyncDisabling63(t *testing.T) { testFastSyncDisabling(t, 63) }
|
||||
func TestFastSyncDisabling64(t *testing.T) { testFastSyncDisabling(t, 64) }
|
||||
func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) }
|
||||
|
||||
// Tests that fast sync gets disabled as soon as a real block is successfully
|
||||
// imported into the blockchain.
|
||||
func TestFastSyncDisabling(t *testing.T) {
|
||||
func testFastSyncDisabling(t *testing.T, protocol int) {
|
||||
// Create a pristine protocol manager, check that fast sync is left enabled
|
||||
pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
|
||||
if atomic.LoadUint32(&pmEmpty.fastSync) == 0 {
|
||||
@ -42,8 +46,8 @@ func TestFastSyncDisabling(t *testing.T) {
|
||||
// Sync up the two peers
|
||||
io1, io2 := p2p.MsgPipe()
|
||||
|
||||
go pmFull.handle(pmFull.newPeer(63, p2p.NewPeer(enode.ID{}, "empty", nil), io2))
|
||||
go pmEmpty.handle(pmEmpty.newPeer(63, p2p.NewPeer(enode.ID{}, "full", nil), io1))
|
||||
go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get))
|
||||
go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get))
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
pmEmpty.synchronise(pmEmpty.peers.BestPeer())
|
||||
|
@ -93,6 +93,15 @@ type memoryWrapper struct {
|
||||
|
||||
// slice returns the requested range of memory as a byte slice.
|
||||
func (mw *memoryWrapper) slice(begin, end int64) []byte {
|
||||
if end == begin {
|
||||
return []byte{}
|
||||
}
|
||||
if end < begin || begin < 0 {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end)
|
||||
return nil
|
||||
}
|
||||
if mw.memory.Len() < int(end) {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
@ -104,7 +113,7 @@ func (mw *memoryWrapper) slice(begin, end int64) []byte {
|
||||
|
||||
// getUint returns the 32 bytes at the specified address interpreted as a uint.
|
||||
func (mw *memoryWrapper) getUint(addr int64) *big.Int {
|
||||
if mw.memory.Len() < int(addr)+32 {
|
||||
if mw.memory.Len() < int(addr)+32 || addr < 0 {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32)
|
||||
@ -147,7 +156,7 @@ type stackWrapper struct {
|
||||
|
||||
// peek returns the nth-from-the-top element of the stack.
|
||||
func (sw *stackWrapper) peek(idx int) *big.Int {
|
||||
if len(sw.stack.Data()) <= idx {
|
||||
if len(sw.stack.Data()) <= idx || idx < 0 {
|
||||
// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
|
||||
// runtime goes belly up https://github.com/golang/go/issues/15639.
|
||||
log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx)
|
||||
|
@ -63,6 +63,39 @@ func runTrace(tracer *Tracer) (json.RawMessage, error) {
|
||||
return tracer.GetResult()
|
||||
}
|
||||
|
||||
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access
|
||||
func TestRegressionPanicSlice(t *testing.T) {
|
||||
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = runTrace(tracer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks
|
||||
func TestRegressionPanicPeek(t *testing.T) {
|
||||
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = runTrace(tracer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint
|
||||
func TestRegressionPanicGetUint(t *testing.T) {
|
||||
tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = runTrace(tracer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracing(t *testing.T) {
|
||||
tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}")
|
||||
if err != nil {
|
||||
|
@ -138,6 +138,7 @@ func (f *Feed) Send(value interface{}) (nsent int) {
|
||||
|
||||
if !f.typecheck(rvalue.Type()) {
|
||||
f.sendLock <- struct{}{}
|
||||
f.mu.Unlock()
|
||||
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
@ -145,7 +145,6 @@ func (s *resubscribeSub) loop() {
|
||||
func (s *resubscribeSub) subscribe() Subscription {
|
||||
subscribed := make(chan error)
|
||||
var sub Subscription
|
||||
retry:
|
||||
for {
|
||||
s.lastTry = mclock.Now()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@ -157,19 +156,19 @@ retry:
|
||||
select {
|
||||
case err := <-subscribed:
|
||||
cancel()
|
||||
if err != nil {
|
||||
// Subscribing failed, wait before launching the next try.
|
||||
if s.backoffWait() {
|
||||
return nil
|
||||
if err == nil {
|
||||
if sub == nil {
|
||||
panic("event: ResubscribeFunc returned nil subscription and no error")
|
||||
}
|
||||
continue retry
|
||||
return sub
|
||||
}
|
||||
if sub == nil {
|
||||
panic("event: ResubscribeFunc returned nil subscription and no error")
|
||||
// Subscribing failed, wait before launching the next try.
|
||||
if s.backoffWait() {
|
||||
return nil // unsubscribed during wait
|
||||
}
|
||||
return sub
|
||||
case <-s.unsub:
|
||||
cancel()
|
||||
<-subscribed // avoid leaking the s.fn goroutine.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ func TestResubscribe(t *testing.T) {
|
||||
func TestResubscribeAbort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
done := make(chan error)
|
||||
done := make(chan error, 1)
|
||||
sub := Resubscribe(0, func(ctx context.Context) (Subscription, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
@ -26,6 +26,14 @@ targets:
|
||||
function: Fuzz
|
||||
package: github.com/ethereum/go-ethereum/tests/fuzzers/trie
|
||||
checkout: github.com/ethereum/go-ethereum/
|
||||
- name: txfetcher
|
||||
language: go
|
||||
version: "1.13"
|
||||
corpus: ./fuzzers/txfetcher/corpus
|
||||
harness:
|
||||
function: Fuzz
|
||||
package: github.com/ethereum/go-ethereum/tests/fuzzers/txfetcher
|
||||
checkout: github.com/ethereum/go-ethereum/
|
||||
- name: whisperv6
|
||||
language: go
|
||||
version: "1.13"
|
||||
|
@ -314,6 +314,33 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
|
||||
tx, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
return hexutil.Big{}, err
|
||||
}
|
||||
_, r, _ := tx.RawSignatureValues()
|
||||
return hexutil.Big(*r), nil
|
||||
}
|
||||
|
||||
func (t *Transaction) S(ctx context.Context) (hexutil.Big, error) {
|
||||
tx, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
return hexutil.Big{}, err
|
||||
}
|
||||
_, _, s := tx.RawSignatureValues()
|
||||
return hexutil.Big(*s), nil
|
||||
}
|
||||
|
||||
func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) {
|
||||
tx, err := t.resolve(ctx)
|
||||
if err != nil || tx == nil {
|
||||
return hexutil.Big{}, err
|
||||
}
|
||||
v, _, _ := tx.RawSignatureValues()
|
||||
return hexutil.Big(*v), nil
|
||||
}
|
||||
|
||||
type BlockType int
|
||||
|
||||
// Block represents an Ethereum block.
|
||||
|
@ -115,6 +115,9 @@ const schema string = `
|
||||
# Logs is a list of log entries emitted by this transaction. If the
|
||||
# transaction has not yet been mined, this field will be null.
|
||||
logs: [Log!]
|
||||
r: BigInt!
|
||||
s: BigInt!
|
||||
v: BigInt!
|
||||
}
|
||||
|
||||
# BlockFilterCriteria encapsulates log filter criteria for a filter applied
|
||||
|
@ -83,8 +83,10 @@ func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
|
||||
fmt.Printf("downloading from %s\n", url)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil || resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("download error: code %d, err %v", resp.StatusCode, err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("download error: %v", err)
|
||||
} else if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("download error: status %d", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
|
||||
|
@ -642,7 +642,7 @@ func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.B
|
||||
response, err := s.rpcMarshalBlock(block, true, fullTx)
|
||||
if err == nil && number == rpc.PendingBlockNumber {
|
||||
// Pending blocks need to nil out a few fields
|
||||
for _, field := range []string{"hash", "nonce", "miner", "number"} {
|
||||
for _, field := range []string{"hash", "nonce", "miner"} {
|
||||
response[field] = nil
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ func (h *serverHandler) handle(p *peer) error {
|
||||
}
|
||||
// Reject light clients if server is not synced.
|
||||
if !h.synced() {
|
||||
p.Log().Debug("Light server not synced, rejecting peer")
|
||||
return p2p.DiscRequested
|
||||
}
|
||||
defer p.fcClient.Disconnect()
|
||||
|
@ -179,6 +179,19 @@ func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
|
||||
pool.checkDial()
|
||||
pool.wg.Add(1)
|
||||
go pool.eventLoop()
|
||||
|
||||
// Inject the bootstrap nodes as initial dial candiates.
|
||||
pool.wg.Add(1)
|
||||
go func() {
|
||||
defer pool.wg.Done()
|
||||
for _, n := range server.BootstrapNodes {
|
||||
select {
|
||||
case pool.discNodes <- n:
|
||||
case <-pool.closeCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (pool *serverPool) stop() {
|
||||
|
@ -194,7 +194,7 @@ func (n *Node) Start() error {
|
||||
for _, constructor := range n.serviceFuncs {
|
||||
// Create a new context for the particular service
|
||||
ctx := &ServiceContext{
|
||||
config: n.config,
|
||||
Config: *n.config,
|
||||
services: make(map[reflect.Type]Service),
|
||||
EventMux: n.eventmux,
|
||||
AccountManager: n.accman,
|
||||
|
@ -32,20 +32,20 @@ import (
|
||||
// the protocol stack, that is passed to all constructors to be optionally used;
|
||||
// as well as utility methods to operate on the service environment.
|
||||
type ServiceContext struct {
|
||||
config *Config
|
||||
services map[reflect.Type]Service // Index of the already constructed services
|
||||
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
||||
AccountManager *accounts.Manager // Account manager created by the node.
|
||||
Config Config
|
||||
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
||||
AccountManager *accounts.Manager // Account manager created by the node.
|
||||
}
|
||||
|
||||
// OpenDatabase opens an existing database with the given name (or creates one
|
||||
// if no previous can be found) from within the node's data directory. If the
|
||||
// node is an ephemeral one, a memory database is returned.
|
||||
func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, namespace string) (ethdb.Database, error) {
|
||||
if ctx.config.DataDir == "" {
|
||||
if ctx.Config.DataDir == "" {
|
||||
return rawdb.NewMemoryDatabase(), nil
|
||||
}
|
||||
return rawdb.NewLevelDBDatabase(ctx.config.ResolvePath(name), cache, handles, namespace)
|
||||
return rawdb.NewLevelDBDatabase(ctx.Config.ResolvePath(name), cache, handles, namespace)
|
||||
}
|
||||
|
||||
// OpenDatabaseWithFreezer opens an existing database with the given name (or
|
||||
@ -54,16 +54,16 @@ func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, nam
|
||||
// database to immutable append-only files. If the node is an ephemeral one, a
|
||||
// memory database is returned.
|
||||
func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) {
|
||||
if ctx.config.DataDir == "" {
|
||||
if ctx.Config.DataDir == "" {
|
||||
return rawdb.NewMemoryDatabase(), nil
|
||||
}
|
||||
root := ctx.config.ResolvePath(name)
|
||||
root := ctx.Config.ResolvePath(name)
|
||||
|
||||
switch {
|
||||
case freezer == "":
|
||||
freezer = filepath.Join(root, "ancient")
|
||||
case !filepath.IsAbs(freezer):
|
||||
freezer = ctx.config.ResolvePath(freezer)
|
||||
freezer = ctx.Config.ResolvePath(freezer)
|
||||
}
|
||||
return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace)
|
||||
}
|
||||
@ -72,7 +72,7 @@ func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handl
|
||||
// and if the user actually uses persistent storage. It will return an empty string
|
||||
// for emphemeral storage and the user's own input for absolute paths.
|
||||
func (ctx *ServiceContext) ResolvePath(path string) string {
|
||||
return ctx.config.ResolvePath(path)
|
||||
return ctx.Config.ResolvePath(path)
|
||||
}
|
||||
|
||||
// Service retrieves a currently running service registered of a specific type.
|
||||
@ -88,7 +88,7 @@ func (ctx *ServiceContext) Service(service interface{}) error {
|
||||
// ExtRPCEnabled returns the indicator whether node enables the external
|
||||
// RPC(http, ws or graphql).
|
||||
func (ctx *ServiceContext) ExtRPCEnabled() bool {
|
||||
return ctx.config.ExtRPCEnabled()
|
||||
return ctx.Config.ExtRPCEnabled()
|
||||
}
|
||||
|
||||
// ServiceConstructor is the function signature of the constructors needed to be
|
||||
|
@ -38,7 +38,7 @@ func TestContextDatabases(t *testing.T) {
|
||||
t.Fatalf("non-created database already exists")
|
||||
}
|
||||
// Request the opening/creation of a database and ensure it persists to disk
|
||||
ctx := &ServiceContext{config: &Config{Name: "unit-test", DataDir: dir}}
|
||||
ctx := &ServiceContext{Config: Config{Name: "unit-test", DataDir: dir}}
|
||||
db, err := ctx.OpenDatabase("persistent", 0, 0, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open persistent database: %v", err)
|
||||
@ -49,7 +49,7 @@ func TestContextDatabases(t *testing.T) {
|
||||
t.Fatalf("persistent database doesn't exists: %v", err)
|
||||
}
|
||||
// Request th opening/creation of an ephemeral database and ensure it's not persisted
|
||||
ctx = &ServiceContext{config: &Config{DataDir: ""}}
|
||||
ctx = &ServiceContext{Config: Config{DataDir: ""}}
|
||||
db, err = ctx.OpenDatabase("ephemeral", 0, 0, "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open ephemeral database: %v", err)
|
||||
|
637
p2p/dial.go
637
p2p/dial.go
@ -17,11 +17,17 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
@ -33,8 +39,9 @@ const (
|
||||
// private networks.
|
||||
dialHistoryExpiration = inboundThrottleTime + 5*time.Second
|
||||
|
||||
// If no peers are found for this amount of time, the initial bootnodes are dialed.
|
||||
fallbackInterval = 20 * time.Second
|
||||
// Config for the "Looking for peers" message.
|
||||
dialStatsLogInterval = 10 * time.Second // printed at most this often
|
||||
dialStatsPeerLimit = 3 // but not if more than this many dialed peers
|
||||
|
||||
// Endpoint resolution is throttled with bounded backoff.
|
||||
initialResolveDelay = 60 * time.Second
|
||||
@ -42,161 +49,29 @@ const (
|
||||
)
|
||||
|
||||
// NodeDialer is used to connect to nodes in the network, typically by using
|
||||
// an underlying net.Dialer but also using net.Pipe in tests
|
||||
// an underlying net.Dialer but also using net.Pipe in tests.
|
||||
type NodeDialer interface {
|
||||
Dial(*enode.Node) (net.Conn, error)
|
||||
Dial(context.Context, *enode.Node) (net.Conn, error)
|
||||
}
|
||||
|
||||
type nodeResolver interface {
|
||||
Resolve(*enode.Node) *enode.Node
|
||||
}
|
||||
|
||||
// TCPDialer implements the NodeDialer interface by using a net.Dialer to
|
||||
// create TCP connections to nodes in the network
|
||||
type TCPDialer struct {
|
||||
*net.Dialer
|
||||
// tcpDialer implements NodeDialer using real TCP connections.
|
||||
type tcpDialer struct {
|
||||
d *net.Dialer
|
||||
}
|
||||
|
||||
// Dial creates a TCP connection to the node
|
||||
func (t TCPDialer) Dial(dest *enode.Node) (net.Conn, error) {
|
||||
addr := &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()}
|
||||
return t.Dialer.Dial("tcp", addr.String())
|
||||
func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
|
||||
return t.d.DialContext(ctx, "tcp", nodeAddr(dest).String())
|
||||
}
|
||||
|
||||
// dialstate schedules dials and discovery lookups.
|
||||
// It gets a chance to compute new tasks on every iteration
|
||||
// of the main loop in Server.run.
|
||||
type dialstate struct {
|
||||
maxDynDials int
|
||||
netrestrict *netutil.Netlist
|
||||
self enode.ID
|
||||
bootnodes []*enode.Node // default dials when there are no peers
|
||||
log log.Logger
|
||||
|
||||
start time.Time // time when the dialer was first used
|
||||
lookupRunning bool
|
||||
dialing map[enode.ID]connFlag
|
||||
lookupBuf []*enode.Node // current discovery lookup results
|
||||
static map[enode.ID]*dialTask
|
||||
hist expHeap
|
||||
}
|
||||
|
||||
type task interface {
|
||||
Do(*Server)
|
||||
}
|
||||
|
||||
func newDialState(self enode.ID, maxdyn int, cfg *Config) *dialstate {
|
||||
s := &dialstate{
|
||||
maxDynDials: maxdyn,
|
||||
self: self,
|
||||
netrestrict: cfg.NetRestrict,
|
||||
log: cfg.Logger,
|
||||
static: make(map[enode.ID]*dialTask),
|
||||
dialing: make(map[enode.ID]connFlag),
|
||||
bootnodes: make([]*enode.Node, len(cfg.BootstrapNodes)),
|
||||
}
|
||||
copy(s.bootnodes, cfg.BootstrapNodes)
|
||||
if s.log == nil {
|
||||
s.log = log.Root()
|
||||
}
|
||||
for _, n := range cfg.StaticNodes {
|
||||
s.addStatic(n)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *dialstate) addStatic(n *enode.Node) {
|
||||
// This overwrites the task instead of updating an existing
|
||||
// entry, giving users the opportunity to force a resolve operation.
|
||||
s.static[n.ID()] = &dialTask{flags: staticDialedConn, dest: n}
|
||||
}
|
||||
|
||||
func (s *dialstate) removeStatic(n *enode.Node) {
|
||||
// This removes a task so future attempts to connect will not be made.
|
||||
delete(s.static, n.ID())
|
||||
}
|
||||
|
||||
func (s *dialstate) newTasks(nRunning int, peers map[enode.ID]*Peer, now time.Time) []task {
|
||||
var newtasks []task
|
||||
addDial := func(flag connFlag, n *enode.Node) bool {
|
||||
if err := s.checkDial(n, peers); err != nil {
|
||||
s.log.Trace("Skipping dial candidate", "id", n.ID(), "addr", &net.TCPAddr{IP: n.IP(), Port: n.TCP()}, "err", err)
|
||||
return false
|
||||
}
|
||||
s.dialing[n.ID()] = flag
|
||||
newtasks = append(newtasks, &dialTask{flags: flag, dest: n})
|
||||
return true
|
||||
}
|
||||
|
||||
if s.start.IsZero() {
|
||||
s.start = now
|
||||
}
|
||||
s.hist.expire(now)
|
||||
|
||||
// Create dials for static nodes if they are not connected.
|
||||
for id, t := range s.static {
|
||||
err := s.checkDial(t.dest, peers)
|
||||
switch err {
|
||||
case errNotWhitelisted, errSelf:
|
||||
s.log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}, "err", err)
|
||||
delete(s.static, t.dest.ID())
|
||||
case nil:
|
||||
s.dialing[id] = t.flags
|
||||
newtasks = append(newtasks, t)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute number of dynamic dials needed.
|
||||
needDynDials := s.maxDynDials
|
||||
for _, p := range peers {
|
||||
if p.rw.is(dynDialedConn) {
|
||||
needDynDials--
|
||||
}
|
||||
}
|
||||
for _, flag := range s.dialing {
|
||||
if flag&dynDialedConn != 0 {
|
||||
needDynDials--
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have any peers whatsoever, try to dial a random bootnode. This
|
||||
// scenario is useful for the testnet (and private networks) where the discovery
|
||||
// table might be full of mostly bad peers, making it hard to find good ones.
|
||||
if len(peers) == 0 && len(s.bootnodes) > 0 && needDynDials > 0 && now.Sub(s.start) > fallbackInterval {
|
||||
bootnode := s.bootnodes[0]
|
||||
s.bootnodes = append(s.bootnodes[:0], s.bootnodes[1:]...)
|
||||
s.bootnodes = append(s.bootnodes, bootnode)
|
||||
if addDial(dynDialedConn, bootnode) {
|
||||
needDynDials--
|
||||
}
|
||||
}
|
||||
|
||||
// Create dynamic dials from discovery results.
|
||||
i := 0
|
||||
for ; i < len(s.lookupBuf) && needDynDials > 0; i++ {
|
||||
if addDial(dynDialedConn, s.lookupBuf[i]) {
|
||||
needDynDials--
|
||||
}
|
||||
}
|
||||
s.lookupBuf = s.lookupBuf[:copy(s.lookupBuf, s.lookupBuf[i:])]
|
||||
|
||||
// Launch a discovery lookup if more candidates are needed.
|
||||
if len(s.lookupBuf) < needDynDials && !s.lookupRunning {
|
||||
s.lookupRunning = true
|
||||
newtasks = append(newtasks, &discoverTask{want: needDynDials - len(s.lookupBuf)})
|
||||
}
|
||||
|
||||
// Launch a timer to wait for the next node to expire if all
|
||||
// candidates have been tried and no task is currently active.
|
||||
// This should prevent cases where the dialer logic is not ticked
|
||||
// because there are no pending events.
|
||||
if nRunning == 0 && len(newtasks) == 0 && s.hist.Len() > 0 {
|
||||
t := &waitExpireTask{s.hist.nextExpiry().Sub(now)}
|
||||
newtasks = append(newtasks, t)
|
||||
}
|
||||
return newtasks
|
||||
func nodeAddr(n *enode.Node) net.Addr {
|
||||
return &net.TCPAddr{IP: n.IP(), Port: n.TCP()}
|
||||
}
|
||||
|
||||
// checkDial errors:
|
||||
var (
|
||||
errSelf = errors.New("is self")
|
||||
errAlreadyDialing = errors.New("already dialing")
|
||||
@ -205,56 +80,412 @@ var (
|
||||
errNotWhitelisted = errors.New("not contained in netrestrict whitelist")
|
||||
)
|
||||
|
||||
func (s *dialstate) checkDial(n *enode.Node, peers map[enode.ID]*Peer) error {
|
||||
_, dialing := s.dialing[n.ID()]
|
||||
switch {
|
||||
case dialing:
|
||||
return errAlreadyDialing
|
||||
case peers[n.ID()] != nil:
|
||||
return errAlreadyConnected
|
||||
case n.ID() == s.self:
|
||||
// dialer creates outbound connections and submits them into Server.
|
||||
// Two types of peer connections can be created:
|
||||
//
|
||||
// - static dials are pre-configured connections. The dialer attempts
|
||||
// keep these nodes connected at all times.
|
||||
//
|
||||
// - dynamic dials are created from node discovery results. The dialer
|
||||
// continuously reads candidate nodes from its input iterator and attempts
|
||||
// to create peer connections to nodes arriving through the iterator.
|
||||
//
|
||||
type dialScheduler struct {
|
||||
dialConfig
|
||||
setupFunc dialSetupFunc
|
||||
wg sync.WaitGroup
|
||||
cancel context.CancelFunc
|
||||
ctx context.Context
|
||||
nodesIn chan *enode.Node
|
||||
doneCh chan *dialTask
|
||||
addStaticCh chan *enode.Node
|
||||
remStaticCh chan *enode.Node
|
||||
addPeerCh chan *conn
|
||||
remPeerCh chan *conn
|
||||
|
||||
// Everything below here belongs to loop and
|
||||
// should only be accessed by code on the loop goroutine.
|
||||
dialing map[enode.ID]*dialTask // active tasks
|
||||
peers map[enode.ID]connFlag // all connected peers
|
||||
dialPeers int // current number of dialed peers
|
||||
|
||||
// The static map tracks all static dial tasks. The subset of usable static dial tasks
|
||||
// (i.e. those passing checkDial) is kept in staticPool. The scheduler prefers
|
||||
// launching random static tasks from the pool over launching dynamic dials from the
|
||||
// iterator.
|
||||
static map[enode.ID]*dialTask
|
||||
staticPool []*dialTask
|
||||
|
||||
// The dial history keeps recently dialed nodes. Members of history are not dialed.
|
||||
history expHeap
|
||||
historyTimer mclock.Timer
|
||||
historyTimerTime mclock.AbsTime
|
||||
|
||||
// for logStats
|
||||
lastStatsLog mclock.AbsTime
|
||||
doneSinceLastLog int
|
||||
}
|
||||
|
||||
type dialSetupFunc func(net.Conn, connFlag, *enode.Node) error
|
||||
|
||||
type dialConfig struct {
|
||||
self enode.ID // our own ID
|
||||
maxDialPeers int // maximum number of dialed peers
|
||||
maxActiveDials int // maximum number of active dials
|
||||
netRestrict *netutil.Netlist // IP whitelist, disabled if nil
|
||||
resolver nodeResolver
|
||||
dialer NodeDialer
|
||||
log log.Logger
|
||||
clock mclock.Clock
|
||||
rand *mrand.Rand
|
||||
}
|
||||
|
||||
func (cfg dialConfig) withDefaults() dialConfig {
|
||||
if cfg.maxActiveDials == 0 {
|
||||
cfg.maxActiveDials = defaultMaxPendingPeers
|
||||
}
|
||||
if cfg.log == nil {
|
||||
cfg.log = log.Root()
|
||||
}
|
||||
if cfg.clock == nil {
|
||||
cfg.clock = mclock.System{}
|
||||
}
|
||||
if cfg.rand == nil {
|
||||
seedb := make([]byte, 8)
|
||||
crand.Read(seedb)
|
||||
seed := int64(binary.BigEndian.Uint64(seedb))
|
||||
cfg.rand = mrand.New(mrand.NewSource(seed))
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler {
|
||||
d := &dialScheduler{
|
||||
dialConfig: config.withDefaults(),
|
||||
setupFunc: setupFunc,
|
||||
dialing: make(map[enode.ID]*dialTask),
|
||||
static: make(map[enode.ID]*dialTask),
|
||||
peers: make(map[enode.ID]connFlag),
|
||||
doneCh: make(chan *dialTask),
|
||||
nodesIn: make(chan *enode.Node),
|
||||
addStaticCh: make(chan *enode.Node),
|
||||
remStaticCh: make(chan *enode.Node),
|
||||
addPeerCh: make(chan *conn),
|
||||
remPeerCh: make(chan *conn),
|
||||
}
|
||||
d.lastStatsLog = d.clock.Now()
|
||||
d.ctx, d.cancel = context.WithCancel(context.Background())
|
||||
d.wg.Add(2)
|
||||
go d.readNodes(it)
|
||||
go d.loop(it)
|
||||
return d
|
||||
}
|
||||
|
||||
// stop shuts down the dialer, canceling all current dial tasks.
|
||||
func (d *dialScheduler) stop() {
|
||||
d.cancel()
|
||||
d.wg.Wait()
|
||||
}
|
||||
|
||||
// addStatic adds a static dial candidate.
|
||||
func (d *dialScheduler) addStatic(n *enode.Node) {
|
||||
select {
|
||||
case d.addStaticCh <- n:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// removeStatic removes a static dial candidate.
|
||||
func (d *dialScheduler) removeStatic(n *enode.Node) {
|
||||
select {
|
||||
case d.remStaticCh <- n:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// peerAdded updates the peer set.
|
||||
func (d *dialScheduler) peerAdded(c *conn) {
|
||||
select {
|
||||
case d.addPeerCh <- c:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// peerRemoved updates the peer set.
|
||||
func (d *dialScheduler) peerRemoved(c *conn) {
|
||||
select {
|
||||
case d.remPeerCh <- c:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// loop is the main loop of the dialer.
|
||||
func (d *dialScheduler) loop(it enode.Iterator) {
|
||||
var (
|
||||
nodesCh chan *enode.Node
|
||||
historyExp = make(chan struct{}, 1)
|
||||
)
|
||||
|
||||
loop:
|
||||
for {
|
||||
// Launch new dials if slots are available.
|
||||
slots := d.freeDialSlots()
|
||||
slots -= d.startStaticDials(slots)
|
||||
if slots > 0 {
|
||||
nodesCh = d.nodesIn
|
||||
} else {
|
||||
nodesCh = nil
|
||||
}
|
||||
d.rearmHistoryTimer(historyExp)
|
||||
d.logStats()
|
||||
|
||||
select {
|
||||
case node := <-nodesCh:
|
||||
if err := d.checkDial(node); err != nil {
|
||||
d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IP(), "reason", err)
|
||||
} else {
|
||||
d.startDial(newDialTask(node, dynDialedConn))
|
||||
}
|
||||
|
||||
case task := <-d.doneCh:
|
||||
id := task.dest.ID()
|
||||
delete(d.dialing, id)
|
||||
d.updateStaticPool(id)
|
||||
d.doneSinceLastLog++
|
||||
|
||||
case c := <-d.addPeerCh:
|
||||
if c.is(dynDialedConn) || c.is(staticDialedConn) {
|
||||
d.dialPeers++
|
||||
}
|
||||
id := c.node.ID()
|
||||
d.peers[id] = c.flags
|
||||
// Remove from static pool because the node is now connected.
|
||||
task := d.static[id]
|
||||
if task != nil && task.staticPoolIndex >= 0 {
|
||||
d.removeFromStaticPool(task.staticPoolIndex)
|
||||
}
|
||||
// TODO: cancel dials to connected peers
|
||||
|
||||
case c := <-d.remPeerCh:
|
||||
if c.is(dynDialedConn) || c.is(staticDialedConn) {
|
||||
d.dialPeers--
|
||||
}
|
||||
delete(d.peers, c.node.ID())
|
||||
d.updateStaticPool(c.node.ID())
|
||||
|
||||
case node := <-d.addStaticCh:
|
||||
id := node.ID()
|
||||
_, exists := d.static[id]
|
||||
d.log.Trace("Adding static node", "id", id, "ip", node.IP(), "added", !exists)
|
||||
if exists {
|
||||
continue loop
|
||||
}
|
||||
task := newDialTask(node, staticDialedConn)
|
||||
d.static[id] = task
|
||||
if d.checkDial(node) == nil {
|
||||
d.addToStaticPool(task)
|
||||
}
|
||||
|
||||
case node := <-d.remStaticCh:
|
||||
id := node.ID()
|
||||
task := d.static[id]
|
||||
d.log.Trace("Removing static node", "id", id, "ok", task != nil)
|
||||
if task != nil {
|
||||
delete(d.static, id)
|
||||
if task.staticPoolIndex >= 0 {
|
||||
d.removeFromStaticPool(task.staticPoolIndex)
|
||||
}
|
||||
}
|
||||
|
||||
case <-historyExp:
|
||||
d.expireHistory()
|
||||
|
||||
case <-d.ctx.Done():
|
||||
it.Close()
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
d.stopHistoryTimer(historyExp)
|
||||
for range d.dialing {
|
||||
<-d.doneCh
|
||||
}
|
||||
d.wg.Done()
|
||||
}
|
||||
|
||||
// readNodes runs in its own goroutine and delivers nodes from
|
||||
// the input iterator to the nodesIn channel.
|
||||
func (d *dialScheduler) readNodes(it enode.Iterator) {
|
||||
defer d.wg.Done()
|
||||
|
||||
for it.Next() {
|
||||
select {
|
||||
case d.nodesIn <- it.Node():
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logStats prints dialer statistics to the log. The message is suppressed when enough
|
||||
// peers are connected because users should only see it while their client is starting up
|
||||
// or comes back online.
|
||||
func (d *dialScheduler) logStats() {
|
||||
now := d.clock.Now()
|
||||
if d.lastStatsLog.Add(dialStatsLogInterval) > now {
|
||||
return
|
||||
}
|
||||
if d.dialPeers < dialStatsPeerLimit && d.dialPeers < d.maxDialPeers {
|
||||
d.log.Info("Looking for peers", "peercount", len(d.peers), "tried", d.doneSinceLastLog, "static", len(d.static))
|
||||
}
|
||||
d.doneSinceLastLog = 0
|
||||
d.lastStatsLog = now
|
||||
}
|
||||
|
||||
// rearmHistoryTimer configures d.historyTimer to fire when the
|
||||
// next item in d.history expires.
|
||||
func (d *dialScheduler) rearmHistoryTimer(ch chan struct{}) {
|
||||
if len(d.history) == 0 || d.historyTimerTime == d.history.nextExpiry() {
|
||||
return
|
||||
}
|
||||
d.stopHistoryTimer(ch)
|
||||
d.historyTimerTime = d.history.nextExpiry()
|
||||
timeout := time.Duration(d.historyTimerTime - d.clock.Now())
|
||||
d.historyTimer = d.clock.AfterFunc(timeout, func() { ch <- struct{}{} })
|
||||
}
|
||||
|
||||
// stopHistoryTimer stops the timer and drains the channel it sends on.
|
||||
func (d *dialScheduler) stopHistoryTimer(ch chan struct{}) {
|
||||
if d.historyTimer != nil && !d.historyTimer.Stop() {
|
||||
<-ch
|
||||
}
|
||||
}
|
||||
|
||||
// expireHistory removes expired items from d.history.
|
||||
func (d *dialScheduler) expireHistory() {
|
||||
d.historyTimer.Stop()
|
||||
d.historyTimer = nil
|
||||
d.historyTimerTime = 0
|
||||
d.history.expire(d.clock.Now(), func(hkey string) {
|
||||
var id enode.ID
|
||||
copy(id[:], hkey)
|
||||
d.updateStaticPool(id)
|
||||
})
|
||||
}
|
||||
|
||||
// freeDialSlots returns the number of free dial slots. The result can be negative
|
||||
// when peers are connected while their task is still running.
|
||||
func (d *dialScheduler) freeDialSlots() int {
|
||||
slots := (d.maxDialPeers - d.dialPeers) * 2
|
||||
if slots > d.maxActiveDials {
|
||||
slots = d.maxActiveDials
|
||||
}
|
||||
free := slots - len(d.dialing)
|
||||
return free
|
||||
}
|
||||
|
||||
// checkDial returns an error if node n should not be dialed.
|
||||
func (d *dialScheduler) checkDial(n *enode.Node) error {
|
||||
if n.ID() == d.self {
|
||||
return errSelf
|
||||
case s.netrestrict != nil && !s.netrestrict.Contains(n.IP()):
|
||||
}
|
||||
if _, ok := d.dialing[n.ID()]; ok {
|
||||
return errAlreadyDialing
|
||||
}
|
||||
if _, ok := d.peers[n.ID()]; ok {
|
||||
return errAlreadyConnected
|
||||
}
|
||||
if d.netRestrict != nil && !d.netRestrict.Contains(n.IP()) {
|
||||
return errNotWhitelisted
|
||||
case s.hist.contains(string(n.ID().Bytes())):
|
||||
}
|
||||
if d.history.contains(string(n.ID().Bytes())) {
|
||||
return errRecentlyDialed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dialstate) taskDone(t task, now time.Time) {
|
||||
switch t := t.(type) {
|
||||
case *dialTask:
|
||||
s.hist.add(string(t.dest.ID().Bytes()), now.Add(dialHistoryExpiration))
|
||||
delete(s.dialing, t.dest.ID())
|
||||
case *discoverTask:
|
||||
s.lookupRunning = false
|
||||
s.lookupBuf = append(s.lookupBuf, t.results...)
|
||||
// startStaticDials starts n static dial tasks.
|
||||
func (d *dialScheduler) startStaticDials(n int) (started int) {
|
||||
for started = 0; started < n && len(d.staticPool) > 0; started++ {
|
||||
idx := d.rand.Intn(len(d.staticPool))
|
||||
task := d.staticPool[idx]
|
||||
d.startDial(task)
|
||||
d.removeFromStaticPool(idx)
|
||||
}
|
||||
return started
|
||||
}
|
||||
|
||||
// updateStaticPool attempts to move the given static dial back into staticPool.
|
||||
func (d *dialScheduler) updateStaticPool(id enode.ID) {
|
||||
task, ok := d.static[id]
|
||||
if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest) == nil {
|
||||
d.addToStaticPool(task)
|
||||
}
|
||||
}
|
||||
|
||||
// A dialTask is generated for each node that is dialed. Its
|
||||
// fields cannot be accessed while the task is running.
|
||||
func (d *dialScheduler) addToStaticPool(task *dialTask) {
|
||||
if task.staticPoolIndex >= 0 {
|
||||
panic("attempt to add task to staticPool twice")
|
||||
}
|
||||
d.staticPool = append(d.staticPool, task)
|
||||
task.staticPoolIndex = len(d.staticPool) - 1
|
||||
}
|
||||
|
||||
// removeFromStaticPool removes the task at idx from staticPool. It does that by moving the
|
||||
// current last element of the pool to idx and then shortening the pool by one.
|
||||
func (d *dialScheduler) removeFromStaticPool(idx int) {
|
||||
task := d.staticPool[idx]
|
||||
end := len(d.staticPool) - 1
|
||||
d.staticPool[idx] = d.staticPool[end]
|
||||
d.staticPool[idx].staticPoolIndex = idx
|
||||
d.staticPool[end] = nil
|
||||
d.staticPool = d.staticPool[:end]
|
||||
task.staticPoolIndex = -1
|
||||
}
|
||||
|
||||
// startDial runs the given dial task in a separate goroutine.
|
||||
func (d *dialScheduler) startDial(task *dialTask) {
|
||||
d.log.Trace("Starting p2p dial", "id", task.dest.ID(), "ip", task.dest.IP(), "flag", task.flags)
|
||||
hkey := string(task.dest.ID().Bytes())
|
||||
d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration))
|
||||
d.dialing[task.dest.ID()] = task
|
||||
go func() {
|
||||
task.run(d)
|
||||
d.doneCh <- task
|
||||
}()
|
||||
}
|
||||
|
||||
// A dialTask generated for each node that is dialed.
|
||||
type dialTask struct {
|
||||
flags connFlag
|
||||
staticPoolIndex int
|
||||
flags connFlag
|
||||
// These fields are private to the task and should not be
|
||||
// accessed by dialScheduler while the task is running.
|
||||
dest *enode.Node
|
||||
lastResolved time.Time
|
||||
lastResolved mclock.AbsTime
|
||||
resolveDelay time.Duration
|
||||
}
|
||||
|
||||
func (t *dialTask) Do(srv *Server) {
|
||||
func newDialTask(dest *enode.Node, flags connFlag) *dialTask {
|
||||
return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1}
|
||||
}
|
||||
|
||||
type dialError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (t *dialTask) run(d *dialScheduler) {
|
||||
if t.dest.Incomplete() {
|
||||
if !t.resolve(srv) {
|
||||
if !t.resolve(d) {
|
||||
return
|
||||
}
|
||||
}
|
||||
err := t.dial(srv, t.dest)
|
||||
|
||||
err := t.dial(d, t.dest)
|
||||
if err != nil {
|
||||
srv.log.Trace("Dial error", "task", t, "err", err)
|
||||
// Try resolving the ID of static nodes if dialing failed.
|
||||
if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 {
|
||||
if t.resolve(srv) {
|
||||
t.dial(srv, t.dest)
|
||||
if t.resolve(d) {
|
||||
t.dial(d, t.dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,46 +497,42 @@ func (t *dialTask) Do(srv *Server) {
|
||||
// Resolve operations are throttled with backoff to avoid flooding the
|
||||
// discovery network with useless queries for nodes that don't exist.
|
||||
// The backoff delay resets when the node is found.
|
||||
func (t *dialTask) resolve(srv *Server) bool {
|
||||
if srv.staticNodeResolver == nil {
|
||||
srv.log.Debug("Can't resolve node", "id", t.dest.ID(), "err", "discovery is disabled")
|
||||
func (t *dialTask) resolve(d *dialScheduler) bool {
|
||||
if d.resolver == nil {
|
||||
return false
|
||||
}
|
||||
if t.resolveDelay == 0 {
|
||||
t.resolveDelay = initialResolveDelay
|
||||
}
|
||||
if time.Since(t.lastResolved) < t.resolveDelay {
|
||||
if t.lastResolved > 0 && time.Duration(d.clock.Now()-t.lastResolved) < t.resolveDelay {
|
||||
return false
|
||||
}
|
||||
resolved := srv.staticNodeResolver.Resolve(t.dest)
|
||||
t.lastResolved = time.Now()
|
||||
resolved := d.resolver.Resolve(t.dest)
|
||||
t.lastResolved = d.clock.Now()
|
||||
if resolved == nil {
|
||||
t.resolveDelay *= 2
|
||||
if t.resolveDelay > maxResolveDelay {
|
||||
t.resolveDelay = maxResolveDelay
|
||||
}
|
||||
srv.log.Debug("Resolving node failed", "id", t.dest.ID(), "newdelay", t.resolveDelay)
|
||||
d.log.Debug("Resolving node failed", "id", t.dest.ID(), "newdelay", t.resolveDelay)
|
||||
return false
|
||||
}
|
||||
// The node was found.
|
||||
t.resolveDelay = initialResolveDelay
|
||||
t.dest = resolved
|
||||
srv.log.Debug("Resolved node", "id", t.dest.ID(), "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()})
|
||||
d.log.Debug("Resolved node", "id", t.dest.ID(), "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()})
|
||||
return true
|
||||
}
|
||||
|
||||
type dialError struct {
|
||||
error
|
||||
}
|
||||
|
||||
// dial performs the actual connection attempt.
|
||||
func (t *dialTask) dial(srv *Server, dest *enode.Node) error {
|
||||
fd, err := srv.Dialer.Dial(dest)
|
||||
func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
|
||||
fd, err := d.dialer.Dial(d.ctx, t.dest)
|
||||
if err != nil {
|
||||
d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err))
|
||||
return &dialError{err}
|
||||
}
|
||||
mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()})
|
||||
return srv.SetupConn(mfd, t.flags, dest)
|
||||
return d.setupFunc(mfd, t.flags, dest)
|
||||
}
|
||||
|
||||
func (t *dialTask) String() string {
|
||||
@ -313,37 +540,9 @@ func (t *dialTask) String() string {
|
||||
return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP())
|
||||
}
|
||||
|
||||
// discoverTask runs discovery table operations.
|
||||
// Only one discoverTask is active at any time.
|
||||
// discoverTask.Do performs a random lookup.
|
||||
type discoverTask struct {
|
||||
want int
|
||||
results []*enode.Node
|
||||
}
|
||||
|
||||
func (t *discoverTask) Do(srv *Server) {
|
||||
t.results = enode.ReadNodes(srv.discmix, t.want)
|
||||
}
|
||||
|
||||
func (t *discoverTask) String() string {
|
||||
s := "discovery query"
|
||||
if len(t.results) > 0 {
|
||||
s += fmt.Sprintf(" (%d results)", len(t.results))
|
||||
} else {
|
||||
s += fmt.Sprintf(" (want %d)", t.want)
|
||||
func cleanupDialErr(err error) error {
|
||||
if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" {
|
||||
return netErr.Err
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// A waitExpireTask is generated if there are no other tasks
|
||||
// to keep the loop in Server.run ticking.
|
||||
type waitExpireTask struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (t waitExpireTask) Do(*Server) {
|
||||
time.Sleep(t.Duration)
|
||||
}
|
||||
func (t waitExpireTask) String() string {
|
||||
return fmt.Sprintf("wait for dial hist expire (%v)", t.Duration)
|
||||
return err
|
||||
}
|
||||
|
1084
p2p/dial_test.go
1084
p2p/dial_test.go
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@ package dnsdisc
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -176,11 +177,62 @@ func TestIteratorNodeUpdates(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// sync the original tree.
|
||||
// Sync the original tree.
|
||||
resolver.add(tree1.ToTXT("n"))
|
||||
checkIterator(t, it, nodes[:25])
|
||||
|
||||
// Update some nodes and ensure RandomNode returns the new nodes as well.
|
||||
// Ensure RandomNode returns the new nodes after the tree is updated.
|
||||
updateSomeNodes(nodesSeed1, nodes)
|
||||
tree2, _ := makeTestTree("n", nodes, nil)
|
||||
resolver.clear()
|
||||
resolver.add(tree2.ToTXT("n"))
|
||||
t.Log("tree updated")
|
||||
|
||||
clock.Run(c.cfg.RecheckInterval + 1*time.Second)
|
||||
checkIterator(t, it, nodes)
|
||||
}
|
||||
|
||||
// This test checks that the tree root is rechecked when a couple of leaf
|
||||
// requests have failed. The test is just like TestIteratorNodeUpdates, but
|
||||
// without advancing the clock by recheckInterval after the tree update.
|
||||
func TestIteratorRootRecheckOnFail(t *testing.T) {
|
||||
var (
|
||||
clock = new(mclock.Simulated)
|
||||
nodes = testNodes(nodesSeed1, 30)
|
||||
resolver = newMapResolver()
|
||||
c = NewClient(Config{
|
||||
Resolver: resolver,
|
||||
Logger: testlog.Logger(t, log.LvlTrace),
|
||||
RecheckInterval: 20 * time.Minute,
|
||||
RateLimit: 500,
|
||||
// Disabling the cache is required for this test because the client doesn't
|
||||
// notice leaf failures if all records are cached.
|
||||
CacheLimit: 1,
|
||||
})
|
||||
)
|
||||
c.clock = clock
|
||||
tree1, url := makeTestTree("n", nodes[:25], nil)
|
||||
it, err := c.NewIterator(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Sync the original tree.
|
||||
resolver.add(tree1.ToTXT("n"))
|
||||
checkIterator(t, it, nodes[:25])
|
||||
|
||||
// Ensure RandomNode returns the new nodes after the tree is updated.
|
||||
updateSomeNodes(nodesSeed1, nodes)
|
||||
tree2, _ := makeTestTree("n", nodes, nil)
|
||||
resolver.clear()
|
||||
resolver.add(tree2.ToTXT("n"))
|
||||
t.Log("tree updated")
|
||||
|
||||
checkIterator(t, it, nodes)
|
||||
}
|
||||
|
||||
// updateSomeNodes applies ENR updates to some of the given nodes.
|
||||
func updateSomeNodes(keySeed int64, nodes []*enode.Node) {
|
||||
keys := testKeys(nodesSeed1, len(nodes))
|
||||
for i, n := range nodes[:len(nodes)/2] {
|
||||
r := n.Record()
|
||||
@ -190,11 +242,6 @@ func TestIteratorNodeUpdates(t *testing.T) {
|
||||
n2, _ := enode.New(enode.ValidSchemes, r)
|
||||
nodes[i] = n2
|
||||
}
|
||||
tree2, _ := makeTestTree("n", nodes, nil)
|
||||
clock.Run(c.cfg.RecheckInterval + 1*time.Second)
|
||||
resolver.clear()
|
||||
resolver.add(tree2.ToTXT("n"))
|
||||
checkIterator(t, it, nodes)
|
||||
}
|
||||
|
||||
// This test verifies that randomIterator re-checks the root of the tree to catch
|
||||
@ -230,9 +277,10 @@ func TestIteratorLinkUpdates(t *testing.T) {
|
||||
// Add link to tree3, remove link to tree2.
|
||||
tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
|
||||
resolver.add(tree1.ToTXT("t1"))
|
||||
clock.Run(c.cfg.RecheckInterval + 1*time.Second)
|
||||
t.Log("tree1 updated")
|
||||
|
||||
clock.Run(c.cfg.RecheckInterval + 1*time.Second)
|
||||
|
||||
var wantNodes []*enode.Node
|
||||
wantNodes = append(wantNodes, tree1.Nodes()...)
|
||||
wantNodes = append(wantNodes, tree3.Nodes()...)
|
||||
@ -345,5 +393,5 @@ func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, err
|
||||
if record, ok := mr[name]; ok {
|
||||
return []string{record}, nil
|
||||
}
|
||||
return nil, nil
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
@ -25,15 +25,22 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
const (
|
||||
rootRecheckFailCount = 5 // update root if this many leaf requests fail
|
||||
)
|
||||
|
||||
// clientTree is a full tree being synced.
|
||||
type clientTree struct {
|
||||
c *Client
|
||||
loc *linkEntry // link to this tree
|
||||
|
||||
lastRootCheck mclock.AbsTime // last revalidation of root
|
||||
root *rootEntry
|
||||
enrs *subtreeSync
|
||||
links *subtreeSync
|
||||
leafFailCount int
|
||||
rootFailCount int
|
||||
|
||||
root *rootEntry
|
||||
enrs *subtreeSync
|
||||
links *subtreeSync
|
||||
|
||||
lc *linkCache // tracks all links between all trees
|
||||
curLinks map[string]struct{} // links contained in this tree
|
||||
@ -46,7 +53,7 @@ func newClientTree(c *Client, lc *linkCache, loc *linkEntry) *clientTree {
|
||||
|
||||
// syncAll retrieves all entries of the tree.
|
||||
func (ct *clientTree) syncAll(dest map[string]entry) error {
|
||||
if err := ct.updateRoot(); err != nil {
|
||||
if err := ct.updateRoot(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ct.links.resolveAll(dest); err != nil {
|
||||
@ -60,12 +67,20 @@ func (ct *clientTree) syncAll(dest map[string]entry) error {
|
||||
|
||||
// syncRandom retrieves a single entry of the tree. The Node return value
|
||||
// is non-nil if the entry was a node.
|
||||
func (ct *clientTree) syncRandom(ctx context.Context) (*enode.Node, error) {
|
||||
func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) {
|
||||
if ct.rootUpdateDue() {
|
||||
if err := ct.updateRoot(); err != nil {
|
||||
if err := ct.updateRoot(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Update fail counter for leaf request errors.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ct.leafFailCount++
|
||||
}
|
||||
}()
|
||||
|
||||
// Link tree sync has priority, run it to completion before syncing ENRs.
|
||||
if !ct.links.done() {
|
||||
err := ct.syncNextLink(ctx)
|
||||
@ -138,15 +153,22 @@ func removeHash(h []string, index int) []string {
|
||||
}
|
||||
|
||||
// updateRoot ensures that the given tree has an up-to-date root.
|
||||
func (ct *clientTree) updateRoot() error {
|
||||
func (ct *clientTree) updateRoot(ctx context.Context) error {
|
||||
if !ct.slowdownRootUpdate(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
ct.lastRootCheck = ct.c.clock.Now()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ct.c.cfg.Timeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, ct.c.cfg.Timeout)
|
||||
defer cancel()
|
||||
root, err := ct.c.resolveRoot(ctx, ct.loc)
|
||||
if err != nil {
|
||||
ct.rootFailCount++
|
||||
return err
|
||||
}
|
||||
ct.root = &root
|
||||
ct.rootFailCount = 0
|
||||
ct.leafFailCount = 0
|
||||
|
||||
// Invalidate subtrees if changed.
|
||||
if ct.links == nil || root.lroot != ct.links.root {
|
||||
@ -161,7 +183,32 @@ func (ct *clientTree) updateRoot() error {
|
||||
|
||||
// rootUpdateDue returns true when a root update is needed.
|
||||
func (ct *clientTree) rootUpdateDue() bool {
|
||||
return ct.root == nil || time.Duration(ct.c.clock.Now()-ct.lastRootCheck) > ct.c.cfg.RecheckInterval
|
||||
tooManyFailures := ct.leafFailCount > rootRecheckFailCount
|
||||
scheduledCheck := ct.c.clock.Now().Sub(ct.lastRootCheck) > ct.c.cfg.RecheckInterval
|
||||
return ct.root == nil || tooManyFailures || scheduledCheck
|
||||
}
|
||||
|
||||
// slowdownRootUpdate applies a delay to root resolution if is tried
|
||||
// too frequently. This avoids busy polling when the client is offline.
|
||||
// Returns true if the timeout passed, false if sync was canceled.
|
||||
func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool {
|
||||
var delay time.Duration
|
||||
switch {
|
||||
case ct.rootFailCount > 20:
|
||||
delay = 10 * time.Second
|
||||
case ct.rootFailCount > 5:
|
||||
delay = 5 * time.Second
|
||||
default:
|
||||
return true
|
||||
}
|
||||
timeout := ct.c.clock.NewTimer(delay)
|
||||
defer timeout.Stop()
|
||||
select {
|
||||
case <-timeout.C():
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// subtreeSync is the sync of an ENR or link subtree.
|
||||
|
166
p2p/metrics.go
166
p2p/metrics.go
@ -20,102 +20,37 @@ package p2p
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
MetricsInboundTraffic = "p2p/ingress" // Name for the registered inbound traffic meter
|
||||
MetricsOutboundTraffic = "p2p/egress" // Name for the registered outbound traffic meter
|
||||
MetricsOutboundConnects = "p2p/dials" // Name for the registered outbound connects meter
|
||||
MetricsInboundConnects = "p2p/serves" // Name for the registered inbound connects meter
|
||||
|
||||
MeteredPeerLimit = 1024 // This amount of peers are individually metered
|
||||
ingressMeterName = "p2p/ingress"
|
||||
egressMeterName = "p2p/egress"
|
||||
)
|
||||
|
||||
var (
|
||||
ingressConnectMeter = metrics.NewRegisteredMeter(MetricsInboundConnects, nil) // Meter counting the ingress connections
|
||||
ingressTrafficMeter = metrics.NewRegisteredMeter(MetricsInboundTraffic, nil) // Meter metering the cumulative ingress traffic
|
||||
egressConnectMeter = metrics.NewRegisteredMeter(MetricsOutboundConnects, nil) // Meter counting the egress connections
|
||||
egressTrafficMeter = metrics.NewRegisteredMeter(MetricsOutboundTraffic, nil) // Meter metering the cumulative egress traffic
|
||||
activePeerGauge = metrics.NewRegisteredGauge("p2p/peers", nil) // Gauge tracking the current peer count
|
||||
|
||||
PeerIngressRegistry = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsInboundTraffic+"/") // Registry containing the peer ingress
|
||||
PeerEgressRegistry = metrics.NewPrefixedChildRegistry(metrics.EphemeralRegistry, MetricsOutboundTraffic+"/") // Registry containing the peer egress
|
||||
|
||||
meteredPeerFeed event.Feed // Event feed for peer metrics
|
||||
meteredPeerCount int32 // Actually stored peer connection count
|
||||
ingressConnectMeter = metrics.NewRegisteredMeter("p2p/serves", nil)
|
||||
ingressTrafficMeter = metrics.NewRegisteredMeter(ingressMeterName, nil)
|
||||
egressConnectMeter = metrics.NewRegisteredMeter("p2p/dials", nil)
|
||||
egressTrafficMeter = metrics.NewRegisteredMeter(egressMeterName, nil)
|
||||
activePeerGauge = metrics.NewRegisteredGauge("p2p/peers", nil)
|
||||
)
|
||||
|
||||
// MeteredPeerEventType is the type of peer events emitted by a metered connection.
|
||||
type MeteredPeerEventType int
|
||||
|
||||
const (
|
||||
// PeerHandshakeSucceeded is the type of event
|
||||
// emitted when a peer successfully makes the handshake.
|
||||
PeerHandshakeSucceeded MeteredPeerEventType = iota
|
||||
|
||||
// PeerHandshakeFailed is the type of event emitted when a peer fails to
|
||||
// make the handshake or disconnects before it.
|
||||
PeerHandshakeFailed
|
||||
|
||||
// PeerDisconnected is the type of event emitted when a peer disconnects.
|
||||
PeerDisconnected
|
||||
)
|
||||
|
||||
// MeteredPeerEvent is an event emitted when peers connect or disconnect.
|
||||
type MeteredPeerEvent struct {
|
||||
Type MeteredPeerEventType // Type of peer event
|
||||
Addr string // TCP address of the peer
|
||||
Elapsed time.Duration // Time elapsed between the connection and the handshake/disconnection
|
||||
Peer *Peer // Connected remote node instance
|
||||
Ingress uint64 // Ingress count at the moment of the event
|
||||
Egress uint64 // Egress count at the moment of the event
|
||||
}
|
||||
|
||||
// SubscribeMeteredPeerEvent registers a subscription for peer life-cycle events
|
||||
// if metrics collection is enabled.
|
||||
func SubscribeMeteredPeerEvent(ch chan<- MeteredPeerEvent) event.Subscription {
|
||||
return meteredPeerFeed.Subscribe(ch)
|
||||
}
|
||||
|
||||
// meteredConn is a wrapper around a net.Conn that meters both the
|
||||
// inbound and outbound network traffic.
|
||||
type meteredConn struct {
|
||||
net.Conn // Network connection to wrap with metering
|
||||
|
||||
connected time.Time // Connection time of the peer
|
||||
addr *net.TCPAddr // TCP address of the peer
|
||||
peer *Peer // Peer instance
|
||||
|
||||
// trafficMetered denotes if the peer is registered in the traffic registries.
|
||||
// Its value is true if the metered peer count doesn't reach the limit in the
|
||||
// moment of the peer's connection.
|
||||
trafficMetered bool
|
||||
ingressMeter metrics.Meter // Meter for the read bytes of the peer
|
||||
egressMeter metrics.Meter // Meter for the written bytes of the peer
|
||||
|
||||
lock sync.RWMutex // Lock protecting the metered connection's internals
|
||||
net.Conn
|
||||
}
|
||||
|
||||
// newMeteredConn creates a new metered connection, bumps the ingress or egress
|
||||
// connection meter and also increases the metered peer count. If the metrics
|
||||
// system is disabled or the IP address is unspecified, this function returns
|
||||
// the original object.
|
||||
// system is disabled, function returns the original connection.
|
||||
func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn {
|
||||
// Short circuit if metrics are disabled
|
||||
if !metrics.Enabled {
|
||||
return conn
|
||||
}
|
||||
if addr == nil || addr.IP.IsUnspecified() {
|
||||
log.Warn("Peer address is unspecified")
|
||||
return conn
|
||||
}
|
||||
// Bump the connection counters and wrap the connection
|
||||
if ingress {
|
||||
ingressConnectMeter.Mark(1)
|
||||
@ -123,12 +58,7 @@ func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn {
|
||||
egressConnectMeter.Mark(1)
|
||||
}
|
||||
activePeerGauge.Inc(1)
|
||||
|
||||
return &meteredConn{
|
||||
Conn: conn,
|
||||
addr: addr,
|
||||
connected: time.Now(),
|
||||
}
|
||||
return &meteredConn{Conn: conn}
|
||||
}
|
||||
|
||||
// Read delegates a network read to the underlying connection, bumping the common
|
||||
@ -136,11 +66,6 @@ func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn {
|
||||
func (c *meteredConn) Read(b []byte) (n int, err error) {
|
||||
n, err = c.Conn.Read(b)
|
||||
ingressTrafficMeter.Mark(int64(n))
|
||||
c.lock.RLock()
|
||||
if c.trafficMetered {
|
||||
c.ingressMeter.Mark(int64(n))
|
||||
}
|
||||
c.lock.RUnlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
@ -149,84 +74,15 @@ func (c *meteredConn) Read(b []byte) (n int, err error) {
|
||||
func (c *meteredConn) Write(b []byte) (n int, err error) {
|
||||
n, err = c.Conn.Write(b)
|
||||
egressTrafficMeter.Mark(int64(n))
|
||||
c.lock.RLock()
|
||||
if c.trafficMetered {
|
||||
c.egressMeter.Mark(int64(n))
|
||||
}
|
||||
c.lock.RUnlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// handshakeDone is called after the connection passes the handshake.
|
||||
func (c *meteredConn) handshakeDone(peer *Peer) {
|
||||
if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit {
|
||||
// Don't register the peer in the traffic registries.
|
||||
atomic.AddInt32(&meteredPeerCount, -1)
|
||||
c.lock.Lock()
|
||||
c.peer, c.trafficMetered = peer, false
|
||||
c.lock.Unlock()
|
||||
log.Warn("Metered peer count reached the limit")
|
||||
} else {
|
||||
enode := peer.Node().String()
|
||||
c.lock.Lock()
|
||||
c.peer, c.trafficMetered = peer, true
|
||||
c.ingressMeter = metrics.NewRegisteredMeter(enode, PeerIngressRegistry)
|
||||
c.egressMeter = metrics.NewRegisteredMeter(enode, PeerEgressRegistry)
|
||||
c.lock.Unlock()
|
||||
}
|
||||
meteredPeerFeed.Send(MeteredPeerEvent{
|
||||
Type: PeerHandshakeSucceeded,
|
||||
Addr: c.addr.String(),
|
||||
Peer: peer,
|
||||
Elapsed: time.Since(c.connected),
|
||||
})
|
||||
}
|
||||
|
||||
// Close delegates a close operation to the underlying connection, unregisters
|
||||
// the peer from the traffic registries and emits close event.
|
||||
func (c *meteredConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
c.lock.RLock()
|
||||
if c.peer == nil {
|
||||
// If the peer disconnects before/during the handshake.
|
||||
c.lock.RUnlock()
|
||||
meteredPeerFeed.Send(MeteredPeerEvent{
|
||||
Type: PeerHandshakeFailed,
|
||||
Addr: c.addr.String(),
|
||||
Elapsed: time.Since(c.connected),
|
||||
})
|
||||
if err == nil {
|
||||
activePeerGauge.Dec(1)
|
||||
return err
|
||||
}
|
||||
peer := c.peer
|
||||
if !c.trafficMetered {
|
||||
// If the peer isn't registered in the traffic registries.
|
||||
c.lock.RUnlock()
|
||||
meteredPeerFeed.Send(MeteredPeerEvent{
|
||||
Type: PeerDisconnected,
|
||||
Addr: c.addr.String(),
|
||||
Peer: peer,
|
||||
})
|
||||
activePeerGauge.Dec(1)
|
||||
return err
|
||||
}
|
||||
ingress, egress, enode := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count()), c.peer.Node().String()
|
||||
c.lock.RUnlock()
|
||||
|
||||
// Decrement the metered peer count
|
||||
atomic.AddInt32(&meteredPeerCount, -1)
|
||||
|
||||
// Unregister the peer from the traffic registries
|
||||
PeerIngressRegistry.Unregister(enode)
|
||||
PeerEgressRegistry.Unregister(enode)
|
||||
|
||||
meteredPeerFeed.Send(MeteredPeerEvent{
|
||||
Type: PeerDisconnected,
|
||||
Addr: c.addr.String(),
|
||||
Peer: peer,
|
||||
Ingress: ingress,
|
||||
Egress: egress,
|
||||
})
|
||||
activePeerGauge.Dec(1)
|
||||
return err
|
||||
}
|
||||
|
@ -302,7 +302,8 @@ func (p *Peer) handle(msg Msg) error {
|
||||
return fmt.Errorf("msg code out of range: %v", msg.Code)
|
||||
}
|
||||
if metrics.Enabled {
|
||||
metrics.GetOrRegisterMeter(fmt.Sprintf("%s/%s/%d/%#02x", MetricsInboundTraffic, proto.Name, proto.Version, msg.Code-proto.offset), nil).Mark(int64(msg.meterSize))
|
||||
m := fmt.Sprintf("%s/%s/%d/%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset)
|
||||
metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
|
||||
}
|
||||
select {
|
||||
case proto.in <- msg:
|
||||
|
@ -17,15 +17,20 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
)
|
||||
|
||||
var discard = Protocol{
|
||||
@ -45,10 +50,45 @@ var discard = Protocol{
|
||||
},
|
||||
}
|
||||
|
||||
// uintID encodes i into a node ID.
|
||||
func uintID(i uint16) enode.ID {
|
||||
var id enode.ID
|
||||
binary.BigEndian.PutUint16(id[:], i)
|
||||
return id
|
||||
}
|
||||
|
||||
// newNode creates a node record with the given address.
|
||||
func newNode(id enode.ID, addr string) *enode.Node {
|
||||
var r enr.Record
|
||||
if addr != "" {
|
||||
// Set the port if present.
|
||||
if strings.Contains(addr, ":") {
|
||||
hs, ps, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("invalid address %q", addr))
|
||||
}
|
||||
port, err := strconv.Atoi(ps)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("invalid port in %q", addr))
|
||||
}
|
||||
r.Set(enr.TCP(port))
|
||||
r.Set(enr.UDP(port))
|
||||
addr = hs
|
||||
}
|
||||
// Set the IP.
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
panic(fmt.Errorf("invalid IP %q", addr))
|
||||
}
|
||||
r.Set(enr.IP(ip))
|
||||
}
|
||||
return enode.SignNull(&r, id)
|
||||
}
|
||||
|
||||
func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) {
|
||||
fd1, fd2 := net.Pipe()
|
||||
c1 := &conn{fd: fd1, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd1)}
|
||||
c2 := &conn{fd: fd2, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd2)}
|
||||
c1 := &conn{fd: fd1, node: newNode(randomID(), ""), transport: newTestTransport(&newkey().PublicKey, fd1)}
|
||||
c2 := &conn{fd: fd2, node: newNode(randomID(), ""), transport: newTestTransport(&newkey().PublicKey, fd2)}
|
||||
for _, p := range protos {
|
||||
c1.caps = append(c1.caps, p.cap())
|
||||
c2.caps = append(c2.caps, p.cap())
|
||||
|
@ -595,7 +595,8 @@ func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {
|
||||
}
|
||||
msg.meterSize = msg.Size
|
||||
if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages
|
||||
metrics.GetOrRegisterMeter(fmt.Sprintf("%s/%s/%d/%#02x", MetricsOutboundTraffic, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode), nil).Mark(int64(msg.meterSize))
|
||||
m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode)
|
||||
metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
|
||||
}
|
||||
// write header
|
||||
headbuf := make([]byte, 32)
|
||||
|
317
p2p/server.go
317
p2p/server.go
@ -51,7 +51,6 @@ const (
|
||||
discmixTimeout = 5 * time.Second
|
||||
|
||||
// Connectivity defaults.
|
||||
maxActiveDialTasks = 16
|
||||
defaultMaxPendingPeers = 50
|
||||
defaultDialRatio = 3
|
||||
|
||||
@ -156,6 +155,8 @@ type Config struct {
|
||||
|
||||
// Logger is a custom logger to use with the p2p.Server.
|
||||
Logger log.Logger `toml:",omitempty"`
|
||||
|
||||
clock mclock.Clock
|
||||
}
|
||||
|
||||
// Server manages all peer connections.
|
||||
@ -183,13 +184,10 @@ type Server struct {
|
||||
ntab *discover.UDPv4
|
||||
DiscV5 *discv5.Network
|
||||
discmix *enode.FairMix
|
||||
|
||||
staticNodeResolver nodeResolver
|
||||
dialsched *dialScheduler
|
||||
|
||||
// Channels into the run loop.
|
||||
quit chan struct{}
|
||||
addstatic chan *enode.Node
|
||||
removestatic chan *enode.Node
|
||||
addtrusted chan *enode.Node
|
||||
removetrusted chan *enode.Node
|
||||
peerOp chan peerOpFunc
|
||||
@ -302,47 +300,57 @@ func (srv *Server) LocalNode() *enode.LocalNode {
|
||||
// Peers returns all connected peers.
|
||||
func (srv *Server) Peers() []*Peer {
|
||||
var ps []*Peer
|
||||
select {
|
||||
// Note: We'd love to put this function into a variable but
|
||||
// that seems to cause a weird compiler error in some
|
||||
// environments.
|
||||
case srv.peerOp <- func(peers map[enode.ID]*Peer) {
|
||||
srv.doPeerOp(func(peers map[enode.ID]*Peer) {
|
||||
for _, p := range peers {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}:
|
||||
<-srv.peerOpDone
|
||||
case <-srv.quit:
|
||||
}
|
||||
})
|
||||
return ps
|
||||
}
|
||||
|
||||
// PeerCount returns the number of connected peers.
|
||||
func (srv *Server) PeerCount() int {
|
||||
var count int
|
||||
select {
|
||||
case srv.peerOp <- func(ps map[enode.ID]*Peer) { count = len(ps) }:
|
||||
<-srv.peerOpDone
|
||||
case <-srv.quit:
|
||||
}
|
||||
srv.doPeerOp(func(ps map[enode.ID]*Peer) {
|
||||
count = len(ps)
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
// AddPeer connects to the given node and maintains the connection until the
|
||||
// server is shut down. If the connection fails for any reason, the server will
|
||||
// attempt to reconnect the peer.
|
||||
// AddPeer adds the given node to the static node set. When there is room in the peer set,
|
||||
// the server will connect to the node. If the connection fails for any reason, the server
|
||||
// will attempt to reconnect the peer.
|
||||
func (srv *Server) AddPeer(node *enode.Node) {
|
||||
select {
|
||||
case srv.addstatic <- node:
|
||||
case <-srv.quit:
|
||||
}
|
||||
srv.dialsched.addStatic(node)
|
||||
}
|
||||
|
||||
// RemovePeer disconnects from the given node
|
||||
// RemovePeer removes a node from the static node set. It also disconnects from the given
|
||||
// node if it is currently connected as a peer.
|
||||
//
|
||||
// This method blocks until all protocols have exited and the peer is removed. Do not use
|
||||
// RemovePeer in protocol implementations, call Disconnect on the Peer instead.
|
||||
func (srv *Server) RemovePeer(node *enode.Node) {
|
||||
select {
|
||||
case srv.removestatic <- node:
|
||||
case <-srv.quit:
|
||||
var (
|
||||
ch chan *PeerEvent
|
||||
sub event.Subscription
|
||||
)
|
||||
// Disconnect the peer on the main loop.
|
||||
srv.doPeerOp(func(peers map[enode.ID]*Peer) {
|
||||
srv.dialsched.removeStatic(node)
|
||||
if peer := peers[node.ID()]; peer != nil {
|
||||
ch = make(chan *PeerEvent, 1)
|
||||
sub = srv.peerFeed.Subscribe(ch)
|
||||
peer.Disconnect(DiscRequested)
|
||||
}
|
||||
})
|
||||
// Wait for the peer connection to end.
|
||||
if ch != nil {
|
||||
defer sub.Unsubscribe()
|
||||
for ev := range ch {
|
||||
if ev.Peer == node.ID() && ev.Type == PeerEventTypeDrop {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,6 +445,9 @@ func (srv *Server) Start() (err error) {
|
||||
if srv.log == nil {
|
||||
srv.log = log.Root()
|
||||
}
|
||||
if srv.clock == nil {
|
||||
srv.clock = mclock.System{}
|
||||
}
|
||||
if srv.NoDial && srv.ListenAddr == "" {
|
||||
srv.log.Warn("P2P server will be useless, neither dialing nor listening")
|
||||
}
|
||||
@ -451,15 +462,10 @@ func (srv *Server) Start() (err error) {
|
||||
if srv.listenFunc == nil {
|
||||
srv.listenFunc = net.Listen
|
||||
}
|
||||
if srv.Dialer == nil {
|
||||
srv.Dialer = TCPDialer{&net.Dialer{Timeout: defaultDialTimeout}}
|
||||
}
|
||||
srv.quit = make(chan struct{})
|
||||
srv.delpeer = make(chan peerDrop)
|
||||
srv.checkpointPostHandshake = make(chan *conn)
|
||||
srv.checkpointAddPeer = make(chan *conn)
|
||||
srv.addstatic = make(chan *enode.Node)
|
||||
srv.removestatic = make(chan *enode.Node)
|
||||
srv.addtrusted = make(chan *enode.Node)
|
||||
srv.removetrusted = make(chan *enode.Node)
|
||||
srv.peerOp = make(chan peerOpFunc)
|
||||
@ -476,11 +482,10 @@ func (srv *Server) Start() (err error) {
|
||||
if err := srv.setupDiscovery(); err != nil {
|
||||
return err
|
||||
}
|
||||
srv.setupDialScheduler()
|
||||
|
||||
dynPeers := srv.maxDialedConns()
|
||||
dialer := newDialState(srv.localnode.ID(), dynPeers, &srv.Config)
|
||||
srv.loopWG.Add(1)
|
||||
go srv.run(dialer)
|
||||
go srv.run()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -583,7 +588,6 @@ func (srv *Server) setupDiscovery() error {
|
||||
}
|
||||
srv.ntab = ntab
|
||||
srv.discmix.AddSource(ntab.RandomNodes())
|
||||
srv.staticNodeResolver = ntab
|
||||
}
|
||||
|
||||
// Discovery V5
|
||||
@ -606,6 +610,47 @@ func (srv *Server) setupDiscovery() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) setupDialScheduler() {
|
||||
config := dialConfig{
|
||||
self: srv.localnode.ID(),
|
||||
maxDialPeers: srv.maxDialedConns(),
|
||||
maxActiveDials: srv.MaxPendingPeers,
|
||||
log: srv.Logger,
|
||||
netRestrict: srv.NetRestrict,
|
||||
dialer: srv.Dialer,
|
||||
clock: srv.clock,
|
||||
}
|
||||
if srv.ntab != nil {
|
||||
config.resolver = srv.ntab
|
||||
}
|
||||
if config.dialer == nil {
|
||||
config.dialer = tcpDialer{&net.Dialer{Timeout: defaultDialTimeout}}
|
||||
}
|
||||
srv.dialsched = newDialScheduler(config, srv.discmix, srv.SetupConn)
|
||||
for _, n := range srv.StaticNodes {
|
||||
srv.dialsched.addStatic(n)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) maxInboundConns() int {
|
||||
return srv.MaxPeers - srv.maxDialedConns()
|
||||
}
|
||||
|
||||
func (srv *Server) maxDialedConns() (limit int) {
|
||||
if srv.NoDial || srv.MaxPeers == 0 {
|
||||
return 0
|
||||
}
|
||||
if srv.DialRatio == 0 {
|
||||
limit = srv.MaxPeers / defaultDialRatio
|
||||
} else {
|
||||
limit = srv.MaxPeers / srv.DialRatio
|
||||
}
|
||||
if limit == 0 {
|
||||
limit = 1
|
||||
}
|
||||
return limit
|
||||
}
|
||||
|
||||
func (srv *Server) setupListening() error {
|
||||
// Launch the listener.
|
||||
listener, err := srv.listenFunc("tcp", srv.ListenAddr)
|
||||
@ -632,112 +677,55 @@ func (srv *Server) setupListening() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type dialer interface {
|
||||
newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task
|
||||
taskDone(task, time.Time)
|
||||
addStatic(*enode.Node)
|
||||
removeStatic(*enode.Node)
|
||||
// doPeerOp runs fn on the main loop.
|
||||
func (srv *Server) doPeerOp(fn peerOpFunc) {
|
||||
select {
|
||||
case srv.peerOp <- fn:
|
||||
<-srv.peerOpDone
|
||||
case <-srv.quit:
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) run(dialstate dialer) {
|
||||
// run is the main loop of the server.
|
||||
func (srv *Server) run() {
|
||||
srv.log.Info("Started P2P networking", "self", srv.localnode.Node().URLv4())
|
||||
defer srv.loopWG.Done()
|
||||
defer srv.nodedb.Close()
|
||||
defer srv.discmix.Close()
|
||||
defer srv.dialsched.stop()
|
||||
|
||||
var (
|
||||
peers = make(map[enode.ID]*Peer)
|
||||
inboundCount = 0
|
||||
trusted = make(map[enode.ID]bool, len(srv.TrustedNodes))
|
||||
taskdone = make(chan task, maxActiveDialTasks)
|
||||
tick = time.NewTicker(30 * time.Second)
|
||||
runningTasks []task
|
||||
queuedTasks []task // tasks that can't run yet
|
||||
)
|
||||
defer tick.Stop()
|
||||
|
||||
// Put trusted nodes into a map to speed up checks.
|
||||
// Trusted peers are loaded on startup or added via AddTrustedPeer RPC.
|
||||
for _, n := range srv.TrustedNodes {
|
||||
trusted[n.ID()] = true
|
||||
}
|
||||
|
||||
// removes t from runningTasks
|
||||
delTask := func(t task) {
|
||||
for i := range runningTasks {
|
||||
if runningTasks[i] == t {
|
||||
runningTasks = append(runningTasks[:i], runningTasks[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// starts until max number of active tasks is satisfied
|
||||
startTasks := func(ts []task) (rest []task) {
|
||||
i := 0
|
||||
for ; len(runningTasks) < maxActiveDialTasks && i < len(ts); i++ {
|
||||
t := ts[i]
|
||||
srv.log.Trace("New dial task", "task", t)
|
||||
go func() { t.Do(srv); taskdone <- t }()
|
||||
runningTasks = append(runningTasks, t)
|
||||
}
|
||||
return ts[i:]
|
||||
}
|
||||
scheduleTasks := func() {
|
||||
// Start from queue first.
|
||||
queuedTasks = append(queuedTasks[:0], startTasks(queuedTasks)...)
|
||||
// Query dialer for new tasks and start as many as possible now.
|
||||
if len(runningTasks) < maxActiveDialTasks {
|
||||
nt := dialstate.newTasks(len(runningTasks)+len(queuedTasks), peers, time.Now())
|
||||
queuedTasks = append(queuedTasks, startTasks(nt)...)
|
||||
}
|
||||
}
|
||||
|
||||
running:
|
||||
for {
|
||||
scheduleTasks()
|
||||
|
||||
select {
|
||||
case <-tick.C:
|
||||
// This is just here to ensure the dial scheduler runs occasionally.
|
||||
|
||||
case <-srv.quit:
|
||||
// The server was stopped. Run the cleanup logic.
|
||||
break running
|
||||
|
||||
case n := <-srv.addstatic:
|
||||
// This channel is used by AddPeer to add to the
|
||||
// ephemeral static peer list. Add it to the dialer,
|
||||
// it will keep the node connected.
|
||||
srv.log.Trace("Adding static node", "node", n)
|
||||
dialstate.addStatic(n)
|
||||
|
||||
case n := <-srv.removestatic:
|
||||
// This channel is used by RemovePeer to send a
|
||||
// disconnect request to a peer and begin the
|
||||
// stop keeping the node connected.
|
||||
srv.log.Trace("Removing static node", "node", n)
|
||||
dialstate.removeStatic(n)
|
||||
if p, ok := peers[n.ID()]; ok {
|
||||
p.Disconnect(DiscRequested)
|
||||
}
|
||||
|
||||
case n := <-srv.addtrusted:
|
||||
// This channel is used by AddTrustedPeer to add an enode
|
||||
// This channel is used by AddTrustedPeer to add a node
|
||||
// to the trusted node set.
|
||||
srv.log.Trace("Adding trusted node", "node", n)
|
||||
trusted[n.ID()] = true
|
||||
// Mark any already-connected peer as trusted
|
||||
if p, ok := peers[n.ID()]; ok {
|
||||
p.rw.set(trustedConn, true)
|
||||
}
|
||||
|
||||
case n := <-srv.removetrusted:
|
||||
// This channel is used by RemoveTrustedPeer to remove an enode
|
||||
// This channel is used by RemoveTrustedPeer to remove a node
|
||||
// from the trusted node set.
|
||||
srv.log.Trace("Removing trusted node", "node", n)
|
||||
delete(trusted, n.ID())
|
||||
|
||||
// Unmark any already-connected peer as trusted
|
||||
if p, ok := peers[n.ID()]; ok {
|
||||
p.rw.set(trustedConn, false)
|
||||
}
|
||||
@ -747,14 +735,6 @@ running:
|
||||
op(peers)
|
||||
srv.peerOpDone <- struct{}{}
|
||||
|
||||
case t := <-taskdone:
|
||||
// A task got done. Tell dialstate about it so it
|
||||
// can update its state and remove it from the active
|
||||
// tasks list.
|
||||
srv.log.Trace("Dial task done", "task", t)
|
||||
dialstate.taskDone(t, time.Now())
|
||||
delTask(t)
|
||||
|
||||
case c := <-srv.checkpointPostHandshake:
|
||||
// A connection has passed the encryption handshake so
|
||||
// the remote identity is known (but hasn't been verified yet).
|
||||
@ -771,33 +751,22 @@ running:
|
||||
err := srv.addPeerChecks(peers, inboundCount, c)
|
||||
if err == nil {
|
||||
// The handshakes are done and it passed all checks.
|
||||
p := newPeer(srv.log, c, srv.Protocols)
|
||||
// If message events are enabled, pass the peerFeed
|
||||
// to the peer
|
||||
if srv.EnableMsgEvents {
|
||||
p.events = &srv.peerFeed
|
||||
}
|
||||
name := truncateName(c.name)
|
||||
p.log.Debug("Adding p2p peer", "addr", p.RemoteAddr(), "peers", len(peers)+1, "name", name)
|
||||
go srv.runPeer(p)
|
||||
p := srv.launchPeer(c)
|
||||
peers[c.node.ID()] = p
|
||||
srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", truncateName(c.name))
|
||||
srv.dialsched.peerAdded(c)
|
||||
if p.Inbound() {
|
||||
inboundCount++
|
||||
}
|
||||
if conn, ok := c.fd.(*meteredConn); ok {
|
||||
conn.handshakeDone(p)
|
||||
}
|
||||
}
|
||||
// The dialer logic relies on the assumption that
|
||||
// dial tasks complete after the peer has been added or
|
||||
// discarded. Unblock the task last.
|
||||
c.cont <- err
|
||||
|
||||
case pd := <-srv.delpeer:
|
||||
// A peer disconnected.
|
||||
d := common.PrettyDuration(mclock.Now() - pd.created)
|
||||
pd.log.Debug("Removing p2p peer", "addr", pd.RemoteAddr(), "peers", len(peers)-1, "duration", d, "req", pd.requested, "err", pd.err)
|
||||
delete(peers, pd.ID())
|
||||
srv.log.Debug("Removing p2p peer", "peercount", len(peers), "id", pd.ID(), "duration", d, "req", pd.requested, "err", pd.err)
|
||||
srv.dialsched.peerRemoved(pd.rw)
|
||||
if pd.Inbound() {
|
||||
inboundCount--
|
||||
}
|
||||
@ -822,14 +791,14 @@ running:
|
||||
// is closed.
|
||||
for len(peers) > 0 {
|
||||
p := <-srv.delpeer
|
||||
p.log.Trace("<-delpeer (spindown)", "remainingTasks", len(runningTasks))
|
||||
p.log.Trace("<-delpeer (spindown)")
|
||||
delete(peers, p.ID())
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error {
|
||||
switch {
|
||||
case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers:
|
||||
case !c.is(trustedConn) && len(peers) >= srv.MaxPeers:
|
||||
return DiscTooManyPeers
|
||||
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
|
||||
return DiscTooManyPeers
|
||||
@ -852,21 +821,6 @@ func (srv *Server) addPeerChecks(peers map[enode.ID]*Peer, inboundCount int, c *
|
||||
return srv.postHandshakeChecks(peers, inboundCount, c)
|
||||
}
|
||||
|
||||
func (srv *Server) maxInboundConns() int {
|
||||
return srv.MaxPeers - srv.maxDialedConns()
|
||||
}
|
||||
|
||||
func (srv *Server) maxDialedConns() int {
|
||||
if srv.NoDiscovery || srv.NoDial {
|
||||
return 0
|
||||
}
|
||||
r := srv.DialRatio
|
||||
if r == 0 {
|
||||
r = defaultDialRatio
|
||||
}
|
||||
return srv.MaxPeers / r
|
||||
}
|
||||
|
||||
// listenLoop runs in its own goroutine and accepts
|
||||
// inbound connections.
|
||||
func (srv *Server) listenLoop() {
|
||||
@ -935,18 +889,20 @@ func (srv *Server) listenLoop() {
|
||||
}
|
||||
|
||||
func (srv *Server) checkInboundConn(fd net.Conn, remoteIP net.IP) error {
|
||||
if remoteIP != nil {
|
||||
// Reject connections that do not match NetRestrict.
|
||||
if srv.NetRestrict != nil && !srv.NetRestrict.Contains(remoteIP) {
|
||||
return fmt.Errorf("not whitelisted in NetRestrict")
|
||||
}
|
||||
// Reject Internet peers that try too often.
|
||||
srv.inboundHistory.expire(time.Now())
|
||||
if !netutil.IsLAN(remoteIP) && srv.inboundHistory.contains(remoteIP.String()) {
|
||||
return fmt.Errorf("too many attempts")
|
||||
}
|
||||
srv.inboundHistory.add(remoteIP.String(), time.Now().Add(inboundThrottleTime))
|
||||
if remoteIP == nil {
|
||||
return nil
|
||||
}
|
||||
// Reject connections that do not match NetRestrict.
|
||||
if srv.NetRestrict != nil && !srv.NetRestrict.Contains(remoteIP) {
|
||||
return fmt.Errorf("not whitelisted in NetRestrict")
|
||||
}
|
||||
// Reject Internet peers that try too often.
|
||||
now := srv.clock.Now()
|
||||
srv.inboundHistory.expire(now, nil)
|
||||
if !netutil.IsLAN(remoteIP) && srv.inboundHistory.contains(remoteIP.String()) {
|
||||
return fmt.Errorf("too many attempts")
|
||||
}
|
||||
srv.inboundHistory.add(remoteIP.String(), now.Add(inboundThrottleTime))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -958,7 +914,6 @@ func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node)
|
||||
err := srv.setupConn(c, flags, dialDest)
|
||||
if err != nil {
|
||||
c.close(err)
|
||||
srv.log.Trace("Setting up connection failed", "addr", fd.RemoteAddr(), "err", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -977,7 +932,9 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
|
||||
if dialDest != nil {
|
||||
dialPubkey = new(ecdsa.PublicKey)
|
||||
if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil {
|
||||
return errors.New("dial destination doesn't have a secp256k1 public key")
|
||||
err = errors.New("dial destination doesn't have a secp256k1 public key")
|
||||
srv.log.Trace("Setting up connection failed", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -1006,7 +963,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
|
||||
// Run the capability negotiation handshake.
|
||||
phs, err := c.doProtoHandshake(srv.ourHandshake)
|
||||
if err != nil {
|
||||
clog.Trace("Failed proto handshake", "err", err)
|
||||
clog.Trace("Failed p2p handshake", "err", err)
|
||||
return err
|
||||
}
|
||||
if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) {
|
||||
@ -1020,9 +977,6 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
|
||||
return err
|
||||
}
|
||||
|
||||
// If the checks completed successfully, the connection has been added as a peer and
|
||||
// runPeer has been launched.
|
||||
clog.Trace("Connection set up", "inbound", dialDest == nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1054,15 +1008,22 @@ func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error {
|
||||
return <-c.cont
|
||||
}
|
||||
|
||||
func (srv *Server) launchPeer(c *conn) *Peer {
|
||||
p := newPeer(srv.log, c, srv.Protocols)
|
||||
if srv.EnableMsgEvents {
|
||||
// If message events are enabled, pass the peerFeed
|
||||
// to the peer.
|
||||
p.events = &srv.peerFeed
|
||||
}
|
||||
go srv.runPeer(p)
|
||||
return p
|
||||
}
|
||||
|
||||
// runPeer runs in its own goroutine for each peer.
|
||||
// it waits until the Peer logic returns and removes
|
||||
// the peer.
|
||||
func (srv *Server) runPeer(p *Peer) {
|
||||
if srv.newPeerHook != nil {
|
||||
srv.newPeerHook(p)
|
||||
}
|
||||
|
||||
// broadcast peer add
|
||||
srv.peerFeed.Send(&PeerEvent{
|
||||
Type: PeerEventTypeAdd,
|
||||
Peer: p.ID(),
|
||||
@ -1070,10 +1031,18 @@ func (srv *Server) runPeer(p *Peer) {
|
||||
LocalAddress: p.LocalAddr().String(),
|
||||
})
|
||||
|
||||
// run the protocol
|
||||
// Run the per-peer main loop.
|
||||
remoteRequested, err := p.run()
|
||||
|
||||
// broadcast peer drop
|
||||
// Announce disconnect on the main loop to update the peer set.
|
||||
// The main loop waits for existing peers to be sent on srv.delpeer
|
||||
// before returning, so this send should not select on srv.quit.
|
||||
srv.delpeer <- peerDrop{p, err, remoteRequested}
|
||||
|
||||
// Broadcast peer drop to external subscribers. This needs to be
|
||||
// after the send to delpeer so subscribers have a consistent view of
|
||||
// the peer set (i.e. Server.Peers() doesn't include the peer when the
|
||||
// event is received.
|
||||
srv.peerFeed.Send(&PeerEvent{
|
||||
Type: PeerEventTypeDrop,
|
||||
Peer: p.ID(),
|
||||
@ -1081,10 +1050,6 @@ func (srv *Server) runPeer(p *Peer) {
|
||||
RemoteAddress: p.RemoteAddr().String(),
|
||||
LocalAddress: p.LocalAddr().String(),
|
||||
})
|
||||
|
||||
// Note: run waits for existing peers to be sent on srv.delpeer
|
||||
// before returning, so this send should not select on srv.quit.
|
||||
srv.delpeer <- peerDrop{p, err, remoteRequested}
|
||||
}
|
||||
|
||||
// NodeInfo represents a short summary of the information known about the host.
|
||||
|
@ -34,10 +34,6 @@ import (
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// func init() {
|
||||
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||
// }
|
||||
|
||||
type testTransport struct {
|
||||
rpub *ecdsa.PublicKey
|
||||
*rlpx
|
||||
@ -72,11 +68,12 @@ func (c *testTransport) close(err error) {
|
||||
|
||||
func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *Server {
|
||||
config := Config{
|
||||
Name: "test",
|
||||
MaxPeers: 10,
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
PrivateKey: newkey(),
|
||||
Logger: testlog.Logger(t, log.LvlTrace),
|
||||
Name: "test",
|
||||
MaxPeers: 10,
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
NoDiscovery: true,
|
||||
PrivateKey: newkey(),
|
||||
Logger: testlog.Logger(t, log.LvlTrace),
|
||||
}
|
||||
server := &Server{
|
||||
Config: config,
|
||||
@ -131,11 +128,10 @@ func TestServerDial(t *testing.T) {
|
||||
t.Fatalf("could not setup listener: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
accepted := make(chan net.Conn)
|
||||
accepted := make(chan net.Conn, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Error("accept error:", err)
|
||||
return
|
||||
}
|
||||
accepted <- conn
|
||||
@ -205,155 +201,38 @@ func TestServerDial(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks that tasks generated by dialstate are
|
||||
// actually executed and taskdone is called for them.
|
||||
func TestServerTaskScheduling(t *testing.T) {
|
||||
var (
|
||||
done = make(chan *testTask)
|
||||
quit, returned = make(chan struct{}), make(chan struct{})
|
||||
tc = 0
|
||||
tg = taskgen{
|
||||
newFunc: func(running int, peers map[enode.ID]*Peer) []task {
|
||||
tc++
|
||||
return []task{&testTask{index: tc - 1}}
|
||||
},
|
||||
doneFunc: func(t task) {
|
||||
select {
|
||||
case done <- t.(*testTask):
|
||||
case <-quit:
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
// This test checks that RemovePeer disconnects the peer if it is connected.
|
||||
func TestServerRemovePeerDisconnect(t *testing.T) {
|
||||
srv1 := &Server{Config: Config{
|
||||
PrivateKey: newkey(),
|
||||
MaxPeers: 1,
|
||||
NoDiscovery: true,
|
||||
Logger: testlog.Logger(t, log.LvlTrace).New("server", "1"),
|
||||
}}
|
||||
srv2 := &Server{Config: Config{
|
||||
PrivateKey: newkey(),
|
||||
MaxPeers: 1,
|
||||
NoDiscovery: true,
|
||||
NoDial: true,
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
Logger: testlog.Logger(t, log.LvlTrace).New("server", "2"),
|
||||
}}
|
||||
srv1.Start()
|
||||
defer srv1.Stop()
|
||||
srv2.Start()
|
||||
defer srv2.Stop()
|
||||
|
||||
// The Server in this test isn't actually running
|
||||
// because we're only interested in what run does.
|
||||
db, _ := enode.OpenDB("")
|
||||
srv := &Server{
|
||||
Config: Config{MaxPeers: 10},
|
||||
localnode: enode.NewLocalNode(db, newkey()),
|
||||
nodedb: db,
|
||||
discmix: enode.NewFairMix(0),
|
||||
quit: make(chan struct{}),
|
||||
running: true,
|
||||
log: log.New(),
|
||||
if !syncAddPeer(srv1, srv2.Self()) {
|
||||
t.Fatal("peer not connected")
|
||||
}
|
||||
srv.loopWG.Add(1)
|
||||
go func() {
|
||||
srv.run(tg)
|
||||
close(returned)
|
||||
}()
|
||||
|
||||
var gotdone []*testTask
|
||||
for i := 0; i < 100; i++ {
|
||||
gotdone = append(gotdone, <-done)
|
||||
}
|
||||
for i, task := range gotdone {
|
||||
if task.index != i {
|
||||
t.Errorf("task %d has wrong index, got %d", i, task.index)
|
||||
break
|
||||
}
|
||||
if !task.called {
|
||||
t.Errorf("task %d was not called", i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
close(quit)
|
||||
srv.Stop()
|
||||
select {
|
||||
case <-returned:
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
t.Error("Server.run did not return within 500ms")
|
||||
srv1.RemovePeer(srv2.Self())
|
||||
if srv1.PeerCount() > 0 {
|
||||
t.Fatal("removed peer still connected")
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks that Server doesn't drop tasks,
|
||||
// even if newTasks returns more than the maximum number of tasks.
|
||||
func TestServerManyTasks(t *testing.T) {
|
||||
alltasks := make([]task, 300)
|
||||
for i := range alltasks {
|
||||
alltasks[i] = &testTask{index: i}
|
||||
}
|
||||
|
||||
var (
|
||||
db, _ = enode.OpenDB("")
|
||||
srv = &Server{
|
||||
quit: make(chan struct{}),
|
||||
localnode: enode.NewLocalNode(db, newkey()),
|
||||
nodedb: db,
|
||||
running: true,
|
||||
log: log.New(),
|
||||
discmix: enode.NewFairMix(0),
|
||||
}
|
||||
done = make(chan *testTask)
|
||||
start, end = 0, 0
|
||||
)
|
||||
defer srv.Stop()
|
||||
srv.loopWG.Add(1)
|
||||
go srv.run(taskgen{
|
||||
newFunc: func(running int, peers map[enode.ID]*Peer) []task {
|
||||
start, end = end, end+maxActiveDialTasks+10
|
||||
if end > len(alltasks) {
|
||||
end = len(alltasks)
|
||||
}
|
||||
return alltasks[start:end]
|
||||
},
|
||||
doneFunc: func(tt task) {
|
||||
done <- tt.(*testTask)
|
||||
},
|
||||
})
|
||||
|
||||
doneset := make(map[int]bool)
|
||||
timeout := time.After(2 * time.Second)
|
||||
for len(doneset) < len(alltasks) {
|
||||
select {
|
||||
case tt := <-done:
|
||||
if doneset[tt.index] {
|
||||
t.Errorf("task %d got done more than once", tt.index)
|
||||
} else {
|
||||
doneset[tt.index] = true
|
||||
}
|
||||
case <-timeout:
|
||||
t.Errorf("%d of %d tasks got done within 2s", len(doneset), len(alltasks))
|
||||
for i := 0; i < len(alltasks); i++ {
|
||||
if !doneset[i] {
|
||||
t.Logf("task %d not done", i)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type taskgen struct {
|
||||
newFunc func(running int, peers map[enode.ID]*Peer) []task
|
||||
doneFunc func(task)
|
||||
}
|
||||
|
||||
func (tg taskgen) newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task {
|
||||
return tg.newFunc(running, peers)
|
||||
}
|
||||
func (tg taskgen) taskDone(t task, now time.Time) {
|
||||
tg.doneFunc(t)
|
||||
}
|
||||
func (tg taskgen) addStatic(*enode.Node) {
|
||||
}
|
||||
func (tg taskgen) removeStatic(*enode.Node) {
|
||||
}
|
||||
|
||||
type testTask struct {
|
||||
index int
|
||||
called bool
|
||||
}
|
||||
|
||||
func (t *testTask) Do(srv *Server) {
|
||||
t.called = true
|
||||
}
|
||||
|
||||
// This test checks that connections are disconnected
|
||||
// just after the encryption handshake when the server is
|
||||
// at capacity. Trusted connections should still be accepted.
|
||||
// This test checks that connections are disconnected just after the encryption handshake
|
||||
// when the server is at capacity. Trusted connections should still be accepted.
|
||||
func TestServerAtCap(t *testing.T) {
|
||||
trustedNode := newkey()
|
||||
trustedID := enode.PubkeyToIDV4(&trustedNode.PublicKey)
|
||||
@ -363,7 +242,8 @@ func TestServerAtCap(t *testing.T) {
|
||||
MaxPeers: 10,
|
||||
NoDial: true,
|
||||
NoDiscovery: true,
|
||||
TrustedNodes: []*enode.Node{newNode(trustedID, nil)},
|
||||
TrustedNodes: []*enode.Node{newNode(trustedID, "")},
|
||||
Logger: testlog.Logger(t, log.LvlTrace),
|
||||
},
|
||||
}
|
||||
if err := srv.Start(); err != nil {
|
||||
@ -401,14 +281,14 @@ func TestServerAtCap(t *testing.T) {
|
||||
}
|
||||
|
||||
// Remove from trusted set and try again
|
||||
srv.RemoveTrustedPeer(newNode(trustedID, nil))
|
||||
srv.RemoveTrustedPeer(newNode(trustedID, ""))
|
||||
c = newconn(trustedID)
|
||||
if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != DiscTooManyPeers {
|
||||
t.Error("wrong error for insert:", err)
|
||||
}
|
||||
|
||||
// Add anotherID to trusted set and try again
|
||||
srv.AddTrustedPeer(newNode(anotherID, nil))
|
||||
srv.AddTrustedPeer(newNode(anotherID, ""))
|
||||
c = newconn(anotherID)
|
||||
if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != nil {
|
||||
t.Error("unexpected error for trusted conn @posthandshake:", err)
|
||||
@ -439,9 +319,9 @@ func TestServerPeerLimits(t *testing.T) {
|
||||
NoDial: true,
|
||||
NoDiscovery: true,
|
||||
Protocols: []Protocol{discard},
|
||||
Logger: testlog.Logger(t, log.LvlTrace),
|
||||
},
|
||||
newTransport: func(fd net.Conn) transport { return tp },
|
||||
log: log.New(),
|
||||
}
|
||||
if err := srv.Start(); err != nil {
|
||||
t.Fatalf("couldn't start server: %v", err)
|
||||
@ -670,7 +550,7 @@ func TestServerInboundThrottle(t *testing.T) {
|
||||
conn.Close()
|
||||
|
||||
// Dial again. This time the server should close the connection immediately.
|
||||
connClosed := make(chan struct{})
|
||||
connClosed := make(chan struct{}, 1)
|
||||
conn, err = net.DialTimeout("tcp", srv.ListenAddr, timeout)
|
||||
if err != nil {
|
||||
t.Fatalf("could not dial: %v", err)
|
||||
@ -724,3 +604,23 @@ func (l *fakeAddrListener) Accept() (net.Conn, error) {
|
||||
func (c *fakeAddrConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func syncAddPeer(srv *Server, node *enode.Node) bool {
|
||||
var (
|
||||
ch = make(chan *PeerEvent)
|
||||
sub = srv.SubscribeEvents(ch)
|
||||
timeout = time.After(2 * time.Second)
|
||||
)
|
||||
defer sub.Unsubscribe()
|
||||
srv.AddPeer(node)
|
||||
for {
|
||||
select {
|
||||
case ev := <-ch:
|
||||
if ev.Type == PeerEventTypeAdd && ev.Peer == node.ID() {
|
||||
return true
|
||||
}
|
||||
case <-timeout:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ func (n *ExecNode) Stop() error {
|
||||
if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||
return n.Cmd.Process.Kill()
|
||||
}
|
||||
waitErr := make(chan error)
|
||||
waitErr := make(chan error, 1)
|
||||
go func() {
|
||||
waitErr <- n.Cmd.Wait()
|
||||
}()
|
||||
|
@ -17,6 +17,7 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
@ -126,7 +127,7 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
|
||||
|
||||
// Dial implements the p2p.NodeDialer interface by connecting to the node using
|
||||
// an in-memory net.Pipe
|
||||
func (s *SimAdapter) Dial(dest *enode.Node) (conn net.Conn, err error) {
|
||||
func (s *SimAdapter) Dial(ctx context.Context, dest *enode.Node) (conn net.Conn, err error) {
|
||||
node, ok := s.GetNode(dest.ID())
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown node: %s", dest.ID())
|
||||
|
20
p2p/util.go
20
p2p/util.go
@ -18,7 +18,8 @@ package p2p
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
)
|
||||
|
||||
// expHeap tracks strings and their expiry time.
|
||||
@ -27,16 +28,16 @@ type expHeap []expItem
|
||||
// expItem is an entry in addrHistory.
|
||||
type expItem struct {
|
||||
item string
|
||||
exp time.Time
|
||||
exp mclock.AbsTime
|
||||
}
|
||||
|
||||
// nextExpiry returns the next expiry time.
|
||||
func (h *expHeap) nextExpiry() time.Time {
|
||||
func (h *expHeap) nextExpiry() mclock.AbsTime {
|
||||
return (*h)[0].exp
|
||||
}
|
||||
|
||||
// add adds an item and sets its expiry time.
|
||||
func (h *expHeap) add(item string, exp time.Time) {
|
||||
func (h *expHeap) add(item string, exp mclock.AbsTime) {
|
||||
heap.Push(h, expItem{item, exp})
|
||||
}
|
||||
|
||||
@ -51,15 +52,18 @@ func (h expHeap) contains(item string) bool {
|
||||
}
|
||||
|
||||
// expire removes items with expiry time before 'now'.
|
||||
func (h *expHeap) expire(now time.Time) {
|
||||
for h.Len() > 0 && h.nextExpiry().Before(now) {
|
||||
heap.Pop(h)
|
||||
func (h *expHeap) expire(now mclock.AbsTime, onExp func(string)) {
|
||||
for h.Len() > 0 && h.nextExpiry() < now {
|
||||
item := heap.Pop(h)
|
||||
if onExp != nil {
|
||||
onExp(item.(expItem).item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// heap.Interface boilerplate
|
||||
func (h expHeap) Len() int { return len(h) }
|
||||
func (h expHeap) Less(i, j int) bool { return h[i].exp.Before(h[j].exp) }
|
||||
func (h expHeap) Less(i, j int) bool { return h[i].exp < h[j].exp }
|
||||
func (h expHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h *expHeap) Push(x interface{}) { *h = append(*h, x.(expItem)) }
|
||||
func (h *expHeap) Pop() interface{} {
|
||||
|
@ -19,30 +19,32 @@ package p2p
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
)
|
||||
|
||||
func TestExpHeap(t *testing.T) {
|
||||
var h expHeap
|
||||
|
||||
var (
|
||||
basetime = time.Unix(4000, 0)
|
||||
basetime = mclock.AbsTime(10)
|
||||
exptimeA = basetime.Add(2 * time.Second)
|
||||
exptimeB = basetime.Add(3 * time.Second)
|
||||
exptimeC = basetime.Add(4 * time.Second)
|
||||
)
|
||||
h.add("a", exptimeA)
|
||||
h.add("b", exptimeB)
|
||||
h.add("a", exptimeA)
|
||||
h.add("c", exptimeC)
|
||||
|
||||
if !h.nextExpiry().Equal(exptimeA) {
|
||||
if h.nextExpiry() != exptimeA {
|
||||
t.Fatal("wrong nextExpiry")
|
||||
}
|
||||
if !h.contains("a") || !h.contains("b") || !h.contains("c") {
|
||||
t.Fatal("heap doesn't contain all live items")
|
||||
}
|
||||
|
||||
h.expire(exptimeA.Add(1))
|
||||
if !h.nextExpiry().Equal(exptimeB) {
|
||||
h.expire(exptimeA.Add(1), nil)
|
||||
if h.nextExpiry() != exptimeB {
|
||||
t.Fatal("wrong nextExpiry")
|
||||
}
|
||||
if h.contains("a") {
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package params
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
// MainnetBootnodes are the enode URLs of the P2P bootstrap nodes running on
|
||||
// the main Ethereum network.
|
||||
var MainnetBootnodes = []string{
|
||||
@ -69,3 +71,14 @@ var DiscoveryV5Bootnodes = []string{
|
||||
"enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30306",
|
||||
"enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30307",
|
||||
}
|
||||
|
||||
const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@"
|
||||
|
||||
// These DNS names provide bootstrap connectivity for public testnets and the mainnet.
|
||||
// See https://github.com/ethereum/discv4-dns-lists for more information.
|
||||
var KnownDNSNetworks = map[common.Hash]string{
|
||||
MainnetGenesisHash: dnsPrefix + "all.mainnet.ethdisco.net",
|
||||
TestnetGenesisHash: dnsPrefix + "all.ropsten.ethdisco.net",
|
||||
RinkebyGenesisHash: dnsPrefix + "all.rinkeby.ethdisco.net",
|
||||
GoerliGenesisHash: dnsPrefix + "all.goerli.ethdisco.net",
|
||||
}
|
||||
|
@ -72,10 +72,10 @@ var (
|
||||
|
||||
// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
|
||||
MainnetTrustedCheckpoint = &TrustedCheckpoint{
|
||||
SectionIndex: 283,
|
||||
SectionHead: common.HexToHash("0x7da9639e5b378421f2cabd1d3bdae02dbf6d4ba79fc9b3c3916c66412ef7d0b6"),
|
||||
CHTRoot: common.HexToHash("0xb8c6f06e1d5a4fddf593d1ff787e6b89eff4183d0b40d276f19e7d41178a53cf"),
|
||||
BloomRoot: common.HexToHash("0xc47d7d6924ba46090d568296bb667a3ea4b6a00dd69bd5fb4f4f78be2fe05ec2"),
|
||||
SectionIndex: 289,
|
||||
SectionHead: common.HexToHash("0x5a95eed1a6e01d58b59f86c754cda88e8d6bede65428530eb0bec03267cda6a9"),
|
||||
CHTRoot: common.HexToHash("0x6d4abf2b0f3c015952e6a3cbd5cc9885aacc29b8e55d4de662d29783c74a62bf"),
|
||||
BloomRoot: common.HexToHash("0x1af2a8abbaca8048136b02f782cb6476ab546313186a1d1bd2b02df88ea48e7e"),
|
||||
}
|
||||
|
||||
// MainnetCheckpointOracle contains a set of configs for the main network oracle.
|
||||
@ -111,10 +111,10 @@ var (
|
||||
|
||||
// TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network.
|
||||
TestnetTrustedCheckpoint = &TrustedCheckpoint{
|
||||
SectionIndex: 217,
|
||||
SectionHead: common.HexToHash("0x1895e3cceb6fb201044679db2b9f4f9df4233b52e8d3c5ec4b75ae0ae75c90fa"),
|
||||
CHTRoot: common.HexToHash("0x8f2016fb336b64bd8ef4e9a73659a0a99476ea8789aacad695d65295a50fdb8d"),
|
||||
BloomRoot: common.HexToHash("0x57f5b8ecfa10ada7509a45f7e0f2283c6b2dc08d8771163ffbb4ff0e3e6bca1c"),
|
||||
SectionIndex: 223,
|
||||
SectionHead: common.HexToHash("0x9aa51ca383f5075f816e0b8ce7125075cd562b918839ee286c03770722147661"),
|
||||
CHTRoot: common.HexToHash("0x755c6a5931b7bd36e55e47f3f1e81fa79c930ae15c55682d3a85931eedaf8cf2"),
|
||||
BloomRoot: common.HexToHash("0xabc37762d11b29dc7dde11b89846e2308ba681eeb015b6a202ef5e242bc107e8"),
|
||||
}
|
||||
|
||||
// TestnetCheckpointOracle contains a set of configs for the Ropsten test network oracle.
|
||||
@ -152,10 +152,10 @@ var (
|
||||
|
||||
// RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network.
|
||||
RinkebyTrustedCheckpoint = &TrustedCheckpoint{
|
||||
SectionIndex: 176,
|
||||
SectionHead: common.HexToHash("0xb2cbd3396f25647fd80598fc4f7b015fb4131c0831630d878742b33dd286b641"),
|
||||
CHTRoot: common.HexToHash("0xd81776495227babd75d8a519cb55e506e7b24265de5aa1847d38cb8f216ac09e"),
|
||||
BloomRoot: common.HexToHash("0x0ea52b610139fcd6b1bc75351787e21c72922b18ea8a27091a51eb29b6723cd6"),
|
||||
SectionIndex: 181,
|
||||
SectionHead: common.HexToHash("0xdda275f3e9ecadf4834a6a682db1ca3db6945fa4014c82dadcad032fc5c1aefa"),
|
||||
CHTRoot: common.HexToHash("0x0fdfdbdb12e947e838fe26dd3ada4cc3092d6fa22aefec719b83f16004b5e596"),
|
||||
BloomRoot: common.HexToHash("0xfd8dc404a438eaa5cf93dd58dbaeed648aa49d563b511892262acff77c5db7db"),
|
||||
}
|
||||
|
||||
// RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle.
|
||||
@ -191,10 +191,10 @@ var (
|
||||
|
||||
// GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network.
|
||||
GoerliTrustedCheckpoint = &TrustedCheckpoint{
|
||||
SectionIndex: 60,
|
||||
SectionHead: common.HexToHash("0x9bc784b6ad0c944f6aebf930fb5e811497059788b616e211db655a885e65f1cb"),
|
||||
CHTRoot: common.HexToHash("0x961a811e2843f6196903edc1d04adbf13dee92627a89c21b3e0cdf69e0100638"),
|
||||
BloomRoot: common.HexToHash("0x1564889bbe8c68011d29d3966439956303283c0fb8b08daa8376028e82dcd763"),
|
||||
SectionIndex: 66,
|
||||
SectionHead: common.HexToHash("0xeea3a7b2cb275956f3049dd27e6cdacd8a6ef86738d593d556efee5361019475"),
|
||||
CHTRoot: common.HexToHash("0x11712af50b4083dc5910e452ca69fbfc0f2940770b9846200a573f87a0af94e6"),
|
||||
BloomRoot: common.HexToHash("0x331b7a7b273e81daeac8cafb9952a16669d7facc7be3b0ebd3a792b4d8b95cc5"),
|
||||
}
|
||||
|
||||
// GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle.
|
||||
|
@ -21,10 +21,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
VersionMajor = 1 // Major version component of the current release
|
||||
VersionMinor = 9 // Minor version component of the current release
|
||||
VersionPatch = 11 // Patch version component of the current release
|
||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||
VersionMajor = 1 // Major version component of the current release
|
||||
VersionMinor = 9 // Minor version component of the current release
|
||||
VersionPatch = 11 // Patch version component of the current release
|
||||
VersionMeta = "stable" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
// Version holds the textual version string.
|
||||
|
@ -276,6 +276,9 @@ func (c *Client) Call(result interface{}, method string, args ...interface{}) er
|
||||
// The result must be a pointer so that package json can unmarshal into it. You
|
||||
// can also pass nil, in which case the result is ignored.
|
||||
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||
if result != nil && reflect.TypeOf(result).Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("call result parameter must be pointer or nil interface: %v", result)
|
||||
}
|
||||
msg, err := c.newMessage(method, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -49,6 +49,23 @@ func TestClientRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientResponseType(t *testing.T) {
|
||||
server := newTestServer()
|
||||
defer server.Stop()
|
||||
client := DialInProc(server)
|
||||
defer client.Close()
|
||||
|
||||
if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
|
||||
t.Errorf("Passing nil as result should be fine, but got an error: %v", err)
|
||||
}
|
||||
var resultVar echoResult
|
||||
// Note: passing the var, not a ref
|
||||
err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"})
|
||||
if err == nil {
|
||||
t.Error("Passing a var as result should be an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientBatchRequest(t *testing.T) {
|
||||
server := newTestServer()
|
||||
defer server.Stop()
|
||||
@ -280,7 +297,7 @@ func TestClientSubscribeClose(t *testing.T) {
|
||||
|
||||
var (
|
||||
nc = make(chan int)
|
||||
errc = make(chan error)
|
||||
errc = make(chan error, 1)
|
||||
sub *ClientSubscription
|
||||
err error
|
||||
)
|
||||
|
@ -17,7 +17,6 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@ -25,30 +24,26 @@ import (
|
||||
|
||||
// checkModuleAvailability check that all names given in modules are actually
|
||||
// available API services.
|
||||
func checkModuleAvailability(modules []string, apis []API) error {
|
||||
available := make(map[string]struct{})
|
||||
var availableNames string
|
||||
for i, api := range apis {
|
||||
if _, ok := available[api.Namespace]; !ok {
|
||||
available[api.Namespace] = struct{}{}
|
||||
if i > 0 {
|
||||
availableNames += ", "
|
||||
}
|
||||
availableNames += api.Namespace
|
||||
func checkModuleAvailability(modules []string, apis []API) (bad, available []string) {
|
||||
availableSet := make(map[string]struct{})
|
||||
for _, api := range apis {
|
||||
if _, ok := availableSet[api.Namespace]; !ok {
|
||||
availableSet[api.Namespace] = struct{}{}
|
||||
available = append(available, api.Namespace)
|
||||
}
|
||||
}
|
||||
for _, name := range modules {
|
||||
if _, ok := available[name]; !ok {
|
||||
return fmt.Errorf("invalid API %q in whitelist (available: %s)", name, availableNames)
|
||||
if _, ok := availableSet[name]; !ok {
|
||||
bad = append(bad, name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return bad, available
|
||||
}
|
||||
|
||||
// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules.
|
||||
func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string, timeouts HTTPTimeouts) (net.Listener, *Server, error) {
|
||||
if err := checkModuleAvailability(modules, apis); err != nil {
|
||||
return nil, nil, err
|
||||
if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
|
||||
log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
|
||||
}
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
@ -79,8 +74,8 @@ func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []str
|
||||
|
||||
// StartWSEndpoint starts a websocket endpoint.
|
||||
func StartWSEndpoint(endpoint string, apis []API, modules []string, wsOrigins []string, exposeAll bool) (net.Listener, *Server, error) {
|
||||
if err := checkModuleAvailability(modules, apis); err != nil {
|
||||
return nil, nil, err
|
||||
if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
|
||||
log.Error("Unavailable modules in WS API list", "unavailable", bad, "available", available)
|
||||
}
|
||||
// Generate the whitelist based on the allowed modules
|
||||
whitelist := make(map[string]bool)
|
||||
|
@ -923,7 +923,9 @@ func isPrimitiveTypeValid(primitiveType string) bool {
|
||||
primitiveType == "bytes30" ||
|
||||
primitiveType == "bytes30[]" ||
|
||||
primitiveType == "bytes31" ||
|
||||
primitiveType == "bytes31[]" {
|
||||
primitiveType == "bytes31[]" ||
|
||||
primitiveType == "bytes32" ||
|
||||
primitiveType == "bytes32[]" {
|
||||
return true
|
||||
}
|
||||
if primitiveType == "int" ||
|
||||
|
91
statediff/api.go
Normal file
91
statediff/api.go
Normal file
@ -0,0 +1,91 @@
|
||||
// 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 statediff
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// APIName is the namespace used for the state diffing service API
|
||||
const APIName = "statediff"
|
||||
|
||||
// APIVersion is the version of the state diffing service API
|
||||
const APIVersion = "0.0.1"
|
||||
|
||||
// PublicStateDiffAPI provides an RPC subscription interface
|
||||
// that can be used to stream out state diffs as they
|
||||
// are produced by a full node
|
||||
type PublicStateDiffAPI struct {
|
||||
sds IService
|
||||
}
|
||||
|
||||
// NewPublicStateDiffAPI creates an rpc subscription interface for the underlying statediff service
|
||||
func NewPublicStateDiffAPI(sds IService) *PublicStateDiffAPI {
|
||||
return &PublicStateDiffAPI{
|
||||
sds: sds,
|
||||
}
|
||||
}
|
||||
|
||||
// Stream is the public method to setup a subscription that fires off statediff service payloads as they are created
|
||||
func (api *PublicStateDiffAPI) Stream(ctx context.Context) (*rpc.Subscription, error) {
|
||||
// ensure that the RPC connection supports subscriptions
|
||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||
if !supported {
|
||||
return nil, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
// create subscription and start waiting for events
|
||||
rpcSub := notifier.CreateSubscription()
|
||||
|
||||
go func() {
|
||||
// subscribe to events from the statediff service
|
||||
payloadChannel := make(chan Payload, chainEventChanSize)
|
||||
quitChan := make(chan bool, 1)
|
||||
api.sds.Subscribe(rpcSub.ID, payloadChannel, quitChan)
|
||||
// loop and await payloads and relay them to the subscriber with the notifier
|
||||
for {
|
||||
select {
|
||||
case payload := <-payloadChannel:
|
||||
if notifyErr := notifier.Notify(rpcSub.ID, payload); notifyErr != nil {
|
||||
log.Error("Failed to send state diff packet; error: " + notifyErr.Error())
|
||||
unSubErr := api.sds.Unsubscribe(rpcSub.ID)
|
||||
if unSubErr != nil {
|
||||
log.Error("Failed to unsubscribe from the state diff service; error: " + unSubErr.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
case err := <-rpcSub.Err():
|
||||
if err != nil {
|
||||
log.Error("State diff service rpcSub error: " + err.Error())
|
||||
err = api.sds.Unsubscribe(rpcSub.ID)
|
||||
if err != nil {
|
||||
log.Error("Failed to unsubscribe from the state diff service; error: " + err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
case <-quitChan:
|
||||
// don't need to unsubscribe, service does so before sending the quit signal
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return rpcSub, nil
|
||||
}
|
320
statediff/builder.go
Normal file
320
statediff/builder.go
Normal file
@ -0,0 +1,320 @@
|
||||
// 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/>.
|
||||
|
||||
// Contains a batch of utility type declarations used by the tests. As the node
|
||||
// operates on unique types, a lot of them are needed to check various features.
|
||||
|
||||
package statediff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
var nullNode = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
// Builder interface exposes the method for building a state diff between two blocks
|
||||
type Builder interface {
|
||||
BuildStateDiff(oldStateRoot, newStateRoot common.Hash, blockNumber *big.Int, blockHash common.Hash) (StateDiff, error)
|
||||
}
|
||||
|
||||
type builder struct {
|
||||
chainDB ethdb.Database
|
||||
config Config
|
||||
blockChain *core.BlockChain
|
||||
stateCache state.Database
|
||||
}
|
||||
|
||||
// NewBuilder is used to create a statediff builder
|
||||
func NewBuilder(db ethdb.Database, blockChain *core.BlockChain, config Config) Builder {
|
||||
return &builder{
|
||||
chainDB: db,
|
||||
config: config,
|
||||
blockChain: blockChain,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildStateDiff builds a statediff object from two blocks
|
||||
func (sdb *builder) BuildStateDiff(oldStateRoot, newStateRoot common.Hash, blockNumber *big.Int, blockHash common.Hash) (StateDiff, error) {
|
||||
// Generate tries for old and new states
|
||||
sdb.stateCache = sdb.blockChain.StateCache()
|
||||
oldTrie, err := sdb.stateCache.OpenTrie(oldStateRoot)
|
||||
if err != nil {
|
||||
return StateDiff{}, fmt.Errorf("error creating trie for oldStateRoot: %v", err)
|
||||
}
|
||||
newTrie, err := sdb.stateCache.OpenTrie(newStateRoot)
|
||||
if err != nil {
|
||||
return StateDiff{}, fmt.Errorf("error creating trie for newStateRoot: %v", err)
|
||||
}
|
||||
|
||||
// Find created accounts
|
||||
oldIt := oldTrie.NodeIterator([]byte{})
|
||||
newIt := newTrie.NodeIterator([]byte{})
|
||||
creations, err := sdb.collectDiffNodes(oldIt, newIt)
|
||||
if err != nil {
|
||||
return StateDiff{}, fmt.Errorf("error collecting creation diff nodes: %v", err)
|
||||
}
|
||||
|
||||
// Find deleted accounts
|
||||
oldIt = oldTrie.NodeIterator([]byte{})
|
||||
newIt = newTrie.NodeIterator([]byte{})
|
||||
deletions, err := sdb.collectDiffNodes(newIt, oldIt)
|
||||
if err != nil {
|
||||
return StateDiff{}, fmt.Errorf("error collecting deletion diff nodes: %v", err)
|
||||
}
|
||||
|
||||
// Find all the diffed keys
|
||||
createKeys := sortKeys(creations)
|
||||
deleteKeys := sortKeys(deletions)
|
||||
updatedKeys := findIntersection(createKeys, deleteKeys)
|
||||
|
||||
// Build and return the statediff
|
||||
updatedAccounts, err := sdb.buildDiffIncremental(creations, deletions, updatedKeys)
|
||||
if err != nil {
|
||||
return StateDiff{}, fmt.Errorf("error building diff for updated accounts: %v", err)
|
||||
}
|
||||
createdAccounts, err := sdb.buildDiffEventual(creations)
|
||||
if err != nil {
|
||||
return StateDiff{}, fmt.Errorf("error building diff for created accounts: %v", err)
|
||||
}
|
||||
deletedAccounts, err := sdb.buildDiffEventual(deletions)
|
||||
if err != nil {
|
||||
return StateDiff{}, fmt.Errorf("error building diff for deleted accounts: %v", err)
|
||||
}
|
||||
|
||||
return StateDiff{
|
||||
BlockNumber: blockNumber,
|
||||
BlockHash: blockHash,
|
||||
CreatedAccounts: createdAccounts,
|
||||
DeletedAccounts: deletedAccounts,
|
||||
UpdatedAccounts: updatedAccounts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isWatchedAddress is used to check if a state account corresponds to one of the addresses the builder is configured to watch
|
||||
func (sdb *builder) isWatchedAddress(hashKey []byte) bool {
|
||||
// If we aren't watching any specific addresses, we are watching everything
|
||||
if len(sdb.config.WatchedAddresses) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, addrStr := range sdb.config.WatchedAddresses {
|
||||
addr := common.HexToAddress(addrStr)
|
||||
addrHashKey := crypto.Keccak256(addr[:])
|
||||
if bytes.Equal(addrHashKey, hashKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sdb *builder) collectDiffNodes(a, b trie.NodeIterator) (AccountsMap, error) {
|
||||
var diffAccounts = make(AccountsMap)
|
||||
it, _ := trie.NewDifferenceIterator(a, b)
|
||||
for {
|
||||
log.Debug("Current Path and Hash", "path", pathToStr(it), "old hash", it.Hash())
|
||||
if it.Leaf() && sdb.isWatchedAddress(it.LeafKey()) {
|
||||
leafKey := make([]byte, len(it.LeafKey()))
|
||||
copy(leafKey, it.LeafKey())
|
||||
leafKeyHash := common.BytesToHash(leafKey)
|
||||
leafValue := make([]byte, len(it.LeafBlob()))
|
||||
copy(leafValue, it.LeafBlob())
|
||||
// lookup account state
|
||||
var account state.Account
|
||||
if err := rlp.DecodeBytes(leafValue, &account); err != nil {
|
||||
return nil, fmt.Errorf("error looking up account via address %s\r\nerror: %v", leafKeyHash.Hex(), err)
|
||||
}
|
||||
aw := accountWrapper{
|
||||
Leaf: true,
|
||||
Account: &account,
|
||||
RawKey: leafKey,
|
||||
RawValue: leafValue,
|
||||
}
|
||||
if sdb.config.PathsAndProofs {
|
||||
leafProof := make([][]byte, len(it.LeafProof()))
|
||||
copy(leafProof, it.LeafProof())
|
||||
leafPath := make([]byte, len(it.Path()))
|
||||
copy(leafPath, it.Path())
|
||||
aw.Proof = leafProof
|
||||
aw.Path = leafPath
|
||||
}
|
||||
// record account to diffs (creation if we are looking at new - old; deletion if old - new)
|
||||
log.Debug("Account lookup successful", "address", leafKeyHash, "account", account)
|
||||
diffAccounts[leafKeyHash] = aw
|
||||
} else if sdb.config.IntermediateNodes && !bytes.Equal(nullNode, it.Hash().Bytes()) {
|
||||
nodeKey := it.Hash()
|
||||
node, err := sdb.stateCache.TrieDB().Node(nodeKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up intermediate state trie node %s\r\nerror: %v", nodeKey.Hex(), err)
|
||||
}
|
||||
aw := accountWrapper{
|
||||
Leaf: false,
|
||||
RawKey: nodeKey.Bytes(),
|
||||
RawValue: node,
|
||||
}
|
||||
log.Debug("intermediate state trie node lookup successful", "key", nodeKey.Hex(), "value", node)
|
||||
diffAccounts[nodeKey] = aw
|
||||
}
|
||||
cont := it.Next(true)
|
||||
if !cont {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return diffAccounts, nil
|
||||
}
|
||||
|
||||
func (sdb *builder) buildDiffEventual(accounts AccountsMap) ([]AccountDiff, error) {
|
||||
accountDiffs := make([]AccountDiff, 0)
|
||||
var err error
|
||||
for _, val := range accounts {
|
||||
// If account is not nil, we need to process storage diffs
|
||||
var storageDiffs []StorageDiff
|
||||
if val.Account != nil {
|
||||
storageDiffs, err = sdb.buildStorageDiffsEventual(val.Account.Root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed building eventual storage diffs for %s\r\nerror: %v", common.BytesToHash(val.RawKey), err)
|
||||
}
|
||||
}
|
||||
accountDiffs = append(accountDiffs, AccountDiff{
|
||||
Leaf: val.Leaf,
|
||||
Key: val.RawKey,
|
||||
Value: val.RawValue,
|
||||
Proof: val.Proof,
|
||||
Path: val.Path,
|
||||
Storage: storageDiffs,
|
||||
})
|
||||
}
|
||||
|
||||
return accountDiffs, nil
|
||||
}
|
||||
|
||||
func (sdb *builder) buildDiffIncremental(creations AccountsMap, deletions AccountsMap, updatedKeys []string) ([]AccountDiff, error) {
|
||||
updatedAccounts := make([]AccountDiff, 0)
|
||||
var err error
|
||||
for _, val := range updatedKeys {
|
||||
hashKey := common.HexToHash(val)
|
||||
createdAcc := creations[hashKey]
|
||||
deletedAcc := deletions[hashKey]
|
||||
var storageDiffs []StorageDiff
|
||||
if deletedAcc.Account != nil && createdAcc.Account != nil {
|
||||
oldSR := deletedAcc.Account.Root
|
||||
newSR := createdAcc.Account.Root
|
||||
storageDiffs, err = sdb.buildStorageDiffsIncremental(oldSR, newSR)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed building incremental storage diffs for %s\r\nerror: %v", hashKey.Hex(), err)
|
||||
}
|
||||
}
|
||||
updatedAccounts = append(updatedAccounts, AccountDiff{
|
||||
Leaf: createdAcc.Leaf,
|
||||
Key: createdAcc.RawKey,
|
||||
Value: createdAcc.RawValue,
|
||||
Proof: createdAcc.Proof,
|
||||
Path: createdAcc.Path,
|
||||
Storage: storageDiffs,
|
||||
})
|
||||
delete(creations, common.HexToHash(val))
|
||||
delete(deletions, common.HexToHash(val))
|
||||
}
|
||||
|
||||
return updatedAccounts, nil
|
||||
}
|
||||
|
||||
func (sdb *builder) buildStorageDiffsEventual(sr common.Hash) ([]StorageDiff, error) {
|
||||
log.Debug("Storage Root For Eventual Diff", "root", sr.Hex())
|
||||
stateCache := sdb.blockChain.StateCache()
|
||||
sTrie, err := stateCache.OpenTrie(sr)
|
||||
if err != nil {
|
||||
log.Info("error in build storage diff eventual", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
it := sTrie.NodeIterator(make([]byte, 0))
|
||||
return sdb.buildStorageDiffsFromTrie(it)
|
||||
}
|
||||
|
||||
func (sdb *builder) buildStorageDiffsIncremental(oldSR common.Hash, newSR common.Hash) ([]StorageDiff, error) {
|
||||
log.Debug("Storage Roots for Incremental Diff", "old", oldSR.Hex(), "new", newSR.Hex())
|
||||
stateCache := sdb.blockChain.StateCache()
|
||||
|
||||
oldTrie, err := stateCache.OpenTrie(oldSR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTrie, err := stateCache.OpenTrie(newSR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldIt := oldTrie.NodeIterator(make([]byte, 0))
|
||||
newIt := newTrie.NodeIterator(make([]byte, 0))
|
||||
it, _ := trie.NewDifferenceIterator(oldIt, newIt)
|
||||
return sdb.buildStorageDiffsFromTrie(it)
|
||||
}
|
||||
|
||||
func (sdb *builder) buildStorageDiffsFromTrie(it trie.NodeIterator) ([]StorageDiff, error) {
|
||||
storageDiffs := make([]StorageDiff, 0)
|
||||
for {
|
||||
log.Debug("Iterating over state at path ", "path", pathToStr(it))
|
||||
if it.Leaf() {
|
||||
log.Debug("Found leaf in storage", "path", pathToStr(it))
|
||||
leafKey := make([]byte, len(it.LeafKey()))
|
||||
copy(leafKey, it.LeafKey())
|
||||
leafValue := make([]byte, len(it.LeafBlob()))
|
||||
copy(leafValue, it.LeafBlob())
|
||||
sd := StorageDiff{
|
||||
Leaf: true,
|
||||
Key: leafKey,
|
||||
Value: leafValue,
|
||||
}
|
||||
if sdb.config.PathsAndProofs {
|
||||
leafProof := make([][]byte, len(it.LeafProof()))
|
||||
copy(leafProof, it.LeafProof())
|
||||
leafPath := make([]byte, len(it.Path()))
|
||||
copy(leafPath, it.Path())
|
||||
sd.Proof = leafProof
|
||||
sd.Path = leafPath
|
||||
}
|
||||
storageDiffs = append(storageDiffs, sd)
|
||||
} else if sdb.config.IntermediateNodes && !bytes.Equal(nullNode, it.Hash().Bytes()) {
|
||||
nodeKey := it.Hash()
|
||||
node, err := sdb.stateCache.TrieDB().Node(nodeKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error looking up intermediate storage trie node %s\r\nerror: %v", nodeKey.Hex(), err)
|
||||
}
|
||||
storageDiffs = append(storageDiffs, StorageDiff{
|
||||
Leaf: false,
|
||||
Key: nodeKey.Bytes(),
|
||||
Value: node,
|
||||
})
|
||||
log.Debug("intermediate storage trie node lookup successful", "key", nodeKey.Hex(), "value", node)
|
||||
}
|
||||
cont := it.Next(true)
|
||||
if !cont {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return storageDiffs, nil
|
||||
}
|
563
statediff/builder_test.go
Normal file
563
statediff/builder_test.go
Normal file
@ -0,0 +1,563 @@
|
||||
// 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 statediff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/statediff"
|
||||
"github.com/ethereum/go-ethereum/statediff/testhelpers"
|
||||
)
|
||||
|
||||
var (
|
||||
contractLeafKey common.Hash
|
||||
emptyAccountDiffEventualMap = make([]statediff.AccountDiff, 0)
|
||||
emptyAccountDiffIncrementalMap = make([]statediff.AccountDiff, 0)
|
||||
block0, block1, block2, block3 *types.Block
|
||||
builder statediff.Builder
|
||||
miningReward = int64(2000000000000000000)
|
||||
burnAddress = common.HexToAddress("0x0")
|
||||
burnLeafKey = testhelpers.AddressToLeafKey(burnAddress)
|
||||
block0Hash = common.HexToHash("0xd1721cfd0b29c36fd7a68f25c128e86413fb666a6e1d68e89b875bd299262661")
|
||||
block1Hash = common.HexToHash("0xbbe88de60ba33a3f18c0caa37d827bfb70252e19e40a07cd34041696c35ecb1a")
|
||||
block2Hash = common.HexToHash("0xbc57256a9d5ed6055d924da2b40118194c5a567025dc4d97c2267ab25afa72f9")
|
||||
block3Hash = common.HexToHash("0x93cf01a3e6a81b796f9b96509182d2a20e95ca48c990d2777628a5ba7e060312")
|
||||
balanceChange10000 = int64(10000)
|
||||
balanceChange1000 = int64(1000)
|
||||
block1BankBalance = int64(99990000)
|
||||
block1Account1Balance = int64(10000)
|
||||
block2Account2Balance = int64(1000)
|
||||
nonce0 = uint64(0)
|
||||
nonce1 = uint64(1)
|
||||
nonce2 = uint64(2)
|
||||
nonce3 = uint64(3)
|
||||
originalContractRoot = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
||||
contractContractRoot = "0x821e2556a290c86405f8160a2d662042a431ba456b9db265c79bb837c04be5f0"
|
||||
newContractRoot = "0x71e0d14b2b93e5c7f9748e69e1fe5f17498a1c3ac3cec29f96af13d7f8a4e070"
|
||||
originalStorageLocation = common.HexToHash("0")
|
||||
originalStorageKey = crypto.Keccak256Hash(originalStorageLocation[:]).Bytes()
|
||||
updatedStorageLocation = common.HexToHash("2")
|
||||
updatedStorageKey = crypto.Keccak256Hash(updatedStorageLocation[:]).Bytes()
|
||||
originalStorageValue = common.Hex2Bytes("01")
|
||||
updatedStorageValue = common.Hex2Bytes("03")
|
||||
|
||||
account1, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce0,
|
||||
Balance: big.NewInt(balanceChange10000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
burnAccount1, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce0,
|
||||
Balance: big.NewInt(miningReward),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
bankAccount1, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce1,
|
||||
Balance: big.NewInt(testhelpers.TestBankFunds.Int64() - balanceChange10000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
account2, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce0,
|
||||
Balance: big.NewInt(balanceChange1000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
contractAccount, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce1,
|
||||
Balance: big.NewInt(0),
|
||||
CodeHash: common.HexToHash("0x753f98a8d4328b15636e46f66f2cb4bc860100aa17967cc145fcd17d1d4710ea").Bytes(),
|
||||
Root: common.HexToHash(contractContractRoot),
|
||||
})
|
||||
bankAccount2, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce2,
|
||||
Balance: big.NewInt(block1BankBalance - balanceChange1000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
account3, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce2,
|
||||
Balance: big.NewInt(block1Account1Balance - balanceChange1000 + balanceChange1000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
burnAccount2, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce0,
|
||||
Balance: big.NewInt(miningReward + miningReward),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
account4, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce0,
|
||||
Balance: big.NewInt(block2Account2Balance + miningReward),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
contractAccount2, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce1,
|
||||
Balance: big.NewInt(0),
|
||||
CodeHash: common.HexToHash("0x753f98a8d4328b15636e46f66f2cb4bc860100aa17967cc145fcd17d1d4710ea").Bytes(),
|
||||
Root: common.HexToHash(newContractRoot),
|
||||
})
|
||||
bankAccount3, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: nonce3,
|
||||
Balance: big.NewInt(99989000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash(originalContractRoot),
|
||||
})
|
||||
)
|
||||
|
||||
type arguments struct {
|
||||
oldStateRoot common.Hash
|
||||
newStateRoot common.Hash
|
||||
blockNumber *big.Int
|
||||
blockHash common.Hash
|
||||
}
|
||||
|
||||
func TestBuilder(t *testing.T) {
|
||||
_, blockMap, chain := testhelpers.MakeChain(3, testhelpers.Genesis)
|
||||
contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr)
|
||||
defer chain.Stop()
|
||||
block0 = blockMap[block0Hash]
|
||||
block1 = blockMap[block1Hash]
|
||||
block2 = blockMap[block2Hash]
|
||||
block3 = blockMap[block3Hash]
|
||||
config := statediff.Config{
|
||||
PathsAndProofs: true,
|
||||
IntermediateNodes: false,
|
||||
}
|
||||
builder = statediff.NewBuilder(testhelpers.Testdb, chain, config)
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
startingArguments arguments
|
||||
expected *statediff.StateDiff
|
||||
}{
|
||||
{
|
||||
"testEmptyDiff",
|
||||
arguments{
|
||||
oldStateRoot: block0.Root(),
|
||||
newStateRoot: block0.Root(),
|
||||
blockNumber: block0.Number(),
|
||||
blockHash: block0Hash,
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block0.Number(),
|
||||
BlockHash: block0Hash,
|
||||
CreatedAccounts: emptyAccountDiffEventualMap,
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: emptyAccountDiffIncrementalMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
"testBlock1",
|
||||
//10000 transferred from testBankAddress to account1Addr
|
||||
arguments{
|
||||
oldStateRoot: block0.Root(),
|
||||
newStateRoot: block1.Root(),
|
||||
blockNumber: block1.Number(),
|
||||
blockHash: block1Hash,
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block1.Number(),
|
||||
BlockHash: block1.Hash(),
|
||||
CreatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: burnLeafKey.Bytes(),
|
||||
Value: burnAccount1,
|
||||
Proof: [][]byte{{248, 113, 160, 87, 118, 82, 182, 37, 183, 123, 219, 91, 247, 123, 196, 63, 49, 37, 202, 215, 70, 77, 103, 157, 21, 117, 86, 82, 119, 211, 97, 27, 128, 83, 231, 128, 128, 128, 128, 160, 254, 136, 159, 16, 229, 219, 143, 44, 43, 243, 85, 146, 129, 82, 161, 127, 110, 59, 185, 154, 146, 65, 172, 109, 132, 199, 126, 98, 100, 80, 156, 121, 128, 128, 128, 128, 128, 128, 128, 128, 160, 17, 219, 12, 218, 52, 168, 150, 218, 190, 182, 131, 155, 176, 106, 56, 244, 149, 20, 207, 164, 134, 67, 89, 132, 235, 1, 59, 125, 249, 238, 133, 197, 128, 128},
|
||||
{248, 113, 160, 51, 128, 199, 183, 174, 129, 165, 142, 185, 141, 156, 120, 222, 74, 31, 215, 253, 149, 53, 252, 149, 62, 210, 190, 96, 45, 170, 164, 23, 103, 49, 42, 184, 78, 248, 76, 128, 136, 27, 193, 109, 103, 78, 200, 0, 0, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{5, 3, 8, 0, 12, 7, 11, 7, 10, 14, 8, 1, 10, 5, 8, 14, 11, 9, 8, 13, 9, 12, 7, 8, 13, 14, 4, 10, 1, 15, 13, 7, 15, 13, 9, 5, 3, 5, 15, 12, 9, 5, 3, 14, 13, 2, 11, 14, 6, 0, 2, 13, 10, 10, 10, 4, 1, 7, 6, 7, 3, 1, 2, 10, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.Account1LeafKey.Bytes(),
|
||||
Value: account1,
|
||||
Proof: [][]byte{{248, 113, 160, 87, 118, 82, 182, 37, 183, 123, 219, 91, 247, 123, 196, 63, 49, 37, 202, 215, 70, 77, 103, 157, 21, 117, 86, 82, 119, 211, 97, 27, 128, 83, 231, 128, 128, 128, 128, 160, 254, 136, 159, 16, 229, 219, 143, 44, 43, 243, 85, 146, 129, 82, 161, 127, 110, 59, 185, 154, 146, 65, 172, 109, 132, 199, 126, 98, 100, 80, 156, 121, 128, 128, 128, 128, 128, 128, 128, 128, 160, 17, 219, 12, 218, 52, 168, 150, 218, 190, 182, 131, 155, 176, 106, 56, 244, 149, 20, 207, 164, 134, 67, 89, 132, 235, 1, 59, 125, 249, 238, 133, 197, 128, 128},
|
||||
{248, 107, 160, 57, 38, 219, 105, 170, 206, 213, 24, 233, 185, 240, 244, 52, 164, 115, 231, 23, 65, 9, 201, 67, 84, 139, 184, 242, 59, 228, 28, 167, 109, 154, 210, 184, 72, 248, 70, 128, 130, 39, 16, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{14, 9, 2, 6, 13, 11, 6, 9, 10, 10, 12, 14, 13, 5, 1, 8, 14, 9, 11, 9, 15, 0, 15, 4, 3, 4, 10, 4, 7, 3, 14, 7, 1, 7, 4, 1, 0, 9, 12, 9, 4, 3, 5, 4, 8, 11, 11, 8, 15, 2, 3, 11, 14, 4, 1, 12, 10, 7, 6, 13, 9, 10, 13, 2, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.BankLeafKey.Bytes(),
|
||||
Value: bankAccount1,
|
||||
Proof: [][]byte{{248, 113, 160, 87, 118, 82, 182, 37, 183, 123, 219, 91, 247, 123, 196, 63, 49, 37, 202, 215, 70, 77, 103, 157, 21, 117, 86, 82, 119, 211, 97, 27, 128, 83, 231, 128, 128, 128, 128, 160, 254, 136, 159, 16, 229, 219, 143, 44, 43, 243, 85, 146, 129, 82, 161, 127, 110, 59, 185, 154, 146, 65, 172, 109, 132, 199, 126, 98, 100, 80, 156, 121, 128, 128, 128, 128, 128, 128, 128, 128, 160, 17, 219, 12, 218, 52, 168, 150, 218, 190, 182, 131, 155, 176, 106, 56, 244, 149, 20, 207, 164, 134, 67, 89, 132, 235, 1, 59, 125, 249, 238, 133, 197, 128, 128},
|
||||
{248, 109, 160, 48, 191, 73, 244, 64, 161, 205, 5, 39, 228, 208, 110, 39, 101, 101, 76, 15, 86, 69, 34, 87, 81, 109, 121, 58, 155, 141, 96, 77, 207, 223, 42, 184, 74, 248, 72, 1, 132, 5, 245, 185, 240, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{0, 0, 11, 15, 4, 9, 15, 4, 4, 0, 10, 1, 12, 13, 0, 5, 2, 7, 14, 4, 13, 0, 6, 14, 2, 7, 6, 5, 6, 5, 4, 12, 0, 15, 5, 6, 4, 5, 2, 2, 5, 7, 5, 1, 6, 13, 7, 9, 3, 10, 9, 11, 8, 13, 6, 0, 4, 13, 12, 15, 13, 15, 2, 10, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"testBlock2",
|
||||
//1000 transferred from testBankAddress to account1Addr
|
||||
//1000 transferred from account1Addr to account2Addr
|
||||
arguments{
|
||||
oldStateRoot: block1.Root(),
|
||||
newStateRoot: block2.Root(),
|
||||
blockNumber: block2.Number(),
|
||||
blockHash: block2Hash,
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block2.Number(),
|
||||
BlockHash: block2.Hash(),
|
||||
CreatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: contractLeafKey.Bytes(),
|
||||
Value: contractAccount,
|
||||
Proof: [][]byte{{248, 177, 160, 177, 155, 238, 178, 242, 47, 83, 2, 49, 141, 155, 92, 149, 175, 245, 120, 233, 177, 101, 67, 46, 200, 23, 250, 41, 74, 135, 94, 61, 133, 51, 162, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 114, 57, 32, 11, 115, 232, 140, 238, 165, 222, 121, 226, 208, 2, 192, 216, 67, 198, 179, 31, 181, 27, 208, 243, 99, 202, 48, 148, 207, 107, 106, 177, 128, 128, 128, 128, 128, 160, 10, 173, 165, 125, 110, 240, 77, 112, 149, 100, 135, 237, 25, 228, 116, 7, 195, 9, 210, 166, 208, 148, 101, 23, 244, 238, 84, 84, 211, 249, 138, 137, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 105, 160, 49, 20, 101, 138, 116, 217, 204, 159, 122, 207, 44, 92, 214, 150, 195, 73, 77, 124, 52, 77, 120, 191, 236, 58, 221, 13, 145, 236, 78, 141, 28, 69, 184, 70, 248, 68, 1, 128, 160, 130, 30, 37, 86, 162, 144, 200, 100, 5, 248, 22, 10, 45, 102, 32, 66, 164, 49, 186, 69, 107, 157, 178, 101, 199, 155, 184, 55, 192, 75, 229, 240, 160, 117, 63, 152, 168, 212, 50, 139, 21, 99, 110, 70, 246, 111, 44, 180, 188, 134, 1, 0, 170, 23, 150, 124, 193, 69, 252, 209, 125, 29, 71, 16, 234}},
|
||||
Path: []byte{6, 1, 1, 4, 6, 5, 8, 10, 7, 4, 13, 9, 12, 12, 9, 15, 7, 10, 12, 15, 2, 12, 5, 12, 13, 6, 9, 6, 12, 3, 4, 9, 4, 13, 7, 12, 3, 4, 4, 13, 7, 8, 11, 15, 14, 12, 3, 10, 13, 13, 0, 13, 9, 1, 14, 12, 4, 14, 8, 13, 1, 12, 4, 5, 16},
|
||||
Storage: []statediff.StorageDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: originalStorageKey,
|
||||
Value: originalStorageValue,
|
||||
Proof: [][]byte{{227, 161, 32, 41, 13, 236, 217, 84, 139, 98, 168, 214, 3, 69, 169, 136, 56, 111, 200, 75, 166, 188, 149, 72, 64, 8, 246, 54, 47, 147, 22, 14, 243, 229, 99, 1}},
|
||||
Path: []byte{2, 9, 0, 13, 14, 12, 13, 9, 5, 4, 8, 11, 6, 2, 10, 8, 13, 6, 0, 3, 4, 5, 10, 9, 8, 8, 3, 8, 6, 15, 12, 8, 4, 11, 10, 6, 11, 12, 9, 5, 4, 8, 4, 0, 0, 8, 15, 6, 3, 6, 2, 15, 9, 3, 1, 6, 0, 14, 15, 3, 14, 5, 6, 3, 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.Account2LeafKey.Bytes(),
|
||||
Value: account2,
|
||||
Proof: [][]byte{{248, 177, 160, 177, 155, 238, 178, 242, 47, 83, 2, 49, 141, 155, 92, 149, 175, 245, 120, 233, 177, 101, 67, 46, 200, 23, 250, 41, 74, 135, 94, 61, 133, 51, 162, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 114, 57, 32, 11, 115, 232, 140, 238, 165, 222, 121, 226, 208, 2, 192, 216, 67, 198, 179, 31, 181, 27, 208, 243, 99, 202, 48, 148, 207, 107, 106, 177, 128, 128, 128, 128, 128, 160, 10, 173, 165, 125, 110, 240, 77, 112, 149, 100, 135, 237, 25, 228, 116, 7, 195, 9, 210, 166, 208, 148, 101, 23, 244, 238, 84, 84, 211, 249, 138, 137, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 107, 160, 57, 87, 243, 226, 240, 74, 7, 100, 195, 160, 73, 27, 23, 95, 105, 146, 109, 166, 30, 251, 204, 143, 97, 250, 20, 85, 253, 45, 43, 76, 221, 69, 184, 72, 248, 70, 128, 130, 3, 232, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{12, 9, 5, 7, 15, 3, 14, 2, 15, 0, 4, 10, 0, 7, 6, 4, 12, 3, 10, 0, 4, 9, 1, 11, 1, 7, 5, 15, 6, 9, 9, 2, 6, 13, 10, 6, 1, 14, 15, 11, 12, 12, 8, 15, 6, 1, 15, 10, 1, 4, 5, 5, 15, 13, 2, 13, 2, 11, 4, 12, 13, 13, 4, 5, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.BankLeafKey.Bytes(),
|
||||
Value: bankAccount2,
|
||||
Proof: [][]byte{{248, 177, 160, 177, 155, 238, 178, 242, 47, 83, 2, 49, 141, 155, 92, 149, 175, 245, 120, 233, 177, 101, 67, 46, 200, 23, 250, 41, 74, 135, 94, 61, 133, 51, 162, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 114, 57, 32, 11, 115, 232, 140, 238, 165, 222, 121, 226, 208, 2, 192, 216, 67, 198, 179, 31, 181, 27, 208, 243, 99, 202, 48, 148, 207, 107, 106, 177, 128, 128, 128, 128, 128, 160, 10, 173, 165, 125, 110, 240, 77, 112, 149, 100, 135, 237, 25, 228, 116, 7, 195, 9, 210, 166, 208, 148, 101, 23, 244, 238, 84, 84, 211, 249, 138, 137, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 109, 160, 48, 191, 73, 244, 64, 161, 205, 5, 39, 228, 208, 110, 39, 101, 101, 76, 15, 86, 69, 34, 87, 81, 109, 121, 58, 155, 141, 96, 77, 207, 223, 42, 184, 74, 248, 72, 2, 132, 5, 245, 182, 8, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{0, 0, 11, 15, 4, 9, 15, 4, 4, 0, 10, 1, 12, 13, 0, 5, 2, 7, 14, 4, 13, 0, 6, 14, 2, 7, 6, 5, 6, 5, 4, 12, 0, 15, 5, 6, 4, 5, 2, 2, 5, 7, 5, 1, 6, 13, 7, 9, 3, 10, 9, 11, 8, 13, 6, 0, 4, 13, 12, 15, 13, 15, 2, 10, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
{
|
||||
Leaf: true,
|
||||
Key: burnLeafKey.Bytes(),
|
||||
Value: burnAccount2,
|
||||
Proof: [][]byte{{248, 177, 160, 177, 155, 238, 178, 242, 47, 83, 2, 49, 141, 155, 92, 149, 175, 245, 120, 233, 177, 101, 67, 46, 200, 23, 250, 41, 74, 135, 94, 61, 133, 51, 162, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 114, 57, 32, 11, 115, 232, 140, 238, 165, 222, 121, 226, 208, 2, 192, 216, 67, 198, 179, 31, 181, 27, 208, 243, 99, 202, 48, 148, 207, 107, 106, 177, 128, 128, 128, 128, 128, 160, 10, 173, 165, 125, 110, 240, 77, 112, 149, 100, 135, 237, 25, 228, 116, 7, 195, 9, 210, 166, 208, 148, 101, 23, 244, 238, 84, 84, 211, 249, 138, 137, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 113, 160, 51, 128, 199, 183, 174, 129, 165, 142, 185, 141, 156, 120, 222, 74, 31, 215, 253, 149, 53, 252, 149, 62, 210, 190, 96, 45, 170, 164, 23, 103, 49, 42, 184, 78, 248, 76, 128, 136, 55, 130, 218, 206, 157, 144, 0, 0, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{5, 3, 8, 0, 12, 7, 11, 7, 10, 14, 8, 1, 10, 5, 8, 14, 11, 9, 8, 13, 9, 12, 7, 8, 13, 14, 4, 10, 1, 15, 13, 7, 15, 13, 9, 5, 3, 5, 15, 12, 9, 5, 3, 14, 13, 2, 11, 14, 6, 0, 2, 13, 10, 10, 10, 4, 1, 7, 6, 7, 3, 1, 2, 10, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.Account1LeafKey.Bytes(),
|
||||
Value: account3,
|
||||
Proof: [][]byte{{248, 177, 160, 177, 155, 238, 178, 242, 47, 83, 2, 49, 141, 155, 92, 149, 175, 245, 120, 233, 177, 101, 67, 46, 200, 23, 250, 41, 74, 135, 94, 61, 133, 51, 162, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 114, 57, 32, 11, 115, 232, 140, 238, 165, 222, 121, 226, 208, 2, 192, 216, 67, 198, 179, 31, 181, 27, 208, 243, 99, 202, 48, 148, 207, 107, 106, 177, 128, 128, 128, 128, 128, 160, 10, 173, 165, 125, 110, 240, 77, 112, 149, 100, 135, 237, 25, 228, 116, 7, 195, 9, 210, 166, 208, 148, 101, 23, 244, 238, 84, 84, 211, 249, 138, 137, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 107, 160, 57, 38, 219, 105, 170, 206, 213, 24, 233, 185, 240, 244, 52, 164, 115, 231, 23, 65, 9, 201, 67, 84, 139, 184, 242, 59, 228, 28, 167, 109, 154, 210, 184, 72, 248, 70, 2, 130, 39, 16, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{14, 9, 2, 6, 13, 11, 6, 9, 10, 10, 12, 14, 13, 5, 1, 8, 14, 9, 11, 9, 15, 0, 15, 4, 3, 4, 10, 4, 7, 3, 14, 7, 1, 7, 4, 1, 0, 9, 12, 9, 4, 3, 5, 4, 8, 11, 11, 8, 15, 2, 3, 11, 14, 4, 1, 12, 10, 7, 6, 13, 9, 10, 13, 2, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"testBlock3",
|
||||
//the contract's storage is changed
|
||||
//and the block is mined by account 2
|
||||
arguments{
|
||||
oldStateRoot: block2.Root(),
|
||||
newStateRoot: block3.Root(),
|
||||
blockNumber: block3.Number(),
|
||||
blockHash: block3.Hash(),
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block3.Number(),
|
||||
BlockHash: block3.Hash(),
|
||||
CreatedAccounts: []statediff.AccountDiff{},
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.BankLeafKey.Bytes(),
|
||||
Value: bankAccount3,
|
||||
Proof: [][]byte{{248, 177, 160, 101, 223, 138, 81, 34, 40, 229, 170, 198, 188, 136, 99, 7, 55, 33, 112, 160, 111, 181, 131, 167, 201, 131, 24, 201, 211, 177, 30, 159, 229, 246, 6, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 32, 135, 108, 213, 150, 150, 110, 44, 170, 65, 75, 154, 74, 249, 94, 65, 74, 107, 100, 115, 39, 5, 3, 26, 22, 238, 138, 114, 254, 21, 6, 171, 128, 128, 128, 128, 128, 160, 4, 228, 121, 222, 255, 218, 60, 247, 15, 0, 34, 198, 28, 229, 180, 129, 109, 157, 68, 181, 248, 229, 200, 123, 29, 81, 145, 114, 90, 209, 205, 210, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 109, 160, 48, 191, 73, 244, 64, 161, 205, 5, 39, 228, 208, 110, 39, 101, 101, 76, 15, 86, 69, 34, 87, 81, 109, 121, 58, 155, 141, 96, 77, 207, 223, 42, 184, 74, 248, 72, 3, 132, 5, 245, 182, 8, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{0, 0, 11, 15, 4, 9, 15, 4, 4, 0, 10, 1, 12, 13, 0, 5, 2, 7, 14, 4, 13, 0, 6, 14, 2, 7, 6, 5, 6, 5, 4, 12, 0, 15, 5, 6, 4, 5, 2, 2, 5, 7, 5, 1, 6, 13, 7, 9, 3, 10, 9, 11, 8, 13, 6, 0, 4, 13, 12, 15, 13, 15, 2, 10, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
{
|
||||
Leaf: true,
|
||||
Key: contractLeafKey.Bytes(),
|
||||
Value: contractAccount2,
|
||||
Proof: [][]byte{{248, 177, 160, 101, 223, 138, 81, 34, 40, 229, 170, 198, 188, 136, 99, 7, 55, 33, 112, 160, 111, 181, 131, 167, 201, 131, 24, 201, 211, 177, 30, 159, 229, 246, 6, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 32, 135, 108, 213, 150, 150, 110, 44, 170, 65, 75, 154, 74, 249, 94, 65, 74, 107, 100, 115, 39, 5, 3, 26, 22, 238, 138, 114, 254, 21, 6, 171, 128, 128, 128, 128, 128, 160, 4, 228, 121, 222, 255, 218, 60, 247, 15, 0, 34, 198, 28, 229, 180, 129, 109, 157, 68, 181, 248, 229, 200, 123, 29, 81, 145, 114, 90, 209, 205, 210, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 105, 160, 49, 20, 101, 138, 116, 217, 204, 159, 122, 207, 44, 92, 214, 150, 195, 73, 77, 124, 52, 77, 120, 191, 236, 58, 221, 13, 145, 236, 78, 141, 28, 69, 184, 70, 248, 68, 1, 128, 160, 113, 224, 209, 75, 43, 147, 229, 199, 249, 116, 142, 105, 225, 254, 95, 23, 73, 138, 28, 58, 195, 206, 194, 159, 150, 175, 19, 215, 248, 164, 224, 112, 160, 117, 63, 152, 168, 212, 50, 139, 21, 99, 110, 70, 246, 111, 44, 180, 188, 134, 1, 0, 170, 23, 150, 124, 193, 69, 252, 209, 125, 29, 71, 16, 234}},
|
||||
Path: []byte{6, 1, 1, 4, 6, 5, 8, 10, 7, 4, 13, 9, 12, 12, 9, 15, 7, 10, 12, 15, 2, 12, 5, 12, 13, 6, 9, 6, 12, 3, 4, 9, 4, 13, 7, 12, 3, 4, 4, 13, 7, 8, 11, 15, 14, 12, 3, 10, 13, 13, 0, 13, 9, 1, 14, 12, 4, 14, 8, 13, 1, 12, 4, 5, 16},
|
||||
Storage: []statediff.StorageDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: updatedStorageKey,
|
||||
Value: updatedStorageValue,
|
||||
Proof: [][]byte{{248, 81, 128, 128, 160, 79, 197, 241, 58, 178, 249, 186, 12, 45, 168, 139, 1, 81, 171, 14, 124, 244, 216, 93, 8, 204, 164, 92, 205, 146, 60, 106, 183, 99, 35, 235, 40, 128, 160, 205, 69, 114, 89, 105, 97, 21, 35, 94, 100, 199, 130, 35, 52, 214, 33, 41, 226, 241, 96, 68, 37, 167, 218, 100, 148, 243, 95, 196, 91, 229, 24, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},
|
||||
{226, 160, 48, 87, 135, 250, 18, 168, 35, 224, 242, 183, 99, 28, 196, 27, 59, 168, 130, 139, 51, 33, 202, 129, 17, 17, 250, 117, 205, 58, 163, 187, 90, 206, 3}},
|
||||
Path: []byte{4, 0, 5, 7, 8, 7, 15, 10, 1, 2, 10, 8, 2, 3, 14, 0, 15, 2, 11, 7, 6, 3, 1, 12, 12, 4, 1, 11, 3, 11, 10, 8, 8, 2, 8, 11, 3, 3, 2, 1, 12, 10, 8, 1, 1, 1, 1, 1, 15, 10, 7, 5, 12, 13, 3, 10, 10, 3, 11, 11, 5, 10, 12, 14, 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.Account2LeafKey.Bytes(),
|
||||
Value: account4,
|
||||
Proof: [][]byte{{248, 177, 160, 101, 223, 138, 81, 34, 40, 229, 170, 198, 188, 136, 99, 7, 55, 33, 112, 160, 111, 181, 131, 167, 201, 131, 24, 201, 211, 177, 30, 159, 229, 246, 6, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 32, 135, 108, 213, 150, 150, 110, 44, 170, 65, 75, 154, 74, 249, 94, 65, 74, 107, 100, 115, 39, 5, 3, 26, 22, 238, 138, 114, 254, 21, 6, 171, 128, 128, 128, 128, 128, 160, 4, 228, 121, 222, 255, 218, 60, 247, 15, 0, 34, 198, 28, 229, 180, 129, 109, 157, 68, 181, 248, 229, 200, 123, 29, 81, 145, 114, 90, 209, 205, 210, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 113, 160, 57, 87, 243, 226, 240, 74, 7, 100, 195, 160, 73, 27, 23, 95, 105, 146, 109, 166, 30, 251, 204, 143, 97, 250, 20, 85, 253, 45, 43, 76, 221, 69, 184, 78, 248, 76, 128, 136, 27, 193, 109, 103, 78, 200, 3, 232, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{12, 9, 5, 7, 15, 3, 14, 2, 15, 0, 4, 10, 0, 7, 6, 4, 12, 3, 10, 0, 4, 9, 1, 11, 1, 7, 5, 15, 6, 9, 9, 2, 6, 13, 10, 6, 1, 14, 15, 11, 12, 12, 8, 15, 6, 1, 15, 10, 1, 4, 5, 5, 15, 13, 2, 13, 2, 11, 4, 12, 13, 13, 4, 5, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
arguments := test.startingArguments
|
||||
diff, err := builder.BuildStateDiff(arguments.oldStateRoot, arguments.newStateRoot, arguments.blockNumber, arguments.blockHash)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
receivedStateDiffRlp, err := rlp.EncodeToBytes(diff)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] })
|
||||
sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] })
|
||||
if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) {
|
||||
t.Logf("Test failed: %s", test.name)
|
||||
t.Errorf("actual state diff rlp: %+v\nexpected state diff rlp: %+v", receivedStateDiffRlp, expectedStateDiffRlp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderWithWatchedAddressList(t *testing.T) {
|
||||
_, blockMap, chain := testhelpers.MakeChain(3, testhelpers.Genesis)
|
||||
contractLeafKey = testhelpers.AddressToLeafKey(testhelpers.ContractAddr)
|
||||
defer chain.Stop()
|
||||
block0 = blockMap[block0Hash]
|
||||
block1 = blockMap[block1Hash]
|
||||
block2 = blockMap[block2Hash]
|
||||
block3 = blockMap[block3Hash]
|
||||
config := statediff.Config{
|
||||
PathsAndProofs: true,
|
||||
IntermediateNodes: false,
|
||||
WatchedAddresses: []string{testhelpers.Account1Addr.Hex(), testhelpers.ContractAddr.Hex()},
|
||||
}
|
||||
builder = statediff.NewBuilder(testhelpers.Testdb, chain, config)
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
startingArguments arguments
|
||||
expected *statediff.StateDiff
|
||||
}{
|
||||
{
|
||||
"testEmptyDiff",
|
||||
arguments{
|
||||
oldStateRoot: block0.Root(),
|
||||
newStateRoot: block0.Root(),
|
||||
blockNumber: block0.Number(),
|
||||
blockHash: block0Hash,
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block0.Number(),
|
||||
BlockHash: block0Hash,
|
||||
CreatedAccounts: emptyAccountDiffEventualMap,
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: emptyAccountDiffIncrementalMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
"testBlock1",
|
||||
//10000 transferred from testBankAddress to account1Addr
|
||||
arguments{
|
||||
oldStateRoot: block0.Root(),
|
||||
newStateRoot: block1.Root(),
|
||||
blockNumber: block1.Number(),
|
||||
blockHash: block1Hash,
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block1.Number(),
|
||||
BlockHash: block1.Hash(),
|
||||
CreatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.Account1LeafKey.Bytes(),
|
||||
Value: account1,
|
||||
Proof: [][]byte{{248, 113, 160, 87, 118, 82, 182, 37, 183, 123, 219, 91, 247, 123, 196, 63, 49, 37, 202, 215, 70, 77, 103, 157, 21, 117, 86, 82, 119, 211, 97, 27, 128, 83, 231, 128, 128, 128, 128, 160, 254, 136, 159, 16, 229, 219, 143, 44, 43, 243, 85, 146, 129, 82, 161, 127, 110, 59, 185, 154, 146, 65, 172, 109, 132, 199, 126, 98, 100, 80, 156, 121, 128, 128, 128, 128, 128, 128, 128, 128, 160, 17, 219, 12, 218, 52, 168, 150, 218, 190, 182, 131, 155, 176, 106, 56, 244, 149, 20, 207, 164, 134, 67, 89, 132, 235, 1, 59, 125, 249, 238, 133, 197, 128, 128},
|
||||
{248, 107, 160, 57, 38, 219, 105, 170, 206, 213, 24, 233, 185, 240, 244, 52, 164, 115, 231, 23, 65, 9, 201, 67, 84, 139, 184, 242, 59, 228, 28, 167, 109, 154, 210, 184, 72, 248, 70, 128, 130, 39, 16, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{14, 9, 2, 6, 13, 11, 6, 9, 10, 10, 12, 14, 13, 5, 1, 8, 14, 9, 11, 9, 15, 0, 15, 4, 3, 4, 10, 4, 7, 3, 14, 7, 1, 7, 4, 1, 0, 9, 12, 9, 4, 3, 5, 4, 8, 11, 11, 8, 15, 2, 3, 11, 14, 4, 1, 12, 10, 7, 6, 13, 9, 10, 13, 2, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: []statediff.AccountDiff{},
|
||||
},
|
||||
},
|
||||
{
|
||||
"testBlock2",
|
||||
//1000 transferred from testBankAddress to account1Addr
|
||||
//1000 transferred from account1Addr to account2Addr
|
||||
arguments{
|
||||
oldStateRoot: block1.Root(),
|
||||
newStateRoot: block2.Root(),
|
||||
blockNumber: block2.Number(),
|
||||
blockHash: block2Hash,
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block2.Number(),
|
||||
BlockHash: block2.Hash(),
|
||||
CreatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: contractLeafKey.Bytes(),
|
||||
Value: contractAccount,
|
||||
Proof: [][]byte{{248, 177, 160, 177, 155, 238, 178, 242, 47, 83, 2, 49, 141, 155, 92, 149, 175, 245, 120, 233, 177, 101, 67, 46, 200, 23, 250, 41, 74, 135, 94, 61, 133, 51, 162, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 114, 57, 32, 11, 115, 232, 140, 238, 165, 222, 121, 226, 208, 2, 192, 216, 67, 198, 179, 31, 181, 27, 208, 243, 99, 202, 48, 148, 207, 107, 106, 177, 128, 128, 128, 128, 128, 160, 10, 173, 165, 125, 110, 240, 77, 112, 149, 100, 135, 237, 25, 228, 116, 7, 195, 9, 210, 166, 208, 148, 101, 23, 244, 238, 84, 84, 211, 249, 138, 137, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 105, 160, 49, 20, 101, 138, 116, 217, 204, 159, 122, 207, 44, 92, 214, 150, 195, 73, 77, 124, 52, 77, 120, 191, 236, 58, 221, 13, 145, 236, 78, 141, 28, 69, 184, 70, 248, 68, 1, 128, 160, 130, 30, 37, 86, 162, 144, 200, 100, 5, 248, 22, 10, 45, 102, 32, 66, 164, 49, 186, 69, 107, 157, 178, 101, 199, 155, 184, 55, 192, 75, 229, 240, 160, 117, 63, 152, 168, 212, 50, 139, 21, 99, 110, 70, 246, 111, 44, 180, 188, 134, 1, 0, 170, 23, 150, 124, 193, 69, 252, 209, 125, 29, 71, 16, 234}},
|
||||
Path: []byte{6, 1, 1, 4, 6, 5, 8, 10, 7, 4, 13, 9, 12, 12, 9, 15, 7, 10, 12, 15, 2, 12, 5, 12, 13, 6, 9, 6, 12, 3, 4, 9, 4, 13, 7, 12, 3, 4, 4, 13, 7, 8, 11, 15, 14, 12, 3, 10, 13, 13, 0, 13, 9, 1, 14, 12, 4, 14, 8, 13, 1, 12, 4, 5, 16},
|
||||
Storage: []statediff.StorageDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: originalStorageKey,
|
||||
Value: originalStorageValue,
|
||||
Proof: [][]byte{{227, 161, 32, 41, 13, 236, 217, 84, 139, 98, 168, 214, 3, 69, 169, 136, 56, 111, 200, 75, 166, 188, 149, 72, 64, 8, 246, 54, 47, 147, 22, 14, 243, 229, 99, 1}},
|
||||
Path: []byte{2, 9, 0, 13, 14, 12, 13, 9, 5, 4, 8, 11, 6, 2, 10, 8, 13, 6, 0, 3, 4, 5, 10, 9, 8, 8, 3, 8, 6, 15, 12, 8, 4, 11, 10, 6, 11, 12, 9, 5, 4, 8, 4, 0, 0, 8, 15, 6, 3, 6, 2, 15, 9, 3, 1, 6, 0, 14, 15, 3, 14, 5, 6, 3, 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.Account1LeafKey.Bytes(),
|
||||
Value: account3,
|
||||
Proof: [][]byte{{248, 177, 160, 177, 155, 238, 178, 242, 47, 83, 2, 49, 141, 155, 92, 149, 175, 245, 120, 233, 177, 101, 67, 46, 200, 23, 250, 41, 74, 135, 94, 61, 133, 51, 162, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 114, 57, 32, 11, 115, 232, 140, 238, 165, 222, 121, 226, 208, 2, 192, 216, 67, 198, 179, 31, 181, 27, 208, 243, 99, 202, 48, 148, 207, 107, 106, 177, 128, 128, 128, 128, 128, 160, 10, 173, 165, 125, 110, 240, 77, 112, 149, 100, 135, 237, 25, 228, 116, 7, 195, 9, 210, 166, 208, 148, 101, 23, 244, 238, 84, 84, 211, 249, 138, 137, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 107, 160, 57, 38, 219, 105, 170, 206, 213, 24, 233, 185, 240, 244, 52, 164, 115, 231, 23, 65, 9, 201, 67, 84, 139, 184, 242, 59, 228, 28, 167, 109, 154, 210, 184, 72, 248, 70, 2, 130, 39, 16, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{14, 9, 2, 6, 13, 11, 6, 9, 10, 10, 12, 14, 13, 5, 1, 8, 14, 9, 11, 9, 15, 0, 15, 4, 3, 4, 10, 4, 7, 3, 14, 7, 1, 7, 4, 1, 0, 9, 12, 9, 4, 3, 5, 4, 8, 11, 11, 8, 15, 2, 3, 11, 14, 4, 1, 12, 10, 7, 6, 13, 9, 10, 13, 2, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"testBlock3",
|
||||
//the contract's storage is changed
|
||||
//and the block is mined by account 2
|
||||
arguments{
|
||||
oldStateRoot: block2.Root(),
|
||||
newStateRoot: block3.Root(),
|
||||
blockNumber: block3.Number(),
|
||||
blockHash: block3.Hash(),
|
||||
},
|
||||
&statediff.StateDiff{
|
||||
BlockNumber: block3.Number(),
|
||||
BlockHash: block3.Hash(),
|
||||
CreatedAccounts: []statediff.AccountDiff{},
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: contractLeafKey.Bytes(),
|
||||
Value: contractAccount2,
|
||||
Proof: [][]byte{{248, 177, 160, 101, 223, 138, 81, 34, 40, 229, 170, 198, 188, 136, 99, 7, 55, 33, 112, 160, 111, 181, 131, 167, 201, 131, 24, 201, 211, 177, 30, 159, 229, 246, 6, 128, 128, 128, 128, 160, 179, 86, 53, 29, 96, 188, 152, 148, 207, 31, 29, 108, 182, 140, 129, 95, 1, 49, 213, 15, 29, 168, 60, 64, 35, 160, 158, 200, 85, 207, 255, 145, 160, 32, 135, 108, 213, 150, 150, 110, 44, 170, 65, 75, 154, 74, 249, 94, 65, 74, 107, 100, 115, 39, 5, 3, 26, 22, 238, 138, 114, 254, 21, 6, 171, 128, 128, 128, 128, 128, 160, 4, 228, 121, 222, 255, 218, 60, 247, 15, 0, 34, 198, 28, 229, 180, 129, 109, 157, 68, 181, 248, 229, 200, 123, 29, 81, 145, 114, 90, 209, 205, 210, 128, 160, 255, 115, 147, 190, 57, 135, 174, 188, 86, 51, 227, 70, 22, 253, 237, 49, 24, 19, 149, 199, 142, 195, 186, 244, 70, 51, 138, 0, 146, 148, 117, 60, 128, 128},
|
||||
{248, 105, 160, 49, 20, 101, 138, 116, 217, 204, 159, 122, 207, 44, 92, 214, 150, 195, 73, 77, 124, 52, 77, 120, 191, 236, 58, 221, 13, 145, 236, 78, 141, 28, 69, 184, 70, 248, 68, 1, 128, 160, 113, 224, 209, 75, 43, 147, 229, 199, 249, 116, 142, 105, 225, 254, 95, 23, 73, 138, 28, 58, 195, 206, 194, 159, 150, 175, 19, 215, 248, 164, 224, 112, 160, 117, 63, 152, 168, 212, 50, 139, 21, 99, 110, 70, 246, 111, 44, 180, 188, 134, 1, 0, 170, 23, 150, 124, 193, 69, 252, 209, 125, 29, 71, 16, 234}},
|
||||
Path: []byte{6, 1, 1, 4, 6, 5, 8, 10, 7, 4, 13, 9, 12, 12, 9, 15, 7, 10, 12, 15, 2, 12, 5, 12, 13, 6, 9, 6, 12, 3, 4, 9, 4, 13, 7, 12, 3, 4, 4, 13, 7, 8, 11, 15, 14, 12, 3, 10, 13, 13, 0, 13, 9, 1, 14, 12, 4, 14, 8, 13, 1, 12, 4, 5, 16},
|
||||
Storage: []statediff.StorageDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: updatedStorageKey,
|
||||
Value: updatedStorageValue,
|
||||
Proof: [][]byte{{248, 81, 128, 128, 160, 79, 197, 241, 58, 178, 249, 186, 12, 45, 168, 139, 1, 81, 171, 14, 124, 244, 216, 93, 8, 204, 164, 92, 205, 146, 60, 106, 183, 99, 35, 235, 40, 128, 160, 205, 69, 114, 89, 105, 97, 21, 35, 94, 100, 199, 130, 35, 52, 214, 33, 41, 226, 241, 96, 68, 37, 167, 218, 100, 148, 243, 95, 196, 91, 229, 24, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128},
|
||||
{226, 160, 48, 87, 135, 250, 18, 168, 35, 224, 242, 183, 99, 28, 196, 27, 59, 168, 130, 139, 51, 33, 202, 129, 17, 17, 250, 117, 205, 58, 163, 187, 90, 206, 3}},
|
||||
Path: []byte{4, 0, 5, 7, 8, 7, 15, 10, 1, 2, 10, 8, 2, 3, 14, 0, 15, 2, 11, 7, 6, 3, 1, 12, 12, 4, 1, 11, 3, 11, 10, 8, 8, 2, 8, 11, 3, 3, 2, 1, 12, 10, 8, 1, 1, 1, 1, 1, 15, 10, 7, 5, 12, 13, 3, 10, 10, 3, 11, 11, 5, 10, 12, 14, 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
arguments := test.startingArguments
|
||||
diff, err := builder.BuildStateDiff(arguments.oldStateRoot, arguments.newStateRoot, arguments.blockNumber, arguments.blockHash)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
receivedStateDiffRlp, err := rlp.EncodeToBytes(diff)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
expectedStateDiffRlp, err := rlp.EncodeToBytes(test.expected)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
sort.Slice(receivedStateDiffRlp, func(i, j int) bool { return receivedStateDiffRlp[i] < receivedStateDiffRlp[j] })
|
||||
sort.Slice(expectedStateDiffRlp, func(i, j int) bool { return expectedStateDiffRlp[i] < expectedStateDiffRlp[j] })
|
||||
if !bytes.Equal(receivedStateDiffRlp, expectedStateDiffRlp) {
|
||||
t.Logf("Test failed: %s", test.name)
|
||||
t.Errorf("actual state diff rlp: %+v\nexpected state diff rlp: %+v", receivedStateDiffRlp, expectedStateDiffRlp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
contract test {
|
||||
|
||||
uint256[100] data;
|
||||
|
||||
constructor() public {
|
||||
data = [1];
|
||||
}
|
||||
|
||||
function Put(uint256 addr, uint256 value) {
|
||||
data[addr] = value;
|
||||
}
|
||||
|
||||
function Get(uint256 addr) constant returns (uint256 value) {
|
||||
return data[addr];
|
||||
}
|
||||
}
|
||||
*/
|
25
statediff/config.go
Normal file
25
statediff/config.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 statediff
|
||||
|
||||
// Config is used to carry in parameters from CLI configuration
|
||||
type Config struct {
|
||||
PathsAndProofs bool
|
||||
IntermediateNodes bool
|
||||
StreamBlock bool
|
||||
WatchedAddresses []string
|
||||
}
|
61
statediff/doc.go
Normal file
61
statediff/doc.go
Normal file
@ -0,0 +1,61 @@
|
||||
// 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 statediff provides an auxiliary service that processes state diff objects from incoming chain events,
|
||||
relaying the objects to any rpc subscriptions.
|
||||
|
||||
This work is adapted from work by Charles Crain at https://github.com/jpmorganchase/quorum/blob/9b7fd9af8082795eeeb6863d9746f12b82dd5078/statediff/statediff.go
|
||||
|
||||
The service is spun up using the below CLI flags
|
||||
--statediff: boolean flag, turns on the service
|
||||
--statediff.streamblock: boolean flag, configures the service to associate and stream out the rest of the block data with the state diffs.
|
||||
--statediff.intermediatenodes: boolean flag, tells service to include intermediate (branch and extension) nodes; default (false) processes leaf nodes only.
|
||||
--statediff.pathsandproofs: boolean flag, tells service to generate paths and proofs for the diffed storage and state trie leaf nodes.
|
||||
--statediff.watchedaddresses: string slice flag, used to limit the state diffing process to the given addresses. Usage: --statediff.watchedaddresses=addr1 --statediff.watchedaddresses=addr2 --statediff.watchedaddresses=addr3
|
||||
|
||||
Even though statediffs will not be processed for addresses not included in the watchedaddresses slice, an empty statediff payload still may be sent over the subscription, resulting in a "sending state diff payload to subscription" log.
|
||||
This will be addresses in a future release by filtering out empty statediff payloads.
|
||||
|
||||
If you wish to use the websocket endpoint to subscribe to the statediff service, be sure to open up the Websocket RPC server with the `--ws` flag. The IPC-RPC server is turned on by default.
|
||||
|
||||
The statediffing services works only with `--syncmode="full", but -importantly- does not require garbage collection to be turned off (i.e. does not require an archival node).
|
||||
|
||||
e.g.
|
||||
|
||||
$ ./geth --statediff --statediff.streamblock --ws --syncmode "full"
|
||||
|
||||
This starts up the geth node in full sync mode, starts up the statediffing service, and opens up the websocket endpoint to subscribe to the service.
|
||||
Because the "streamblock" flag has been turned on, the service will strean out block data (headers, transactions, and receipts) along with the diffed state and storage leafs.
|
||||
|
||||
Rpc subscriptions to the service can be created using the rpc.Client.Subscribe() method,
|
||||
with the "statediff" namespace, a statediff.Payload channel, and the name of the statediff api's rpc method- "stream".
|
||||
|
||||
e.g.
|
||||
|
||||
cli, _ := rpc.Dial("ipcPathOrWsURL")
|
||||
stateDiffPayloadChan := make(chan statediff.Payload, 20000)
|
||||
rpcSub, err := cli.Subscribe(context.Background(), "statediff", stateDiffPayloadChan, "stream"})
|
||||
for {
|
||||
select {
|
||||
case stateDiffPayload := <- stateDiffPayloadChan:
|
||||
processPayload(stateDiffPayload)
|
||||
case err := <- rpcSub.Err():
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
package statediff
|
101
statediff/helpers.go
Normal file
101
statediff/helpers.go
Normal file
@ -0,0 +1,101 @@
|
||||
// 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/>.
|
||||
|
||||
// Contains a batch of utility type declarations used by the tests. As the node
|
||||
// operates on unique types, a lot of them are needed to check various features.
|
||||
|
||||
package statediff
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
func sortKeys(data AccountsMap) []string {
|
||||
var keys []string
|
||||
for key := range data {
|
||||
keys = append(keys, key.Hex())
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// bytesToNiblePath converts the byte representation of a path to its string representation
|
||||
func bytesToNiblePath(path []byte) string {
|
||||
if hasTerm(path) {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
nibblePath := ""
|
||||
for i, v := range common.ToHex(path) {
|
||||
if i%2 == 0 && i > 1 {
|
||||
continue
|
||||
}
|
||||
nibblePath = nibblePath + string(v)
|
||||
}
|
||||
|
||||
return nibblePath
|
||||
}
|
||||
|
||||
// findIntersection finds the set of strings from both arrays that are equivalent (same key as same index)
|
||||
// this is used to find which keys have been both "deleted" and "created" i.e. they were updated
|
||||
func findIntersection(a, b []string) []string {
|
||||
lenA := len(a)
|
||||
lenB := len(b)
|
||||
iOfA, iOfB := 0, 0
|
||||
updates := make([]string, 0)
|
||||
if iOfA >= lenA || iOfB >= lenB {
|
||||
return updates
|
||||
}
|
||||
for {
|
||||
switch strings.Compare(a[iOfA], b[iOfB]) {
|
||||
// -1 when a[iOfA] < b[iOfB]
|
||||
case -1:
|
||||
iOfA++
|
||||
if iOfA >= lenA {
|
||||
return updates
|
||||
}
|
||||
// 0 when a[iOfA] == b[iOfB]
|
||||
case 0:
|
||||
updates = append(updates, a[iOfA])
|
||||
iOfA++
|
||||
iOfB++
|
||||
if iOfA >= lenA || iOfB >= lenB {
|
||||
return updates
|
||||
}
|
||||
// 1 when a[iOfA] > b[iOfB]
|
||||
case 1:
|
||||
iOfB++
|
||||
if iOfB >= lenB {
|
||||
return updates
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// pathToStr converts the NodeIterator path to a string representation
|
||||
func pathToStr(it trie.NodeIterator) string {
|
||||
return bytesToNiblePath(it.Path())
|
||||
}
|
||||
|
||||
// hasTerm returns whether a hex key has the terminator flag.
|
||||
func hasTerm(s []byte) bool {
|
||||
return len(s) > 0 && s[len(s)-1] == 16
|
||||
}
|
271
statediff/service.go
Normal file
271
statediff/service.go
Normal file
@ -0,0 +1,271 @@
|
||||
// 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 statediff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
const chainEventChanSize = 20000
|
||||
|
||||
type blockChain interface {
|
||||
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
||||
GetBlockByHash(hash common.Hash) *types.Block
|
||||
AddToStateDiffProcessedCollection(hash common.Hash)
|
||||
GetReceiptsByHash(hash common.Hash) types.Receipts
|
||||
}
|
||||
|
||||
// IService is the state-diffing service interface
|
||||
type IService interface {
|
||||
// APIs(), Protocols(), Start() and Stop()
|
||||
node.Service
|
||||
// Main event loop for processing state diffs
|
||||
Loop(chainEventCh chan core.ChainEvent)
|
||||
// Method to subscribe to receive state diff processing output
|
||||
Subscribe(id rpc.ID, sub chan<- Payload, quitChan chan<- bool)
|
||||
// Method to unsubscribe from state diff processing
|
||||
Unsubscribe(id rpc.ID) error
|
||||
}
|
||||
|
||||
// Service is the underlying struct for the state diffing service
|
||||
type Service struct {
|
||||
// Used to sync access to the Subscriptions
|
||||
sync.Mutex
|
||||
// Used to build the state diff objects
|
||||
Builder Builder
|
||||
// Used to subscribe to chain events (blocks)
|
||||
BlockChain blockChain
|
||||
// Used to signal shutdown of the service
|
||||
QuitChan chan bool
|
||||
// A mapping of rpc.IDs to their subscription channels
|
||||
Subscriptions map[rpc.ID]Subscription
|
||||
// Cache the last block so that we can avoid having to lookup the next block's parent
|
||||
lastBlock *types.Block
|
||||
// Whether or not the block data is streamed alongside the state diff data in the subscription payload
|
||||
StreamBlock bool
|
||||
// Whether or not we have any subscribers; only if we do, do we processes state diffs
|
||||
subscribers int32
|
||||
}
|
||||
|
||||
// NewStateDiffService creates a new statediff.Service
|
||||
func NewStateDiffService(db ethdb.Database, blockChain *core.BlockChain, config Config) (*Service, error) {
|
||||
return &Service{
|
||||
Mutex: sync.Mutex{},
|
||||
BlockChain: blockChain,
|
||||
Builder: NewBuilder(db, blockChain, config),
|
||||
QuitChan: make(chan bool),
|
||||
Subscriptions: make(map[rpc.ID]Subscription),
|
||||
StreamBlock: config.StreamBlock,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Protocols exports the services p2p protocols, this service has none
|
||||
func (sds *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns the RPC descriptors the statediff.Service offers
|
||||
func (sds *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: APIName,
|
||||
Version: APIVersion,
|
||||
Service: NewPublicStateDiffAPI(sds),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Loop is the main processing method
|
||||
func (sds *Service) Loop(chainEventCh chan core.ChainEvent) {
|
||||
chainEventSub := sds.BlockChain.SubscribeChainEvent(chainEventCh)
|
||||
defer chainEventSub.Unsubscribe()
|
||||
errCh := chainEventSub.Err()
|
||||
for {
|
||||
select {
|
||||
//Notify chain event channel of events
|
||||
case chainEvent := <-chainEventCh:
|
||||
log.Debug("Event received from chainEventCh", "event", chainEvent)
|
||||
// if we don't have any subscribers, do not process a statediff
|
||||
if atomic.LoadInt32(&sds.subscribers) == 0 {
|
||||
log.Debug("Currently no subscribers to the statediffing service; processing is halted")
|
||||
continue
|
||||
}
|
||||
currentBlock := chainEvent.Block
|
||||
parentHash := currentBlock.ParentHash()
|
||||
var parentBlock *types.Block
|
||||
if sds.lastBlock != nil && bytes.Equal(sds.lastBlock.Hash().Bytes(), currentBlock.ParentHash().Bytes()) {
|
||||
parentBlock = sds.lastBlock
|
||||
} else {
|
||||
parentBlock = sds.BlockChain.GetBlockByHash(parentHash)
|
||||
}
|
||||
sds.lastBlock = currentBlock
|
||||
if parentBlock == nil {
|
||||
log.Error(fmt.Sprintf("Parent block is nil, skipping this block (%d)", currentBlock.Number()))
|
||||
continue
|
||||
}
|
||||
if err := sds.processStateDiff(currentBlock, parentBlock); err != nil {
|
||||
log.Error(fmt.Sprintf("Error building statediff for block %d; error: ", currentBlock.Number()) + err.Error())
|
||||
}
|
||||
case err := <-errCh:
|
||||
log.Warn("Error from chain event subscription, breaking loop", "error", err)
|
||||
sds.close()
|
||||
return
|
||||
case <-sds.QuitChan:
|
||||
log.Info("Quitting the statediffing process")
|
||||
sds.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processStateDiff method builds the state diff payload from the current and parent block before sending it to listening subscriptions
|
||||
func (sds *Service) processStateDiff(currentBlock, parentBlock *types.Block) error {
|
||||
stateDiff, err := sds.Builder.BuildStateDiff(parentBlock.Root(), currentBlock.Root(), currentBlock.Number(), currentBlock.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stateDiffRlp, err := rlp.EncodeToBytes(stateDiff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := Payload{
|
||||
StateDiffRlp: stateDiffRlp,
|
||||
}
|
||||
if sds.StreamBlock {
|
||||
blockBuff := new(bytes.Buffer)
|
||||
if err = currentBlock.EncodeRLP(blockBuff); err != nil {
|
||||
return err
|
||||
}
|
||||
payload.BlockRlp = blockBuff.Bytes()
|
||||
receiptBuff := new(bytes.Buffer)
|
||||
receipts := sds.BlockChain.GetReceiptsByHash(currentBlock.Hash())
|
||||
if err = rlp.Encode(receiptBuff, receipts); err != nil {
|
||||
return err
|
||||
}
|
||||
payload.ReceiptsRlp = receiptBuff.Bytes()
|
||||
}
|
||||
|
||||
sds.send(payload)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscribe is used by the API to subscribe to the service loop
|
||||
func (sds *Service) Subscribe(id rpc.ID, sub chan<- Payload, quitChan chan<- bool) {
|
||||
log.Info("Subscribing to the statediff service")
|
||||
if atomic.CompareAndSwapInt32(&sds.subscribers, 0, 1) {
|
||||
log.Info("State diffing subscription received; beginning statediff processing")
|
||||
}
|
||||
sds.Lock()
|
||||
sds.Subscriptions[id] = Subscription{
|
||||
PayloadChan: sub,
|
||||
QuitChan: quitChan,
|
||||
}
|
||||
sds.Unlock()
|
||||
}
|
||||
|
||||
// Unsubscribe is used to unsubscribe from the service loop
|
||||
func (sds *Service) Unsubscribe(id rpc.ID) error {
|
||||
log.Info("Unsubscribing from the statediff service")
|
||||
sds.Lock()
|
||||
_, ok := sds.Subscriptions[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot unsubscribe; subscription for id %s does not exist", id)
|
||||
}
|
||||
delete(sds.Subscriptions, id)
|
||||
if len(sds.Subscriptions) == 0 {
|
||||
if atomic.CompareAndSwapInt32(&sds.subscribers, 1, 0) {
|
||||
log.Info("No more subscriptions; halting statediff processing")
|
||||
}
|
||||
}
|
||||
sds.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start is used to begin the service
|
||||
func (sds *Service) Start(*p2p.Server) error {
|
||||
log.Info("Starting statediff service")
|
||||
|
||||
chainEventCh := make(chan core.ChainEvent, chainEventChanSize)
|
||||
go sds.Loop(chainEventCh)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is used to close down the service
|
||||
func (sds *Service) Stop() error {
|
||||
log.Info("Stopping statediff service")
|
||||
close(sds.QuitChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
// send is used to fan out and serve the payloads to all subscriptions
|
||||
func (sds *Service) send(payload Payload) {
|
||||
sds.Lock()
|
||||
for id, sub := range sds.Subscriptions {
|
||||
select {
|
||||
case sub.PayloadChan <- payload:
|
||||
log.Info(fmt.Sprintf("sending state diff payload to subscription %s", id))
|
||||
default:
|
||||
log.Info(fmt.Sprintf("unable to send payload to subscription %s; channel has no receiver", id))
|
||||
// in this case, try to close the bad subscription and remove it
|
||||
select {
|
||||
case sub.QuitChan <- true:
|
||||
log.Info(fmt.Sprintf("closing subscription %s", id))
|
||||
default:
|
||||
log.Info(fmt.Sprintf("unable to close subscription %s; channel has no receiver", id))
|
||||
}
|
||||
delete(sds.Subscriptions, id)
|
||||
}
|
||||
}
|
||||
// If after removing all bad subscriptions we have none left, halt processing
|
||||
if len(sds.Subscriptions) == 0 {
|
||||
if atomic.CompareAndSwapInt32(&sds.subscribers, 1, 0) {
|
||||
log.Info("No more subscriptions; halting statediff processing")
|
||||
}
|
||||
}
|
||||
sds.Unlock()
|
||||
}
|
||||
|
||||
// close is used to close all listening subscriptions
|
||||
func (sds *Service) close() {
|
||||
sds.Lock()
|
||||
for id, sub := range sds.Subscriptions {
|
||||
select {
|
||||
case sub.QuitChan <- true:
|
||||
log.Info(fmt.Sprintf("closing subscription %s", id))
|
||||
default:
|
||||
log.Info(fmt.Sprintf("unable to close subscription %s; channel has no receiver", id))
|
||||
}
|
||||
delete(sds.Subscriptions, id)
|
||||
}
|
||||
sds.Unlock()
|
||||
}
|
196
statediff/service_test.go
Normal file
196
statediff/service_test.go
Normal file
@ -0,0 +1,196 @@
|
||||
// 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 statediff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/statediff"
|
||||
"github.com/ethereum/go-ethereum/statediff/testhelpers/mocks"
|
||||
)
|
||||
|
||||
func TestServiceLoop(t *testing.T) {
|
||||
testErrorInChainEventLoop(t)
|
||||
testErrorInBlockLoop(t)
|
||||
}
|
||||
|
||||
var (
|
||||
eventsChannel = make(chan core.ChainEvent, 1)
|
||||
|
||||
parentRoot1 = common.HexToHash("0x01")
|
||||
parentRoot2 = common.HexToHash("0x02")
|
||||
parentHeader1 = types.Header{Number: big.NewInt(rand.Int63()), Root: parentRoot1}
|
||||
parentHeader2 = types.Header{Number: big.NewInt(rand.Int63()), Root: parentRoot2}
|
||||
|
||||
parentBlock1 = types.NewBlock(&parentHeader1, nil, nil, nil)
|
||||
parentBlock2 = types.NewBlock(&parentHeader2, nil, nil, nil)
|
||||
|
||||
parentHash1 = parentBlock1.Hash()
|
||||
parentHash2 = parentBlock2.Hash()
|
||||
|
||||
testRoot1 = common.HexToHash("0x03")
|
||||
testRoot2 = common.HexToHash("0x04")
|
||||
testRoot3 = common.HexToHash("0x04")
|
||||
header1 = types.Header{ParentHash: parentHash1, Root: testRoot1}
|
||||
header2 = types.Header{ParentHash: parentHash2, Root: testRoot2}
|
||||
header3 = types.Header{ParentHash: common.HexToHash("parent hash"), Root: testRoot3}
|
||||
|
||||
testBlock1 = types.NewBlock(&header1, nil, nil, nil)
|
||||
testBlock2 = types.NewBlock(&header2, nil, nil, nil)
|
||||
testBlock3 = types.NewBlock(&header3, nil, nil, nil)
|
||||
|
||||
receiptRoot1 = common.HexToHash("0x05")
|
||||
receiptRoot2 = common.HexToHash("0x06")
|
||||
receiptRoot3 = common.HexToHash("0x07")
|
||||
testReceipts1 = []*types.Receipt{types.NewReceipt(receiptRoot1.Bytes(), false, 1000), types.NewReceipt(receiptRoot2.Bytes(), false, 2000)}
|
||||
testReceipts2 = []*types.Receipt{types.NewReceipt(receiptRoot3.Bytes(), false, 3000)}
|
||||
|
||||
event1 = core.ChainEvent{Block: testBlock1}
|
||||
event2 = core.ChainEvent{Block: testBlock2}
|
||||
event3 = core.ChainEvent{Block: testBlock3}
|
||||
)
|
||||
|
||||
func testErrorInChainEventLoop(t *testing.T) {
|
||||
//the first chain event causes and error (in blockchain mock)
|
||||
builder := mocks.Builder{}
|
||||
blockChain := mocks.BlockChain{}
|
||||
service := statediff.Service{
|
||||
Mutex: sync.Mutex{},
|
||||
Builder: &builder,
|
||||
BlockChain: &blockChain,
|
||||
QuitChan: make(chan bool),
|
||||
Subscriptions: make(map[rpc.ID]statediff.Subscription),
|
||||
StreamBlock: true,
|
||||
}
|
||||
payloadChan := make(chan statediff.Payload, 2)
|
||||
quitChan := make(chan bool)
|
||||
service.Subscribe(rpc.NewID(), payloadChan, quitChan)
|
||||
testRoot2 = common.HexToHash("0xTestRoot2")
|
||||
blockMapping := make(map[common.Hash]*types.Block)
|
||||
blockMapping[parentBlock1.Hash()] = parentBlock1
|
||||
blockMapping[parentBlock2.Hash()] = parentBlock2
|
||||
blockChain.SetParentBlocksToReturn(blockMapping)
|
||||
blockChain.SetChainEvents([]core.ChainEvent{event1, event2, event3})
|
||||
blockChain.SetReceiptsForHash(testBlock1.Hash(), testReceipts1)
|
||||
blockChain.SetReceiptsForHash(testBlock2.Hash(), testReceipts2)
|
||||
|
||||
payloads := make([]statediff.Payload, 0, 2)
|
||||
wg := sync.WaitGroup{}
|
||||
go func() {
|
||||
wg.Add(1)
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case payload := <-payloadChan:
|
||||
payloads = append(payloads, payload)
|
||||
case <-quitChan:
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
service.Loop(eventsChannel)
|
||||
wg.Wait()
|
||||
if len(payloads) != 2 {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual number of payloads does not equal expected.\nactual: %+v\nexpected: 3", len(payloads))
|
||||
}
|
||||
|
||||
testReceipts1Rlp, err := rlp.EncodeToBytes(testReceipts1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
testReceipts2Rlp, err := rlp.EncodeToBytes(testReceipts2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
expectedReceiptsRlp := [][]byte{testReceipts1Rlp, testReceipts2Rlp, nil}
|
||||
for i, payload := range payloads {
|
||||
if !bytes.Equal(payload.ReceiptsRlp, expectedReceiptsRlp[i]) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual receipt rlp for payload %d does not equal expected.\nactual: %+v\nexpected: %+v", i, payload.ReceiptsRlp, expectedReceiptsRlp[i])
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(builder.BlockHash, testBlock2.Hash()) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual blockhash does not equal expected.\nactual:%+v\nexpected: %+v", builder.BlockHash, testBlock2.Hash())
|
||||
}
|
||||
if !bytes.Equal(builder.OldStateRoot.Bytes(), parentBlock2.Root().Bytes()) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual root does not equal expected.\nactual:%+v\nexpected: %+v", builder.OldStateRoot, parentBlock2.Root())
|
||||
}
|
||||
if !bytes.Equal(builder.NewStateRoot.Bytes(), testBlock2.Root().Bytes()) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual root does not equal expected.\nactual:%+v\nexpected: %+v", builder.NewStateRoot, testBlock2.Root())
|
||||
}
|
||||
//look up the parent block from its hash
|
||||
expectedHashes := []common.Hash{testBlock1.ParentHash(), testBlock2.ParentHash()}
|
||||
if !reflect.DeepEqual(blockChain.ParentHashesLookedUp, expectedHashes) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual parent hash does not equal expected.\nactual:%+v\nexpected: %+v", blockChain.ParentHashesLookedUp, expectedHashes)
|
||||
}
|
||||
}
|
||||
|
||||
func testErrorInBlockLoop(t *testing.T) {
|
||||
//second block's parent block can't be found
|
||||
builder := mocks.Builder{}
|
||||
blockChain := mocks.BlockChain{}
|
||||
service := statediff.Service{
|
||||
Builder: &builder,
|
||||
BlockChain: &blockChain,
|
||||
QuitChan: make(chan bool),
|
||||
Subscriptions: make(map[rpc.ID]statediff.Subscription),
|
||||
}
|
||||
payloadChan := make(chan statediff.Payload)
|
||||
quitChan := make(chan bool)
|
||||
service.Subscribe(rpc.NewID(), payloadChan, quitChan)
|
||||
blockMapping := make(map[common.Hash]*types.Block)
|
||||
blockMapping[parentBlock1.Hash()] = parentBlock1
|
||||
blockChain.SetParentBlocksToReturn(blockMapping)
|
||||
blockChain.SetChainEvents([]core.ChainEvent{event1, event2})
|
||||
// Need to have listeners on the channels or the subscription will be closed and the processing halted
|
||||
go func() {
|
||||
select {
|
||||
case <-payloadChan:
|
||||
case <-quitChan:
|
||||
}
|
||||
}()
|
||||
service.Loop(eventsChannel)
|
||||
|
||||
if !bytes.Equal(builder.BlockHash.Bytes(), testBlock1.Hash().Bytes()) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual does not equal expected.\nactual:%+v\nexpected: %+v", builder.BlockHash, testBlock1.Hash())
|
||||
}
|
||||
if !bytes.Equal(builder.OldStateRoot.Bytes(), parentBlock1.Root().Bytes()) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual does not equal expected.\nactual:%+v\nexpected: %+v", builder.OldStateRoot, parentBlock1.Root())
|
||||
}
|
||||
if !bytes.Equal(builder.NewStateRoot.Bytes(), testBlock1.Root().Bytes()) {
|
||||
t.Error("Test failure:", t.Name())
|
||||
t.Logf("Actual does not equal expected.\nactual:%+v\nexpected: %+v", builder.NewStateRoot, testBlock1.Root())
|
||||
}
|
||||
}
|
83
statediff/testhelpers/helpers.go
Normal file
83
statediff/testhelpers/helpers.go
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 testhelpers
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// MakeChain creates a chain of n blocks starting at and including parent.
|
||||
// the returned hash chain is ordered head->parent. In addition, every 3rd block
|
||||
// contains a transaction and every 5th an uncle to allow testing correct block
|
||||
// reassembly.
|
||||
func MakeChain(n int, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block, *core.BlockChain) {
|
||||
blocks, _ := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), Testdb, n, testChainGen)
|
||||
headers := make([]*types.Header, len(blocks))
|
||||
for i, block := range blocks {
|
||||
headers[i] = block.Header()
|
||||
}
|
||||
chain, _ := core.NewBlockChain(Testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
|
||||
|
||||
hashes := make([]common.Hash, n+1)
|
||||
hashes[len(hashes)-1] = parent.Hash()
|
||||
blockm := make(map[common.Hash]*types.Block, n+1)
|
||||
blockm[parent.Hash()] = parent
|
||||
for i, b := range blocks {
|
||||
hashes[len(hashes)-i-2] = b.Hash()
|
||||
blockm[b.Hash()] = b
|
||||
}
|
||||
return hashes, blockm, chain
|
||||
}
|
||||
|
||||
func testChainGen(i int, block *core.BlockGen) {
|
||||
signer := types.HomesteadSigner{}
|
||||
switch i {
|
||||
case 0:
|
||||
// In block 1, the test bank sends account #1 some ether.
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), Account1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, TestBankKey)
|
||||
block.AddTx(tx)
|
||||
case 1:
|
||||
// In block 2, the test bank sends some more ether to account #1.
|
||||
// account1Addr passes it on to account #2.
|
||||
// account1Addr creates a test contract.
|
||||
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), Account1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, TestBankKey)
|
||||
nonce := block.TxNonce(Account1Addr)
|
||||
tx2, _ := types.SignTx(types.NewTransaction(nonce, Account2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, Account1Key)
|
||||
nonce++
|
||||
tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), ContractCode), signer, Account1Key)
|
||||
ContractAddr = crypto.CreateAddress(Account1Addr, nonce) //0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592
|
||||
block.AddTx(tx1)
|
||||
block.AddTx(tx2)
|
||||
block.AddTx(tx3)
|
||||
case 2:
|
||||
// Block 3 is empty but was mined by account #2.
|
||||
block.SetCoinbase(Account2Addr)
|
||||
//get function: 60cd2685
|
||||
//put function: c16431b9
|
||||
data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003")
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), ContractAddr, big.NewInt(0), 100000, nil, data), signer, TestBankKey)
|
||||
block.AddTx(tx)
|
||||
}
|
||||
}
|
187
statediff/testhelpers/mocks/api.go
Normal file
187
statediff/testhelpers/mocks/api.go
Normal file
@ -0,0 +1,187 @@
|
||||
// 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 mocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/statediff"
|
||||
)
|
||||
|
||||
// MockStateDiffService is a mock state diff service
|
||||
type MockStateDiffService struct {
|
||||
sync.Mutex
|
||||
Builder statediff.Builder
|
||||
ReturnProtocol []p2p.Protocol
|
||||
ReturnAPIs []rpc.API
|
||||
BlockChan chan *types.Block
|
||||
ParentBlockChan chan *types.Block
|
||||
QuitChan chan bool
|
||||
Subscriptions map[rpc.ID]statediff.Subscription
|
||||
streamBlock bool
|
||||
}
|
||||
|
||||
// Protocols mock method
|
||||
func (sds *MockStateDiffService) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs mock method
|
||||
func (sds *MockStateDiffService) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: statediff.APIName,
|
||||
Version: statediff.APIVersion,
|
||||
Service: statediff.NewPublicStateDiffAPI(sds),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Loop mock method
|
||||
func (sds *MockStateDiffService) Loop(chan core.ChainEvent) {
|
||||
//loop through chain events until no more
|
||||
for {
|
||||
select {
|
||||
case block := <-sds.BlockChan:
|
||||
currentBlock := block
|
||||
parentBlock := <-sds.ParentBlockChan
|
||||
parentHash := parentBlock.Hash()
|
||||
if parentBlock == nil {
|
||||
log.Error("Parent block is nil, skipping this block",
|
||||
"parent block hash", parentHash.String(),
|
||||
"current block number", currentBlock.Number())
|
||||
continue
|
||||
}
|
||||
if err := sds.process(currentBlock, parentBlock); err != nil {
|
||||
println(err.Error())
|
||||
log.Error("Error building statediff", "block number", currentBlock.Number(), "error", err)
|
||||
}
|
||||
case <-sds.QuitChan:
|
||||
log.Debug("Quitting the statediff block channel")
|
||||
sds.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process method builds the state diff payload from the current and parent block and streams it to listening subscriptions
|
||||
func (sds *MockStateDiffService) process(currentBlock, parentBlock *types.Block) error {
|
||||
stateDiff, err := sds.Builder.BuildStateDiff(parentBlock.Root(), currentBlock.Root(), currentBlock.Number(), currentBlock.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stateDiffRlp, err := rlp.EncodeToBytes(stateDiff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := statediff.Payload{
|
||||
StateDiffRlp: stateDiffRlp,
|
||||
}
|
||||
if sds.streamBlock {
|
||||
rlpBuff := new(bytes.Buffer)
|
||||
if err = currentBlock.EncodeRLP(rlpBuff); err != nil {
|
||||
return err
|
||||
}
|
||||
payload.BlockRlp = rlpBuff.Bytes()
|
||||
}
|
||||
|
||||
// If we have any websocket subscription listening in, send the data to them
|
||||
sds.send(payload)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscribe mock method
|
||||
func (sds *MockStateDiffService) Subscribe(id rpc.ID, sub chan<- statediff.Payload, quitChan chan<- bool) {
|
||||
log.Info("Subscribing to the mock statediff service")
|
||||
sds.Lock()
|
||||
sds.Subscriptions[id] = statediff.Subscription{
|
||||
PayloadChan: sub,
|
||||
QuitChan: quitChan,
|
||||
}
|
||||
sds.Unlock()
|
||||
}
|
||||
|
||||
// Unsubscribe mock method
|
||||
func (sds *MockStateDiffService) Unsubscribe(id rpc.ID) error {
|
||||
log.Info("Unsubscribing from the mock statediff service")
|
||||
sds.Lock()
|
||||
_, ok := sds.Subscriptions[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot unsubscribe; subscription for id %s does not exist", id)
|
||||
}
|
||||
delete(sds.Subscriptions, id)
|
||||
sds.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sds *MockStateDiffService) send(payload statediff.Payload) {
|
||||
sds.Lock()
|
||||
for id, sub := range sds.Subscriptions {
|
||||
select {
|
||||
case sub.PayloadChan <- payload:
|
||||
log.Info("sending state diff payload to subscription %s", id)
|
||||
default:
|
||||
log.Info("unable to send payload to subscription %s; channel has no receiver", id)
|
||||
}
|
||||
}
|
||||
sds.Unlock()
|
||||
}
|
||||
|
||||
func (sds *MockStateDiffService) close() {
|
||||
sds.Lock()
|
||||
for id, sub := range sds.Subscriptions {
|
||||
select {
|
||||
case sub.QuitChan <- true:
|
||||
delete(sds.Subscriptions, id)
|
||||
log.Info("closing subscription %s", id)
|
||||
default:
|
||||
log.Info("unable to close subscription %s; channel has no receiver", id)
|
||||
}
|
||||
}
|
||||
sds.Unlock()
|
||||
}
|
||||
|
||||
// Start mock method
|
||||
func (sds *MockStateDiffService) Start(server *p2p.Server) error {
|
||||
log.Info("Starting mock statediff service")
|
||||
if sds.ParentBlockChan == nil || sds.BlockChan == nil {
|
||||
return errors.New("MockStateDiffingService needs to be configured with a MockParentBlockChan and MockBlockChan")
|
||||
}
|
||||
chainEventCh := make(chan core.ChainEvent, 10)
|
||||
go sds.Loop(chainEventCh)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop mock method
|
||||
func (sds *MockStateDiffService) Stop() error {
|
||||
log.Info("Stopping mock statediff service")
|
||||
close(sds.QuitChan)
|
||||
return nil
|
||||
}
|
142
statediff/testhelpers/mocks/api_test.go
Normal file
142
statediff/testhelpers/mocks/api_test.go
Normal file
@ -0,0 +1,142 @@
|
||||
// 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 mocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/statediff"
|
||||
"github.com/ethereum/go-ethereum/statediff/testhelpers"
|
||||
)
|
||||
|
||||
var block0, block1 *types.Block
|
||||
var burnLeafKey = testhelpers.AddressToLeafKey(common.HexToAddress("0x0"))
|
||||
var emptyAccountDiffEventualMap = make([]statediff.AccountDiff, 0)
|
||||
var account1, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: uint64(0),
|
||||
Balance: big.NewInt(10000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"),
|
||||
})
|
||||
var burnAccount1, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: uint64(0),
|
||||
Balance: big.NewInt(2000000000000000000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"),
|
||||
})
|
||||
var bankAccount1, _ = rlp.EncodeToBytes(state.Account{
|
||||
Nonce: uint64(1),
|
||||
Balance: big.NewInt(testhelpers.TestBankFunds.Int64() - 10000),
|
||||
CodeHash: common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes(),
|
||||
Root: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"),
|
||||
})
|
||||
|
||||
func TestAPI(t *testing.T) {
|
||||
_, blockMap, chain := testhelpers.MakeChain(3, testhelpers.Genesis)
|
||||
defer chain.Stop()
|
||||
block0Hash := common.HexToHash("0xd1721cfd0b29c36fd7a68f25c128e86413fb666a6e1d68e89b875bd299262661")
|
||||
block1Hash := common.HexToHash("0xbbe88de60ba33a3f18c0caa37d827bfb70252e19e40a07cd34041696c35ecb1a")
|
||||
block0 = blockMap[block0Hash]
|
||||
block1 = blockMap[block1Hash]
|
||||
blockChan := make(chan *types.Block)
|
||||
parentBlockChain := make(chan *types.Block)
|
||||
serviceQuitChan := make(chan bool)
|
||||
config := statediff.Config{
|
||||
PathsAndProofs: true,
|
||||
IntermediateNodes: false,
|
||||
}
|
||||
mockService := MockStateDiffService{
|
||||
Mutex: sync.Mutex{},
|
||||
Builder: statediff.NewBuilder(testhelpers.Testdb, chain, config),
|
||||
BlockChan: blockChan,
|
||||
ParentBlockChan: parentBlockChain,
|
||||
QuitChan: serviceQuitChan,
|
||||
Subscriptions: make(map[rpc.ID]statediff.Subscription),
|
||||
streamBlock: true,
|
||||
}
|
||||
mockService.Start(nil)
|
||||
id := rpc.NewID()
|
||||
payloadChan := make(chan statediff.Payload)
|
||||
quitChan := make(chan bool)
|
||||
mockService.Subscribe(id, payloadChan, quitChan)
|
||||
blockChan <- block1
|
||||
parentBlockChain <- block0
|
||||
expectedBlockRlp, _ := rlp.EncodeToBytes(block1)
|
||||
expectedStateDiff := statediff.StateDiff{
|
||||
BlockNumber: block1.Number(),
|
||||
BlockHash: block1.Hash(),
|
||||
CreatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: burnLeafKey.Bytes(),
|
||||
Value: burnAccount1,
|
||||
Proof: [][]byte{{248, 113, 160, 87, 118, 82, 182, 37, 183, 123, 219, 91, 247, 123, 196, 63, 49, 37, 202, 215, 70, 77, 103, 157, 21, 117, 86, 82, 119, 211, 97, 27, 128, 83, 231, 128, 128, 128, 128, 160, 254, 136, 159, 16, 229, 219, 143, 44, 43, 243, 85, 146, 129, 82, 161, 127, 110, 59, 185, 154, 146, 65, 172, 109, 132, 199, 126, 98, 100, 80, 156, 121, 128, 128, 128, 128, 128, 128, 128, 128, 160, 17, 219, 12, 218, 52, 168, 150, 218, 190, 182, 131, 155, 176, 106, 56, 244, 149, 20, 207, 164, 134, 67, 89, 132, 235, 1, 59, 125, 249, 238, 133, 197, 128, 128},
|
||||
{248, 113, 160, 51, 128, 199, 183, 174, 129, 165, 142, 185, 141, 156, 120, 222, 74, 31, 215, 253, 149, 53, 252, 149, 62, 210, 190, 96, 45, 170, 164, 23, 103, 49, 42, 184, 78, 248, 76, 128, 136, 27, 193, 109, 103, 78, 200, 0, 0, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{5, 3, 8, 0, 12, 7, 11, 7, 10, 14, 8, 1, 10, 5, 8, 14, 11, 9, 8, 13, 9, 12, 7, 8, 13, 14, 4, 10, 1, 15, 13, 7, 15, 13, 9, 5, 3, 5, 15, 12, 9, 5, 3, 14, 13, 2, 11, 14, 6, 0, 2, 13, 10, 10, 10, 4, 1, 7, 6, 7, 3, 1, 2, 10, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.Account1LeafKey.Bytes(),
|
||||
Value: account1,
|
||||
Proof: [][]byte{{248, 113, 160, 87, 118, 82, 182, 37, 183, 123, 219, 91, 247, 123, 196, 63, 49, 37, 202, 215, 70, 77, 103, 157, 21, 117, 86, 82, 119, 211, 97, 27, 128, 83, 231, 128, 128, 128, 128, 160, 254, 136, 159, 16, 229, 219, 143, 44, 43, 243, 85, 146, 129, 82, 161, 127, 110, 59, 185, 154, 146, 65, 172, 109, 132, 199, 126, 98, 100, 80, 156, 121, 128, 128, 128, 128, 128, 128, 128, 128, 160, 17, 219, 12, 218, 52, 168, 150, 218, 190, 182, 131, 155, 176, 106, 56, 244, 149, 20, 207, 164, 134, 67, 89, 132, 235, 1, 59, 125, 249, 238, 133, 197, 128, 128},
|
||||
{248, 107, 160, 57, 38, 219, 105, 170, 206, 213, 24, 233, 185, 240, 244, 52, 164, 115, 231, 23, 65, 9, 201, 67, 84, 139, 184, 242, 59, 228, 28, 167, 109, 154, 210, 184, 72, 248, 70, 128, 130, 39, 16, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{14, 9, 2, 6, 13, 11, 6, 9, 10, 10, 12, 14, 13, 5, 1, 8, 14, 9, 11, 9, 15, 0, 15, 4, 3, 4, 10, 4, 7, 3, 14, 7, 1, 7, 4, 1, 0, 9, 12, 9, 4, 3, 5, 4, 8, 11, 11, 8, 15, 2, 3, 11, 14, 4, 1, 12, 10, 7, 6, 13, 9, 10, 13, 2, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
DeletedAccounts: emptyAccountDiffEventualMap,
|
||||
UpdatedAccounts: []statediff.AccountDiff{
|
||||
{
|
||||
Leaf: true,
|
||||
Key: testhelpers.BankLeafKey.Bytes(),
|
||||
Value: bankAccount1,
|
||||
Proof: [][]byte{{248, 113, 160, 87, 118, 82, 182, 37, 183, 123, 219, 91, 247, 123, 196, 63, 49, 37, 202, 215, 70, 77, 103, 157, 21, 117, 86, 82, 119, 211, 97, 27, 128, 83, 231, 128, 128, 128, 128, 160, 254, 136, 159, 16, 229, 219, 143, 44, 43, 243, 85, 146, 129, 82, 161, 127, 110, 59, 185, 154, 146, 65, 172, 109, 132, 199, 126, 98, 100, 80, 156, 121, 128, 128, 128, 128, 128, 128, 128, 128, 160, 17, 219, 12, 218, 52, 168, 150, 218, 190, 182, 131, 155, 176, 106, 56, 244, 149, 20, 207, 164, 134, 67, 89, 132, 235, 1, 59, 125, 249, 238, 133, 197, 128, 128},
|
||||
{248, 109, 160, 48, 191, 73, 244, 64, 161, 205, 5, 39, 228, 208, 110, 39, 101, 101, 76, 15, 86, 69, 34, 87, 81, 109, 121, 58, 155, 141, 96, 77, 207, 223, 42, 184, 74, 248, 72, 1, 132, 5, 245, 185, 240, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112}},
|
||||
Path: []byte{0, 0, 11, 15, 4, 9, 15, 4, 4, 0, 10, 1, 12, 13, 0, 5, 2, 7, 14, 4, 13, 0, 6, 14, 2, 7, 6, 5, 6, 5, 4, 12, 0, 15, 5, 6, 4, 5, 2, 2, 5, 7, 5, 1, 6, 13, 7, 9, 3, 10, 9, 11, 8, 13, 6, 0, 4, 13, 12, 15, 13, 15, 2, 10, 16},
|
||||
Storage: []statediff.StorageDiff{},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedStateDiffBytes, err := rlp.EncodeToBytes(expectedStateDiff)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
sort.Slice(expectedStateDiffBytes, func(i, j int) bool { return expectedStateDiffBytes[i] < expectedStateDiffBytes[j] })
|
||||
|
||||
select {
|
||||
case payload := <-payloadChan:
|
||||
if !bytes.Equal(payload.BlockRlp, expectedBlockRlp) {
|
||||
t.Errorf("payload does not have expected block\r\actual block rlp: %v\r\nexpected block rlp: %v", payload.BlockRlp, expectedBlockRlp)
|
||||
}
|
||||
sort.Slice(payload.StateDiffRlp, func(i, j int) bool { return payload.StateDiffRlp[i] < payload.StateDiffRlp[j] })
|
||||
if !bytes.Equal(payload.StateDiffRlp, expectedStateDiffBytes) {
|
||||
t.Errorf("payload does not have expected state diff\r\actual state diff rlp: %v\r\nexpected state diff rlp: %v", payload.StateDiffRlp, expectedStateDiffBytes)
|
||||
}
|
||||
case <-quitChan:
|
||||
t.Errorf("channel quit before delivering payload")
|
||||
}
|
||||
}
|
102
statediff/testhelpers/mocks/blockchain.go
Normal file
102
statediff/testhelpers/mocks/blockchain.go
Normal file
@ -0,0 +1,102 @@
|
||||
// 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 mocks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
// BlockChain is a mock blockchain for testing
|
||||
type BlockChain struct {
|
||||
ParentHashesLookedUp []common.Hash
|
||||
parentBlocksToReturn map[common.Hash]*types.Block
|
||||
callCount int
|
||||
ChainEvents []core.ChainEvent
|
||||
Receipts map[common.Hash]types.Receipts
|
||||
}
|
||||
|
||||
// AddToStateDiffProcessedCollection mock method
|
||||
func (blockChain *BlockChain) AddToStateDiffProcessedCollection(hash common.Hash) {}
|
||||
|
||||
// SetParentBlocksToReturn mock method
|
||||
func (blockChain *BlockChain) SetParentBlocksToReturn(blocks map[common.Hash]*types.Block) {
|
||||
if blockChain.parentBlocksToReturn == nil {
|
||||
blockChain.parentBlocksToReturn = make(map[common.Hash]*types.Block)
|
||||
}
|
||||
blockChain.parentBlocksToReturn = blocks
|
||||
}
|
||||
|
||||
// GetBlockByHash mock method
|
||||
func (blockChain *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
|
||||
blockChain.ParentHashesLookedUp = append(blockChain.ParentHashesLookedUp, hash)
|
||||
|
||||
var parentBlock *types.Block
|
||||
if len(blockChain.parentBlocksToReturn) > 0 {
|
||||
parentBlock = blockChain.parentBlocksToReturn[hash]
|
||||
}
|
||||
|
||||
return parentBlock
|
||||
}
|
||||
|
||||
// SetChainEvents mock method
|
||||
func (blockChain *BlockChain) SetChainEvents(chainEvents []core.ChainEvent) {
|
||||
blockChain.ChainEvents = chainEvents
|
||||
}
|
||||
|
||||
// SubscribeChainEvent mock method
|
||||
func (blockChain *BlockChain) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||||
subErr := errors.New("Subscription Error")
|
||||
|
||||
var eventCounter int
|
||||
subscription := event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
for _, chainEvent := range blockChain.ChainEvents {
|
||||
if eventCounter > 1 {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
return subErr
|
||||
}
|
||||
select {
|
||||
case ch <- chainEvent:
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
eventCounter++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return subscription
|
||||
}
|
||||
|
||||
// SetReceiptsForHash mock method
|
||||
func (blockChain *BlockChain) SetReceiptsForHash(hash common.Hash, receipts types.Receipts) {
|
||||
if blockChain.Receipts == nil {
|
||||
blockChain.Receipts = make(map[common.Hash]types.Receipts)
|
||||
}
|
||||
blockChain.Receipts[hash] = receipts
|
||||
}
|
||||
|
||||
// GetReceiptsByHash mock method
|
||||
func (blockChain *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
||||
return blockChain.Receipts[hash]
|
||||
}
|
54
statediff/testhelpers/mocks/builder.go
Normal file
54
statediff/testhelpers/mocks/builder.go
Normal file
@ -0,0 +1,54 @@
|
||||
// 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 mocks
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/statediff"
|
||||
)
|
||||
|
||||
// Builder is a mock state diff builder
|
||||
type Builder struct {
|
||||
OldStateRoot common.Hash
|
||||
NewStateRoot common.Hash
|
||||
BlockNumber *big.Int
|
||||
BlockHash common.Hash
|
||||
stateDiff statediff.StateDiff
|
||||
builderError error
|
||||
}
|
||||
|
||||
// BuildStateDiff mock method
|
||||
func (builder *Builder) BuildStateDiff(oldStateRoot, newStateRoot common.Hash, blockNumber *big.Int, blockHash common.Hash) (statediff.StateDiff, error) {
|
||||
builder.OldStateRoot = oldStateRoot
|
||||
builder.NewStateRoot = newStateRoot
|
||||
builder.BlockNumber = blockNumber
|
||||
builder.BlockHash = blockHash
|
||||
|
||||
return builder.stateDiff, builder.builderError
|
||||
}
|
||||
|
||||
// SetStateDiffToBuild mock method
|
||||
func (builder *Builder) SetStateDiffToBuild(stateDiff statediff.StateDiff) {
|
||||
builder.stateDiff = stateDiff
|
||||
}
|
||||
|
||||
// SetBuilderError mock method
|
||||
func (builder *Builder) SetBuilderError(err error) {
|
||||
builder.builderError = err
|
||||
}
|
114
statediff/testhelpers/test_data.go
Normal file
114
statediff/testhelpers/test_data.go
Normal file
@ -0,0 +1,114 @@
|
||||
// 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 testhelpers
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"math/rand"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/statediff"
|
||||
)
|
||||
|
||||
// AddressToLeafKey hashes an returns an address
|
||||
func AddressToLeafKey(address common.Address) common.Hash {
|
||||
return common.BytesToHash(crypto.Keccak256(address[:]))
|
||||
}
|
||||
|
||||
// Test variables
|
||||
var (
|
||||
BlockNumber = big.NewInt(rand.Int63())
|
||||
BlockHash = "0xfa40fbe2d98d98b3363a778d52f2bcd29d6790b9b3f3cab2b167fd12d3550f73"
|
||||
CodeHash = common.Hex2Bytes("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
|
||||
NewNonceValue = rand.Uint64()
|
||||
NewBalanceValue = rand.Int63()
|
||||
ContractRoot = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
StoragePath = common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").Bytes()
|
||||
StorageKey = common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001").Bytes()
|
||||
StorageValue = common.Hex2Bytes("0x03")
|
||||
storage = []statediff.StorageDiff{{
|
||||
Key: StorageKey,
|
||||
Value: StorageValue,
|
||||
Path: StoragePath,
|
||||
Proof: [][]byte{},
|
||||
}}
|
||||
emptyStorage = make([]statediff.StorageDiff, 0)
|
||||
address = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592")
|
||||
ContractLeafKey = AddressToLeafKey(address)
|
||||
anotherAddress = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476593")
|
||||
AnotherContractLeafKey = AddressToLeafKey(anotherAddress)
|
||||
testAccount = state.Account{
|
||||
Nonce: NewNonceValue,
|
||||
Balance: big.NewInt(NewBalanceValue),
|
||||
Root: ContractRoot,
|
||||
CodeHash: CodeHash,
|
||||
}
|
||||
valueBytes, _ = rlp.EncodeToBytes(testAccount)
|
||||
CreatedAccountDiffs = []statediff.AccountDiff{
|
||||
{
|
||||
Key: ContractLeafKey.Bytes(),
|
||||
Value: valueBytes,
|
||||
Storage: storage,
|
||||
},
|
||||
{
|
||||
Key: AnotherContractLeafKey.Bytes(),
|
||||
Value: valueBytes,
|
||||
Storage: emptyStorage,
|
||||
},
|
||||
}
|
||||
|
||||
UpdatedAccountDiffs = []statediff.AccountDiff{{
|
||||
Key: ContractLeafKey.Bytes(),
|
||||
Value: valueBytes,
|
||||
Storage: storage,
|
||||
}}
|
||||
|
||||
DeletedAccountDiffs = []statediff.AccountDiff{{
|
||||
Key: ContractLeafKey.Bytes(),
|
||||
Value: valueBytes,
|
||||
Storage: storage,
|
||||
}}
|
||||
|
||||
TestStateDiff = statediff.StateDiff{
|
||||
BlockNumber: BlockNumber,
|
||||
BlockHash: common.HexToHash(BlockHash),
|
||||
CreatedAccounts: CreatedAccountDiffs,
|
||||
DeletedAccounts: DeletedAccountDiffs,
|
||||
UpdatedAccounts: UpdatedAccountDiffs,
|
||||
}
|
||||
Testdb = rawdb.NewMemoryDatabase()
|
||||
|
||||
TestBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
TestBankAddress = crypto.PubkeyToAddress(TestBankKey.PublicKey) //0x71562b71999873DB5b286dF957af199Ec94617F7
|
||||
BankLeafKey = AddressToLeafKey(TestBankAddress)
|
||||
TestBankFunds = big.NewInt(100000000)
|
||||
Genesis = core.GenesisBlockForTesting(Testdb, TestBankAddress, TestBankFunds)
|
||||
|
||||
Account1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
Account2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
Account1Addr = crypto.PubkeyToAddress(Account1Key.PublicKey) //0x703c4b2bD70c169f5717101CaeE543299Fc946C7
|
||||
Account2Addr = crypto.PubkeyToAddress(Account2Key.PublicKey) //0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e
|
||||
Account1LeafKey = AddressToLeafKey(Account1Addr)
|
||||
Account2LeafKey = AddressToLeafKey(Account2Addr)
|
||||
ContractCode = common.Hex2Bytes("608060405234801561001057600080fd5b50602060405190810160405280600160ff16815250600090600161003592919061003b565b506100a5565b826064810192821561006f579160200282015b8281111561006e578251829060ff1690559160200191906001019061004e565b5b50905061007c9190610080565b5090565b6100a291905b8082111561009e576000816000905550600101610086565b5090565b90565b610124806100b46000396000f3fe6080604052348015600f57600080fd5b5060043610604f576000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146054578063c16431b9146093575b600080fd5b607d60048036036020811015606857600080fd5b810190808035906020019092919050505060c8565b6040518082815260200191505060405180910390f35b60c66004803603604081101560a757600080fd5b81019080803590602001909291908035906020019092919050505060e0565b005b6000808260648110151560d757fe5b01549050919050565b8060008360648110151560ef57fe5b0181905550505056fea165627a7a7230582064e918c3140a117bf3aa65865a9b9e83fae21ad1720506e7933b2a9f54bb40260029")
|
||||
ContractAddr common.Address
|
||||
)
|
107
statediff/types.go
Normal file
107
statediff/types.go
Normal file
@ -0,0 +1,107 @@
|
||||
// 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/>.
|
||||
|
||||
// Contains a batch of utility type declarations used by the tests. As the node
|
||||
// operates on unique types, a lot of them are needed to check various features.
|
||||
|
||||
package statediff
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// Subscription struct holds our subscription channels
|
||||
type Subscription struct {
|
||||
PayloadChan chan<- Payload
|
||||
QuitChan chan<- bool
|
||||
}
|
||||
|
||||
// Payload packages the data to send to statediff subscriptions
|
||||
type Payload struct {
|
||||
BlockRlp []byte `json:"blockRlp"`
|
||||
ReceiptsRlp []byte `json:"receiptsRlp"`
|
||||
StateDiffRlp []byte `json:"stateDiff" gencodec:"required"`
|
||||
|
||||
encoded []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (sd *Payload) ensureEncoded() {
|
||||
if sd.encoded == nil && sd.err == nil {
|
||||
sd.encoded, sd.err = json.Marshal(sd)
|
||||
}
|
||||
}
|
||||
|
||||
// Length to implement Encoder interface for Payload
|
||||
func (sd *Payload) Length() int {
|
||||
sd.ensureEncoded()
|
||||
return len(sd.encoded)
|
||||
}
|
||||
|
||||
// Encode to implement Encoder interface for Payload
|
||||
func (sd *Payload) Encode() ([]byte, error) {
|
||||
sd.ensureEncoded()
|
||||
return sd.encoded, sd.err
|
||||
}
|
||||
|
||||
// StateDiff is the final output structure from the builder
|
||||
type StateDiff struct {
|
||||
BlockNumber *big.Int `json:"blockNumber" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
CreatedAccounts []AccountDiff `json:"createdAccounts" gencodec:"required"`
|
||||
DeletedAccounts []AccountDiff `json:"deletedAccounts" gencodec:"required"`
|
||||
UpdatedAccounts []AccountDiff `json:"updatedAccounts" gencodec:"required"`
|
||||
|
||||
encoded []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// AccountDiff holds the data for a single state diff node
|
||||
type AccountDiff struct {
|
||||
Leaf bool `json:"leaf" gencodec:"required"`
|
||||
Key []byte `json:"key" gencodec:"required"`
|
||||
Value []byte `json:"value" gencodec:"required"`
|
||||
Proof [][]byte `json:"proof" gencodec:"required"`
|
||||
Path []byte `json:"path" gencodec:"required"`
|
||||
Storage []StorageDiff `json:"storage" gencodec:"required"`
|
||||
}
|
||||
|
||||
// StorageDiff holds the data for a single storage diff node
|
||||
type StorageDiff struct {
|
||||
Leaf bool `json:"leaf" gencodec:"required"`
|
||||
Key []byte `json:"key" gencodec:"required"`
|
||||
Value []byte `json:"value" gencodec:"required"`
|
||||
Proof [][]byte `json:"proof" gencodec:"required"`
|
||||
Path []byte `json:"path" gencodec:"required"`
|
||||
}
|
||||
|
||||
// AccountsMap is a mapping of keccak256(address) => accountWrapper
|
||||
type AccountsMap map[common.Hash]accountWrapper
|
||||
|
||||
// AccountWrapper is used to temporary associate the unpacked account with its raw values
|
||||
type accountWrapper struct {
|
||||
Account *state.Account
|
||||
Leaf bool
|
||||
RawKey []byte
|
||||
RawValue []byte
|
||||
Proof [][]byte
|
||||
Path []byte
|
||||
}
|
@ -0,0 +1 @@
|
||||
ソス
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user