Merge remote-tracking branch 'upstream/master' into rlp-review

This commit is contained in:
David Theodore 2024-06-11 15:03:52 -05:00
commit 9f6e2517ce
53 changed files with 686 additions and 499 deletions

View file

@ -16,6 +16,7 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: 1.21.4
cache: false
- name: Run tests
run: go test -short ./...
env:

View file

@ -5,48 +5,55 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# version:golang 1.22.3
# version:golang 1.22.4
# https://go.dev/dl/
80648ef34f903193d72a59c0dff019f5f98ae0c9aa13ade0b0ecbff991a76f68 go1.22.3.src.tar.gz
adc9f5fee89cd53d907eb542d3b269d9d8a08a66bf1ab42175450ffbb58733fb go1.22.3.aix-ppc64.tar.gz
610e48c1df4d2f852de8bc2e7fd2dc1521aac216f0c0026625db12f67f192024 go1.22.3.darwin-amd64.tar.gz
02abeab3f4b8981232237ebd88f0a9bad933bc9621791cd7720a9ca29eacbe9d go1.22.3.darwin-arm64.tar.gz
a5b3d54905f17af2ceaf7fcfe92edee67a5bd4eccd962dd89df719ace3e0894d go1.22.3.dragonfly-amd64.tar.gz
b9989ca87695ae93bacde6f3aa7b13cde5f3825515eb9ed9bbef014273739889 go1.22.3.freebsd-386.tar.gz
7483961fae29d7d768afd5c9c0f229354ca3263ab7119c20bc182761f87cbc74 go1.22.3.freebsd-amd64.tar.gz
edf1f0b8ecf68b14faeedb4f5d868a58c4777a0282bd85e5115c39c010cd0130 go1.22.3.freebsd-arm.tar.gz
572eb70e5e835fbff7d53ebf473f611d7eb458c428f8dbd98a49196883c3309e go1.22.3.freebsd-arm64.tar.gz
ef94eb2b74402e436dce970584222c4e454eb3093908591149bd2ded6862b8af go1.22.3.freebsd-riscv64.tar.gz
3c3f498c68334cbd11f72aadfb6bcb507eb8436cebc50f437a0523cd4c5e03d1 go1.22.3.illumos-amd64.tar.gz
fefba30bb0d3dd1909823ee38c9f1930c3dc5337a2ac4701c2277a329a386b57 go1.22.3.linux-386.tar.gz
8920ea521bad8f6b7bc377b4824982e011c19af27df88a815e3586ea895f1b36 go1.22.3.linux-amd64.tar.gz
6c33e52a5b26e7aa021b94475587fce80043a727a54ceb0eee2f9fc160646434 go1.22.3.linux-arm64.tar.gz
f2bacad20cd2b96f23a86d4826525d42b229fd431cc6d0dec61ff3bc448ef46e go1.22.3.linux-armv6l.tar.gz
41e9328340544893482b2928ae18a9a88ba18b2fdd29ac77f4d33cf1815bbdc2 go1.22.3.linux-loong64.tar.gz
cf4d5faff52e642492729eaf396968f43af179518be769075b90bc1bf650abf6 go1.22.3.linux-mips.tar.gz
3bd009fe2e3d2bfd52433a11cb210d1dfa50b11b4c347a293951efd9e36de945 go1.22.3.linux-mips64.tar.gz
5913b82a042188ef698f7f2dfd0cd0c71f0508a4739de9e41fceff3f4dc769b4 go1.22.3.linux-mips64le.tar.gz
441afebca555be5313867b4577f237c7b5c0fff4386e22e47875b9f805abbec5 go1.22.3.linux-mipsle.tar.gz
f3b53190a76f4a35283501ba6d94cbb72093be0c62ff735c6f9e586a1c983381 go1.22.3.linux-ppc64.tar.gz
04b7b05283de30dd2da20bf3114b2e22cc727938aed3148babaf35cc951051ac go1.22.3.linux-ppc64le.tar.gz
d4992d4a85696e3f1de06cefbfc2fd840c9c6695d77a0f35cfdc4e28b2121c20 go1.22.3.linux-riscv64.tar.gz
2aba796417a69be5f3ed489076bac79c1c02b36e29422712f9f3bf51da9cf2d4 go1.22.3.linux-s390x.tar.gz
d6e6113542dd9f23db899e177fe23772bac114a5ea5e8ee436b9da68628335a8 go1.22.3.netbsd-386.tar.gz
c33cee3075bd18ceefddd75bafa8efb51fbdc17b5ee74275122e7a927a237a4c go1.22.3.netbsd-amd64.tar.gz
1ab251df3c85f3b391a09565ca52fb6e1306527d72852d553e9ab74eabb4ecf8 go1.22.3.netbsd-arm.tar.gz
1d194fe53f5d82f9a612f848950d8af8cab7cb40ccc03f10c4eb1c9808ff1a0c go1.22.3.netbsd-arm64.tar.gz
91d6601727f08506e938640885d3ded784925045e3a4444fd9b4b936efe1b1e0 go1.22.3.openbsd-386.tar.gz
09d0c91ae35a4eea92615426992062ca236cc2f66444fb0b0a24cd3b13bd5297 go1.22.3.openbsd-amd64.tar.gz
338da30cc2c97b9458e0b4caa2509f67bba55d3de16fb7d31775baca82d2e3dc go1.22.3.openbsd-arm.tar.gz
53eadfabd2b7dd09a64941421afee2a2888e2a4f94f353b27919b1dad1171a21 go1.22.3.openbsd-arm64.tar.gz
8a1a2842ae8dcf2374bb05dff58074b368bb698dc9c211c794c1ff119cd9fdc7 go1.22.3.plan9-386.tar.gz
f9816d3dd9e730cad55085ea08c1f0c925720728f9c945fff59cd24d2ac2db7b go1.22.3.plan9-amd64.tar.gz
f4d3d7b17c9e1b1635fcb287b5b5ab5b60acc9db3ba6a27f2b2f5d6537a2ef95 go1.22.3.plan9-arm.tar.gz
46b7999ee94d91b21ad6940b5a3131ff6fe53ef97be9a34e582e2a3ad7263e95 go1.22.3.solaris-amd64.tar.gz
f60f63b8a0885e0d924f39fd284aee5438fe87d8c3d8545a312adf43e0d9edac go1.22.3.windows-386.zip
cab2af6951a6e2115824263f6df13ff069c47270f5788714fa1d776f7f60cb39 go1.22.3.windows-amd64.zip
40b37f4b068fc759f3a0dd61176a0f7570a4ba48bed8561c31d3967a3583981a go1.22.3.windows-arm.zip
59b76ee22b9b1c3afbf7f50e3cb4edb954d6c0d25e5e029ab5483a6804d61e71 go1.22.3.windows-arm64.zip
fed720678e728a7ca30ba8d1ded1caafe27d16028fab0232b8ba8e22008fb784 go1.22.4.src.tar.gz
b9647fa9fc83a0cc5d4f092a19eaeaecf45f063a5aa7d4962fde65aeb7ae6ce1 go1.22.4.aix-ppc64.tar.gz
7788f40f3a46f201df1dc46ca640403eb535d5513fc33449164a90dbd229b761 go1.22.4.darwin-amd64.pkg
c95967f50aa4ace34af0c236cbdb49a9a3e80ee2ad09d85775cb4462a5c19ed3 go1.22.4.darwin-amd64.tar.gz
4036c88faf57a6b096916f1827edcdbf5290a47cc5f59956e88cdd9b1b71088c go1.22.4.darwin-arm64.pkg
242b78dc4c8f3d5435d28a0d2cec9b4c1aa999b601fb8aa59fb4e5a1364bf827 go1.22.4.darwin-arm64.tar.gz
f2fbb51af4719d3616efb482d6ed2b96579b474156f85a7ddc6f126764feec4b go1.22.4.dragonfly-amd64.tar.gz
7c54884bb9f274884651d41e61d1bc12738863ad1497e97ea19ad0e9aa6bf7b5 go1.22.4.freebsd-386.tar.gz
88d44500e1701dd35797619774d6dd51bf60f45a8338b0a82ddc018e4e63fb78 go1.22.4.freebsd-amd64.tar.gz
3d9efe47db142a22679aba46b1772e3900b0d87ae13bd2b3bc80dbf2ac0b2cd6 go1.22.4.freebsd-arm.tar.gz
726dc093cf020277be45debf03c3b02b43c2efb3e2a5d4fba8f52579d65327dc go1.22.4.freebsd-arm64.tar.gz
5f6b67e5e32f1d6ccb2d4dcb44934a5e2e870a877ba7443d86ec43cfc28afa71 go1.22.4.freebsd-riscv64.tar.gz
d56ecc2f85b6418a21ef83879594d0c42ab4f65391a676bb12254870e6690d63 go1.22.4.illumos-amd64.tar.gz
47a2a8d249a91eb8605c33bceec63aedda0441a43eac47b4721e3975ff916cec go1.22.4.linux-386.tar.gz
ba79d4526102575196273416239cca418a651e049c2b099f3159db85e7bade7d go1.22.4.linux-amd64.tar.gz
a8e177c354d2e4a1b61020aca3562e27ea3e8f8247eca3170e3fa1e0c2f9e771 go1.22.4.linux-arm64.tar.gz
e2b143fbacbc9cbd448e9ef41ac3981f0488ce849af1cf37e2341d09670661de go1.22.4.linux-armv6l.tar.gz
e2ff9436e4b34bf6926b06d97916e26d67a909a2effec17967245900f0816f1d go1.22.4.linux-loong64.tar.gz
73f0dcc60458c4770593b05a7bc01cc0d31fc98f948c0c2334812c7a1f2fc3f1 go1.22.4.linux-mips.tar.gz
417af97fc2630a647052375768be4c38adcc5af946352ea5b28613ea81ca5d45 go1.22.4.linux-mips64.tar.gz
7486e2d7dd8c98eb44df815ace35a7fe7f30b7c02326e3741bd934077508139b go1.22.4.linux-mips64le.tar.gz
69479c8aad301e459a8365b40cad1074a0dbba5defb9291669f94809c4c4be6e go1.22.4.linux-mipsle.tar.gz
dd238847e65bc3e2745caca475a5db6522a2fcf85cf6c38fc36a06642b19efd7 go1.22.4.linux-ppc64.tar.gz
a3e5834657ef92523f570f798fed42f1f87bc18222a16815ec76b84169649ec4 go1.22.4.linux-ppc64le.tar.gz
56a827ff7dc6245bcd7a1e9288dffaa1d8b0fd7468562264c1523daf3b4f1b4a go1.22.4.linux-riscv64.tar.gz
7590c3e278e2dc6040aae0a39da3ca1eb2e3921673a7304cc34d588c45889eec go1.22.4.linux-s390x.tar.gz
ddd2eebe34471a2502de6c5dad04ab27c9fc80cbde7a9ad5b3c66ecec4504e1d go1.22.4.netbsd-386.tar.gz
33af79f6f935f6fbacc5d23876450b3567b79348fc065beef8e64081127dd234 go1.22.4.netbsd-amd64.tar.gz
fa3550ebd5375a70b3bcd342b5a71f4bd271dcbbfaf4eabefa2144ab5d8924b6 go1.22.4.netbsd-arm.tar.gz
c9a2971dec9f6d320c6f2b049b2353c6d0a2d35e87b8a4b2d78a2f0d62545f8e go1.22.4.netbsd-arm64.tar.gz
d21af022331bfdc2b5b161d616c3a1a4573d33cf7a30416ee509a8f3641deb47 go1.22.4.openbsd-386.tar.gz
72c0094c43f7e5722ec49c2a3e9dfa7a1123ac43a5f3a63eecf3e3795d3ff0ae go1.22.4.openbsd-amd64.tar.gz
1096831ea3c5ea3ca57d14251d9eda3786889531eb40d7d6775dcaa324d4b065 go1.22.4.openbsd-arm.tar.gz
a7ab8d4e0b02bf06ed144ba42c61c0e93ee00f2b433415dfd4ad4b6e79f31650 go1.22.4.openbsd-arm64.tar.gz
9716327c8a628358798898dc5148c49dbbeb5196bf2cbf088e550721a6e4f60b go1.22.4.openbsd-ppc64.tar.gz
a8dd4503c95c32a502a616ab78870a19889c9325fe9bd31eb16dd69346e4bfa8 go1.22.4.plan9-386.tar.gz
5423a25808d76fe5aca8607a2e5ac5673abf45446b168cb5e9d8519ee9fe39a1 go1.22.4.plan9-amd64.tar.gz
6af939ad583f5c85c09c53728ab7d38c3cc2b39167562d6c18a07c5c6608b370 go1.22.4.plan9-arm.tar.gz
e8cabe69c03085725afdb32a6f9998191a3e55a747b270d835fd05000d56abba go1.22.4.solaris-amd64.tar.gz
5c6446e2ea80bc6a971d2b34446f16e6517e638b0ff8d3ea229228d1931790b0 go1.22.4.windows-386.msi
aca4e2c37278a10f1c70dd0df142f7d66b50334fcee48978d409202d308d6d25 go1.22.4.windows-386.zip
3c21105d7b584759b6e266383b777caf6e87142d304a10b539dbc66ab482bb5f go1.22.4.windows-amd64.msi
26321c4d945a0035d8a5bc4a1965b0df401ff8ceac66ce2daadabf9030419a98 go1.22.4.windows-amd64.zip
c4303f02b864304eb83dd1db0b4ebf9d2ec9d216e7ef44a7657b166a52889c7f go1.22.4.windows-arm.msi
5fcd0671a49cecf39b41021621ee1b6e7aa1370f37122b72e80d4fd4185833b6 go1.22.4.windows-arm.zip
553cc6c460f4e3eb4fad5b897c0bb22cd8bbeb20929f0e3eeb939420320292ce go1.22.4.windows-arm64.msi
8a2daa9ea28cbdafddc6171aefed384f4e5b6e714fb52116fe9ed25a132f37ed go1.22.4.windows-arm64.zip
# version:golangci 1.59.0
# https://github.com/golangci/golangci-lint/releases/

View file

@ -225,8 +225,8 @@ Response
- `value` [number:optional]: amount of Wei to send with the transaction
- `data` [data:optional]: input data
- `nonce` [number]: account nonce
1. method signature [string:optional]
- The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected.
2. method signature [string:optional]
- The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected.
#### Result

View file

@ -53,7 +53,8 @@ func (s *Suite) dial() (*Conn, error) {
// dialAs attempts to dial a given node and perform a handshake using the given
// private key.
func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
tcpEndpoint, _ := s.Dest.TCPEndpoint()
fd, err := net.Dial("tcp", tcpEndpoint.String())
if err != nil {
return nil, err
}

View file

@ -53,10 +53,12 @@ func newTestEnv(remote string, listen1, listen2 string) *testenv {
if err != nil {
panic(err)
}
if node.IP() == nil || node.UDP() == 0 {
if !node.IPAddr().IsValid() || node.UDP() == 0 {
var ip net.IP
var tcpPort, udpPort int
if ip = node.IP(); ip == nil {
if node.IPAddr().IsValid() {
ip = node.IPAddr().AsSlice()
} else {
ip = net.ParseIP("127.0.0.1")
}
if tcpPort = node.TCP(); tcpPort == 0 {

View file

@ -19,7 +19,7 @@ package main
import (
"errors"
"fmt"
"net"
"net/netip"
"sort"
"strconv"
"strings"
@ -205,11 +205,11 @@ func trueFilter(args []string) (nodeFilter, error) {
}
func ipFilter(args []string) (nodeFilter, error) {
_, cidr, err := net.ParseCIDR(args[0])
prefix, err := netip.ParsePrefix(args[0])
if err != nil {
return nil, err
}
f := func(n nodeJSON) bool { return cidr.Contains(n.N.IP()) }
f := func(n nodeJSON) bool { return prefix.Contains(n.N.IPAddr()) }
return f, nil
}

View file

@ -77,7 +77,11 @@ var (
func rlpxPing(ctx *cli.Context) error {
n := getNodeArg(ctx)
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", n.IP(), n.TCP()))
tcpEndpoint, ok := n.TCPEndpoint()
if !ok {
return fmt.Errorf("node has no TCP endpoint")
}
fd, err := net.Dial("tcp", tcpEndpoint.String())
if err != nil {
return err
}
@ -105,7 +109,7 @@ func rlpxPing(ctx *cli.Context) error {
}
return fmt.Errorf("received disconnect message: %v", msg[0])
default:
return fmt.Errorf("invalid message code %d, expected handshake (code zero)", code)
return fmt.Errorf("invalid message code %d, expected handshake (code zero) or disconnect (code one)", code)
}
return nil
}

View file

@ -14,15 +14,15 @@ The `evm t8n` tool is a stateless state transition utility. It is a utility
which can
1. Take a prestate, including
- Accounts,
- Block context information,
- Previous blockshashes (*optional)
- Accounts,
- Block context information,
- Previous blockshashes (*optional)
2. Apply a set of transactions,
3. Apply a mining-reward (*optional),
4. And generate a post-state, including
- State root, transaction root, receipt root,
- Information about rejected transactions,
- Optionally: a full or partial post-state dump
- State root, transaction root, receipt root,
- Information about rejected transactions,
- Optionally: a full or partial post-state dump
### Specification

View file

@ -181,7 +181,7 @@ func Transition(ctx *cli.Context) error {
// Set the chain id
chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name))
if txIt, err = loadTransactions(txStr, inputData, prestate.Env, chainConfig); err != nil {
if txIt, err = loadTransactions(txStr, inputData, chainConfig); err != nil {
return err
}
if err := applyLondonChecks(&prestate.Env, chainConfig); err != nil {

View file

@ -112,7 +112,7 @@ func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Tran
return signedTxs, nil
}
func loadTransactions(txStr string, inputData *input, env stEnv, chainConfig *params.ChainConfig) (txIterator, error) {
func loadTransactions(txStr string, inputData *input, chainConfig *params.ChainConfig) (txIterator, error) {
var txsWithKeys []*txWithKey
if txStr != stdinSelector {
data, err := os.ReadFile(txStr)

View file

@ -39,7 +39,6 @@ import (
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli/v2"
)
@ -516,7 +515,7 @@ func importPreimages(ctx *cli.Context) error {
return nil
}
func parseDumpConfig(ctx *cli.Context, stack *node.Node, db ethdb.Database) (*state.DumpConfig, common.Hash, error) {
func parseDumpConfig(ctx *cli.Context, db ethdb.Database) (*state.DumpConfig, common.Hash, error) {
var header *types.Header
if ctx.NArg() > 1 {
return nil, common.Hash{}, fmt.Errorf("expected 1 argument (number or hash), got %d", ctx.NArg())
@ -580,7 +579,7 @@ func dump(ctx *cli.Context) error {
db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close()
conf, root, err := parseDumpConfig(ctx, stack, db)
conf, root, err := parseDumpConfig(ctx, db)
if err != nil {
return err
}

View file

@ -156,6 +156,7 @@ var (
utils.BeaconGenesisRootFlag,
utils.BeaconGenesisTimeFlag,
utils.BeaconCheckpointFlag,
utils.CollectWitnessFlag,
}, utils.NetworkFlags, utils.DatabaseFlags)
rpcFlags = []cli.Flag{

View file

@ -544,7 +544,7 @@ func dumpState(ctx *cli.Context) error {
db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close()
conf, root, err := parseDumpConfig(ctx, stack, db)
conf, root, err := parseDumpConfig(ctx, db)
if err != nil {
return err
}

View file

@ -604,6 +604,11 @@ var (
Usage: "Disables db compaction after import",
Category: flags.LoggingCategory,
}
CollectWitnessFlag = &cli.BoolFlag{
Name: "collectwitness",
Usage: "Enable state witness generation during block execution. Work in progress flag, don't use.",
Category: flags.MiscCategory,
}
// MISC settings
SyncTargetFlag = &cli.StringFlag{
@ -1760,6 +1765,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
}
if ctx.IsSet(CollectWitnessFlag.Name) {
cfg.EnableWitnessCollection = ctx.Bool(CollectWitnessFlag.Name)
}
if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
@ -2190,7 +2198,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) {
cache.TrieDirtyLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100
}
vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)}
vmcfg := vm.Config{
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
EnableWitnessCollection: ctx.Bool(CollectWitnessFlag.Name),
}
if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" {
var config json.RawMessage

View file

@ -1809,7 +1809,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) {
statedb.StartPrefetcher("chain")
statedb.StartPrefetcher("chain", !bc.vmConfig.EnableWitnessCollection)
}
activeState = statedb

View file

@ -594,7 +594,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
// Pre-deploy EIP-4788 system contract
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode},
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
},
}
if faucet != nil {

View file

@ -19,7 +19,6 @@ package rawdb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math/big"
"slices"
@ -695,27 +694,6 @@ func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
return nil
}
// deriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc.
func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error {
logIndex := uint(0)
if len(txs) != len(receipts) {
return errors.New("transaction and receipt count mismatch")
}
for i := 0; i < len(receipts); i++ {
txHash := txs[i].Hash()
// The derived log fields can simply be set from the block and transaction
for j := 0; j < len(receipts[i].Logs); j++ {
receipts[i].Logs[j].BlockNumber = number
receipts[i].Logs[j].BlockHash = hash
receipts[i].Logs[j].TxHash = txHash
receipts[i].Logs[j].TxIndex = uint(i)
receipts[i].Logs[j].Index = logIndex
logIndex++
}
}
return nil
}
// ReadLogs retrieves the logs for all transactions in a block. In case
// receipts is not found, a nil is returned.
// Note: ReadLogs does not derive unstored log fields.

View file

@ -794,7 +794,7 @@ func TestDeriveLogFields(t *testing.T) {
}),
}
// Create the corresponding receipts
receipts := []*receiptLogs{
receipts := []*types.Receipt{
{
Logs: []*types.Log{
{Address: common.BytesToAddress([]byte{0x11})},
@ -818,9 +818,7 @@ func TestDeriveLogFields(t *testing.T) {
// Derive log metadata fields
number := big.NewInt(1)
hash := common.BytesToHash([]byte{0x03, 0x14})
if err := deriveLogFields(receipts, hash, number.Uint64(), txs); err != nil {
t.Fatal(err)
}
types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 0, big.NewInt(0), big.NewInt(0), txs)
// Iterate over all the computed fields and check that they're correct
logIndex := uint(0)

View file

@ -60,11 +60,11 @@ func newAccessList() *accessList {
}
// Copy creates an independent copy of an accessList.
func (a *accessList) Copy() *accessList {
func (al *accessList) Copy() *accessList {
cp := newAccessList()
cp.addresses = maps.Clone(a.addresses)
cp.slots = make([]map[common.Hash]struct{}, len(a.slots))
for i, slotMap := range a.slots {
cp.addresses = maps.Clone(al.addresses)
cp.slots = make([]map[common.Hash]struct{}, len(al.slots))
for i, slotMap := range al.slots {
cp.slots[i] = maps.Clone(slotMap)
}
return cp

View file

@ -815,7 +815,7 @@ func TestStorageIteratorDeletions(t *testing.T) {
verifyIterator(t, 2, snaps.Snapshot(common.HexToHash("0x06")).(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage)
}
// BenchmarkAccountIteratorTraversal is a bit a bit notorious -- all layers contain the
// BenchmarkAccountIteratorTraversal is a bit notorious -- all layers contain the
// exact same 200 accounts. That means that we need to process 2000 items, but
// only spit out 200 values eventually.
//

View file

@ -230,6 +230,14 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
}
value.SetBytes(val)
}
// Independent of where we loaded the data from, add it to the prefetcher.
// Whilst this would be a bit weird if snapshots are disabled, but we still
// want the trie nodes to end up in the prefetcher too, so just push through.
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
}
}
s.originStorage[key] = value
return value
}
@ -293,7 +301,7 @@ func (s *stateObject) finalise() {
s.pendingStorage[key] = value
}
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch, false); err != nil {
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
}
}

View file

@ -200,14 +200,14 @@ func (s *StateDB) SetLogger(l *tracing.Hooks) {
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot.
func (s *StateDB) StartPrefetcher(namespace string) {
func (s *StateDB) StartPrefetcher(namespace string, noreads bool) {
if s.prefetcher != nil {
s.prefetcher.terminate(false)
s.prefetcher.report()
s.prefetcher = nil
}
if s.snap != nil {
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, noreads)
// With the switch to the Proof-of-Stake consensus algorithm, block production
// rewards are now handled at the consensus layer. Consequently, a block may
@ -218,7 +218,7 @@ func (s *StateDB) StartPrefetcher(namespace string) {
// To prevent this, the account trie is always scheduled for prefetching once
// the prefetcher is constructed. For more details, see:
// https://github.com/ethereum/go-ethereum/issues/29880
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil); err != nil {
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil, false); err != nil {
log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err)
}
}
@ -616,6 +616,14 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
return nil
}
}
// Independent of where we loaded the data from, add it to the prefetcher.
// Whilst this would be a bit weird if snapshots are disabled, but we still
// want the trie nodes to end up in the prefetcher too, so just push through.
if s.prefetcher != nil {
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, [][]byte{addr[:]}, true); err != nil {
log.Error("Failed to prefetch account", "addr", addr, "err", err)
}
}
// Insert into the live set
obj := newObject(s, addr, data)
s.setStateObject(obj)
@ -792,7 +800,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
}
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch); err != nil {
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch, false); err != nil {
log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
}
}

View file

@ -44,31 +44,49 @@ type triePrefetcher struct {
root common.Hash // Root hash of the account trie for metrics
fetchers map[string]*subfetcher // Subfetchers for each trie
term chan struct{} // Channel to signal interruption
noreads bool // Whether to ignore state-read-only prefetch requests
deliveryMissMeter metrics.Meter
accountLoadMeter metrics.Meter
accountDupMeter metrics.Meter
accountWasteMeter metrics.Meter
storageLoadMeter metrics.Meter
storageDupMeter metrics.Meter
storageWasteMeter metrics.Meter
accountLoadReadMeter metrics.Meter
accountLoadWriteMeter metrics.Meter
accountDupReadMeter metrics.Meter
accountDupWriteMeter metrics.Meter
accountDupCrossMeter metrics.Meter
accountWasteMeter metrics.Meter
storageLoadReadMeter metrics.Meter
storageLoadWriteMeter metrics.Meter
storageDupReadMeter metrics.Meter
storageDupWriteMeter metrics.Meter
storageDupCrossMeter metrics.Meter
storageWasteMeter metrics.Meter
}
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher {
prefix := triePrefetchMetricsPrefix + namespace
return &triePrefetcher{
db: db,
root: root,
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
term: make(chan struct{}),
noreads: noreads,
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
accountLoadReadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load/read", nil),
accountLoadWriteMeter: metrics.GetOrRegisterMeter(prefix+"/account/load/write", nil),
accountDupReadMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/read", nil),
accountDupWriteMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/write", nil),
accountDupCrossMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/cross", nil),
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
storageLoadReadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load/read", nil),
storageLoadWriteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load/write", nil),
storageDupReadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/read", nil),
storageDupWriteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/write", nil),
storageDupCrossMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/cross", nil),
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
}
}
@ -82,7 +100,7 @@ func (p *triePrefetcher) terminate(async bool) {
return
default:
}
// Termiante all sub-fetchers, sync or async, depending on the request
// Terminate all sub-fetchers, sync or async, depending on the request
for _, fetcher := range p.fetchers {
fetcher.terminate(async)
}
@ -98,19 +116,31 @@ func (p *triePrefetcher) report() {
fetcher.wait() // ensure the fetcher's idle before poking in its internals
if fetcher.root == p.root {
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
p.accountDupMeter.Mark(int64(fetcher.dups))
p.accountLoadReadMeter.Mark(int64(len(fetcher.seenRead)))
p.accountLoadWriteMeter.Mark(int64(len(fetcher.seenWrite)))
p.accountDupReadMeter.Mark(int64(fetcher.dupsRead))
p.accountDupWriteMeter.Mark(int64(fetcher.dupsWrite))
p.accountDupCrossMeter.Mark(int64(fetcher.dupsCross))
for _, key := range fetcher.used {
delete(fetcher.seen, string(key))
delete(fetcher.seenRead, string(key))
delete(fetcher.seenWrite, string(key))
}
p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
p.accountWasteMeter.Mark(int64(len(fetcher.seenRead) + len(fetcher.seenWrite)))
} else {
p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
p.storageDupMeter.Mark(int64(fetcher.dups))
p.storageLoadReadMeter.Mark(int64(len(fetcher.seenRead)))
p.storageLoadWriteMeter.Mark(int64(len(fetcher.seenWrite)))
p.storageDupReadMeter.Mark(int64(fetcher.dupsRead))
p.storageDupWriteMeter.Mark(int64(fetcher.dupsWrite))
p.storageDupCrossMeter.Mark(int64(fetcher.dupsCross))
for _, key := range fetcher.used {
delete(fetcher.seen, string(key))
delete(fetcher.seenRead, string(key))
delete(fetcher.seenWrite, string(key))
}
p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
p.storageWasteMeter.Mark(int64(len(fetcher.seenRead) + len(fetcher.seenWrite)))
}
}
}
@ -126,7 +156,11 @@ func (p *triePrefetcher) report() {
// upon the same contract, the parameters invoking this method may be
// repeated.
// 2. Finalize of the main account trie. This happens only once per block.
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) error {
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte, read bool) error {
// If the state item is only being read, but reads are disabled, return
if read && p.noreads {
return nil
}
// Ensure the subfetcher is still alive
select {
case <-p.term:
@ -139,7 +173,7 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr comm
fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
p.fetchers[id] = fetcher
}
return fetcher.schedule(keys)
return fetcher.schedule(keys, read)
}
// trie returns the trie matching the root hash, blocking until the fetcher of
@ -186,38 +220,51 @@ type subfetcher struct {
addr common.Address // Address of the account that the trie belongs to
trie Trie // Trie being populated with nodes
tasks [][]byte // Items queued up for retrieval
lock sync.Mutex // Lock protecting the task queue
tasks []*subfetcherTask // Items queued up for retrieval
lock sync.Mutex // Lock protecting the task queue
wake chan struct{} // Wake channel if a new task is scheduled
stop chan struct{} // Channel to interrupt processing
term chan struct{} // Channel to signal interruption
seen map[string]struct{} // Tracks the entries already loaded
dups int // Number of duplicate preload tasks
used [][]byte // Tracks the entries used in the end
seenRead map[string]struct{} // Tracks the entries already loaded via read operations
seenWrite map[string]struct{} // Tracks the entries already loaded via write operations
dupsRead int // Number of duplicate preload tasks via reads only
dupsWrite int // Number of duplicate preload tasks via writes only
dupsCross int // Number of duplicate preload tasks via read-write-crosses
used [][]byte // Tracks the entries used in the end
}
// subfetcherTask is a trie path to prefetch, tagged with whether it originates
// from a read or a write request.
type subfetcherTask struct {
read bool
key []byte
}
// newSubfetcher creates a goroutine to prefetch state items belonging to a
// particular root hash.
func newSubfetcher(db Database, state common.Hash, owner common.Hash, root common.Hash, addr common.Address) *subfetcher {
sf := &subfetcher{
db: db,
state: state,
owner: owner,
root: root,
addr: addr,
wake: make(chan struct{}, 1),
stop: make(chan struct{}),
term: make(chan struct{}),
seen: make(map[string]struct{}),
db: db,
state: state,
owner: owner,
root: root,
addr: addr,
wake: make(chan struct{}, 1),
stop: make(chan struct{}),
term: make(chan struct{}),
seenRead: make(map[string]struct{}),
seenWrite: make(map[string]struct{}),
}
go sf.loop()
return sf
}
// schedule adds a batch of trie keys to the queue to prefetch.
func (sf *subfetcher) schedule(keys [][]byte) error {
func (sf *subfetcher) schedule(keys [][]byte, read bool) error {
// Ensure the subfetcher is still alive
select {
case <-sf.term:
@ -226,7 +273,10 @@ func (sf *subfetcher) schedule(keys [][]byte) error {
}
// Append the tasks to the current queue
sf.lock.Lock()
sf.tasks = append(sf.tasks, keys...)
for _, key := range keys {
key := key // closure for the append below
sf.tasks = append(sf.tasks, &subfetcherTask{read: read, key: key})
}
sf.lock.Unlock()
// Notify the background thread to execute scheduled tasks
@ -303,16 +353,36 @@ func (sf *subfetcher) loop() {
sf.lock.Unlock()
for _, task := range tasks {
if _, ok := sf.seen[string(task)]; ok {
sf.dups++
continue
}
if len(task) == common.AddressLength {
sf.trie.GetAccount(common.BytesToAddress(task))
key := string(task.key)
if task.read {
if _, ok := sf.seenRead[key]; ok {
sf.dupsRead++
continue
}
if _, ok := sf.seenWrite[key]; ok {
sf.dupsCross++
continue
}
} else {
sf.trie.GetStorage(sf.addr, task)
if _, ok := sf.seenRead[key]; ok {
sf.dupsCross++
continue
}
if _, ok := sf.seenWrite[key]; ok {
sf.dupsWrite++
continue
}
}
if len(task.key) == common.AddressLength {
sf.trie.GetAccount(common.BytesToAddress(task.key))
} else {
sf.trie.GetStorage(sf.addr, task.key)
}
if task.read {
sf.seenRead[key] = struct{}{}
} else {
sf.seenWrite[key] = struct{}{}
}
sf.seen[string(task)] = struct{}{}
}
case <-sf.stop:

View file

@ -47,15 +47,15 @@ func filledStateDB() *StateDB {
func TestUseAfterTerminate(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", true)
skey := common.HexToHash("aaa")
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err != nil {
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}, false); err != nil {
t.Errorf("Prefetch failed before terminate: %v", err)
}
prefetcher.terminate(false)
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}, false); err == nil {
t.Errorf("Prefetch succeeded after terminate: %v", err)
}
if tr := prefetcher.trie(common.Hash{}, db.originalRoot); tr == nil {

View file

@ -33,6 +33,7 @@ type Config struct {
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled
EnableWitnessCollection bool // true if witness collection is enabled
}
// ScopeContext contains the things that are per-call, such as stack and memory,

View file

@ -184,6 +184,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
var (
vmConfig = vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording,
EnableWitnessCollection: config.EnableWitnessCollection,
}
cacheConfig = &core.CacheConfig{
TrieCleanLimit: config.TrieCleanCache,

View file

@ -141,6 +141,9 @@ type Config struct {
// Enables tracking of SHA3 preimages in the VM
EnablePreimageRecording bool
// Enables prefetching trie nodes for read operations too
EnableWitnessCollection bool `toml:"-"`
// Enables VM tracing
VMTrace string
VMTraceJsonConfig string

View file

@ -50,6 +50,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
BlobPool blobpool.Config
GPO gasprice.Config
EnablePreimageRecording bool
EnableWitnessCollection bool `toml:"-"`
VMTrace string
VMTraceJsonConfig string
DocRoot string `toml:"-"`
@ -93,6 +94,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.BlobPool = c.BlobPool
enc.GPO = c.GPO
enc.EnablePreimageRecording = c.EnablePreimageRecording
enc.EnableWitnessCollection = c.EnableWitnessCollection
enc.VMTrace = c.VMTrace
enc.VMTraceJsonConfig = c.VMTraceJsonConfig
enc.DocRoot = c.DocRoot
@ -140,6 +142,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
BlobPool *blobpool.Config
GPO *gasprice.Config
EnablePreimageRecording *bool
EnableWitnessCollection *bool `toml:"-"`
VMTrace *string
VMTraceJsonConfig *string
DocRoot *string `toml:"-"`
@ -252,6 +255,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.EnablePreimageRecording != nil {
c.EnablePreimageRecording = *dec.EnablePreimageRecording
}
if dec.EnableWitnessCollection != nil {
c.EnableWitnessCollection = *dec.EnableWitnessCollection
}
if dec.VMTrace != nil {
c.VMTrace = *dec.VMTrace
}

View file

@ -398,7 +398,7 @@ func TestSupplySelfdestruct(t *testing.T) {
}
// Tests selfdestructing contract to send its balance to itself (burn).
// It tests both cases of selfdestructing succeding and being reverted.
// It tests both cases of selfdestructing succeeding and being reverted.
// - Contract A calls B and D.
// - Contract B selfdestructs and sends the eth1 to itself (Burn amount to be counted).
// - Contract C selfdestructs and sends the eth1 to itself.

View file

@ -205,7 +205,7 @@ func (s *supply) internalTxsHandler(call *supplyTxCallstack) {
}
if len(call.calls) > 0 {
// Recursivelly handle internal calls
// Recursively handle internal calls
for _, call := range call.calls {
callCopy := call
s.internalTxsHandler(&callCopy)

2
go.mod
View file

@ -68,6 +68,7 @@ require (
github.com/urfave/cli/v2 v2.25.7
go.uber.org/automaxprocs v1.5.2
golang.org/x/crypto v0.22.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/sync v0.7.0
golang.org/x/sys v0.20.0
golang.org/x/text v0.14.0
@ -139,7 +140,6 @@ require (
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect

View file

@ -65,11 +65,8 @@ type tcpDialer struct {
}
func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
return t.d.DialContext(ctx, "tcp", nodeAddr(dest).String())
}
func nodeAddr(n *enode.Node) net.Addr {
return &net.TCPAddr{IP: n.IP(), Port: n.TCP()}
addr, _ := dest.TCPEndpoint()
return t.d.DialContext(ctx, "tcp", addr.String())
}
// checkDial errors:
@ -243,7 +240,7 @@ loop:
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)
d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IPAddr(), "reason", err)
} else {
d.startDial(newDialTask(node, dynDialedConn))
}
@ -277,7 +274,7 @@ loop:
case node := <-d.addStaticCh:
id := node.ID()
_, exists := d.static[id]
d.log.Trace("Adding static node", "id", id, "ip", node.IP(), "added", !exists)
d.log.Trace("Adding static node", "id", id, "ip", node.IPAddr(), "added", !exists)
if exists {
continue loop
}
@ -376,7 +373,7 @@ func (d *dialScheduler) checkDial(n *enode.Node) error {
if n.ID() == d.self {
return errSelf
}
if n.IP() != nil && n.TCP() == 0 {
if n.IPAddr().IsValid() && n.TCP() == 0 {
// This check can trigger if a non-TCP node is found
// by discovery. If there is no IP, the node is a static
// node and the actual endpoint will be resolved later in dialTask.
@ -388,7 +385,7 @@ func (d *dialScheduler) checkDial(n *enode.Node) error {
if _, ok := d.peers[n.ID()]; ok {
return errAlreadyConnected
}
if d.netRestrict != nil && !d.netRestrict.Contains(n.IP()) {
if d.netRestrict != nil && !d.netRestrict.ContainsAddr(n.IPAddr()) {
return errNetRestrict
}
if d.history.contains(string(n.ID().Bytes())) {
@ -439,7 +436,7 @@ func (d *dialScheduler) removeFromStaticPool(idx int) {
// startDial runs the given dial task in a separate goroutine.
func (d *dialScheduler) startDial(task *dialTask) {
node := task.dest()
d.log.Trace("Starting p2p dial", "id", node.ID(), "ip", node.IP(), "flag", task.flags)
d.log.Trace("Starting p2p dial", "id", node.ID(), "ip", node.IPAddr(), "flag", task.flags)
hkey := string(node.ID().Bytes())
d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration))
d.dialing[node.ID()] = task
@ -492,7 +489,7 @@ func (t *dialTask) run(d *dialScheduler) {
}
func (t *dialTask) needResolve() bool {
return t.flags&staticDialedConn != 0 && t.dest().IP() == nil
return t.flags&staticDialedConn != 0 && !t.dest().IPAddr().IsValid()
}
// resolve attempts to find the current endpoint for the destination
@ -526,7 +523,8 @@ func (t *dialTask) resolve(d *dialScheduler) bool {
// The node was found.
t.resolveDelay = initialResolveDelay
t.destPtr.Store(resolved)
d.log.Debug("Resolved node", "id", resolved.ID(), "addr", &net.TCPAddr{IP: resolved.IP(), Port: resolved.TCP()})
resAddr, _ := resolved.TCPEndpoint()
d.log.Debug("Resolved node", "id", resolved.ID(), "addr", resAddr)
return true
}
@ -535,7 +533,8 @@ func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
dialMeter.Mark(1)
fd, err := d.dialer.Dial(d.ctx, dest)
if err != nil {
d.log.Trace("Dial error", "id", dest.ID(), "addr", nodeAddr(dest), "conn", t.flags, "err", cleanupDialErr(err))
addr, _ := dest.TCPEndpoint()
d.log.Trace("Dial error", "id", dest.ID(), "addr", addr, "conn", t.flags, "err", cleanupDialErr(err))
dialConnectionError.Mark(1)
return &dialError{err}
}
@ -545,7 +544,7 @@ func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
func (t *dialTask) String() string {
node := t.dest()
id := node.ID()
return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], node.IP(), node.TCP())
return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], node.IPAddr(), node.TCP())
}
func cleanupDialErr(err error) error {

View file

@ -25,7 +25,7 @@ package discover
import (
"context"
"fmt"
"net"
"net/netip"
"slices"
"sync"
"time"
@ -207,8 +207,8 @@ func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
if err := n.ValidateComplete(); err != nil {
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
}
if tab.cfg.NetRestrict != nil && !tab.cfg.NetRestrict.Contains(n.IP()) {
tab.log.Error("Bootstrap node filtered by netrestrict", "id", n.ID(), "ip", n.IP())
if tab.cfg.NetRestrict != nil && !tab.cfg.NetRestrict.ContainsAddr(n.IPAddr()) {
tab.log.Error("Bootstrap node filtered by netrestrict", "id", n.ID(), "ip", n.IPAddr())
continue
}
nursery = append(nursery, n)
@ -448,11 +448,13 @@ func (tab *Table) loadSeedNodes() {
for i := range seeds {
seed := seeds[i]
if tab.log.Enabled(context.Background(), log.LevelTrace) {
age := time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP()))
age := time.Since(tab.db.LastPongReceived(seed.ID(), seed.IPAddr()))
addr, _ := seed.UDPEndpoint()
tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", addr, "age", age)
}
tab.mutex.Lock()
tab.handleAddNode(addNodeOp{node: seed, isInbound: false})
tab.mutex.Unlock()
}
}
@ -474,31 +476,31 @@ func (tab *Table) bucketAtDistance(d int) *bucket {
return tab.buckets[d-bucketMinDistance-1]
}
func (tab *Table) addIP(b *bucket, ip net.IP) bool {
if len(ip) == 0 {
func (tab *Table) addIP(b *bucket, ip netip.Addr) bool {
if !ip.IsValid() || ip.IsUnspecified() {
return false // Nodes without IP cannot be added.
}
if netutil.IsLAN(ip) {
if netutil.AddrIsLAN(ip) {
return true
}
if !tab.ips.Add(ip) {
if !tab.ips.AddAddr(ip) {
tab.log.Debug("IP exceeds table limit", "ip", ip)
return false
}
if !b.ips.Add(ip) {
if !b.ips.AddAddr(ip) {
tab.log.Debug("IP exceeds bucket limit", "ip", ip)
tab.ips.Remove(ip)
tab.ips.RemoveAddr(ip)
return false
}
return true
}
func (tab *Table) removeIP(b *bucket, ip net.IP) {
if netutil.IsLAN(ip) {
func (tab *Table) removeIP(b *bucket, ip netip.Addr) {
if netutil.AddrIsLAN(ip) {
return
}
tab.ips.Remove(ip)
b.ips.Remove(ip)
tab.ips.RemoveAddr(ip)
b.ips.RemoveAddr(ip)
}
// handleAddNode adds the node in the request to the table, if there is space.
@ -524,7 +526,7 @@ func (tab *Table) handleAddNode(req addNodeOp) bool {
tab.addReplacement(b, req.node)
return false
}
if !tab.addIP(b, req.node.IP()) {
if !tab.addIP(b, req.node.IPAddr()) {
// Can't add: IP limit reached.
return false
}
@ -547,7 +549,7 @@ func (tab *Table) addReplacement(b *bucket, n *enode.Node) {
// TODO: update ENR
return
}
if !tab.addIP(b, n.IP()) {
if !tab.addIP(b, n.IPAddr()) {
return
}
@ -555,7 +557,7 @@ func (tab *Table) addReplacement(b *bucket, n *enode.Node) {
var removed *tableNode
b.replacements, removed = pushNode(b.replacements, wn, maxReplacements)
if removed != nil {
tab.removeIP(b, removed.IP())
tab.removeIP(b, removed.IPAddr())
}
}
@ -595,12 +597,12 @@ func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *tableNode {
// Remove the node.
n := b.entries[index]
b.entries = slices.Delete(b.entries, index, index+1)
tab.removeIP(b, n.IP())
tab.removeIP(b, n.IPAddr())
tab.nodeRemoved(b, n)
// Add replacement.
if len(b.replacements) == 0 {
tab.log.Debug("Removed dead node", "b", b.index, "id", n.ID(), "ip", n.IP())
tab.log.Debug("Removed dead node", "b", b.index, "id", n.ID(), "ip", n.IPAddr())
return nil
}
rindex := tab.rand.Intn(len(b.replacements))
@ -608,7 +610,7 @@ func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *tableNode {
b.replacements = slices.Delete(b.replacements, rindex, rindex+1)
b.entries = append(b.entries, rep)
tab.nodeAdded(b, rep)
tab.log.Debug("Replaced dead node", "b", b.index, "id", n.ID(), "ip", n.IP(), "r", rep.ID(), "rip", rep.IP())
tab.log.Debug("Replaced dead node", "b", b.index, "id", n.ID(), "ip", n.IPAddr(), "r", rep.ID(), "rip", rep.IPAddr())
return rep
}
@ -635,10 +637,10 @@ func (tab *Table) bumpInBucket(b *bucket, newRecord *enode.Node, isInbound bool)
ipchanged := newRecord.IPAddr() != n.IPAddr()
portchanged := newRecord.UDP() != n.UDP()
if ipchanged {
tab.removeIP(b, n.IP())
if !tab.addIP(b, newRecord.IP()) {
tab.removeIP(b, n.IPAddr())
if !tab.addIP(b, newRecord.IPAddr()) {
// It doesn't fit with the limit, put the previous record back.
tab.addIP(b, n.IP())
tab.addIP(b, n.IPAddr())
return n, false
}
}
@ -657,11 +659,11 @@ func (tab *Table) handleTrackRequest(op trackRequestOp) {
var fails int
if op.success {
// Reset failure counter because it counts _consecutive_ failures.
tab.db.UpdateFindFails(op.node.ID(), op.node.IP(), 0)
tab.db.UpdateFindFails(op.node.ID(), op.node.IPAddr(), 0)
} else {
fails = tab.db.FindFails(op.node.ID(), op.node.IP())
fails = tab.db.FindFails(op.node.ID(), op.node.IPAddr())
fails++
tab.db.UpdateFindFails(op.node.ID(), op.node.IP(), fails)
tab.db.UpdateFindFails(op.node.ID(), op.node.IPAddr(), fails)
}
tab.mutex.Lock()

View file

@ -188,7 +188,7 @@ func checkIPLimitInvariant(t *testing.T, tab *Table) {
tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit}
for _, b := range tab.buckets {
for _, n := range b.entries {
tabset.Add(n.IP())
tabset.AddAddr(n.IPAddr())
}
}
if tabset.String() != tab.ips.String() {
@ -268,7 +268,7 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
}
for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
r := new(enr.Record)
r.Set(enr.IP(genIP(rand)))
r.Set(enr.IPv4Addr(netutil.RandomAddr(rand, true)))
n := enode.SignNull(r, id)
t.All = append(t.All, n)
}
@ -385,11 +385,11 @@ func checkBucketContent(t *testing.T, tab *Table, nodes []*enode.Node) {
}
t.Log("wrong bucket content. have nodes:")
for _, n := range b.entries {
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IP())
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IPAddr())
}
t.Log("want nodes:")
for _, n := range nodes {
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IP())
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IPAddr())
}
t.FailNow()
@ -483,12 +483,6 @@ func gen(typ interface{}, rand *rand.Rand) interface{} {
return v.Interface()
}
func genIP(rand *rand.Rand) net.IP {
ip := make(net.IP, 4)
rand.Read(ip)
return ip
}
func quickcfg() *quick.Config {
return &quick.Config{
MaxCount: 5000,

View file

@ -100,8 +100,9 @@ func idAtDistance(a enode.ID, n int) (b enode.ID) {
return b
}
// intIP returns a LAN IP address based on i.
func intIP(i int) net.IP {
return net.IP{byte(i), 0, 2, byte(i)}
return net.IP{10, 0, byte(i >> 8), byte(i & 0xFF)}
}
// fillBucket inserts nodes into the given bucket until it is full.
@ -254,7 +255,7 @@ NotEqual:
}
func nodeEqual(n1 *enode.Node, n2 *enode.Node) bool {
return n1.ID() == n2.ID() && n1.IP().Equal(n2.IP())
return n1.ID() == n2.ID() && n1.IPAddr() == n2.IPAddr()
}
func sortByID[N nodeType](nodes []N) {

View file

@ -25,7 +25,6 @@ import (
"errors"
"fmt"
"io"
"net"
"net/netip"
"sync"
"time"
@ -250,8 +249,7 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr netip.AddrPort, callback func())
return matched, matched
})
// Send the packet.
toUDPAddr := &net.UDPAddr{IP: toaddr.Addr().AsSlice()}
t.localNode.UDPContact(toUDPAddr)
t.localNode.UDPContact(toaddr)
t.write(toaddr, toid, req.Name(), packet)
return rm
}
@ -383,7 +381,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
if respN.Seq() < n.Seq() {
return n, nil // response record is older
}
if err := netutil.CheckRelayIP(addr.Addr().AsSlice(), respN.IP()); err != nil {
if err := netutil.CheckRelayAddr(addr.Addr(), respN.IPAddr()); err != nil {
return nil, fmt.Errorf("invalid IP in response record: %v", err)
}
return respN, nil
@ -559,6 +557,11 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
}
func (t *UDPv4) handlePacket(from netip.AddrPort, buf []byte) error {
// Unwrap IPv4-in-6 source address.
if from.Addr().Is4In6() {
from = netip.AddrPortFrom(netip.AddrFrom4(from.Addr().As4()), from.Port())
}
rawpacket, fromKey, hash, err := v4wire.Decode(buf)
if err != nil {
t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
@ -578,15 +581,14 @@ func (t *UDPv4) handlePacket(from netip.AddrPort, buf []byte) error {
// checkBond checks if the given node has a recent enough endpoint proof.
func (t *UDPv4) checkBond(id enode.ID, ip netip.AddrPort) bool {
return time.Since(t.db.LastPongReceived(id, ip.Addr().AsSlice())) < bondExpiration
return time.Since(t.db.LastPongReceived(id, ip.Addr())) < bondExpiration
}
// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while.
// This ensures there is a valid endpoint proof on the remote end.
func (t *UDPv4) ensureBond(toid enode.ID, toaddr netip.AddrPort) {
ip := toaddr.Addr().AsSlice()
tooOld := time.Since(t.db.LastPingReceived(toid, ip)) > bondExpiration
if tooOld || t.db.FindFails(toid, ip) > maxFindnodeFailures {
tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.Addr())) > bondExpiration
if tooOld || t.db.FindFails(toid, toaddr.Addr()) > maxFindnodeFailures {
rm := t.sendPing(toid, toaddr, nil)
<-rm.errc
// Wait for them to ping back and process our pong.
@ -687,7 +689,7 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from netip.AddrPort, fromID enode
// Ping back if our last pong on file is too far in the past.
fromIP := from.Addr().AsSlice()
n := enode.NewV4(h.senderKey, fromIP, int(req.From.TCP), int(from.Port()))
if time.Since(t.db.LastPongReceived(n.ID(), fromIP)) > bondExpiration {
if time.Since(t.db.LastPongReceived(n.ID(), from.Addr())) > bondExpiration {
t.sendPing(fromID, from, func() {
t.tab.addInboundNode(n)
})
@ -696,10 +698,9 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from netip.AddrPort, fromID enode
}
// Update node database and endpoint predictor.
t.db.UpdateLastPingReceived(n.ID(), fromIP, time.Now())
fromUDPAddr := &net.UDPAddr{IP: fromIP, Port: int(from.Port())}
toUDPAddr := &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
t.db.UpdateLastPingReceived(n.ID(), from.Addr(), time.Now())
toaddr := netip.AddrPortFrom(netutil.IPToAddr(req.To.IP), req.To.UDP)
t.localNode.UDPEndpointStatement(from, toaddr)
}
// PONG/v4
@ -713,11 +714,9 @@ func (t *UDPv4) verifyPong(h *packetHandlerV4, from netip.AddrPort, fromID enode
if !t.handleReply(fromID, from.Addr(), req) {
return errUnsolicitedReply
}
fromIP := from.Addr().AsSlice()
fromUDPAddr := &net.UDPAddr{IP: fromIP, Port: int(from.Port())}
toUDPAddr := &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
t.db.UpdateLastPongReceived(fromID, fromIP, time.Now())
toaddr := netip.AddrPortFrom(netutil.IPToAddr(req.To.IP), req.To.UDP)
t.localNode.UDPEndpointStatement(from, toaddr)
t.db.UpdateLastPongReceived(fromID, from.Addr(), time.Now())
return nil
}
@ -753,8 +752,7 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from netip.AddrPort, fromID e
p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
var sent bool
for _, n := range closest {
fromIP := from.Addr().AsSlice()
if netutil.CheckRelayIP(fromIP, n.IP()) == nil {
if netutil.CheckRelayAddr(from.Addr(), n.IPAddr()) == nil {
p.Nodes = append(p.Nodes, nodeToRPC(n))
}
if len(p.Nodes) == v4wire.MaxNeighbors {

View file

@ -274,7 +274,7 @@ func TestUDPv4_findnode(t *testing.T) {
// ensure there's a bond with the test node,
// findnode won't be accepted otherwise.
remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID()
test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.Addr().AsSlice(), time.Now())
test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.Addr(), time.Now())
// check that closest neighbors are returned.
expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true)
@ -309,7 +309,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
defer test.close()
rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.Addr().AsSlice(), time.Now())
test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.Addr(), time.Now())
// queue a pending findnode request
resultc, errc := make(chan []*enode.Node, 1), make(chan error, 1)
@ -437,8 +437,8 @@ func TestUDPv4_successfulPing(t *testing.T) {
if n.ID() != rid {
t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid)
}
if !n.IP().Equal(test.remoteaddr.Addr().AsSlice()) {
t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.Addr())
if n.IPAddr() != test.remoteaddr.Addr() {
t.Errorf("node has wrong IP: got %v, want: %v", n.IPAddr(), test.remoteaddr.Addr())
}
if n.UDP() != int(test.remoteaddr.Port()) {
t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port())

View file

@ -428,10 +428,10 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
if err != nil {
return nil, err
}
if err := netutil.CheckRelayIP(c.addr.Addr().AsSlice(), node.IP()); err != nil {
if err := netutil.CheckRelayAddr(c.addr.Addr(), node.IPAddr()); err != nil {
return nil, err
}
if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) {
if t.netrestrict != nil && !t.netrestrict.ContainsAddr(node.IPAddr()) {
return nil, errors.New("not contained in netrestrict list")
}
if node.UDP() <= 1024 {
@ -674,6 +674,10 @@ func (t *UDPv5) readLoop() {
// dispatchReadPacket sends a packet into the dispatch loop.
func (t *UDPv5) dispatchReadPacket(from netip.AddrPort, content []byte) bool {
// Unwrap IPv4-in-6 source address.
if from.Addr().Is4In6() {
from = netip.AddrPortFrom(netip.AddrFrom4(from.Addr().As4()), from.Port())
}
select {
case t.packetInCh <- ReadPacket{content, from}:
return true
@ -754,9 +758,8 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr netip.AddrPort
t.handlePing(p, fromID, fromAddr)
case *v5wire.Pong:
if t.handleCallResponse(fromID, fromAddr, p) {
fromUDPAddr := &net.UDPAddr{IP: fromAddr.Addr().AsSlice(), Port: int(fromAddr.Port())}
toUDPAddr := &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
toAddr := netip.AddrPortFrom(netutil.IPToAddr(p.ToIP), p.ToPort)
t.localNode.UDPEndpointStatement(fromAddr, toAddr)
}
case *v5wire.Findnode:
t.handleFindnode(p, fromID, fromAddr)
@ -848,7 +851,6 @@ func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr net
// collectTableNodes creates a FINDNODE result set for the given distances.
func (t *UDPv5) collectTableNodes(rip netip.Addr, distances []uint, limit int) []*enode.Node {
ripSlice := rip.AsSlice()
var bn []*enode.Node
var nodes []*enode.Node
var processed = make(map[uint]struct{})
@ -863,7 +865,7 @@ func (t *UDPv5) collectTableNodes(rip netip.Addr, distances []uint, limit int) [
for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) {
// Apply some pre-checks to avoid sending invalid nodes.
// Note liveness is checked by appendLiveNodes.
if netutil.CheckRelayIP(ripSlice, n.IP()) != nil {
if netutil.CheckRelayAddr(rip, n.IPAddr()) != nil {
continue
}
nodes = append(nodes, n)

View file

@ -606,7 +606,7 @@ func (n *handshakeTestNode) n() *enode.Node {
}
func (n *handshakeTestNode) addr() string {
return n.ln.Node().IP().String()
return n.ln.Node().IPAddr().String()
}
func (n *handshakeTestNode) id() enode.ID {

View file

@ -20,8 +20,8 @@ import (
"crypto/ecdsa"
"fmt"
"net"
"net/netip"
"reflect"
"strconv"
"sync"
"sync/atomic"
"time"
@ -175,8 +175,8 @@ func (ln *LocalNode) delete(e enr.Entry) {
}
}
func (ln *LocalNode) endpointForIP(ip net.IP) *lnEndpoint {
if ip.To4() != nil {
func (ln *LocalNode) endpointForIP(ip netip.Addr) *lnEndpoint {
if ip.Is4() {
return &ln.endpoint4
}
return &ln.endpoint6
@ -188,7 +188,7 @@ func (ln *LocalNode) SetStaticIP(ip net.IP) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.endpointForIP(ip).staticIP = ip
ln.endpointForIP(netutil.IPToAddr(ip)).staticIP = ip
ln.updateEndpoints()
}
@ -198,7 +198,7 @@ func (ln *LocalNode) SetFallbackIP(ip net.IP) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.endpointForIP(ip).fallbackIP = ip
ln.endpointForIP(netutil.IPToAddr(ip)).fallbackIP = ip
ln.updateEndpoints()
}
@ -215,21 +215,21 @@ func (ln *LocalNode) SetFallbackUDP(port int) {
// UDPEndpointStatement should be called whenever a statement about the local node's
// UDP endpoint is received. It feeds the local endpoint predictor.
func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) {
func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint netip.AddrPort) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.endpointForIP(endpoint.IP).track.AddStatement(fromaddr.String(), endpoint.String())
ln.endpointForIP(endpoint.Addr()).track.AddStatement(fromaddr.Addr(), endpoint)
ln.updateEndpoints()
}
// UDPContact should be called whenever the local node has announced itself to another node
// via UDP. It feeds the local endpoint predictor.
func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) {
func (ln *LocalNode) UDPContact(toaddr netip.AddrPort) {
ln.mu.Lock()
defer ln.mu.Unlock()
ln.endpointForIP(toaddr.IP).track.AddContact(toaddr.String())
ln.endpointForIP(toaddr.Addr()).track.AddContact(toaddr.Addr())
ln.updateEndpoints()
}
@ -268,29 +268,13 @@ func (e *lnEndpoint) get() (newIP net.IP, newPort uint16) {
}
if e.staticIP != nil {
newIP = e.staticIP
} else if ip, port := predictAddr(e.track); ip != nil {
newIP = ip
newPort = port
} else if ap := e.track.PredictEndpoint(); ap.IsValid() {
newIP = ap.Addr().AsSlice()
newPort = ap.Port()
}
return newIP, newPort
}
// predictAddr wraps IPTracker.PredictEndpoint, converting from its string-based
// endpoint representation to IP and port types.
func predictAddr(t *netutil.IPTracker) (net.IP, uint16) {
ep := t.PredictEndpoint()
if ep == "" {
return nil, 0
}
ipString, portString, _ := net.SplitHostPort(ep)
ip := net.ParseIP(ipString)
port, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
return nil, 0
}
return ip, uint16(port)
}
func (ln *LocalNode) invalidate() {
ln.cur.Store((*Node)(nil))
}
@ -314,7 +298,7 @@ func (ln *LocalNode) sign() {
panic(fmt.Errorf("enode: can't verify local record: %v", err))
}
ln.cur.Store(n)
log.Info("New local node record", "seq", ln.seq, "id", n.ID(), "ip", n.IP(), "udp", n.UDP(), "tcp", n.TCP())
log.Info("New local node record", "seq", ln.seq, "id", n.ID(), "ip", n.IPAddr(), "udp", n.UDP(), "tcp", n.TCP())
}
func (ln *LocalNode) bumpSeq() {

View file

@ -17,12 +17,14 @@
package enode
import (
"crypto/rand"
"math/rand"
"net"
"net/netip"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/stretchr/testify/assert"
)
@ -88,6 +90,7 @@ func TestLocalNodeSeqPersist(t *testing.T) {
// This test checks behavior of the endpoint predictor.
func TestLocalNodeEndpoint(t *testing.T) {
var (
rng = rand.New(rand.NewSource(4))
fallback = &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 80}
predicted = &net.UDPAddr{IP: net.IP{127, 0, 1, 2}, Port: 81}
staticIP = net.IP{127, 0, 1, 2}
@ -96,6 +99,7 @@ func TestLocalNodeEndpoint(t *testing.T) {
defer db.Close()
// Nothing is set initially.
assert.Equal(t, netip.Addr{}, ln.Node().IPAddr())
assert.Equal(t, net.IP(nil), ln.Node().IP())
assert.Equal(t, 0, ln.Node().UDP())
initialSeq := ln.Node().Seq()
@ -103,26 +107,30 @@ func TestLocalNodeEndpoint(t *testing.T) {
// Set up fallback address.
ln.SetFallbackIP(fallback.IP)
ln.SetFallbackUDP(fallback.Port)
assert.Equal(t, netutil.IPToAddr(fallback.IP), ln.Node().IPAddr())
assert.Equal(t, fallback.IP, ln.Node().IP())
assert.Equal(t, fallback.Port, ln.Node().UDP())
assert.Equal(t, initialSeq+1, ln.Node().Seq())
// Add endpoint statements from random hosts.
for i := 0; i < iptrackMinStatements; i++ {
assert.Equal(t, netutil.IPToAddr(fallback.IP), ln.Node().IPAddr())
assert.Equal(t, fallback.IP, ln.Node().IP())
assert.Equal(t, fallback.Port, ln.Node().UDP())
assert.Equal(t, initialSeq+1, ln.Node().Seq())
from := &net.UDPAddr{IP: make(net.IP, 4), Port: 90}
rand.Read(from.IP)
ln.UDPEndpointStatement(from, predicted)
from := netip.AddrPortFrom(netutil.RandomAddr(rng, true), 9000)
endpoint := netip.AddrPortFrom(netutil.IPToAddr(predicted.IP), uint16(predicted.Port))
ln.UDPEndpointStatement(from, endpoint)
}
assert.Equal(t, netutil.IPToAddr(predicted.IP), ln.Node().IPAddr())
assert.Equal(t, predicted.IP, ln.Node().IP())
assert.Equal(t, predicted.Port, ln.Node().UDP())
assert.Equal(t, initialSeq+2, ln.Node().Seq())
// Static IP overrides prediction.
ln.SetStaticIP(staticIP)
assert.Equal(t, netutil.IPToAddr(staticIP), ln.Node().IPAddr())
assert.Equal(t, staticIP, ln.Node().IP())
assert.Equal(t, fallback.Port, ln.Node().UDP())
assert.Equal(t, initialSeq+3, ln.Node().Seq())

View file

@ -21,7 +21,7 @@ import (
"crypto/rand"
"encoding/binary"
"fmt"
"net"
"net/netip"
"os"
"sync"
"time"
@ -66,7 +66,7 @@ var (
errInvalidIP = errors.New("invalid IP")
)
var zeroIP = make(net.IP, 16)
var zeroIP = netip.IPv6Unspecified()
// DB is the node database, storing previously seen nodes and any collected metadata about
// them for QoS purposes.
@ -151,39 +151,37 @@ func splitNodeKey(key []byte) (id ID, rest []byte) {
}
// nodeItemKey returns the database key for a node metadata field.
func nodeItemKey(id ID, ip net.IP, field string) []byte {
ip16 := ip.To16()
if ip16 == nil {
panic(fmt.Errorf("invalid IP (length %d)", len(ip)))
func nodeItemKey(id ID, ip netip.Addr, field string) []byte {
if !ip.IsValid() {
panic("invalid IP")
}
return bytes.Join([][]byte{nodeKey(id), ip16, []byte(field)}, []byte{':'})
ip16 := ip.As16()
return bytes.Join([][]byte{nodeKey(id), ip16[:], []byte(field)}, []byte{':'})
}
// splitNodeItemKey returns the components of a key created by nodeItemKey.
func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) {
func splitNodeItemKey(key []byte) (id ID, ip netip.Addr, field string) {
id, key = splitNodeKey(key)
// Skip discover root.
if string(key) == dbDiscoverRoot {
return id, nil, ""
return id, netip.Addr{}, ""
}
key = key[len(dbDiscoverRoot)+1:]
// Split out the IP.
ip = key[:16]
if ip4 := ip.To4(); ip4 != nil {
ip = ip4
}
ip, _ = netip.AddrFromSlice(key[:16])
key = key[16+1:]
// Field is the remainder of key.
field = string(key)
return id, ip, field
}
func v5Key(id ID, ip net.IP, field string) []byte {
func v5Key(id ID, ip netip.Addr, field string) []byte {
ip16 := ip.As16()
return bytes.Join([][]byte{
[]byte(dbNodePrefix),
id[:],
[]byte(dbDiscv5Root),
ip.To16(),
ip16[:],
[]byte(field),
}, []byte{':'})
}
@ -364,24 +362,24 @@ func (db *DB) expireNodes() {
// LastPingReceived retrieves the time of the last ping packet received from
// a remote node.
func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time {
if ip = ip.To16(); ip == nil {
func (db *DB) LastPingReceived(id ID, ip netip.Addr) time.Time {
if !ip.IsValid() {
return time.Time{}
}
return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0)
}
// UpdateLastPingReceived updates the last time we tried contacting a remote node.
func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error {
if ip = ip.To16(); ip == nil {
func (db *DB) UpdateLastPingReceived(id ID, ip netip.Addr, instance time.Time) error {
if !ip.IsValid() {
return errInvalidIP
}
return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix())
}
// LastPongReceived retrieves the time of the last successful pong from remote node.
func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time {
if ip = ip.To16(); ip == nil {
func (db *DB) LastPongReceived(id ID, ip netip.Addr) time.Time {
if !ip.IsValid() {
return time.Time{}
}
// Launch expirer
@ -390,40 +388,40 @@ func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time {
}
// UpdateLastPongReceived updates the last pong time of a node.
func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error {
if ip = ip.To16(); ip == nil {
func (db *DB) UpdateLastPongReceived(id ID, ip netip.Addr, instance time.Time) error {
if !ip.IsValid() {
return errInvalidIP
}
return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix())
}
// FindFails retrieves the number of findnode failures since bonding.
func (db *DB) FindFails(id ID, ip net.IP) int {
if ip = ip.To16(); ip == nil {
func (db *DB) FindFails(id ID, ip netip.Addr) int {
if !ip.IsValid() {
return 0
}
return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails)))
}
// UpdateFindFails updates the number of findnode failures since bonding.
func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error {
if ip = ip.To16(); ip == nil {
func (db *DB) UpdateFindFails(id ID, ip netip.Addr, fails int) error {
if !ip.IsValid() {
return errInvalidIP
}
return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails))
}
// FindFailsV5 retrieves the discv5 findnode failure counter.
func (db *DB) FindFailsV5(id ID, ip net.IP) int {
if ip = ip.To16(); ip == nil {
func (db *DB) FindFailsV5(id ID, ip netip.Addr) int {
if !ip.IsValid() {
return 0
}
return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails)))
}
// UpdateFindFailsV5 stores the discv5 findnode failure counter.
func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error {
if ip = ip.To16(); ip == nil {
func (db *DB) UpdateFindFailsV5(id ID, ip netip.Addr, fails int) error {
if !ip.IsValid() {
return errInvalidIP
}
return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails))
@ -470,7 +468,7 @@ seek:
id[0] = 0
continue seek // iterator exhausted
}
if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > maxAge {
if now.Sub(db.LastPongReceived(n.ID(), n.IPAddr())) > maxAge {
continue seek
}
for i := range nodes {

View file

@ -20,6 +20,7 @@ import (
"bytes"
"fmt"
"net"
"net/netip"
"path/filepath"
"reflect"
"testing"
@ -48,8 +49,10 @@ func TestDBNodeKey(t *testing.T) {
}
func TestDBNodeItemKey(t *testing.T) {
wantIP := net.IP{127, 0, 0, 3}
wantIP := netip.MustParseAddr("127.0.0.3")
wantIP4in6 := netip.AddrFrom16(wantIP.As16())
wantField := "foobar"
enc := nodeItemKey(keytestID, wantIP, wantField)
want := []byte{
'n', ':',
@ -69,7 +72,7 @@ func TestDBNodeItemKey(t *testing.T) {
if id != keytestID {
t.Errorf("splitNodeItemKey returned wrong ID: %v", id)
}
if !ip.Equal(wantIP) {
if ip != wantIP4in6 {
t.Errorf("splitNodeItemKey returned wrong IP: %v", ip)
}
if field != wantField {
@ -123,33 +126,33 @@ func TestDBFetchStore(t *testing.T) {
defer db.Close()
// Check fetch/store operations on a node ping object
if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != 0 {
if stored := db.LastPingReceived(node.ID(), node.IPAddr()); stored.Unix() != 0 {
t.Errorf("ping: non-existing object: %v", stored)
}
if err := db.UpdateLastPingReceived(node.ID(), node.IP(), inst); err != nil {
if err := db.UpdateLastPingReceived(node.ID(), node.IPAddr(), inst); err != nil {
t.Errorf("ping: failed to update: %v", err)
}
if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() {
if stored := db.LastPingReceived(node.ID(), node.IPAddr()); stored.Unix() != inst.Unix() {
t.Errorf("ping: value mismatch: have %v, want %v", stored, inst)
}
// Check fetch/store operations on a node pong object
if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != 0 {
if stored := db.LastPongReceived(node.ID(), node.IPAddr()); stored.Unix() != 0 {
t.Errorf("pong: non-existing object: %v", stored)
}
if err := db.UpdateLastPongReceived(node.ID(), node.IP(), inst); err != nil {
if err := db.UpdateLastPongReceived(node.ID(), node.IPAddr(), inst); err != nil {
t.Errorf("pong: failed to update: %v", err)
}
if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() {
if stored := db.LastPongReceived(node.ID(), node.IPAddr()); stored.Unix() != inst.Unix() {
t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
}
// Check fetch/store operations on a node findnode-failure object
if stored := db.FindFails(node.ID(), node.IP()); stored != 0 {
if stored := db.FindFails(node.ID(), node.IPAddr()); stored != 0 {
t.Errorf("find-node fails: non-existing object: %v", stored)
}
if err := db.UpdateFindFails(node.ID(), node.IP(), num); err != nil {
if err := db.UpdateFindFails(node.ID(), node.IPAddr(), num); err != nil {
t.Errorf("find-node fails: failed to update: %v", err)
}
if stored := db.FindFails(node.ID(), node.IP()); stored != num {
if stored := db.FindFails(node.ID(), node.IPAddr()); stored != num {
t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num)
}
// Check fetch/store operations on an actual node object
@ -266,7 +269,7 @@ func testSeedQuery() error {
if err := db.UpdateNode(seed.node); err != nil {
return fmt.Errorf("node %d: failed to insert: %v", i, err)
}
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil {
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IPAddr(), seed.pong); err != nil {
return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err)
}
}
@ -427,7 +430,7 @@ func TestDBExpiration(t *testing.T) {
t.Fatalf("node %d: failed to insert: %v", i, err)
}
}
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil {
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IPAddr(), seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err)
}
}
@ -438,13 +441,13 @@ func TestDBExpiration(t *testing.T) {
unixZeroTime := time.Unix(0, 0)
for i, seed := range nodeDBExpirationNodes {
node := db.Node(seed.node.ID())
pong := db.LastPongReceived(seed.node.ID(), seed.node.IP())
pong := db.LastPongReceived(seed.node.ID(), seed.node.IPAddr())
if seed.exp {
if seed.storeNode && node != nil {
t.Errorf("node %d (%s) shouldn't be present after expiration", i, seed.node.ID().TerminalString())
}
if !pong.Equal(unixZeroTime) {
t.Errorf("pong time %d (%s %v) shouldn't be present after expiration", i, seed.node.ID().TerminalString(), seed.node.IP())
t.Errorf("pong time %d (%s %v) shouldn't be present after expiration", i, seed.node.ID().TerminalString(), seed.node.IPAddr())
}
} else {
if seed.storeNode && node == nil {
@ -463,7 +466,7 @@ func TestDBExpireV5(t *testing.T) {
db, _ := OpenDB("")
defer db.Close()
ip := net.IP{127, 0, 0, 1}
ip := netip.MustParseAddr("127.0.0.1")
db.UpdateFindFailsV5(ID{}, ip, 4)
db.expireNodes()
}

View file

@ -16,18 +16,53 @@
package netutil
import "net"
import (
"fmt"
"math/rand"
"net"
"net/netip"
)
// AddrIP gets the IP address contained in addr. It returns nil if no address is present.
func AddrIP(addr net.Addr) net.IP {
// AddrAddr gets the IP address contained in addr. The result will be invalid if the
// address type is unsupported.
func AddrAddr(addr net.Addr) netip.Addr {
switch a := addr.(type) {
case *net.IPAddr:
return a.IP
return IPToAddr(a.IP)
case *net.TCPAddr:
return a.IP
return IPToAddr(a.IP)
case *net.UDPAddr:
return a.IP
return IPToAddr(a.IP)
default:
return nil
return netip.Addr{}
}
}
// IPToAddr converts net.IP to netip.Addr. Note that unlike netip.AddrFromSlice, this
// function will always ensure that the resulting Addr is IPv4 when the input is.
func IPToAddr(ip net.IP) netip.Addr {
if ip4 := ip.To4(); ip4 != nil {
addr, _ := netip.AddrFromSlice(ip4)
return addr
} else if ip6 := ip.To16(); ip6 != nil {
addr, _ := netip.AddrFromSlice(ip6)
return addr
}
return netip.Addr{}
}
// RandomAddr creates a random IP address.
func RandomAddr(rng *rand.Rand, ipv4 bool) netip.Addr {
var bytes []byte
if ipv4 || rng.Intn(2) == 0 {
bytes = make([]byte, 4)
} else {
bytes = make([]byte, 16)
}
rng.Read(bytes)
addr, ok := netip.AddrFromSlice(bytes)
if !ok {
panic(fmt.Errorf("BUG! invalid IP %v", bytes))
}
return addr
}

View file

@ -17,6 +17,7 @@
package netutil
import (
"net/netip"
"time"
"github.com/ethereum/go-ethereum/common/mclock"
@ -29,14 +30,14 @@ type IPTracker struct {
contactWindow time.Duration
minStatements int
clock mclock.Clock
statements map[string]ipStatement
contact map[string]mclock.AbsTime
statements map[netip.Addr]ipStatement
contact map[netip.Addr]mclock.AbsTime
lastStatementGC mclock.AbsTime
lastContactGC mclock.AbsTime
}
type ipStatement struct {
endpoint string
endpoint netip.AddrPort
time mclock.AbsTime
}
@ -51,9 +52,9 @@ func NewIPTracker(window, contactWindow time.Duration, minStatements int) *IPTra
return &IPTracker{
window: window,
contactWindow: contactWindow,
statements: make(map[string]ipStatement),
statements: make(map[netip.Addr]ipStatement),
minStatements: minStatements,
contact: make(map[string]mclock.AbsTime),
contact: make(map[netip.Addr]mclock.AbsTime),
clock: mclock.System{},
}
}
@ -74,12 +75,15 @@ func (it *IPTracker) PredictFullConeNAT() bool {
}
// PredictEndpoint returns the current prediction of the external endpoint.
func (it *IPTracker) PredictEndpoint() string {
func (it *IPTracker) PredictEndpoint() netip.AddrPort {
it.gcStatements(it.clock.Now())
// The current strategy is simple: find the endpoint with most statements.
counts := make(map[string]int, len(it.statements))
maxcount, max := 0, ""
var (
counts = make(map[netip.AddrPort]int, len(it.statements))
maxcount int
max netip.AddrPort
)
for _, s := range it.statements {
c := counts[s.endpoint] + 1
counts[s.endpoint] = c
@ -91,7 +95,7 @@ func (it *IPTracker) PredictEndpoint() string {
}
// AddStatement records that a certain host thinks our external endpoint is the one given.
func (it *IPTracker) AddStatement(host, endpoint string) {
func (it *IPTracker) AddStatement(host netip.Addr, endpoint netip.AddrPort) {
now := it.clock.Now()
it.statements[host] = ipStatement{endpoint, now}
if time.Duration(now-it.lastStatementGC) >= it.window {
@ -101,7 +105,7 @@ func (it *IPTracker) AddStatement(host, endpoint string) {
// AddContact records that a packet containing our endpoint information has been sent to a
// certain host.
func (it *IPTracker) AddContact(host string) {
func (it *IPTracker) AddContact(host netip.Addr) {
now := it.clock.Now()
it.contact[host] = now
if time.Duration(now-it.lastContactGC) >= it.contactWindow {

View file

@ -19,6 +19,7 @@ package netutil
import (
crand "crypto/rand"
"fmt"
"net/netip"
"testing"
"time"
@ -42,37 +43,37 @@ func TestIPTracker(t *testing.T) {
tests := map[string][]iptrackTestEvent{
"minStatements": {
{opPredict, 0, "", ""},
{opStatement, 0, "127.0.0.1", "127.0.0.2"},
{opStatement, 0, "127.0.0.1:8000", "127.0.0.2"},
{opPredict, 1000, "", ""},
{opStatement, 1000, "127.0.0.1", "127.0.0.3"},
{opStatement, 1000, "127.0.0.1:8000", "127.0.0.3"},
{opPredict, 1000, "", ""},
{opStatement, 1000, "127.0.0.1", "127.0.0.4"},
{opPredict, 1000, "127.0.0.1", ""},
{opStatement, 1000, "127.0.0.1:8000", "127.0.0.4"},
{opPredict, 1000, "127.0.0.1:8000", ""},
},
"window": {
{opStatement, 0, "127.0.0.1", "127.0.0.2"},
{opStatement, 2000, "127.0.0.1", "127.0.0.3"},
{opStatement, 3000, "127.0.0.1", "127.0.0.4"},
{opPredict, 10000, "127.0.0.1", ""},
{opStatement, 0, "127.0.0.1:8000", "127.0.0.2"},
{opStatement, 2000, "127.0.0.1:8000", "127.0.0.3"},
{opStatement, 3000, "127.0.0.1:8000", "127.0.0.4"},
{opPredict, 10000, "127.0.0.1:8000", ""},
{opPredict, 10001, "", ""}, // first statement expired
{opStatement, 10100, "127.0.0.1", "127.0.0.2"},
{opPredict, 10200, "127.0.0.1", ""},
{opStatement, 10100, "127.0.0.1:8000", "127.0.0.2"},
{opPredict, 10200, "127.0.0.1:8000", ""},
},
"fullcone": {
{opContact, 0, "", "127.0.0.2"},
{opStatement, 10, "127.0.0.1", "127.0.0.2"},
{opStatement, 10, "127.0.0.1:8000", "127.0.0.2"},
{opContact, 2000, "", "127.0.0.3"},
{opStatement, 2010, "127.0.0.1", "127.0.0.3"},
{opStatement, 2010, "127.0.0.1:8000", "127.0.0.3"},
{opContact, 3000, "", "127.0.0.4"},
{opStatement, 3010, "127.0.0.1", "127.0.0.4"},
{opStatement, 3010, "127.0.0.1:8000", "127.0.0.4"},
{opCheckFullCone, 3500, "false", ""},
},
"fullcone_2": {
{opContact, 0, "", "127.0.0.2"},
{opStatement, 10, "127.0.0.1", "127.0.0.2"},
{opStatement, 10, "127.0.0.1:8000", "127.0.0.2"},
{opContact, 2000, "", "127.0.0.3"},
{opStatement, 2010, "127.0.0.1", "127.0.0.3"},
{opStatement, 3000, "127.0.0.1", "127.0.0.4"},
{opStatement, 2010, "127.0.0.1:8000", "127.0.0.3"},
{opStatement, 3000, "127.0.0.1:8000", "127.0.0.4"},
{opContact, 3010, "", "127.0.0.4"},
{opCheckFullCone, 3500, "true", ""},
},
@ -93,12 +94,19 @@ func runIPTrackerTest(t *testing.T, evs []iptrackTestEvent) {
clock.Run(evtime - time.Duration(clock.Now()))
switch ev.op {
case opStatement:
it.AddStatement(ev.from, ev.ip)
it.AddStatement(netip.MustParseAddr(ev.from), netip.MustParseAddrPort(ev.ip))
case opContact:
it.AddContact(ev.from)
it.AddContact(netip.MustParseAddr(ev.from))
case opPredict:
if pred := it.PredictEndpoint(); pred != ev.ip {
t.Errorf("op %d: wrong prediction %q, want %q", i, pred, ev.ip)
pred := it.PredictEndpoint()
if ev.ip == "" {
if pred.IsValid() {
t.Errorf("op %d: wrong prediction %v, expected invalid", i, pred)
}
} else {
if pred != netip.MustParseAddrPort(ev.ip) {
t.Errorf("op %d: wrong prediction %v, want %q", i, pred, ev.ip)
}
}
case opCheckFullCone:
pred := fmt.Sprintf("%t", it.PredictFullConeNAT())
@ -121,12 +129,11 @@ func TestIPTrackerForceGC(t *testing.T) {
it.clock = &clock
for i := 0; i < 5*max; i++ {
e1 := make([]byte, 4)
e2 := make([]byte, 4)
crand.Read(e1)
crand.Read(e2)
it.AddStatement(string(e1), string(e2))
it.AddContact(string(e1))
var e1, e2 [4]byte
crand.Read(e1[:])
crand.Read(e2[:])
it.AddStatement(netip.AddrFrom4(e1), netip.AddrPortFrom(netip.AddrFrom4(e2), 9000))
it.AddContact(netip.AddrFrom4(e1))
clock.Run(rate)
}
if len(it.contact) > 2*max {

View file

@ -22,21 +22,19 @@ import (
"errors"
"fmt"
"net"
"sort"
"net/netip"
"slices"
"strings"
"golang.org/x/exp/maps"
)
var lan4, lan6, special4, special6 Netlist
var special4, special6 Netlist
func init() {
// Lists from RFC 5735, RFC 5156,
// https://www.iana.org/assignments/iana-ipv4-special-registry/
lan4.Add("0.0.0.0/8") // "This" network
lan4.Add("10.0.0.0/8") // Private Use
lan4.Add("172.16.0.0/12") // Private Use
lan4.Add("192.168.0.0/16") // Private Use
lan6.Add("fe80::/10") // Link-Local
lan6.Add("fc00::/7") // Unique-Local
special4.Add("0.0.0.0/8") // "This" network.
special4.Add("192.0.0.0/29") // IPv4 Service Continuity
special4.Add("192.0.0.9/32") // PCP Anycast
special4.Add("192.0.0.170/32") // NAT64/DNS64 Discovery
@ -66,7 +64,7 @@ func init() {
}
// Netlist is a list of IP networks.
type Netlist []net.IPNet
type Netlist []netip.Prefix
// ParseNetlist parses a comma-separated list of CIDR masks.
// Whitespace and extra commas are ignored.
@ -78,11 +76,11 @@ func ParseNetlist(s string) (*Netlist, error) {
if mask == "" {
continue
}
_, n, err := net.ParseCIDR(mask)
prefix, err := netip.ParsePrefix(mask)
if err != nil {
return nil, err
}
l = append(l, *n)
l = append(l, prefix)
}
return &l, nil
}
@ -103,11 +101,11 @@ func (l *Netlist) UnmarshalTOML(fn func(interface{}) error) error {
return err
}
for _, mask := range masks {
_, n, err := net.ParseCIDR(mask)
prefix, err := netip.ParsePrefix(mask)
if err != nil {
return err
}
*l = append(*l, *n)
*l = append(*l, prefix)
}
return nil
}
@ -115,15 +113,20 @@ func (l *Netlist) UnmarshalTOML(fn func(interface{}) error) error {
// Add parses a CIDR mask and appends it to the list. It panics for invalid masks and is
// intended to be used for setting up static lists.
func (l *Netlist) Add(cidr string) {
_, n, err := net.ParseCIDR(cidr)
prefix, err := netip.ParsePrefix(cidr)
if err != nil {
panic(err)
}
*l = append(*l, *n)
*l = append(*l, prefix)
}
// Contains reports whether the given IP is contained in the list.
func (l *Netlist) Contains(ip net.IP) bool {
return l.ContainsAddr(IPToAddr(ip))
}
// ContainsAddr reports whether the given IP is contained in the list.
func (l *Netlist) ContainsAddr(ip netip.Addr) bool {
if l == nil {
return false
}
@ -137,25 +140,39 @@ func (l *Netlist) Contains(ip net.IP) bool {
// IsLAN reports whether an IP is a local network address.
func IsLAN(ip net.IP) bool {
return AddrIsLAN(IPToAddr(ip))
}
// AddrIsLAN reports whether an IP is a local network address.
func AddrIsLAN(ip netip.Addr) bool {
if ip.Is4In6() {
ip = netip.AddrFrom4(ip.As4())
}
if ip.IsLoopback() {
return true
}
if v4 := ip.To4(); v4 != nil {
return lan4.Contains(v4)
}
return lan6.Contains(ip)
return ip.IsPrivate() || ip.IsLinkLocalUnicast()
}
// IsSpecialNetwork reports whether an IP is located in a special-use network range
// This includes broadcast, multicast and documentation addresses.
func IsSpecialNetwork(ip net.IP) bool {
return AddrIsSpecialNetwork(IPToAddr(ip))
}
// AddrIsSpecialNetwork reports whether an IP is located in a special-use network range
// This includes broadcast, multicast and documentation addresses.
func AddrIsSpecialNetwork(ip netip.Addr) bool {
if ip.Is4In6() {
ip = netip.AddrFrom4(ip.As4())
}
if ip.IsMulticast() {
return true
}
if v4 := ip.To4(); v4 != nil {
return special4.Contains(v4)
if ip.Is4() {
return special4.ContainsAddr(ip)
}
return special6.Contains(ip)
return special6.ContainsAddr(ip)
}
var (
@ -175,19 +192,31 @@ var (
// - LAN addresses are OK if relayed by a LAN host.
// - All other addresses are always acceptable.
func CheckRelayIP(sender, addr net.IP) error {
if len(addr) != net.IPv4len && len(addr) != net.IPv6len {
return CheckRelayAddr(IPToAddr(sender), IPToAddr(addr))
}
// CheckRelayAddr reports whether an IP relayed from the given sender IP
// is a valid connection target.
//
// There are four rules:
// - Special network addresses are never valid.
// - Loopback addresses are OK if relayed by a loopback host.
// - LAN addresses are OK if relayed by a LAN host.
// - All other addresses are always acceptable.
func CheckRelayAddr(sender, addr netip.Addr) error {
if !addr.IsValid() {
return errInvalid
}
if addr.IsUnspecified() {
return errUnspecified
}
if IsSpecialNetwork(addr) {
if AddrIsSpecialNetwork(addr) {
return errSpecial
}
if addr.IsLoopback() && !sender.IsLoopback() {
return errLoopback
}
if IsLAN(addr) && !IsLAN(sender) {
if AddrIsLAN(addr) && !AddrIsLAN(sender) {
return errLAN
}
return nil
@ -221,17 +250,22 @@ type DistinctNetSet struct {
Subnet uint // number of common prefix bits
Limit uint // maximum number of IPs in each subnet
members map[string]uint
buf net.IP
members map[netip.Prefix]uint
}
// Add adds an IP address to the set. It returns false (and doesn't add the IP) if the
// number of existing IPs in the defined range exceeds the limit.
func (s *DistinctNetSet) Add(ip net.IP) bool {
return s.AddAddr(IPToAddr(ip))
}
// AddAddr adds an IP address to the set. It returns false (and doesn't add the IP) if the
// number of existing IPs in the defined range exceeds the limit.
func (s *DistinctNetSet) AddAddr(ip netip.Addr) bool {
key := s.key(ip)
n := s.members[string(key)]
n := s.members[key]
if n < s.Limit {
s.members[string(key)] = n + 1
s.members[key] = n + 1
return true
}
return false
@ -239,20 +273,30 @@ func (s *DistinctNetSet) Add(ip net.IP) bool {
// Remove removes an IP from the set.
func (s *DistinctNetSet) Remove(ip net.IP) {
s.RemoveAddr(IPToAddr(ip))
}
// RemoveAddr removes an IP from the set.
func (s *DistinctNetSet) RemoveAddr(ip netip.Addr) {
key := s.key(ip)
if n, ok := s.members[string(key)]; ok {
if n, ok := s.members[key]; ok {
if n == 1 {
delete(s.members, string(key))
delete(s.members, key)
} else {
s.members[string(key)] = n - 1
s.members[key] = n - 1
}
}
}
// Contains whether the given IP is contained in the set.
// Contains reports whether the given IP is contained in the set.
func (s DistinctNetSet) Contains(ip net.IP) bool {
return s.ContainsAddr(IPToAddr(ip))
}
// ContainsAddr reports whether the given IP is contained in the set.
func (s DistinctNetSet) ContainsAddr(ip netip.Addr) bool {
key := s.key(ip)
_, ok := s.members[string(key)]
_, ok := s.members[key]
return ok
}
@ -265,54 +309,30 @@ func (s DistinctNetSet) Len() int {
return int(n)
}
// key encodes the map key for an address into a temporary buffer.
//
// The first byte of key is '4' or '6' to distinguish IPv4/IPv6 address types.
// The remainder of the key is the IP, truncated to the number of bits.
func (s *DistinctNetSet) key(ip net.IP) net.IP {
// key returns the map key for ip.
func (s *DistinctNetSet) key(ip netip.Addr) netip.Prefix {
// Lazily initialize storage.
if s.members == nil {
s.members = make(map[string]uint)
s.buf = make(net.IP, 17)
s.members = make(map[netip.Prefix]uint)
}
// Canonicalize ip and bits.
typ := byte('6')
if ip4 := ip.To4(); ip4 != nil {
typ, ip = '4', ip4
p, err := ip.Prefix(int(s.Subnet))
if err != nil {
panic(err)
}
bits := s.Subnet
if bits > uint(len(ip)*8) {
bits = uint(len(ip) * 8)
}
// Encode the prefix into s.buf.
nb := int(bits / 8)
mask := ^byte(0xFF >> (bits % 8))
s.buf[0] = typ
buf := append(s.buf[:1], ip[:nb]...)
if nb < len(ip) && mask != 0 {
buf = append(buf, ip[nb]&mask)
}
return buf
return p
}
// String implements fmt.Stringer
func (s DistinctNetSet) String() string {
keys := maps.Keys(s.members)
slices.SortFunc(keys, func(a, b netip.Prefix) int {
return strings.Compare(a.String(), b.String())
})
var buf bytes.Buffer
buf.WriteString("{")
keys := make([]string, 0, len(s.members))
for k := range s.members {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
var ip net.IP
if k[0] == '4' {
ip = make(net.IP, 4)
} else {
ip = make(net.IP, 16)
}
copy(ip, k[1:])
fmt.Fprintf(&buf, "%v×%d", ip, s.members[k])
fmt.Fprintf(&buf, "%v×%d", k, s.members[k])
if i != len(keys)-1 {
buf.WriteString(" ")
}

View file

@ -18,7 +18,9 @@ package netutil
import (
"fmt"
"math/rand"
"net"
"net/netip"
"reflect"
"testing"
"testing/quick"
@ -29,7 +31,7 @@ import (
func TestParseNetlist(t *testing.T) {
var tests = []struct {
input string
wantErr error
wantErr string
wantList *Netlist
}{
{
@ -38,25 +40,27 @@ func TestParseNetlist(t *testing.T) {
},
{
input: "127.0.0.0/8",
wantErr: nil,
wantList: &Netlist{{IP: net.IP{127, 0, 0, 0}, Mask: net.CIDRMask(8, 32)}},
wantList: &Netlist{netip.MustParsePrefix("127.0.0.0/8")},
},
{
input: "127.0.0.0/44",
wantErr: &net.ParseError{Type: "CIDR address", Text: "127.0.0.0/44"},
wantErr: `netip.ParsePrefix("127.0.0.0/44"): prefix length out of range`,
},
{
input: "127.0.0.0/16, 23.23.23.23/24,",
wantList: &Netlist{
{IP: net.IP{127, 0, 0, 0}, Mask: net.CIDRMask(16, 32)},
{IP: net.IP{23, 23, 23, 0}, Mask: net.CIDRMask(24, 32)},
netip.MustParsePrefix("127.0.0.0/16"),
netip.MustParsePrefix("23.23.23.23/24"),
},
},
}
for _, test := range tests {
l, err := ParseNetlist(test.input)
if !reflect.DeepEqual(err, test.wantErr) {
if err == nil && test.wantErr != "" {
t.Errorf("%q: got no error, expected %q", test.input, test.wantErr)
continue
} else if err != nil && err.Error() != test.wantErr {
t.Errorf("%q: got error %q, want %q", test.input, err, test.wantErr)
continue
}
@ -70,14 +74,12 @@ func TestParseNetlist(t *testing.T) {
func TestNilNetListContains(t *testing.T) {
var list *Netlist
checkContains(t, list.Contains, nil, []string{"1.2.3.4"})
checkContains(t, list.Contains, list.ContainsAddr, nil, []string{"1.2.3.4"})
}
func TestIsLAN(t *testing.T) {
checkContains(t, IsLAN,
checkContains(t, IsLAN, AddrIsLAN,
[]string{ // included
"0.0.0.0",
"0.2.0.8",
"127.0.0.1",
"10.0.1.1",
"10.22.0.3",
@ -86,25 +88,35 @@ func TestIsLAN(t *testing.T) {
"fe80::f4a1:8eff:fec5:9d9d",
"febf::ab32:2233",
"fc00::4",
// 4-in-6
"::ffff:127.0.0.1",
"::ffff:10.10.0.2",
},
[]string{ // excluded
"192.0.2.1",
"1.0.0.0",
"172.32.0.1",
"fec0::2233",
// 4-in-6
"::ffff:88.99.100.2",
},
)
}
func TestIsSpecialNetwork(t *testing.T) {
checkContains(t, IsSpecialNetwork,
checkContains(t, IsSpecialNetwork, AddrIsSpecialNetwork,
[]string{ // included
"0.0.0.0",
"0.2.0.8",
"192.0.2.1",
"192.0.2.44",
"2001:db8:85a3:8d3:1319:8a2e:370:7348",
"255.255.255.255",
"224.0.0.22", // IPv4 multicast
"ff05::1:3", // IPv6 multicast
// 4-in-6
"::ffff:255.255.255.255",
"::ffff:192.0.2.1",
},
[]string{ // excluded
"192.0.3.1",
@ -115,15 +127,21 @@ func TestIsSpecialNetwork(t *testing.T) {
)
}
func checkContains(t *testing.T, fn func(net.IP) bool, inc, exc []string) {
func checkContains(t *testing.T, fn func(net.IP) bool, fn2 func(netip.Addr) bool, inc, exc []string) {
for _, s := range inc {
if !fn(parseIP(s)) {
t.Error("returned false for included address", s)
t.Error("returned false for included net.IP", s)
}
if !fn2(netip.MustParseAddr(s)) {
t.Error("returned false for included netip.Addr", s)
}
}
for _, s := range exc {
if fn(parseIP(s)) {
t.Error("returned true for excluded address", s)
t.Error("returned true for excluded net.IP", s)
}
if fn2(netip.MustParseAddr(s)) {
t.Error("returned true for excluded netip.Addr", s)
}
}
}
@ -244,14 +262,22 @@ func TestDistinctNetSet(t *testing.T) {
}
func TestDistinctNetSetAddRemove(t *testing.T) {
cfg := &quick.Config{}
fn := func(ips []net.IP) bool {
cfg := &quick.Config{
Values: func(s []reflect.Value, rng *rand.Rand) {
slice := make([]netip.Addr, rng.Intn(20)+1)
for i := range slice {
slice[i] = RandomAddr(rng, false)
}
s[0] = reflect.ValueOf(slice)
},
}
fn := func(ips []netip.Addr) bool {
s := DistinctNetSet{Limit: 3, Subnet: 2}
for _, ip := range ips {
s.Add(ip)
s.AddAddr(ip)
}
for _, ip := range ips {
s.Remove(ip)
s.RemoveAddr(ip)
}
return s.Len() == 0
}

View file

@ -19,6 +19,7 @@ package p2p
import (
"bytes"
"cmp"
"crypto/ecdsa"
"encoding/hex"
"errors"
@ -905,14 +906,14 @@ func (srv *Server) listenLoop() {
break
}
remoteIP := netutil.AddrIP(fd.RemoteAddr())
remoteIP := netutil.AddrAddr(fd.RemoteAddr())
if err := srv.checkInboundConn(remoteIP); err != nil {
srv.log.Debug("Rejected inbound connection", "addr", fd.RemoteAddr(), "err", err)
fd.Close()
slots <- struct{}{}
continue
}
if remoteIP != nil {
if remoteIP.IsValid() {
fd = newMeteredConn(fd)
serveMeter.Mark(1)
srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
@ -924,18 +925,19 @@ func (srv *Server) listenLoop() {
}
}
func (srv *Server) checkInboundConn(remoteIP net.IP) error {
if remoteIP == nil {
func (srv *Server) checkInboundConn(remoteIP netip.Addr) error {
if !remoteIP.IsValid() {
// This case happens for internal test connections without remote address.
return nil
}
// Reject connections that do not match NetRestrict.
if srv.NetRestrict != nil && !srv.NetRestrict.Contains(remoteIP) {
if srv.NetRestrict != nil && !srv.NetRestrict.ContainsAddr(remoteIP) {
return errors.New("not in netrestrict list")
}
// 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()) {
if !netutil.AddrIsLAN(remoteIP) && srv.inboundHistory.contains(remoteIP.String()) {
return errors.New("too many attempts")
}
srv.inboundHistory.add(remoteIP.String(), now.Add(inboundThrottleTime))
@ -1108,7 +1110,7 @@ func (srv *Server) NodeInfo() *NodeInfo {
Name: srv.Name,
Enode: node.URLv4(),
ID: node.ID().String(),
IP: node.IP().String(),
IP: node.IPAddr().String(),
ListenAddr: srv.ListenAddr,
Protocols: make(map[string]interface{}),
}
@ -1139,12 +1141,9 @@ func (srv *Server) PeersInfo() []*PeerInfo {
}
}
// Sort the result array alphabetically by node identifier
for i := 0; i < len(infos); i++ {
for j := i + 1; j < len(infos); j++ {
if infos[i].ID > infos[j].ID {
infos[i], infos[j] = infos[j], infos[i]
}
}
}
slices.SortFunc(infos, func(a, b *PeerInfo) int {
return cmp.Compare(a.ID, b.ID)
})
return infos
}

View file

@ -18,6 +18,7 @@ package p2p
import (
"net"
"net/netip"
"sync/atomic"
"testing"
"time"
@ -64,8 +65,8 @@ func TestServerPortMapping(t *testing.T) {
t.Error("wrong request count:", reqCount)
}
enr := srv.LocalNode().Node()
if enr.IP().String() != "192.0.2.0" {
t.Error("wrong IP in ENR:", enr.IP())
if enr.IPAddr() != netip.MustParseAddr("192.0.2.0") {
t.Error("wrong IP in ENR:", enr.IPAddr())
}
if enr.TCP() != 30000 {
t.Error("wrong TCP port in ENR:", enr.TCP())

View file

@ -123,20 +123,25 @@ The API is initialised with a particular node adapter and has the following
endpoints:
```
GET / Get network information
POST /start Start all nodes in the network
POST /stop Stop all nodes in the network
GET /events Stream network events
GET /snapshot Take a network snapshot
POST /snapshot Load a network snapshot
POST /nodes Create a node
GET /nodes Get all nodes in the network
GET /nodes/:nodeid Get node information
POST /nodes/:nodeid/start Start a node
POST /nodes/:nodeid/stop Stop a node
POST /nodes/:nodeid/conn/:peerid Connect two nodes
DELETE /nodes/:nodeid/conn/:peerid Disconnect two nodes
GET /nodes/:nodeid/rpc Make RPC requests to a node via WebSocket
OPTIONS / Response 200 with "Access-Control-Allow-Headers"" header set to "Content-Type""
GET / Get network information
POST /start Start all nodes in the network
POST /stop Stop all nodes in the network
POST /mocker/start Start the mocker node simulation
POST /mocker/stop Stop the mocker node simulation
GET /mocker Get a list of available mockers
POST /reset Reset all properties of a network to initial (empty) state
GET /events Stream network events
GET /snapshot Take a network snapshot
POST /snapshot Load a network snapshot
POST /nodes Create a node
GET /nodes Get all nodes in the network
GET /nodes/:nodeid Get node information
POST /nodes/:nodeid/start Start a node
POST /nodes/:nodeid/stop Stop a node
POST /nodes/:nodeid/conn/:peerid Connect two nodes
DELETE /nodes/:nodeid/conn/:peerid Disconnect two nodes
GET /nodes/:nodeid/rpc Make RPC requests to a node via WebSocket
```
For convenience, `nodeid` in the URL can be the name of a node rather than its

View file

@ -23,7 +23,7 @@ import (
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 14 // Minor version component of the current release
VersionPatch = 4 // Patch version component of the current release
VersionPatch = 6 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string
)

View file

@ -30,33 +30,33 @@ var rawValueType = reflect.TypeOf(RawValue{})
// StringSize returns the encoded size of a string.
func StringSize(s string) uint64 {
switch {
case len(s) == 0:
switch n := len(s); n {
case 0:
return 1
case len(s) == 1:
case 1:
if s[0] <= 0x7f {
return 1
} else {
return 2
}
default:
return uint64(headsize(uint64(len(s))) + len(s))
return uint64(headsize(uint64(n)) + n)
}
}
// BytesSize returns the encoded size of a byte slice.
func BytesSize(b []byte) uint64 {
switch {
case len(b) == 0:
switch n := len(b); n {
case 0:
return 1
case len(b) == 1:
case 1:
if b[0] <= 0x7f {
return 1
} else {
return 2
}
default:
return uint64(headsize(uint64(len(b))) + len(b))
return uint64(headsize(uint64(n)) + n)
}
}
@ -105,18 +105,20 @@ func SplitUint64(b []byte) (x uint64, rest []byte, err error) {
if err != nil {
return 0, b, err
}
switch {
case len(content) == 0:
switch n := len(content); n {
case 0:
return 0, rest, nil
case len(content) == 1:
case 1:
if content[0] == 0 {
return 0, b, ErrCanonInt
}
return uint64(content[0]), rest, nil
case len(content) > 8:
return 0, b, errUintOverflow
default:
x, err = readSize(content, byte(len(content)))
if n > 8 {
return 0, b, errUintOverflow
}
x, err = readSize(content, byte(n))
if err != nil {
return 0, b, ErrCanonInt
}