|
| 1 | +package etherman |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "math/big" |
| 7 | + |
| 8 | + aggkitcommon "github.com/agglayer/aggkit/common" |
| 9 | + commontypes "github.com/agglayer/aggkit/common/types" |
| 10 | + "github.com/agglayer/aggkit/log" |
| 11 | + aggkittypes "github.com/agglayer/aggkit/types" |
| 12 | + "github.com/ethereum/go-ethereum/ethclient" |
| 13 | + "github.com/ethereum/go-ethereum/rpc" |
| 14 | +) |
| 15 | + |
| 16 | +var _ aggkittypes.EthClienter = (*DefaultEthClient)(nil) |
| 17 | + |
| 18 | +type DefaultEthClient struct { |
| 19 | + aggkittypes.EthereumClienter |
| 20 | + aggkittypes.RPCClienter |
| 21 | + |
| 22 | + // If true, the block Hash is getted from JSON RPC |
| 23 | + // if false, the block Hash is getted from go-ethereum RLP hashing of header |
| 24 | + HashFromJSON bool |
| 25 | +} |
| 26 | + |
| 27 | +// DialWithRetry attempts to connect to an Ethereum client with retries and exponential backoff. |
| 28 | +// It returns an EthClienter on success or an error if all attempts fail. |
| 29 | +func DialWithRetry(ctx context.Context, url string, retryHandler commontypes.RetryHandler) (aggkittypes.EthClienter, error) { |
| 30 | + return aggkitcommon.Execute(retryHandler, ctx, log.Infof, fmt.Sprintf("dial %s rpc", url), |
| 31 | + func() (aggkittypes.EthClienter, error) { |
| 32 | + client, err := ethclient.Dial(url) |
| 33 | + if err != nil { |
| 34 | + return nil, err |
| 35 | + } |
| 36 | + return NewDefaultEthClient(client, client.Client()), nil |
| 37 | + }) |
| 38 | +} |
| 39 | + |
| 40 | +func NewDefaultEthClient(client aggkittypes.EthereumClienter, rpcClient aggkittypes.RPCClienter) *DefaultEthClient { |
| 41 | + if rpcClient == nil { |
| 42 | + rpcClient = &NoopRPCClient{} |
| 43 | + } |
| 44 | + return &DefaultEthClient{ |
| 45 | + EthereumClienter: client, |
| 46 | + RPCClienter: rpcClient, |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +func (c *DefaultEthClient) CustomBlockNumber(ctx context.Context, number aggkittypes.BlockName) (uint64, error) { |
| 51 | + ethHeader, err := c.EthereumClienter.HeaderByNumber(ctx, number.ToBigInt()) |
| 52 | + if err != nil { |
| 53 | + return 0, err |
| 54 | + } |
| 55 | + return ethHeader.Number.Uint64(), nil |
| 56 | +} |
| 57 | + |
| 58 | +func (c *DefaultEthClient) CustomHeaderByNumber(ctx context.Context, number *aggkittypes.BlockNumberFinality) (*aggkittypes.BlockHeader, error) { |
| 59 | + if number == nil { |
| 60 | + number = &aggkittypes.LatestBlock |
| 61 | + } |
| 62 | + // The number can have an offset, so maybe we need to resolve the blockName, apply offset to require the header |
| 63 | + numberBigInt, err := c.resolveBlockNumber(ctx, number) |
| 64 | + if err != nil { |
| 65 | + return nil, err |
| 66 | + } |
| 67 | + |
| 68 | + if c.HashFromJSON { |
| 69 | + rpcGetBlockByNumber, err := c.rpcGetBlockByNumber(ctx, numberBigInt) |
| 70 | + if err != nil { |
| 71 | + return nil, err |
| 72 | + } |
| 73 | + return rpcGetBlockByNumber, nil |
| 74 | + } |
| 75 | + |
| 76 | + ethHeader, err := c.EthereumClienter.HeaderByNumber(ctx, numberBigInt) |
| 77 | + if err != nil { |
| 78 | + return nil, err |
| 79 | + } |
| 80 | + res := aggkittypes.NewBlockHeaderFromEthHeader(ethHeader) |
| 81 | + res.RequestedBlock = number |
| 82 | + return res, nil |
| 83 | + |
| 84 | +} |
| 85 | + |
| 86 | +func (c *DefaultEthClient) resolveBlockNumber(ctx context.Context, number *aggkittypes.BlockNumberFinality) (*big.Int, error) { |
| 87 | + // If is a number or don't have offset with 1 query it's enough |
| 88 | + if number.IsConstant() || !number.HasOffset() { |
| 89 | + return number.ToBigInt(), nil |
| 90 | + } |
| 91 | + // Resolve the base block number |
| 92 | + hdr, err := c.rpcGetBlockByNumber(ctx, number.ToBigInt()) |
| 93 | + if err != nil { |
| 94 | + return nil, err |
| 95 | + } |
| 96 | + num := number.CalculateBlockNumber(hdr.Number) |
| 97 | + return big.NewInt(int64(num)), nil |
| 98 | +} |
| 99 | + |
| 100 | +func (c *DefaultEthClient) rpcGetBlockByNumber(ctx context.Context, number *big.Int) (*aggkittypes.BlockHeader, error) { |
| 101 | + blockArg := rpc.BlockNumber(number.Int64()).String() |
| 102 | + fmt.Printf("rpcGetBlockByNumber: requesting block %s via JSON RPC\n", blockArg) |
| 103 | + var rawEthHeader *blockRawEth |
| 104 | + err := c.CallContext(ctx, &rawEthHeader, "eth_getBlockByNumber", blockArg, false) |
| 105 | + if err != nil { |
| 106 | + return nil, fmt.Errorf("rpcGetBlockByNumber: %w", err) |
| 107 | + } |
| 108 | + return rawEthHeader.ToBlockHeader() |
| 109 | +} |
0 commit comments