diff --git a/internal/era/e2store/e2store.go b/internal/era/e2store/e2store.go index 9832b72d48..b0d43bf55a 100644 --- a/internal/era/e2store/e2store.go +++ b/internal/era/e2store/e2store.go @@ -219,3 +219,15 @@ func (r *Reader) FindAll(want uint16) ([]*Entry, error) { off += int64(headerSize + length) } } + +// SkipN skips `n` entries starting from `offset` and returns the new offset. +func (r *Reader) SkipN(offset int64, n uint64) (int64, error) { + for i := uint64(0); i < n; i++ { + length, err := r.LengthAt(offset) + if err != nil { + return 0, err + } + offset += length + } + return offset, nil +} diff --git a/internal/era/era.go b/internal/era/era.go index daf337963d..5129186fe7 100644 --- a/internal/era/era.go +++ b/internal/era/era.go @@ -70,7 +70,7 @@ func ReadDir(dir, network string) ([]string, error) { } parts := strings.Split(entry.Name(), "-") if len(parts) != 3 || parts[0] != network { - // invalid era1 filename, skip + // Invalid era1 filename, skip. continue } if epoch, err := strconv.ParseUint(parts[1], 10, 64); err != nil { @@ -126,6 +126,29 @@ func (e *Era) Close() error { return e.f.Close() } +// GetHeaderByNumber returns the header for the given block number. +func (e *Era) GetHeaderByNumber(num uint64) (*types.Header, error) { + if e.m.start > num || e.m.start+e.m.count <= num { + return nil, errors.New("out-of-bounds") + } + off, err := e.readOffset(num) + if err != nil { + return nil, err + } + + // Read and decompress header. + r, _, err := newSnappyReader(e.s, TypeCompressedHeader, off) + if err != nil { + return nil, err + } + var header types.Header + if err := rlp.Decode(r, &header); err != nil { + return nil, err + } + return &header, nil +} + +// GetBlockByNumber returns the block for the given block number. func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) { if e.m.start > num || e.m.start+e.m.count <= num { return nil, errors.New("out-of-bounds") @@ -154,6 +177,34 @@ func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) { return types.NewBlockWithHeader(&header).WithBody(body), nil } +// GetReceiptsByNumber returns the receipts for the given block number. +func (e *Era) GetReceiptsByNumber(num uint64) (types.Receipts, error) { + if e.m.start > num || e.m.start+e.m.count <= num { + return nil, errors.New("out-of-bounds") + } + off, err := e.readOffset(num) + if err != nil { + return nil, err + } + + // Skip over header and body. + off, err = e.s.SkipN(off, 2) + if err != nil { + return nil, err + } + + // Read and decompress receipts. + r, _, err := newSnappyReader(e.s, TypeCompressedReceipts, off) + if err != nil { + return nil, err + } + var receipts types.Receipts + if err := rlp.Decode(r, &receipts); err != nil { + return nil, err + } + return receipts, nil +} + // Accumulator reads the accumulator entry in the Era1 file. func (e *Era) Accumulator() (common.Hash, error) { entry, err := e.s.Find(TypeAccumulator) @@ -187,13 +238,10 @@ func (e *Era) InitialTD() (*big.Int, error) { } off += n - // Skip over next two records. - for i := 0; i < 2; i++ { - length, err := e.s.LengthAt(off) - if err != nil { - return nil, err - } - off += length + // Skip over header and body. + off, err = e.s.SkipN(off, 2) + if err != nil { + return nil, err } // Read total difficulty after first block. diff --git a/internal/era/era_test.go b/internal/era/era_test.go index 72c3b385dd..46fc2e91f3 100644 --- a/internal/era/era_test.go +++ b/internal/era/era_test.go @@ -18,12 +18,15 @@ package era import ( "bytes" + "fmt" "io" "math/big" "os" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" ) type testchain struct { @@ -48,9 +51,9 @@ func TestEra1Builder(t *testing.T) { chain = testchain{} ) for i := 0; i < 128; i++ { - chain.headers = append(chain.headers, []byte{byte('h'), byte(i)}) - chain.bodies = append(chain.bodies, []byte{byte('b'), byte(i)}) - chain.receipts = append(chain.receipts, []byte{byte('r'), byte(i)}) + chain.headers = append(chain.headers, mustEncode(&types.Header{Number: big.NewInt(int64(i))})) + chain.bodies = append(chain.bodies, mustEncode(&types.Body{Transactions: []*types.Transaction{types.NewTransaction(0, common.Address{byte(i)}, nil, 0, nil, nil)}})) + chain.receipts = append(chain.receipts, mustEncode(&types.Receipts{{CumulativeGasUsed: uint64(i)}})) chain.tds = append(chain.tds, big.NewInt(int64(i))) } @@ -91,13 +94,25 @@ func TestEra1Builder(t *testing.T) { t.Fatalf("unexpected error %v", it.Error()) } // Check headers. - header, err := io.ReadAll(it.Header) + rawHeader, err := io.ReadAll(it.Header) + if err != nil { + t.Fatalf("error reading header from iterator: %v", err) + } + if !bytes.Equal(rawHeader, chain.headers[i]) { + t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], rawHeader) + } + header, err := e.GetHeaderByNumber(i) if err != nil { t.Fatalf("error reading header: %v", err) } - if !bytes.Equal(header, chain.headers[i]) { - t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], header) + encHeader, err := rlp.EncodeToBytes(header) + if err != nil { + t.Fatalf("error encoding header: %v", err) } + if !bytes.Equal(encHeader, chain.headers[i]) { + t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], encHeader) + } + // Check bodies. body, err := io.ReadAll(it.Body) if err != nil { @@ -106,13 +121,25 @@ func TestEra1Builder(t *testing.T) { if !bytes.Equal(body, chain.bodies[i]) { t.Fatalf("mismatched body: want %s, got %s", chain.bodies[i], body) } + // Check receipts. - receipts, err := io.ReadAll(it.Receipts) + rawReceipts, err := io.ReadAll(it.Receipts) + if err != nil { + t.Fatalf("error reading receipts from iterator: %v", err) + } + if !bytes.Equal(rawReceipts, chain.receipts[i]) { + t.Fatalf("mismatched receipts: want %s, got %s", chain.receipts[i], rawReceipts) + } + receipts, err := e.GetReceiptsByNumber(i) if err != nil { t.Fatalf("error reading receipts: %v", err) } - if !bytes.Equal(receipts, chain.receipts[i]) { - t.Fatalf("mismatched receipts: want %s, got %s", chain.receipts[i], receipts) + encReceipts, err := rlp.EncodeToBytes(receipts) + if err != nil { + t.Fatalf("error encoding receipts: %v", err) + } + if !bytes.Equal(encReceipts, chain.receipts[i]) { + t.Fatalf("mismatched receipts: want %s, got %s", chain.receipts[i], encReceipts) } // Check total difficulty. @@ -144,3 +171,11 @@ func TestEraFilename(t *testing.T) { } } } + +func mustEncode(obj any) []byte { + b, err := rlp.EncodeToBytes(obj) + if err != nil { + panic(fmt.Sprintf("failed in encode obj: %v", err)) + } + return b +} diff --git a/p2p/nat/natpmp.go b/p2p/nat/natpmp.go index 4a9644ac1a..ee07eb4ff6 100644 --- a/p2p/nat/natpmp.go +++ b/p2p/nat/natpmp.go @@ -49,6 +49,9 @@ func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lif if lifetime <= 0 { return 0, errors.New("lifetime must not be <= 0") } + if extport == 0 { + extport = intport + } // Note order of port arguments is switched between our // AddMapping and the client's AddPortMapping. res, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second)) diff --git a/p2p/nat/natupnp.go b/p2p/nat/natupnp.go index 1014dc69d2..ed00b8eeb6 100644 --- a/p2p/nat/natupnp.go +++ b/p2p/nat/natupnp.go @@ -86,15 +86,15 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li } protocol = strings.ToUpper(protocol) lifetimeS := uint32(lifetime / time.Second) - n.DeleteMapping(protocol, extport, intport) - err = n.withRateLimit(func() error { - return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) - }) - if err == nil { - return uint16(extport), nil + if extport == 0 { + extport = intport + } else { + // Only delete port mapping if the external port was already used by geth. + n.DeleteMapping(protocol, extport, intport) } - // Try addAnyPortMapping if mapping specified port didn't work. + + // Try to add port mapping, preferring the specified external port. err = n.withRateLimit(func() error { p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS) if err == nil { @@ -105,18 +105,28 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li return uint16(extport), err } +// 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. 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 { return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) } - // It will retry with a random port number if the client does - // not support AddAnyPortMapping. - extport = n.randomPort() + // 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) - if err != nil { - return 0, err + if err == nil { + return uint16(extport), nil } - return uint16(extport), nil + + // If above fails, we retry with a random port. + // We retry several times because of possible port conflicts. + for i := 0; i < 3; i++ { + extport = n.randomPort() + err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) + if err == nil { + return uint16(extport), nil + } + } + return 0, err } func (n *upnp) randomPort() int { diff --git a/p2p/server_nat.go b/p2p/server_nat.go index 933993bc1f..5830f950e1 100644 --- a/p2p/server_nat.go +++ b/p2p/server_nat.go @@ -150,14 +150,9 @@ func (srv *Server) portMappingLoop() { continue } - external := m.port - if m.extPort != 0 { - external = m.extPort - } - log := newLogger(m.protocol, external, m.port) - + log := newLogger(m.protocol, m.extPort, m.port) log.Trace("Attempting port mapping") - p, err := srv.NAT.AddMapping(m.protocol, external, m.port, m.name, portMapDuration) + p, err := srv.NAT.AddMapping(m.protocol, m.extPort, m.port, m.name, portMapDuration) if err != nil { log.Debug("Couldn't add port mapping", "err", err) m.extPort = 0 @@ -167,8 +162,8 @@ func (srv *Server) portMappingLoop() { // It was mapped! m.extPort = int(p) m.nextTime = srv.clock.Now().Add(portMapRefreshInterval) - if external != m.extPort { - log = newLogger(m.protocol, m.extPort, m.port) + 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")