Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
715c921
Add VM trace support for StateSyncTx tracing
milando12 Feb 6, 2026
7b13a88
generate mocks
milando12 Feb 6, 2026
12e21b6
Remove debug trace logs from StateSyncTx processing
milando12 Feb 12, 2026
276a404
Propagate VM config through HeaderChain for tracer access
milando12 Feb 12, 2026
2b43baf
Pass tracingStateDB to Finalize in state processor
milando12 Feb 12, 2026
afa51c2
Add vm.Config parameter to failingGenesisContract's CommitState method
milando12 Feb 23, 2026
ee7c122
Refactor StateSyncTx tracing logic to instantiate only if tracing is …
milando12 Feb 23, 2026
f1e065e
Remove unused comment in StateSyncTx tracing initialization logic
milando12 Feb 23, 2026
9e48c5e
trigger PR update
milando12 Feb 24, 2026
8ddbaad
trigger PR update
milando12 Feb 24, 2026
911e13f
Merge upstream/develop
milando12 Feb 25, 2026
fbbc506
Add `vm.Config` parameter to methods for improved VM tracing support
milando12 Feb 27, 2026
61ffef5
Merge remote-tracking branch 'upstream/develop' into md/feat-tracer
milando12 Feb 27, 2026
8c27ab3
trigger PR update
milando12 Feb 27, 2026
52a90b4
Refactor: Extract VM config retrieval logic into `extractVMConfig` he…
milando12 Feb 27, 2026
d7d8dca
Merge remote-tracking branch 'upstream/develop' into md/feat-tracer
milando12 Mar 2, 2026
c2d6e33
eth/tracers: fix supply tracer tests to expect block rewards
milando12 Mar 2, 2026
ce855ca
merge upstream
milando12 Mar 11, 2026
bd255d4
consensus/bor: use getter for systemAddress instead of exporting vari…
milando12 Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import (

"github.com/ethereum/go-ethereum/internal/cli"
"github.com/ethereum/go-ethereum/params"

// Force-load the tracer engines to trigger registration
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to register js and native tracers here? IIUC, you're only leveraging LiveDirectory in this PR.

_ "github.com/ethereum/go-ethereum/eth/tracers/live"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
)

func main() {
Expand Down
1 change: 1 addition & 0 deletions cmd/keeper/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ require (
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/sys v0.40.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

Expand Down
61 changes: 51 additions & 10 deletions consensus/bor/bor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,19 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, w
return nil
}

// extractVMConfig retrieves the vm.Config from the chain reader, if available.
func extractVMConfig(chain consensus.ChainHeaderReader) vm.Config {
if bc, ok := chain.(*core.BlockChain); ok {
return *bc.GetVMConfig()
}
if hc, ok := chain.(*core.HeaderChain); ok {
if cfg := hc.GetVMConfig(); cfg != nil {
return *cfg
}
}
return vm.Config{}
}

// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given.
func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header, wrappedState vm.StateDB, body *types.Body, receipts []*types.Receipt) []*types.Receipt {
Expand All @@ -1186,20 +1199,37 @@ func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header,
err error
)

vmCfg := extractVMConfig(chain)

if IsSprintStart(headerNumber, c.config.CalculateSprint(headerNumber)) {
start := time.Now()
cx := statefull.ChainContext{Chain: chain, Bor: c}

// Start tracing StateSyncTx (if present in the block body)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might have integration tests locally (we maintain our own Polygon Live Tracing branch for now).

I'll check if I could add that on top of your PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds great. Feel free to add them on top of this PR whenever you're ready.

if hooks := vmCfg.Tracer; hooks != nil && hooks.OnTxStart != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like this starts a single tx trace before checkAndCommitSpan and the CommitStates loop, but statefull.ApplyMessage does not emit per-call OnTxStart/OnTxEnd hooks. Can this cause any issue on sprint blocks (span commits and multiple state-syncs) breaking tracers that keep single tx state? If that's the case, maybe the hooks need to wrap each system call, not the whole finalize.

if c.config.IsMadhugiri(header.Number) && len(body.Transactions) > 0 {
lastTx := body.Transactions[len(body.Transactions)-1]
if lastTx.Type() == types.StateSyncTxType {
vmenv := vm.NewEVM(
core.NewEVMBlockContext(header, cx, &header.Coinbase),
wrappedState, c.chainConfig, vmCfg,
)
hooks.OnTxStart(vmenv.GetVMContext(), lastTx, statefull.GetSystemAddress())
}
}
}

// check and commit span
if !c.config.IsRio(header.Number) {
if err := c.checkAndCommitSpan(wrappedState, header, cx); err != nil {
if err := c.checkAndCommitSpan(wrappedState, header, cx, vmCfg); err != nil {
log.Error("Error while committing span", "error", err)
return nil
}
}

if c.HeimdallClient != nil {
// commit states
stateSyncData, err = c.CommitStates(wrappedState, header, cx)
stateSyncData, err = c.CommitStates(wrappedState, header, cx, vmCfg)
if err != nil {
log.Error("Error while committing states", "error", err)
return nil
Expand Down Expand Up @@ -1230,7 +1260,7 @@ func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header,
return receipts
}
if lastTx.Type() == types.StateSyncTxType {
receipts = insertStateSyncTransactionAndCalculateReceipt(lastTx, header, body, wrappedState, receipts)
receipts = insertStateSyncTransactionAndCalculateReceipt(lastTx, header, body, wrappedState, receipts, vmCfg)
}
}
} else {
Expand All @@ -1241,7 +1271,7 @@ func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header,
return receipts
}

func insertStateSyncTransactionAndCalculateReceipt(stateSyncTx *types.Transaction, header *types.Header, body *types.Body, state vm.StateDB, receipts []*types.Receipt) []*types.Receipt {
func insertStateSyncTransactionAndCalculateReceipt(stateSyncTx *types.Transaction, header *types.Header, body *types.Body, state vm.StateDB, receipts []*types.Receipt, vmConfig vm.Config) []*types.Receipt {
allLogs := state.Logs()
sort.SliceStable(allLogs, func(i, j int) bool {
return allLogs[i].Index < allLogs[j].Index
Expand Down Expand Up @@ -1274,6 +1304,12 @@ func insertStateSyncTransactionAndCalculateReceipt(stateSyncTx *types.Transactio
}

stateSyncReceipt.Bloom = types.CreateBloom(stateSyncReceipt)

// End tracing for StateSyncTx
if hooks := vmConfig.Tracer; hooks != nil && hooks.OnTxEnd != nil {
hooks.OnTxEnd(stateSyncReceipt, nil)
}

receipts = append(receipts, stateSyncReceipt)

return receipts
Expand Down Expand Up @@ -1332,20 +1368,22 @@ func (c *Bor) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *typ
err error
)

vmCfg := extractVMConfig(chain)

if IsSprintStart(headerNumber, c.config.CalculateSprint(headerNumber)) {
cx := statefull.ChainContext{Chain: chain, Bor: c}

// check and commit span
if !c.config.IsRio(header.Number) {
if err = c.checkAndCommitSpan(state, header, cx); err != nil {
if err = c.checkAndCommitSpan(state, header, cx, vmCfg); err != nil {
log.Error("Error while committing span", "error", err)
return nil, nil, 0, err
}
}

if c.HeimdallClient != nil {
// commit states
stateSyncData, err = c.CommitStates(state, header, cx)
stateSyncData, err = c.CommitStates(state, header, cx, vmCfg)
if err != nil {
log.Error("Error while committing states", "error", err)
return nil, nil, 0, err
Expand All @@ -1371,7 +1409,7 @@ func (c *Bor) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *typ
StateSyncData: stateSyncData,
})
body.Transactions = append(body.Transactions, stateSyncTx)
receipts = insertStateSyncTransactionAndCalculateReceipt(stateSyncTx, header, body, state, receipts)
receipts = insertStateSyncTransactionAndCalculateReceipt(stateSyncTx, header, body, state, receipts, vmCfg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the tracer lifecycle symmetric between Finalize and FinalizeAndAssemble? I think there's a difference there wrt insertStateSyncTransactionAndCalculateReceipt calls

} else {
// set state sync
bc := chain.(core.BorStateSyncer)
Expand Down Expand Up @@ -1573,6 +1611,7 @@ func (c *Bor) checkAndCommitSpan(
state vm.StateDB,
header *types.Header,
chain core.ChainContext,
vmCfg vm.Config,
) error {
var ctx = context.Background()
headerNumber := header.Number.Uint64()
Expand All @@ -1589,7 +1628,7 @@ func (c *Bor) checkAndCommitSpan(
tempState.IntermediateRoot(false)

if c.needToCommitSpan(span, headerNumber) {
return c.FetchAndCommitSpan(ctx, span.Id+1, state, header, chain)
return c.FetchAndCommitSpan(ctx, span.Id+1, state, header, chain, vmCfg)
}

return nil
Expand Down Expand Up @@ -1628,6 +1667,7 @@ func (c *Bor) FetchAndCommitSpan(
state vm.StateDB,
header *types.Header,
chain core.ChainContext,
vmCfg vm.Config,
) error {
var (
minSpan borTypes.Span
Expand Down Expand Up @@ -1691,14 +1731,15 @@ func (c *Bor) FetchAndCommitSpan(
)
}

return c.spanner.CommitSpan(ctx, minSpan, validators, producers, state, header, chain)
return c.spanner.CommitSpan(ctx, minSpan, validators, producers, state, header, chain, vmCfg)
}

// CommitStates commit states
func (c *Bor) CommitStates(
state vm.StateDB,
header *types.Header,
chain statefull.ChainContext,
vmCfg vm.Config,
) ([]*types.StateSyncData, error) {
fetchStart := time.Now()
number := header.Number.Uint64()
Expand Down Expand Up @@ -1812,7 +1853,7 @@ func (c *Bor) CommitStates(
// we expect that this call MUST emit an event, otherwise we wouldn't make a receipt
// if the receiver address is not a contract then we'll skip the most of the execution and emitting an event as well
// https://github.com/0xPolygon/genesis-contracts/blob/master/contracts/StateReceiver.sol#L27
gasUsed, err = c.GenesisContractsClient.CommitState(eventRecord, state, header, chain)
gasUsed, err = c.GenesisContractsClient.CommitState(eventRecord, state, header, chain, vmCfg)
if err != nil {
return nil, err
}
Expand Down
34 changes: 17 additions & 17 deletions consensus/bor/bor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
func (s *fakeSpanner) GetCurrentValidatorsByBlockNrOrHash(ctx context.Context, _ rpc.BlockNumberOrHash, _ uint64) ([]*valset.Validator, error) {
return s.vals, nil
}
func (s *fakeSpanner) CommitSpan(ctx context.Context, _ borTypes.Span, _ []stakeTypes.MinimalVal, _ []stakeTypes.MinimalVal, _ vm.StateDB, _ *types.Header, _ core.ChainContext) error {
func (s *fakeSpanner) CommitSpan(ctx context.Context, _ borTypes.Span, _ []stakeTypes.MinimalVal, _ []stakeTypes.MinimalVal, _ vm.StateDB, _ *types.Header, _ core.ChainContext, _ vm.Config) error {

Check warning on line 70 in consensus/bor/bor_test.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This function has 8 parameters, which is greater than the 7 authorized.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_bor&issues=AZxwqNXtwy9t3N92pKRj&open=AZxwqNXtwy9t3N92pKRj&pullRequest=2062
if s.shouldFailCommit {
return errors.New("span commit failed")
}
Expand All @@ -80,7 +80,7 @@
// failingGenesisContract simulates GenesisContract failures
type failingGenesisContract struct{}

func (f *failingGenesisContract) CommitState(event *clerk.EventRecordWithTime, state vm.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error) {
func (f *failingGenesisContract) CommitState(event *clerk.EventRecordWithTime, state vm.StateDB, header *types.Header, chCtx statefull.ChainContext, vmCfg vm.Config) (uint64, error) {
return 0, errors.New("commit state failed")
}

Expand Down Expand Up @@ -2102,7 +2102,7 @@
},
}

receipts := insertStateSyncTransactionAndCalculateReceipt(stateSyncTx, header, body, mockState, existingReceipts)
receipts := insertStateSyncTransactionAndCalculateReceipt(stateSyncTx, header, body, mockState, existingReceipts, vm.Config{})
require.Len(t, receipts, 2)

ssReceipt := receipts[1]
Expand Down Expand Up @@ -2673,7 +2673,7 @@

h := &types.Header{Number: big.NewInt(64), ParentHash: genesis.Hash()}

err := b.FetchAndCommitSpan(context.Background(), 1, statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
err := b.FetchAndCommitSpan(context.Background(), 1, statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
}

Expand Down Expand Up @@ -2707,7 +2707,7 @@

h := &types.Header{Number: big.NewInt(64), ParentHash: genesis.Hash()}

err := b.FetchAndCommitSpan(context.Background(), 1, statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
err := b.FetchAndCommitSpan(context.Background(), 1, statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.Error(t, err)
require.Contains(t, err.Error(), "doesn't match")
}
Expand All @@ -2726,7 +2726,7 @@

h := &types.Header{Number: big.NewInt(64), ParentHash: genesis.Hash()}

err := b.FetchAndCommitSpan(context.Background(), 1, statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
err := b.FetchAndCommitSpan(context.Background(), 1, statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.Error(t, err)
}

Expand Down Expand Up @@ -2771,7 +2771,7 @@
h := &types.Header{Number: big.NewInt(16), ParentHash: genesis.Hash(), Time: genesis.Time + 32}

// CommitStates with override that sets records to 0 should skip
result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
require.Empty(t, result)
}
Expand Down Expand Up @@ -2803,7 +2803,7 @@

h := &types.Header{Number: big.NewInt(16), ParentHash: genesis.Hash(), Time: genesis.Time + 32}

result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
require.Empty(t, result) // no events
}
Expand Down Expand Up @@ -2846,7 +2846,7 @@

h := &types.Header{Number: big.NewInt(16), ParentHash: genesis.Hash(), Time: uint64(now.Unix())}

result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
require.Len(t, result, 1)
require.Equal(t, uint64(1), result[0].ID)
Expand Down Expand Up @@ -3222,7 +3222,7 @@
gasUsed uint64
}

func (m *mockGenesisContractForCommitStatesIndore) CommitState(event *clerk.EventRecordWithTime, state vm.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error) {
func (m *mockGenesisContractForCommitStatesIndore) CommitState(event *clerk.EventRecordWithTime, state vm.StateDB, header *types.Header, chCtx statefull.ChainContext, vmCfg vm.Config) (uint64, error) {
return m.gasUsed, nil
}

Expand Down Expand Up @@ -3282,7 +3282,7 @@
Time: uint64(now.Unix()),
}

result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
require.Len(t, result, 2) // both events should be processed
}
Expand Down Expand Up @@ -3336,7 +3336,7 @@
Time: uint64(time.Now().Unix()),
}

result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
result, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
require.Len(t, result, 1)
}
Expand Down Expand Up @@ -3861,7 +3861,7 @@
GasLimit: genesis.GasLimit,
}

data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
// With OverrideStateSyncRecords truncating to 0, should get empty data
require.Empty(t, data)
Expand Down Expand Up @@ -4121,7 +4121,7 @@
GasLimit: genesis.GasLimit,
}

data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
require.Empty(t, data) // truncated to 0 by range override
}
Expand Down Expand Up @@ -4160,7 +4160,7 @@
GasLimit: genesis.GasLimit,
}

data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err) // error is logged but returns empty data
require.Empty(t, data)
}
Expand Down Expand Up @@ -4210,7 +4210,7 @@
GasLimit: genesis.GasLimit,
}

data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err)
// Event ID=3 should be skipped (3 <= 5), event ID=6 should be processed
require.Len(t, data, 1)
Expand Down Expand Up @@ -4259,7 +4259,7 @@
GasLimit: genesis.GasLimit,
}

data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b})
data, err := b.CommitStates(statedb, h, statefull.ChainContext{Chain: chain.HeaderChain(), Bor: b}, vm.Config{})
require.NoError(t, err) // validation error is logged but returned data should be empty
require.Empty(t, data)
}
Expand Down
3 changes: 2 additions & 1 deletion consensus/bor/contract/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (gc *GenesisContractsClient) CommitState(
state vm.StateDB,
header *types.Header,
chCtx statefull.ChainContext,
vmCfg vm.Config,
) (uint64, error) {
eventRecord := event.BuildEventRecord()

Expand All @@ -96,7 +97,7 @@ func (gc *GenesisContractsClient) CommitState(

log.Info("→ committing new state", "eventRecord", event.ID)

gasUsed, err := statefull.ApplyMessage(context.Background(), msg, state, header, gc.chainConfig, chCtx)
gasUsed, err := statefull.ApplyMessage(context.Background(), msg, state, header, gc.chainConfig, chCtx, vmCfg)

// Logging event log with time and individual gasUsed
log.Info("→ committed new state", "eventRecord", event.String(gasUsed))
Expand Down
2 changes: 1 addition & 1 deletion consensus/bor/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ import (

//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract
type GenesisContract interface {
CommitState(event *clerk.EventRecordWithTime, state vm.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error)
CommitState(event *clerk.EventRecordWithTime, state vm.StateDB, header *types.Header, chCtx statefull.ChainContext, vmCfg vm.Config) (uint64, error)
LastStateId(state *state.StateDB, number uint64, hash common.Hash) (*big.Int, error)
}
8 changes: 4 additions & 4 deletions consensus/bor/genesis_contract_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading