Files

119 lines
3.8 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package handlers
import (
"bytes"
"context"
"time"
"github.com/luxfi/node/v2/codec"
"github.com/luxfi/node/v2/utils/units"
"github.com/luxfi/ids"
"github.com/luxfi/evm/plugin/evm/message"
"github.com/luxfi/evm/sync/handlers/stats"
"github.com/luxfi/geth/common"
"github.com/luxfi/log"
)
const (
// parentLimit specifies how many parents to retrieve and send given a starting hash
// This value overrides any specified limit in blockRequest.Parents if it is greater than this value
parentLimit = uint16(64)
targetMessageByteSize = units.MiB - units.KiB // Target total block bytes slightly under original network codec max size of 1MB
)
// BlockRequestHandler is a peer.RequestHandler for message.BlockRequest
// serving requested blocks starting at specified hash
type BlockRequestHandler struct {
stats stats.BlockRequestHandlerStats
blockProvider BlockProvider
codec codec.Manager
}
func NewBlockRequestHandler(blockProvider BlockProvider, codec codec.Manager, handlerStats stats.BlockRequestHandlerStats) *BlockRequestHandler {
return &BlockRequestHandler{
blockProvider: blockProvider,
codec: codec,
stats: handlerStats,
}
}
// OnBlockRequest handles incoming message.BlockRequest, returning blocks as requested
// Never returns error
// Expects returned errors to be treated as FATAL
// Returns empty response or subset of requested blocks if ctx expires during fetch
// Assumes ctx is active
func (b *BlockRequestHandler) OnBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest message.BlockRequest) ([]byte, error) {
startTime := time.Now()
b.stats.IncBlockRequest()
// override given Parents limit if it is greater than parentLimit
parents := blockRequest.Parents
if parents > parentLimit {
parents = parentLimit
}
blocks := make([][]byte, 0, parents)
totalBytes := 0
// ensure metrics are captured properly on all return paths
defer func() {
b.stats.UpdateBlockRequestProcessingTime(time.Since(startTime))
b.stats.UpdateBlocksReturned(uint16(len(blocks)))
}()
hash := blockRequest.Hash
height := blockRequest.Height
for i := 0; i < int(parents); i++ {
// we return whatever we have until ctx errors, limit is exceeded, or we reach the genesis block
// this will happen either when the ctx is cancelled or we hit the ctx deadline
if ctx.Err() != nil {
break
}
if (hash == common.Hash{}) {
break
}
block := b.blockProvider.GetBlock(hash, height)
if block == nil {
b.stats.IncMissingBlockHash()
break
}
buf := new(bytes.Buffer)
if err := block.EncodeRLP(buf); err != nil {
log.Error("failed to RLP encode block", "hash", block.Hash(), "height", block.NumberU64(), "err", err)
return nil, nil
}
if buf.Len()+totalBytes > targetMessageByteSize && len(blocks) > 0 {
log.Debug("Skipping block due to max total bytes size", "totalBlockDataSize", totalBytes, "blockSize", buf.Len(), "maxTotalBytesSize", targetMessageByteSize)
break
}
blocks = append(blocks, buf.Bytes())
totalBytes += buf.Len()
hash = block.ParentHash()
height--
}
if len(blocks) == 0 {
// drop this request
log.Debug("no requested blocks found, dropping request", "nodeID", nodeID, "requestID", requestID, "hash", blockRequest.Hash, "parents", blockRequest.Parents)
return nil, nil
}
response := message.BlockResponse{
Blocks: blocks,
}
responseBytes, err := b.codec.Marshal(message.Version, response)
if err != nil {
log.Error("failed to marshal BlockResponse, dropping request", "nodeID", nodeID, "requestID", requestID, "hash", blockRequest.Hash, "parents", blockRequest.Parents, "blocksLen", len(response.Blocks), "err", err)
return nil, nil
}
return responseBytes, nil
}