diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 41e9631f15..844cfb5d24 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -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: diff --git a/build/checksums.txt b/build/checksums.txt index 94cac9738c..d099e53156 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -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/ diff --git a/cmd/clef/README.md b/cmd/clef/README.md index cf09265136..b7018a5f41 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -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 diff --git a/cmd/devp2p/internal/ethtest/conn.go b/cmd/devp2p/internal/ethtest/conn.go index ba3c0585fd..757b137aa1 100644 --- a/cmd/devp2p/internal/ethtest/conn.go +++ b/cmd/devp2p/internal/ethtest/conn.go @@ -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 } diff --git a/cmd/devp2p/internal/v4test/framework.go b/cmd/devp2p/internal/v4test/framework.go index df1f1f8aba..958fb71179 100644 --- a/cmd/devp2p/internal/v4test/framework.go +++ b/cmd/devp2p/internal/v4test/framework.go @@ -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 { diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index 6fbc185ad8..f0773edfb8 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -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 } diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index aa7d065818..77f09e6b85 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -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 } diff --git a/cmd/evm/README.md b/cmd/evm/README.md index 25647c18a9..f95b6b4d7b 100644 --- a/cmd/evm/README.md +++ b/cmd/evm/README.md @@ -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 diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 8fd068b2ea..fa052f5954 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -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 { diff --git a/cmd/evm/internal/t8ntool/tx_iterator.go b/cmd/evm/internal/t8ntool/tx_iterator.go index 046f62314d..d4ebb4b399 100644 --- a/cmd/evm/internal/t8ntool/tx_iterator.go +++ b/cmd/evm/internal/t8ntool/tx_iterator.go @@ -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) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index d787f340a3..2965b99d94 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -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 } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b7885608bc..f6bb09ee54 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -156,6 +156,7 @@ var ( utils.BeaconGenesisRootFlag, utils.BeaconGenesisTimeFlag, utils.BeaconCheckpointFlag, + utils.CollectWitnessFlag, }, utils.NetworkFlags, utils.DatabaseFlags) rpcFlags = []cli.Flag{ diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index cf7093e605..7d713ad110 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -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 } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ecf6acc186..46d380b984 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -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 diff --git a/core/blockchain.go b/core/blockchain.go index 7c8ab3abc4..ac4eb1c47e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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 diff --git a/core/genesis.go b/core/genesis.go index f1b4490f2e..4ca24807fc 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -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 { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 025be7ade7..c4735c850c 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -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. diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index fdc940b57e..2d30af4b3d 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -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) diff --git a/core/state/access_list.go b/core/state/access_list.go index b0effbeadc..90e5590748 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -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 diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 54614427a5..daa8cdcc54 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -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. // diff --git a/core/state/state_object.go b/core/state/state_object.go index b7a215bd17..16a5f3e5e2 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -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) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 61e76cdd77..b4ef0a6e47 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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) } } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index ce94ab5139..491b3807c8 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -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: diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go index 478407dfbb..8f01acd221 100644 --- a/core/state/trie_prefetcher_test.go +++ b/core/state/trie_prefetcher_test.go @@ -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 { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 66a20f434e..2b1ea38483 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -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, diff --git a/eth/backend.go b/eth/backend.go index 798ffa600b..91a07811f0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index f36f212d9c..7453fb1efd 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -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 diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index b8b9eee294..147a559984 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -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 } diff --git a/eth/tracers/internal/tracetest/supply_test.go b/eth/tracers/internal/tracetest/supply_test.go index 2d4f1b0890..d608b1e002 100644 --- a/eth/tracers/internal/tracetest/supply_test.go +++ b/eth/tracers/internal/tracetest/supply_test.go @@ -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. diff --git a/eth/tracers/live/supply.go b/eth/tracers/live/supply.go index 0c9141e99d..936ffb9472 100644 --- a/eth/tracers/live/supply.go +++ b/eth/tracers/live/supply.go @@ -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) diff --git a/go.mod b/go.mod index 8968140fbf..9947df31f3 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/p2p/dial.go b/p2p/dial.go index 08e1db2877..24d4dc2e89 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -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 { diff --git a/p2p/discover/table.go b/p2p/discover/table.go index bd3c9b4143..8045f1389e 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -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() diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 30e7d56f4a..2f1797d1e2 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -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, diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 997ac37799..5b2699d460 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -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) { diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index 3880ca34a7..cca01bd3ce 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -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 { diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 28a6fb8675..9d6df08ead 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -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()) diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index 9ba54b3d40..81d94812aa 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -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) diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go index 27966f2afc..8dd02620eb 100644 --- a/p2p/discover/v5wire/encoding_test.go +++ b/p2p/discover/v5wire/encoding_test.go @@ -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 { diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go index a18204e752..6e79c9cbdc 100644 --- a/p2p/enode/localnode.go +++ b/p2p/enode/localnode.go @@ -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() { diff --git a/p2p/enode/localnode_test.go b/p2p/enode/localnode_test.go index 7f97ad392f..86b962a74e 100644 --- a/p2p/enode/localnode_test.go +++ b/p2p/enode/localnode_test.go @@ -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()) diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 654d71d47b..1f31c98d22 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -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 { diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go index 38764f31b1..bc0291665d 100644 --- a/p2p/enode/nodedb_test.go +++ b/p2p/enode/nodedb_test.go @@ -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() } diff --git a/p2p/netutil/addrutil.go b/p2p/netutil/addrutil.go index fb6d8d2731..b8b318571b 100644 --- a/p2p/netutil/addrutil.go +++ b/p2p/netutil/addrutil.go @@ -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 +} diff --git a/p2p/netutil/iptrack.go b/p2p/netutil/iptrack.go index a070499e19..5140ac7539 100644 --- a/p2p/netutil/iptrack.go +++ b/p2p/netutil/iptrack.go @@ -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 { diff --git a/p2p/netutil/iptrack_test.go b/p2p/netutil/iptrack_test.go index ee3bba861e..81653a2733 100644 --- a/p2p/netutil/iptrack_test.go +++ b/p2p/netutil/iptrack_test.go @@ -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 { diff --git a/p2p/netutil/net.go b/p2p/netutil/net.go index d5da3c694f..7d8da88670 100644 --- a/p2p/netutil/net.go +++ b/p2p/netutil/net.go @@ -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(" ") } diff --git a/p2p/netutil/net_test.go b/p2p/netutil/net_test.go index 3a6aa081f2..569c7ac454 100644 --- a/p2p/netutil/net_test.go +++ b/p2p/netutil/net_test.go @@ -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 } diff --git a/p2p/server.go b/p2p/server.go index 13eebed3f4..172f0667eb 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -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 } diff --git a/p2p/server_nat_test.go b/p2p/server_nat_test.go index de935fcfc5..cbb1f37e0a 100644 --- a/p2p/server_nat_test.go +++ b/p2p/server_nat_test.go @@ -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()) diff --git a/p2p/simulations/README.md b/p2p/simulations/README.md index 023f73a098..1f9f72dcda 100644 --- a/p2p/simulations/README.md +++ b/p2p/simulations/README.md @@ -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 diff --git a/params/version.go b/params/version.go index a0e2de5a49..48bca3c5b2 100644 --- a/params/version.go +++ b/params/version.go @@ -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 ) diff --git a/rlp/raw.go b/rlp/raw.go index 773aa7e614..879e3bfe5d 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -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 }