Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8b038bf
Move BeforeBlock and AfterBlock into gastime
StephenButtolph Dec 5, 2025
af596f7
Replace full blocks with headers in hook.Points
StephenButtolph Dec 5, 2025
178724f
Update gas target immediately after block execution
StephenButtolph Dec 5, 2025
19c3fb9
Add test
StephenButtolph Dec 5, 2025
3b7e488
reduce comments
StephenButtolph Dec 5, 2025
6851d5a
Update hook.Points
StephenButtolph Dec 5, 2025
0d8cb33
Merged
StephenButtolph Dec 5, 2025
d8e67f3
merged
StephenButtolph Dec 8, 2025
b4450bb
ok
StephenButtolph Dec 8, 2025
f34665a
nit
StephenButtolph Dec 8, 2025
0373d0a
lint?
StephenButtolph Dec 8, 2025
94e1fb9
lint
StephenButtolph Dec 8, 2025
5c4a6e1
Update test
StephenButtolph Dec 9, 2025
16badd0
Add genesis block target override
StephenButtolph Dec 9, 2025
abe0d6d
merged
StephenButtolph Dec 9, 2025
8a8b9e2
address comments
StephenButtolph Dec 9, 2025
537bc52
re-push execution file
StephenButtolph Dec 10, 2025
ad66e07
Default to max gas target
StephenButtolph Dec 10, 2025
2339b16
Merge branch 'main' into StephenButtolph/update-target-sooner
StephenButtolph Dec 10, 2025
73f2f6c
Merge branch 'StephenButtolph/update-target-sooner' into StephenButto…
StephenButtolph Dec 10, 2025
cb79047
Merge branch 'main' into StephenButtolph/update-target-sooner
StephenButtolph Dec 10, 2025
f206ac4
Merge branch 'StephenButtolph/update-target-sooner' into StephenButto…
StephenButtolph Dec 10, 2025
67171b5
lint
StephenButtolph Dec 10, 2025
6a11f2a
Update gastime/acp176_test.go
StephenButtolph Dec 11, 2025
b7f4ce2
align test
StephenButtolph Dec 11, 2025
694b7f3
merged
StephenButtolph Dec 11, 2025
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
33 changes: 33 additions & 0 deletions gastime/acp176.go
Copy link
Collaborator

Choose a reason for hiding this comment

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

This precludes the hook package ever importing the blocks package. Not a problem for now, but flagging it so you're thinking about it. This will be OK if hooks only ever deal with Eth types, but that might not always be the case.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package gastime

import (
"fmt"

"github.com/ava-labs/avalanchego/vms/components/gas"
"github.com/ava-labs/libevm/core/types"

"github.com/ava-labs/strevm/hook"
)

// BeforeBlock is intended to be called before processing a block, with the
// timestamp sourced from [hook.Points] and [types.Header].
func (tm *Time) BeforeBlock(hooks hook.Points, h *types.Header) {
tm.FastForwardTo(
h.Time,
hooks.SubSecondBlockTime(tm.Rate(), h),
)
}

// AfterBlock is intended to be called after processing a block, with the target
// sourced from [hook.Points] and [types.Header].
func (tm *Time) AfterBlock(used gas.Gas, hooks hook.Points, h *types.Header) error {
tm.Tick(used)
target := hooks.GasTargetAfter(h)
if err := tm.SetTarget(target); err != nil {
return fmt.Errorf("%T.SetTarget() after block: %w", tm, err)
}
return nil
}
137 changes: 137 additions & 0 deletions gastime/acp176_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package gastime

import (
"testing"

"github.com/ava-labs/avalanchego/vms/components/gas"
"github.com/ava-labs/libevm/core/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ava-labs/strevm/hook/hookstest"
)

// TestTargetUpdateTiming verifies that the gas target is modified in AfterBlock
// rather than BeforeBlock.
func TestTargetUpdateTiming(t *testing.T) {
const (
initialTime = 42
initialTarget gas.Gas = 1_600_000
initialExcess = 1_234_567_890
)
tm := New(initialTime, initialTarget, initialExcess)
initialRate := tm.Rate()

const (
newTime uint64 = initialTime + 1
newTarget = initialTarget + 100_000
)
hook := &hookstest.Stub{
Target: newTarget,
}
header := &types.Header{
Time: newTime,
}

initialPrice := tm.Price()
tm.BeforeBlock(hook, header)
assert.Equal(t, newTime, tm.Unix(), "Unix time advanced by BeforeBlock()")
assert.Equal(t, initialTarget, tm.Target(), "Target not changed by BeforeBlock()")
// While the price technically could remain the same, being more strict
// ensures the test is meaningful.
enforcedPrice := tm.Price()
assert.Less(t, enforcedPrice, initialPrice, "Price should not increase in BeforeBlock()")
if t.Failed() {
t.FailNow()
}

const (
secondsOfGasUsed = 3
expectedEndTime = newTime + secondsOfGasUsed
)
used := initialRate * secondsOfGasUsed
require.NoError(t, tm.AfterBlock(used, hook, header), "AfterBlock()")
assert.Equal(t, expectedEndTime, tm.Unix(), "Unix time advanced by AfterBlock() due to gas consumption")
assert.Equal(t, newTarget, tm.Target(), "Target updated by AfterBlock()")
// While the price technically could remain the same, being more strict
// ensures the test is meaningful.
assert.Greater(t, tm.Price(), enforcedPrice, "Price should not decrease in AfterBlock()")
}

func FuzzWorstCasePrice(f *testing.F) {
f.Fuzz(func(
t *testing.T,
initTimestamp, initTarget, initExcess,
time0, timeFrac0, used0, limit0, target0,
time1, timeFrac1, used1, limit1, target1,
time2, timeFrac2, used2, limit2, target2,
time3, timeFrac3, used3, limit3, target3 uint64,
) {
initTarget = max(initTarget, 1)

worstcase := New(initTimestamp, gas.Gas(initTarget), gas.Gas(initExcess))
actual := New(initTimestamp, gas.Gas(initTarget), gas.Gas(initExcess))

blocks := []struct {
time uint64
timeFrac gas.Gas
used gas.Gas
limit gas.Gas
target gas.Gas
}{
{
time: time0,
timeFrac: gas.Gas(timeFrac0),
used: gas.Gas(used0),
limit: gas.Gas(limit0),
target: gas.Gas(target0),
},
{
time: time1,
timeFrac: gas.Gas(timeFrac1),
used: gas.Gas(used1),
limit: gas.Gas(limit1),
target: gas.Gas(target1),
},
{
time: time2,
timeFrac: gas.Gas(timeFrac2),
used: gas.Gas(used2),
limit: gas.Gas(limit2),
target: gas.Gas(target2),
},
{
time: time3,
timeFrac: gas.Gas(timeFrac3),
used: gas.Gas(used3),
limit: gas.Gas(limit3),
target: gas.Gas(target3),
},
}
for _, block := range blocks {
block.limit = max(block.used, block.limit)
block.target = clampTarget(max(block.target, 1))
block.timeFrac %= rateOf(block.target)

header := &types.Header{
Time: block.time,
}
hook := &hookstest.Stub{
Target: block.target,
SubSecondTime: block.timeFrac,
}

worstcase.BeforeBlock(hook, header)
actual.BeforeBlock(hook, header)

// The crux of this test lies in the maintaining of this inequality
// through the use of `limit` instead of `used` in `AfterBlock()`
require.LessOrEqualf(t, actual.Price(), worstcase.Price(), "actual <= worst-case %T.Price()", actual)
require.NoError(t, worstcase.AfterBlock(block.limit, hook, header), "worstcase.AfterBlock()")
require.NoError(t, actual.AfterBlock(block.used, hook, header), "actual.AfterBlock()")
}
})
}
23 changes: 0 additions & 23 deletions hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
package hook

import (
"fmt"

"github.com/ava-labs/avalanchego/vms/components/gas"
"github.com/ava-labs/libevm/core/state"
"github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/params"

"github.com/ava-labs/strevm/gastime"
"github.com/ava-labs/strevm/intmath"
saeparams "github.com/ava-labs/strevm/params"
)
Expand All @@ -37,26 +34,6 @@ type Points interface {
AfterExecutingBlock(*state.StateDB, *types.Block, types.Receipts)
}

// BeforeBlock is intended to be called before processing a block.
func BeforeBlock(pts Points, rules params.Rules, sdb *state.StateDB, b *types.Block, clock *gastime.Time) error {
clock.FastForwardTo(
b.Time(),
pts.SubSecondBlockTime(clock.Rate(), b.Header()),
)
return pts.BeforeExecutingBlock(rules, sdb, b)
}

// AfterBlock is intended to be called after processing a block.
func AfterBlock(pts Points, sdb *state.StateDB, b *types.Block, clock *gastime.Time, used gas.Gas, rs types.Receipts) error {
clock.Tick(used)
target := pts.GasTargetAfter(b.Header())
if err := clock.SetTarget(target); err != nil {
return fmt.Errorf("%T.SetTarget() after block: %w", clock, err)
}
pts.AfterExecutingBlock(sdb, b, rs)
return nil
}

// MinimumGasConsumption MUST be used as the implementation for the respective
// method on [params.RulesHooks]. The concrete type implementing the hooks MUST
// propagate incoming and return arguments unchanged.
Expand Down
68 changes: 0 additions & 68 deletions hook/hook_test.go

This file was deleted.

10 changes: 6 additions & 4 deletions hook/hookstest/stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import (

// Stub implements [hook.Points].
type Stub struct {
Target gas.Gas
Target gas.Gas
SubSecondTime gas.Gas
}

var _ hook.Points = (*Stub)(nil)
Expand All @@ -25,9 +26,10 @@ func (s *Stub) GasTargetAfter(*types.Header) gas.Gas {
return s.Target
}

// SubSecondBlockTime time ignores its arguments and always returns 0.
func (*Stub) SubSecondBlockTime(gas.Gas, *types.Header) gas.Gas {
return 0
// SubSecondBlockTime time ignores its arguments and always returns
// [Stub.SubSecondTime].
func (s *Stub) SubSecondBlockTime(gas.Gas, *types.Header) gas.Gas {
return s.SubSecondTime
}

// BeforeExecutingBlock is a no-op that always returns nil.
Expand Down
14 changes: 8 additions & 6 deletions saexec/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"go.uber.org/zap"

"github.com/ava-labs/strevm/blocks"
"github.com/ava-labs/strevm/hook"
)

var errExecutorClosed = errors.New("saexec.Executor closed")
Expand Down Expand Up @@ -103,12 +102,14 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error {
return fmt.Errorf("state.New(%#x, ...): %v", parent.PostExecutionStateRoot(), err)
}

rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime())
gasClock := parent.ExecutedByGasTime().Clone()
if err := hook.BeforeBlock(e.hooks, rules, stateDB, b.EthBlock(), gasClock); err != nil {
gasClock.BeforeBlock(e.hooks, b.Header())
perTxClock := gasClock.Time.Clone()

rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime())
if err := e.hooks.BeforeExecutingBlock(rules, stateDB, b.EthBlock()); err != nil {
return fmt.Errorf("before-block hook: %v", err)
}
perTxClock := gasClock.Time.Clone()

header := types.CopyHeader(b.Header())
header.BaseFee = gasClock.BaseFee().ToBig()
Expand Down Expand Up @@ -161,9 +162,10 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error {
// to access them before the end of the block.
receipts[ti] = receipt
}
e.hooks.AfterExecutingBlock(stateDB, b.EthBlock(), receipts)
endTime := time.Now()
if err := hook.AfterBlock(e.hooks, stateDB, b.EthBlock(), gasClock, blockGasConsumed, receipts); err != nil {
return fmt.Errorf("after-block hook: %v", err)
if err := gasClock.AfterBlock(blockGasConsumed, e.hooks, b.Header()); err != nil {
return fmt.Errorf("after-block gas time update: %w", err)
}

logger.Debug(
Expand Down
Loading