diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index faf922df01..0dabaf4df5 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -10,6 +10,7 @@ core/ @karalabe @holiman @rjl493456442
eth/ @karalabe @holiman @rjl493456442
eth/catalyst/ @gballet
eth/tracers/ @s1na
+core/tracing/ @s1na
graphql/ @s1na
les/ @zsfelfoldi @rjl493456442
light/ @zsfelfoldi @rjl493456442
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/.golangci.yml b/.golangci.yml
index 0343c4b4eb..2132f5403a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -6,8 +6,6 @@ run:
# default is true. Enables skipping of directories:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs-use-default: true
- skip-files:
- - core/genesis_alloc.go
linters:
disable-all: true
@@ -25,7 +23,10 @@ linters:
- durationcheck
- exportloopref
- whitespace
+ - revive # only certain checks enabled
+ ### linters we tried and will not be using:
+ ###
# - structcheck # lots of false positives
# - errcheck #lot of false positives
# - contextcheck
@@ -38,13 +39,27 @@ linters:
linters-settings:
gofmt:
simplify: true
+ revive:
+ enable-all-rules: false
+ # here we enable specific useful rules
+ # see https://golangci-lint.run/usage/linters/#revive for supported rules
+ rules:
+ - name: receiver-naming
+ severity: warning
+ disabled: false
+ exclude: [""]
issues:
+ exclude-files:
+ - core/genesis_alloc.go
exclude-rules:
- path: crypto/bn256/cloudflare/optate.go
linters:
- deadcode
- staticcheck
+ - path: crypto/bn256/
+ linters:
+ - revive
- path: internal/build/pgp.go
text: 'SA1019: "golang.org/x/crypto/openpgp" is deprecated: this package is unmaintained except for security fixes.'
- path: core/vm/contracts.go
diff --git a/accounts/abi/type.go b/accounts/abi/type.go
index 3839826633..d57fa3d4e6 100644
--- a/accounts/abi/type.go
+++ b/accounts/abi/type.go
@@ -64,6 +64,9 @@ type Type struct {
var (
// typeRegex parses the abi sub types
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
+
+ // sliceSizeRegex grab the slice size
+ sliceSizeRegex = regexp.MustCompile("[0-9]+")
)
// NewType creates a new reflection type of abi type given in t.
@@ -91,8 +94,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
// grab the last cell and create a type from there
sliced := t[i:]
// grab the slice size with regexp
- re := regexp.MustCompile("[0-9]+")
- intz := re.FindAllString(sliced, -1)
+ intz := sliceSizeRegex.FindAllString(sliced, -1)
if len(intz) == 0 {
// is a slice
diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go
index f0ca9085b6..58cfc88301 100644
--- a/accounts/scwallet/wallet.go
+++ b/accounts/scwallet/wallet.go
@@ -73,6 +73,14 @@ var (
DerivationSignatureHash = sha256.Sum256(common.Hash{}.Bytes())
)
+var (
+ // PinRegexp is the regular expression used to validate PIN codes.
+ pinRegexp = regexp.MustCompile(`^[0-9]{6,}$`)
+
+ // PukRegexp is the regular expression used to validate PUK codes.
+ pukRegexp = regexp.MustCompile(`^[0-9]{12,}$`)
+)
+
// List of APDU command-related constants
const (
claISO7816 = 0
@@ -380,7 +388,7 @@ func (w *Wallet) Open(passphrase string) error {
case passphrase == "":
return ErrPINUnblockNeeded
case status.PinRetryCount > 0:
- if !regexp.MustCompile(`^[0-9]{6,}$`).MatchString(passphrase) {
+ if !pinRegexp.MatchString(passphrase) {
w.log.Error("PIN needs to be at least 6 digits")
return ErrPINNeeded
}
@@ -388,7 +396,7 @@ func (w *Wallet) Open(passphrase string) error {
return err
}
default:
- if !regexp.MustCompile(`^[0-9]{12,}$`).MatchString(passphrase) {
+ if !pukRegexp.MatchString(passphrase) {
w.log.Error("PUK needs to be at least 12 digits")
return ErrPINUnblockNeeded
}
diff --git a/beacon/engine/types.go b/beacon/engine/types.go
index a73691ca05..1dfcf5b71a 100644
--- a/beacon/engine/types.go
+++ b/beacon/engine/types.go
@@ -209,7 +209,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash,
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
}
- var blobHashes []common.Hash
+ var blobHashes = make([]common.Hash, 0, len(txs))
for _, tx := range txs {
blobHashes = append(blobHashes, tx.BlobHashes()...)
}
diff --git a/beacon/light/api/light_api.go b/beacon/light/api/light_api.go
index 903db57344..6f60fc0cc6 100755
--- a/beacon/light/api/light_api.go
+++ b/beacon/light/api/light_api.go
@@ -494,9 +494,6 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
for {
select {
- case <-ctx.Done():
- stream.Close()
-
case event, ok := <-stream.Events:
if !ok {
log.Trace("Event stream closed")
diff --git a/beacon/light/request/server.go b/beacon/light/request/server.go
index 9f3b09b81e..a06dec99ae 100644
--- a/beacon/light/request/server.go
+++ b/beacon/light/request/server.go
@@ -186,10 +186,14 @@ func (s *serverWithTimeout) eventCallback(event Event) {
// call will just do nothing
timer.Stop()
delete(s.timeouts, id)
- s.childEventCb(event)
+ if s.childEventCb != nil {
+ s.childEventCb(event)
+ }
}
default:
- s.childEventCb(event)
+ if s.childEventCb != nil {
+ s.childEventCb(event)
+ }
}
}
@@ -211,25 +215,27 @@ func (s *serverWithTimeout) startTimeout(reqData RequestResponse) {
delete(s.timeouts, id)
childEventCb := s.childEventCb
s.lock.Unlock()
- childEventCb(Event{Type: EvFail, Data: reqData})
+ if childEventCb != nil {
+ childEventCb(Event{Type: EvFail, Data: reqData})
+ }
})
childEventCb := s.childEventCb
s.lock.Unlock()
- childEventCb(Event{Type: EvTimeout, Data: reqData})
+ if childEventCb != nil {
+ childEventCb(Event{Type: EvTimeout, Data: reqData})
+ }
})
}
// unsubscribe stops all goroutines associated with the server.
func (s *serverWithTimeout) unsubscribe() {
s.lock.Lock()
- defer s.lock.Unlock()
-
for _, timer := range s.timeouts {
if timer != nil {
timer.Stop()
}
}
- s.childEventCb = nil
+ s.lock.Unlock()
s.parent.Unsubscribe()
}
@@ -328,10 +334,10 @@ func (s *serverWithLimits) eventCallback(event Event) {
}
childEventCb := s.childEventCb
s.lock.Unlock()
- if passEvent {
+ if passEvent && childEventCb != nil {
childEventCb(event)
}
- if sendCanRequestAgain {
+ if sendCanRequestAgain && childEventCb != nil {
childEventCb(Event{Type: EvCanRequestAgain})
}
}
@@ -347,13 +353,12 @@ func (s *serverWithLimits) sendRequest(request Request) (reqId ID) {
// unsubscribe stops all goroutines associated with the server.
func (s *serverWithLimits) unsubscribe() {
s.lock.Lock()
- defer s.lock.Unlock()
-
if s.delayTimer != nil {
s.delayTimer.Stop()
s.delayTimer = nil
}
s.childEventCb = nil
+ s.lock.Unlock()
s.serverWithTimeout.unsubscribe()
}
@@ -383,7 +388,7 @@ func (s *serverWithLimits) canRequestNow() bool {
}
childEventCb := s.childEventCb
s.lock.Unlock()
- if sendCanRequestAgain {
+ if sendCanRequestAgain && childEventCb != nil {
childEventCb(Event{Type: EvCanRequestAgain})
}
return canRequest
@@ -415,7 +420,7 @@ func (s *serverWithLimits) delay(delay time.Duration) {
}
childEventCb := s.childEventCb
s.lock.Unlock()
- if sendCanRequestAgain {
+ if sendCanRequestAgain && childEventCb != nil {
childEventCb(Event{Type: EvCanRequestAgain})
}
})
diff --git a/beacon/light/request/server_test.go b/beacon/light/request/server_test.go
index 38629cb8c4..fef5d062ea 100644
--- a/beacon/light/request/server_test.go
+++ b/beacon/light/request/server_test.go
@@ -51,6 +51,7 @@ func TestServerEvents(t *testing.T) {
expEvent(EvFail)
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
expEvent(nil)
+ srv.unsubscribe()
}
func TestServerParallel(t *testing.T) {
@@ -129,9 +130,7 @@ func TestServerEventRateLimit(t *testing.T) {
srv := NewServer(rs, clock)
var eventCount int
srv.subscribe(func(event Event) {
- if !event.IsRequestEvent() {
- eventCount++
- }
+ eventCount++
})
expEvents := func(send, expAllowed int) {
eventCount = 0
@@ -147,6 +146,30 @@ func TestServerEventRateLimit(t *testing.T) {
expEvents(5, 1)
clock.Run(maxServerEventRate * maxServerEventBuffer * 2)
expEvents(maxServerEventBuffer+5, maxServerEventBuffer)
+ srv.unsubscribe()
+}
+
+func TestServerUnsubscribe(t *testing.T) {
+ rs := &testRequestServer{}
+ clock := &mclock.Simulated{}
+ srv := NewServer(rs, clock)
+ var eventCount int
+ srv.subscribe(func(event Event) {
+ eventCount++
+ })
+ eventCb := rs.eventCb
+ eventCb(Event{Type: testEventType})
+ if eventCount != 1 {
+ t.Errorf("Server event callback not called before unsubscribe")
+ }
+ srv.unsubscribe()
+ if rs.eventCb != nil {
+ t.Errorf("Server event callback not removed after unsubscribe")
+ }
+ eventCb(Event{Type: testEventType})
+ if eventCount != 1 {
+ t.Errorf("Server event callback called after unsubscribe")
+ }
}
type testRequestServer struct {
@@ -156,4 +179,4 @@ type testRequestServer struct {
func (rs *testRequestServer) Name() string { return "" }
func (rs *testRequestServer) Subscribe(eventCb func(Event)) { rs.eventCb = eventCb }
func (rs *testRequestServer) SendRequest(ID, Request) {}
-func (rs *testRequestServer) Unsubscribe() {}
+func (rs *testRequestServer) Unsubscribe() { rs.eventCb = nil }
diff --git a/beacon/light/sync/test_helpers.go b/beacon/light/sync/test_helpers.go
index cfca8ad8a4..b331bf7110 100644
--- a/beacon/light/sync/test_helpers.go
+++ b/beacon/light/sync/test_helpers.go
@@ -173,24 +173,24 @@ type TestCommitteeChain struct {
init bool
}
-func (t *TestCommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
- t.fsp, t.nsp, t.init = bootstrap.Header.SyncPeriod(), bootstrap.Header.SyncPeriod()+2, true
+func (tc *TestCommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
+ tc.fsp, tc.nsp, tc.init = bootstrap.Header.SyncPeriod(), bootstrap.Header.SyncPeriod()+2, true
return nil
}
-func (t *TestCommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
+func (tc *TestCommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
period := update.AttestedHeader.Header.SyncPeriod()
- if period < t.fsp || period > t.nsp || !t.init {
+ if period < tc.fsp || period > tc.nsp || !tc.init {
return light.ErrInvalidPeriod
}
- if period == t.nsp {
- t.nsp++
+ if period == tc.nsp {
+ tc.nsp++
}
return nil
}
-func (t *TestCommitteeChain) NextSyncPeriod() (uint64, bool) {
- return t.nsp, t.init
+func (tc *TestCommitteeChain) NextSyncPeriod() (uint64, bool) {
+ return tc.nsp, tc.init
}
func (tc *TestCommitteeChain) ExpInit(t *testing.T, ExpInit bool) {
@@ -199,8 +199,8 @@ func (tc *TestCommitteeChain) ExpInit(t *testing.T, ExpInit bool) {
}
}
-func (t *TestCommitteeChain) SetNextSyncPeriod(nsp uint64) {
- t.init, t.nsp = true, nsp
+func (tc *TestCommitteeChain) SetNextSyncPeriod(nsp uint64) {
+ tc.init, tc.nsp = true, nsp
}
func (tc *TestCommitteeChain) ExpNextSyncPeriod(t *testing.T, expNsp uint64) {
diff --git a/build/checksums.txt b/build/checksums.txt
index da2988452a..d099e53156 100644
--- a/build/checksums.txt
+++ b/build/checksums.txt
@@ -5,79 +5,87 @@
# 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.55.2
+# version:golangci 1.59.0
# https://github.com/golangci/golangci-lint/releases/
-# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/
-632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz
-234463f059249f82045824afdcdd5db5682d0593052f58f6a3039a0a1c3899f6 golangci-lint-1.55.2-darwin-arm64.tar.gz
-2bdd105e2d4e003a9058c33a22bb191a1e0f30fa0790acca0d8fbffac1d6247c golangci-lint-1.55.2-freebsd-386.tar.gz
-e75056e8b082386676ce23eba455cf893931a792c0d87e1e3743c0aec33c7fb5 golangci-lint-1.55.2-freebsd-amd64.tar.gz
-5789b933facaf6136bd23f1d50add67b79bbcf8dfdfc9069a37f729395940a66 golangci-lint-1.55.2-freebsd-armv6.tar.gz
-7f21ab1008d05f32c954f99470fc86a83a059e530fe2add1d0b7d8ed4d8992a7 golangci-lint-1.55.2-freebsd-armv7.tar.gz
-33ab06139b9219a28251f10821da94423db30285cc2af97494cbb2a281927de9 golangci-lint-1.55.2-illumos-amd64.tar.gz
-57ce6f8ce3ad6ee45d7cc3d9a047545a851c2547637834a3fcb086c7b40b1e6b golangci-lint-1.55.2-linux-386.tar.gz
-ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c golangci-lint-1.55.2-linux-amd64.tar.gz
-8eb0cee9b1dbf0eaa49871798c7f8a5b35f2960c52d776a5f31eb7d886b92746 golangci-lint-1.55.2-linux-arm64.tar.gz
-3195f3e0f37d353fd5bd415cabcd4e263f5c29d3d0ffb176c26ff3d2c75eb3bb golangci-lint-1.55.2-linux-armv6.tar.gz
-c823ee36eb1a719e171de1f2f5ca3068033dce8d9817232fd10ed71fd6650406 golangci-lint-1.55.2-linux-armv7.tar.gz
-758a5d2a356dc494bd13ed4c0d4bf5a54a4dc91267ea5ecdd87b86c7ca0624e7 golangci-lint-1.55.2-linux-loong64.tar.gz
-2c7b9abdce7cae802a67d583cd7c6dca520bff6d0e17c8535a918e2f2b437aa0 golangci-lint-1.55.2-linux-mips64.tar.gz
-024e0a15b85352cc27271285526e16a4ab66d3e67afbbe446c9808c06cb8dbed golangci-lint-1.55.2-linux-mips64le.tar.gz
-6b00f89ba5506c1de1efdd9fa17c54093013a294fefd8b9b31534db626a672ee golangci-lint-1.55.2-linux-ppc64le.tar.gz
-0faa0d047d9bf7b703ed3ea65b6117043c93504f9ca1de25ae929d3901c73d4a golangci-lint-1.55.2-linux-riscv64.tar.gz
-30dec9b22e7d5bb4e9d5ccea96da20f71cd7db3c8cf30b8ddc7cb9174c4d742a golangci-lint-1.55.2-linux-s390x.tar.gz
-5a0ede48f79ad707902fdb29be8cd2abd8302dc122b65ebae3fdfc86751c7698 golangci-lint-1.55.2-netbsd-386.tar.gz
-95af20a2e617126dd5b08122ece7819101070e1582a961067ce8c41172f901ad golangci-lint-1.55.2-netbsd-amd64.tar.gz
-94fb7dacb7527847cc95d7120904e19a2a0a81a0d50d61766c9e0251da72ab9d golangci-lint-1.55.2-netbsd-armv6.tar.gz
-ca906bce5fee9619400e4a321c56476fe4a4efb6ac4fc989d340eb5563348873 golangci-lint-1.55.2-netbsd-armv7.tar.gz
-45b442f69fc8915c4500201c0247b7f3f69544dbc9165403a61f9095f2c57355 golangci-lint-1.55.2-windows-386.zip
-f57d434d231d43417dfa631587522f8c1991220b43c8ffadb9c7bd279508bf81 golangci-lint-1.55.2-windows-amd64.zip
-fd7dc8f4c6829ee6fafb252a4d81d2155cd35da7833665cbb25d53ce7cecd990 golangci-lint-1.55.2-windows-arm64.zip
-1892c3c24f9e7ef44b02f6750c703864b6dc350129f3ec39510300007b2376f1 golangci-lint-1.55.2-windows-armv6.zip
-a5e68ae73d38748b5269fad36ac7575e3c162a5dc63ef58abdea03cc5da4522a golangci-lint-1.55.2-windows-armv7.zip
+# https://github.com/golangci/golangci-lint/releases/download/v1.59.0/
+418acf7e255ddc0783e97129c9b03d9311b77826a5311d425a01c708a86417e7 golangci-lint-1.59.0-darwin-amd64.tar.gz
+5f6a1d95a6dd69f6e328eb56dd311a38e04cfab79a1305fbf4957f4e203f47b6 golangci-lint-1.59.0-darwin-arm64.tar.gz
+8899bf589185d49f747f3e5db9f0bde8a47245a100c64a3dd4d65e8e92cfc4f2 golangci-lint-1.59.0-freebsd-386.tar.gz
+658212f138d9df2ac89427e22115af34bf387c0871d70f2a25101718946a014f golangci-lint-1.59.0-freebsd-amd64.tar.gz
+4c6395ea40f314d3b6fa17d8997baab93464d5d1deeaab513155e625473bd03a golangci-lint-1.59.0-freebsd-armv6.tar.gz
+ff37da4fbaacdb6bbae70fdbdbb1ba932a859956f788c82822fa06bef5b7c6b3 golangci-lint-1.59.0-freebsd-armv7.tar.gz
+439739469ed2bda182b1ec276d40c40e02f195537f78e3672996741ad223d6b6 golangci-lint-1.59.0-illumos-amd64.tar.gz
+940801d46790e40d0a097d8fee34e2606f0ef148cd039654029b0b8750a15ed6 golangci-lint-1.59.0-linux-386.tar.gz
+3b14a439f33c4fff83dbe0349950d984042b9a1feb6c62f82787b598fc3ab5f4 golangci-lint-1.59.0-linux-amd64.tar.gz
+c57e6c0b0fa03089a2611dceddd5bc5d206716cccdff8b149da8baac598719a1 golangci-lint-1.59.0-linux-arm64.tar.gz
+93149e2d3b25ac754df9a23172403d8aa6d021a7e0d9c090a12f51897f68c9a0 golangci-lint-1.59.0-linux-armv6.tar.gz
+d10ac38239d9efee3ee87b55c96cdf3fa09e1a525babe3ffdaaf65ccc48cf3dc golangci-lint-1.59.0-linux-armv7.tar.gz
+047338114b4f0d5f08f0fb9a397b03cc171916ed0960be7dfb355c2320cd5e9c golangci-lint-1.59.0-linux-loong64.tar.gz
+5632df0f7f8fc03a80a266130faef0b5902d280cf60621f1b2bdc1aef6d97ee9 golangci-lint-1.59.0-linux-mips64.tar.gz
+71dd638c82fa4439171e7126d2c7a32b5d103bfdef282cea40c83632cb3d1f4b golangci-lint-1.59.0-linux-mips64le.tar.gz
+6cf9ea0d34e91669948483f9ae7f07da319a879344373a1981099fbd890cde00 golangci-lint-1.59.0-linux-ppc64le.tar.gz
+af0205fa6fbab197cee613c359947711231739095d21b5c837086233b36ad971 golangci-lint-1.59.0-linux-riscv64.tar.gz
+a9d2fb93f3c688ebccef94f5dc96c0b07c4d20bf6556cddebd8442159b0c80f6 golangci-lint-1.59.0-linux-s390x.tar.gz
+68ab4c57a847b8ace9679887f2f8b2b6760e57ee29dcde8c3f40dd8bb2654fa2 golangci-lint-1.59.0-netbsd-386.tar.gz
+d277b8b435c19406d00de4d509eadf5a024a5782878332e9a1b7c02bb76e87a7 golangci-lint-1.59.0-netbsd-amd64.tar.gz
+83211656be8dcfa1545af4f92894409f412d1f37566798cb9460a526593ad62c golangci-lint-1.59.0-netbsd-arm64.tar.gz
+6c6866d28bf79fa9817a0f7d2b050890ed109cae80bdb4dfa39536a7226da237 golangci-lint-1.59.0-netbsd-armv6.tar.gz
+11587566363bd03ca586b7df9776ccaed569fcd1f3489930ac02f9375b307503 golangci-lint-1.59.0-netbsd-armv7.tar.gz
+466181a8967bafa495e41494f93a0bec829c2cf715de874583b0460b3b8ae2b8 golangci-lint-1.59.0-windows-386.zip
+3317d8a87a99a49a0a1321d295c010790e6dbf43ee96b318f4b8bb23eae7a565 golangci-lint-1.59.0-windows-amd64.zip
+b3af955c7fceac8220a36fc799e1b3f19d3b247d32f422caac5f9845df8f7316 golangci-lint-1.59.0-windows-arm64.zip
+6f083c7d0c764e5a0e5bde46ee3e91ae357d80c194190fe1d9754392e9064c7e golangci-lint-1.59.0-windows-armv6.zip
+3709b4dd425deadab27748778d08e03c0f804d7748f7dd5b6bb488d98aa031c7 golangci-lint-1.59.0-windows-armv7.zip
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
#
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 9286594181..958fb71179 100644
--- a/cmd/devp2p/internal/v4test/framework.go
+++ b/cmd/devp2p/internal/v4test/framework.go
@@ -53,16 +53,18 @@ 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 {
tcpPort = 30303
}
- if udpPort = node.TCP(); udpPort == 0 {
+ if udpPort = node.UDP(); udpPort == 0 {
udpPort = 30303
}
node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort)
@@ -110,7 +112,7 @@ func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint {
}
func (te *testenv) remoteEndpoint() v4wire.Endpoint {
- return v4wire.NewEndpoint(te.remoteAddr, 0)
+ return v4wire.NewEndpoint(te.remoteAddr.AddrPort(), 0)
}
func contains(ns []v4wire.Node, key v4wire.Pubkey) bool {
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 9ea94d195e..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 {
@@ -217,7 +217,7 @@ func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
return nil
}
if env.ParentBaseFee == nil || env.Number == 0 {
- return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
+ return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'parentBaseFee' in env section"))
}
env.BaseFee = eip1559.CalcBaseFee(chainConfig, &types.Header{
Number: new(big.Int).SetUint64(env.Number - 1),
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/common/math/big_test.go b/common/math/big_test.go
index 803b5e1cc6..ee8f09e7b4 100644
--- a/common/math/big_test.go
+++ b/common/math/big_test.go
@@ -180,9 +180,9 @@ func BenchmarkByteAtOld(b *testing.B) {
func TestReadBits(t *testing.T) {
check := func(input string) {
want, _ := hex.DecodeString(input)
- int, _ := new(big.Int).SetString(input, 16)
+ n, _ := new(big.Int).SetString(input, 16)
buf := make([]byte, len(want))
- ReadBits(int, buf)
+ ReadBits(n, buf)
if !bytes.Equal(buf, want) {
t.Errorf("have: %x\nwant: %x", buf, want)
}
diff --git a/common/math/integer.go b/common/math/integer.go
index da01c0a08e..080fba8fea 100644
--- a/common/math/integer.go
+++ b/common/math/integer.go
@@ -54,11 +54,11 @@ func (i *HexOrDecimal64) UnmarshalJSON(input []byte) error {
// UnmarshalText implements encoding.TextUnmarshaler.
func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
- int, ok := ParseUint64(string(input))
+ n, ok := ParseUint64(string(input))
if !ok {
return fmt.Errorf("invalid hex or decimal integer %q", input)
}
- *i = HexOrDecimal64(int)
+ *i = HexOrDecimal64(n)
return nil
}
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_test.go b/core/genesis_test.go
index 31401e214c..ab408327d4 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -304,7 +304,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
},
}
- expected := common.Hex2Bytes("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
+ expected := common.FromHex("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
@@ -314,7 +314,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
triedb := triedb.NewDatabase(db, &triedb.Config{IsVerkle: true, PathDB: pathdb.Defaults})
block := genesis.MustCommit(db, triedb)
if !bytes.Equal(block.Root().Bytes(), expected) {
- t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
+ t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
}
// Test that the trie is verkle
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/database.go b/core/state/database.go
index 04d7c06687..d71f8f34b6 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -123,7 +123,7 @@ type Trie interface {
// The returned nodeset can be nil if the trie is clean(nothing to commit).
// Once the trie is committed, it's not usable anymore. A new trie must
// be created with new root and updated trie database for following usage
- Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
+ Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. And error will be returned
diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go
index 8de4b134d3..d81a628c91 100644
--- a/core/state/snapshot/generate.go
+++ b/core/state/snapshot/generate.go
@@ -360,10 +360,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi
for i, key := range result.keys {
snapTrie.Update(key, result.vals[i])
}
- root, nodes, err := snapTrie.Commit(false)
- if err != nil {
- return false, nil, err
- }
+ root, nodes := snapTrie.Commit(false)
if nodes != nil {
tdb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
tdb.Commit(root, false)
diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go
index da93ebc875..891111973a 100644
--- a/core/state/snapshot/generate_test.go
+++ b/core/state/snapshot/generate_test.go
@@ -210,7 +210,7 @@ func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []st
if !commit {
return stTrie.Hash()
}
- root, nodes, _ := stTrie.Commit(false)
+ root, nodes := stTrie.Commit(false)
if nodes != nil {
t.nodes.Merge(nodes)
}
@@ -218,7 +218,7 @@ func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []st
}
func (t *testHelper) Commit() common.Hash {
- root, nodes, _ := t.accTrie.Commit(true)
+ root, nodes := t.accTrie.Commit(true)
if nodes != nil {
t.nodes.Merge(nodes)
}
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 252dcdf2b0..5c1dab53dc 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -19,9 +19,7 @@ package state
import (
"bytes"
"fmt"
- "io"
"maps"
- "sync"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -34,14 +32,6 @@ import (
"github.com/holiman/uint256"
)
-// hasherPool holds a pool of hashers used by state objects during concurrent
-// trie updates.
-var hasherPool = sync.Pool{
- New: func() interface{} {
- return crypto.NewKeccakState()
- },
-}
-
type Storage map[common.Hash]common.Hash
func (s Storage) Copy() Storage {
@@ -65,9 +55,20 @@ type stateObject struct {
trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded
- originStorage Storage // Storage cache of original entries to dedup rewrites
- pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
- dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction
+ originStorage Storage // Storage entries that have been accessed within the current block
+ dirtyStorage Storage // Storage entries that have been modified within the current transaction
+ pendingStorage Storage // Storage entries that have been modified within the current block
+
+ // uncommittedStorage tracks a set of storage entries that have been modified
+ // but not yet committed since the "last commit operation", along with their
+ // original values before mutation.
+ //
+ // Specifically, the commit will be performed after each transaction before
+ // the byzantium fork, therefore the map is already reset at the transaction
+ // boundary; however post the byzantium fork, the commit will only be performed
+ // at the end of block, this set essentially tracks all the modifications
+ // made within the block.
+ uncommittedStorage Storage
// Cache flags.
dirtyCode bool // true if the code was updated
@@ -96,22 +97,18 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
acct = types.NewEmptyStateAccount()
}
return &stateObject{
- db: db,
- address: address,
- addrHash: crypto.Keccak256Hash(address[:]),
- origin: origin,
- data: *acct,
- originStorage: make(Storage),
- pendingStorage: make(Storage),
- dirtyStorage: make(Storage),
+ db: db,
+ address: address,
+ addrHash: crypto.Keccak256Hash(address[:]),
+ origin: origin,
+ data: *acct,
+ originStorage: make(Storage),
+ dirtyStorage: make(Storage),
+ pendingStorage: make(Storage),
+ uncommittedStorage: make(Storage),
}
}
-// EncodeRLP implements rlp.Encoder.
-func (s *stateObject) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, &s.data)
-}
-
func (s *stateObject) markSelfdestructed() {
s.selfDestructed = true
}
@@ -160,7 +157,7 @@ func (s *stateObject) getPrefetchedTrie() Trie {
return s.db.prefetcher.trie(s.addrHash, s.data.Root)
}
-// GetState retrieves a value from the account storage trie.
+// GetState retrieves a value associated with the given storage key.
func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key)
return value
@@ -177,7 +174,8 @@ func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
return origin, origin
}
-// GetCommittedState retrieves a value from the committed account storage trie.
+// GetCommittedState retrieves the value associated with the specific key
+// without any mutations caused in the current execution.
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// If we have a pending write or clean cached, return that
if value, pending := s.pendingStorage[key]; pending {
@@ -193,6 +191,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
+ s.originStorage[key] = common.Hash{} // track the empty slot as origin value
return common.Hash{}
}
// If no live objects are available, attempt to use snapshots
@@ -231,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
}
@@ -272,20 +279,29 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common
func (s *stateObject) finalise() {
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage {
- // If the slot is different from its original value, move it into the
- // pending area to be committed at the end of the block (and prefetch
- // the pathways).
- if value != s.originStorage[key] {
- s.pendingStorage[key] = value
- slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
+ if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
+ // The slot is reverted to its original value, delete the entry
+ // to avoid thrashing the data structures.
+ delete(s.uncommittedStorage, key)
+ } else if exist {
+ // The slot is modified to another value and the slot has been
+ // tracked for commit, do nothing here.
} else {
- // Otherwise, the slot was reverted to its original value, remove it
- // from the pending area to avoid thrashing the data structure.
- delete(s.pendingStorage, key)
+ // The slot is different from its original value and hasn't been
+ // tracked for commit yet.
+ s.uncommittedStorage[key] = s.GetCommittedState(key)
+ slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
}
+ // Aggregate the dirty storage slots into the pending area. It might
+ // be possible that the value of tracked slot here is same with the
+ // one in originStorage (e.g. the slot was modified in tx_a and then
+ // modified back in tx_b). We can't blindly remove it from pending
+ // map as the dirty slot might have been committed already (before the
+ // byzantium fork) and entry is necessary to modify the value back.
+ 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)
}
}
@@ -308,7 +324,7 @@ func (s *stateObject) finalise() {
// It assumes all the dirty storage slots have been finalized before.
func (s *stateObject) updateTrie() (Trie, error) {
// Short circuit if nothing changed, don't bother with hashing anything
- if len(s.pendingStorage) == 0 {
+ if len(s.uncommittedStorage) == 0 {
return s.trie, nil
}
// Retrieve a pretecher populated trie, or fall back to the database
@@ -325,20 +341,8 @@ func (s *stateObject) updateTrie() (Trie, error) {
return nil, err
}
}
-
- // The snapshot storage map for the object
- var (
- storage map[common.Hash][]byte
- origin map[common.Hash][]byte
- )
- // Insert all the pending storage updates into the trie
- usedStorage := make([][]byte, 0, len(s.pendingStorage))
-
- hasher := hasherPool.Get().(crypto.KeccakState)
- defer hasherPool.Put(hasher)
-
- // Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
- // in circumstances similar to the following:
+ // Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
+ // in circumstances similar to the following:
//
// Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings.
// During the execution of a block:
@@ -347,21 +351,23 @@ func (s *stateObject) updateTrie() (Trie, error) {
// If the deletion is handled first, then `P` would be left with only one child, thus collapsed
// into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
- var deletions []common.Hash
- for key, value := range s.pendingStorage {
+ var (
+ deletions []common.Hash
+ used = make([][]byte, 0, len(s.uncommittedStorage))
+ )
+ for key, origin := range s.uncommittedStorage {
// Skip noop changes, persist actual changes
- if value == s.originStorage[key] {
+ value, exist := s.pendingStorage[key]
+ if value == origin {
+ log.Error("Storage update was noop", "address", s.address, "slot", key)
+ continue
+ }
+ if !exist {
+ log.Error("Storage slot is not found in pending area", s.address, "slot", key)
continue
}
- prev := s.originStorage[key]
- s.originStorage[key] = value
-
- var encoded []byte // rlp-encoded value to be used by the snapshot
if (value != common.Hash{}) {
- // Encoding []byte cannot fail, ok to ignore the error.
- trimmed := common.TrimLeftZeroes(value[:])
- encoded, _ = rlp.EncodeToBytes(trimmed)
- if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil {
+ if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil {
s.db.setError(err)
return nil, err
}
@@ -369,39 +375,8 @@ func (s *stateObject) updateTrie() (Trie, error) {
} else {
deletions = append(deletions, key)
}
- // Cache the mutated storage slots until commit
- if storage == nil {
- s.db.storagesLock.Lock()
- if storage = s.db.storages[s.addrHash]; storage == nil {
- storage = make(map[common.Hash][]byte)
- s.db.storages[s.addrHash] = storage
- }
- s.db.storagesLock.Unlock()
- }
- khash := crypto.HashData(hasher, key[:])
- storage[khash] = encoded // encoded will be nil if it's deleted
-
- // Cache the original value of mutated storage slots
- if origin == nil {
- s.db.storagesLock.Lock()
- if origin = s.db.storagesOrigin[s.address]; origin == nil {
- origin = make(map[common.Hash][]byte)
- s.db.storagesOrigin[s.address] = origin
- }
- s.db.storagesLock.Unlock()
- }
- // Track the original value of slot only if it's mutated first time
- if _, ok := origin[khash]; !ok {
- if prev == (common.Hash{}) {
- origin[khash] = nil // nil if it was not present previously
- } else {
- // Encoding []byte cannot fail, ok to ignore the error.
- b, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(prev[:]))
- origin[khash] = b
- }
- }
// Cache the items for preloading
- usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
+ used = append(used, common.CopyBytes(key[:])) // Copy needed for closure
}
for _, key := range deletions {
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
@@ -410,15 +385,10 @@ func (s *stateObject) updateTrie() (Trie, error) {
}
s.db.StorageDeleted.Add(1)
}
- // If no slots were touched, issue a warning as we shouldn't have done all
- // the above work in the first place
- if len(usedStorage) == 0 {
- log.Error("State object update was noop", "addr", s.address, "slots", len(s.pendingStorage))
- }
if s.db.prefetcher != nil {
- s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage)
+ s.db.prefetcher.used(s.addrHash, s.data.Root, used)
}
- s.pendingStorage = make(Storage) // reset pending map
+ s.uncommittedStorage = make(Storage) // empties the commit markers
return tr, nil
}
@@ -434,30 +404,76 @@ func (s *stateObject) updateRoot() {
s.data.Root = tr.Hash()
}
-// commit obtains a set of dirty storage trie nodes and updates the account data.
-// The returned set can be nil if nothing to commit. This function assumes all
-// storage mutations have already been flushed into trie by updateRoot.
+// commitStorage overwrites the clean storage with the storage changes and
+// fulfills the storage diffs into the given accountUpdate struct.
+func (s *stateObject) commitStorage(op *accountUpdate) {
+ var (
+ buf = crypto.NewKeccakState()
+ encode = func(val common.Hash) []byte {
+ if val == (common.Hash{}) {
+ return nil
+ }
+ blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
+ return blob
+ }
+ )
+ for key, val := range s.pendingStorage {
+ // Skip the noop storage changes, it might be possible the value
+ // of tracked slot is same in originStorage and pendingStorage
+ // map, e.g. the storage slot is modified in tx_a and then reset
+ // back in tx_b.
+ if val == s.originStorage[key] {
+ continue
+ }
+ hash := crypto.HashData(buf, key[:])
+ if op.storages == nil {
+ op.storages = make(map[common.Hash][]byte)
+ }
+ op.storages[hash] = encode(val)
+ if op.storagesOrigin == nil {
+ op.storagesOrigin = make(map[common.Hash][]byte)
+ }
+ op.storagesOrigin[hash] = encode(s.originStorage[key])
+
+ // Overwrite the clean value of storage slots
+ s.originStorage[key] = val
+ }
+ s.pendingStorage = make(Storage)
+}
+
+// commit obtains the account changes (metadata, storage slots, code) caused by
+// state execution along with the dirty storage trie nodes.
//
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
-func (s *stateObject) commit() (*trienode.NodeSet, error) {
- // Short circuit if trie is not even loaded, don't bother with committing anything
- if s.trie == nil {
+func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
+ // commit the account metadata changes
+ op := &accountUpdate{
+ address: s.address,
+ data: types.SlimAccountRLP(s.data),
+ }
+ if s.origin != nil {
+ op.origin = types.SlimAccountRLP(*s.origin)
+ }
+ // commit the contract code if it's modified
+ if s.dirtyCode {
+ op.code = &contractCode{
+ hash: common.BytesToHash(s.CodeHash()),
+ blob: s.code,
+ }
+ s.dirtyCode = false // reset the dirty flag
+ }
+ // Commit storage changes and the associated storage trie
+ s.commitStorage(op)
+ if len(op.storages) == 0 {
+ // nothing changed, don't bother to commit the trie
s.origin = s.data.Copy()
- return nil, nil
- }
- // The trie is currently in an open state and could potentially contain
- // cached mutations. Call commit to acquire a set of nodes that have been
- // modified, the set can be nil if nothing to commit.
- root, nodes, err := s.trie.Commit(false)
- if err != nil {
- return nil, err
+ return op, nil, nil
}
+ root, nodes := s.trie.Commit(false)
s.data.Root = root
-
- // Update original account data after commit
s.origin = s.data.Copy()
- return nodes, nil
+ return op, nodes, nil
}
// AddBalance adds amount to s's balance.
@@ -500,18 +516,19 @@ func (s *stateObject) setBalance(amount *uint256.Int) {
func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj := &stateObject{
- db: db,
- address: s.address,
- addrHash: s.addrHash,
- origin: s.origin,
- data: s.data,
- code: s.code,
- originStorage: s.originStorage.Copy(),
- pendingStorage: s.pendingStorage.Copy(),
- dirtyStorage: s.dirtyStorage.Copy(),
- dirtyCode: s.dirtyCode,
- selfDestructed: s.selfDestructed,
- newContract: s.newContract,
+ db: db,
+ address: s.address,
+ addrHash: s.addrHash,
+ origin: s.origin,
+ data: s.data,
+ code: s.code,
+ originStorage: s.originStorage.Copy(),
+ pendingStorage: s.pendingStorage.Copy(),
+ dirtyStorage: s.dirtyStorage.Copy(),
+ uncommittedStorage: s.uncommittedStorage.Copy(),
+ dirtyCode: s.dirtyCode,
+ selfDestructed: s.selfDestructed,
+ newContract: s.newContract,
}
if s.trie != nil {
obj.trie = db.db.CopyTrie(s.trie)
diff --git a/core/state/statedb.go b/core/state/statedb.go
index ccc7ca4ba2..4f84d93d63 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -18,6 +18,7 @@
package state
import (
+ "errors"
"fmt"
"maps"
"math/big"
@@ -95,15 +96,6 @@ type StateDB struct {
// It will be updated when the Commit is called.
originalRoot common.Hash
- // These maps hold the state changes (including the corresponding
- // original value) that occurred in this **block**.
- accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding
- accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
-
- storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format
- storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
- storagesLock sync.Mutex // Mutex protecting the maps during concurrent updates/commits
-
// This map holds 'live' objects, which will get modified while
// processing a state transition.
stateObjects map[common.Address]*stateObject
@@ -171,9 +163,6 @@ type StateDB struct {
StorageUpdated atomic.Int64
AccountDeleted int
StorageDeleted atomic.Int64
-
- // Testing hooks
- onCommit func(states *triestate.Set) // Hook invoked when commit is performed
}
// New creates a new state from a given trie.
@@ -187,10 +176,6 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
trie: tr,
originalRoot: root,
snaps: snaps,
- accounts: make(map[common.Hash][]byte),
- storages: make(map[common.Hash]map[common.Hash][]byte),
- accountsOrigin: make(map[common.Address][]byte),
- storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
mutations: make(map[common.Address]*mutation),
@@ -215,14 +200,27 @@ 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
+ // have no state transitions if it contains no transactions and no withdrawals.
+ // In such cases, the account trie won't be scheduled for prefetching, leading
+ // to unnecessary error logs.
+ //
+ // 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, false); err != nil {
+ log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err)
+ }
}
}
@@ -351,7 +349,7 @@ func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
return common.Hash{}
}
-// TxIndex returns the current transaction index set by Prepare.
+// TxIndex returns the current transaction index set by SetTxContext.
func (s *StateDB) TxIndex() int {
return s.txIndex
}
@@ -380,7 +378,7 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
return common.Hash{}
}
-// GetState retrieves a value from the given account's storage trie.
+// GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr)
if stateObject != nil {
@@ -389,7 +387,8 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
return common.Hash{}
}
-// GetCommittedState retrieves a value from the given account's committed storage trie.
+// GetCommittedState retrieves the value associated with the specific key
+// without any mutations caused in the current execution.
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr)
if stateObject != nil {
@@ -557,22 +556,6 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
if obj.dirtyCode {
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
}
- // Cache the data until commit. Note, this update mechanism is not symmetric
- // to the deletion, because whereas it is enough to track account updates
- // at commit time, deletions need tracking at transaction boundary level to
- // ensure we capture state clearing.
- s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data)
-
- // Track the original value of mutated account, nil means it was not present.
- // Skip if it has been tracked (because updateStateObject may be called
- // multiple times in a block).
- if _, ok := s.accountsOrigin[obj.address]; !ok {
- if obj.origin == nil {
- s.accountsOrigin[obj.address] = nil
- } else {
- s.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin)
- }
- }
}
// deleteStateObject removes the given object from the state trie.
@@ -633,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)
@@ -691,10 +682,6 @@ func (s *StateDB) Copy() *StateDB {
trie: s.db.CopyTrie(s.trie),
hasher: crypto.NewKeccakState(),
originalRoot: s.originalRoot,
- accounts: copySet(s.accounts),
- storages: copy2DSet(s.storages),
- accountsOrigin: copySet(s.accountsOrigin),
- storagesOrigin: copy2DSet(s.storagesOrigin),
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
mutations: make(map[common.Address]*mutation, len(s.mutations)),
@@ -803,13 +790,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
s.stateObjectsDestruct[obj.address] = obj.origin
}
- // Note, we can't do this only at the end of a block because multiple
- // transactions within the same block might self destruct and then
- // resurrect an account; but the snapshotter needs both events.
- delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
- delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
- delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect)
- delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
} else {
obj.finalise()
s.markUpdate(addr)
@@ -820,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)
}
}
@@ -1020,10 +1000,9 @@ func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, r
}
// deleteStorage is designed to delete the storage trie of a designated account.
-// It could potentially be terminated if the storage size is excessively large,
-// potentially leading to an out-of-memory panic. The function will make an attempt
-// to utilize an efficient strategy if the associated state snapshot is reachable;
-// otherwise, it will resort to a less-efficient approach.
+// The function will make an attempt to utilize an efficient strategy if the
+// associated state snapshot is reachable; otherwise, it will resort to a less
+// efficient approach.
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, *trienode.NodeSet, error) {
var (
start = time.Now()
@@ -1058,75 +1037,61 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
}
// handleDestruction processes all destruction markers and deletes the account
-// and associated storage slots if necessary. There are four possible situations
-// here:
+// and associated storage slots if necessary. There are four potential scenarios
+// as following:
//
-// - the account was not existent and be marked as destructed
-//
-// - the account was not existent and be marked as destructed,
-// however, it's resurrected later in the same block.
-//
-// - the account was existent and be marked as destructed
-//
-// - the account was existent and be marked as destructed,
-// however it's resurrected later in the same block.
+// (a) the account was not existent and be marked as destructed
+// (b) the account was not existent and be marked as destructed,
+// however, it's resurrected later in the same block.
+// (c) the account was existent and be marked as destructed
+// (d) the account was existent and be marked as destructed,
+// however it's resurrected later in the same block.
//
// In case (a), nothing needs be deleted, nil to nil transition can be ignored.
-//
// In case (b), nothing needs be deleted, nil is used as the original value for
// newly created account and storages
-//
// In case (c), **original** account along with its storages should be deleted,
// with their values be tracked as original value.
-//
// In case (d), **original** account along with its storages should be deleted,
// with their values be tracked as original value.
-func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) error {
- // Short circuit if geth is running with hash mode. This procedure can consume
- // considerable time and storage deletion isn't supported in hash mode, thus
- // preemptively avoiding unnecessary expenses.
- if s.db.TrieDB().Scheme() == rawdb.HashScheme {
- return nil
- }
+func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
+ var (
+ nodes []*trienode.NodeSet
+ buf = crypto.NewKeccakState()
+ deletes = make(map[common.Hash]*accountDelete)
+ )
for addr, prev := range s.stateObjectsDestruct {
- // The original account was non-existing, and it's marked as destructed
- // in the scope of block. It can be case (a) or (b).
- // - for (a), skip it without doing anything.
- // - for (b), track account's original value as nil. It may overwrite
- // the data cached in s.accountsOrigin set by 'updateStateObject'.
- addrHash := crypto.Keccak256Hash(addr[:])
+ // The account was non-existent, and it's marked as destructed in the scope
+ // of block. It can be either case (a) or (b) and will be interpreted as
+ // null->null state transition.
+ // - for (a), skip it without doing anything
+ // - for (b), the resurrected account with nil as original will be handled afterwards
if prev == nil {
- if _, ok := s.accounts[addrHash]; ok {
- s.accountsOrigin[addr] = nil // case (b)
- }
continue
}
- // It can overwrite the data in s.accountsOrigin set by 'updateStateObject'.
- s.accountsOrigin[addr] = types.SlimAccountRLP(*prev) // case (c) or (d)
+ // The account was existent, it can be either case (c) or (d).
+ addrHash := crypto.HashData(buf, addr.Bytes())
+ op := &accountDelete{
+ address: addr,
+ origin: types.SlimAccountRLP(*prev),
+ }
+ deletes[addrHash] = op
- // Short circuit if the storage was empty.
+ // Short circuit if the origin storage was empty.
if prev.Root == types.EmptyRootHash {
continue
}
- // Remove storage slots belong to the account.
+ // Remove storage slots belonging to the account.
slots, set, err := s.deleteStorage(addr, addrHash, prev.Root)
if err != nil {
- return fmt.Errorf("failed to delete storage, err: %w", err)
- }
- if s.storagesOrigin[addr] == nil {
- s.storagesOrigin[addr] = slots
- } else {
- // It can overwrite the data in s.storagesOrigin[addrHash] set by
- // 'object.updateTrie'.
- for key, val := range slots {
- s.storagesOrigin[addr][key] = val
- }
- }
- if err := nodes.Merge(set); err != nil {
- return err
+ return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
}
+ op.storagesOrigin = slots
+
+ // Aggregate the associated trie node changes.
+ nodes = append(nodes, set)
}
- return nil
+ return deletes, nodes, nil
}
// GetTrie returns the account trie.
@@ -1134,18 +1099,12 @@ func (s *StateDB) GetTrie() Trie {
return s.trie
}
-// Commit writes the state to the underlying in-memory trie database.
-// Once the state is committed, tries cached in stateDB (including account
-// trie, storage tries) will no longer be functional. A new state instance
-// must be created with new root and updated database for accessing post-
-// commit states.
-//
-// The associated block number of the state transition is also provided
-// for more chain context.
-func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) {
+// commit gathers the state mutations accumulated along with the associated
+// trie changes, resetting all internal flags with the new state as the base.
+func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
// Short circuit in case any database failure occurred earlier.
if s.dbErr != nil {
- return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
+ return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
}
// Finalize any pending changes and merge everything into the tries
s.IntermediateRoot(deleteEmptyObjects)
@@ -1156,19 +1115,56 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
accountTrieNodesDeleted int
storageTrieNodesUpdated int
storageTrieNodesDeleted int
- nodes = trienode.NewMergedNodeSet()
+
+ lock sync.Mutex // protect two maps below
+ nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
+ updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates
+
+ // merge aggregates the dirty trie nodes into the global set.
+ //
+ // Given that some accounts may be destroyed and then recreated within
+ // the same block, it's possible that a node set with the same owner
+ // may already exists. In such cases, these two sets are combined, with
+ // the later one overwriting the previous one if any nodes are modified
+ // or deleted in both sets.
+ //
+ // merge run concurrently across all the state objects and account trie.
+ merge = func(set *trienode.NodeSet) error {
+ if set == nil {
+ return nil
+ }
+ lock.Lock()
+ defer lock.Unlock()
+
+ updates, deletes := set.Size()
+ if set.Owner == (common.Hash{}) {
+ accountTrieNodesUpdated += updates
+ accountTrieNodesDeleted += deletes
+ } else {
+ storageTrieNodesUpdated += updates
+ storageTrieNodesDeleted += deletes
+ }
+ return nodes.Merge(set)
+ }
)
- // Handle all state deletions first
- if err := s.handleDestruction(nodes); err != nil {
- return common.Hash{}, err
+ // Given that some accounts could be destroyed and then recreated within
+ // the same block, account deletions must be processed first. This ensures
+ // that the storage trie nodes deleted during destruction and recreated
+ // during subsequent resurrection can be combined correctly.
+ deletes, delNodes, err := s.handleDestruction()
+ if err != nil {
+ return nil, err
+ }
+ for _, set := range delNodes {
+ if err := merge(set); err != nil {
+ return nil, err
+ }
}
// Handle all state updates afterwards, concurrently to one another to shave
// off some milliseconds from the commit operation. Also accumulate the code
// writes to run in parallel with the computations.
- start := time.Now()
var (
- code = s.db.DiskDB().NewBatch()
- lock sync.Mutex
+ start = time.Now()
root common.Hash
workers errgroup.Group
)
@@ -1183,21 +1179,11 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
// code didn't anticipate for.
workers.Go(func() error {
// Write the account trie changes, measuring the amount of wasted time
- newroot, set, err := s.trie.Commit(true)
- if err != nil {
- return err
- }
+ newroot, set := s.trie.Commit(true)
root = newroot
- // Merge the dirty nodes of account trie into global set
- lock.Lock()
- defer lock.Unlock()
-
- if set != nil {
- if err = nodes.Merge(set); err != nil {
- return err
- }
- accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size()
+ if err := merge(set); err != nil {
+ return err
}
s.AccountCommits = time.Since(start)
return nil
@@ -1215,49 +1201,29 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
}
// Write any contract code associated with the state object
obj := s.stateObjects[addr]
- if obj.code != nil && obj.dirtyCode {
- rawdb.WriteCode(code, common.BytesToHash(obj.CodeHash()), obj.code)
- obj.dirtyCode = false
+ if obj == nil {
+ return nil, errors.New("missing state object")
}
// Run the storage updates concurrently to one another
workers.Go(func() error {
// Write any storage changes in the state object to its storage trie
- set, err := obj.commit()
+ update, set, err := obj.commit()
if err != nil {
return err
}
- // Merge the dirty nodes of storage trie into global set. It is possible
- // that the account was destructed and then resurrected in the same block.
- // In this case, the node set is shared by both accounts.
- lock.Lock()
- defer lock.Unlock()
-
- if set != nil {
- if err = nodes.Merge(set); err != nil {
- return err
- }
- updates, deleted := set.Size()
- storageTrieNodesUpdated += updates
- storageTrieNodesDeleted += deleted
+ if err := merge(set); err != nil {
+ return err
}
+ lock.Lock()
+ updates[obj.addrHash] = update
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
+ lock.Unlock()
return nil
})
}
- // Schedule the code commits to run concurrently too. This shouldn't really
- // take much since we don't often commit code, but since it's disk access,
- // it's always yolo.
- workers.Go(func() error {
- if code.ValueSize() > 0 {
- if err := code.Write(); err != nil {
- log.Crit("Failed to commit dirty codes", "error", err)
- }
- }
- return nil
- })
// Wait for everything to finish and update the metrics
if err := workers.Wait(); err != nil {
- return common.Hash{}, err
+ return nil, err
}
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
storageUpdatedMeter.Mark(s.StorageUpdated.Load())
@@ -1271,53 +1237,78 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
s.StorageUpdated.Store(0)
s.StorageDeleted.Store(0)
- // If snapshotting is enabled, update the snapshot tree with this new version
- if s.snap != nil {
- start = time.Now()
- // Only update if there's a state transition (skip empty Clique blocks)
- if parent := s.snap.Root(); parent != root {
- if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil {
- log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
+ // Clear all internal flags and update state root at the end.
+ s.mutations = make(map[common.Address]*mutation)
+ s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
+
+ origin := s.originalRoot
+ s.originalRoot = root
+ return newStateUpdate(origin, root, deletes, updates, nodes), nil
+}
+
+// commitAndFlush is a wrapper of commit which also commits the state mutations
+// to the configured data stores.
+func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateUpdate, error) {
+ ret, err := s.commit(deleteEmptyObjects)
+ if err != nil {
+ return nil, err
+ }
+ // Commit dirty contract code if any exists
+ if db := s.db.DiskDB(); db != nil && len(ret.codes) > 0 {
+ batch := db.NewBatch()
+ for _, code := range ret.codes {
+ rawdb.WriteCode(batch, code.hash, code.blob)
+ }
+ if err := batch.Write(); err != nil {
+ return nil, err
+ }
+ }
+ if !ret.empty() {
+ // If snapshotting is enabled, update the snapshot tree with this new version
+ if s.snap != nil {
+ s.snap = nil
+
+ start := time.Now()
+ if err := s.snaps.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil {
+ log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err)
}
- // Keep TriesInMemory diff layers in the memory, persistent layer is 129th.
+ // Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
- if err := s.snaps.Cap(root, TriesInMemory); err != nil {
- log.Warn("Failed to cap snapshot tree", "root", root, "layers", TriesInMemory, "err", err)
+ if err := s.snaps.Cap(ret.root, TriesInMemory); err != nil {
+ log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err)
}
+ s.SnapshotCommits += time.Since(start)
}
- s.SnapshotCommits += time.Since(start)
- s.snap = nil
- }
- if root == (common.Hash{}) {
- root = types.EmptyRootHash
- }
- origin := s.originalRoot
- if origin == (common.Hash{}) {
- origin = types.EmptyRootHash
- }
- if root != origin {
- start = time.Now()
- set := triestate.New(s.accountsOrigin, s.storagesOrigin)
- if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil {
- return common.Hash{}, err
+ // If trie database is enabled, commit the state update as a new layer
+ if db := s.db.TrieDB(); db != nil {
+ start := time.Now()
+ set := triestate.New(ret.accountsOrigin, ret.storagesOrigin)
+ if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, set); err != nil {
+ return nil, err
+ }
+ s.TrieDBCommits += time.Since(start)
}
- s.originalRoot = root
- s.TrieDBCommits += time.Since(start)
+ }
+ return ret, err
+}
- if s.onCommit != nil {
- s.onCommit(set)
- }
+// Commit writes the state mutations into the configured data stores.
+//
+// Once the state is committed, tries cached in stateDB (including account
+// trie, storage tries) will no longer be functional. A new state instance
+// must be created with new root and updated database for accessing post-
+// commit states.
+//
+// The associated block number of the state transition is also provided
+// for more chain context.
+func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) {
+ ret, err := s.commitAndFlush(block, deleteEmptyObjects)
+ if err != nil {
+ return common.Hash{}, err
}
- // Clear all internal flags at the end of commit operation.
- s.accounts = make(map[common.Hash][]byte)
- s.storages = make(map[common.Hash]map[common.Hash][]byte)
- s.accountsOrigin = make(map[common.Address][]byte)
- s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
- s.mutations = make(map[common.Address]*mutation)
- s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
- return root, nil
+ return ret.root, nil
}
// Prepare handles the preparatory steps for executing a state transition with.
@@ -1399,41 +1390,9 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
return s.accessList.Contains(addr, slot)
}
-// convertAccountSet converts a provided account set from address keyed to hash keyed.
-func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount) map[common.Hash]struct{} {
- ret := make(map[common.Hash]struct{}, len(set))
- for addr := range set {
- obj, exist := s.stateObjects[addr]
- if !exist {
- ret[crypto.Keccak256Hash(addr[:])] = struct{}{}
- } else {
- ret[obj.addrHash] = struct{}{}
- }
- }
- return ret
-}
-
-// copySet returns a deep-copied set.
-func copySet[k comparable](set map[k][]byte) map[k][]byte {
- copied := make(map[k][]byte, len(set))
- for key, val := range set {
- copied[key] = common.CopyBytes(val)
- }
- return copied
-}
-
-// copy2DSet returns a two-dimensional deep-copied set.
-func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.Hash][]byte {
- copied := make(map[k]map[common.Hash][]byte, len(set))
- for addr, subset := range set {
- copied[addr] = make(map[common.Hash][]byte, len(subset))
- for key, val := range subset {
- copied[addr][key] = common.CopyBytes(val)
- }
- }
- return copied
-}
-
+// markDelete is invoked when an account is deleted but the deletion is
+// not yet committed. The pending mutation is cached and will be applied
+// all together
func (s *StateDB) markDelete(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go
index 6317681a7f..40b079cd8a 100644
--- a/core/state/statedb_fuzz_test.go
+++ b/core/state/statedb_fuzz_test.go
@@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
- "github.com/ethereum/go-ethereum/trie/triestate"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256"
@@ -180,9 +179,21 @@ func (test *stateTest) run() bool {
roots []common.Hash
accountList []map[common.Address][]byte
storageList []map[common.Address]map[common.Hash][]byte
- onCommit = func(states *triestate.Set) {
- accountList = append(accountList, copySet(states.Accounts))
- storageList = append(storageList, copy2DSet(states.Storages))
+ copyUpdate = func(update *stateUpdate) {
+ accounts := make(map[common.Address][]byte, len(update.accountsOrigin))
+ for key, val := range update.accountsOrigin {
+ accounts[key] = common.CopyBytes(val)
+ }
+ accountList = append(accountList, accounts)
+
+ storages := make(map[common.Address]map[common.Hash][]byte, len(update.storagesOrigin))
+ for addr, subset := range update.storagesOrigin {
+ storages[addr] = make(map[common.Hash][]byte, len(subset))
+ for key, val := range subset {
+ storages[addr][key] = common.CopyBytes(val)
+ }
+ }
+ storageList = append(storageList, storages)
}
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
@@ -210,8 +221,6 @@ func (test *stateTest) run() bool {
if err != nil {
panic(err)
}
- state.onCommit = onCommit
-
for i, action := range actions {
if i%test.chunk == 0 && i != 0 {
if byzantium {
@@ -227,14 +236,15 @@ func (test *stateTest) run() bool {
} else {
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
}
- nroot, err := state.Commit(0, true) // call commit at the block boundary
+ ret, err := state.commitAndFlush(0, true) // call commit at the block boundary
if err != nil {
panic(err)
}
- if nroot == root {
- return true // filter out non-change state transition
+ if ret.empty() {
+ return true
}
- roots = append(roots, nroot)
+ copyUpdate(ret)
+ roots = append(roots, ret.root)
}
for i := 0; i < len(test.actions); i++ {
root := types.EmptyRootHash
diff --git a/core/state/stateupdate.go b/core/state/stateupdate.go
new file mode 100644
index 0000000000..f3e6af997e
--- /dev/null
+++ b/core/state/stateupdate.go
@@ -0,0 +1,133 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package state
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/trie/trienode"
+)
+
+// contractCode represents a contract code with associated metadata.
+type contractCode struct {
+ hash common.Hash // hash is the cryptographic hash of the contract code.
+ blob []byte // blob is the binary representation of the contract code.
+}
+
+// accountDelete represents an operation for deleting an Ethereum account.
+type accountDelete struct {
+ address common.Address // address is the unique account identifier
+ origin []byte // origin is the original value of account data in slim-RLP encoding.
+ storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format.
+}
+
+// accountUpdate represents an operation for updating an Ethereum account.
+type accountUpdate struct {
+ address common.Address // address is the unique account identifier
+ data []byte // data is the slim-RLP encoded account data.
+ origin []byte // origin is the original value of account data in slim-RLP encoding.
+ code *contractCode // code represents mutated contract code; nil means it's not modified.
+ storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
+ storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format.
+}
+
+// stateUpdate represents the difference between two states resulting from state
+// execution. It contains information about mutated contract codes, accounts,
+// and storage slots, along with their original values.
+type stateUpdate struct {
+ originRoot common.Hash // hash of the state before applying mutation
+ root common.Hash // hash of the state after applying mutation
+ destructs map[common.Hash]struct{} // destructs contains the list of destructed accounts
+ accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
+ accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
+ storages map[common.Hash]map[common.Hash][]byte // storages stores mutated slots in 'prefix-zero-trimmed' RLP format
+ storagesOrigin map[common.Address]map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in 'prefix-zero-trimmed' RLP format
+ codes map[common.Address]contractCode // codes contains the set of dirty codes
+ nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
+}
+
+// empty returns a flag indicating the state transition is empty or not.
+func (sc *stateUpdate) empty() bool {
+ return sc.originRoot == sc.root
+}
+
+// newStateUpdate constructs a state update object, representing the differences
+// between two states by performing state execution. It aggregates the given
+// account deletions and account updates to form a comprehensive state update.
+func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate {
+ var (
+ destructs = make(map[common.Hash]struct{})
+ accounts = make(map[common.Hash][]byte)
+ accountsOrigin = make(map[common.Address][]byte)
+ storages = make(map[common.Hash]map[common.Hash][]byte)
+ storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
+ codes = make(map[common.Address]contractCode)
+ )
+ // Due to the fact that some accounts could be destructed and resurrected
+ // within the same block, the deletions must be aggregated first.
+ for addrHash, op := range deletes {
+ addr := op.address
+ destructs[addrHash] = struct{}{}
+ accountsOrigin[addr] = op.origin
+ if len(op.storagesOrigin) > 0 {
+ storagesOrigin[addr] = op.storagesOrigin
+ }
+ }
+ // Aggregate account updates then.
+ for addrHash, op := range updates {
+ // Aggregate dirty contract codes if they are available.
+ addr := op.address
+ if op.code != nil {
+ codes[addr] = *op.code
+ }
+ // Aggregate the account changes. The original account value will only
+ // be tracked if it's not present yet.
+ accounts[addrHash] = op.data
+ if _, found := accountsOrigin[addr]; !found {
+ accountsOrigin[addr] = op.origin
+ }
+ // Aggregate the storage changes. The original storage slot value will
+ // only be tracked if it's not present yet.
+ if len(op.storages) > 0 {
+ storages[addrHash] = op.storages
+ }
+ if len(op.storagesOrigin) > 0 {
+ origin := storagesOrigin[addr]
+ if origin == nil {
+ storagesOrigin[addr] = op.storagesOrigin
+ continue
+ }
+ for key, slot := range op.storagesOrigin {
+ if _, found := origin[key]; !found {
+ origin[key] = slot
+ }
+ }
+ storagesOrigin[addr] = origin
+ }
+ }
+ return &stateUpdate{
+ originRoot: types.TrieRootHash(originRoot),
+ root: types.TrieRootHash(root),
+ destructs: destructs,
+ accounts: accounts,
+ accountsOrigin: accountsOrigin,
+ storages: storages,
+ storagesOrigin: storagesOrigin,
+ codes: codes,
+ nodes: nodes,
+ }
+}
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/tracing/gen_balance_change_reason_stringer.go b/core/tracing/gen_balance_change_reason_stringer.go
new file mode 100644
index 0000000000..d3a515a12d
--- /dev/null
+++ b/core/tracing/gen_balance_change_reason_stringer.go
@@ -0,0 +1,37 @@
+// Code generated by "stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go"; DO NOT EDIT.
+
+package tracing
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[BalanceChangeUnspecified-0]
+ _ = x[BalanceIncreaseRewardMineUncle-1]
+ _ = x[BalanceIncreaseRewardMineBlock-2]
+ _ = x[BalanceIncreaseWithdrawal-3]
+ _ = x[BalanceIncreaseGenesisBalance-4]
+ _ = x[BalanceIncreaseRewardTransactionFee-5]
+ _ = x[BalanceDecreaseGasBuy-6]
+ _ = x[BalanceIncreaseGasReturn-7]
+ _ = x[BalanceIncreaseDaoContract-8]
+ _ = x[BalanceDecreaseDaoAccount-9]
+ _ = x[BalanceChangeTransfer-10]
+ _ = x[BalanceChangeTouchAccount-11]
+ _ = x[BalanceIncreaseSelfdestruct-12]
+ _ = x[BalanceDecreaseSelfdestruct-13]
+ _ = x[BalanceDecreaseSelfdestructBurn-14]
+}
+
+const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurn"
+
+var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400}
+
+func (i BalanceChangeReason) String() string {
+ if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {
+ return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _BalanceChangeReason_name[_BalanceChangeReason_index[i]:_BalanceChangeReason_index[i+1]]
+}
diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go
index 9b08cffd45..db058e847c 100644
--- a/core/tracing/hooks.go
+++ b/core/tracing/hooks.go
@@ -199,6 +199,8 @@ type Hooks struct {
// for tracing and reporting.
type BalanceChangeReason byte
+//go:generate stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go
+
const (
BalanceChangeUnspecified BalanceChangeReason = 0
diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go
index 85e13980be..d658a6daf4 100644
--- a/core/txpool/blobpool/blobpool_test.go
+++ b/core/txpool/blobpool/blobpool_test.go
@@ -143,7 +143,7 @@ func (bc *testBlockChain) CurrentFinalBlock() *types.Header {
}
}
-func (bt *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
+func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return nil
}
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index 6e5f6712f8..2ae38661f3 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -459,11 +459,11 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
// homestead rules.
type HomesteadSigner struct{ FrontierSigner }
-func (s HomesteadSigner) ChainID() *big.Int {
+func (hs HomesteadSigner) ChainID() *big.Int {
return nil
}
-func (s HomesteadSigner) Equal(s2 Signer) bool {
+func (hs HomesteadSigner) Equal(s2 Signer) bool {
_, ok := s2.(HomesteadSigner)
return ok
}
@@ -486,11 +486,11 @@ func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) {
// frontier rules.
type FrontierSigner struct{}
-func (s FrontierSigner) ChainID() *big.Int {
+func (fs FrontierSigner) ChainID() *big.Int {
return nil
}
-func (s FrontierSigner) Equal(s2 Signer) bool {
+func (fs FrontierSigner) Equal(s2 Signer) bool {
_, ok := s2.(FrontierSigner)
return ok
}
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/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go
index 9b26ab2928..85ba885d6f 100644
--- a/crypto/secp256k1/curve.go
+++ b/crypto/secp256k1/curve.go
@@ -79,52 +79,52 @@ type BitCurve struct {
BitSize int // the size of the underlying field
}
-func (BitCurve *BitCurve) Params() *elliptic.CurveParams {
+func (bitCurve *BitCurve) Params() *elliptic.CurveParams {
return &elliptic.CurveParams{
- P: BitCurve.P,
- N: BitCurve.N,
- B: BitCurve.B,
- Gx: BitCurve.Gx,
- Gy: BitCurve.Gy,
- BitSize: BitCurve.BitSize,
+ P: bitCurve.P,
+ N: bitCurve.N,
+ B: bitCurve.B,
+ Gx: bitCurve.Gx,
+ Gy: bitCurve.Gy,
+ BitSize: bitCurve.BitSize,
}
}
// IsOnCurve returns true if the given (x,y) lies on the BitCurve.
-func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
+func (bitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
// y² = x³ + b
y2 := new(big.Int).Mul(y, y) //y²
- y2.Mod(y2, BitCurve.P) //y²%P
+ y2.Mod(y2, bitCurve.P) //y²%P
x3 := new(big.Int).Mul(x, x) //x²
x3.Mul(x3, x) //x³
- x3.Add(x3, BitCurve.B) //x³+B
- x3.Mod(x3, BitCurve.P) //(x³+B)%P
+ x3.Add(x3, bitCurve.B) //x³+B
+ x3.Mod(x3, bitCurve.P) //(x³+B)%P
return x3.Cmp(y2) == 0
}
// affineFromJacobian reverses the Jacobian transform. See the comment at the
// top of the file.
-func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
+func (bitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
if z.Sign() == 0 {
return new(big.Int), new(big.Int)
}
- zinv := new(big.Int).ModInverse(z, BitCurve.P)
+ zinv := new(big.Int).ModInverse(z, bitCurve.P)
zinvsq := new(big.Int).Mul(zinv, zinv)
xOut = new(big.Int).Mul(x, zinvsq)
- xOut.Mod(xOut, BitCurve.P)
+ xOut.Mod(xOut, bitCurve.P)
zinvsq.Mul(zinvsq, zinv)
yOut = new(big.Int).Mul(y, zinvsq)
- yOut.Mod(yOut, BitCurve.P)
+ yOut.Mod(yOut, bitCurve.P)
return
}
// Add returns the sum of (x1,y1) and (x2,y2)
-func (BitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
+func (bitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
// If one point is at infinity, return the other point.
// Adding the point at infinity to any point will preserve the other point.
if x1.Sign() == 0 && y1.Sign() == 0 {
@@ -135,27 +135,27 @@ func (BitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
}
z := new(big.Int).SetInt64(1)
if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 {
- return BitCurve.affineFromJacobian(BitCurve.doubleJacobian(x1, y1, z))
+ return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z))
}
- return BitCurve.affineFromJacobian(BitCurve.addJacobian(x1, y1, z, x2, y2, z))
+ return bitCurve.affineFromJacobian(bitCurve.addJacobian(x1, y1, z, x2, y2, z))
}
// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and
// (x2, y2, z2) and returns their sum, also in Jacobian form.
-func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
+func (bitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) {
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
z1z1 := new(big.Int).Mul(z1, z1)
- z1z1.Mod(z1z1, BitCurve.P)
+ z1z1.Mod(z1z1, bitCurve.P)
z2z2 := new(big.Int).Mul(z2, z2)
- z2z2.Mod(z2z2, BitCurve.P)
+ z2z2.Mod(z2z2, bitCurve.P)
u1 := new(big.Int).Mul(x1, z2z2)
- u1.Mod(u1, BitCurve.P)
+ u1.Mod(u1, bitCurve.P)
u2 := new(big.Int).Mul(x2, z1z1)
- u2.Mod(u2, BitCurve.P)
+ u2.Mod(u2, bitCurve.P)
h := new(big.Int).Sub(u2, u1)
if h.Sign() == -1 {
- h.Add(h, BitCurve.P)
+ h.Add(h, bitCurve.P)
}
i := new(big.Int).Lsh(h, 1)
i.Mul(i, i)
@@ -163,13 +163,13 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int
s1 := new(big.Int).Mul(y1, z2)
s1.Mul(s1, z2z2)
- s1.Mod(s1, BitCurve.P)
+ s1.Mod(s1, bitCurve.P)
s2 := new(big.Int).Mul(y2, z1)
s2.Mul(s2, z1z1)
- s2.Mod(s2, BitCurve.P)
+ s2.Mod(s2, bitCurve.P)
r := new(big.Int).Sub(s2, s1)
if r.Sign() == -1 {
- r.Add(r, BitCurve.P)
+ r.Add(r, bitCurve.P)
}
r.Lsh(r, 1)
v := new(big.Int).Mul(u1, i)
@@ -179,7 +179,7 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int
x3.Sub(x3, j)
x3.Sub(x3, v)
x3.Sub(x3, v)
- x3.Mod(x3, BitCurve.P)
+ x3.Mod(x3, bitCurve.P)
y3 := new(big.Int).Set(r)
v.Sub(v, x3)
@@ -187,33 +187,33 @@ func (BitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int
s1.Mul(s1, j)
s1.Lsh(s1, 1)
y3.Sub(y3, s1)
- y3.Mod(y3, BitCurve.P)
+ y3.Mod(y3, bitCurve.P)
z3 := new(big.Int).Add(z1, z2)
z3.Mul(z3, z3)
z3.Sub(z3, z1z1)
if z3.Sign() == -1 {
- z3.Add(z3, BitCurve.P)
+ z3.Add(z3, bitCurve.P)
}
z3.Sub(z3, z2z2)
if z3.Sign() == -1 {
- z3.Add(z3, BitCurve.P)
+ z3.Add(z3, bitCurve.P)
}
z3.Mul(z3, h)
- z3.Mod(z3, BitCurve.P)
+ z3.Mod(z3, bitCurve.P)
return x3, y3, z3
}
// Double returns 2*(x,y)
-func (BitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
+func (bitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
z1 := new(big.Int).SetInt64(1)
- return BitCurve.affineFromJacobian(BitCurve.doubleJacobian(x1, y1, z1))
+ return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z1))
}
// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and
// returns its double, also in Jacobian form.
-func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
+func (bitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) {
// See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
a := new(big.Int).Mul(x, x) //X1²
@@ -231,30 +231,30 @@ func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int,
x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D
x3.Sub(f, x3) //F-2*D
- x3.Mod(x3, BitCurve.P)
+ x3.Mod(x3, bitCurve.P)
y3 := new(big.Int).Sub(d, x3) //D-X3
y3.Mul(e, y3) //E*(D-X3)
y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C
- y3.Mod(y3, BitCurve.P)
+ y3.Mod(y3, bitCurve.P)
z3 := new(big.Int).Mul(y, z) //Y1*Z1
z3.Mul(big.NewInt(2), z3) //3*Y1*Z1
- z3.Mod(z3, BitCurve.P)
+ z3.Mod(z3, bitCurve.P)
return x3, y3, z3
}
// ScalarBaseMult returns k*G, where G is the base point of the group and k is
// an integer in big-endian form.
-func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
- return BitCurve.ScalarMult(BitCurve.Gx, BitCurve.Gy, k)
+func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
+ return bitCurve.ScalarMult(bitCurve.Gx, bitCurve.Gy, k)
}
// Marshal converts a point into the form specified in section 4.3.6 of ANSI
// X9.62.
-func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
- byteLen := (BitCurve.BitSize + 7) >> 3
+func (bitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
+ byteLen := (bitCurve.BitSize + 7) >> 3
ret := make([]byte, 1+2*byteLen)
ret[0] = 4 // uncompressed point flag
readBits(x, ret[1:1+byteLen])
@@ -264,8 +264,8 @@ func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On
// error, x = nil.
-func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
- byteLen := (BitCurve.BitSize + 7) >> 3
+func (bitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
+ byteLen := (bitCurve.BitSize + 7) >> 3
if len(data) != 1+2*byteLen {
return
}
diff --git a/crypto/secp256k1/scalar_mult_cgo.go b/crypto/secp256k1/scalar_mult_cgo.go
index bdf8eeede7..d11c11faf8 100644
--- a/crypto/secp256k1/scalar_mult_cgo.go
+++ b/crypto/secp256k1/scalar_mult_cgo.go
@@ -21,7 +21,7 @@ extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned
*/
import "C"
-func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
+func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
// Ensure scalar is exactly 32 bytes. We pad always, even if
// scalar is 32 bytes long, to avoid a timing side channel.
if len(scalar) > 32 {
diff --git a/crypto/secp256k1/scalar_mult_nocgo.go b/crypto/secp256k1/scalar_mult_nocgo.go
index 22f53ac6ae..feb13a8dfd 100644
--- a/crypto/secp256k1/scalar_mult_nocgo.go
+++ b/crypto/secp256k1/scalar_mult_nocgo.go
@@ -9,6 +9,6 @@ package secp256k1
import "math/big"
-func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
+func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
panic("ScalarMult is not available when secp256k1 is built without cgo")
}
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/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go
index fecd83f276..2d6569e422 100644
--- a/eth/catalyst/simulated_beacon.go
+++ b/eth/catalyst/simulated_beacon.go
@@ -279,9 +279,12 @@ func (c *SimulatedBeacon) Rollback() {
// Fork sets the head to the provided hash.
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
+ // Ensure no pending transactions.
+ c.eth.TxPool().Sync()
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 {
return errors.New("pending block dirty")
}
+
parent := c.eth.BlockChain().GetBlockByHash(parentHash)
if parent == nil {
return errors.New("parent not found")
diff --git a/eth/downloader/api.go b/eth/downloader/api.go
index 90c36afbb5..ac175672a0 100644
--- a/eth/downloader/api.go
+++ b/eth/downloader/api.go
@@ -129,7 +129,7 @@ func (api *DownloaderAPI) eventLoop() {
}
}
-// Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished.
+// Syncing provides information when this node starts synchronising with the Ethereum network and when it's finished.
func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) {
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
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/gasprice/feehistory.go b/eth/gasprice/feehistory.go
index d039bcb401..1e625e21c0 100644
--- a/eth/gasprice/feehistory.go
+++ b/eth/gasprice/feehistory.go
@@ -44,7 +44,8 @@ const (
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
// for the fee history calculation (mostly relevant for LES).
maxBlockFetchers = 4
- maxQueryLimit = 100
+ // maxQueryLimit is the max number of requested percentiles.
+ maxQueryLimit = 100
)
// blockFees represents a single block for processing
diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go
index 53cba5d115..88d7d34dcc 100644
--- a/eth/protocols/snap/sync.go
+++ b/eth/protocols/snap/sync.go
@@ -3250,9 +3250,9 @@ func (t *healRequestSort) Merge() []TrieNodePathSet {
// sortByAccountPath takes hashes and paths, and sorts them. After that, it generates
// the TrieNodePaths and merges paths which belongs to the same account path.
func sortByAccountPath(paths []string, hashes []common.Hash) ([]string, []common.Hash, []trie.SyncPath, []TrieNodePathSet) {
- var syncPaths []trie.SyncPath
- for _, path := range paths {
- syncPaths = append(syncPaths, trie.NewSyncPath([]byte(path)))
+ syncPaths := make([]trie.SyncPath, len(paths))
+ for i, path := range paths {
+ syncPaths[i] = trie.NewSyncPath([]byte(path))
}
n := &healRequestSort{paths, hashes, syncPaths}
sort.Sort(n)
diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go
index 5f6826373a..82360ae0e3 100644
--- a/eth/protocols/snap/sync_test.go
+++ b/eth/protocols/snap/sync_test.go
@@ -1525,7 +1525,7 @@ func makeAccountTrieNoStorage(n int, scheme string) (string, *trie.Trie, []*kv)
// Commit the state changes into db and re-create the trie
// for accessing later.
- root, nodes, _ := accTrie.Commit(false)
+ root, nodes := accTrie.Commit(false)
db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
accTrie, _ = trie.New(trie.StateTrieID(root), db)
@@ -1587,7 +1587,7 @@ func makeBoundaryAccountTrie(scheme string, n int) (string, *trie.Trie, []*kv) {
// Commit the state changes into db and re-create the trie
// for accessing later.
- root, nodes, _ := accTrie.Commit(false)
+ root, nodes := accTrie.Commit(false)
db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
accTrie, _ = trie.New(trie.StateTrieID(root), db)
@@ -1633,7 +1633,7 @@ func makeAccountTrieWithStorageWithUniqueStorage(scheme string, accounts, slots
slices.SortFunc(entries, (*kv).cmp)
// Commit account trie
- root, set, _ := accTrie.Commit(true)
+ root, set := accTrie.Commit(true)
nodes.Merge(set)
// Commit gathered dirty nodes into database
@@ -1700,7 +1700,7 @@ func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, bounda
slices.SortFunc(entries, (*kv).cmp)
// Commit account trie
- root, set, _ := accTrie.Commit(true)
+ root, set := accTrie.Commit(true)
nodes.Merge(set)
// Commit gathered dirty nodes into database
@@ -1742,7 +1742,7 @@ func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *triedb.Datab
entries = append(entries, elem)
}
slices.SortFunc(entries, (*kv).cmp)
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
return root, nodes, entries
}
@@ -1793,7 +1793,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *triedb.Database) (com
entries = append(entries, elem)
}
slices.SortFunc(entries, (*kv).cmp)
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
return root, nodes, entries
}
@@ -1825,7 +1825,7 @@ func makeUnevenStorageTrie(owner common.Hash, slots int, db *triedb.Database) (c
}
}
slices.SortFunc(entries, (*kv).cmp)
- root, nodes, _ := tr.Commit(false)
+ root, nodes := tr.Commit(false)
return root, nodes, entries
}
diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go
index 120cb585c7..6fbb50848d 100644
--- a/eth/tracers/api_test.go
+++ b/eth/tracers/api_test.go
@@ -312,7 +312,7 @@ func TestTraceCall(t *testing.T) {
config: &TraceCallConfig{TxIndex: uintPtr(1)},
expectErr: fmt.Errorf("tracing failed: insufficient funds for gas * price + value: address %s have 1000000000000000000 want 1000000000000000100", accounts[2].addr),
},
- // After the target transaction, should be succeed
+ // After the target transaction, should be succeeded
{
blockNumber: rpc.BlockNumber(genBlocks - 1),
call: ethapi.TransactionArgs{
diff --git a/eth/tracers/internal/tracetest/supply_test.go b/eth/tracers/internal/tracetest/supply_test.go
new file mode 100644
index 0000000000..d608b1e002
--- /dev/null
+++ b/eth/tracers/internal/tracetest/supply_test.go
@@ -0,0 +1,613 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package tracetest
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "math/big"
+ "os"
+ "path"
+ "path/filepath"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/consensus/beacon"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+ "github.com/ethereum/go-ethereum/params"
+
+ // Force-load live packages, to trigger registration
+ _ "github.com/ethereum/go-ethereum/eth/tracers/live"
+)
+
+type supplyInfoIssuance struct {
+ GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"`
+ Reward *hexutil.Big `json:"reward,omitempty"`
+ Withdrawals *hexutil.Big `json:"withdrawals,omitempty"`
+}
+
+type supplyInfoBurn struct {
+ EIP1559 *hexutil.Big `json:"1559,omitempty"`
+ Blob *hexutil.Big `json:"blob,omitempty"`
+ Misc *hexutil.Big `json:"misc,omitempty"`
+}
+
+type supplyInfo struct {
+ Issuance *supplyInfoIssuance `json:"issuance,omitempty"`
+ Burn *supplyInfoBurn `json:"burn,omitempty"`
+
+ // Block info
+ Number uint64 `json:"blockNumber"`
+ Hash common.Hash `json:"hash"`
+ ParentHash common.Hash `json:"parentHash"`
+}
+
+func emptyBlockGenerationFunc(b *core.BlockGen) {}
+
+func TestSupplyOmittedFields(t *testing.T) {
+ var (
+ config = *params.MergedTestChainConfig
+ gspec = &core.Genesis{
+ Config: &config,
+ }
+ )
+
+ gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
+
+ out, _, err := testSupplyTracer(t, gspec, func(b *core.BlockGen) {
+ b.SetPoS()
+ })
+ if err != nil {
+ t.Fatalf("failed to test supply tracer: %v", err)
+ }
+
+ expected := supplyInfo{
+ Number: 0,
+ Hash: common.HexToHash("0x52f276d96f0afaaf2c3cb358868bdc2779c4b0cb8de3e7e5302e247c0b66a703"),
+ ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
+ }
+ actual := out[expected.Number]
+
+ compareAsJSON(t, expected, actual)
+}
+
+func TestSupplyGenesisAlloc(t *testing.T) {
+ var (
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ addr2 = crypto.PubkeyToAddress(key2.PublicKey)
+ eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
+
+ config = *params.AllEthashProtocolChanges
+
+ gspec = &core.Genesis{
+ Config: &config,
+ Alloc: types.GenesisAlloc{
+ addr1: {Balance: eth1},
+ addr2: {Balance: eth1},
+ },
+ }
+ )
+
+ expected := supplyInfo{
+ Issuance: &supplyInfoIssuance{
+ GenesisAlloc: (*hexutil.Big)(new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))),
+ },
+ Number: 0,
+ Hash: common.HexToHash("0xbcc9466e9fc6a8b56f4b29ca353a421ff8b51a0c1a58ca4743b427605b08f2ca"),
+ ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"),
+ }
+
+ out, _, err := testSupplyTracer(t, gspec, emptyBlockGenerationFunc)
+ if err != nil {
+ t.Fatalf("failed to test supply tracer: %v", err)
+ }
+
+ actual := out[expected.Number]
+
+ compareAsJSON(t, expected, actual)
+}
+
+func TestSupplyRewards(t *testing.T) {
+ var (
+ config = *params.AllEthashProtocolChanges
+
+ gspec = &core.Genesis{
+ Config: &config,
+ }
+ )
+
+ expected := supplyInfo{
+ Issuance: &supplyInfoIssuance{
+ Reward: (*hexutil.Big)(new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))),
+ },
+ Number: 1,
+ Hash: common.HexToHash("0xcbb08370505be503dafedc4e96d139ea27aba3cbc580148568b8a307b3f51052"),
+ ParentHash: common.HexToHash("0xadeda0a83e337b6c073e3f0e9a17531a04009b397a9588c093b628f21b8bc5a3"),
+ }
+
+ out, _, err := testSupplyTracer(t, gspec, emptyBlockGenerationFunc)
+ if err != nil {
+ t.Fatalf("failed to test supply tracer: %v", err)
+ }
+
+ actual := out[expected.Number]
+
+ compareAsJSON(t, expected, actual)
+}
+
+func TestSupplyEip1559Burn(t *testing.T) {
+ var (
+ config = *params.AllEthashProtocolChanges
+
+ aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
+ // A sender who makes transactions, has some eth1
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei))
+ eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
+
+ gspec = &core.Genesis{
+ Config: &config,
+ BaseFee: big.NewInt(params.InitialBaseFee),
+ Alloc: types.GenesisAlloc{
+ addr1: {Balance: eth1},
+ },
+ }
+ )
+
+ signer := types.LatestSigner(gspec.Config)
+
+ eip1559BlockGenerationFunc := func(b *core.BlockGen) {
+ txdata := &types.DynamicFeeTx{
+ ChainID: gspec.Config.ChainID,
+ Nonce: 0,
+ To: &aa,
+ Gas: 21000,
+ GasFeeCap: gwei5,
+ GasTipCap: big.NewInt(2),
+ }
+ tx := types.NewTx(txdata)
+ tx, _ = types.SignTx(tx, signer, key1)
+
+ b.AddTx(tx)
+ }
+
+ out, chain, err := testSupplyTracer(t, gspec, eip1559BlockGenerationFunc)
+ if err != nil {
+ t.Fatalf("failed to test supply tracer: %v", err)
+ }
+ var (
+ head = chain.CurrentBlock()
+ reward = new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))
+ burn = new(big.Int).Mul(big.NewInt(21000), head.BaseFee)
+ expected = supplyInfo{
+ Issuance: &supplyInfoIssuance{
+ Reward: (*hexutil.Big)(reward),
+ },
+ Burn: &supplyInfoBurn{
+ EIP1559: (*hexutil.Big)(burn),
+ },
+ Number: 1,
+ Hash: head.Hash(),
+ ParentHash: head.ParentHash,
+ }
+ )
+
+ actual := out[expected.Number]
+ compareAsJSON(t, expected, actual)
+}
+
+func TestSupplyWithdrawals(t *testing.T) {
+ var (
+ config = *params.MergedTestChainConfig
+ gspec = &core.Genesis{
+ Config: &config,
+ }
+ )
+
+ withdrawalsBlockGenerationFunc := func(b *core.BlockGen) {
+ b.SetPoS()
+
+ b.AddWithdrawal(&types.Withdrawal{
+ Validator: 42,
+ Address: common.Address{0xee},
+ Amount: 1337,
+ })
+ }
+
+ out, chain, err := testSupplyTracer(t, gspec, withdrawalsBlockGenerationFunc)
+ if err != nil {
+ t.Fatalf("failed to test supply tracer: %v", err)
+ }
+
+ var (
+ head = chain.CurrentBlock()
+ expected = supplyInfo{
+ Issuance: &supplyInfoIssuance{
+ Withdrawals: (*hexutil.Big)(big.NewInt(1337000000000)),
+ },
+ Number: 1,
+ Hash: head.Hash(),
+ ParentHash: head.ParentHash,
+ }
+ actual = out[expected.Number]
+ )
+
+ compareAsJSON(t, expected, actual)
+}
+
+// Tests fund retrieval after contract's selfdestruct.
+// Contract A calls contract B which selfdestructs, but B receives eth1
+// after the selfdestruct opcode executes from Contract A.
+// Because Contract B is removed only at the end of the transaction
+// the ether sent in between is burnt before Cancun hard fork.
+func TestSupplySelfdestruct(t *testing.T) {
+ var (
+ config = *params.TestChainConfig
+
+ aa = common.HexToAddress("0x1111111111111111111111111111111111111111")
+ bb = common.HexToAddress("0x2222222222222222222222222222222222222222")
+ dad = common.HexToAddress("0x0000000000000000000000000000000000000dad")
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei))
+ eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
+
+ gspec = &core.Genesis{
+ Config: &config,
+ BaseFee: big.NewInt(params.InitialBaseFee),
+ Alloc: types.GenesisAlloc{
+ addr1: {Balance: eth1},
+ aa: {
+ Code: common.FromHex("0x61face60f01b6000527322222222222222222222222222222222222222226000806002600080855af160008103603457600080fd5b60008060008034865af1905060008103604c57600080fd5b5050"),
+ // Nonce: 0,
+ Balance: big.NewInt(0),
+ },
+ bb: {
+ Code: common.FromHex("0x6000357fface000000000000000000000000000000000000000000000000000000000000808203602f57610dad80ff5b5050"),
+ Nonce: 0,
+ Balance: eth1,
+ },
+ },
+ }
+ )
+
+ gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
+
+ signer := types.LatestSigner(gspec.Config)
+
+ testBlockGenerationFunc := func(b *core.BlockGen) {
+ b.SetPoS()
+
+ txdata := &types.LegacyTx{
+ Nonce: 0,
+ To: &aa,
+ Value: gwei5,
+ Gas: 150000,
+ GasPrice: gwei5,
+ Data: []byte{},
+ }
+
+ tx := types.NewTx(txdata)
+ tx, _ = types.SignTx(tx, signer, key1)
+
+ b.AddTx(tx)
+ }
+
+ // 1. Test pre Cancun
+ preCancunOutput, preCancunChain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc)
+ if err != nil {
+ t.Fatalf("Pre-cancun failed to test supply tracer: %v", err)
+ }
+
+ // Check balance at state:
+ // 1. 0x0000...000dad has 1 ether
+ // 2. A has 0 ether
+ // 3. B has 0 ether
+ statedb, _ := preCancunChain.State()
+ if got, exp := statedb.GetBalance(dad), eth1; got.CmpBig(exp) != 0 {
+ t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", dad, got, exp)
+ }
+ if got, exp := statedb.GetBalance(aa), big.NewInt(0); got.CmpBig(exp) != 0 {
+ t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", aa, got, exp)
+ }
+ if got, exp := statedb.GetBalance(bb), big.NewInt(0); got.CmpBig(exp) != 0 {
+ t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", bb, got, exp)
+ }
+
+ head := preCancunChain.CurrentBlock()
+ // Check live trace output
+ expected := supplyInfo{
+ Burn: &supplyInfoBurn{
+ EIP1559: (*hexutil.Big)(big.NewInt(55289500000000)),
+ Misc: (*hexutil.Big)(big.NewInt(5000000000)),
+ },
+ Number: 1,
+ Hash: head.Hash(),
+ ParentHash: head.ParentHash,
+ }
+
+ actual := preCancunOutput[expected.Number]
+
+ compareAsJSON(t, expected, actual)
+
+ // 2. Test post Cancun
+ cancunTime := uint64(0)
+ gspec.Config.ShanghaiTime = &cancunTime
+ gspec.Config.CancunTime = &cancunTime
+
+ postCancunOutput, postCancunChain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc)
+ if err != nil {
+ t.Fatalf("Post-cancun failed to test supply tracer: %v", err)
+ }
+
+ // Check balance at state:
+ // 1. 0x0000...000dad has 1 ether
+ // 3. A has 0 ether
+ // 3. B has 5 gwei
+ statedb, _ = postCancunChain.State()
+ if got, exp := statedb.GetBalance(dad), eth1; got.CmpBig(exp) != 0 {
+ t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", dad, got, exp)
+ }
+ if got, exp := statedb.GetBalance(aa), big.NewInt(0); got.CmpBig(exp) != 0 {
+ t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", aa, got, exp)
+ }
+ if got, exp := statedb.GetBalance(bb), gwei5; got.CmpBig(exp) != 0 {
+ t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", bb, got, exp)
+ }
+
+ // Check live trace output
+ head = postCancunChain.CurrentBlock()
+ expected = supplyInfo{
+ Burn: &supplyInfoBurn{
+ EIP1559: (*hexutil.Big)(big.NewInt(55289500000000)),
+ },
+ Number: 1,
+ Hash: head.Hash(),
+ ParentHash: head.ParentHash,
+ }
+
+ actual = postCancunOutput[expected.Number]
+
+ compareAsJSON(t, expected, actual)
+}
+
+// Tests selfdestructing contract to send its balance to itself (burn).
+// 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.
+// - Contract D calls C and reverts (Burn amount of C
+// has to be reverted as well).
+func TestSupplySelfdestructItselfAndRevert(t *testing.T) {
+ var (
+ config = *params.TestChainConfig
+
+ aa = common.HexToAddress("0x1111111111111111111111111111111111111111")
+ bb = common.HexToAddress("0x2222222222222222222222222222222222222222")
+ cc = common.HexToAddress("0x3333333333333333333333333333333333333333")
+ dd = common.HexToAddress("0x4444444444444444444444444444444444444444")
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei))
+ eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
+ eth2 = new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))
+ eth5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.Ether))
+
+ gspec = &core.Genesis{
+ Config: &config,
+ // BaseFee: big.NewInt(params.InitialBaseFee),
+ Alloc: types.GenesisAlloc{
+ addr1: {Balance: eth1},
+ aa: {
+ // Contract code in YUL:
+ //
+ // object "ContractA" {
+ // code {
+ // let B := 0x2222222222222222222222222222222222222222
+ // let D := 0x4444444444444444444444444444444444444444
+
+ // // Call to Contract B
+ // let resB:= call(gas(), B, 0, 0x0, 0x0, 0, 0)
+
+ // // Call to Contract D
+ // let resD := call(gas(), D, 0, 0x0, 0x0, 0, 0)
+ // }
+ // }
+ Code: common.FromHex("0x73222222222222222222222222222222222222222273444444444444444444444444444444444444444460006000600060006000865af160006000600060006000865af150505050"),
+ Balance: common.Big0,
+ },
+ bb: {
+ // Contract code in YUL:
+ //
+ // object "ContractB" {
+ // code {
+ // let self := address()
+ // selfdestruct(self)
+ // }
+ // }
+ Code: common.FromHex("0x3080ff50"),
+ Balance: eth5,
+ },
+ cc: {
+ Code: common.FromHex("0x3080ff50"),
+ Balance: eth1,
+ },
+ dd: {
+ // Contract code in YUL:
+ //
+ // object "ContractD" {
+ // code {
+ // let C := 0x3333333333333333333333333333333333333333
+
+ // // Call to Contract C
+ // let resC := call(gas(), C, 0, 0x0, 0x0, 0, 0)
+
+ // // Revert
+ // revert(0, 0)
+ // }
+ // }
+ Code: common.FromHex("0x73333333333333333333333333333333333333333360006000600060006000855af160006000fd5050"),
+ Balance: eth2,
+ },
+ },
+ }
+ )
+
+ gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
+
+ signer := types.LatestSigner(gspec.Config)
+
+ testBlockGenerationFunc := func(b *core.BlockGen) {
+ b.SetPoS()
+
+ txdata := &types.LegacyTx{
+ Nonce: 0,
+ To: &aa,
+ Value: common.Big0,
+ Gas: 150000,
+ GasPrice: gwei5,
+ Data: []byte{},
+ }
+
+ tx := types.NewTx(txdata)
+ tx, _ = types.SignTx(tx, signer, key1)
+
+ b.AddTx(tx)
+ }
+
+ output, chain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc)
+ if err != nil {
+ t.Fatalf("failed to test supply tracer: %v", err)
+ }
+
+ // Check balance at state:
+ // 1. A has 0 ether
+ // 2. B has 0 ether, burned
+ // 3. C has 2 ether, selfdestructed but parent D reverted
+ // 4. D has 1 ether, reverted
+ statedb, _ := chain.State()
+ if got, exp := statedb.GetBalance(aa), common.Big0; got.CmpBig(exp) != 0 {
+ t.Fatalf("address \"%v\" balance, got %v exp %v\n", aa, got, exp)
+ }
+ if got, exp := statedb.GetBalance(bb), common.Big0; got.CmpBig(exp) != 0 {
+ t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp)
+ }
+ if got, exp := statedb.GetBalance(cc), eth1; got.CmpBig(exp) != 0 {
+ t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp)
+ }
+ if got, exp := statedb.GetBalance(dd), eth2; got.CmpBig(exp) != 0 {
+ t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp)
+ }
+
+ // Check live trace output
+ block := chain.GetBlockByNumber(1)
+
+ expected := supplyInfo{
+ Burn: &supplyInfoBurn{
+ EIP1559: (*hexutil.Big)(new(big.Int).Mul(block.BaseFee(), big.NewInt(int64(block.GasUsed())))),
+ Misc: (*hexutil.Big)(eth5), // 5ETH burned from contract B
+ },
+ Number: 1,
+ Hash: block.Hash(),
+ ParentHash: block.ParentHash(),
+ }
+
+ actual := output[expected.Number]
+
+ compareAsJSON(t, expected, actual)
+}
+
+func testSupplyTracer(t *testing.T, genesis *core.Genesis, gen func(*core.BlockGen)) ([]supplyInfo, *core.BlockChain, error) {
+ var (
+ engine = beacon.New(ethash.NewFaker())
+ )
+
+ traceOutputPath := filepath.ToSlash(t.TempDir())
+ traceOutputFilename := path.Join(traceOutputPath, "supply.jsonl")
+
+ // Load supply tracer
+ tracer, err := tracers.LiveDirectory.New("supply", json.RawMessage(fmt.Sprintf(`{"path":"%s"}`, traceOutputPath)))
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to create call tracer: %v", err)
+ }
+
+ chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), core.DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, engine, vm.Config{Tracer: tracer}, nil, nil)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to create tester chain: %v", err)
+ }
+ defer chain.Stop()
+
+ _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 1, func(i int, b *core.BlockGen) {
+ b.SetCoinbase(common.Address{1})
+ gen(b)
+ })
+
+ if n, err := chain.InsertChain(blocks); err != nil {
+ return nil, chain, fmt.Errorf("block %d: failed to insert into chain: %v", n, err)
+ }
+
+ // Check and compare the results
+ file, err := os.OpenFile(traceOutputFilename, os.O_RDONLY, 0666)
+ if err != nil {
+ return nil, chain, fmt.Errorf("failed to open output file: %v", err)
+ }
+ defer file.Close()
+
+ var output []supplyInfo
+ scanner := bufio.NewScanner(file)
+
+ for scanner.Scan() {
+ blockBytes := scanner.Bytes()
+
+ var info supplyInfo
+ if err := json.Unmarshal(blockBytes, &info); err != nil {
+ return nil, chain, fmt.Errorf("failed to unmarshal result: %v", err)
+ }
+
+ output = append(output, info)
+ }
+
+ return output, chain, nil
+}
+
+func compareAsJSON(t *testing.T, expected interface{}, actual interface{}) {
+ want, err := json.Marshal(expected)
+ if err != nil {
+ t.Fatalf("failed to marshal expected value to JSON: %v", err)
+ }
+
+ have, err := json.Marshal(actual)
+ if err != nil {
+ t.Fatalf("failed to marshal actual value to JSON: %v", err)
+ }
+
+ if !bytes.Equal(want, have) {
+ t.Fatalf("incorrect supply info: expected %s, got %s", string(want), string(have))
+ }
+}
diff --git a/eth/tracers/live/gen_supplyinfoburn.go b/eth/tracers/live/gen_supplyinfoburn.go
new file mode 100644
index 0000000000..d01eda3975
--- /dev/null
+++ b/eth/tracers/live/gen_supplyinfoburn.go
@@ -0,0 +1,49 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package live
+
+import (
+ "encoding/json"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*supplyInfoBurnMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (s supplyInfoBurn) MarshalJSON() ([]byte, error) {
+ type supplyInfoBurn struct {
+ EIP1559 *hexutil.Big `json:"1559,omitempty"`
+ Blob *hexutil.Big `json:"blob,omitempty"`
+ Misc *hexutil.Big `json:"misc,omitempty"`
+ }
+ var enc supplyInfoBurn
+ enc.EIP1559 = (*hexutil.Big)(s.EIP1559)
+ enc.Blob = (*hexutil.Big)(s.Blob)
+ enc.Misc = (*hexutil.Big)(s.Misc)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (s *supplyInfoBurn) UnmarshalJSON(input []byte) error {
+ type supplyInfoBurn struct {
+ EIP1559 *hexutil.Big `json:"1559,omitempty"`
+ Blob *hexutil.Big `json:"blob,omitempty"`
+ Misc *hexutil.Big `json:"misc,omitempty"`
+ }
+ var dec supplyInfoBurn
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.EIP1559 != nil {
+ s.EIP1559 = (*big.Int)(dec.EIP1559)
+ }
+ if dec.Blob != nil {
+ s.Blob = (*big.Int)(dec.Blob)
+ }
+ if dec.Misc != nil {
+ s.Misc = (*big.Int)(dec.Misc)
+ }
+ return nil
+}
diff --git a/eth/tracers/live/gen_supplyinfoissuance.go b/eth/tracers/live/gen_supplyinfoissuance.go
new file mode 100644
index 0000000000..e2536ee325
--- /dev/null
+++ b/eth/tracers/live/gen_supplyinfoissuance.go
@@ -0,0 +1,49 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package live
+
+import (
+ "encoding/json"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*supplyInfoIssuanceMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (s supplyInfoIssuance) MarshalJSON() ([]byte, error) {
+ type supplyInfoIssuance struct {
+ GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"`
+ Reward *hexutil.Big `json:"reward,omitempty"`
+ Withdrawals *hexutil.Big `json:"withdrawals,omitempty"`
+ }
+ var enc supplyInfoIssuance
+ enc.GenesisAlloc = (*hexutil.Big)(s.GenesisAlloc)
+ enc.Reward = (*hexutil.Big)(s.Reward)
+ enc.Withdrawals = (*hexutil.Big)(s.Withdrawals)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (s *supplyInfoIssuance) UnmarshalJSON(input []byte) error {
+ type supplyInfoIssuance struct {
+ GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"`
+ Reward *hexutil.Big `json:"reward,omitempty"`
+ Withdrawals *hexutil.Big `json:"withdrawals,omitempty"`
+ }
+ var dec supplyInfoIssuance
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.GenesisAlloc != nil {
+ s.GenesisAlloc = (*big.Int)(dec.GenesisAlloc)
+ }
+ if dec.Reward != nil {
+ s.Reward = (*big.Int)(dec.Reward)
+ }
+ if dec.Withdrawals != nil {
+ s.Withdrawals = (*big.Int)(dec.Withdrawals)
+ }
+ return nil
+}
diff --git a/eth/tracers/live/supply.go b/eth/tracers/live/supply.go
new file mode 100644
index 0000000000..936ffb9472
--- /dev/null
+++ b/eth/tracers/live/supply.go
@@ -0,0 +1,310 @@
+package live
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+ "path/filepath"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
+ "github.com/ethereum/go-ethereum/core/tracing"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+ "github.com/ethereum/go-ethereum/log"
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+func init() {
+ tracers.LiveDirectory.Register("supply", newSupply)
+}
+
+type supplyInfoIssuance struct {
+ GenesisAlloc *big.Int `json:"genesisAlloc,omitempty"`
+ Reward *big.Int `json:"reward,omitempty"`
+ Withdrawals *big.Int `json:"withdrawals,omitempty"`
+}
+
+//go:generate go run github.com/fjl/gencodec -type supplyInfoIssuance -field-override supplyInfoIssuanceMarshaling -out gen_supplyinfoissuance.go
+type supplyInfoIssuanceMarshaling struct {
+ GenesisAlloc *hexutil.Big
+ Reward *hexutil.Big
+ Withdrawals *hexutil.Big
+}
+
+type supplyInfoBurn struct {
+ EIP1559 *big.Int `json:"1559,omitempty"`
+ Blob *big.Int `json:"blob,omitempty"`
+ Misc *big.Int `json:"misc,omitempty"`
+}
+
+//go:generate go run github.com/fjl/gencodec -type supplyInfoBurn -field-override supplyInfoBurnMarshaling -out gen_supplyinfoburn.go
+type supplyInfoBurnMarshaling struct {
+ EIP1559 *hexutil.Big
+ Blob *hexutil.Big
+ Misc *hexutil.Big
+}
+
+type supplyInfo struct {
+ Issuance *supplyInfoIssuance `json:"issuance,omitempty"`
+ Burn *supplyInfoBurn `json:"burn,omitempty"`
+
+ // Block info
+ Number uint64 `json:"blockNumber"`
+ Hash common.Hash `json:"hash"`
+ ParentHash common.Hash `json:"parentHash"`
+}
+
+type supplyTxCallstack struct {
+ calls []supplyTxCallstack
+ burn *big.Int
+}
+
+type supply struct {
+ delta supplyInfo
+ txCallstack []supplyTxCallstack // Callstack for current transaction
+ logger *lumberjack.Logger
+}
+
+type supplyTracerConfig struct {
+ Path string `json:"path"` // Path to the directory where the tracer logs will be stored
+ MaxSize int `json:"maxSize"` // MaxSize is the maximum size in megabytes of the tracer log file before it gets rotated. It defaults to 100 megabytes.
+}
+
+func newSupply(cfg json.RawMessage) (*tracing.Hooks, error) {
+ var config supplyTracerConfig
+ if cfg != nil {
+ if err := json.Unmarshal(cfg, &config); err != nil {
+ return nil, fmt.Errorf("failed to parse config: %v", err)
+ }
+ }
+ if config.Path == "" {
+ return nil, errors.New("supply tracer output path is required")
+ }
+
+ // Store traces in a rotating file
+ logger := &lumberjack.Logger{
+ Filename: filepath.Join(config.Path, "supply.jsonl"),
+ }
+ if config.MaxSize > 0 {
+ logger.MaxSize = config.MaxSize
+ }
+
+ t := &supply{
+ delta: newSupplyInfo(),
+ logger: logger,
+ }
+ return &tracing.Hooks{
+ OnBlockStart: t.OnBlockStart,
+ OnBlockEnd: t.OnBlockEnd,
+ OnGenesisBlock: t.OnGenesisBlock,
+ OnTxStart: t.OnTxStart,
+ OnBalanceChange: t.OnBalanceChange,
+ OnEnter: t.OnEnter,
+ OnExit: t.OnExit,
+ OnClose: t.OnClose,
+ }, nil
+}
+
+func newSupplyInfo() supplyInfo {
+ return supplyInfo{
+ Issuance: &supplyInfoIssuance{
+ GenesisAlloc: big.NewInt(0),
+ Reward: big.NewInt(0),
+ Withdrawals: big.NewInt(0),
+ },
+ Burn: &supplyInfoBurn{
+ EIP1559: big.NewInt(0),
+ Blob: big.NewInt(0),
+ Misc: big.NewInt(0),
+ },
+
+ Number: 0,
+ Hash: common.Hash{},
+ ParentHash: common.Hash{},
+ }
+}
+
+func (s *supply) resetDelta() {
+ s.delta = newSupplyInfo()
+}
+
+func (s *supply) OnBlockStart(ev tracing.BlockEvent) {
+ s.resetDelta()
+
+ s.delta.Number = ev.Block.NumberU64()
+ s.delta.Hash = ev.Block.Hash()
+ s.delta.ParentHash = ev.Block.ParentHash()
+
+ // Calculate Burn for this block
+ if ev.Block.BaseFee() != nil {
+ burn := new(big.Int).Mul(new(big.Int).SetUint64(ev.Block.GasUsed()), ev.Block.BaseFee())
+ s.delta.Burn.EIP1559 = burn
+ }
+ // Blob burnt gas
+ if blobGas := ev.Block.BlobGasUsed(); blobGas != nil && *blobGas > 0 && ev.Block.ExcessBlobGas() != nil {
+ var (
+ excess = *ev.Block.ExcessBlobGas()
+ baseFee = eip4844.CalcBlobFee(excess)
+ burn = new(big.Int).Mul(new(big.Int).SetUint64(*blobGas), baseFee)
+ )
+ s.delta.Burn.Blob = burn
+ }
+}
+
+func (s *supply) OnBlockEnd(err error) {
+ s.write(s.delta)
+}
+
+func (s *supply) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) {
+ s.resetDelta()
+
+ s.delta.Number = b.NumberU64()
+ s.delta.Hash = b.Hash()
+ s.delta.ParentHash = b.ParentHash()
+
+ // Initialize supply with total allocation in genesis block
+ for _, account := range alloc {
+ s.delta.Issuance.GenesisAlloc.Add(s.delta.Issuance.GenesisAlloc, account.Balance)
+ }
+
+ s.write(s.delta)
+}
+
+func (s *supply) OnBalanceChange(a common.Address, prevBalance, newBalance *big.Int, reason tracing.BalanceChangeReason) {
+ diff := new(big.Int).Sub(newBalance, prevBalance)
+
+ // NOTE: don't handle "BalanceIncreaseGenesisBalance" because it is handled in OnGenesisBlock
+ switch reason {
+ case tracing.BalanceIncreaseRewardMineUncle:
+ case tracing.BalanceIncreaseRewardMineBlock:
+ s.delta.Issuance.Reward.Add(s.delta.Issuance.Reward, diff)
+ case tracing.BalanceIncreaseWithdrawal:
+ s.delta.Issuance.Withdrawals.Add(s.delta.Issuance.Withdrawals, diff)
+ case tracing.BalanceDecreaseSelfdestructBurn:
+ // BalanceDecreaseSelfdestructBurn is non-reversible as it happens
+ // at the end of the transaction.
+ s.delta.Burn.Misc.Sub(s.delta.Burn.Misc, diff)
+ default:
+ return
+ }
+}
+
+func (s *supply) OnTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
+ s.txCallstack = make([]supplyTxCallstack, 0, 1)
+}
+
+// internalTxsHandler handles internal transactions burned amount
+func (s *supply) internalTxsHandler(call *supplyTxCallstack) {
+ // Handle Burned amount
+ if call.burn != nil {
+ s.delta.Burn.Misc.Add(s.delta.Burn.Misc, call.burn)
+ }
+
+ if len(call.calls) > 0 {
+ // Recursively handle internal calls
+ for _, call := range call.calls {
+ callCopy := call
+ s.internalTxsHandler(&callCopy)
+ }
+ }
+}
+
+func (s *supply) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ call := supplyTxCallstack{
+ calls: make([]supplyTxCallstack, 0),
+ }
+
+ // This is a special case of burned amount which has to be handled here
+ // which happens when type == selfdestruct and from == to.
+ if vm.OpCode(typ) == vm.SELFDESTRUCT && from == to && value.Cmp(common.Big0) == 1 {
+ call.burn = value
+ }
+
+ // Append call to the callstack, so we can fill the details in CaptureExit
+ s.txCallstack = append(s.txCallstack, call)
+}
+
+func (s *supply) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
+ if depth == 0 {
+ // No need to handle Burned amount if transaction is reverted
+ if !reverted {
+ s.internalTxsHandler(&s.txCallstack[0])
+ }
+ return
+ }
+
+ size := len(s.txCallstack)
+ if size <= 1 {
+ return
+ }
+ // Pop call
+ call := s.txCallstack[size-1]
+ s.txCallstack = s.txCallstack[:size-1]
+ size -= 1
+
+ // In case of a revert, we can drop the call and all its subcalls.
+ // Caution, that this has to happen after popping the call from the stack.
+ if reverted {
+ return
+ }
+ s.txCallstack[size-1].calls = append(s.txCallstack[size-1].calls, call)
+}
+
+func (s *supply) OnClose() {
+ if err := s.logger.Close(); err != nil {
+ log.Warn("failed to close supply tracer log file", "error", err)
+ }
+}
+
+func (s *supply) write(data any) {
+ supply, ok := data.(supplyInfo)
+ if !ok {
+ log.Warn("failed to cast supply tracer data on write to log file")
+ return
+ }
+
+ // Remove empty fields
+ if supply.Issuance.GenesisAlloc.Sign() == 0 {
+ supply.Issuance.GenesisAlloc = nil
+ }
+
+ if supply.Issuance.Reward.Sign() == 0 {
+ supply.Issuance.Reward = nil
+ }
+
+ if supply.Issuance.Withdrawals.Sign() == 0 {
+ supply.Issuance.Withdrawals = nil
+ }
+
+ if supply.Issuance.GenesisAlloc == nil && supply.Issuance.Reward == nil && supply.Issuance.Withdrawals == nil {
+ supply.Issuance = nil
+ }
+
+ if supply.Burn.EIP1559.Sign() == 0 {
+ supply.Burn.EIP1559 = nil
+ }
+
+ if supply.Burn.Blob.Sign() == 0 {
+ supply.Burn.Blob = nil
+ }
+
+ if supply.Burn.Misc.Sign() == 0 {
+ supply.Burn.Misc = nil
+ }
+
+ if supply.Burn.EIP1559 == nil && supply.Burn.Blob == nil && supply.Burn.Misc == nil {
+ supply.Burn = nil
+ }
+
+ out, _ := json.Marshal(supply)
+ if _, err := s.logger.Write(out); err != nil {
+ log.Warn("failed to write to supply tracer log file", "error", err)
+ }
+ if _, err := s.logger.Write([]byte{'\n'}); err != nil {
+ log.Warn("failed to write to supply tracer log file", "error", err)
+ }
+}
diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go
index 7137d29396..29a773ced4 100644
--- a/ethdb/dbtest/testsuite.go
+++ b/ethdb/dbtest/testsuite.go
@@ -530,7 +530,7 @@ func makeDataset(size, ksize, vsize int, order bool) ([][]byte, [][]byte) {
vals = append(vals, randBytes(vsize))
}
if order {
- slices.SortFunc(keys, func(a, b []byte) int { return bytes.Compare(a, b) })
+ slices.SortFunc(keys, bytes.Compare)
}
return keys, vals
}
diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go
index dda084ae3b..0fac07c960 100644
--- a/ethdb/pebble/pebble.go
+++ b/ethdb/pebble/pebble.go
@@ -207,7 +207,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool, e
// The default compaction concurrency(1 thread),
// Here use all available CPUs for faster compaction.
- MaxConcurrentCompactions: func() int { return runtime.NumCPU() },
+ MaxConcurrentCompactions: runtime.NumCPU,
// Per-level options. Options for at least one level must be specified. The
// options for the last level are used for all subsequent levels.
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/internal/ethapi/api.go b/internal/ethapi/api.go
index 8866a5b870..1c209fb5a4 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -26,9 +26,6 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
- "github.com/holiman/uint256"
- "github.com/tyler-smith/go-bip39"
-
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
@@ -51,6 +48,8 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
+ "github.com/holiman/uint256"
+ "github.com/tyler-smith/go-bip39"
)
// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is
@@ -70,20 +69,20 @@ func NewEthereumAPI(b Backend) *EthereumAPI {
}
// GasPrice returns a suggestion for a gas price for legacy transactions.
-func (s *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
- tipcap, err := s.b.SuggestGasTipCap(ctx)
+func (api *EthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
+ tipcap, err := api.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
- if head := s.b.CurrentHeader(); head.BaseFee != nil {
+ if head := api.b.CurrentHeader(); head.BaseFee != nil {
tipcap.Add(tipcap, head.BaseFee)
}
return (*hexutil.Big)(tipcap), err
}
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
-func (s *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
- tipcap, err := s.b.SuggestGasTipCap(ctx)
+func (api *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
+ tipcap, err := api.b.SuggestGasTipCap(ctx)
if err != nil {
return nil, err
}
@@ -100,8 +99,8 @@ type feeHistoryResult struct {
}
// FeeHistory returns the fee market history.
-func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
- oldest, reward, baseFee, gasUsed, blobBaseFee, blobGasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
+func (api *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
+ oldest, reward, baseFee, gasUsed, blobBaseFee, blobGasUsed, err := api.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
if err != nil {
return nil, err
}
@@ -137,8 +136,8 @@ func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecim
}
// BlobBaseFee returns the base fee for blob gas at the current head.
-func (s *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
- return (*hexutil.Big)(s.b.BlobBaseFee(ctx))
+func (api *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
+ return (*hexutil.Big)(api.b.BlobBaseFee(ctx))
}
// Syncing returns false in case the node is currently not syncing with the network. It can be up-to-date or has not
@@ -148,8 +147,8 @@ func (s *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
// - highestBlock: block number of the highest block header this node has received from peers
// - pulledStates: number of state entries processed until now
// - knownStates: number of known state entries that still need to be pulled
-func (s *EthereumAPI) Syncing() (interface{}, error) {
- progress := s.b.SyncProgress()
+func (api *EthereumAPI) Syncing() (interface{}, error) {
+ progress := api.b.SyncProgress()
// Return not syncing if the synchronisation already completed
if progress.Done() {
@@ -188,18 +187,18 @@ func NewTxPoolAPI(b Backend) *TxPoolAPI {
}
// Content returns the transactions contained within the transaction pool.
-func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
+func (api *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
content := map[string]map[string]map[string]*RPCTransaction{
"pending": make(map[string]map[string]*RPCTransaction),
"queued": make(map[string]map[string]*RPCTransaction),
}
- pending, queue := s.b.TxPoolContent()
- curHeader := s.b.CurrentHeader()
+ pending, queue := api.b.TxPoolContent()
+ curHeader := api.b.CurrentHeader()
// Flatten the pending transactions
for account, txs := range pending {
dump := make(map[string]*RPCTransaction)
for _, tx := range txs {
- dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
+ dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig())
}
content["pending"][account.Hex()] = dump
}
@@ -207,7 +206,7 @@ func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
for account, txs := range queue {
dump := make(map[string]*RPCTransaction)
for _, tx := range txs {
- dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
+ dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig())
}
content["queued"][account.Hex()] = dump
}
@@ -215,22 +214,22 @@ func (s *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
}
// ContentFrom returns the transactions contained within the transaction pool.
-func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCTransaction {
+func (api *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCTransaction {
content := make(map[string]map[string]*RPCTransaction, 2)
- pending, queue := s.b.TxPoolContentFrom(addr)
- curHeader := s.b.CurrentHeader()
+ pending, queue := api.b.TxPoolContentFrom(addr)
+ curHeader := api.b.CurrentHeader()
// Build the pending transactions
dump := make(map[string]*RPCTransaction, len(pending))
for _, tx := range pending {
- dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
+ dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig())
}
content["pending"] = dump
// Build the queued transactions
dump = make(map[string]*RPCTransaction, len(queue))
for _, tx := range queue {
- dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig())
+ dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig())
}
content["queued"] = dump
@@ -238,8 +237,8 @@ func (s *TxPoolAPI) ContentFrom(addr common.Address) map[string]map[string]*RPCT
}
// Status returns the number of pending and queued transaction in the pool.
-func (s *TxPoolAPI) Status() map[string]hexutil.Uint {
- pending, queue := s.b.Stats()
+func (api *TxPoolAPI) Status() map[string]hexutil.Uint {
+ pending, queue := api.b.Stats()
return map[string]hexutil.Uint{
"pending": hexutil.Uint(pending),
"queued": hexutil.Uint(queue),
@@ -248,12 +247,12 @@ func (s *TxPoolAPI) Status() map[string]hexutil.Uint {
// Inspect retrieves the content of the transaction pool and flattens it into an
// easily inspectable list.
-func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string {
+func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string {
content := map[string]map[string]map[string]string{
"pending": make(map[string]map[string]string),
"queued": make(map[string]map[string]string),
}
- pending, queue := s.b.TxPoolContent()
+ pending, queue := api.b.TxPoolContent()
// Define a formatter to flatten a transaction into a string
var format = func(tx *types.Transaction) string {
@@ -293,8 +292,8 @@ func NewEthereumAccountAPI(am *accounts.Manager) *EthereumAccountAPI {
}
// Accounts returns the collection of accounts this node manages.
-func (s *EthereumAccountAPI) Accounts() []common.Address {
- return s.am.Accounts()
+func (api *EthereumAccountAPI) Accounts() []common.Address {
+ return api.am.Accounts()
}
// PersonalAccountAPI provides an API to access accounts managed by this node.
@@ -316,8 +315,8 @@ func NewPersonalAccountAPI(b Backend, nonceLock *AddrLocker) *PersonalAccountAPI
}
// ListAccounts will return a list of addresses for accounts this node manages.
-func (s *PersonalAccountAPI) ListAccounts() []common.Address {
- return s.am.Accounts()
+func (api *PersonalAccountAPI) ListAccounts() []common.Address {
+ return api.am.Accounts()
}
// rawWallet is a JSON representation of an accounts.Wallet interface, with its
@@ -330,9 +329,9 @@ type rawWallet struct {
}
// ListWallets will return a list of wallets this node manages.
-func (s *PersonalAccountAPI) ListWallets() []rawWallet {
+func (api *PersonalAccountAPI) ListWallets() []rawWallet {
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
- for _, wallet := range s.am.Wallets() {
+ for _, wallet := range api.am.Wallets() {
status, failure := wallet.Status()
raw := rawWallet{
@@ -352,8 +351,8 @@ func (s *PersonalAccountAPI) ListWallets() []rawWallet {
// connection and attempting to authenticate via the provided passphrase. Note,
// the method may return an extra challenge requiring a second open (e.g. the
// Trezor PIN matrix challenge).
-func (s *PersonalAccountAPI) OpenWallet(url string, passphrase *string) error {
- wallet, err := s.am.Wallet(url)
+func (api *PersonalAccountAPI) OpenWallet(url string, passphrase *string) error {
+ wallet, err := api.am.Wallet(url)
if err != nil {
return err
}
@@ -366,8 +365,8 @@ func (s *PersonalAccountAPI) OpenWallet(url string, passphrase *string) error {
// DeriveAccount requests an HD wallet to derive a new account, optionally pinning
// it for later reuse.
-func (s *PersonalAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
- wallet, err := s.am.Wallet(url)
+func (api *PersonalAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
+ wallet, err := api.am.Wallet(url)
if err != nil {
return accounts.Account{}, err
}
@@ -382,8 +381,8 @@ func (s *PersonalAccountAPI) DeriveAccount(url string, path string, pin *bool) (
}
// NewAccount will create a new account and returns the address for the new account.
-func (s *PersonalAccountAPI) NewAccount(password string) (common.AddressEIP55, error) {
- ks, err := fetchKeystore(s.am)
+func (api *PersonalAccountAPI) NewAccount(password string) (common.AddressEIP55, error) {
+ ks, err := fetchKeystore(api.am)
if err != nil {
return common.AddressEIP55{}, err
}
@@ -408,12 +407,12 @@ func fetchKeystore(am *accounts.Manager) (*keystore.KeyStore, error) {
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
// encrypting it with the passphrase.
-func (s *PersonalAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
+func (api *PersonalAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
key, err := crypto.HexToECDSA(privkey)
if err != nil {
return common.Address{}, err
}
- ks, err := fetchKeystore(s.am)
+ ks, err := fetchKeystore(api.am)
if err != nil {
return common.Address{}, err
}
@@ -424,11 +423,11 @@ func (s *PersonalAccountAPI) ImportRawKey(privkey string, password string) (comm
// UnlockAccount will unlock the account associated with the given address with
// the given password for duration seconds. If duration is nil it will use a
// default of 300 seconds. It returns an indication if the account was unlocked.
-func (s *PersonalAccountAPI) UnlockAccount(ctx context.Context, addr common.Address, password string, duration *uint64) (bool, error) {
+func (api *PersonalAccountAPI) UnlockAccount(ctx context.Context, addr common.Address, password string, duration *uint64) (bool, error) {
// When the API is exposed by external RPC(http, ws etc), unless the user
// explicitly specifies to allow the insecure account unlocking, otherwise
// it is disabled.
- if s.b.ExtRPCEnabled() && !s.b.AccountManager().Config().InsecureUnlockAllowed {
+ if api.b.ExtRPCEnabled() && !api.b.AccountManager().Config().InsecureUnlockAllowed {
return false, errors.New("account unlock with HTTP access is forbidden")
}
@@ -441,7 +440,7 @@ func (s *PersonalAccountAPI) UnlockAccount(ctx context.Context, addr common.Addr
} else {
d = time.Duration(*duration) * time.Second
}
- ks, err := fetchKeystore(s.am)
+ ks, err := fetchKeystore(api.am)
if err != nil {
return false, err
}
@@ -453,8 +452,8 @@ func (s *PersonalAccountAPI) UnlockAccount(ctx context.Context, addr common.Addr
}
// LockAccount will lock the account associated with the given address when it's unlocked.
-func (s *PersonalAccountAPI) LockAccount(addr common.Address) bool {
- if ks, err := fetchKeystore(s.am); err == nil {
+func (api *PersonalAccountAPI) LockAccount(addr common.Address) bool {
+ if ks, err := fetchKeystore(api.am); err == nil {
return ks.Lock(addr) == nil
}
return false
@@ -463,49 +462,49 @@ func (s *PersonalAccountAPI) LockAccount(addr common.Address) bool {
// signTransaction sets defaults and signs the given transaction
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
// and release it after the transaction has been submitted to the tx pool
-func (s *PersonalAccountAPI) signTransaction(ctx context.Context, args *TransactionArgs, passwd string) (*types.Transaction, error) {
+func (api *PersonalAccountAPI) signTransaction(ctx context.Context, args *TransactionArgs, passwd string) (*types.Transaction, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.from()}
- wallet, err := s.am.Find(account)
+ wallet, err := api.am.Find(account)
if err != nil {
return nil, err
}
// Set some sanity defaults and terminate on failure
- if err := args.setDefaults(ctx, s.b, false); err != nil {
+ if err := args.setDefaults(ctx, api.b, false); err != nil {
return nil, err
}
// Assemble the transaction and sign with the wallet
tx := args.ToTransaction()
- return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID)
+ return wallet.SignTxWithPassphrase(account, passwd, tx, api.b.ChainConfig().ChainID)
}
// SendTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.From. If the given
// passwd isn't able to decrypt the key it fails.
-func (s *PersonalAccountAPI) SendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) {
+func (api *PersonalAccountAPI) SendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) {
if args.Nonce == nil {
// Hold the mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
- s.nonceLock.LockAddr(args.from())
- defer s.nonceLock.UnlockAddr(args.from())
+ api.nonceLock.LockAddr(args.from())
+ defer api.nonceLock.UnlockAddr(args.from())
}
if args.IsEIP4844() {
return common.Hash{}, errBlobTxNotSupported
}
- signed, err := s.signTransaction(ctx, &args, passwd)
+ signed, err := api.signTransaction(ctx, &args, passwd)
if err != nil {
log.Warn("Failed transaction send attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
return common.Hash{}, err
}
- return SubmitTransaction(ctx, s.b, signed)
+ return SubmitTransaction(ctx, api.b, signed)
}
// SignTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.From. If the given passwd isn't
// able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast
// to other nodes
-func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args TransactionArgs, passwd string) (*SignTransactionResult, error) {
+func (api *PersonalAccountAPI) SignTransaction(ctx context.Context, args TransactionArgs, passwd string) (*SignTransactionResult, error) {
// No need to obtain the noncelock mutex, since we won't be sending this
// tx into the transaction pool, but right back to the user
if args.From == nil {
@@ -525,10 +524,10 @@ func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transacti
}
// Before actually signing the transaction, ensure the transaction fee is reasonable.
tx := args.ToTransaction()
- if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
+ if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
return nil, err
}
- signed, err := s.signTransaction(ctx, &args, passwd)
+ signed, err := api.signTransaction(ctx, &args, passwd)
if err != nil {
log.Warn("Failed transaction sign attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err)
return nil, err
@@ -549,11 +548,11 @@ func (s *PersonalAccountAPI) SignTransaction(ctx context.Context, args Transacti
// The key used to calculate the signature is decrypted with the given password.
//
// https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal#personal-sign
-func (s *PersonalAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
+func (api *PersonalAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
- wallet, err := s.b.AccountManager().Find(account)
+ wallet, err := api.b.AccountManager().Find(account)
if err != nil {
return nil, err
}
@@ -577,7 +576,7 @@ func (s *PersonalAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr
// the V value must be 27 or 28 for legacy reasons.
//
// https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-personal#personal-ecrecover
-func (s *PersonalAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
+func (api *PersonalAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
if len(sig) != crypto.SignatureLength {
return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
}
@@ -594,8 +593,8 @@ func (s *PersonalAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.By
}
// InitializeWallet initializes a new wallet at the provided URL, by generating and returning a new private key.
-func (s *PersonalAccountAPI) InitializeWallet(ctx context.Context, url string) (string, error) {
- wallet, err := s.am.Wallet(url)
+func (api *PersonalAccountAPI) InitializeWallet(ctx context.Context, url string) (string, error) {
+ wallet, err := api.am.Wallet(url)
if err != nil {
return "", err
}
@@ -621,8 +620,8 @@ func (s *PersonalAccountAPI) InitializeWallet(ctx context.Context, url string) (
}
// Unpair deletes a pairing between wallet and geth.
-func (s *PersonalAccountAPI) Unpair(ctx context.Context, url string, pin string) error {
- wallet, err := s.am.Wallet(url)
+func (api *PersonalAccountAPI) Unpair(ctx context.Context, url string, pin string) error {
+ wallet, err := api.am.Wallet(url)
if err != nil {
return err
}
@@ -656,16 +655,16 @@ func (api *BlockChainAPI) ChainId() *hexutil.Big {
}
// BlockNumber returns the block number of the chain head.
-func (s *BlockChainAPI) BlockNumber() hexutil.Uint64 {
- header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
+func (api *BlockChainAPI) BlockNumber() hexutil.Uint64 {
+ header, _ := api.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
return hexutil.Uint64(header.Number.Uint64())
}
// GetBalance returns the amount of wei for the given address in the state of the
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed.
-func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
- state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+func (api *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
+ state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
@@ -704,7 +703,7 @@ func (n *proofList) Delete(key []byte) error {
}
// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
-func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
+func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
var (
keys = make([]common.Hash, len(storageKeys))
keyLengths = make([]int, len(storageKeys))
@@ -718,7 +717,7 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
return nil, err
}
}
- statedb, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+ statedb, header, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if statedb == nil || err != nil {
return nil, err
}
@@ -804,10 +803,10 @@ func decodeHash(s string) (h common.Hash, inputLength int, err error) {
// - When blockNr is -2 the chain latest header is returned.
// - When blockNr is -3 the chain finalized header is returned.
// - When blockNr is -4 the chain safe header is returned.
-func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
- header, err := s.b.HeaderByNumber(ctx, number)
+func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
+ header, err := api.b.HeaderByNumber(ctx, number)
if header != nil && err == nil {
- response := s.rpcMarshalHeader(ctx, header)
+ response := api.rpcMarshalHeader(ctx, header)
if number == rpc.PendingBlockNumber {
// Pending header need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
@@ -820,10 +819,10 @@ func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockN
}
// GetHeaderByHash returns the requested header by hash.
-func (s *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
- header, _ := s.b.HeaderByHash(ctx, hash)
+func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
+ header, _ := api.b.HeaderByHash(ctx, hash)
if header != nil {
- return s.rpcMarshalHeader(ctx, header)
+ return api.rpcMarshalHeader(ctx, header)
}
return nil
}
@@ -835,10 +834,10 @@ func (s *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) m
// - When blockNr is -4 the chain safe block is returned.
// - When fullTx is true all transactions in the block are returned, otherwise
// only the transaction hash is returned.
-func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
- block, err := s.b.BlockByNumber(ctx, number)
+func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
+ block, err := api.b.BlockByNumber(ctx, number)
if block != nil && err == nil {
- response, err := s.rpcMarshalBlock(ctx, block, true, fullTx)
+ response, err := api.rpcMarshalBlock(ctx, block, true, fullTx)
if err == nil && number == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
@@ -852,17 +851,17 @@ func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNu
// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full
// detail, otherwise only the transaction hash is returned.
-func (s *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
- block, err := s.b.BlockByHash(ctx, hash)
+func (api *BlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
+ block, err := api.b.BlockByHash(ctx, hash)
if block != nil {
- return s.rpcMarshalBlock(ctx, block, true, fullTx)
+ return api.rpcMarshalBlock(ctx, block, true, fullTx)
}
return nil, err
}
// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index.
-func (s *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) {
- block, err := s.b.BlockByNumber(ctx, blockNr)
+func (api *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) {
+ block, err := api.b.BlockByNumber(ctx, blockNr)
if block != nil {
uncles := block.Uncles()
if index >= hexutil.Uint(len(uncles)) {
@@ -870,14 +869,14 @@ func (s *BlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, block
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
- return s.rpcMarshalBlock(ctx, block, false, false)
+ return api.rpcMarshalBlock(ctx, block, false, false)
}
return nil, err
}
// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index.
-func (s *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) {
- block, err := s.b.BlockByHash(ctx, blockHash)
+func (api *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) {
+ block, err := api.b.BlockByHash(ctx, blockHash)
if block != nil {
uncles := block.Uncles()
if index >= hexutil.Uint(len(uncles)) {
@@ -885,14 +884,14 @@ func (s *BlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHa
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
- return s.rpcMarshalBlock(ctx, block, false, false)
+ return api.rpcMarshalBlock(ctx, block, false, false)
}
return nil, err
}
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
-func (s *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
- if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
+func (api *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
+ if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
}
@@ -900,8 +899,8 @@ func (s *BlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr
}
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
-func (s *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
- if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
+func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
+ if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
}
@@ -909,8 +908,8 @@ func (s *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash
}
// GetCode returns the code stored at the given address in the state for the given block number.
-func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
- state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+func (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
+ state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
@@ -921,8 +920,8 @@ func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blo
// GetStorageAt returns the storage from the state at the given address, key and
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
// numbers are also allowed.
-func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
- state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
+ state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
@@ -935,14 +934,14 @@ func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address
}
// GetBlockReceipts returns the block receipts for the given block hash or number or tag.
-func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
- block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash)
+func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
+ block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
if block == nil || err != nil {
// When the block doesn't exist, the RPC method should return JSON null
// as per specification.
return nil, nil
}
- receipts, err := s.b.GetReceipts(ctx, block.Hash())
+ receipts, err := api.b.GetReceipts(ctx, block.Hash())
if err != nil {
return nil, err
}
@@ -952,7 +951,7 @@ func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.
}
// Derive the sender.
- signer := types.MakeSigner(s.b.ChainConfig(), block.Number(), block.Time())
+ signer := types.MakeSigner(api.b.ChainConfig(), block.Number(), block.Time())
result := make([]map[string]interface{}, len(receipts))
for i, receipt := range receipts {
@@ -1162,12 +1161,12 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
//
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
-func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) {
+func (api *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) {
if blockNrOrHash == nil {
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
blockNrOrHash = &latest
}
- result, err := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, blockOverrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
+ result, err := DoCall(ctx, api.b, args, *blockNrOrHash, overrides, blockOverrides, api.b.RPCEVMTimeout(), api.b.RPCGasCap())
if err != nil {
return nil, err
}
@@ -1199,10 +1198,16 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
State: state,
ErrorRatio: estimateGasErrorRatio,
}
+ // Set any required transaction default, but make sure the gas cap itself is not messed with
+ // if it was not specified in the original argument list.
+ if args.Gas == nil {
+ args.Gas = new(hexutil.Uint64)
+ }
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
return 0, err
}
call := args.ToMessage(header.BaseFee)
+
// Run the gas estimation and wrap any revertals into a custom return
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
if err != nil {
@@ -1220,12 +1225,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
// value is capped by both `args.Gas` (if non-nil & non-zero) and the backend's RPCGasCap
// configuration (if non-zero).
// Note: Required blob gas is not computed in this method.
-func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) {
+func (api *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) {
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
}
- return DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides, s.b.RPCGasCap())
+ return DoEstimateGas(ctx, api.b, args, bNrOrHash, overrides, api.b.RPCGasCap())
}
// RPCMarshalHeader converts the given header to the RPC output .
@@ -1303,18 +1308,18 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param
// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires
// a `BlockchainAPI`.
-func (s *BlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} {
+func (api *BlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} {
fields := RPCMarshalHeader(header)
- fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, header.Hash()))
+ fields["totalDifficulty"] = (*hexutil.Big)(api.b.GetTd(ctx, header.Hash()))
return fields
}
// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires
// a `BlockchainAPI`.
-func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
- fields := RPCMarshalBlock(b, inclTx, fullTx, s.b.ChainConfig())
+func (api *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
+ fields := RPCMarshalBlock(b, inclTx, fullTx, api.b.ChainConfig())
if inclTx {
- fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash()))
+ fields["totalDifficulty"] = (*hexutil.Big)(api.b.GetTd(ctx, b.Hash()))
}
return fields, nil
}
@@ -1478,12 +1483,12 @@ type accessListResult struct {
// CreateAccessList creates an EIP-2930 type AccessList for the given transaction.
// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
-func (s *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
+func (api *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
}
- acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args)
+ acl, gasUsed, vmerr, err := AccessList(ctx, api.b, bNrOrHash, args)
if err != nil {
return nil, err
}
@@ -1568,8 +1573,8 @@ func NewTransactionAPI(b Backend, nonceLock *AddrLocker) *TransactionAPI {
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
-func (s *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
- if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
+func (api *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
+ if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
}
@@ -1577,8 +1582,8 @@ func (s *TransactionAPI) GetBlockTransactionCountByNumber(ctx context.Context, b
}
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
-func (s *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
- if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
+func (api *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
+ if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
}
@@ -1586,49 +1591,49 @@ func (s *TransactionAPI) GetBlockTransactionCountByHash(ctx context.Context, blo
}
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
-func (s *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
- if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
- return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig())
+func (api *TransactionAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
+ if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
+ return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
}
return nil
}
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
-func (s *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
- if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
- return newRPCTransactionFromBlockIndex(block, uint64(index), s.b.ChainConfig())
+func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
+ if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
+ return newRPCTransactionFromBlockIndex(block, uint64(index), api.b.ChainConfig())
}
return nil
}
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
-func (s *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes {
- if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
+func (api *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes {
+ if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil {
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
}
return nil
}
// GetRawTransactionByBlockHashAndIndex returns the bytes of the transaction for the given block hash and index.
-func (s *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes {
- if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
+func (api *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes {
+ if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil {
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
}
return nil
}
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
-func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
+func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
// Ask transaction pool for the nonce which includes pending transactions
if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
- nonce, err := s.b.GetPoolNonce(ctx, address)
+ nonce, err := api.b.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
}
return (*hexutil.Uint64)(&nonce), nil
}
// Resolve block number and use its state to ask for the nonce
- state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+ state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
@@ -1637,32 +1642,32 @@ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common
}
// GetTransactionByHash returns the transaction for the given hash
-func (s *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
+func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction
- found, tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
+ found, tx, blockHash, blockNumber, index, err := api.b.GetTransaction(ctx, hash)
if !found {
// No finalized transaction, try to retrieve it from the pool
- if tx := s.b.GetPoolTransaction(hash); tx != nil {
- return NewRPCPendingTransaction(tx, s.b.CurrentHeader(), s.b.ChainConfig()), nil
+ if tx := api.b.GetPoolTransaction(hash); tx != nil {
+ return NewRPCPendingTransaction(tx, api.b.CurrentHeader(), api.b.ChainConfig()), nil
}
if err == nil {
return nil, nil
}
return nil, NewTxIndexingError()
}
- header, err := s.b.HeaderByHash(ctx, blockHash)
+ header, err := api.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
- return newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, s.b.ChainConfig()), nil
+ return newRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, api.b.ChainConfig()), nil
}
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
-func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
+func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
- found, tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
+ found, tx, _, _, _, err := api.b.GetTransaction(ctx, hash)
if !found {
- if tx = s.b.GetPoolTransaction(hash); tx != nil {
+ if tx = api.b.GetPoolTransaction(hash); tx != nil {
return tx.MarshalBinary()
}
if err == nil {
@@ -1674,19 +1679,19 @@ func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash commo
}
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
-func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
- found, tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
+func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
+ found, tx, blockHash, blockNumber, index, err := api.b.GetTransaction(ctx, hash)
if err != nil {
return nil, NewTxIndexingError() // transaction is not fully indexed
}
if !found {
return nil, nil // transaction is not existent or reachable
}
- header, err := s.b.HeaderByHash(ctx, blockHash)
+ header, err := api.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
- receipts, err := s.b.GetReceipts(ctx, blockHash)
+ receipts, err := api.b.GetReceipts(ctx, blockHash)
if err != nil {
return nil, err
}
@@ -1696,7 +1701,7 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.
receipt := receipts[index]
// Derive the sender.
- signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time)
+ signer := types.MakeSigner(api.b.ChainConfig(), header.Number, header.Time)
return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil
}
@@ -1743,16 +1748,16 @@ func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber u
}
// sign is a helper function that signs a transaction with the private key of the given address.
-func (s *TransactionAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
+func (api *TransactionAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
- wallet, err := s.b.AccountManager().Find(account)
+ wallet, err := api.b.AccountManager().Find(account)
if err != nil {
return nil, err
}
// Request the wallet to sign the transaction
- return wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
+ return wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
}
// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
@@ -1788,11 +1793,11 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
-func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) {
+func (api *TransactionAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.from()}
- wallet, err := s.b.AccountManager().Find(account)
+ wallet, err := api.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
}
@@ -1800,35 +1805,35 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr
if args.Nonce == nil {
// Hold the mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
- s.nonceLock.LockAddr(args.from())
- defer s.nonceLock.UnlockAddr(args.from())
+ api.nonceLock.LockAddr(args.from())
+ defer api.nonceLock.UnlockAddr(args.from())
}
if args.IsEIP4844() {
return common.Hash{}, errBlobTxNotSupported
}
// Set some sanity defaults and terminate on failure
- if err := args.setDefaults(ctx, s.b, false); err != nil {
+ if err := args.setDefaults(ctx, api.b, false); err != nil {
return common.Hash{}, err
}
// Assemble the transaction and sign with the wallet
tx := args.ToTransaction()
- signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
+ signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
if err != nil {
return common.Hash{}, err
}
- return SubmitTransaction(ctx, s.b, signed)
+ return SubmitTransaction(ctx, api.b, signed)
}
// FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields)
// on a given unsigned transaction, and returns it to the caller for further
// processing (signing + broadcast).
-func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
+func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
args.blobSidecarAllowed = true
// Set some sanity defaults and terminate on failure
- if err := args.setDefaults(ctx, s.b, false); err != nil {
+ if err := args.setDefaults(ctx, api.b, false); err != nil {
return nil, err
}
// Assemble the transaction and obtain rlp
@@ -1842,12 +1847,12 @@ func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionAr
// SendRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
-func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
+func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}
- return SubmitTransaction(ctx, s.b, tx)
+ return SubmitTransaction(ctx, api.b, tx)
}
// Sign calculates an ECDSA signature for:
@@ -1859,11 +1864,11 @@ func (s *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.B
// The account associated with addr must be unlocked.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
-func (s *TransactionAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
+func (api *TransactionAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
- wallet, err := s.b.AccountManager().Find(account)
+ wallet, err := api.b.AccountManager().Find(account)
if err != nil {
return nil, err
}
@@ -1884,7 +1889,7 @@ type SignTransactionResult struct {
// SignTransaction will sign the given transaction with the from account.
// The node needs to have the private key of the account corresponding with
// the given from address and it needs to be unlocked.
-func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
+func (api *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
args.blobSidecarAllowed = true
if args.Gas == nil {
@@ -1896,15 +1901,15 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr
if args.Nonce == nil {
return nil, errors.New("nonce not specified")
}
- if err := args.setDefaults(ctx, s.b, false); err != nil {
+ if err := args.setDefaults(ctx, api.b, false); err != nil {
return nil, err
}
// Before actually sign the transaction, ensure the transaction fee is reasonable.
tx := args.ToTransaction()
- if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
+ if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
return nil, err
}
- signed, err := s.sign(args.from(), tx)
+ signed, err := api.sign(args.from(), tx)
if err != nil {
return nil, err
}
@@ -1927,23 +1932,23 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr
// PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
-func (s *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) {
- pending, err := s.b.GetPoolTransactions()
+func (api *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) {
+ pending, err := api.b.GetPoolTransactions()
if err != nil {
return nil, err
}
accounts := make(map[common.Address]struct{})
- for _, wallet := range s.b.AccountManager().Wallets() {
+ for _, wallet := range api.b.AccountManager().Wallets() {
for _, account := range wallet.Accounts() {
accounts[account.Address] = struct{}{}
}
}
- curHeader := s.b.CurrentHeader()
+ curHeader := api.b.CurrentHeader()
transactions := make([]*RPCTransaction, 0, len(pending))
for _, tx := range pending {
- from, _ := types.Sender(s.signer, tx)
+ from, _ := types.Sender(api.signer, tx)
if _, exists := accounts[from]; exists {
- transactions = append(transactions, NewRPCPendingTransaction(tx, curHeader, s.b.ChainConfig()))
+ transactions = append(transactions, NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig()))
}
}
return transactions, nil
@@ -1951,11 +1956,11 @@ func (s *TransactionAPI) PendingTransactions() ([]*RPCTransaction, error) {
// Resend accepts an existing transaction and a new gas price and limit. It will remove
// the given transaction from the pool and reinsert it with the new gas price and limit.
-func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
+func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
if sendArgs.Nonce == nil {
return common.Hash{}, errors.New("missing transaction nonce in transaction spec")
}
- if err := sendArgs.setDefaults(ctx, s.b, false); err != nil {
+ if err := sendArgs.setDefaults(ctx, api.b, false); err != nil {
return common.Hash{}, err
}
matchTx := sendArgs.ToTransaction()
@@ -1969,18 +1974,18 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g
if gasLimit != nil {
gas = uint64(*gasLimit)
}
- if err := checkTxFee(price, gas, s.b.RPCTxFeeCap()); err != nil {
+ if err := checkTxFee(price, gas, api.b.RPCTxFeeCap()); err != nil {
return common.Hash{}, err
}
// Iterate the pending list for replacement
- pending, err := s.b.GetPoolTransactions()
+ pending, err := api.b.GetPoolTransactions()
if err != nil {
return common.Hash{}, err
}
for _, p := range pending {
- wantSigHash := s.signer.Hash(matchTx)
- pFrom, err := types.Sender(s.signer, p)
- if err == nil && pFrom == sendArgs.from() && s.signer.Hash(p) == wantSigHash {
+ wantSigHash := api.signer.Hash(matchTx)
+ pFrom, err := types.Sender(api.signer, p)
+ if err == nil && pFrom == sendArgs.from() && api.signer.Hash(p) == wantSigHash {
// Match. Re-sign and send the transaction.
if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
sendArgs.GasPrice = gasPrice
@@ -1988,11 +1993,11 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g
if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit
}
- signedTx, err := s.sign(sendArgs.from(), sendArgs.ToTransaction())
+ signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction())
if err != nil {
return common.Hash{}, err
}
- if err = s.b.SendTx(ctx, signedTx); err != nil {
+ if err = api.b.SendTx(ctx, signedTx); err != nil {
return common.Hash{}, err
}
return signedTx.Hash(), nil
@@ -2078,11 +2083,11 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block
}
// GetRawTransaction returns the bytes of the transaction for the given hash.
-func (s *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
+func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
- found, tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
+ found, tx, _, _, _, err := api.b.GetTransaction(ctx, hash)
if !found {
- if tx = s.b.GetPoolTransaction(hash); tx != nil {
+ if tx = api.b.GetPoolTransaction(hash); tx != nil {
return tx.MarshalBinary()
}
if err == nil {
@@ -2145,18 +2150,18 @@ func NewNetAPI(net *p2p.Server, networkVersion uint64) *NetAPI {
}
// Listening returns an indication if the node is listening for network connections.
-func (s *NetAPI) Listening() bool {
+func (api *NetAPI) Listening() bool {
return true // always listening
}
// PeerCount returns the number of connected peers
-func (s *NetAPI) PeerCount() hexutil.Uint {
- return hexutil.Uint(s.net.PeerCount())
+func (api *NetAPI) PeerCount() hexutil.Uint {
+ return hexutil.Uint(api.net.PeerCount())
}
// Version returns the current ethereum protocol version.
-func (s *NetAPI) Version() string {
- return fmt.Sprintf("%d", s.networkVersion)
+func (api *NetAPI) Version() string {
+ return fmt.Sprintf("%d", api.networkVersion)
}
// checkTxFee is an internal function used to check whether the fee of
diff --git a/log/handler.go b/log/handler.go
index c604a62301..56eff6671f 100644
--- a/log/handler.go
+++ b/log/handler.go
@@ -101,10 +101,10 @@ func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
}
// ResetFieldPadding zeroes the field-padding for all attribute pairs.
-func (t *TerminalHandler) ResetFieldPadding() {
- t.mu.Lock()
- t.fieldPadding = make(map[string]int)
- t.mu.Unlock()
+func (h *TerminalHandler) ResetFieldPadding() {
+ h.mu.Lock()
+ h.fieldPadding = make(map[string]int)
+ h.mu.Unlock()
}
type leveler struct{ minLevel slog.Level }
diff --git a/log/logger_test.go b/log/logger_test.go
index 6f415eb471..f1a9a93bce 100644
--- a/log/logger_test.go
+++ b/log/logger_test.go
@@ -26,7 +26,7 @@ func TestLoggingWithVmodule(t *testing.T) {
logger.Trace("a message", "foo", "bar")
have := out.String()
// The timestamp is locale-dependent, so we want to trim that off
- // "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
+ // "INFO [01-01|00:00:00.000] a message ..." -> "a message..."
have = strings.Split(have, "]")[1]
want := " a message foo=bar\n"
if have != want {
@@ -42,7 +42,7 @@ func TestTerminalHandlerWithAttrs(t *testing.T) {
logger.Trace("a message", "foo", "bar")
have := out.String()
// The timestamp is locale-dependent, so we want to trim that off
- // "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..."
+ // "INFO [01-01|00:00:00.000] a message ..." -> "a message..."
have = strings.Split(have, "]")[1]
want := " a message baz=bat foo=bar\n"
if have != want {
diff --git a/miner/miner.go b/miner/miner.go
index 430efcb2fc..ff81d0e8f5 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -53,7 +53,7 @@ type Config struct {
// DefaultConfig contains default settings for miner.
var DefaultConfig = Config{
GasCeil: 30_000_000,
- GasPrice: big.NewInt(params.GWei),
+ GasPrice: big.NewInt(params.GWei / 1000),
// The default recommit time is chosen as two seconds since
// consensus-layer usually will wait a half slot of time(6s)
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/common.go b/p2p/discover/common.go
index bebea8cc38..0716f7472f 100644
--- a/p2p/discover/common.go
+++ b/p2p/discover/common.go
@@ -22,6 +22,7 @@ import (
"encoding/binary"
"math/rand"
"net"
+ "net/netip"
"sync"
"time"
@@ -34,8 +35,8 @@ import (
// UDPConn is a network connection on which discovery can operate.
type UDPConn interface {
- ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
- WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
+ ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error)
+ WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (n int, err error)
Close() error
LocalAddr() net.Addr
}
@@ -94,7 +95,7 @@ func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
// channel if configured.
type ReadPacket struct {
Data []byte
- Addr *net.UDPAddr
+ Addr netip.AddrPort
}
type randomSource interface {
diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go
index 5c3d90d6c9..09808b71e0 100644
--- a/p2p/discover/lookup.go
+++ b/p2p/discover/lookup.go
@@ -29,16 +29,16 @@ import (
// not need to be an actual node identifier.
type lookup struct {
tab *Table
- queryfunc func(*node) ([]*node, error)
- replyCh chan []*node
+ queryfunc queryFunc
+ replyCh chan []*enode.Node
cancelCh <-chan struct{}
asked, seen map[enode.ID]bool
result nodesByDistance
- replyBuffer []*node
+ replyBuffer []*enode.Node
queries int
}
-type queryFunc func(*node) ([]*node, error)
+type queryFunc func(*enode.Node) ([]*enode.Node, error)
func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
it := &lookup{
@@ -47,7 +47,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
asked: make(map[enode.ID]bool),
seen: make(map[enode.ID]bool),
result: nodesByDistance{target: target},
- replyCh: make(chan []*node, alpha),
+ replyCh: make(chan []*enode.Node, alpha),
cancelCh: ctx.Done(),
queries: -1,
}
@@ -61,7 +61,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
func (it *lookup) run() []*enode.Node {
for it.advance() {
}
- return unwrapNodes(it.result.entries)
+ return it.result.entries
}
// advance advances the lookup until any new nodes have been found.
@@ -139,7 +139,7 @@ func (it *lookup) slowdown() {
}
}
-func (it *lookup) query(n *node, reply chan<- []*node) {
+func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) {
r, err := it.queryfunc(n)
if !errors.Is(err, errClosed) { // avoid recording failures on shutdown.
success := len(r) > 0
@@ -154,7 +154,7 @@ func (it *lookup) query(n *node, reply chan<- []*node) {
// lookupIterator performs lookup operations and iterates over all seen nodes.
// When a lookup finishes, a new one is created through nextLookup.
type lookupIterator struct {
- buffer []*node
+ buffer []*enode.Node
nextLookup lookupFunc
ctx context.Context
cancel func()
@@ -173,7 +173,7 @@ func (it *lookupIterator) Node() *enode.Node {
if len(it.buffer) == 0 {
return nil
}
- return unwrapNode(it.buffer[0])
+ return it.buffer[0]
}
// Next moves to the next node.
diff --git a/p2p/discover/metrics.go b/p2p/discover/metrics.go
index 3cd0ab0414..8deafbbce4 100644
--- a/p2p/discover/metrics.go
+++ b/p2p/discover/metrics.go
@@ -18,7 +18,7 @@ package discover
import (
"fmt"
- "net"
+ "net/netip"
"github.com/ethereum/go-ethereum/metrics"
)
@@ -58,16 +58,16 @@ func newMeteredConn(conn UDPConn) UDPConn {
return &meteredUdpConn{UDPConn: conn}
}
-// ReadFromUDP delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
-func (c *meteredUdpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
- n, addr, err = c.UDPConn.ReadFromUDP(b)
+// ReadFromUDPAddrPort delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
+func (c *meteredUdpConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
+ n, addr, err = c.UDPConn.ReadFromUDPAddrPort(b)
ingressTrafficMeter.Mark(int64(n))
return n, addr, err
}
-// Write delegates a network write to the underlying connection, bumping the udp egress traffic meter along the way.
-func (c *meteredUdpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
- n, err = c.UDPConn.WriteToUDP(b, addr)
+// WriteToUDP delegates a network write to the underlying connection, bumping the udp egress traffic meter along the way.
+func (c *meteredUdpConn) WriteToUDP(b []byte, addr netip.AddrPort) (n int, err error) {
+ n, err = c.UDPConn.WriteToUDPAddrPort(b, addr)
egressTrafficMeter.Mark(int64(n))
return n, err
}
diff --git a/p2p/discover/node.go b/p2p/discover/node.go
index 47788248f4..042619221b 100644
--- a/p2p/discover/node.go
+++ b/p2p/discover/node.go
@@ -21,7 +21,8 @@ import (
"crypto/elliptic"
"errors"
"math/big"
- "net"
+ "slices"
+ "sort"
"time"
"github.com/ethereum/go-ethereum/common/math"
@@ -37,9 +38,8 @@ type BucketNode struct {
Live bool `json:"live"`
}
-// node represents a host on the network.
-// The fields of Node may not be modified.
-type node struct {
+// tableNode is an entry in Table.
+type tableNode struct {
*enode.Node
revalList *revalidationList
addedToTable time.Time // first time node was added to bucket or replacement list
@@ -75,34 +75,59 @@ func (e encPubkey) id() enode.ID {
return enode.ID(crypto.Keccak256Hash(e[:]))
}
-func wrapNode(n *enode.Node) *node {
- return &node{Node: n}
-}
-
-func wrapNodes(ns []*enode.Node) []*node {
- result := make([]*node, len(ns))
- for i, n := range ns {
- result[i] = wrapNode(n)
- }
- return result
-}
-
-func unwrapNode(n *node) *enode.Node {
- return n.Node
-}
-
-func unwrapNodes(ns []*node) []*enode.Node {
+func unwrapNodes(ns []*tableNode) []*enode.Node {
result := make([]*enode.Node, len(ns))
for i, n := range ns {
- result[i] = unwrapNode(n)
+ result[i] = n.Node
}
return result
}
-func (n *node) addr() *net.UDPAddr {
- return &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
-}
-
-func (n *node) String() string {
+func (n *tableNode) String() string {
return n.Node.String()
}
+
+// nodesByDistance is a list of nodes, ordered by distance to target.
+type nodesByDistance struct {
+ entries []*enode.Node
+ target enode.ID
+}
+
+// push adds the given node to the list, keeping the total size below maxElems.
+func (h *nodesByDistance) push(n *enode.Node, maxElems int) {
+ ix := sort.Search(len(h.entries), func(i int) bool {
+ return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
+ })
+
+ end := len(h.entries)
+ if len(h.entries) < maxElems {
+ h.entries = append(h.entries, n)
+ }
+ if ix < end {
+ // Slide existing entries down to make room.
+ // This will overwrite the entry we just appended.
+ copy(h.entries[ix+1:], h.entries[ix:])
+ h.entries[ix] = n
+ }
+}
+
+type nodeType interface {
+ ID() enode.ID
+}
+
+// containsID reports whether ns contains a node with the given ID.
+func containsID[N nodeType](ns []N, id enode.ID) bool {
+ for _, n := range ns {
+ if n.ID() == id {
+ return true
+ }
+ }
+ return false
+}
+
+// deleteNode removes a node from the list.
+func deleteNode[N nodeType](list []N, id enode.ID) []N {
+ return slices.DeleteFunc(list, func(n N) bool {
+ return n.ID() == id
+ })
+}
diff --git a/p2p/discover/table.go b/p2p/discover/table.go
index 2b4ba7f5d8..8045f1389e 100644
--- a/p2p/discover/table.go
+++ b/p2p/discover/table.go
@@ -25,9 +25,8 @@ package discover
import (
"context"
"fmt"
- "net"
+ "net/netip"
"slices"
- "sort"
"sync"
"time"
@@ -65,7 +64,7 @@ const (
type Table struct {
mutex sync.Mutex // protects buckets, bucket content, nursery, rand
buckets [nBuckets]*bucket // index of known nodes by distance
- nursery []*node // bootstrap nodes
+ nursery []*enode.Node // bootstrap nodes
rand reseedingRandom // source of randomness, periodically reseeded
ips netutil.DistinctNetSet
revalidation tableRevalidation
@@ -85,8 +84,8 @@ type Table struct {
closeReq chan struct{}
closed chan struct{}
- nodeAddedHook func(*bucket, *node)
- nodeRemovedHook func(*bucket, *node)
+ nodeAddedHook func(*bucket, *tableNode)
+ nodeRemovedHook func(*bucket, *tableNode)
}
// transport is implemented by the UDP transports.
@@ -101,20 +100,21 @@ type transport interface {
// bucket contains nodes, ordered by their last activity. the entry
// that was most recently active is the first element in entries.
type bucket struct {
- entries []*node // live entries, sorted by time of last contact
- replacements []*node // recently seen nodes to be used if revalidation fails
+ entries []*tableNode // live entries, sorted by time of last contact
+ replacements []*tableNode // recently seen nodes to be used if revalidation fails
ips netutil.DistinctNetSet
index int
}
type addNodeOp struct {
- node *node
- isInbound bool
+ node *enode.Node
+ isInbound bool
+ forceSetLive bool // for tests
}
type trackRequestOp struct {
- node *node
- foundNodes []*node
+ node *enode.Node
+ foundNodes []*enode.Node
success bool
}
@@ -186,7 +186,7 @@ func (tab *Table) getNode(id enode.ID) *enode.Node {
b := tab.bucket(id)
for _, e := range b.entries {
if e.ID() == id {
- return unwrapNode(e)
+ return e.Node
}
}
return nil
@@ -202,16 +202,16 @@ func (tab *Table) close() {
// are used to connect to the network if the table is empty and there
// are no known nodes in the database.
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
- nursery := make([]*node, 0, len(nodes))
+ nursery := make([]*enode.Node, 0, len(nodes))
for _, n := range nodes {
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, wrapNode(n))
+ nursery = append(nursery, n)
}
tab.nursery = nursery
return nil
@@ -255,9 +255,9 @@ func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *
liveNodes := &nodesByDistance{target: target}
for _, b := range &tab.buckets {
for _, n := range b.entries {
- nodes.push(n, nresults)
+ nodes.push(n.Node, nresults)
if preferLive && n.isValidatedLive {
- liveNodes.push(n, nresults)
+ liveNodes.push(n.Node, nresults)
}
}
}
@@ -309,8 +309,8 @@ func (tab *Table) len() (n int) {
// list.
//
// The caller must not hold tab.mutex.
-func (tab *Table) addFoundNode(n *node) bool {
- op := addNodeOp{node: n, isInbound: false}
+func (tab *Table) addFoundNode(n *enode.Node, forceSetLive bool) bool {
+ op := addNodeOp{node: n, isInbound: false, forceSetLive: forceSetLive}
select {
case tab.addNodeCh <- op:
return <-tab.addNodeHandled
@@ -327,7 +327,7 @@ func (tab *Table) addFoundNode(n *node) bool {
// repeatedly.
//
// The caller must not hold tab.mutex.
-func (tab *Table) addInboundNode(n *node) bool {
+func (tab *Table) addInboundNode(n *enode.Node) bool {
op := addNodeOp{node: n, isInbound: true}
select {
case tab.addNodeCh <- op:
@@ -337,7 +337,7 @@ func (tab *Table) addInboundNode(n *node) bool {
}
}
-func (tab *Table) trackRequest(n *node, success bool, foundNodes []*node) {
+func (tab *Table) trackRequest(n *enode.Node, success bool, foundNodes []*enode.Node) {
op := trackRequestOp{n, foundNodes, success}
select {
case tab.trackRequestCh <- op:
@@ -443,15 +443,18 @@ func (tab *Table) doRefresh(done chan struct{}) {
}
func (tab *Table) loadSeedNodes() {
- seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
+ seeds := tab.db.QuerySeeds(seedCount, seedMaxAge)
seeds = append(seeds, tab.nursery...)
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()))
- tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
+ 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()
}
}
@@ -473,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.
@@ -513,7 +516,7 @@ func (tab *Table) handleAddNode(req addNodeOp) bool {
}
b := tab.bucket(req.node.ID())
- n, _ := tab.bumpInBucket(b, req.node.Node, req.isInbound)
+ n, _ := tab.bumpInBucket(b, req.node, req.isInbound)
if n != nil {
// Already in bucket.
return false
@@ -523,37 +526,42 @@ 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
}
// Add to bucket.
- b.entries = append(b.entries, req.node)
- b.replacements = deleteNode(b.replacements, req.node)
- tab.nodeAdded(b, req.node)
+ wn := &tableNode{Node: req.node}
+ if req.forceSetLive {
+ wn.livenessChecks = 1
+ wn.isValidatedLive = true
+ }
+ b.entries = append(b.entries, wn)
+ b.replacements = deleteNode(b.replacements, wn.ID())
+ tab.nodeAdded(b, wn)
return true
}
// addReplacement adds n to the replacement cache of bucket b.
-func (tab *Table) addReplacement(b *bucket, n *node) {
- if contains(b.replacements, n.ID()) {
+func (tab *Table) addReplacement(b *bucket, n *enode.Node) {
+ if containsID(b.replacements, n.ID()) {
// TODO: update ENR
return
}
- if !tab.addIP(b, n.IP()) {
+ if !tab.addIP(b, n.IPAddr()) {
return
}
- n.addedToTable = time.Now()
- var removed *node
- b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
+ wn := &tableNode{Node: n, addedToTable: time.Now()}
+ var removed *tableNode
+ b.replacements, removed = pushNode(b.replacements, wn, maxReplacements)
if removed != nil {
- tab.removeIP(b, removed.IP())
+ tab.removeIP(b, removed.IPAddr())
}
}
-func (tab *Table) nodeAdded(b *bucket, n *node) {
+func (tab *Table) nodeAdded(b *bucket, n *tableNode) {
if n.addedToTable == (time.Time{}) {
n.addedToTable = time.Now()
}
@@ -567,7 +575,7 @@ func (tab *Table) nodeAdded(b *bucket, n *node) {
}
}
-func (tab *Table) nodeRemoved(b *bucket, n *node) {
+func (tab *Table) nodeRemoved(b *bucket, n *tableNode) {
tab.revalidation.nodeRemoved(n)
if tab.nodeRemovedHook != nil {
tab.nodeRemovedHook(b, n)
@@ -579,8 +587,8 @@ func (tab *Table) nodeRemoved(b *bucket, n *node) {
// deleteInBucket removes node n from the table.
// If there are replacement nodes in the bucket, the node is replaced.
-func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *node {
- index := slices.IndexFunc(b.entries, func(e *node) bool { return e.ID() == id })
+func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *tableNode {
+ index := slices.IndexFunc(b.entries, func(e *tableNode) bool { return e.ID() == id })
if index == -1 {
// Entry has been removed already.
return nil
@@ -589,12 +597,12 @@ func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *node {
// 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))
@@ -602,14 +610,14 @@ func (tab *Table) deleteInBucket(b *bucket, id enode.ID) *node {
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
}
// bumpInBucket updates a node record if it exists in the bucket.
// The second return value reports whether the node's endpoint (IP/port) was updated.
-func (tab *Table) bumpInBucket(b *bucket, newRecord *enode.Node, isInbound bool) (n *node, endpointChanged bool) {
- i := slices.IndexFunc(b.entries, func(elem *node) bool {
+func (tab *Table) bumpInBucket(b *bucket, newRecord *enode.Node, isInbound bool) (n *tableNode, endpointChanged bool) {
+ i := slices.IndexFunc(b.entries, func(elem *tableNode) bool {
return elem.ID() == newRecord.ID()
})
if i == -1 {
@@ -629,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
}
}
@@ -651,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()
@@ -672,21 +680,12 @@ func (tab *Table) handleTrackRequest(op trackRequestOp) {
// Add found nodes.
for _, n := range op.foundNodes {
- tab.handleAddNode(addNodeOp{n, false})
+ tab.handleAddNode(addNodeOp{n, false, false})
}
}
-func contains(ns []*node, id enode.ID) bool {
- for _, n := range ns {
- if n.ID() == id {
- return true
- }
- }
- return false
-}
-
// pushNode adds n to the front of list, keeping at most max items.
-func pushNode(list []*node, n *node, max int) ([]*node, *node) {
+func pushNode(list []*tableNode, n *tableNode, max int) ([]*tableNode, *tableNode) {
if len(list) < max {
list = append(list, nil)
}
@@ -695,37 +694,3 @@ func pushNode(list []*node, n *node, max int) ([]*node, *node) {
list[0] = n
return list, removed
}
-
-// deleteNode removes n from list.
-func deleteNode(list []*node, n *node) []*node {
- for i := range list {
- if list[i].ID() == n.ID() {
- return append(list[:i], list[i+1:]...)
- }
- }
- return list
-}
-
-// nodesByDistance is a list of nodes, ordered by distance to target.
-type nodesByDistance struct {
- entries []*node
- target enode.ID
-}
-
-// push adds the given node to the list, keeping the total size below maxElems.
-func (h *nodesByDistance) push(n *node, maxElems int) {
- ix := sort.Search(len(h.entries), func(i int) bool {
- return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
- })
-
- end := len(h.entries)
- if len(h.entries) < maxElems {
- h.entries = append(h.entries, n)
- }
- if ix < end {
- // Slide existing entries down to make room.
- // This will overwrite the entry we just appended.
- copy(h.entries[ix+1:], h.entries[ix:])
- h.entries[ix] = n
- }
-}
diff --git a/p2p/discover/table_reval.go b/p2p/discover/table_reval.go
index 5d185aa8b4..f2ea8b34fa 100644
--- a/p2p/discover/table_reval.go
+++ b/p2p/discover/table_reval.go
@@ -39,7 +39,7 @@ type tableRevalidation struct {
}
type revalidationResponse struct {
- n *node
+ n *tableNode
newRecord *enode.Node
didRespond bool
}
@@ -55,12 +55,12 @@ func (tr *tableRevalidation) init(cfg *Config) {
}
// nodeAdded is called when the table receives a new node.
-func (tr *tableRevalidation) nodeAdded(tab *Table, n *node) {
+func (tr *tableRevalidation) nodeAdded(tab *Table, n *tableNode) {
tr.fast.push(n, tab.cfg.Clock.Now(), &tab.rand)
}
// nodeRemoved is called when a node was removed from the table.
-func (tr *tableRevalidation) nodeRemoved(n *node) {
+func (tr *tableRevalidation) nodeRemoved(n *tableNode) {
if n.revalList == nil {
panic(fmt.Errorf("removed node %v has nil revalList", n.ID()))
}
@@ -68,7 +68,7 @@ func (tr *tableRevalidation) nodeRemoved(n *node) {
}
// nodeEndpointChanged is called when a change in IP or port is detected.
-func (tr *tableRevalidation) nodeEndpointChanged(tab *Table, n *node) {
+func (tr *tableRevalidation) nodeEndpointChanged(tab *Table, n *tableNode) {
n.isValidatedLive = false
tr.moveToList(&tr.fast, n, tab.cfg.Clock.Now(), &tab.rand)
}
@@ -90,7 +90,7 @@ func (tr *tableRevalidation) run(tab *Table, now mclock.AbsTime) (nextTime mcloc
}
// startRequest spawns a revalidation request for node n.
-func (tr *tableRevalidation) startRequest(tab *Table, n *node) {
+func (tr *tableRevalidation) startRequest(tab *Table, n *tableNode) {
if _, ok := tr.activeReq[n.ID()]; ok {
panic(fmt.Errorf("duplicate startRequest (node %v)", n.ID()))
}
@@ -180,7 +180,7 @@ func (tr *tableRevalidation) handleResponse(tab *Table, resp revalidationRespons
}
// moveToList ensures n is in the 'dest' list.
-func (tr *tableRevalidation) moveToList(dest *revalidationList, n *node, now mclock.AbsTime, rand randomSource) {
+func (tr *tableRevalidation) moveToList(dest *revalidationList, n *tableNode, now mclock.AbsTime, rand randomSource) {
if n.revalList == dest {
return
}
@@ -192,14 +192,14 @@ func (tr *tableRevalidation) moveToList(dest *revalidationList, n *node, now mcl
// revalidationList holds a list nodes and the next revalidation time.
type revalidationList struct {
- nodes []*node
+ nodes []*tableNode
nextTime mclock.AbsTime
interval time.Duration
name string
}
// get returns a random node from the queue. Nodes in the 'exclude' map are not returned.
-func (list *revalidationList) get(now mclock.AbsTime, rand randomSource, exclude map[enode.ID]struct{}) *node {
+func (list *revalidationList) get(now mclock.AbsTime, rand randomSource, exclude map[enode.ID]struct{}) *tableNode {
if now < list.nextTime || len(list.nodes) == 0 {
return nil
}
@@ -217,7 +217,7 @@ func (list *revalidationList) schedule(now mclock.AbsTime, rand randomSource) {
list.nextTime = now.Add(time.Duration(rand.Int63n(int64(list.interval))))
}
-func (list *revalidationList) push(n *node, now mclock.AbsTime, rand randomSource) {
+func (list *revalidationList) push(n *tableNode, now mclock.AbsTime, rand randomSource) {
list.nodes = append(list.nodes, n)
if list.nextTime == never {
list.schedule(now, rand)
@@ -225,7 +225,7 @@ func (list *revalidationList) push(n *node, now mclock.AbsTime, rand randomSourc
n.revalList = list
}
-func (list *revalidationList) remove(n *node) {
+func (list *revalidationList) remove(n *tableNode) {
i := slices.Index(list.nodes, n)
if i == -1 {
panic(fmt.Errorf("node %v not found in list", n.ID()))
@@ -238,7 +238,7 @@ func (list *revalidationList) remove(n *node) {
}
func (list *revalidationList) contains(id enode.ID) bool {
- return slices.ContainsFunc(list.nodes, func(n *node) bool {
+ return slices.ContainsFunc(list.nodes, func(n *tableNode) bool {
return n.ID() == id
})
}
diff --git a/p2p/discover/table_reval_test.go b/p2p/discover/table_reval_test.go
index d168767e0d..3605443934 100644
--- a/p2p/discover/table_reval_test.go
+++ b/p2p/discover/table_reval_test.go
@@ -110,10 +110,10 @@ func TestRevalidation_endpointUpdate(t *testing.T) {
}
tr.handleResponse(tab, resp)
- if !tr.fast.contains(node.ID()) {
+ if tr.fast.nodes[0].ID() != node.ID() {
t.Fatal("node not contained in fast revalidation list")
}
- if node.isValidatedLive {
+ if tr.fast.nodes[0].isValidatedLive {
t.Fatal("node is marked live after endpoint change")
}
}
diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go
index b0be2a94c5..2f1797d1e2 100644
--- a/p2p/discover/table_test.go
+++ b/p2p/discover/table_test.go
@@ -22,6 +22,7 @@ import (
"math/rand"
"net"
"reflect"
+ "slices"
"testing"
"testing/quick"
"time"
@@ -64,7 +65,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
// Fill up the sender's bucket.
replacementNodeKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
- replacementNode := wrapNode(enode.NewV4(&replacementNodeKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99))
+ replacementNode := enode.NewV4(&replacementNodeKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99)
last := fillBucket(tab, replacementNode.ID())
tab.mutex.Lock()
nodeEvents := newNodeEventRecorder(128)
@@ -78,7 +79,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
transport.dead[replacementNode.ID()] = !newNodeIsResponding
// Add replacement node to table.
- tab.addFoundNode(replacementNode)
+ tab.addFoundNode(replacementNode, false)
t.Log("last:", last.ID())
t.Log("replacement:", replacementNode.ID())
@@ -115,11 +116,11 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
if l := len(bucket.entries); l != wantSize {
t.Errorf("wrong bucket size after revalidation: got %d, want %d", l, wantSize)
}
- if ok := contains(bucket.entries, last.ID()); ok != lastInBucketIsResponding {
+ if ok := containsID(bucket.entries, last.ID()); ok != lastInBucketIsResponding {
t.Errorf("revalidated node found: %t, want: %t", ok, lastInBucketIsResponding)
}
wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding
- if ok := contains(bucket.entries, replacementNode.ID()); ok != wantNewEntry {
+ if ok := containsID(bucket.entries, replacementNode.ID()); ok != wantNewEntry {
t.Errorf("replacement node found: %t, want: %t", ok, wantNewEntry)
}
}
@@ -153,7 +154,7 @@ func TestTable_IPLimit(t *testing.T) {
for i := 0; i < tableIPLimit+1; i++ {
n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
- tab.addFoundNode(n)
+ tab.addFoundNode(n, false)
}
if tab.len() > tableIPLimit {
t.Errorf("too many nodes in table")
@@ -171,7 +172,7 @@ func TestTable_BucketIPLimit(t *testing.T) {
d := 3
for i := 0; i < bucketIPLimit+1; i++ {
n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
- tab.addFoundNode(n)
+ tab.addFoundNode(n, false)
}
if tab.len() > bucketIPLimit {
t.Errorf("too many nodes in table")
@@ -187,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() {
@@ -232,7 +233,7 @@ func TestTable_findnodeByID(t *testing.T) {
// check that the result nodes have minimum distance to target.
for _, b := range tab.buckets {
for _, n := range b.entries {
- if contains(result, n.ID()) {
+ if containsID(result, n.ID()) {
continue // don't run the check below for nodes in result
}
farthestResult := result[len(result)-1].ID()
@@ -255,7 +256,7 @@ func TestTable_findnodeByID(t *testing.T) {
type closeTest struct {
Self enode.ID
Target enode.ID
- All []*node
+ All []*enode.Node
N int
}
@@ -267,9 +268,8 @@ 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)))
- n := wrapNode(enode.SignNull(r, id))
- n.livenessChecks = 1
+ r.Set(enr.IPv4Addr(netutil.RandomAddr(rand, true)))
+ n := enode.SignNull(r, id)
t.All = append(t.All, n)
}
return reflect.ValueOf(t)
@@ -284,16 +284,16 @@ func TestTable_addInboundNode(t *testing.T) {
// Insert two nodes.
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
- tab.addFoundNode(n1)
- tab.addFoundNode(n2)
- checkBucketContent(t, tab, []*enode.Node{n1.Node, n2.Node})
+ tab.addFoundNode(n1, false)
+ tab.addFoundNode(n2, false)
+ checkBucketContent(t, tab, []*enode.Node{n1, n2})
// Add a changed version of n2. The bucket should be updated.
newrec := n2.Record()
newrec.Set(enr.IP{99, 99, 99, 99})
n2v2 := enode.SignNull(newrec, n2.ID())
- tab.addInboundNode(wrapNode(n2v2))
- checkBucketContent(t, tab, []*enode.Node{n1.Node, n2v2})
+ tab.addInboundNode(n2v2)
+ checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
// Try updating n2 without sequence number change. The update is accepted
// because it's inbound.
@@ -301,8 +301,8 @@ func TestTable_addInboundNode(t *testing.T) {
newrec.Set(enr.IP{100, 100, 100, 100})
newrec.SetSeq(n2.Seq())
n2v3 := enode.SignNull(newrec, n2.ID())
- tab.addInboundNode(wrapNode(n2v3))
- checkBucketContent(t, tab, []*enode.Node{n1.Node, n2v3})
+ tab.addInboundNode(n2v3)
+ checkBucketContent(t, tab, []*enode.Node{n1, n2v3})
}
func TestTable_addFoundNode(t *testing.T) {
@@ -314,16 +314,16 @@ func TestTable_addFoundNode(t *testing.T) {
// Insert two nodes.
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
- tab.addFoundNode(n1)
- tab.addFoundNode(n2)
- checkBucketContent(t, tab, []*enode.Node{n1.Node, n2.Node})
+ tab.addFoundNode(n1, false)
+ tab.addFoundNode(n2, false)
+ checkBucketContent(t, tab, []*enode.Node{n1, n2})
// Add a changed version of n2. The bucket should be updated.
newrec := n2.Record()
newrec.Set(enr.IP{99, 99, 99, 99})
n2v2 := enode.SignNull(newrec, n2.ID())
- tab.addFoundNode(wrapNode(n2v2))
- checkBucketContent(t, tab, []*enode.Node{n1.Node, n2v2})
+ tab.addFoundNode(n2v2, false)
+ checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
// Try updating n2 without a sequence number change.
// The update should not be accepted.
@@ -331,8 +331,8 @@ func TestTable_addFoundNode(t *testing.T) {
newrec.Set(enr.IP{100, 100, 100, 100})
newrec.SetSeq(n2.Seq())
n2v3 := enode.SignNull(newrec, n2.ID())
- tab.addFoundNode(wrapNode(n2v3))
- checkBucketContent(t, tab, []*enode.Node{n1.Node, n2v2})
+ tab.addFoundNode(n2v3, false)
+ checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
}
// This test checks that discv4 nodes can update their own endpoint via PING.
@@ -345,13 +345,13 @@ func TestTable_addInboundNodeUpdateV4Accept(t *testing.T) {
// Add a v4 node.
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
- tab.addInboundNode(wrapNode(n1))
+ tab.addInboundNode(n1)
checkBucketContent(t, tab, []*enode.Node{n1})
// Add an updated version with changed IP.
// The update will be accepted because it is inbound.
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
- tab.addInboundNode(wrapNode(n1v2))
+ tab.addInboundNode(n1v2)
checkBucketContent(t, tab, []*enode.Node{n1v2})
}
@@ -366,13 +366,13 @@ func TestTable_addFoundNodeV4UpdateReject(t *testing.T) {
// Add a v4 node.
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
- tab.addFoundNode(wrapNode(n1))
+ tab.addFoundNode(n1, false)
checkBucketContent(t, tab, []*enode.Node{n1})
// Add an updated version with changed IP.
// The update won't be accepted because it isn't inbound.
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
- tab.addFoundNode(wrapNode(n1v2))
+ tab.addFoundNode(n1v2, false)
checkBucketContent(t, tab, []*enode.Node{n1})
}
@@ -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()
@@ -413,8 +413,8 @@ func TestTable_revalidateSyncRecord(t *testing.T) {
var r enr.Record
r.Set(enr.IP(net.IP{127, 0, 0, 1}))
id := enode.ID{1}
- n1 := wrapNode(enode.SignNull(&r, id))
- tab.addFoundNode(n1)
+ n1 := enode.SignNull(&r, id)
+ tab.addFoundNode(n1, false)
// Update the node record.
r.Set(enr.WithEntry("foo", "bar"))
@@ -437,7 +437,7 @@ func TestNodesPush(t *testing.T) {
n1 := nodeAtDistance(target, 255, intIP(1))
n2 := nodeAtDistance(target, 254, intIP(2))
n3 := nodeAtDistance(target, 253, intIP(3))
- perm := [][]*node{
+ perm := [][]*enode.Node{
{n3, n2, n1},
{n3, n1, n2},
{n2, n3, n1},
@@ -452,7 +452,7 @@ func TestNodesPush(t *testing.T) {
for _, n := range nodes {
list.push(n, 3)
}
- if !slicesEqual(list.entries, perm[0], nodeIDEqual) {
+ if !slices.EqualFunc(list.entries, perm[0], nodeIDEqual) {
t.Fatal("not equal")
}
}
@@ -463,28 +463,16 @@ func TestNodesPush(t *testing.T) {
for _, n := range nodes {
list.push(n, 2)
}
- if !slicesEqual(list.entries, perm[0][:2], nodeIDEqual) {
+ if !slices.EqualFunc(list.entries, perm[0][:2], nodeIDEqual) {
t.Fatal("not equal")
}
}
}
-func nodeIDEqual(n1, n2 *node) bool {
+func nodeIDEqual[N nodeType](n1, n2 N) bool {
return n1.ID() == n2.ID()
}
-func slicesEqual[T any](s1, s2 []T, check func(e1, e2 T) bool) bool {
- if len(s1) != len(s2) {
- return false
- }
- for i := range s1 {
- if !check(s1[i], s2[i]) {
- return false
- }
- }
- return true
-}
-
// gen wraps quick.Value so it's easier to use.
// it generates a random value of the given value's type.
func gen(typ interface{}, rand *rand.Rand) interface{} {
@@ -495,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 59045bf2a8..5b2699d460 100644
--- a/p2p/discover/table_util_test.go
+++ b/p2p/discover/table_util_test.go
@@ -56,18 +56,18 @@ func newInactiveTestTable(t transport, cfg Config) (*Table, *enode.DB) {
}
// nodeAtDistance creates a node for which enode.LogDist(base, n.id) == ld.
-func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node {
+func nodeAtDistance(base enode.ID, ld int, ip net.IP) *enode.Node {
var r enr.Record
r.Set(enr.IP(ip))
r.Set(enr.UDP(30303))
- return wrapNode(enode.SignNull(&r, idAtDistance(base, ld)))
+ return enode.SignNull(&r, idAtDistance(base, ld))
}
// nodesAtDistance creates n nodes for which enode.LogDist(base, node.ID()) == ld.
func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node {
results := make([]*enode.Node, n)
for i := range results {
- results[i] = unwrapNode(nodeAtDistance(base, ld, intIP(i)))
+ results[i] = nodeAtDistance(base, ld, intIP(i))
}
return results
}
@@ -100,17 +100,18 @@ 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.
-func fillBucket(tab *Table, id enode.ID) (last *node) {
+func fillBucket(tab *Table, id enode.ID) (last *tableNode) {
ld := enode.LogDist(tab.self().ID(), id)
b := tab.bucket(id)
for len(b.entries) < bucketSize {
node := nodeAtDistance(tab.self().ID(), ld, intIP(ld))
- if !tab.addFoundNode(node) {
+ if !tab.addFoundNode(node, false) {
panic("node not added")
}
}
@@ -119,13 +120,9 @@ func fillBucket(tab *Table, id enode.ID) (last *node) {
// fillTable adds nodes the table to the end of their corresponding bucket
// if the bucket is not full. The caller must not hold tab.mutex.
-func fillTable(tab *Table, nodes []*node, setLive bool) {
+func fillTable(tab *Table, nodes []*enode.Node, setLive bool) {
for _, n := range nodes {
- if setLive {
- n.livenessChecks = 1
- n.isValidatedLive = true
- }
- tab.addFoundNode(n)
+ tab.addFoundNode(n, setLive)
}
}
@@ -219,7 +216,7 @@ func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) {
return t.records[n.ID()], nil
}
-func hasDuplicates(slice []*node) bool {
+func hasDuplicates(slice []*enode.Node) bool {
seen := make(map[enode.ID]bool, len(slice))
for i, e := range slice {
if e == nil {
@@ -258,17 +255,17 @@ 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(nodes []*enode.Node) {
- slices.SortFunc(nodes, func(a, b *enode.Node) int {
+func sortByID[N nodeType](nodes []N) {
+ slices.SortFunc(nodes, func(a, b N) int {
return bytes.Compare(a.ID().Bytes(), b.ID().Bytes())
})
}
-func sortedByDistanceTo(distbase enode.ID, slice []*node) bool {
- return slices.IsSortedFunc(slice, func(a, b *node) int {
+func sortedByDistanceTo(distbase enode.ID, slice []*enode.Node) bool {
+ return slices.IsSortedFunc(slice, func(a, b *enode.Node) int {
return enode.DistCmp(distbase, a.ID(), b.ID())
})
}
@@ -304,7 +301,7 @@ type nodeEventRecorder struct {
}
type recordedNodeEvent struct {
- node *node
+ node *tableNode
added bool
}
@@ -314,7 +311,7 @@ func newNodeEventRecorder(buffer int) *nodeEventRecorder {
}
}
-func (set *nodeEventRecorder) nodeAdded(b *bucket, n *node) {
+func (set *nodeEventRecorder) nodeAdded(b *bucket, n *tableNode) {
select {
case set.evc <- recordedNodeEvent{n, true}:
default:
@@ -322,7 +319,7 @@ func (set *nodeEventRecorder) nodeAdded(b *bucket, n *node) {
}
}
-func (set *nodeEventRecorder) nodeRemoved(b *bucket, n *node) {
+func (set *nodeEventRecorder) nodeRemoved(b *bucket, n *tableNode) {
select {
case set.evc <- recordedNodeEvent{n, false}:
default:
diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go
index 5682f262be..bc9475a8b3 100644
--- a/p2p/discover/v4_lookup_test.go
+++ b/p2p/discover/v4_lookup_test.go
@@ -19,7 +19,7 @@ package discover
import (
"crypto/ecdsa"
"fmt"
- "net"
+ "net/netip"
"slices"
"testing"
@@ -40,7 +40,7 @@ func TestUDPv4_Lookup(t *testing.T) {
}
// Seed table with initial node.
- fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}, true)
+ fillTable(test.table, []*enode.Node{lookupTestnet.node(256, 0)}, true)
// Start the lookup.
resultC := make(chan []*enode.Node, 1)
@@ -70,9 +70,9 @@ func TestUDPv4_LookupIterator(t *testing.T) {
defer test.close()
// Seed table with initial nodes.
- bootnodes := make([]*node, len(lookupTestnet.dists[256]))
+ bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
for i := range lookupTestnet.dists[256] {
- bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
+ bootnodes[i] = lookupTestnet.node(256, i)
}
fillTable(test.table, bootnodes, true)
go serveTestnet(test, lookupTestnet)
@@ -105,9 +105,9 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
defer test.close()
// Seed table with initial nodes.
- bootnodes := make([]*node, len(lookupTestnet.dists[256]))
+ bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
for i := range lookupTestnet.dists[256] {
- bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
+ bootnodes[i] = lookupTestnet.node(256, i)
}
fillTable(test.table, bootnodes, true)
go serveTestnet(test, lookupTestnet)
@@ -136,7 +136,7 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
func serveTestnet(test *udpTest, testnet *preminedTestnet) {
for done := false; !done; {
- done = test.waitPacketOut(func(p v4wire.Packet, to *net.UDPAddr, hash []byte) {
+ done = test.waitPacketOut(func(p v4wire.Packet, to netip.AddrPort, hash []byte) {
n, key := testnet.nodeByAddr(to)
switch p.(type) {
case *v4wire.Ping:
@@ -158,10 +158,10 @@ func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node
for _, e := range results {
t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes())
}
- if hasDuplicates(wrapNodes(results)) {
+ if hasDuplicates(results) {
t.Errorf("result set contains duplicate entries")
}
- if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) {
+ if !sortedByDistanceTo(tn.target.id(), results) {
t.Errorf("result set not sorted by distance to target")
}
wantNodes := tn.closest(len(results))
@@ -264,9 +264,10 @@ func (tn *preminedTestnet) node(dist, index int) *enode.Node {
return n
}
-func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) {
- dist := int(addr.IP[1])<<8 + int(addr.IP[2])
- index := int(addr.IP[3])
+func (tn *preminedTestnet) nodeByAddr(addr netip.AddrPort) (*enode.Node, *ecdsa.PrivateKey) {
+ ip := addr.Addr().As4()
+ dist := int(ip[1])<<8 + int(ip[2])
+ index := int(ip[3])
key := tn.dists[dist][index]
return tn.node(dist, index), key
}
@@ -274,7 +275,7 @@ func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.Pr
func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node {
result := make([]v4wire.Node, len(tn.dists[dist]))
for i := range result {
- result[i] = nodeToRPC(wrapNode(tn.node(dist, i)))
+ result[i] = nodeToRPC(tn.node(dist, i))
}
return result
}
diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go
index be6058ec50..cca01bd3ce 100644
--- a/p2p/discover/v4_udp.go
+++ b/p2p/discover/v4_udp.go
@@ -25,7 +25,7 @@ import (
"errors"
"fmt"
"io"
- "net"
+ "net/netip"
"sync"
"time"
@@ -45,6 +45,7 @@ var (
errClockWarp = errors.New("reply deadline too far in the future")
errClosed = errors.New("socket closed")
errLowPort = errors.New("low port")
+ errNoUDPEndpoint = errors.New("node has no UDP endpoint")
)
const (
@@ -93,7 +94,7 @@ type UDPv4 struct {
type replyMatcher struct {
// these fields must match in the reply.
from enode.ID
- ip net.IP
+ ip netip.Addr
ptype byte
// time when the request must complete
@@ -119,7 +120,7 @@ type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool)
// reply is a reply packet from a certain node.
type reply struct {
from enode.ID
- ip net.IP
+ ip netip.Addr
data v4wire.Packet
// loop indicates whether there was
// a matching request by sending on this channel.
@@ -201,9 +202,12 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
}
func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
- n := t.Self()
- a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
- return v4wire.NewEndpoint(a, uint16(n.TCP()))
+ node := t.Self()
+ addr, ok := node.UDPEndpoint()
+ if !ok {
+ return v4wire.Endpoint{}
+ }
+ return v4wire.NewEndpoint(addr, uint16(node.TCP()))
}
// Ping sends a ping message to the given node.
@@ -214,7 +218,11 @@ func (t *UDPv4) Ping(n *enode.Node) error {
// ping sends a ping message to the given node and waits for a reply.
func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
- rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil)
+ addr, ok := n.UDPEndpoint()
+ if !ok {
+ return 0, errNoUDPEndpoint
+ }
+ rm := t.sendPing(n.ID(), addr, nil)
if err = <-rm.errc; err == nil {
seq = rm.reply.(*v4wire.Pong).ENRSeq
}
@@ -223,7 +231,7 @@ func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
// sendPing sends a ping message to the given node and invokes the callback
// when the reply arrives.
-func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher {
+func (t *UDPv4) sendPing(toid enode.ID, toaddr netip.AddrPort, callback func()) *replyMatcher {
req := t.makePing(toaddr)
packet, hash, err := v4wire.Encode(t.priv, req)
if err != nil {
@@ -233,7 +241,7 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
}
// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
// reference the ping we're about to send.
- rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
+ rm := t.pending(toid, toaddr.Addr(), v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
if matched && callback != nil {
callback()
@@ -246,7 +254,7 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
return rm
}
-func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping {
+func (t *UDPv4) makePing(toaddr netip.AddrPort) *v4wire.Ping {
return &v4wire.Ping{
Version: 4,
From: t.ourEndpoint(),
@@ -290,35 +298,39 @@ func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
ekey := v4wire.Pubkey(targetKey)
- it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
- return t.findnode(n.ID(), n.addr(), ekey)
+ it := newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
+ addr, ok := n.UDPEndpoint()
+ if !ok {
+ return nil, errNoUDPEndpoint
+ }
+ return t.findnode(n.ID(), addr, ekey)
})
return it
}
// findnode sends a findnode request to the given node and waits until
// the node has sent up to k neighbors.
-func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) {
- t.ensureBond(toid, toaddr)
+func (t *UDPv4) findnode(toid enode.ID, toAddrPort netip.AddrPort, target v4wire.Pubkey) ([]*enode.Node, error) {
+ t.ensureBond(toid, toAddrPort)
// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
// active until enough nodes have been received.
- nodes := make([]*node, 0, bucketSize)
+ nodes := make([]*enode.Node, 0, bucketSize)
nreceived := 0
- rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
+ rm := t.pending(toid, toAddrPort.Addr(), v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
reply := r.(*v4wire.Neighbors)
for _, rn := range reply.Nodes {
nreceived++
- n, err := t.nodeFromRPC(toaddr, rn)
+ n, err := t.nodeFromRPC(toAddrPort, rn)
if err != nil {
- t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err)
+ t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toAddrPort, "err", err)
continue
}
nodes = append(nodes, n)
}
return true, nreceived >= bucketSize
})
- t.send(toaddr, toid, &v4wire.Findnode{
+ t.send(toAddrPort, toid, &v4wire.Findnode{
Target: target,
Expiration: uint64(time.Now().Add(expiration).Unix()),
})
@@ -336,7 +348,7 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubke
// RequestENR sends ENRRequest to the given node and waits for a response.
func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
- addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
+ addr, _ := n.UDPEndpoint()
t.ensureBond(n.ID(), addr)
req := &v4wire.ENRRequest{
@@ -349,7 +361,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
// Add a matcher for the reply to the pending reply queue. Responses are matched if
// they reference the request we're about to send.
- rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
+ rm := t.pending(n.ID(), addr.Addr(), v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
return matched, matched
})
@@ -369,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.IP, 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
@@ -381,7 +393,7 @@ func (t *UDPv4) TableBuckets() [][]BucketNode {
// pending adds a reply matcher to the pending reply queue.
// see the documentation of type replyMatcher for a detailed explanation.
-func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) *replyMatcher {
+func (t *UDPv4) pending(id enode.ID, ip netip.Addr, ptype byte, callback replyMatchFunc) *replyMatcher {
ch := make(chan error, 1)
p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch}
select {
@@ -395,7 +407,7 @@ func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchF
// handleReply dispatches a reply packet, invoking reply matchers. It returns
// whether any matcher considered the packet acceptable.
-func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool {
+func (t *UDPv4) handleReply(from enode.ID, fromIP netip.Addr, req v4wire.Packet) bool {
matched := make(chan bool, 1)
select {
case t.gotreply <- reply{from, fromIP, req, matched}:
@@ -461,7 +473,7 @@ func (t *UDPv4) loop() {
var matched bool // whether any replyMatcher considered the reply acceptable.
for el := plist.Front(); el != nil; el = el.Next() {
p := el.Value.(*replyMatcher)
- if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) {
+ if p.from == r.from && p.ptype == r.data.Kind() && p.ip == r.ip {
ok, requestDone := p.callback(r.data)
matched = matched || ok
p.reply = r.data
@@ -500,7 +512,7 @@ func (t *UDPv4) loop() {
}
}
-func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) {
+func (t *UDPv4) send(toaddr netip.AddrPort, toid enode.ID, req v4wire.Packet) ([]byte, error) {
packet, hash, err := v4wire.Encode(t.priv, req)
if err != nil {
return hash, err
@@ -508,8 +520,8 @@ func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]b
return hash, t.write(toaddr, toid, req.Name(), packet)
}
-func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error {
- _, err := t.conn.WriteToUDP(packet, toaddr)
+func (t *UDPv4) write(toaddr netip.AddrPort, toid enode.ID, what string, packet []byte) error {
+ _, err := t.conn.WriteToUDPAddrPort(packet, toaddr)
t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err)
return err
}
@@ -523,7 +535,7 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
buf := make([]byte, maxPacketSize)
for {
- nbytes, from, err := t.conn.ReadFromUDP(buf)
+ nbytes, from, err := t.conn.ReadFromUDPAddrPort(buf)
if netutil.IsTemporaryError(err) {
// Ignore temporary read errors.
t.log.Debug("Temporary UDP read error", "err", err)
@@ -544,7 +556,12 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
}
}
-func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
+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)
@@ -563,15 +580,15 @@ func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
}
// checkBond checks if the given node has a recent enough endpoint proof.
-func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool {
- return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration
+func (t *UDPv4) checkBond(id enode.ID, ip netip.AddrPort) bool {
+ 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 *net.UDPAddr) {
- tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration
- if tooOld || t.db.FindFails(toid, toaddr.IP) > maxFindnodeFailures {
+func (t *UDPv4) ensureBond(toid enode.ID, toaddr netip.AddrPort) {
+ 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.
@@ -579,11 +596,11 @@ func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
}
}
-func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) {
+func (t *UDPv4) nodeFromRPC(sender netip.AddrPort, rn v4wire.Node) (*enode.Node, error) {
if rn.UDP <= 1024 {
return nil, errLowPort
}
- if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
+ if err := netutil.CheckRelayIP(sender.Addr().AsSlice(), rn.IP); err != nil {
return nil, err
}
if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
@@ -593,12 +610,12 @@ func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error)
if err != nil {
return nil, err
}
- n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
+ n := enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))
err = n.ValidateComplete()
return n, err
}
-func nodeToRPC(n *node) v4wire.Node {
+func nodeToRPC(n *enode.Node) v4wire.Node {
var key ecdsa.PublicKey
var ekey v4wire.Pubkey
if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
@@ -637,14 +654,14 @@ type packetHandlerV4 struct {
senderKey *ecdsa.PublicKey // used for ping
// preverify checks whether the packet is valid and should be handled at all.
- preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error
+ preverify func(p *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error
// handle handles the packet.
- handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte)
+ handle func(req *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte)
}
// PING/v4
-func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+func (t *UDPv4) verifyPing(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
req := h.Packet.(*v4wire.Ping)
if v4wire.Expired(req.Expiration) {
@@ -658,7 +675,7 @@ func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
return nil
}
-func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+func (t *UDPv4) handlePing(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
req := h.Packet.(*v4wire.Ping)
// Reply.
@@ -670,8 +687,9 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
})
// Ping back if our last pong on file is too far in the past.
- n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port))
- if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration {
+ 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(), from.Addr())) > bondExpiration {
t.sendPing(fromID, from, func() {
t.tab.addInboundNode(n)
})
@@ -680,35 +698,37 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
}
// Update node database and endpoint predictor.
- t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now())
- t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
+ 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
-func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+func (t *UDPv4) verifyPong(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
req := h.Packet.(*v4wire.Pong)
if v4wire.Expired(req.Expiration) {
return errExpired
}
- if !t.handleReply(fromID, from.IP, req) {
+ if !t.handleReply(fromID, from.Addr(), req) {
return errUnsolicitedReply
}
- t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
- t.db.UpdateLastPongReceived(fromID, from.IP, 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
}
// FINDNODE/v4
-func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
req := h.Packet.(*v4wire.Findnode)
if v4wire.Expired(req.Expiration) {
return errExpired
}
- if !t.checkBond(fromID, from.IP) {
+ if !t.checkBond(fromID, from) {
// No endpoint proof pong exists, we don't process the packet. This prevents an
// attack vector where the discovery protocol could be used to amplify traffic in a
// DDOS attack. A malicious actor would send a findnode request with the IP address
@@ -720,7 +740,7 @@ func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
return nil
}
-func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+func (t *UDPv4) handleFindnode(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
req := h.Packet.(*v4wire.Findnode)
// Determine closest nodes.
@@ -732,7 +752,7 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
var sent bool
for _, n := range closest {
- if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
+ if netutil.CheckRelayAddr(from.Addr(), n.IPAddr()) == nil {
p.Nodes = append(p.Nodes, nodeToRPC(n))
}
if len(p.Nodes) == v4wire.MaxNeighbors {
@@ -748,13 +768,13 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
// NEIGHBORS/v4
-func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
req := h.Packet.(*v4wire.Neighbors)
if v4wire.Expired(req.Expiration) {
return errExpired
}
- if !t.handleReply(fromID, from.IP, h.Packet) {
+ if !t.handleReply(fromID, from.Addr(), h.Packet) {
return errUnsolicitedReply
}
return nil
@@ -762,19 +782,19 @@ func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID en
// ENRREQUEST/v4
-func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
req := h.Packet.(*v4wire.ENRRequest)
if v4wire.Expired(req.Expiration) {
return errExpired
}
- if !t.checkBond(fromID, from.IP) {
+ if !t.checkBond(fromID, from) {
return errUnknownNode
}
return nil
}
-func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
t.send(from, fromID, &v4wire.ENRResponse{
ReplyTok: mac,
Record: *t.localNode.Node().Record(),
@@ -783,8 +803,8 @@ func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID e
// ENRRESPONSE/v4
-func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
- if !t.handleReply(fromID, from.IP, h.Packet) {
+func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
+ if !t.handleReply(fromID, from.Addr(), h.Packet) {
return errUnsolicitedReply
}
return nil
diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go
index 9c454d98e3..9d6df08ead 100644
--- a/p2p/discover/v4_udp_test.go
+++ b/p2p/discover/v4_udp_test.go
@@ -26,6 +26,7 @@ import (
"io"
"math/rand"
"net"
+ "net/netip"
"reflect"
"sync"
"testing"
@@ -55,7 +56,7 @@ type udpTest struct {
udp *UDPv4
sent [][]byte
localkey, remotekey *ecdsa.PrivateKey
- remoteaddr *net.UDPAddr
+ remoteaddr netip.AddrPort
}
func newUDPTest(t *testing.T) *udpTest {
@@ -64,7 +65,7 @@ func newUDPTest(t *testing.T) *udpTest {
pipe: newpipe(),
localkey: newkey(),
remotekey: newkey(),
- remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
+ remoteaddr: netip.MustParseAddrPort("10.0.1.99:30303"),
}
test.db, _ = enode.OpenDB("")
@@ -92,7 +93,7 @@ func (test *udpTest) packetIn(wantError error, data v4wire.Packet) {
}
// handles a packet as if it had been sent to the transport by the key/endpoint.
-func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) {
+func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr netip.AddrPort, data v4wire.Packet) {
test.t.Helper()
enc, _, err := v4wire.Encode(key, data)
@@ -106,7 +107,7 @@ func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *
}
// waits for a packet to be sent by the transport.
-// validate should have type func(X, *net.UDPAddr, []byte), where X is a packet type.
+// validate should have type func(X, netip.AddrPort, []byte), where X is a packet type.
func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
test.t.Helper()
@@ -128,7 +129,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
return false
}
- fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(hash)})
+ fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(dgram.to), reflect.ValueOf(hash)})
return false
}
@@ -236,7 +237,7 @@ func TestUDPv4_findnodeTimeout(t *testing.T) {
test := newUDPTest(t)
defer test.close()
- toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
+ toaddr := netip.AddrPortFrom(netip.MustParseAddr("1.2.3.4"), 2222)
toid := enode.ID{1, 2, 3, 4}
target := v4wire.Pubkey{4, 5, 6, 7}
result, err := test.udp.findnode(toid, toaddr, target)
@@ -261,26 +262,25 @@ func TestUDPv4_findnode(t *testing.T) {
for i := 0; i < numCandidates; i++ {
key := newkey()
ip := net.IP{10, 13, 0, byte(i)}
- n := wrapNode(enode.NewV4(&key.PublicKey, ip, 0, 2000))
+ n := enode.NewV4(&key.PublicKey, ip, 0, 2000)
// Ensure half of table content isn't verified live yet.
if i > numCandidates/2 {
- n.isValidatedLive = true
live[n.ID()] = true
}
+ test.table.addFoundNode(n, live[n.ID()])
nodes.push(n, numCandidates)
}
- fillTable(test.table, nodes.entries, false)
// 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.IP, 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)
test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp})
- waitNeighbors := func(want []*node) {
- test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) {
+ waitNeighbors := func(want []*enode.Node) {
+ test.waitPacketOut(func(p *v4wire.Neighbors, to netip.AddrPort, hash []byte) {
if len(p.Nodes) != len(want) {
t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), len(want))
return
@@ -309,10 +309,10 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
defer test.close()
rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
- test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.IP, time.Now())
+ test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.Addr(), time.Now())
// queue a pending findnode request
- resultc, errc := make(chan []*node, 1), make(chan error, 1)
+ resultc, errc := make(chan []*enode.Node, 1), make(chan error, 1)
go func() {
rid := encodePubkey(&test.remotekey.PublicKey).id()
ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget)
@@ -325,18 +325,18 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
// wait for the findnode to be sent.
// after it is sent, the transport is waiting for a reply
- test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) {
+ test.waitPacketOut(func(p *v4wire.Findnode, to netip.AddrPort, hash []byte) {
if p.Target != testTarget {
t.Errorf("wrong target: got %v, want %v", p.Target, testTarget)
}
})
// send the reply as two packets.
- list := []*node{
- wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
- wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
- wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
- wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
+ list := []*enode.Node{
+ enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"),
+ enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"),
+ enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"),
+ enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303"),
}
rpclist := make([]v4wire.Node, len(list))
for i := range list {
@@ -368,8 +368,8 @@ func TestUDPv4_pingMatch(t *testing.T) {
crand.Read(randToken)
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
- test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
- test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {})
+ test.waitPacketOut(func(*v4wire.Pong, netip.AddrPort, []byte) {})
+ test.waitPacketOut(func(*v4wire.Ping, netip.AddrPort, []byte) {})
test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
}
@@ -379,10 +379,10 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
defer test.close()
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
- test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
+ test.waitPacketOut(func(*v4wire.Pong, netip.AddrPort, []byte) {})
- test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
- wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000}
+ test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) {
+ wrongAddr := netip.MustParseAddrPort("33.44.1.2:30000")
test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{
ReplyTok: hash,
To: testLocalAnnounced,
@@ -393,41 +393,36 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
func TestUDPv4_successfulPing(t *testing.T) {
test := newUDPTest(t)
- added := make(chan *node, 1)
- test.table.nodeAddedHook = func(b *bucket, n *node) { added <- n }
+ added := make(chan *tableNode, 1)
+ test.table.nodeAddedHook = func(b *bucket, n *tableNode) { added <- n }
defer test.close()
// The remote side sends a ping packet to initiate the exchange.
go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
// The ping is replied to.
- test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) {
+ test.waitPacketOut(func(p *v4wire.Pong, to netip.AddrPort, hash []byte) {
pinghash := test.sent[0][:32]
if !bytes.Equal(p.ReplyTok, pinghash) {
t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash)
}
- wantTo := v4wire.Endpoint{
- // The mirrored UDP address is the UDP packet sender
- IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port),
- // The mirrored TCP port is the one from the ping packet
- TCP: testRemote.TCP,
- }
+ // The mirrored UDP address is the UDP packet sender.
+ // The mirrored TCP port is the one from the ping packet.
+ wantTo := v4wire.NewEndpoint(test.remoteaddr, testRemote.TCP)
if !reflect.DeepEqual(p.To, wantTo) {
t.Errorf("got pong.To %v, want %v", p.To, wantTo)
}
})
// Remote is unknown, the table pings back.
- test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
- if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) {
+ test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) {
+ wantFrom := test.udp.ourEndpoint()
+ wantFrom.IP = net.IP{}
+ if !reflect.DeepEqual(p.From, wantFrom) {
t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint())
}
- wantTo := v4wire.Endpoint{
- // The mirrored UDP address is the UDP packet sender.
- IP: test.remoteaddr.IP,
- UDP: uint16(test.remoteaddr.Port),
- TCP: 0,
- }
+ // The mirrored UDP address is the UDP packet sender.
+ wantTo := v4wire.NewEndpoint(test.remoteaddr, 0)
if !reflect.DeepEqual(p.To, wantTo) {
t.Errorf("got ping.To %v, want %v", p.To, wantTo)
}
@@ -442,11 +437,11 @@ 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.IP) {
- t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP)
+ if n.IPAddr() != test.remoteaddr.Addr() {
+ t.Errorf("node has wrong IP: got %v, want: %v", n.IPAddr(), test.remoteaddr.Addr())
}
- if n.UDP() != test.remoteaddr.Port {
- t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port)
+ if n.UDP() != int(test.remoteaddr.Port()) {
+ t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port())
}
if n.TCP() != int(testRemote.TCP) {
t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP)
@@ -469,12 +464,12 @@ func TestUDPv4_EIP868(t *testing.T) {
// Perform endpoint proof and check for sequence number in packet tail.
test.packetIn(nil, &v4wire.Ping{Expiration: futureExp})
- test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) {
+ test.waitPacketOut(func(p *v4wire.Pong, addr netip.AddrPort, hash []byte) {
if p.ENRSeq != wantNode.Seq() {
t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq())
}
})
- test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) {
+ test.waitPacketOut(func(p *v4wire.Ping, addr netip.AddrPort, hash []byte) {
if p.ENRSeq != wantNode.Seq() {
t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq())
}
@@ -483,7 +478,7 @@ func TestUDPv4_EIP868(t *testing.T) {
// Request should work now.
test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp})
- test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) {
+ test.waitPacketOut(func(p *v4wire.ENRResponse, addr netip.AddrPort, hash []byte) {
n, err := enode.New(enode.ValidSchemes, &p.Record)
if err != nil {
t.Fatalf("invalid record: %v", err)
@@ -584,7 +579,7 @@ type dgramPipe struct {
}
type dgram struct {
- to net.UDPAddr
+ to netip.AddrPort
data []byte
}
@@ -597,8 +592,8 @@ func newpipe() *dgramPipe {
}
}
-// WriteToUDP queues a datagram.
-func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) {
+// WriteToUDPAddrPort queues a datagram.
+func (c *dgramPipe) WriteToUDPAddrPort(b []byte, to netip.AddrPort) (n int, err error) {
msg := make([]byte, len(b))
copy(msg, b)
c.mu.Lock()
@@ -606,15 +601,15 @@ func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) {
if c.closed {
return 0, errors.New("closed")
}
- c.queue = append(c.queue, dgram{*to, b})
+ c.queue = append(c.queue, dgram{to, b})
c.cond.Signal()
return len(b), nil
}
-// ReadFromUDP just hangs until the pipe is closed.
-func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
+// ReadFromUDPAddrPort just hangs until the pipe is closed.
+func (c *dgramPipe) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
<-c.closing
- return 0, nil, io.EOF
+ return 0, netip.AddrPort{}, io.EOF
}
func (c *dgramPipe) Close() error {
diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go
index 9c59359fb2..958cca324d 100644
--- a/p2p/discover/v4wire/v4wire.go
+++ b/p2p/discover/v4wire/v4wire.go
@@ -25,6 +25,7 @@ import (
"fmt"
"math/big"
"net"
+ "net/netip"
"time"
"github.com/ethereum/go-ethereum/common/math"
@@ -150,14 +151,15 @@ type Endpoint struct {
}
// NewEndpoint creates an endpoint.
-func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
- ip := net.IP{}
- if ip4 := addr.IP.To4(); ip4 != nil {
- ip = ip4
- } else if ip6 := addr.IP.To16(); ip6 != nil {
- ip = ip6
+func NewEndpoint(addr netip.AddrPort, tcpPort uint16) Endpoint {
+ var ip net.IP
+ if addr.Addr().Is4() || addr.Addr().Is4In6() {
+ ip4 := addr.Addr().As4()
+ ip = ip4[:]
+ } else {
+ ip = addr.Addr().AsSlice()
}
- return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
+ return Endpoint{IP: ip, UDP: addr.Port(), TCP: tcpPort}
}
type Packet interface {
diff --git a/p2p/discover/v5_talk.go b/p2p/discover/v5_talk.go
index c1f6787940..2246b47141 100644
--- a/p2p/discover/v5_talk.go
+++ b/p2p/discover/v5_talk.go
@@ -18,6 +18,7 @@ package discover
import (
"net"
+ "net/netip"
"sync"
"time"
@@ -70,7 +71,7 @@ func (t *talkSystem) register(protocol string, handler TalkRequestHandler) {
}
// handleRequest handles a talk request.
-func (t *talkSystem) handleRequest(id enode.ID, addr *net.UDPAddr, req *v5wire.TalkRequest) {
+func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire.TalkRequest) {
t.mutex.Lock()
handler, ok := t.handlers[req.Protocol]
t.mutex.Unlock()
@@ -88,7 +89,8 @@ func (t *talkSystem) handleRequest(id enode.ID, addr *net.UDPAddr, req *v5wire.T
case <-t.slots:
go func() {
defer func() { t.slots <- struct{}{} }()
- respMessage := handler(id, addr, req.Message)
+ udpAddr := &net.UDPAddr{IP: addr.Addr().AsSlice(), Port: int(addr.Port())}
+ respMessage := handler(id, udpAddr, req.Message)
resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
t.transport.sendFromAnotherThread(id, addr, resp)
}()
diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go
index 8cdc9dfbce..81d94812aa 100644
--- a/p2p/discover/v5_udp.go
+++ b/p2p/discover/v5_udp.go
@@ -25,6 +25,7 @@ import (
"fmt"
"io"
"net"
+ "net/netip"
"slices"
"sync"
"time"
@@ -101,14 +102,14 @@ type UDPv5 struct {
type sendRequest struct {
destID enode.ID
- destAddr *net.UDPAddr
+ destAddr netip.AddrPort
msg v5wire.Packet
}
// callV5 represents a remote procedure call against another node.
type callV5 struct {
id enode.ID
- addr *net.UDPAddr
+ addr netip.AddrPort
node *enode.Node // This is required to perform handshakes.
packet v5wire.Packet
@@ -233,7 +234,7 @@ func (t *UDPv5) AllNodes() []*enode.Node {
for _, b := range &t.tab.buckets {
for _, n := range b.entries {
- nodes = append(nodes, unwrapNode(n))
+ nodes = append(nodes, n.Node)
}
}
return nodes
@@ -266,7 +267,7 @@ func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]b
}
// TalkRequestToID sends a talk request to a node and waits for a response.
-func (t *UDPv5) TalkRequestToID(id enode.ID, addr *net.UDPAddr, protocol string, request []byte) ([]byte, error) {
+func (t *UDPv5) TalkRequestToID(id enode.ID, addr netip.AddrPort, protocol string, request []byte) ([]byte, error) {
req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
resp := t.callToID(id, addr, v5wire.TalkResponseMsg, req)
defer t.callDone(resp)
@@ -314,26 +315,26 @@ func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
}
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
- return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
+ return newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
return t.lookupWorker(n, target)
})
}
// lookupWorker performs FINDNODE calls against a single node during lookup.
-func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
+func (t *UDPv5) lookupWorker(destNode *enode.Node, target enode.ID) ([]*enode.Node, error) {
var (
dists = lookupDistances(target, destNode.ID())
nodes = nodesByDistance{target: target}
err error
)
var r []*enode.Node
- r, err = t.findnode(unwrapNode(destNode), dists)
+ r, err = t.findnode(destNode, dists)
if errors.Is(err, errClosed) {
return nil, err
}
for _, n := range r {
if n.ID() != t.Self().ID() {
- nodes.push(wrapNode(n), findnodeResultLimit)
+ nodes.push(n, findnodeResultLimit)
}
}
return nodes.entries, err
@@ -427,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.IP, 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 {
@@ -452,14 +453,14 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
// callToNode sends the given call and sets up a handler for response packets (of message
// type responseType). Responses are dispatched to the call's response channel.
func (t *UDPv5) callToNode(n *enode.Node, responseType byte, req v5wire.Packet) *callV5 {
- addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
+ addr, _ := n.UDPEndpoint()
c := &callV5{id: n.ID(), addr: addr, node: n}
t.initCall(c, responseType, req)
return c
}
// callToID is like callToNode, but for cases where the node record is not available.
-func (t *UDPv5) callToID(id enode.ID, addr *net.UDPAddr, responseType byte, req v5wire.Packet) *callV5 {
+func (t *UDPv5) callToID(id enode.ID, addr netip.AddrPort, responseType byte, req v5wire.Packet) *callV5 {
c := &callV5{id: id, addr: addr}
t.initCall(c, responseType, req)
return c
@@ -619,12 +620,12 @@ func (t *UDPv5) sendCall(c *callV5) {
// sendResponse sends a response packet to the given node.
// This doesn't trigger a handshake even if no keys are available.
-func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error {
+func (t *UDPv5) sendResponse(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet) error {
_, err := t.send(toID, toAddr, packet, nil)
return err
}
-func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) {
+func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet) {
select {
case t.sendCh <- sendRequest{toID, toAddr, packet}:
case <-t.closeCtx.Done():
@@ -632,7 +633,7 @@ func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr *net.UDPAddr, packet
}
// send sends a packet to the given node.
-func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
+func (t *UDPv5) send(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
addr := toAddr.String()
t.logcontext = append(t.logcontext[:0], "id", toID, "addr", addr)
t.logcontext = packet.AppendLogInfo(t.logcontext)
@@ -644,7 +645,7 @@ func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c
return nonce, err
}
- _, err = t.conn.WriteToUDP(enc, toAddr)
+ _, err = t.conn.WriteToUDPAddrPort(enc, toAddr)
t.log.Trace(">> "+packet.Name(), t.logcontext...)
return nonce, err
}
@@ -655,7 +656,7 @@ func (t *UDPv5) readLoop() {
buf := make([]byte, maxPacketSize)
for range t.readNextCh {
- nbytes, from, err := t.conn.ReadFromUDP(buf)
+ nbytes, from, err := t.conn.ReadFromUDPAddrPort(buf)
if netutil.IsTemporaryError(err) {
// Ignore temporary read errors.
t.log.Debug("Temporary UDP read error", "err", err)
@@ -672,7 +673,11 @@ func (t *UDPv5) readLoop() {
}
// dispatchReadPacket sends a packet into the dispatch loop.
-func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
+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
@@ -682,7 +687,7 @@ func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
}
// handlePacket decodes and processes an incoming packet from the network.
-func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
+func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr netip.AddrPort) error {
addr := fromAddr.String()
fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
if err != nil {
@@ -699,7 +704,7 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
}
if fromNode != nil {
// Handshake succeeded, add to table.
- t.tab.addInboundNode(wrapNode(fromNode))
+ t.tab.addInboundNode(fromNode)
}
if packet.Kind() != v5wire.WhoareyouPacket {
// WHOAREYOU logged separately to report errors.
@@ -712,13 +717,13 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
}
// handleCallResponse dispatches a response packet to the call waiting for it.
-func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool {
+func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr netip.AddrPort, p v5wire.Packet) bool {
ac := t.activeCallByNode[fromID]
if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
return false
}
- if !fromAddr.IP.Equal(ac.addr.IP) || fromAddr.Port != ac.addr.Port {
+ if fromAddr != ac.addr {
t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
return false
}
@@ -743,7 +748,7 @@ func (t *UDPv5) getNode(id enode.ID) *enode.Node {
}
// handle processes incoming packets according to their message type.
-func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) {
+func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr netip.AddrPort) {
switch p := p.(type) {
case *v5wire.Unknown:
t.handleUnknown(p, fromID, fromAddr)
@@ -753,7 +758,8 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr)
t.handlePing(p, fromID, fromAddr)
case *v5wire.Pong:
if t.handleCallResponse(fromID, fromAddr, p) {
- t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
+ toAddr := netip.AddrPortFrom(netutil.IPToAddr(p.ToIP), p.ToPort)
+ t.localNode.UDPEndpointStatement(fromAddr, toAddr)
}
case *v5wire.Findnode:
t.handleFindnode(p, fromID, fromAddr)
@@ -767,7 +773,7 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr)
}
// handleUnknown initiates a handshake by responding with WHOAREYOU.
-func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) {
+func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr netip.AddrPort) {
challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
crand.Read(challenge.IDNonce[:])
if n := t.getNode(fromID); n != nil {
@@ -783,7 +789,7 @@ var (
)
// handleWhoareyou resends the active call as a handshake packet.
-func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) {
+func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr netip.AddrPort) {
c, err := t.matchWithCall(fromID, p.Nonce)
if err != nil {
t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
@@ -817,32 +823,34 @@ func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, err
}
// handlePing sends a PONG response.
-func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) {
- remoteIP := fromAddr.IP
- // Handle IPv4 mapped IPv6 addresses in the
- // event the local node is binded to an
- // ipv6 interface.
- if remoteIP.To4() != nil {
- remoteIP = remoteIP.To4()
+func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr netip.AddrPort) {
+ var remoteIP net.IP
+ // Handle IPv4 mapped IPv6 addresses in the event the local node is binded
+ // to an ipv6 interface.
+ if fromAddr.Addr().Is4() || fromAddr.Addr().Is4In6() {
+ ip4 := fromAddr.Addr().As4()
+ remoteIP = ip4[:]
+ } else {
+ remoteIP = fromAddr.Addr().AsSlice()
}
t.sendResponse(fromID, fromAddr, &v5wire.Pong{
ReqID: p.ReqID,
ToIP: remoteIP,
- ToPort: uint16(fromAddr.Port),
+ ToPort: fromAddr.Port(),
ENRSeq: t.localNode.Node().Seq(),
})
}
// handleFindnode returns nodes to the requester.
-func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) {
- nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit)
+func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr netip.AddrPort) {
+ nodes := t.collectTableNodes(fromAddr.Addr(), p.Distances, findnodeResultLimit)
for _, resp := range packNodes(p.ReqID, nodes) {
t.sendResponse(fromID, fromAddr, resp)
}
}
// collectTableNodes creates a FINDNODE result set for the given distances.
-func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
+func (t *UDPv5) collectTableNodes(rip netip.Addr, distances []uint, limit int) []*enode.Node {
var bn []*enode.Node
var nodes []*enode.Node
var processed = make(map[uint]struct{})
@@ -857,7 +865,7 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en
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(rip, n.IP()) != nil {
+ if netutil.CheckRelayAddr(rip, n.IPAddr()) != nil {
continue
}
nodes = append(nodes, n)
diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go
index 0015f7cc70..1f8e972200 100644
--- a/p2p/discover/v5_udp_test.go
+++ b/p2p/discover/v5_udp_test.go
@@ -23,6 +23,7 @@ import (
"fmt"
"math/rand"
"net"
+ "net/netip"
"reflect"
"slices"
"testing"
@@ -103,7 +104,7 @@ func TestUDPv5_pingHandling(t *testing.T) {
defer test.close()
test.packetIn(&v5wire.Ping{ReqID: []byte("foo")})
- test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Pong, addr netip.AddrPort, _ v5wire.Nonce) {
if !bytes.Equal(p.ReqID, []byte("foo")) {
t.Error("wrong request ID in response:", p.ReqID)
}
@@ -135,16 +136,16 @@ func TestUDPv5_unknownPacket(t *testing.T) {
// Unknown packet from unknown node.
test.packetIn(&v5wire.Unknown{Nonce: nonce})
- test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
check(p, 0)
})
// Make node known.
n := test.getNode(test.remotekey, test.remoteaddr).Node()
- test.table.addFoundNode(wrapNode(n))
+ test.table.addFoundNode(n, false)
test.packetIn(&v5wire.Unknown{Nonce: nonce})
- test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
check(p, n.Seq())
})
}
@@ -159,9 +160,9 @@ func TestUDPv5_findnodeHandling(t *testing.T) {
nodes253 := nodesAtDistance(test.table.self().ID(), 253, 16)
nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4)
nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10)
- fillTable(test.table, wrapNodes(nodes253), true)
- fillTable(test.table, wrapNodes(nodes249), true)
- fillTable(test.table, wrapNodes(nodes248), true)
+ fillTable(test.table, nodes253, true)
+ fillTable(test.table, nodes249, true)
+ fillTable(test.table, nodes248, true)
// Requesting with distance zero should return the node's own record.
test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}})
@@ -199,7 +200,7 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes
}
for {
- test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Nodes, addr netip.AddrPort, _ v5wire.Nonce) {
if !bytes.Equal(p.ReqID, wantReqID) {
test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID)
}
@@ -238,7 +239,7 @@ func TestUDPv5_pingCall(t *testing.T) {
_, err := test.udp.ping(remote)
done <- err
}()
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {})
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {})
if err := <-done; err != errTimeout {
t.Fatalf("want errTimeout, got %q", err)
}
@@ -248,7 +249,7 @@ func TestUDPv5_pingCall(t *testing.T) {
_, err := test.udp.ping(remote)
done <- err
}()
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID})
})
if err := <-done; err != nil {
@@ -260,8 +261,8 @@ func TestUDPv5_pingCall(t *testing.T) {
_, err := test.udp.ping(remote)
done <- err
}()
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
- wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101}
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
+ wrongAddr := netip.MustParseAddrPort("33.44.55.22:10101")
test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID})
})
if err := <-done; err != errTimeout {
@@ -291,7 +292,7 @@ func TestUDPv5_findnodeCall(t *testing.T) {
}()
// Serve the responses:
- test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Findnode, addr netip.AddrPort, _ v5wire.Nonce) {
if !reflect.DeepEqual(p.Distances, distances) {
t.Fatalf("wrong distances in request: %v", p.Distances)
}
@@ -337,15 +338,15 @@ func TestUDPv5_callResend(t *testing.T) {
}()
// Ping answered by WHOAREYOU.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
})
// Ping should be re-sent.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
})
// Answer the other ping.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
})
if err := <-done; err != nil {
@@ -370,11 +371,11 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) {
}()
// Ping answered by WHOAREYOU.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
})
// Ping answered by WHOAREYOU again.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
})
if err := <-done; err != errTimeout {
@@ -401,7 +402,7 @@ func TestUDPv5_callTimeoutReset(t *testing.T) {
}()
// Serve two responses, slowly.
- test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Findnode, addr netip.AddrPort, _ v5wire.Nonce) {
time.Sleep(respTimeout - 50*time.Millisecond)
test.packetIn(&v5wire.Nodes{
ReqID: p.ReqID,
@@ -439,7 +440,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
Protocol: "test",
Message: []byte("test request"),
})
- test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.TalkResponse, addr netip.AddrPort, _ v5wire.Nonce) {
if !bytes.Equal(p.ReqID, []byte("foo")) {
t.Error("wrong request ID in response:", p.ReqID)
}
@@ -458,7 +459,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
Protocol: "wrong",
Message: []byte("test request"),
})
- test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.TalkResponse, addr netip.AddrPort, _ v5wire.Nonce) {
if !bytes.Equal(p.ReqID, []byte("2")) {
t.Error("wrong request ID in response:", p.ReqID)
}
@@ -485,7 +486,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
done <- err
}()
- test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {})
+ test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {})
if err := <-done; err != errTimeout {
t.Fatalf("want errTimeout, got %q", err)
}
@@ -495,7 +496,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
done <- err
}()
- test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {
if p.Protocol != "test" {
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
}
@@ -516,7 +517,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
_, err := test.udp.TalkRequestToID(remote.ID(), test.remoteaddr, "test", []byte("test request 2"))
done <- err
}()
- test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {
if p.Protocol != "test" {
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
}
@@ -583,13 +584,14 @@ func TestUDPv5_lookup(t *testing.T) {
for d, nn := range lookupTestnet.dists {
for i, key := range nn {
n := lookupTestnet.node(d, i)
- test.getNode(key, &net.UDPAddr{IP: n.IP(), Port: n.UDP()})
+ addr, _ := n.UDPEndpoint()
+ test.getNode(key, addr)
}
}
// Seed table with initial node.
initialNode := lookupTestnet.node(256, 0)
- fillTable(test.table, []*node{wrapNode(initialNode)}, true)
+ fillTable(test.table, []*enode.Node{initialNode}, true)
// Start the lookup.
resultC := make(chan []*enode.Node, 1)
@@ -601,7 +603,7 @@ func TestUDPv5_lookup(t *testing.T) {
// Answer lookup packets.
asked := make(map[enode.ID]bool)
for done := false; !done; {
- done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) {
+ done = test.waitPacketOut(func(p v5wire.Packet, to netip.AddrPort, _ v5wire.Nonce) {
recipient, key := lookupTestnet.nodeByAddr(to)
switch p := p.(type) {
case *v5wire.Ping:
@@ -652,11 +654,8 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
test := newUDPV5Test(t)
defer test.close()
- rawIP := net.IPv4(0xFF, 0x12, 0x33, 0xE5)
- test.remoteaddr = &net.UDPAddr{
- IP: rawIP.To16(),
- Port: 0,
- }
+ rawIP := netip.AddrFrom4([4]byte{0xFF, 0x12, 0x33, 0xE5})
+ test.remoteaddr = netip.AddrPortFrom(netip.AddrFrom16(rawIP.As16()), 0)
remote := test.getNode(test.remotekey, test.remoteaddr).Node()
done := make(chan struct{}, 1)
@@ -665,14 +664,14 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr)
done <- struct{}{}
}()
- test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
+ test.waitPacketOut(func(p *v5wire.Pong, addr netip.AddrPort, _ v5wire.Nonce) {
if len(p.ToIP) == net.IPv6len {
t.Error("Received untruncated ip address")
}
if len(p.ToIP) != net.IPv4len {
t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP))
}
- if !p.ToIP.Equal(rawIP) {
+ if !p.ToIP.Equal(rawIP.AsSlice()) {
t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String())
}
})
@@ -688,9 +687,9 @@ type udpV5Test struct {
db *enode.DB
udp *UDPv5
localkey, remotekey *ecdsa.PrivateKey
- remoteaddr *net.UDPAddr
+ remoteaddr netip.AddrPort
nodesByID map[enode.ID]*enode.LocalNode
- nodesByIP map[string]*enode.LocalNode
+ nodesByIP map[netip.Addr]*enode.LocalNode
}
// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption.
@@ -750,9 +749,9 @@ func newUDPV5Test(t *testing.T) *udpV5Test {
pipe: newpipe(),
localkey: newkey(),
remotekey: newkey(),
- remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
+ remoteaddr: netip.MustParseAddrPort("10.0.1.99:30303"),
nodesByID: make(map[enode.ID]*enode.LocalNode),
- nodesByIP: make(map[string]*enode.LocalNode),
+ nodesByIP: make(map[netip.Addr]*enode.LocalNode),
}
test.db, _ = enode.OpenDB("")
ln := enode.NewLocalNode(test.db, test.localkey)
@@ -777,8 +776,8 @@ func (test *udpV5Test) packetIn(packet v5wire.Packet) {
test.packetInFrom(test.remotekey, test.remoteaddr, packet)
}
-// handles a packet as if it had been sent to the transport by the key/endpoint.
-func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) {
+// packetInFrom handles a packet as if it had been sent to the transport by the key/endpoint.
+func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr netip.AddrPort, packet v5wire.Packet) {
test.t.Helper()
ln := test.getNode(key, addr)
@@ -793,22 +792,22 @@ func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, pa
}
// getNode ensures the test knows about a node at the given endpoint.
-func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.LocalNode {
+func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr netip.AddrPort) *enode.LocalNode {
id := encodePubkey(&key.PublicKey).id()
ln := test.nodesByID[id]
if ln == nil {
db, _ := enode.OpenDB("")
ln = enode.NewLocalNode(db, key)
- ln.SetStaticIP(addr.IP)
- ln.Set(enr.UDP(addr.Port))
+ ln.SetStaticIP(addr.Addr().AsSlice())
+ ln.Set(enr.UDP(addr.Port()))
test.nodesByID[id] = ln
}
- test.nodesByIP[string(addr.IP)] = ln
+ test.nodesByIP[addr.Addr()] = ln
return ln
}
// waitPacketOut waits for the next output packet and handles it using the given 'validate'
-// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is
+// function. The function must be of type func (X, netip.AddrPort, v5wire.Nonce) where X is
// assignable to packetV5.
func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
test.t.Helper()
@@ -824,7 +823,7 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
test.t.Fatalf("timed out waiting for %v", exptype)
return false
}
- ln := test.nodesByIP[string(dgram.to.IP)]
+ ln := test.nodesByIP[dgram.to.Addr()]
if ln == nil {
test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to)
return false
@@ -839,7 +838,7 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
return false
}
- fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(frame.AuthTag)})
+ fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(dgram.to), reflect.ValueOf(frame.AuthTag)})
return false
}
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 a3c53b0781..172f0667eb 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -19,11 +19,13 @@ package p2p
import (
"bytes"
+ "cmp"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"net"
+ "net/netip"
"slices"
"sync"
"sync/atomic"
@@ -435,11 +437,11 @@ type sharedUDPConn struct {
unhandled chan discover.ReadPacket
}
-// ReadFromUDP implements discover.UDPConn
-func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
+// ReadFromUDPAddrPort implements discover.UDPConn
+func (s *sharedUDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
packet, ok := <-s.unhandled
if !ok {
- return 0, nil, errors.New("connection was closed")
+ return 0, netip.AddrPort{}, errors.New("connection was closed")
}
l := len(packet.Data)
if l > len(b) {
@@ -904,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())
@@ -923,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))
@@ -1107,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{}),
}
@@ -1138,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
}
diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go
index 9113c091c5..73243b16a1 100644
--- a/signer/core/apitypes/types.go
+++ b/signer/core/apitypes/types.go
@@ -67,9 +67,9 @@ func (vs *ValidationMessages) Info(msg string) {
}
// GetWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present
-func (v *ValidationMessages) GetWarnings() error {
+func (vs *ValidationMessages) GetWarnings() error {
var messages []string
- for _, msg := range v.Messages {
+ for _, msg := range vs.Messages {
if msg.Typ == WARN || msg.Typ == CRIT {
messages = append(messages, msg.Message)
}
diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go
index b8c3acfb4d..43edfe7d97 100644
--- a/signer/core/uiapi.go
+++ b/signer/core/uiapi.go
@@ -52,9 +52,9 @@ func NewUIServerAPI(extapi *SignerAPI) *UIServerAPI {
// the full Account object and not only Address.
// Example call
// {"jsonrpc":"2.0","method":"clef_listAccounts","params":[], "id":4}
-func (s *UIServerAPI) ListAccounts(ctx context.Context) ([]accounts.Account, error) {
+func (api *UIServerAPI) ListAccounts(ctx context.Context) ([]accounts.Account, error) {
var accs []accounts.Account
- for _, wallet := range s.am.Wallets() {
+ for _, wallet := range api.am.Wallets() {
accs = append(accs, wallet.Accounts()...)
}
return accs, nil
@@ -72,9 +72,9 @@ type rawWallet struct {
// ListWallets will return a list of wallets that clef manages
// Example call
// {"jsonrpc":"2.0","method":"clef_listWallets","params":[], "id":5}
-func (s *UIServerAPI) ListWallets() []rawWallet {
+func (api *UIServerAPI) ListWallets() []rawWallet {
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
- for _, wallet := range s.am.Wallets() {
+ for _, wallet := range api.am.Wallets() {
status, failure := wallet.Status()
raw := rawWallet{
@@ -94,8 +94,8 @@ func (s *UIServerAPI) ListWallets() []rawWallet {
// it for later reuse.
// Example call
// {"jsonrpc":"2.0","method":"clef_deriveAccount","params":["ledger://","m/44'/60'/0'", false], "id":6}
-func (s *UIServerAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
- wallet, err := s.am.Wallet(url)
+func (api *UIServerAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
+ wallet, err := api.am.Wallet(url)
if err != nil {
return accounts.Account{}, err
}
@@ -122,7 +122,7 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
// encrypting it with the passphrase.
// Example call (should fail on password too short)
// {"jsonrpc":"2.0","method":"clef_importRawKey","params":["1111111111111111111111111111111111111111111111111111111111111111","test"], "id":6}
-func (s *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Account, error) {
+func (api *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Account, error) {
key, err := crypto.HexToECDSA(privkey)
if err != nil {
return accounts.Account{}, err
@@ -131,7 +131,7 @@ func (s *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Ac
return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err)
}
// No error
- return fetchKeystore(s.am).ImportECDSA(key, password)
+ return fetchKeystore(api.am).ImportECDSA(key, password)
}
// OpenWallet initiates a hardware wallet opening procedure, establishing a USB
@@ -140,8 +140,8 @@ func (s *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Ac
// Trezor PIN matrix challenge).
// Example
// {"jsonrpc":"2.0","method":"clef_openWallet","params":["ledger://",""], "id":6}
-func (s *UIServerAPI) OpenWallet(url string, passphrase *string) error {
- wallet, err := s.am.Wallet(url)
+func (api *UIServerAPI) OpenWallet(url string, passphrase *string) error {
+ wallet, err := api.am.Wallet(url)
if err != nil {
return err
}
@@ -155,24 +155,24 @@ func (s *UIServerAPI) OpenWallet(url string, passphrase *string) error {
// ChainId returns the chainid in use for Eip-155 replay protection
// Example call
// {"jsonrpc":"2.0","method":"clef_chainId","params":[], "id":8}
-func (s *UIServerAPI) ChainId() math.HexOrDecimal64 {
- return (math.HexOrDecimal64)(s.extApi.chainID.Uint64())
+func (api *UIServerAPI) ChainId() math.HexOrDecimal64 {
+ return (math.HexOrDecimal64)(api.extApi.chainID.Uint64())
}
// SetChainId sets the chain id to use when signing transactions.
// Example call to set Ropsten:
// {"jsonrpc":"2.0","method":"clef_setChainId","params":["3"], "id":8}
-func (s *UIServerAPI) SetChainId(id math.HexOrDecimal64) math.HexOrDecimal64 {
- s.extApi.chainID = new(big.Int).SetUint64(uint64(id))
- return s.ChainId()
+func (api *UIServerAPI) SetChainId(id math.HexOrDecimal64) math.HexOrDecimal64 {
+ api.extApi.chainID = new(big.Int).SetUint64(uint64(id))
+ return api.ChainId()
}
// Export returns encrypted private key associated with the given address in web3 keystore format.
// Example
// {"jsonrpc":"2.0","method":"clef_export","params":["0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a"], "id":4}
-func (s *UIServerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
+func (api *UIServerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
// Look up the wallet containing the requested signer
- wallet, err := s.am.Find(accounts.Account{Address: addr})
+ wallet, err := api.am.Find(accounts.Account{Address: addr})
if err != nil {
return nil, err
}
diff --git a/trie/iterator.go b/trie/iterator.go
index 83ccc0740f..fa01611063 100644
--- a/trie/iterator.go
+++ b/trie/iterator.go
@@ -135,7 +135,7 @@ type nodeIteratorState struct {
node node // Trie node being iterated
parent common.Hash // Hash of the first full ancestor node (nil if current is the root)
index int // Child to be processed next
- pathlen int // Length of the path to this node
+ pathlen int // Length of the path to the parent node
}
type nodeIterator struct {
@@ -145,7 +145,7 @@ type nodeIterator struct {
err error // Failure set in case of an internal error in the iterator
resolver NodeResolver // optional node resolver for avoiding disk hits
- pool []*nodeIteratorState // local pool for iteratorstates
+ pool []*nodeIteratorState // local pool for iterator states
}
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
@@ -304,6 +304,7 @@ func (it *nodeIterator) seek(prefix []byte) error {
// The path we're looking for is the hex encoded key without terminator.
key := keybytesToHex(prefix)
key = key[:len(key)-1]
+
// Move forward until we're just before the closest match to key.
for {
state, parentIndex, path, err := it.peekSeek(key)
@@ -311,7 +312,7 @@ func (it *nodeIterator) seek(prefix []byte) error {
return errIteratorEnd
} else if err != nil {
return seekError{prefix, err}
- } else if bytes.Compare(path, key) >= 0 {
+ } else if reachedPath(path, key) {
return nil
}
it.push(state, parentIndex, path)
@@ -339,7 +340,6 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er
// If we're skipping children, pop the current node first
it.pop()
}
-
// Continue iteration to the next child
for len(it.stack) > 0 {
parent := it.stack[len(it.stack)-1]
@@ -372,7 +372,6 @@ func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []by
// If we're skipping children, pop the current node first
it.pop()
}
-
// Continue iteration to the next child
for len(it.stack) > 0 {
parent := it.stack[len(it.stack)-1]
@@ -449,16 +448,18 @@ func (it *nodeIterator) findChild(n *fullNode, index int, ancestor common.Hash)
state *nodeIteratorState
childPath []byte
)
- for ; index < len(n.Children); index++ {
+ for ; index < len(n.Children); index = nextChildIndex(index) {
if n.Children[index] != nil {
child = n.Children[index]
hash, _ := child.cache()
+
state = it.getFromPool()
state.hash = common.BytesToHash(hash)
state.node = child
state.parent = ancestor
state.index = -1
state.pathlen = len(path)
+
childPath = append(childPath, path...)
childPath = append(childPath, byte(index))
return child, state, childPath, index
@@ -471,8 +472,8 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has
switch node := parent.node.(type) {
case *fullNode:
// Full node, move to the first non-nil child.
- if child, state, path, index := it.findChild(node, parent.index+1, ancestor); child != nil {
- parent.index = index - 1
+ if child, state, path, index := it.findChild(node, nextChildIndex(parent.index), ancestor); child != nil {
+ parent.index = prevChildIndex(index)
return state, path, true
}
case *shortNode:
@@ -498,23 +499,23 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H
switch n := parent.node.(type) {
case *fullNode:
// Full node, move to the first non-nil child before the desired key position
- child, state, path, index := it.findChild(n, parent.index+1, ancestor)
+ child, state, path, index := it.findChild(n, nextChildIndex(parent.index), ancestor)
if child == nil {
// No more children in this fullnode
return parent, it.path, false
}
// If the child we found is already past the seek position, just return it.
- if bytes.Compare(path, key) >= 0 {
- parent.index = index - 1
+ if reachedPath(path, key) {
+ parent.index = prevChildIndex(index)
return state, path, true
}
// The child is before the seek position. Try advancing
for {
- nextChild, nextState, nextPath, nextIndex := it.findChild(n, index+1, ancestor)
+ nextChild, nextState, nextPath, nextIndex := it.findChild(n, nextChildIndex(index), ancestor)
// If we run out of children, or skipped past the target, return the
// previous one
- if nextChild == nil || bytes.Compare(nextPath, key) >= 0 {
- parent.index = index - 1
+ if nextChild == nil || reachedPath(nextPath, key) {
+ parent.index = prevChildIndex(index)
return state, path, true
}
// We found a better child closer to the target
@@ -541,7 +542,7 @@ func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []
it.path = path
it.stack = append(it.stack, state)
if parentIndex != nil {
- *parentIndex++
+ *parentIndex = nextChildIndex(*parentIndex)
}
}
@@ -550,8 +551,54 @@ func (it *nodeIterator) pop() {
it.path = it.path[:last.pathlen]
it.stack[len(it.stack)-1] = nil
it.stack = it.stack[:len(it.stack)-1]
- // last is now unused
- it.putInPool(last)
+
+ it.putInPool(last) // last is now unused
+}
+
+// reachedPath normalizes a path by truncating a terminator if present, and
+// returns true if it is greater than or equal to the target. Using this,
+// the path of a value node embedded a full node will compare less than the
+// full node's children.
+func reachedPath(path, target []byte) bool {
+ if hasTerm(path) {
+ path = path[:len(path)-1]
+ }
+ return bytes.Compare(path, target) >= 0
+}
+
+// A value embedded in a full node occupies the last slot (16) of the array of
+// children. In order to produce a pre-order traversal when iterating children,
+// we jump to this last slot first, then go back iterate the child nodes (and
+// skip the last slot at the end):
+
+// prevChildIndex returns the index of a child in a full node which precedes
+// the given index when performing a pre-order traversal.
+func prevChildIndex(index int) int {
+ switch index {
+ case 0: // We jumped back to iterate the children, from the value slot
+ return 16
+ case 16: // We jumped to the embedded value slot at the end, from the placeholder index
+ return -1
+ case 17: // We skipped the value slot after iterating all the children
+ return 15
+ default: // We are iterating the children in sequence
+ return index - 1
+ }
+}
+
+// nextChildIndex returns the index of a child in a full node which follows
+// the given index when performing a pre-order traversal.
+func nextChildIndex(index int) int {
+ switch index {
+ case -1: // Jump from the placeholder index to the embedded value slot
+ return 16
+ case 15: // Skip the value slot after iterating the children
+ return 17
+ case 16: // From the embedded value slot, jump back to iterate the children
+ return 0
+ default: // Iterate children in sequence
+ return index + 1
+ }
}
func compareNodes(a, b NodeIterator) int {
diff --git a/trie/iterator_test.go b/trie/iterator_test.go
index 41e83f6cb6..b463294b09 100644
--- a/trie/iterator_test.go
+++ b/trie/iterator_test.go
@@ -59,7 +59,7 @@ func TestIterator(t *testing.T) {
all[val.k] = val.v
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
trie, _ = New(TrieID(root), db)
@@ -182,14 +182,14 @@ func testNodeIteratorCoverage(t *testing.T, scheme string) {
type kvs struct{ k, v string }
var testdata1 = []kvs{
+ {"bar", "b"},
{"barb", "ba"},
{"bard", "bc"},
{"bars", "bb"},
- {"bar", "b"},
{"fab", "z"},
+ {"foo", "a"},
{"food", "ab"},
{"foos", "aa"},
- {"foo", "a"},
}
var testdata2 = []kvs{
@@ -218,7 +218,7 @@ func TestIteratorSeek(t *testing.T) {
// Seek to a non-existent key.
it = NewIterator(trie.MustNodeIterator([]byte("barc")))
- if err := checkIteratorOrder(testdata1[1:], it); err != nil {
+ if err := checkIteratorOrder(testdata1[2:], it); err != nil {
t.Fatal(err)
}
@@ -227,6 +227,12 @@ func TestIteratorSeek(t *testing.T) {
if err := checkIteratorOrder(nil, it); err != nil {
t.Fatal(err)
}
+
+ // Seek to a key for which a prefixing key exists.
+ it = NewIterator(trie.MustNodeIterator([]byte("food")))
+ if err := checkIteratorOrder(testdata1[6:], it); err != nil {
+ t.Fatal(err)
+ }
}
func checkIteratorOrder(want []kvs, it *Iterator) error {
@@ -251,7 +257,7 @@ func TestDifferenceIterator(t *testing.T) {
for _, val := range testdata1 {
triea.MustUpdate([]byte(val.k), []byte(val.v))
}
- rootA, nodesA, _ := triea.Commit(false)
+ rootA, nodesA := triea.Commit(false)
dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA))
triea, _ = New(TrieID(rootA), dba)
@@ -260,7 +266,7 @@ func TestDifferenceIterator(t *testing.T) {
for _, val := range testdata2 {
trieb.MustUpdate([]byte(val.k), []byte(val.v))
}
- rootB, nodesB, _ := trieb.Commit(false)
+ rootB, nodesB := trieb.Commit(false)
dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB))
trieb, _ = New(TrieID(rootB), dbb)
@@ -293,7 +299,7 @@ func TestUnionIterator(t *testing.T) {
for _, val := range testdata1 {
triea.MustUpdate([]byte(val.k), []byte(val.v))
}
- rootA, nodesA, _ := triea.Commit(false)
+ rootA, nodesA := triea.Commit(false)
dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA))
triea, _ = New(TrieID(rootA), dba)
@@ -302,7 +308,7 @@ func TestUnionIterator(t *testing.T) {
for _, val := range testdata2 {
trieb.MustUpdate([]byte(val.k), []byte(val.v))
}
- rootB, nodesB, _ := trieb.Commit(false)
+ rootB, nodesB := trieb.Commit(false)
dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB))
trieb, _ = New(TrieID(rootB), dbb)
@@ -311,16 +317,16 @@ func TestUnionIterator(t *testing.T) {
all := []struct{ k, v string }{
{"aardvark", "c"},
+ {"bar", "b"},
{"barb", "ba"},
{"barb", "bd"},
{"bard", "bc"},
{"bars", "bb"},
{"bars", "be"},
- {"bar", "b"},
{"fab", "z"},
+ {"foo", "a"},
{"food", "ab"},
{"foos", "aa"},
- {"foo", "a"},
{"jars", "d"},
}
@@ -365,7 +371,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) {
for _, val := range testdata1 {
tr.MustUpdate([]byte(val.k), []byte(val.v))
}
- root, nodes, _ := tr.Commit(false)
+ root, nodes := tr.Commit(false)
tdb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
if !memonly {
tdb.Commit(root)
@@ -475,7 +481,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme strin
for _, val := range testdata1 {
ctr.MustUpdate([]byte(val.k), []byte(val.v))
}
- root, nodes, _ := ctr.Commit(false)
+ root, nodes := ctr.Commit(false)
for path, n := range nodes.Nodes {
if n.Hash == barNodeHash {
barNodePath = []byte(path)
@@ -512,7 +518,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme strin
rawdb.WriteTrieNode(diskdb, common.Hash{}, barNodePath, barNodeHash, barNodeBlob, triedb.Scheme())
}
// Check that iteration produces the right set of values.
- if err := checkIteratorOrder(testdata1[2:], NewIterator(it)); err != nil {
+ if err := checkIteratorOrder(testdata1[3:], NewIterator(it)); err != nil {
t.Fatal(err)
}
}
@@ -555,7 +561,7 @@ func testIteratorNodeBlob(t *testing.T, scheme string) {
all[val.k] = val.v
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
triedb.Commit(root)
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index e38d5ac4dc..fb39a80609 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -221,7 +221,7 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
// All cached preimages will be also flushed if preimages recording is enabled.
// Once the trie is committed, it's not usable anymore. A new trie must
// be created with new root and updated trie database for following usage
-func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
+func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
// Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
preimages := make(map[common.Hash][]byte)
diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go
index 0a6fd688b7..59958d33f4 100644
--- a/trie/secure_trie_test.go
+++ b/trie/secure_trie_test.go
@@ -60,7 +60,7 @@ func makeTestStateTrie() (*testDb, *StateTrie, map[string][]byte) {
trie.MustUpdate(key, val)
}
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil {
panic(fmt.Errorf("failed to commit db %v", err))
}
diff --git a/trie/stacktrie_fuzzer_test.go b/trie/stacktrie_fuzzer_test.go
index 418b941d94..df487d16bf 100644
--- a/trie/stacktrie_fuzzer_test.go
+++ b/trie/stacktrie_fuzzer_test.go
@@ -79,10 +79,7 @@ func fuzz(data []byte, debugging bool) {
return
}
// Flush trie -> database
- rootA, nodes, err := trieA.Commit(false)
- if err != nil {
- panic(err)
- }
+ rootA, nodes := trieA.Commit(false)
if nodes != nil {
dbA.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
}
diff --git a/trie/sync_test.go b/trie/sync_test.go
index 7221b06f59..ccdee7d014 100644
--- a/trie/sync_test.go
+++ b/trie/sync_test.go
@@ -58,7 +58,7 @@ func makeTestTrie(scheme string) (ethdb.Database, *testDb, *StateTrie, map[strin
trie.MustUpdate(key, val)
}
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil {
panic(fmt.Errorf("failed to commit db %v", err))
}
@@ -771,7 +771,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) {
srcTrie.MustUpdate(key, val)
diff[string(key)] = val
}
- root, nodes, _ := srcTrie.Commit(false)
+ root, nodes := srcTrie.Commit(false)
if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil {
panic(err)
}
@@ -796,7 +796,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) {
srcTrie.MustUpdate([]byte(k), val)
reverted[k] = val
}
- root, nodes, _ = srcTrie.Commit(false)
+ root, nodes = srcTrie.Commit(false)
if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil {
panic(err)
}
@@ -847,7 +847,7 @@ func testPivotMove(t *testing.T, scheme string, tiny bool) {
writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateA)
writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateA)
- rootA, nodesA, _ := srcTrie.Commit(false)
+ rootA, nodesA := srcTrie.Commit(false)
if err := srcTrieDB.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA)); err != nil {
panic(err)
}
@@ -866,7 +866,7 @@ func testPivotMove(t *testing.T, scheme string, tiny bool) {
deleteFn([]byte{0x13, 0x44}, srcTrie, stateB)
writeFn([]byte{0x01, 0x24}, nil, srcTrie, stateB)
- rootB, nodesB, _ := srcTrie.Commit(false)
+ rootB, nodesB := srcTrie.Commit(false)
if err := srcTrieDB.Update(rootB, rootA, trienode.NewWithNodeSet(nodesB)); err != nil {
panic(err)
}
@@ -884,7 +884,7 @@ func testPivotMove(t *testing.T, scheme string, tiny bool) {
writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateC)
writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateC)
- rootC, nodesC, _ := srcTrie.Commit(false)
+ rootC, nodesC := srcTrie.Commit(false)
if err := srcTrieDB.Update(rootC, rootB, trienode.NewWithNodeSet(nodesC)); err != nil {
panic(err)
}
@@ -946,7 +946,7 @@ func testSyncAbort(t *testing.T, scheme string) {
}
writeFn(key, val, srcTrie, stateA)
- rootA, nodesA, _ := srcTrie.Commit(false)
+ rootA, nodesA := srcTrie.Commit(false)
if err := srcTrieDB.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA)); err != nil {
panic(err)
}
@@ -963,7 +963,7 @@ func testSyncAbort(t *testing.T, scheme string) {
srcTrie, _ = New(TrieID(rootA), srcTrieDB)
deleteFn(key, srcTrie, stateB)
- rootB, nodesB, _ := srcTrie.Commit(false)
+ rootB, nodesB := srcTrie.Commit(false)
if err := srcTrieDB.Update(rootB, rootA, trienode.NewWithNodeSet(nodesB)); err != nil {
panic(err)
}
@@ -990,7 +990,7 @@ func testSyncAbort(t *testing.T, scheme string) {
srcTrie, _ = New(TrieID(rootB), srcTrieDB)
writeFn(key, val, srcTrie, stateC)
- rootC, nodesC, _ := srcTrie.Commit(false)
+ rootC, nodesC := srcTrie.Commit(false)
if err := srcTrieDB.Update(rootC, rootB, trienode.NewWithNodeSet(nodesC)); err != nil {
panic(err)
}
diff --git a/trie/tracer_test.go b/trie/tracer_test.go
index 27e42d497a..852a706021 100644
--- a/trie/tracer_test.go
+++ b/trie/tracer_test.go
@@ -70,7 +70,7 @@ func testTrieTracer(t *testing.T, vals []struct{ k, v string }) {
}
insertSet := copySet(trie.tracer.inserts) // copy before commit
deleteSet := copySet(trie.tracer.deletes) // copy before commit
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
seen := setKeys(iterNodes(db, root))
@@ -137,7 +137,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
for _, val := range vals {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
trie, _ = New(TrieID(root), db)
@@ -152,7 +152,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
for _, val := range vals {
trie.MustUpdate([]byte(val.k), randBytes(32))
}
- root, nodes, _ = trie.Commit(false)
+ root, nodes = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
trie, _ = New(TrieID(root), db)
@@ -170,7 +170,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
keys = append(keys, string(key))
trie.MustUpdate(key, randBytes(32))
}
- root, nodes, _ = trie.Commit(false)
+ root, nodes = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
trie, _ = New(TrieID(root), db)
@@ -185,7 +185,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
for _, key := range keys {
trie.MustUpdate([]byte(key), nil)
}
- root, nodes, _ = trie.Commit(false)
+ root, nodes = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
trie, _ = New(TrieID(root), db)
@@ -200,7 +200,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
for _, val := range vals {
trie.MustUpdate([]byte(val.k), nil)
}
- root, nodes, _ = trie.Commit(false)
+ root, nodes = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
trie, _ = New(TrieID(root), db)
@@ -219,7 +219,7 @@ func TestAccessListLeak(t *testing.T) {
for _, val := range standard {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
var cases = []struct {
@@ -269,7 +269,7 @@ func TestTinyTree(t *testing.T) {
for _, val := range tiny {
trie.MustUpdate([]byte(val.k), randBytes(32))
}
- root, set, _ := trie.Commit(false)
+ root, set := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(set))
parent := root
@@ -278,7 +278,7 @@ func TestTinyTree(t *testing.T) {
for _, val := range tiny {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
- root, set, _ = trie.Commit(false)
+ root, set = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(set))
trie, _ = New(TrieID(root), db)
diff --git a/trie/trie.go b/trie/trie.go
index 12764e18d1..935f81fc7d 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -608,7 +608,7 @@ func (t *Trie) Hash() common.Hash {
// The returned nodeset can be nil if the trie is clean (nothing to commit).
// Once the trie is committed, it's not usable anymore. A new trie must
// be created with new root and updated trie database for following usage
-func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
+func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
defer t.tracer.reset()
defer func() {
t.committed = true
@@ -620,13 +620,13 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
if t.root == nil {
paths := t.tracer.deletedNodes()
if len(paths) == 0 {
- return types.EmptyRootHash, nil, nil // case (a)
+ return types.EmptyRootHash, nil // case (a)
}
nodes := trienode.NewNodeSet(t.owner)
for _, path := range paths {
nodes.AddNode([]byte(path), trienode.NewDeleted())
}
- return types.EmptyRootHash, nodes, nil // case (b)
+ return types.EmptyRootHash, nodes // case (b)
}
// Derive the hash for all dirty nodes first. We hold the assumption
// in the following procedure that all nodes are hashed.
@@ -638,14 +638,14 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
// Replace the root node with the origin hash in order to
// ensure all resolved nodes are dropped after the commit.
t.root = hashedNode
- return rootHash, nil, nil
+ return rootHash, nil
}
nodes := trienode.NewNodeSet(t.owner)
for _, path := range t.tracer.deletedNodes() {
nodes.AddNode([]byte(path), trienode.NewDeleted())
}
t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root)
- return rootHash, nodes, nil
+ return rootHash, nodes
}
// hashRoot calculates the root hash of the given trie
diff --git a/trie/trie_test.go b/trie/trie_test.go
index da60a7423d..f31fd393f5 100644
--- a/trie/trie_test.go
+++ b/trie/trie_test.go
@@ -95,7 +95,7 @@ func testMissingNode(t *testing.T, memonly bool, scheme string) {
trie := NewEmpty(triedb)
updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer")
updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
if !memonly {
@@ -184,7 +184,7 @@ func TestInsert(t *testing.T) {
updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab")
- root, _, _ = trie.Commit(false)
+ root, _ = trie.Commit(false)
if root != exp {
t.Errorf("case 2: exp %x got %x", exp, root)
}
@@ -209,7 +209,7 @@ func TestGet(t *testing.T) {
if i == 1 {
return
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
trie, _ = New(TrieID(root), db)
}
@@ -282,7 +282,7 @@ func TestReplication(t *testing.T) {
for _, val := range vals {
updateString(trie, val.k, val.v)
}
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
// create a new trie on top of the database and check that lookups work.
@@ -295,7 +295,7 @@ func TestReplication(t *testing.T) {
t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v)
}
}
- hash, nodes, _ := trie2.Commit(false)
+ hash, nodes := trie2.Commit(false)
if hash != root {
t.Errorf("root failure. expected %x got %x", root, hash)
}
@@ -531,7 +531,7 @@ func runRandTest(rt randTest) error {
case opHash:
tr.Hash()
case opCommit:
- root, nodes, _ := tr.Commit(true)
+ root, nodes := tr.Commit(true)
if nodes != nil {
triedb.Update(root, origin, trienode.NewWithNodeSet(nodes))
}
@@ -768,7 +768,7 @@ func TestCommitAfterHash(t *testing.T) {
if exp != root {
t.Errorf("got %x, exp %x", root, exp)
}
- root, _, _ = trie.Commit(false)
+ root, _ = trie.Commit(false)
if exp != root {
t.Errorf("got %x, exp %x", root, exp)
}
@@ -894,7 +894,7 @@ func TestCommitSequence(t *testing.T) {
trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
}
// Flush trie -> database
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root)
@@ -935,7 +935,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
trie.MustUpdate(key, val)
}
// Flush trie -> database
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root)
@@ -984,7 +984,7 @@ func TestCommitSequenceStackTrie(t *testing.T) {
stTrie.Update(key, val)
}
// Flush trie -> database
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
// Flush memdb -> disk (sponge)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Commit(root)
@@ -1042,7 +1042,7 @@ func TestCommitSequenceSmallRoot(t *testing.T) {
stTrie.Update(key, []byte{0x1})
// Flush trie -> database
- root, nodes, _ := trie.Commit(false)
+ root, nodes := trie.Commit(false)
// Flush memdb -> disk (sponge)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Commit(root)
diff --git a/trie/triestate/state.go b/trie/triestate/state.go
index 9db9211e8c..7508da5d60 100644
--- a/trie/triestate/state.go
+++ b/trie/triestate/state.go
@@ -42,7 +42,7 @@ type Trie interface {
// Commit the trie and returns a set of dirty nodes generated along with
// the new root hash.
- Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
+ Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
}
// TrieLoader wraps functions to load tries.
@@ -125,10 +125,7 @@ func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Addre
return nil, fmt.Errorf("failed to revert state, err: %w", err)
}
}
- root, result, err := tr.Commit(false)
- if err != nil {
- return nil, err
- }
+ root, result := tr.Commit(false)
if root != prevRoot {
return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root)
}
@@ -180,10 +177,7 @@ func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error {
return err
}
}
- root, result, err := st.Commit(false)
- if err != nil {
- return err
- }
+ root, result := st.Commit(false)
if root != prev.Root {
return errors.New("failed to reset storage trie")
}
@@ -234,10 +228,7 @@ func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error {
return err
}
}
- root, result, err := st.Commit(false)
- if err != nil {
- return err
- }
+ root, result := st.Commit(false)
if root != types.EmptyRootHash {
return errors.New("failed to clear storage trie")
}
diff --git a/trie/verkle.go b/trie/verkle.go
index bb0c54857f..1ea23186f9 100644
--- a/trie/verkle.go
+++ b/trie/verkle.go
@@ -217,22 +217,21 @@ func (t *VerkleTrie) Hash() common.Hash {
}
// Commit writes all nodes to the tree's memory database.
-func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
- root, ok := t.root.(*verkle.InternalNode)
- if !ok {
- return common.Hash{}, nil, errors.New("unexpected root node type")
- }
+func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
+ root := t.root.(*verkle.InternalNode)
nodes, err := root.BatchSerialize()
if err != nil {
- return common.Hash{}, nil, fmt.Errorf("serializing tree nodes: %s", err)
+ // Error return from this function indicates error in the code logic
+ // of BatchSerialize, and we fail catastrophically if this is the case.
+ panic(fmt.Errorf("BatchSerialize failed: %v", err))
}
nodeset := trienode.NewNodeSet(common.Hash{})
for _, node := range nodes {
- // hash parameter is not used in pathdb
+ // Hash parameter is not used in pathdb
nodeset.AddNode(node.Path, trienode.New(common.Hash{}, node.SerializedBytes))
}
// Serialize root commitment form
- return t.Hash(), nodeset, nil
+ return t.Hash(), nodeset
}
// NodeIterator implements state.Trie, returning an iterator that returns
diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go
index 7b24082315..04c8af415f 100644
--- a/triedb/pathdb/database_test.go
+++ b/triedb/pathdb/database_test.go
@@ -46,11 +46,7 @@ func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[comm
h.Update(key.Bytes(), val)
}
}
- root, nodes, err := h.Commit(false)
- if err != nil {
- panic(fmt.Errorf("failed to commit hasher, err: %w", err))
- }
- return root, nodes
+ return h.Commit(false)
}
func generateAccount(storageRoot common.Hash) types.StateAccount {
diff --git a/triedb/pathdb/testutils.go b/triedb/pathdb/testutils.go
index 0c99565b8e..af832bc59c 100644
--- a/triedb/pathdb/testutils.go
+++ b/triedb/pathdb/testutils.go
@@ -80,7 +80,7 @@ func (h *testHasher) Delete(key []byte) error {
// Commit computes the new hash of the states and returns the set with all
// state changes.
-func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
+func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
var (
nodes = make(map[common.Hash][]byte)
set = trienode.NewNodeSet(h.owner)
@@ -111,7 +111,7 @@ func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, e
if root == types.EmptyRootHash && h.root != types.EmptyRootHash {
set.AddNode(nil, trienode.NewDeleted())
}
- return root, set, nil
+ return root, set
}
// hash performs the hash computation upon the provided states.