Skip to content

Commit 9aca5f2

Browse files
authored
Add logs filtering method type to ethutil and ignore zero gas logs option to ethkit block (#193)
* Add filtering to logs bloom check for gas price * Update ethutil with optional LogsBloomCheckFunc * Expose logsToBloom * Add LogsFilterFunc signature and sample implementation instead of optional validation method * Enforce function types * Remove func signature from validate * Enforce LogsBloomCheckFunc signature * Enforce type on LogsFilterFunc
1 parent 2344cb5 commit 9aca5f2

File tree

3 files changed

+87
-22
lines changed

3 files changed

+87
-22
lines changed

cmd/ethkit/block.go

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import (
1818
)
1919

2020
const (
21-
flagBlockField = "field"
22-
flagBlockFull = "full"
23-
flagBlockRpcUrl = "rpc-url"
24-
flagBlockJson = "json"
25-
flagBlockCheckLogs = "check-logs-bloom"
21+
flagBlockField = "field"
22+
flagBlockFull = "full"
23+
flagBlockRpcUrl = "rpc-url"
24+
flagBlockJson = "json"
25+
flagBlockCheckLogs = "check-logs-bloom"
26+
flagBlockIgnoreZeroGasLogs = "ignore-zero-gas-logs"
2627
)
2728

2829
func init() {
@@ -48,6 +49,7 @@ func NewBlockCmd() *cobra.Command {
4849
cmd.Flags().StringP(flagBlockRpcUrl, "r", "", "The RPC endpoint to the blockchain node to interact with")
4950
cmd.Flags().BoolP(flagBlockJson, "j", false, "Print the block as JSON")
5051
cmd.Flags().Bool(flagBlockCheckLogs, false, "Check logs bloom against the block header reported value")
52+
cmd.Flags().Bool(flagBlockIgnoreZeroGasLogs, false, "Ignore logs from transactions with gas price 0 when checking bloom (HyperEVM system txs)")
5153

5254
return cmd
5355
}
@@ -74,6 +76,10 @@ func (c *block) Run(cmd *cobra.Command, args []string) error {
7476
if err != nil {
7577
return err
7678
}
79+
fIgnoreZeroGasLogs, err := cmd.Flags().GetBool(flagBlockIgnoreZeroGasLogs)
80+
if err != nil {
81+
return err
82+
}
7783

7884
if _, err = url.ParseRequestURI(fRpc); err != nil {
7985
return errors.New("error: please provide a valid rpc url (e.g. https://nodes.sequence.app/mainnet)")
@@ -115,7 +121,7 @@ func (c *block) Run(cmd *cobra.Command, args []string) error {
115121
}
116122

117123
if fBlockCheckLogs {
118-
CheckLogs(block, provider)
124+
CheckLogs(block, provider, fIgnoreZeroGasLogs)
119125
}
120126

121127
fmt.Fprintln(cmd.OutOrStdout(), obj)
@@ -268,7 +274,7 @@ func (b *Block) String() string {
268274
}
269275

270276
// CheckLogs verifies that the logs bloom and logs hash in the block header match the actual logs
271-
func CheckLogs(block *types.Block, provider *ethrpc.Provider) {
277+
func CheckLogs(block *types.Block, provider *ethrpc.Provider, ignoreZeroGasLogs bool) {
272278
h, err := provider.HeaderByNumber(context.Background(), block.Number())
273279

274280
if err != nil {
@@ -284,23 +290,38 @@ func CheckLogs(block *types.Block, provider *ethrpc.Provider) {
284290
fmt.Println("Error getting logs:", err)
285291
}
286292

293+
filteredLogs := logs
294+
if ignoreZeroGasLogs {
295+
filteredLogs = zeroGasLogsFilter(logs, h, block)
296+
}
297+
287298
fmt.Printf("Block: %d\n", h.Number.Uint64())
288-
fmt.Printf("Logs Count: %d\n", len(logs))
289-
fmt.Printf("Match: %v\n", ethutil.ValidateLogsWithBlockHeader(logs, h))
299+
fmt.Printf("Logs Count: %d\n", len(filteredLogs))
300+
fmt.Printf("Match: %v\n", ethutil.ValidateLogsWithBlockHeader(filteredLogs, h))
290301
fmt.Println()
291-
fmt.Printf("Calculated Log Bloom: 0x%x\n", logsToBloom(logs).Bytes())
302+
fmt.Printf("Calculated Log Bloom: 0x%x\n", ethutil.ConvertLogsToBloom(filteredLogs).Bytes())
292303
fmt.Println()
293304
fmt.Printf("Header Log Bloom: 0x%x\n", h.Bloom.Bytes())
294305
fmt.Println()
295306
}
296307

297-
func logsToBloom(logs []types.Log) types.Bloom {
298-
var logBloom types.Bloom
299-
for _, log := range logs {
300-
logBloom.Add(log.Address.Bytes())
301-
for _, b := range log.Topics {
302-
logBloom.Add(b[:])
308+
// zeroGasLogsFilter removes logs from transactions whose gas price is zero
309+
// (HyperEVM system transactions).
310+
var _ ethutil.LogsFilterFunc = zeroGasLogsFilter
311+
312+
func zeroGasLogsFilter(ls []types.Log, _ *types.Header, block *types.Block) []types.Log {
313+
gasPriceByTx := make(map[common.Hash]*big.Int, len(block.Transactions()))
314+
for _, tx := range block.Transactions() {
315+
gasPriceByTx[tx.Hash()] = tx.GasPrice()
316+
}
317+
318+
out := make([]types.Log, 0, len(ls))
319+
for _, l := range ls {
320+
if gp, ok := gasPriceByTx[l.TxHash]; ok && gp.Sign() == 0 {
321+
// HyperEVM system tx (gas price = 0) — ignore for bloom validation.
322+
continue
303323
}
324+
out = append(out, l)
304325
}
305-
return logBloom
326+
return out
306327
}

ethutil/validate_logs_with_block.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,23 @@ import (
66
"github.com/0xsequence/ethkit/go-ethereum/core/types"
77
)
88

9-
// ValidateLogsWithBlockHeader validates that the logs comes from given block.
10-
// If the list of logs is not complete or the logs are not from the block, it
11-
// will return false.
9+
// LogsBloomCheckFunc is the shape of a logs bloom validation function.
10+
// Returning true means the logs match the header; false means they do not.
11+
type LogsBloomCheckFunc func(logs []types.Log, header *types.Header) bool
12+
13+
// LogsFilterFunc transforms or filters logs before validation.
14+
// The block is provided for cases where filtering depends on transaction data.
15+
type LogsFilterFunc func(logs []types.Log, header *types.Header, block *types.Block) []types.Log
16+
17+
// ValidateLogsWithBlockHeader validates that the logs come from the given block
18+
// by comparing the calculated bloom against the header bloom.
19+
var _ LogsBloomCheckFunc = ValidateLogsWithBlockHeader
20+
1221
func ValidateLogsWithBlockHeader(logs []types.Log, header *types.Header) bool {
13-
return bytes.Compare(logsToBloom(logs).Bytes(), header.Bloom.Bytes()) == 0
22+
return bytes.Equal(ConvertLogsToBloom(logs).Bytes(), header.Bloom.Bytes())
1423
}
1524

16-
func logsToBloom(logs []types.Log) types.Bloom {
25+
func ConvertLogsToBloom(logs []types.Log) types.Bloom {
1726
var logBloom types.Bloom
1827
for _, log := range logs {
1928
logBloom.Add(log.Address.Bytes())

ethutil/validate_logs_with_block_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package ethutil
22

33
import (
4+
"bytes"
45
"context"
56
"math/big"
67
"testing"
78

89
"github.com/0xsequence/ethkit/ethrpc"
910
"github.com/0xsequence/ethkit/go-ethereum"
11+
"github.com/0xsequence/ethkit/go-ethereum/common"
12+
"github.com/0xsequence/ethkit/go-ethereum/core/types"
1013
"github.com/stretchr/testify/require"
1114
)
1215

@@ -26,3 +29,35 @@ func TestValidateLogsWithBlockHeader(t *testing.T) {
2629

2730
require.True(t, ValidateLogsWithBlockHeader(logs, header))
2831
}
32+
33+
func TestValidateLogsWithBlockHeaderWithFilter(t *testing.T) {
34+
logs := []types.Log{
35+
{
36+
Address: common.HexToAddress("0x0000000000000000000000000000000000000001"),
37+
Topics: []common.Hash{common.HexToHash("0x01")},
38+
},
39+
{
40+
Address: common.HexToAddress("0x0000000000000000000000000000000000000002"),
41+
Topics: []common.Hash{common.HexToHash("0x02")},
42+
},
43+
}
44+
45+
headerFull := &types.Header{Bloom: ConvertLogsToBloom(logs)}
46+
headerFiltered := &types.Header{Bloom: ConvertLogsToBloom(logs[1:])}
47+
48+
require.True(t, ValidateLogsWithBlockHeader(logs, headerFull))
49+
require.False(t, ValidateLogsWithBlockHeader(logs, headerFiltered))
50+
51+
var filter LogsFilterFunc = func(ls []types.Log, _ *types.Header, _ *types.Block) []types.Log {
52+
// Ignore the first log (e.g., system tx) and validate bloom against the remainder.
53+
return ls[1:]
54+
}
55+
filteredLogs := filter(logs, headerFiltered, nil)
56+
57+
require.True(t, ValidateLogsWithBlockHeader(filteredLogs, headerFiltered))
58+
59+
var customCheck LogsBloomCheckFunc = func(ls []types.Log, header *types.Header) bool {
60+
return bytes.Equal(ConvertLogsToBloom(ls[1:]).Bytes(), header.Bloom.Bytes())
61+
}
62+
require.True(t, customCheck(logs, headerFiltered))
63+
}

0 commit comments

Comments
 (0)