mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
Merge branch 'ethereum:master' into gethintegration
This commit is contained in:
commit
4a5771aa2e
23 changed files with 791 additions and 204 deletions
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
|
@ -249,7 +250,11 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Extract the Ethereum signature and do a sanity validation
|
// Extract the Ethereum signature and do a sanity validation
|
||||||
if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 {
|
if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 {
|
||||||
|
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||||
|
} else if response.GetSignatureV() == 0 && int(chainID.Int64()) <= (math.MaxUint32-36)/2 {
|
||||||
|
// for chainId >= (MaxUint32-36)/2, Trezor returns signature bit only
|
||||||
|
// https://github.com/trezor/trezor-mcu/pull/399
|
||||||
return common.Address{}, nil, errors.New("reply lacks signature")
|
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||||
}
|
}
|
||||||
signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV()))
|
signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV()))
|
||||||
|
|
@ -261,7 +266,11 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
||||||
} else {
|
} else {
|
||||||
// Trezor backend does not support typed transactions yet.
|
// Trezor backend does not support typed transactions yet.
|
||||||
signer = types.NewEIP155Signer(chainID)
|
signer = types.NewEIP155Signer(chainID)
|
||||||
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
// if chainId is above (MaxUint32 - 36) / 2 then the final v values is returned
|
||||||
|
// directly. Otherwise, the returned value is 35 + chainid * 2.
|
||||||
|
if signature[64] > 1 && int(chainID.Int64()) <= (math.MaxUint32-36)/2 {
|
||||||
|
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the final signature into the transaction and sanity check the sender
|
// Inject the final signature into the transaction and sanity check the sender
|
||||||
|
|
|
||||||
|
|
@ -5,54 +5,54 @@
|
||||||
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/
|
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/
|
||||||
b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz
|
b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz
|
||||||
|
|
||||||
# version:golang 1.24.1
|
# version:golang 1.24.2
|
||||||
# https://go.dev/dl/
|
# https://go.dev/dl/
|
||||||
8244ebf46c65607db10222b5806aeb31c1fcf8979c1b6b12f60c677e9a3c0656 go1.24.1.src.tar.gz
|
9dc77ffadc16d837a1bf32d99c624cb4df0647cee7b119edd9e7b1bcc05f2e00 go1.24.2.src.tar.gz
|
||||||
8d627dc163a4bffa2b1887112ad6194af175dce108d606ed1714a089fb806033 go1.24.1.aix-ppc64.tar.gz
|
427b373540d8fd51dbcc46bdecd340af109cd41514443c000d3dcde72b2c65a3 go1.24.2.aix-ppc64.tar.gz
|
||||||
addbfce2056744962e2d7436313ab93486660cf7a2e066d171b9d6f2da7c7abe go1.24.1.darwin-amd64.tar.gz
|
238d9c065d09ff6af229d2e3b8b5e85e688318d69f4006fb85a96e41c216ea83 go1.24.2.darwin-amd64.tar.gz
|
||||||
58d529334561cff11087cd4ab18fe0b46d8d5aad88f45c02b9645f847e014512 go1.24.1.darwin-amd64.pkg
|
535ed9ff283fee39575a7fb9b6d8b1901b6dc640d06dc71fd7d3faeefdaf8030 go1.24.2.darwin-amd64.pkg
|
||||||
295581b5619acc92f5106e5bcb05c51869337eb19742fdfa6c8346c18e78ff88 go1.24.1.darwin-arm64.tar.gz
|
b70f8b3c5b4ccb0ad4ffa5ee91cd38075df20fdbd953a1daedd47f50fbcff47a go1.24.2.darwin-arm64.tar.gz
|
||||||
78b0fc8ddc344eb499f1a952c687cb84cbd28ba2b739cfa0d4eb042f07e44e82 go1.24.1.darwin-arm64.pkg
|
4732f607a47ce4d898c0af01ff68f07e0820a6b50603aef5d5c777d1102505e2 go1.24.2.darwin-arm64.pkg
|
||||||
e70053f56f7eb93806d80cbd5726f78509a0a467602f7bea0e2c4ee8ed7c3968 go1.24.1.dragonfly-amd64.tar.gz
|
c17686b5fd61a663fbfafccfa177961be59386cf294e935ce35866b9dcb8e78a go1.24.2.dragonfly-amd64.tar.gz
|
||||||
3595e2674ed8fe72e604ca59c964d3e5277aafb08475c2b1aaca2d2fd69c24fc go1.24.1.freebsd-386.tar.gz
|
026f1dd906189acff714c7625686bbc4ed91042618ba010d45b671461acc9e63 go1.24.2.freebsd-386.tar.gz
|
||||||
47d7de8bb64d5c3ee7b6723aa62d5ecb11e3568ef2249bbe1d4bbd432d37c00c go1.24.1.freebsd-amd64.tar.gz
|
49399ba759b570a8f87d12179133403da6c2dd296d63a8830dee309161b9c40c go1.24.2.freebsd-amd64.tar.gz
|
||||||
04eec3bcfaa14c1370cdf98e8307fac7e4853496c3045afb9c3124a29cbca205 go1.24.1.freebsd-arm.tar.gz
|
1f48f47183794d97c29736004247ab541177cf984ac6322c78bc43828daa1172 go1.24.2.freebsd-arm.tar.gz
|
||||||
51aa70146e40cfdc20927424083dc86e6223f85dc08089913a1651973b55665b go1.24.1.freebsd-arm64.tar.gz
|
ef856428b60a8c0bd9a2cba596e83024be6f1c2d5574e89cb1ff2262b08df8b9 go1.24.2.freebsd-arm64.tar.gz
|
||||||
3c131d8e3fc285a1340f87813153e24226d3ddbd6e54f3facbd6e4c46a84655e go1.24.1.freebsd-riscv64.tar.gz
|
ec2088823e16df00600a6d0f72e9a7dc6d2f80c9c140c2043c0cf20e1404d1a9 go1.24.2.freebsd-riscv64.tar.gz
|
||||||
201d09da737ba39d5367f87d4e8b31edaeeb3dc9b9c407cb8cfb40f90c5a727a go1.24.1.illumos-amd64.tar.gz
|
e030e7cedbb8688f1d75cb80f3de6ee2e6617a67d34051e794e5992b53462147 go1.24.2.illumos-amd64.tar.gz
|
||||||
8c530ecedbc17e42ce10177bea07ccc96a3e77c792ea1ea72173a9675d16ffa5 go1.24.1.linux-386.tar.gz
|
4c382776d52313266f3026236297a224a6688751256a2dffa3f524d8d6f6c0ba go1.24.2.linux-386.tar.gz
|
||||||
cb2396bae64183cdccf81a9a6df0aea3bce9511fc21469fb89a0c00470088073 go1.24.1.linux-amd64.tar.gz
|
68097bd680839cbc9d464a0edce4f7c333975e27a90246890e9f1078c7e702ad go1.24.2.linux-amd64.tar.gz
|
||||||
8df5750ffc0281017fb6070fba450f5d22b600a02081dceef47966ffaf36a3af go1.24.1.linux-arm64.tar.gz
|
756274ea4b68fa5535eb9fe2559889287d725a8da63c6aae4d5f23778c229f4b go1.24.2.linux-arm64.tar.gz
|
||||||
6d95f8d7884bfe2364644c837f080f2b585903d0b771eb5b06044e226a4f120a go1.24.1.linux-armv6l.tar.gz
|
438d5d3d7dcb239b58d893a715672eabe670b9730b1fd1c8fc858a46722a598a go1.24.2.linux-armv6l.tar.gz
|
||||||
19304a4a56e46d04604547d2d83235dc4f9b192c79832560ce337d26cc7b835a go1.24.1.linux-loong64.tar.gz
|
6aefd3bf59c3c5592eda4fb287322207f119c2210f3795afa9be48d3ccb73e1b go1.24.2.linux-loong64.tar.gz
|
||||||
6347be77fa5359c12a5308c8ab87147c1fc4717b0c216493d1706c3b9fcde22d go1.24.1.linux-mips.tar.gz
|
93e49bb4692783b0e9a2deab9558c6e8d2867f35592aeff285adda60924167f3 go1.24.2.linux-mips.tar.gz
|
||||||
1647df415f7030b82d4105670192aa7e8910e18563bb0d505192d72800cc2d21 go1.24.1.linux-mips64.tar.gz
|
6e86e703675016f3faf6604b8f68f20dc1bba75849136e6dd4f43f69c8a4a9d9 go1.24.2.linux-mips64.tar.gz
|
||||||
762da594e4ec0f9cf6defae6ef971f5f7901203ee6a2d979e317adec96657317 go1.24.1.linux-mips64le.tar.gz
|
f233d237538ca1559a7d7cf519a29f0147923a951377bc4e467af4c059e68851 go1.24.2.linux-mips64le.tar.gz
|
||||||
9d8133c7b23a557399fab870b5cf464079c2b623a43b214a7567cf11c254a444 go1.24.1.linux-mipsle.tar.gz
|
545e1b9a7939f923fd53bde98334b987ef42eb353ee3e0bfede8aa06079d6b24 go1.24.2.linux-mipsle.tar.gz
|
||||||
132f10999abbaccbada47fa85462db30c423955913b14d6c692de25f4636c766 go1.24.1.linux-ppc64.tar.gz
|
6eab31481f2f46187bc1b6c887662eef06fc9d7271a8390854072cdb387c8d74 go1.24.2.linux-ppc64.tar.gz
|
||||||
0fb522efcefabae6e37e69bdc444094e75bfe824ea6d4cc3cbc70c7ae1b16858 go1.24.1.linux-ppc64le.tar.gz
|
5fff857791d541c71d8ea0171c73f6f99770d15ff7e2ad979104856d01f36563 go1.24.2.linux-ppc64le.tar.gz
|
||||||
eaef4323d5467ff97fb1979c8491764060dade19f02f3275a9313f9a0da3b9c0 go1.24.1.linux-riscv64.tar.gz
|
91bda1558fcbd1c92769ad86c8f5cf796f8c67b0d9d9c19f76eecfc75ce71527 go1.24.2.linux-riscv64.tar.gz
|
||||||
6c05e14d8f11094cb56a1c50f390b6b658bed8a7cbd8d1a57e926581b7eabfce go1.24.1.linux-s390x.tar.gz
|
1cb3448166d6abb515a85a3ee5afbdf932081fb58ad7143a8fb666fbc06146d9 go1.24.2.linux-s390x.tar.gz
|
||||||
5dbb287d343ea00d58a70b11629f32ee716dc50a6875c459ea2018df0f294cd8 go1.24.1.netbsd-386.tar.gz
|
a9a2c0db2e826f20f00b02bee01dfdaeb49591c2f6ffacb78dc64a950894f7ff go1.24.2.netbsd-386.tar.gz
|
||||||
617aa3faee50ce84c343db0888e9a210c310a7203666b4ed620f31030c9fb32f go1.24.1.netbsd-amd64.tar.gz
|
cd1a35b76ed9c7b6c0c1616741bd319699a77867ade0be9924f32496c0a87a3f go1.24.2.netbsd-amd64.tar.gz
|
||||||
59a928b7080c4a6ac985946274b7c65ce1cecc0b468ecd992d17b7c12fab9296 go1.24.1.netbsd-arm.tar.gz
|
8c666388d066e479155cc5116950eeb435df28087ef277c18f1dc7479f836e60 go1.24.2.netbsd-arm.tar.gz
|
||||||
28daa8d0feb4aef2af60cefa3305bb9314de7e8a05cbca41ac548964cdfe89b7 go1.24.1.netbsd-arm64.tar.gz
|
5d42f0be04f58da5be788a1e260f8747c316b8ce182bf0b273c2e4c691feaa1a go1.24.2.netbsd-arm64.tar.gz
|
||||||
b7382b2f5d99813aeac14db482faa3bfbd47a68880b607fa2a7e669e26bab9cd go1.24.1.openbsd-386.tar.gz
|
688effa23ea3973cc8b0fdf4246712cbeef55ff20c45f3a9e28b0c2db04246cf go1.24.2.openbsd-386.tar.gz
|
||||||
2513b6537c45deead5e641c7ce7502913e7d5e6f0b21c52542fb11f81578690f go1.24.1.openbsd-amd64.tar.gz
|
e5daf95f1048d8026b1366450a3f8044d668b0639db6422ad9a83755c6745cf7 go1.24.2.openbsd-amd64.tar.gz
|
||||||
853c1917d4fc7b144c55a02842aa48542d5cc798dde8db96dc0fdbc263200e04 go1.24.1.openbsd-arm.tar.gz
|
aeadaf74bd544d1a12ba9b14c0e7cdb1964de3ba9a52acb4619e91dbae7def7b go1.24.2.openbsd-arm.tar.gz
|
||||||
6bc207b91e6f6ae3347fb54616a8fb2f5c11983713846a4cef111ff3f4f94d14 go1.24.1.openbsd-arm64.tar.gz
|
9e222d9adb0ce836a5b3c8d5aadbd167c8869c030b113f4a81aa88e9a200f279 go1.24.2.openbsd-arm64.tar.gz
|
||||||
4279260e2f2b94ee94e81470d13db7367f4393b061fee60985528fa0fa430df4 go1.24.1.openbsd-ppc64.tar.gz
|
192fffa34536adc3cd1bb7c1ee785b8bc156ae7afd10bbf5db99ec8f2e93066e go1.24.2.openbsd-ppc64.tar.gz
|
||||||
6fc4023a0a339ee0778522364a127d94c78e62122288d47d820dba703f81dc07 go1.24.1.openbsd-riscv64.tar.gz
|
a23e90b451a390549042c2a7efbec6f29ed98b2d5618c8d2a35704e21be96e09 go1.24.2.openbsd-riscv64.tar.gz
|
||||||
b5eb9fafd77146e7e1f748acfd95559580ecc8d2f15abf432a20f58c929c7cd2 go1.24.1.plan9-386.tar.gz
|
5cdcafe455d859b02779611a5a1e1d63e498b922e05818fb3debe410a5959e9e go1.24.2.plan9-386.tar.gz
|
||||||
24dcad6361b141fc8cced15b092351e12a99d2e58d7013204a3013c50daf9fdd go1.24.1.plan9-amd64.tar.gz
|
81351659804fa505c1b3ec6fdf9599f7f88df08614307eeb96071bf5e2e74beb go1.24.2.plan9-amd64.tar.gz
|
||||||
a026ac3b55aa1e6fdc2aaab30207a117eafbe965ed81d3aa0676409f280ddc37 go1.24.1.plan9-arm.tar.gz
|
6e337d5def14ed0123423c1c32e2e6d8b19161e5d5ffaa7356dad48ee0fd80b4 go1.24.2.plan9-arm.tar.gz
|
||||||
8e4f6a77388dc6e5aa481efd5abdb3b9f5c9463bb82f4db074494e04e5c84992 go1.24.1.solaris-amd64.tar.gz
|
07e6926ebc476c044d7d5b17706abfc52be52bccc2073d1734174efe63c6b35e go1.24.2.solaris-amd64.tar.gz
|
||||||
b799f4ab264eef12a014c759383ed934056608c483e0f73e34ea6caf9f1df5f9 go1.24.1.windows-386.zip
|
13d86cb818bba331da75fcd18246ab31a1067b44fb4a243b6dfd93097eda7f37 go1.24.2.windows-386.zip
|
||||||
db128981033ac82a64688a123f631e61297b6b8f52ca913145e57caa8ce94cc3 go1.24.1.windows-386.msi
|
8a702d9f7104a15bd935f4191c58c24c0b6389e066b9d5661b93915114a2bef0 go1.24.2.windows-386.msi
|
||||||
95666b551453209a2b8869d29d177285ff9573af10f085d961d7ae5440f645ce go1.24.1.windows-amd64.zip
|
29c553aabee0743e2ffa3e9fa0cda00ef3b3cc4ff0bc92007f31f80fd69892e1 go1.24.2.windows-amd64.zip
|
||||||
5968e7adcf26e68a54f1cd41ad561275a670a8e2ca5263bc375b524638557dfb go1.24.1.windows-amd64.msi
|
acefb191e72fea0bdb1a3f5f8f6f5ab18b42b3bbce0c7183f189f25953aff275 go1.24.2.windows-amd64.msi
|
||||||
e28c4e6d0b913955765b46157ab88ae59bb636acaa12d7bec959aa6900f1cebd go1.24.1.windows-arm64.zip
|
ab267f7f9a3366d48d7664be9e627ce3e63273231430cce5f7783fb910f14148 go1.24.2.windows-arm64.zip
|
||||||
6d352c1f154a102a5b90c480cc64bab205ccf2681e34e78a3a4d3f1ddfbc81e4 go1.24.1.windows-arm64.msi
|
d187bfe539356c39573d2f46766d1d08122b4f33da00fd14d12485fa9e241ff5 go1.24.2.windows-arm64.msi
|
||||||
|
|
||||||
# version:golangci 2.0.2
|
# version:golangci 2.0.2
|
||||||
# https://github.com/golangci/golangci-lint/releases/
|
# https://github.com/golangci/golangci-lint/releases/
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,7 @@ func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transac
|
||||||
// and leading up to the first no-change.
|
// and leading up to the first no-change.
|
||||||
type BlobPool struct {
|
type BlobPool struct {
|
||||||
config Config // Pool configuration
|
config Config // Pool configuration
|
||||||
reserver *txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
||||||
hasPendingAuth func(common.Address) bool // Determine whether the specified address has a pending 7702-auth
|
hasPendingAuth func(common.Address) bool // Determine whether the specified address has a pending 7702-auth
|
||||||
|
|
||||||
store billy.Database // Persistent data store for the tx metadata and blobs
|
store billy.Database // Persistent data store for the tx metadata and blobs
|
||||||
|
|
@ -355,7 +355,7 @@ func (p *BlobPool) Filter(tx *types.Transaction) bool {
|
||||||
// Init sets the gas price needed to keep a transaction in the pool and the chain
|
// Init sets the gas price needed to keep a transaction in the pool and the chain
|
||||||
// head to allow balance / nonce checks. The transaction journal will be loaded
|
// head to allow balance / nonce checks. The transaction journal will be loaded
|
||||||
// from disk and filtered based on the provided starting settings.
|
// from disk and filtered based on the provided starting settings.
|
||||||
func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver *txpool.Reserver) error {
|
func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reserver) error {
|
||||||
p.reserver = reserver
|
p.reserver = reserver
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -1349,6 +1349,9 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.
|
||||||
|
|
||||||
// Add inserts a set of blob transactions into the pool if they pass validation (both
|
// Add inserts a set of blob transactions into the pool if they pass validation (both
|
||||||
// consensus validity and pool restrictions).
|
// consensus validity and pool restrictions).
|
||||||
|
//
|
||||||
|
// Note, if sync is set the method will block until all internal maintenance
|
||||||
|
// related to the add is finished. Only use this during tests for determinism.
|
||||||
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
var (
|
var (
|
||||||
adds = make([]*types.Transaction, 0, len(txs))
|
adds = make([]*types.Transaction, 0, len(txs))
|
||||||
|
|
@ -1792,6 +1795,9 @@ func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus {
|
||||||
|
|
||||||
// Clear implements txpool.SubPool, removing all tracked transactions
|
// Clear implements txpool.SubPool, removing all tracked transactions
|
||||||
// from the blob pool and persistent store.
|
// from the blob pool and persistent store.
|
||||||
|
//
|
||||||
|
// Note, do not use this in production / live code. In live code, the pool is
|
||||||
|
// meant to reset on a separate thread to avoid DoS vectors.
|
||||||
func (p *BlobPool) Clear() {
|
func (p *BlobPool) Clear() {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -167,6 +168,44 @@ func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
|
||||||
return bc.statedb, nil
|
return bc.statedb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reserver is a utility struct to sanity check that accounts are
|
||||||
|
// properly reserved by the blobpool (no duplicate reserves or unreserves).
|
||||||
|
type reserver struct {
|
||||||
|
accounts map[common.Address]struct{}
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newReserver() txpool.Reserver {
|
||||||
|
return &reserver{accounts: make(map[common.Address]struct{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reserver) Hold(addr common.Address) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
if _, exists := r.accounts[addr]; exists {
|
||||||
|
panic("already reserved")
|
||||||
|
}
|
||||||
|
r.accounts[addr] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reserver) Release(addr common.Address) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
if _, exists := r.accounts[addr]; !exists {
|
||||||
|
panic("not reserved")
|
||||||
|
}
|
||||||
|
delete(r.accounts, addr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reserver) Has(address common.Address) bool {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
_, exists := r.accounts[address]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
// makeTx is a utility method to construct a random blob transaction and sign it
|
// makeTx is a utility method to construct a random blob transaction and sign it
|
||||||
// with a valid key, only setting the interesting fields from the perspective of
|
// with a valid key, only setting the interesting fields from the perspective of
|
||||||
// the blob pool.
|
// the blob pool.
|
||||||
|
|
@ -405,10 +444,6 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReserver() *txpool.Reserver {
|
|
||||||
return txpool.NewReservationTracker().NewHandle(42)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that transactions can be loaded from disk on startup and that they are
|
// Tests that transactions can be loaded from disk on startup and that they are
|
||||||
// correctly discarded if invalid.
|
// correctly discarded if invalid.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ type LegacyPool struct {
|
||||||
currentHead atomic.Pointer[types.Header] // Current head of the blockchain
|
currentHead atomic.Pointer[types.Header] // Current head of the blockchain
|
||||||
currentState *state.StateDB // Current state in the blockchain head
|
currentState *state.StateDB // Current state in the blockchain head
|
||||||
pendingNonces *noncer // Pending state tracking virtual nonces
|
pendingNonces *noncer // Pending state tracking virtual nonces
|
||||||
reserver *txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
||||||
|
|
||||||
pending map[common.Address]*list // All currently processable transactions
|
pending map[common.Address]*list // All currently processable transactions
|
||||||
queue map[common.Address]*list // Queued but non-processable transactions
|
queue map[common.Address]*list // Queued but non-processable transactions
|
||||||
|
|
@ -302,7 +302,7 @@ func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
|
||||||
// Init sets the gas price needed to keep a transaction in the pool and the chain
|
// Init sets the gas price needed to keep a transaction in the pool and the chain
|
||||||
// head to allow balance / nonce checks. The internal
|
// head to allow balance / nonce checks. The internal
|
||||||
// goroutines will be spun up and the pool deemed operational afterwards.
|
// goroutines will be spun up and the pool deemed operational afterwards.
|
||||||
func (pool *LegacyPool) Init(gasTip uint64, head *types.Header, reserver *txpool.Reserver) error {
|
func (pool *LegacyPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reserver) error {
|
||||||
// Set the address reserver to request exclusive access to pooled accounts
|
// Set the address reserver to request exclusive access to pooled accounts
|
||||||
pool.reserver = reserver
|
pool.reserver = reserver
|
||||||
|
|
||||||
|
|
@ -640,11 +640,18 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
|
||||||
if err := pool.checkDelegationLimit(tx); err != nil {
|
if err := pool.checkDelegationLimit(tx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Authorities must not conflict with any pending or queued transactions,
|
// For symmetry, allow at most one in-flight tx for any authority with a
|
||||||
// nor with addresses that have already been reserved.
|
// pending transaction.
|
||||||
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
|
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
|
||||||
for _, auth := range auths {
|
for _, auth := range auths {
|
||||||
if pool.pending[auth] != nil || pool.queue[auth] != nil {
|
var count int
|
||||||
|
if pending := pool.pending[auth]; pending != nil {
|
||||||
|
count += pending.Len()
|
||||||
|
}
|
||||||
|
if queue := pool.queue[auth]; queue != nil {
|
||||||
|
count += queue.Len()
|
||||||
|
}
|
||||||
|
if count > 1 {
|
||||||
return ErrAuthorityReserved
|
return ErrAuthorityReserved
|
||||||
}
|
}
|
||||||
// Because there is no exclusive lock held between different subpools
|
// Because there is no exclusive lock held between different subpools
|
||||||
|
|
@ -927,8 +934,8 @@ func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error {
|
||||||
|
|
||||||
// Add enqueues a batch of transactions into the pool if they are valid.
|
// Add enqueues a batch of transactions into the pool if they are valid.
|
||||||
//
|
//
|
||||||
// If sync is set, the method will block until all internal maintenance related
|
// Note, if sync is set the method will block until all internal maintenance
|
||||||
// to the add is finished. Only use this during tests for determinism!
|
// related to the add is finished. Only use this during tests for determinism.
|
||||||
func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
// Filter out known ones without obtaining the pool lock or recovering signatures
|
// Filter out known ones without obtaining the pool lock or recovering signatures
|
||||||
var (
|
var (
|
||||||
|
|
@ -1886,6 +1893,9 @@ func numSlots(tx *types.Transaction) int {
|
||||||
|
|
||||||
// Clear implements txpool.SubPool, removing all tracked txs from the pool
|
// Clear implements txpool.SubPool, removing all tracked txs from the pool
|
||||||
// and rotating the journal.
|
// and rotating the journal.
|
||||||
|
//
|
||||||
|
// Note, do not use this in production / live code. In live code, the pool is
|
||||||
|
// meant to reset on a separate thread to avoid DoS vectors.
|
||||||
func (pool *LegacyPool) Clear() {
|
func (pool *LegacyPool) Clear() {
|
||||||
pool.mu.Lock()
|
pool.mu.Lock()
|
||||||
defer pool.mu.Unlock()
|
defer pool.mu.Unlock()
|
||||||
|
|
@ -1904,9 +1914,14 @@ func (pool *LegacyPool) Clear() {
|
||||||
// The transaction addition may attempt to reserve the sender addr which
|
// The transaction addition may attempt to reserve the sender addr which
|
||||||
// can't happen until Clear releases the reservation lock. Clear cannot
|
// can't happen until Clear releases the reservation lock. Clear cannot
|
||||||
// acquire the subpool lock until the transaction addition is completed.
|
// acquire the subpool lock until the transaction addition is completed.
|
||||||
for _, tx := range pool.all.txs {
|
|
||||||
senderAddr, _ := types.Sender(pool.signer, tx)
|
for addr := range pool.pending {
|
||||||
pool.reserver.Release(senderAddr)
|
if _, ok := pool.queue[addr]; !ok {
|
||||||
|
pool.reserver.Release(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for addr := range pool.queue {
|
||||||
|
pool.reserver.Release(addr)
|
||||||
}
|
}
|
||||||
pool.all = newLookup()
|
pool.all = newLookup()
|
||||||
pool.priced = newPricedList(pool.all)
|
pool.priced = newPricedList(pool.all)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -171,8 +172,39 @@ func setupPool() (*LegacyPool, *ecdsa.PrivateKey) {
|
||||||
return setupPoolWithConfig(params.TestChainConfig)
|
return setupPoolWithConfig(params.TestChainConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReserver() *txpool.Reserver {
|
// reserver is a utility struct to sanity check that accounts are
|
||||||
return txpool.NewReservationTracker().NewHandle(42)
|
// properly reserved by the blobpool (no duplicate reserves or unreserves).
|
||||||
|
type reserver struct {
|
||||||
|
accounts map[common.Address]struct{}
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newReserver() txpool.Reserver {
|
||||||
|
return &reserver{accounts: make(map[common.Address]struct{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reserver) Hold(addr common.Address) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
if _, exists := r.accounts[addr]; exists {
|
||||||
|
panic("already reserved")
|
||||||
|
}
|
||||||
|
r.accounts[addr] = struct{}{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reserver) Release(addr common.Address) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
if _, exists := r.accounts[addr]; !exists {
|
||||||
|
panic("not reserved")
|
||||||
|
}
|
||||||
|
delete(r.accounts, addr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reserver) Has(address common.Address) bool {
|
||||||
|
return false // reserver only supports a single pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) {
|
func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) {
|
||||||
|
|
@ -2232,9 +2264,8 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// Check that only one in-flight transaction is allowed for accounts
|
// Check that only one in-flight transaction is allowed for accounts
|
||||||
// with delegation set. Also verify the accepted transaction can be
|
// with delegation set.
|
||||||
// replaced by fee.
|
name: "accept-one-inflight-tx-of-delegated-account",
|
||||||
name: "only-one-in-flight",
|
|
||||||
pending: 1,
|
pending: 1,
|
||||||
run: func(name string) {
|
run: func(name string) {
|
||||||
aa := common.Address{0xaa, 0xaa}
|
aa := common.Address{0xaa, 0xaa}
|
||||||
|
|
@ -2249,6 +2280,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
|
||||||
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
// Second and further transactions shall be rejected
|
||||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
||||||
}
|
}
|
||||||
|
|
@ -2260,6 +2292,70 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil {
|
||||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the delegation, avoid leaking state into the other tests
|
||||||
|
statedb.SetCode(addrA, nil)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This test is analogous to the previous one, but the delegation is pending
|
||||||
|
// instead of set.
|
||||||
|
name: "allow-one-tx-from-pooled-delegation",
|
||||||
|
pending: 2,
|
||||||
|
run: func(name string) {
|
||||||
|
// Create a pending delegation request from B.
|
||||||
|
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
|
}
|
||||||
|
// First transaction from B is accepted.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||||
|
}
|
||||||
|
// Second transaction fails due to limit.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
||||||
|
}
|
||||||
|
// Replace by fee for first transaction from B works.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(2), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This is the symmetric case of the previous one, where the delegation request
|
||||||
|
// is received after the transaction. The resulting state shall be the same.
|
||||||
|
name: "accept-authorization-from-sender-of-one-inflight-tx",
|
||||||
|
pending: 2,
|
||||||
|
run: func(name string) {
|
||||||
|
// The first in-flight transaction is accepted.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add with pending delegation: %v", name, err)
|
||||||
|
}
|
||||||
|
// Delegation is accepted.
|
||||||
|
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||||
|
}
|
||||||
|
// The second in-flight transaction is rejected.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reject-authorization-from-sender-with-more-than-one-inflight-tx",
|
||||||
|
pending: 2,
|
||||||
|
run: func(name string) {
|
||||||
|
// Submit two transactions.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add with pending delegation: %v", name, err)
|
||||||
|
}
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add with pending delegation: %v", name, err)
|
||||||
|
}
|
||||||
|
// Delegation rejected since two txs are already in-flight.
|
||||||
|
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyB}})); !errors.Is(err, ErrAuthorityReserved) {
|
||||||
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAuthorityReserved, err)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -2267,7 +2363,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
pending: 2,
|
pending: 2,
|
||||||
run: func(name string) {
|
run: func(name string) {
|
||||||
// Send two transactions where the first has no conflicting delegations and
|
// Send two transactions where the first has no conflicting delegations and
|
||||||
// the second should be allowed despite conflicting with the authorities in 1).
|
// the second should be allowed despite conflicting with the authorities in the first.
|
||||||
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})); err != nil {
|
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})); err != nil {
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
@ -2276,28 +2372,10 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "allow-one-tx-from-pooled-delegation",
|
|
||||||
pending: 2,
|
|
||||||
run: func(name string) {
|
|
||||||
// Verify C cannot originate another transaction when it has a pooled delegation.
|
|
||||||
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
|
||||||
}
|
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyC)); err != nil {
|
|
||||||
t.Fatalf("%s: failed to add with pending delegatio: %v", name, err)
|
|
||||||
}
|
|
||||||
// Also check gapped transaction is rejected.
|
|
||||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
|
||||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "replace-by-fee-setcode-tx",
|
name: "replace-by-fee-setcode-tx",
|
||||||
pending: 1,
|
pending: 1,
|
||||||
run: func(name string) {
|
run: func(name string) {
|
||||||
// 4. Fee bump the setcode tx send.
|
|
||||||
if err := pool.addRemoteSync(setCodeTx(0, keyB, []unsignedAuth{{1, keyC}})); err != nil {
|
if err := pool.addRemoteSync(setCodeTx(0, keyB, []unsignedAuth{{1, keyC}})); err != nil {
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
@ -2307,44 +2385,85 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "allow-tx-from-replaced-authority",
|
name: "allow-more-than-one-tx-from-replaced-authority",
|
||||||
pending: 2,
|
pending: 3,
|
||||||
run: func(name string) {
|
run: func(name string) {
|
||||||
// Fee bump with a different auth list. Make sure that unlocks the authorities.
|
// Send transaction from A with B as an authority.
|
||||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
// Replace transaction with another having C as an authority.
|
||||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(3000), uint256.NewInt(300), keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(3000), uint256.NewInt(300), keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
// Now send a regular tx from B.
|
// B should not be considred as having an in-flight delegation, so
|
||||||
|
// should allow more than one pooled transaction.
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyB)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyB)); err != nil {
|
||||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(10), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// This test is analogous to the previous one, but the the replaced
|
||||||
|
// transaction is self-sponsored.
|
||||||
name: "allow-tx-from-replaced-self-sponsor-authority",
|
name: "allow-tx-from-replaced-self-sponsor-authority",
|
||||||
pending: 2,
|
pending: 3,
|
||||||
run: func(name string) {
|
run: func(name string) {
|
||||||
//
|
// Send transaction from A with A as an authority.
|
||||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyA}})); err != nil {
|
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyA}})); err != nil {
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
// Replace transaction with a transaction with B as an authority.
|
||||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(30), uint256.NewInt(30), keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(30), uint256.NewInt(30), keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
// Now send a regular tx from keyA.
|
// The one in-flight transaction limit from A no longer applies, so we
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyA)); err != nil {
|
// can stack a second transaction for the account.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyA)); err != nil {
|
||||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
// Make sure we can still send from keyB.
|
// B should still be able to send transactions.
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyB)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyB)); err != nil {
|
||||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
|
// However B still has the limitation to one in-flight transaction.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: "replacements-respect-inflight-tx-count",
|
||||||
|
pending: 2,
|
||||||
|
run: func(name string) {
|
||||||
|
// Send transaction from A with B as an authority.
|
||||||
|
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
|
}
|
||||||
|
// Send two transactions from B. Only the first should be accepted due
|
||||||
|
// to in-flight limit.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||||
|
}
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
||||||
|
}
|
||||||
|
// Replace the in-flight transaction from B.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(30), keyB)); err != nil {
|
||||||
|
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||||
|
}
|
||||||
|
// Ensure the in-flight limit for B is still in place.
|
||||||
|
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyB)); !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||||
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrInflightTxLimitReached, err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Since multiple authorizations can be pending simultaneously, replacing
|
||||||
|
// one of them should not break the one in-flight-transaction limit.
|
||||||
name: "track-multiple-conflicting-delegations",
|
name: "track-multiple-conflicting-delegations",
|
||||||
pending: 3,
|
pending: 3,
|
||||||
run: func(name string) {
|
run: func(name string) {
|
||||||
|
|
@ -2369,19 +2488,6 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "reject-delegation-from-pending-account",
|
|
||||||
pending: 1,
|
|
||||||
run: func(name string) {
|
|
||||||
// Attempt to submit a delegation from an account with a pending tx.
|
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil {
|
|
||||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
|
||||||
}
|
|
||||||
if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), ErrAuthorityReserved; !errors.Is(err, want) {
|
|
||||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "remove-hash-from-authority-tracker",
|
name: "remove-hash-from-authority-tracker",
|
||||||
pending: 10,
|
pending: 10,
|
||||||
|
|
|
||||||
|
|
@ -52,22 +52,37 @@ func NewReservationTracker() *ReservationTracker {
|
||||||
|
|
||||||
// NewHandle creates a named handle on the ReservationTracker. The handle
|
// NewHandle creates a named handle on the ReservationTracker. The handle
|
||||||
// identifies the subpool so ownership of reservations can be determined.
|
// identifies the subpool so ownership of reservations can be determined.
|
||||||
func (r *ReservationTracker) NewHandle(id int) *Reserver {
|
func (r *ReservationTracker) NewHandle(id int) *ReservationHandle {
|
||||||
return &Reserver{r, id}
|
return &ReservationHandle{r, id}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserver is a named handle on ReservationTracker. It is held by subpools to
|
// Reserver is an interface for creating and releasing owned reservations in the
|
||||||
|
// ReservationTracker struct, which is shared between subpools.
|
||||||
|
type Reserver interface {
|
||||||
|
// Hold attempts to reserve the specified account address for the given pool.
|
||||||
|
// Returns an error if the account is already reserved.
|
||||||
|
Hold(addr common.Address) error
|
||||||
|
|
||||||
|
// Release attempts to release the reservation for the specified account.
|
||||||
|
// Returns an error if the address is not reserved or is reserved by another pool.
|
||||||
|
Release(addr common.Address) error
|
||||||
|
|
||||||
|
// Has returns a flag indicating if the address has been reserved by a pool
|
||||||
|
// other than one with the current Reserver handle.
|
||||||
|
Has(address common.Address) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReservationHandle is a named handle on ReservationTracker. It is held by subpools to
|
||||||
// make reservations for accounts it is tracking. The id is used to determine
|
// make reservations for accounts it is tracking. The id is used to determine
|
||||||
// which pool owns an address and disallows non-owners to hold or release
|
// which pool owns an address and disallows non-owners to hold or release
|
||||||
// addresses it doesn't own.
|
// addresses it doesn't own.
|
||||||
type Reserver struct {
|
type ReservationHandle struct {
|
||||||
tracker *ReservationTracker
|
tracker *ReservationTracker
|
||||||
id int
|
id int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hold attempts to reserve the specified account address for the given pool.
|
// Hold implements the Reserver interface.
|
||||||
// Returns an error if the account is already reserved.
|
func (h *ReservationHandle) Hold(addr common.Address) error {
|
||||||
func (h *Reserver) Hold(addr common.Address) error {
|
|
||||||
h.tracker.lock.Lock()
|
h.tracker.lock.Lock()
|
||||||
defer h.tracker.lock.Unlock()
|
defer h.tracker.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -89,9 +104,8 @@ func (h *Reserver) Hold(addr common.Address) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release attempts to release the reservation for the specified account.
|
// Release implements the Reserver interface.
|
||||||
// Returns an error if the address is not reserved or is reserved by another pool.
|
func (h *ReservationHandle) Release(addr common.Address) error {
|
||||||
func (h *Reserver) Release(addr common.Address) error {
|
|
||||||
h.tracker.lock.Lock()
|
h.tracker.lock.Lock()
|
||||||
defer h.tracker.lock.Unlock()
|
defer h.tracker.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -114,11 +128,11 @@ func (h *Reserver) Release(addr common.Address) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has returns a flag indicating if the address has been reserved or not.
|
// Has implements the Reserver interface.
|
||||||
func (h *Reserver) Has(address common.Address) bool {
|
func (h *ReservationHandle) Has(address common.Address) bool {
|
||||||
h.tracker.lock.RLock()
|
h.tracker.lock.RLock()
|
||||||
defer h.tracker.lock.RUnlock()
|
defer h.tracker.lock.RUnlock()
|
||||||
|
|
||||||
_, exists := h.tracker.accounts[address]
|
id, exists := h.tracker.accounts[address]
|
||||||
return exists
|
return exists && id != h.id
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ type SubPool interface {
|
||||||
// These should not be passed as a constructor argument - nor should the pools
|
// These should not be passed as a constructor argument - nor should the pools
|
||||||
// start by themselves - in order to keep multiple subpools in lockstep with
|
// start by themselves - in order to keep multiple subpools in lockstep with
|
||||||
// one another.
|
// one another.
|
||||||
Init(gasTip uint64, head *types.Header, reserver *Reserver) error
|
Init(gasTip uint64, head *types.Header, reserver Reserver) error
|
||||||
|
|
||||||
// Close terminates any background processing threads and releases any held
|
// Close terminates any background processing threads and releases any held
|
||||||
// resources.
|
// resources.
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,9 @@ func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
|
||||||
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
||||||
// to the large transaction churn, add may postpone fully integrating the tx
|
// to the large transaction churn, add may postpone fully integrating the tx
|
||||||
// to a later point to batch multiple ones together.
|
// to a later point to batch multiple ones together.
|
||||||
|
//
|
||||||
|
// Note, if sync is set the method will block until all internal maintenance
|
||||||
|
// related to the add is finished. Only use this during tests for determinism.
|
||||||
func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
|
func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
// Split the input transactions between the subpools. It shouldn't really
|
// Split the input transactions between the subpools. It shouldn't really
|
||||||
// happen that we receive merged batches, but better graceful than strange
|
// happen that we receive merged batches, but better graceful than strange
|
||||||
|
|
@ -503,8 +506,8 @@ func (p *TxPool) Status(hash common.Hash) TxStatus {
|
||||||
// internal background reset operations. This method will run an explicit reset
|
// internal background reset operations. This method will run an explicit reset
|
||||||
// operation to ensure the pool stabilises, thus avoiding flakey behavior.
|
// operation to ensure the pool stabilises, thus avoiding flakey behavior.
|
||||||
//
|
//
|
||||||
// Note, do not use this in production / live code. In live code, the pool is
|
// Note, this method is only used for testing and is susceptible to DoS vectors.
|
||||||
// meant to reset on a separate thread to avoid DoS vectors.
|
// In production code, the pool is meant to reset on a separate thread.
|
||||||
func (p *TxPool) Sync() error {
|
func (p *TxPool) Sync() error {
|
||||||
sync := make(chan error)
|
sync := make(chan error)
|
||||||
select {
|
select {
|
||||||
|
|
@ -516,6 +519,10 @@ func (p *TxPool) Sync() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear removes all tracked txs from the subpools.
|
// Clear removes all tracked txs from the subpools.
|
||||||
|
//
|
||||||
|
// Note, this method invokes Sync() and is only used for testing, because it is
|
||||||
|
// susceptible to DoS vectors. In production code, the pool is meant to reset on
|
||||||
|
// a separate thread.
|
||||||
func (p *TxPool) Clear() {
|
func (p *TxPool) Clear() {
|
||||||
// Invoke Sync to ensure that txs pending addition don't get added to the pool after
|
// Invoke Sync to ensure that txs pending addition don't get added to the pool after
|
||||||
// the subpools are subsequently cleared
|
// the subpools are subsequently cleared
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ type Ethereum struct {
|
||||||
|
|
||||||
handler *handler
|
handler *handler
|
||||||
discmix *enode.FairMix
|
discmix *enode.FairMix
|
||||||
|
dropper *dropper
|
||||||
|
|
||||||
// DB interfaces
|
// DB interfaces
|
||||||
chainDb ethdb.Database // Block chain database
|
chainDb ethdb.Database // Block chain database
|
||||||
|
|
@ -300,6 +301,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eth.dropper = newDropper(eth.p2pServer.MaxDialedConns(), eth.p2pServer.MaxInboundConns())
|
||||||
|
|
||||||
eth.miner = miner.New(eth, config.Miner, eth.engine)
|
eth.miner = miner.New(eth, config.Miner, eth.engine)
|
||||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||||
eth.miner.SetPrioAddresses(config.TxPool.Locals)
|
eth.miner.SetPrioAddresses(config.TxPool.Locals)
|
||||||
|
|
@ -410,6 +413,9 @@ func (s *Ethereum) Start() error {
|
||||||
// Start the networking layer
|
// Start the networking layer
|
||||||
s.handler.Start(s.p2pServer.MaxPeers)
|
s.handler.Start(s.p2pServer.MaxPeers)
|
||||||
|
|
||||||
|
// Start the connection manager
|
||||||
|
s.dropper.Start(s.p2pServer, func() bool { return !s.Synced() })
|
||||||
|
|
||||||
// start log indexer
|
// start log indexer
|
||||||
s.filterMaps.Start()
|
s.filterMaps.Start()
|
||||||
go s.updateFilterMapsHeads()
|
go s.updateFilterMapsHeads()
|
||||||
|
|
@ -511,6 +517,7 @@ func (s *Ethereum) setupDiscovery() error {
|
||||||
func (s *Ethereum) Stop() error {
|
func (s *Ethereum) Stop() error {
|
||||||
// Stop all the peer-related stuff first.
|
// Stop all the peer-related stuff first.
|
||||||
s.discmix.Close()
|
s.discmix.Close()
|
||||||
|
s.dropper.Stop()
|
||||||
s.handler.Stop()
|
s.handler.Stop()
|
||||||
|
|
||||||
// Then stop everything else.
|
// Then stop everything else.
|
||||||
|
|
|
||||||
|
|
@ -447,6 +447,9 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
|
||||||
n.Close()
|
n.Close()
|
||||||
t.Fatal("can't import test blocks:", err)
|
t.Fatal("can't import test blocks:", err)
|
||||||
}
|
}
|
||||||
|
if err := ethservice.TxPool().Sync(); err != nil {
|
||||||
|
t.Fatal("failed to sync txpool after initial blockchain import:", err)
|
||||||
|
}
|
||||||
|
|
||||||
ethservice.SetSynced()
|
ethservice.SetSynced()
|
||||||
return n, ethservice
|
return n, ethservice
|
||||||
|
|
|
||||||
167
eth/dropper.go
Normal file
167
eth/dropper.go
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package eth
|
||||||
|
|
||||||
|
import (
|
||||||
|
mrand "math/rand"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Interval between peer drop events (uniform between min and max)
|
||||||
|
peerDropIntervalMin = 3 * time.Minute
|
||||||
|
// Interval between peer drop events (uniform between min and max)
|
||||||
|
peerDropIntervalMax = 7 * time.Minute
|
||||||
|
// Avoid dropping peers for some time after connection
|
||||||
|
doNotDropBefore = 10 * time.Minute
|
||||||
|
// How close to max should we initiate the drop timer. O should be fine,
|
||||||
|
// dropping when no more peers can be added. Larger numbers result in more
|
||||||
|
// aggressive drop behavior.
|
||||||
|
peerDropThreshold = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// droppedInbound is the number of inbound peers dropped
|
||||||
|
droppedInbound = metrics.NewRegisteredMeter("eth/dropper/inbound", nil)
|
||||||
|
// droppedOutbound is the number of outbound peers dropped
|
||||||
|
droppedOutbound = metrics.NewRegisteredMeter("eth/dropper/outbound", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// dropper monitors the state of the peer pool and makes changes as follows:
|
||||||
|
// - during sync the Downloader handles peer connections, so dropper is disabled
|
||||||
|
// - if not syncing and the peer count is close to the limit, it drops peers
|
||||||
|
// randomly every peerDropInterval to make space for new peers
|
||||||
|
// - peers are dropped separately from the inboud pool and from the dialed pool
|
||||||
|
type dropper struct {
|
||||||
|
maxDialPeers int // maximum number of dialed peers
|
||||||
|
maxInboundPeers int // maximum number of inbound peers
|
||||||
|
peersFunc getPeersFunc
|
||||||
|
syncingFunc getSyncingFunc
|
||||||
|
|
||||||
|
// peerDropTimer introduces churn if we are close to limit capacity.
|
||||||
|
// We handle Dialed and Inbound connections separately
|
||||||
|
peerDropTimer *time.Timer
|
||||||
|
|
||||||
|
wg sync.WaitGroup // wg for graceful shutdown
|
||||||
|
shutdownCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback type to get the list of connected peers.
|
||||||
|
type getPeersFunc func() []*p2p.Peer
|
||||||
|
|
||||||
|
// Callback type to get syncing status.
|
||||||
|
// Returns true while syncing, false when synced.
|
||||||
|
type getSyncingFunc func() bool
|
||||||
|
|
||||||
|
func newDropper(maxDialPeers, maxInboundPeers int) *dropper {
|
||||||
|
cm := &dropper{
|
||||||
|
maxDialPeers: maxDialPeers,
|
||||||
|
maxInboundPeers: maxInboundPeers,
|
||||||
|
peerDropTimer: time.NewTimer(randomDuration(peerDropIntervalMin, peerDropIntervalMax)),
|
||||||
|
shutdownCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
if peerDropIntervalMin > peerDropIntervalMax {
|
||||||
|
panic("peerDropIntervalMin duration must be less than or equal to peerDropIntervalMax duration")
|
||||||
|
}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the dropper.
|
||||||
|
func (cm *dropper) Start(srv *p2p.Server, syncingFunc getSyncingFunc) {
|
||||||
|
cm.peersFunc = srv.Peers
|
||||||
|
cm.syncingFunc = syncingFunc
|
||||||
|
cm.wg.Add(1)
|
||||||
|
go cm.loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the dropper.
|
||||||
|
func (cm *dropper) Stop() {
|
||||||
|
cm.peerDropTimer.Stop()
|
||||||
|
close(cm.shutdownCh)
|
||||||
|
cm.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropRandomPeer selects one of the peers randomly and drops it from the peer pool.
|
||||||
|
func (cm *dropper) dropRandomPeer() bool {
|
||||||
|
peers := cm.peersFunc()
|
||||||
|
var numInbound int
|
||||||
|
for _, p := range peers {
|
||||||
|
if p.Inbound() {
|
||||||
|
numInbound++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numDialed := len(peers) - numInbound
|
||||||
|
|
||||||
|
selectDoNotDrop := func(p *p2p.Peer) bool {
|
||||||
|
// Avoid dropping trusted and static peers, or recent peers.
|
||||||
|
// Only drop peers if their respective category (dialed/inbound)
|
||||||
|
// is close to limit capacity.
|
||||||
|
return p.Trusted() || p.StaticDialed() ||
|
||||||
|
p.Lifetime() < mclock.AbsTime(doNotDropBefore) ||
|
||||||
|
(p.DynDialed() && cm.maxDialPeers-numDialed > peerDropThreshold) ||
|
||||||
|
(p.Inbound() && cm.maxInboundPeers-numInbound > peerDropThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
droppable := slices.DeleteFunc(peers, selectDoNotDrop)
|
||||||
|
if len(droppable) > 0 {
|
||||||
|
p := droppable[mrand.Intn(len(droppable))]
|
||||||
|
log.Debug("Dropping random peer", "inbound", p.Inbound(),
|
||||||
|
"id", p.ID(), "duration", common.PrettyDuration(p.Lifetime()), "peercountbefore", len(peers))
|
||||||
|
p.Disconnect(p2p.DiscUselessPeer)
|
||||||
|
if p.Inbound() {
|
||||||
|
droppedInbound.Mark(1)
|
||||||
|
} else {
|
||||||
|
droppedOutbound.Mark(1)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomDuration generates a random duration between min and max.
|
||||||
|
func randomDuration(min, max time.Duration) time.Duration {
|
||||||
|
if min > max {
|
||||||
|
panic("min duration must be less than or equal to max duration")
|
||||||
|
}
|
||||||
|
return time.Duration(mrand.Int63n(int64(max-min)) + int64(min))
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop is the main loop of the connection dropper.
|
||||||
|
func (cm *dropper) loop() {
|
||||||
|
defer cm.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-cm.peerDropTimer.C:
|
||||||
|
// Drop a random peer if we are not syncing and the peer count is close to the limit.
|
||||||
|
if !cm.syncingFunc() {
|
||||||
|
cm.dropRandomPeer()
|
||||||
|
}
|
||||||
|
cm.peerDropTimer.Reset(randomDuration(peerDropIntervalMin, peerDropIntervalMax))
|
||||||
|
case <-cm.shutdownCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -202,22 +202,23 @@ type TxFetcher struct {
|
||||||
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
|
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
|
||||||
dropPeer func(string) // Drops a peer in case of announcement violation
|
dropPeer func(string) // Drops a peer in case of announcement violation
|
||||||
|
|
||||||
step chan struct{} // Notification channel when the fetcher loop iterates
|
step chan struct{} // Notification channel when the fetcher loop iterates
|
||||||
clock mclock.Clock // Time wrapper to simulate in tests
|
clock mclock.Clock // Monotonic clock or simulated clock for tests
|
||||||
rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random)
|
realTime func() time.Time // Real system time or simulated time for tests
|
||||||
|
rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
||||||
// based on hash announcements.
|
// based on hash announcements.
|
||||||
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher {
|
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher {
|
||||||
return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, dropPeer, mclock.System{}, nil)
|
return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, dropPeer, mclock.System{}, time.Now, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
||||||
// a simulated version and the internal randomness with a deterministic one.
|
// a simulated version and the internal randomness with a deterministic one.
|
||||||
func NewTxFetcherForTests(
|
func NewTxFetcherForTests(
|
||||||
hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
|
hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
|
||||||
clock mclock.Clock, rand *mrand.Rand) *TxFetcher {
|
clock mclock.Clock, realTime func() time.Time, rand *mrand.Rand) *TxFetcher {
|
||||||
return &TxFetcher{
|
return &TxFetcher{
|
||||||
notify: make(chan *txAnnounce),
|
notify: make(chan *txAnnounce),
|
||||||
cleanup: make(chan *txDelivery),
|
cleanup: make(chan *txDelivery),
|
||||||
|
|
@ -237,6 +238,7 @@ func NewTxFetcherForTests(
|
||||||
fetchTxs: fetchTxs,
|
fetchTxs: fetchTxs,
|
||||||
dropPeer: dropPeer,
|
dropPeer: dropPeer,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
|
realTime: realTime,
|
||||||
rand: rand,
|
rand: rand,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +295,7 @@ func (f *TxFetcher) Notify(peer string, types []byte, sizes []uint32, hashes []c
|
||||||
// isKnownUnderpriced reports whether a transaction hash was recently found to be underpriced.
|
// isKnownUnderpriced reports whether a transaction hash was recently found to be underpriced.
|
||||||
func (f *TxFetcher) isKnownUnderpriced(hash common.Hash) bool {
|
func (f *TxFetcher) isKnownUnderpriced(hash common.Hash) bool {
|
||||||
prevTime, ok := f.underpriced.Peek(hash)
|
prevTime, ok := f.underpriced.Peek(hash)
|
||||||
if ok && prevTime.Before(time.Now().Add(-maxTxUnderpricedTimeout)) {
|
if ok && prevTime.Before(f.realTime().Add(-maxTxUnderpricedTimeout)) {
|
||||||
f.underpriced.Remove(hash)
|
f.underpriced.Remove(hash)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2150,9 +2150,22 @@ func containsHashInAnnounces(slice []announce, hash common.Hash) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that a transaction is forgotten after the timeout.
|
// TestTransactionForgotten verifies that underpriced transactions are properly
|
||||||
|
// forgotten after the timeout period, testing both the exact timeout boundary
|
||||||
|
// and the cleanup of the underpriced cache.
|
||||||
func TestTransactionForgotten(t *testing.T) {
|
func TestTransactionForgotten(t *testing.T) {
|
||||||
fetcher := NewTxFetcher(
|
// Test ensures that underpriced transactions are properly forgotten after a timeout period,
|
||||||
|
// including checks for timeout boundary and cache cleanup.
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create a mock clock for deterministic time control
|
||||||
|
mockClock := new(mclock.Simulated)
|
||||||
|
mockTime := func() time.Time {
|
||||||
|
nanoTime := int64(mockClock.Now())
|
||||||
|
return time.Unix(nanoTime/1000000000, nanoTime%1000000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetcher := NewTxFetcherForTests(
|
||||||
func(common.Hash) bool { return false },
|
func(common.Hash) bool { return false },
|
||||||
func(txs []*types.Transaction) []error {
|
func(txs []*types.Transaction) []error {
|
||||||
errs := make([]error, len(txs))
|
errs := make([]error, len(txs))
|
||||||
|
|
@ -2163,24 +2176,83 @@ func TestTransactionForgotten(t *testing.T) {
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
func(string) {},
|
func(string) {},
|
||||||
|
mockClock,
|
||||||
|
mockTime,
|
||||||
|
rand.New(rand.NewSource(0)), // Use fixed seed for deterministic behavior
|
||||||
)
|
)
|
||||||
fetcher.Start()
|
fetcher.Start()
|
||||||
defer fetcher.Stop()
|
defer fetcher.Stop()
|
||||||
// Create one TX which is 5 minutes old, and one which is recent
|
|
||||||
tx1 := types.NewTx(&types.LegacyTx{Nonce: 0})
|
|
||||||
tx1.SetTime(time.Now().Add(-maxTxUnderpricedTimeout - 1*time.Second))
|
|
||||||
tx2 := types.NewTx(&types.LegacyTx{Nonce: 1})
|
|
||||||
|
|
||||||
// Enqueue both in the fetcher. They will be immediately tagged as underpriced
|
// Create two test transactions with the same timestamp
|
||||||
if err := fetcher.Enqueue("asdf", []*types.Transaction{tx1, tx2}, false); err != nil {
|
tx1 := types.NewTransaction(0, common.Address{}, big.NewInt(100), 21000, big.NewInt(1), nil)
|
||||||
|
tx2 := types.NewTransaction(1, common.Address{}, big.NewInt(100), 21000, big.NewInt(1), nil)
|
||||||
|
|
||||||
|
now := mockTime()
|
||||||
|
tx1.SetTime(now)
|
||||||
|
tx2.SetTime(now)
|
||||||
|
|
||||||
|
// Initial state: both transactions should be marked as underpriced
|
||||||
|
if err := fetcher.Enqueue("peer", []*types.Transaction{tx1, tx2}, false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// isKnownUnderpriced should trigger removal of the first tx (no longer be known underpriced)
|
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
||||||
if fetcher.isKnownUnderpriced(tx1.Hash()) {
|
t.Error("tx1 should be underpriced")
|
||||||
t.Fatal("transaction should be forgotten by now")
|
|
||||||
}
|
}
|
||||||
// isKnownUnderpriced should not trigger removal of the second
|
|
||||||
if !fetcher.isKnownUnderpriced(tx2.Hash()) {
|
if !fetcher.isKnownUnderpriced(tx2.Hash()) {
|
||||||
t.Fatal("transaction should be known underpriced")
|
t.Error("tx2 should be underpriced")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify cache size
|
||||||
|
if size := fetcher.underpriced.Len(); size != 2 {
|
||||||
|
t.Errorf("wrong underpriced cache size: got %d, want %d", size, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just before timeout: transactions should still be underpriced
|
||||||
|
mockClock.Run(maxTxUnderpricedTimeout - time.Second)
|
||||||
|
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
||||||
|
t.Error("tx1 should still be underpriced before timeout")
|
||||||
|
}
|
||||||
|
if !fetcher.isKnownUnderpriced(tx2.Hash()) {
|
||||||
|
t.Error("tx2 should still be underpriced before timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exactly at timeout boundary: transactions should still be present
|
||||||
|
mockClock.Run(time.Second)
|
||||||
|
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
||||||
|
t.Error("tx1 should be present exactly at timeout")
|
||||||
|
}
|
||||||
|
if !fetcher.isKnownUnderpriced(tx2.Hash()) {
|
||||||
|
t.Error("tx2 should be present exactly at timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// After timeout: transactions should be forgotten
|
||||||
|
mockClock.Run(time.Second)
|
||||||
|
if fetcher.isKnownUnderpriced(tx1.Hash()) {
|
||||||
|
t.Error("tx1 should be forgotten after timeout")
|
||||||
|
}
|
||||||
|
if fetcher.isKnownUnderpriced(tx2.Hash()) {
|
||||||
|
t.Error("tx2 should be forgotten after timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify cache is empty
|
||||||
|
if size := fetcher.underpriced.Len(); size != 0 {
|
||||||
|
t.Errorf("wrong underpriced cache size after timeout: got %d, want 0", size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-enqueue tx1 with updated timestamp
|
||||||
|
tx1.SetTime(mockTime())
|
||||||
|
if err := fetcher.Enqueue("peer", []*types.Transaction{tx1}, false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
||||||
|
t.Error("tx1 should be underpriced after re-enqueueing with new timestamp")
|
||||||
|
}
|
||||||
|
if fetcher.isKnownUnderpriced(tx2.Hash()) {
|
||||||
|
t.Error("tx2 should remain forgotten")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify final cache state
|
||||||
|
if size := fetcher.underpriced.Len(); size != 1 {
|
||||||
|
t.Errorf("wrong final underpriced cache size: got %d, want 1", size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
@ -32,12 +31,21 @@ const (
|
||||||
termTimeFormat = "01-02|15:04:05.000"
|
termTimeFormat = "01-02|15:04:05.000"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// T wraps methods from testing.T used by the test logger into an interface.
|
||||||
|
// It is specified so that unit tests can instantiate the logger with an
|
||||||
|
// implementation of T which can capture the output of logging statements
|
||||||
|
// from T.Logf, as this cannot be using testing.T.
|
||||||
|
type T interface {
|
||||||
|
Logf(format string, args ...any)
|
||||||
|
Helper()
|
||||||
|
}
|
||||||
|
|
||||||
// logger implements log.Logger such that all output goes to the unit test log via
|
// logger implements log.Logger such that all output goes to the unit test log via
|
||||||
// t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test
|
// t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test
|
||||||
// helpers, so the file and line number in unit test output correspond to the call site
|
// helpers, so the file and line number in unit test output correspond to the call site
|
||||||
// which emitted the log message.
|
// which emitted the log message.
|
||||||
type logger struct {
|
type logger struct {
|
||||||
t *testing.T
|
t T
|
||||||
l log.Logger
|
l log.Logger
|
||||||
mu *sync.Mutex
|
mu *sync.Mutex
|
||||||
h *bufHandler
|
h *bufHandler
|
||||||
|
|
@ -78,7 +86,7 @@ func (h *bufHandler) WithGroup(_ string) slog.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger returns a logger which logs to the unit test log of t.
|
// Logger returns a logger which logs to the unit test log of t.
|
||||||
func Logger(t *testing.T, level slog.Level) log.Logger {
|
func Logger(t T, level slog.Level) log.Logger {
|
||||||
handler := bufHandler{
|
handler := bufHandler{
|
||||||
buf: []slog.Record{},
|
buf: []slog.Record{},
|
||||||
attrs: []slog.Attr{},
|
attrs: []slog.Attr{},
|
||||||
|
|
@ -92,17 +100,6 @@ func Logger(t *testing.T, level slog.Level) log.Logger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoggerWithHandler returns
|
|
||||||
func LoggerWithHandler(t *testing.T, handler slog.Handler) log.Logger {
|
|
||||||
var bh bufHandler
|
|
||||||
return &logger{
|
|
||||||
t: t,
|
|
||||||
l: log.NewLogger(handler),
|
|
||||||
mu: new(sync.Mutex),
|
|
||||||
h: &bh,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Handler() slog.Handler {
|
func (l *logger) Handler() slog.Handler {
|
||||||
return l.l.Handler()
|
return l.l.Handler()
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +167,8 @@ func (l *logger) Crit(msg string, ctx ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) With(ctx ...interface{}) log.Logger {
|
func (l *logger) With(ctx ...interface{}) log.Logger {
|
||||||
return &logger{l.t, l.l.With(ctx...), l.mu, l.h}
|
newLogger := l.l.With(ctx...)
|
||||||
|
return &logger{l.t, newLogger, l.mu, newLogger.Handler().(*bufHandler)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logger) New(ctx ...interface{}) log.Logger {
|
func (l *logger) New(ctx ...interface{}) log.Logger {
|
||||||
|
|
|
||||||
67
internal/testlog/testlog_test.go
Normal file
67
internal/testlog/testlog_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
package testlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockT struct {
|
||||||
|
out io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockT) Helper() {
|
||||||
|
// noop for the purposes of unit tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockT) Logf(format string, args ...any) {
|
||||||
|
// we could gate this operation in a mutex, but because testlogger
|
||||||
|
// only calls Logf with its internal mutex held, we just write output here
|
||||||
|
var lineBuf bytes.Buffer
|
||||||
|
if _, err := fmt.Fprintf(&lineBuf, format, args...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// The timestamp is locale-dependent, so we want to trim that off
|
||||||
|
// "INFO [01-01|00:00:00.000] a message ..." -> "a message..."
|
||||||
|
sanitized := strings.Split(lineBuf.String(), "]")[1]
|
||||||
|
if _, err := t.out.Write([]byte(sanitized)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogging(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
run func(t *mockT)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"SubLogger",
|
||||||
|
` Visible
|
||||||
|
Hide and seek foobar=123
|
||||||
|
Also visible
|
||||||
|
`,
|
||||||
|
func(t *mockT) {
|
||||||
|
l := Logger(t, log.LevelInfo)
|
||||||
|
subLogger := l.New("foobar", 123)
|
||||||
|
|
||||||
|
l.Info("Visible")
|
||||||
|
subLogger.Info("Hide and seek")
|
||||||
|
l.Info("Also visible")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
outp := bytes.Buffer{}
|
||||||
|
mock := mockT{&outp}
|
||||||
|
tc.run(&mock)
|
||||||
|
if outp.String() != tc.expected {
|
||||||
|
fmt.Printf("output mismatch.\nwant: '%s'\ngot: '%s'\n", tc.expected, outp.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,6 +51,10 @@ var (
|
||||||
dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil)
|
dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil)
|
||||||
dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil)
|
dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil)
|
||||||
|
|
||||||
|
// count peers that stayed connected for at least 1 min
|
||||||
|
serve1MinSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success/1min", nil)
|
||||||
|
dial1MinSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success/1min", nil)
|
||||||
|
|
||||||
// handshake error meters
|
// handshake error meters
|
||||||
dialTooManyPeers = metrics.NewRegisteredMeter("p2p/dials/error/saturated", nil)
|
dialTooManyPeers = metrics.NewRegisteredMeter("p2p/dials/error/saturated", nil)
|
||||||
dialAlreadyConnected = metrics.NewRegisteredMeter("p2p/dials/error/known", nil)
|
dialAlreadyConnected = metrics.NewRegisteredMeter("p2p/dials/error/known", nil)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/huin/goupnp"
|
"github.com/huin/goupnp"
|
||||||
"github.com/huin/goupnp/dcps/internetgateway1"
|
"github.com/huin/goupnp/dcps/internetgateway1"
|
||||||
"github.com/huin/goupnp/dcps/internetgateway2"
|
"github.com/huin/goupnp/dcps/internetgateway2"
|
||||||
|
|
@ -34,6 +35,8 @@ import (
|
||||||
const (
|
const (
|
||||||
soapRequestTimeout = 3 * time.Second
|
soapRequestTimeout = 3 * time.Second
|
||||||
rateLimit = 200 * time.Millisecond
|
rateLimit = 200 * time.Millisecond
|
||||||
|
retryCount = 3 // number of retries after a failed AddPortMapping
|
||||||
|
randomCount = 3 // number of random ports to try
|
||||||
)
|
)
|
||||||
|
|
||||||
type upnp struct {
|
type upnp struct {
|
||||||
|
|
@ -89,42 +92,43 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
|
||||||
|
|
||||||
if extport == 0 {
|
if extport == 0 {
|
||||||
extport = intport
|
extport = intport
|
||||||
} else {
|
|
||||||
// Only delete port mapping if the external port was already used by geth.
|
|
||||||
n.DeleteMapping(protocol, extport, intport)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to add port mapping, preferring the specified external port.
|
// Try to add port mapping, preferring the specified external port.
|
||||||
err = n.withRateLimit(func() error {
|
return n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
|
||||||
p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
|
|
||||||
if err == nil {
|
|
||||||
extport = int(p)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return uint16(extport), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addAnyPortMapping tries to add a port mapping with the specified external port.
|
// addAnyPortMapping tries to add a port mapping with the specified external port.
|
||||||
// If the external port is already in use, it will try to assign another port.
|
// If the external port is already in use, it will try to assign another port.
|
||||||
func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
|
func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
|
||||||
if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok {
|
if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok {
|
||||||
return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
|
return n.portWithRateLimit(func() (uint16, error) {
|
||||||
|
return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// For IGDv1 and v1 services we should first try to add with extport.
|
// For IGDv1 and v1 services we should first try to add with extport.
|
||||||
err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
|
for i := 0; i < retryCount+1; i++ {
|
||||||
if err == nil {
|
err := n.withRateLimit(func() error {
|
||||||
return uint16(extport), nil
|
return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return uint16(extport), nil
|
||||||
|
}
|
||||||
|
log.Debug("Failed to add port mapping", "protocol", protocol, "extport", extport, "intport", intport, "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If above fails, we retry with a random port.
|
// If above fails, we retry with a random port.
|
||||||
// We retry several times because of possible port conflicts.
|
// We retry several times because of possible port conflicts.
|
||||||
for i := 0; i < 3; i++ {
|
var err error
|
||||||
|
for i := 0; i < randomCount; i++ {
|
||||||
extport = n.randomPort()
|
extport = n.randomPort()
|
||||||
err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
|
err := n.withRateLimit(func() error {
|
||||||
|
return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
|
||||||
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return uint16(extport), nil
|
return uint16(extport), nil
|
||||||
}
|
}
|
||||||
|
log.Debug("Failed to add random port mapping", "protocol", protocol, "extport", extport, "intport", intport, "err", err)
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
@ -169,6 +173,17 @@ func (n *upnp) String() string {
|
||||||
return "UPNP " + n.service
|
return "UPNP " + n.service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *upnp) portWithRateLimit(pfn func() (uint16, error)) (uint16, error) {
|
||||||
|
var port uint16
|
||||||
|
var err error
|
||||||
|
fn := func() error {
|
||||||
|
port, err = pfn()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.withRateLimit(fn)
|
||||||
|
return port, err
|
||||||
|
}
|
||||||
|
|
||||||
func (n *upnp) withRateLimit(fn func() error) error {
|
func (n *upnp) withRateLimit(fn func() error) error {
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
defer n.mu.Unlock()
|
defer n.mu.Unlock()
|
||||||
|
|
|
||||||
34
p2p/peer.go
34
p2p/peer.go
|
|
@ -220,11 +220,35 @@ func (p *Peer) String() string {
|
||||||
return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
|
return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound returns true if the peer is an inbound connection
|
// Inbound returns true if the peer is an inbound (not dialed) connection.
|
||||||
func (p *Peer) Inbound() bool {
|
func (p *Peer) Inbound() bool {
|
||||||
return p.rw.is(inboundConn)
|
return p.rw.is(inboundConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trusted returns true if the peer is configured as trusted.
|
||||||
|
// Trusted peers are accepted in above the MaxInboundConns limit.
|
||||||
|
// The peer can be either inbound or dialed.
|
||||||
|
func (p *Peer) Trusted() bool {
|
||||||
|
return p.rw.is(trustedConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynDialed returns true if the peer was dialed successfully (passed handshake) and
|
||||||
|
// it is not configured as static.
|
||||||
|
func (p *Peer) DynDialed() bool {
|
||||||
|
return p.rw.is(dynDialedConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticDialed returns true if the peer was dialed successfully (passed handshake) and
|
||||||
|
// it is configured as static.
|
||||||
|
func (p *Peer) StaticDialed() bool {
|
||||||
|
return p.rw.is(staticDialedConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifetime returns the time since peer creation.
|
||||||
|
func (p *Peer) Lifetime() mclock.AbsTime {
|
||||||
|
return mclock.Now() - p.created
|
||||||
|
}
|
||||||
|
|
||||||
func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer {
|
func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer {
|
||||||
protomap := matchProtocols(protocols, conn.caps, conn)
|
protomap := matchProtocols(protocols, conn.caps, conn)
|
||||||
p := &Peer{
|
p := &Peer{
|
||||||
|
|
@ -254,6 +278,8 @@ func (p *Peer) run() (remoteRequested bool, err error) {
|
||||||
p.wg.Add(2)
|
p.wg.Add(2)
|
||||||
go p.readLoop(readErr)
|
go p.readLoop(readErr)
|
||||||
go p.pingLoop()
|
go p.pingLoop()
|
||||||
|
live1min := time.NewTimer(1 * time.Minute)
|
||||||
|
defer live1min.Stop()
|
||||||
|
|
||||||
// Start all protocol handlers.
|
// Start all protocol handlers.
|
||||||
writeStart <- struct{}{}
|
writeStart <- struct{}{}
|
||||||
|
|
@ -285,6 +311,12 @@ loop:
|
||||||
case err = <-p.disc:
|
case err = <-p.disc:
|
||||||
reason = discReasonForError(err)
|
reason = discReasonForError(err)
|
||||||
break loop
|
break loop
|
||||||
|
case <-live1min.C:
|
||||||
|
if p.Inbound() {
|
||||||
|
serve1MinSuccessMeter.Mark(1)
|
||||||
|
} else {
|
||||||
|
dial1MinSuccessMeter.Mark(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -508,7 +508,7 @@ func (srv *Server) setupDiscovery() error {
|
||||||
func (srv *Server) setupDialScheduler() {
|
func (srv *Server) setupDialScheduler() {
|
||||||
config := dialConfig{
|
config := dialConfig{
|
||||||
self: srv.localnode.ID(),
|
self: srv.localnode.ID(),
|
||||||
maxDialPeers: srv.maxDialedConns(),
|
maxDialPeers: srv.MaxDialedConns(),
|
||||||
maxActiveDials: srv.MaxPendingPeers,
|
maxActiveDials: srv.MaxPendingPeers,
|
||||||
log: srv.Logger,
|
log: srv.Logger,
|
||||||
netRestrict: srv.NetRestrict,
|
netRestrict: srv.NetRestrict,
|
||||||
|
|
@ -527,11 +527,11 @@ func (srv *Server) setupDialScheduler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) maxInboundConns() int {
|
func (srv *Server) MaxInboundConns() int {
|
||||||
return srv.MaxPeers - srv.maxDialedConns()
|
return srv.MaxPeers - srv.MaxDialedConns()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) maxDialedConns() (limit int) {
|
func (srv *Server) MaxDialedConns() (limit int) {
|
||||||
if srv.NoDial || srv.MaxPeers == 0 {
|
if srv.NoDial || srv.MaxPeers == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -736,7 +736,7 @@ func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount in
|
||||||
switch {
|
switch {
|
||||||
case !c.is(trustedConn) && len(peers) >= srv.MaxPeers:
|
case !c.is(trustedConn) && len(peers) >= srv.MaxPeers:
|
||||||
return DiscTooManyPeers
|
return DiscTooManyPeers
|
||||||
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
|
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.MaxInboundConns():
|
||||||
return DiscTooManyPeers
|
return DiscTooManyPeers
|
||||||
case peers[c.node.ID()] != nil:
|
case peers[c.node.ID()] != nil:
|
||||||
return DiscAlreadyConnected
|
return DiscAlreadyConnected
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,14 @@ const (
|
||||||
portMapRefreshInterval = 8 * time.Minute
|
portMapRefreshInterval = 8 * time.Minute
|
||||||
portMapRetryInterval = 5 * time.Minute
|
portMapRetryInterval = 5 * time.Minute
|
||||||
extipRetryInterval = 2 * time.Minute
|
extipRetryInterval = 2 * time.Minute
|
||||||
|
maxRetries = 5 // max number of failed attempts to refresh the mapping
|
||||||
)
|
)
|
||||||
|
|
||||||
type portMapping struct {
|
type portMapping struct {
|
||||||
protocol string
|
protocol string
|
||||||
name string
|
name string
|
||||||
port int
|
port int
|
||||||
|
retries int // number of failed attempts to refresh the mapping
|
||||||
|
|
||||||
// for use by the portMappingLoop goroutine:
|
// for use by the portMappingLoop goroutine:
|
||||||
extPort int // the mapped port returned by the NAT interface
|
extPort int // the mapped port returned by the NAT interface
|
||||||
|
|
@ -154,28 +156,49 @@ func (srv *Server) portMappingLoop() {
|
||||||
log.Trace("Attempting port mapping")
|
log.Trace("Attempting port mapping")
|
||||||
p, err := srv.NAT.AddMapping(m.protocol, m.extPort, m.port, m.name, portMapDuration)
|
p, err := srv.NAT.AddMapping(m.protocol, m.extPort, m.port, m.name, portMapDuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Couldn't add port mapping", "err", err)
|
// Failed to add or refresh port mapping.
|
||||||
m.extPort = 0
|
if m.extPort == 0 {
|
||||||
|
log.Debug("Couldn't add port mapping", "err", err)
|
||||||
|
} else {
|
||||||
|
// Failed refresh. Since UPnP implementation are often buggy,
|
||||||
|
// and lifetime is larger than the retry interval, this does not
|
||||||
|
// mean we lost our existing mapping. We do not reset the external
|
||||||
|
// port, as it is still our best chance, but we do retry soon.
|
||||||
|
// We could check the error code, but UPnP implementations are buggy.
|
||||||
|
log.Debug("Couldn't refresh port mapping", "err", err)
|
||||||
|
m.retries++
|
||||||
|
if m.retries > maxRetries {
|
||||||
|
m.retries = 0
|
||||||
|
err := srv.NAT.DeleteMapping(m.protocol, m.extPort, m.port)
|
||||||
|
log.Debug("Couldn't refresh port mapping, trying to delete it:", "err", err)
|
||||||
|
m.extPort = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
m.nextTime = srv.clock.Now().Add(portMapRetryInterval)
|
m.nextTime = srv.clock.Now().Add(portMapRetryInterval)
|
||||||
|
// Note ENR is not updated here, i.e. we keep the last port.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// It was mapped!
|
|
||||||
m.extPort = int(p)
|
|
||||||
m.nextTime = srv.clock.Now().Add(portMapRefreshInterval)
|
|
||||||
log = newLogger(m.protocol, m.extPort, m.port)
|
|
||||||
if m.port != m.extPort {
|
|
||||||
log.Info("NAT mapped alternative port")
|
|
||||||
} else {
|
|
||||||
log.Info("NAT mapped port")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update port in local ENR.
|
// It was mapped!
|
||||||
switch m.protocol {
|
m.retries = 0
|
||||||
case "TCP":
|
log = newLogger(m.protocol, int(p), m.port)
|
||||||
srv.localnode.Set(enr.TCP(m.extPort))
|
if int(p) != m.extPort {
|
||||||
case "UDP":
|
m.extPort = int(p)
|
||||||
srv.localnode.SetFallbackUDP(m.extPort)
|
if m.port != m.extPort {
|
||||||
|
log.Info("NAT mapped alternative port")
|
||||||
|
} else {
|
||||||
|
log.Info("NAT mapped port")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update port in local ENR.
|
||||||
|
switch m.protocol {
|
||||||
|
case "TCP":
|
||||||
|
srv.localnode.Set(enr.TCP(m.extPort))
|
||||||
|
case "UDP":
|
||||||
|
srv.localnode.SetFallbackUDP(m.extPort)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
m.nextTime = srv.clock.Now().Add(portMapRefreshInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,12 @@ func fuzz(input []byte) int {
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
nil,
|
nil,
|
||||||
clock, rand,
|
clock,
|
||||||
|
func() time.Time {
|
||||||
|
nanoTime := int64(clock.Now())
|
||||||
|
return time.Unix(nanoTime/1000000000, nanoTime%1000000000)
|
||||||
|
},
|
||||||
|
rand,
|
||||||
)
|
)
|
||||||
f.Start()
|
f.Start()
|
||||||
defer f.Stop()
|
defer f.Stop()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,6 @@ package version
|
||||||
const (
|
const (
|
||||||
Major = 1 // Major version component of the current release
|
Major = 1 // Major version component of the current release
|
||||||
Minor = 15 // Minor version component of the current release
|
Minor = 15 // Minor version component of the current release
|
||||||
Patch = 8 // Patch version component of the current release
|
Patch = 9 // Patch version component of the current release
|
||||||
Meta = "unstable" // Version metadata to append to the version string
|
Meta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue