Skip to content

Commit efe00ca

Browse files
committed
Update ethutil with optional LogsBloomCheckFunc
1 parent 22a11f1 commit efe00ca

File tree

3 files changed

+81
-22
lines changed

3 files changed

+81
-22
lines changed

cmd/ethkit/block.go

Lines changed: 40 additions & 20 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,24 +290,38 @@ func CheckLogs(block *types.Block, provider *ethrpc.Provider) {
284290
fmt.Println("Error getting logs:", err)
285291
}
286292

287-
// Build a quick lookup of tx hash -> gas price so we can drop system (zero price) tx logs.
288-
gasPriceByTx := make(map[common.Hash]*big.Int, len(block.Transactions()))
289-
for _, tx := range block.Transactions() {
290-
gasPriceByTx[tx.Hash()] = tx.GasPrice()
291-
}
293+
filteredLogs := logs
294+
var optCheck []ethutil.LogsBloomCheckFunc
292295

293-
filteredLogs := make([]types.Log, 0, len(logs))
294-
for _, log := range logs {
295-
if gp, ok := gasPriceByTx[log.TxHash]; ok && gp.Sign() == 0 {
296-
// HyperEVM system tx (gas price = 0) — ignore for bloom validation.
297-
continue
296+
if ignoreZeroGasLogs {
297+
// Build a quick lookup of tx hash -> gas price so we can drop system (zero price) tx logs.
298+
gasPriceByTx := make(map[common.Hash]*big.Int, len(block.Transactions()))
299+
for _, tx := range block.Transactions() {
300+
gasPriceByTx[tx.Hash()] = tx.GasPrice()
298301
}
299-
filteredLogs = append(filteredLogs, log)
302+
303+
filter := func(ls []types.Log) []types.Log {
304+
out := make([]types.Log, 0, len(ls))
305+
for _, l := range ls {
306+
if gp, ok := gasPriceByTx[l.TxHash]; ok && gp.Sign() == 0 {
307+
// HyperEVM system tx (gas price = 0) — ignore for bloom validation.
308+
continue
309+
}
310+
out = append(out, l)
311+
}
312+
return out
313+
}
314+
315+
filteredLogs = filter(logs)
316+
317+
optCheck = append(optCheck, func(ls []types.Log, header *types.Header) bool {
318+
return ethutil.ValidateLogsWithBlockHeader(filter(ls), header)
319+
})
300320
}
301321

302322
fmt.Printf("Block: %d\n", h.Number.Uint64())
303-
fmt.Printf("Logs Count (after filtering zero gas price txs): %d\n", len(filteredLogs))
304-
fmt.Printf("Match: %v\n", ethutil.ValidateLogsWithBlockHeader(filteredLogs, h))
323+
fmt.Printf("Logs Count: %d\n", len(filteredLogs))
324+
fmt.Printf("Match: %v\n", ethutil.ValidateLogsWithBlockHeader(filteredLogs, h, optCheck...))
305325
fmt.Println()
306326
fmt.Printf("Calculated Log Bloom: 0x%x\n", logsToBloom(filteredLogs).Bytes())
307327
fmt.Println()

ethutil/validate_logs_with_block.go

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

9+
// LogsBloomCheckFunc allows callers to override how logs bloom validation is performed.
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+
913
// ValidateLogsWithBlockHeader validates that the logs comes from given block.
1014
// If the list of logs is not complete or the logs are not from the block, it
1115
// will return false.
12-
func ValidateLogsWithBlockHeader(logs []types.Log, header *types.Header) bool {
13-
return bytes.Compare(logsToBloom(logs).Bytes(), header.Bloom.Bytes()) == 0
16+
func ValidateLogsWithBlockHeader(logs []types.Log, header *types.Header, optLogsBloomCheck ...LogsBloomCheckFunc) bool {
17+
// Allow callers to override the check logic (e.g. filtering certain logs).
18+
if len(optLogsBloomCheck) > 0 && optLogsBloomCheck[0] != nil {
19+
return optLogsBloomCheck[0](logs, header)
20+
}
21+
22+
return bytes.Equal(logsToBloom(logs).Bytes(), header.Bloom.Bytes())
1423
}
1524

1625
func logsToBloom(logs []types.Log) types.Bloom {

ethutil/validate_logs_with_block_test.go

Lines changed: 30 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,30 @@ func TestValidateLogsWithBlockHeader(t *testing.T) {
2629

2730
require.True(t, ValidateLogsWithBlockHeader(logs, header))
2831
}
32+
33+
func TestValidateLogsWithBlockHeaderWithCustomCheck(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: logsToBloom(logs)}
46+
headerFiltered := &types.Header{Bloom: logsToBloom(logs[1:])}
47+
48+
require.True(t, ValidateLogsWithBlockHeader(logs, headerFull))
49+
require.False(t, ValidateLogsWithBlockHeader(logs, headerFiltered))
50+
51+
customCheck := func(ls []types.Log, header *types.Header) bool {
52+
// Ignore the first log (e.g., system tx) and validate bloom against the remainder.
53+
filtered := ls[1:]
54+
return bytes.Equal(logsToBloom(filtered).Bytes(), header.Bloom.Bytes())
55+
}
56+
57+
require.True(t, ValidateLogsWithBlockHeader(logs, headerFiltered, customCheck))
58+
}

0 commit comments

Comments
 (0)