Skip to content

Commit

Permalink
Merge pull request #2788 from OffchainLabs/express-lane-timeboost-ear…
Browse files Browse the repository at this point in the history
…ly-submission-grace

Add early timeboost submission grace period
  • Loading branch information
Tristan-Wilson authored Dec 11, 2024
2 parents 4863402 + 6b8fd67 commit e1344f7
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 13 deletions.
4 changes: 3 additions & 1 deletion cmd/nitro/nitro.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,9 @@ func mainImpl() int {
execNode.Backend.APIBackend(),
execNode.FilterSystem,
common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctionContractAddress),
common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctioneerAddress))
common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctioneerAddress),
execNodeConfig.Sequencer.Timeboost.EarlySubmissionGrace,
)
}

err = nil
Expand Down
23 changes: 20 additions & 3 deletions execution/gethexec/express_lane_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type expressLaneService struct {
initialTimestamp time.Time
roundDuration time.Duration
auctionClosing time.Duration
earlySubmissionGrace time.Duration
chainConfig *params.ChainConfig
logs chan []*types.Log
auctionContract *express_lane_auctiongen.ExpressLaneAuction
Expand Down Expand Up @@ -128,6 +129,7 @@ func newExpressLaneService(
filterSystem *filters.FilterSystem,
auctionContractAddr common.Address,
bc *core.BlockChain,
earlySubmissionGrace time.Duration,
) (*expressLaneService, error) {
chainConfig := bc.Config()

Expand Down Expand Up @@ -167,6 +169,7 @@ pending:
chainConfig: chainConfig,
initialTimestamp: initialTimestamp,
auctionClosing: auctionClosingDuration,
earlySubmissionGrace: earlySubmissionGrace,
roundControl: lru.NewCache[uint64, *expressLaneControl](8), // Keep 8 rounds cached.
auctionContractAddr: auctionContractAddr,
roundDuration: roundDuration,
Expand Down Expand Up @@ -397,9 +400,23 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu
if msg.AuctionContractAddress != es.auctionContractAddr {
return errors.Wrapf(timeboost.ErrWrongAuctionContract, "msg auction contract address %s does not match sequencer auction contract address %s", msg.AuctionContractAddress, es.auctionContractAddr)
}
currentRound := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration)
if msg.Round != currentRound {
return errors.Wrapf(timeboost.ErrBadRoundNumber, "express lane tx round %d does not match current round %d", msg.Round, currentRound)

for {
currentRound := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration)
if msg.Round == currentRound {
break
}

currentTime := time.Now()
if msg.Round == currentRound+1 &&
timeboost.TimeTilNextRoundAfterTimestamp(es.initialTimestamp, currentTime, es.roundDuration) <= es.earlySubmissionGrace {
// If it becomes the next round in between checking the currentRound
// above, and here, then this will be a negative duration which is
// treated as time.Sleep(0), which is fine.
time.Sleep(timeboost.TimeTilNextRoundAfterTimestamp(es.initialTimestamp, currentTime, es.roundDuration))
} else {
return errors.Wrapf(timeboost.ErrBadRoundNumber, "express lane tx round %d does not match current round %d", msg.Round, currentRound)
}
}
if !es.currentRoundHasController() {
return timeboost.ErrNoOnchainController
Expand Down
56 changes: 51 additions & 5 deletions execution/gethexec/express_lane_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ import (
"github.com/offchainlabs/nitro/timeboost"
)

var testPriv *ecdsa.PrivateKey
var testPriv, testPriv2 *ecdsa.PrivateKey

func init() {
privKey, err := crypto.HexToECDSA("93be75cc4df7acbb636b6abe6de2c0446235ac1dc7da9f290a70d83f088b486d")
if err != nil {
panic(err)
}
testPriv = privKey
privKey2, err := crypto.HexToECDSA("93be75cc4df7acbb636b6abe6de2c0446235ac1dc7da9f290a70d83f088b486e")
if err != nil {
panic(err)
}
testPriv2 = privKey2
}

func Test_expressLaneService_validateExpressLaneTx(t *testing.T) {
Expand Down Expand Up @@ -195,7 +200,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) {
control: expressLaneControl{
controller: common.Address{'b'},
},
sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv),
sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv, 0),
expectedErr: timeboost.ErrNotExpressLaneController,
},
{
Expand All @@ -212,7 +217,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) {
control: expressLaneControl{
controller: crypto.PubkeyToAddress(testPriv.PublicKey),
},
sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv),
sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv, 0),
valid: true,
},
}
Expand All @@ -233,6 +238,46 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) {
}
}

func Test_expressLaneService_validateExpressLaneTx_gracePeriod(t *testing.T) {
auctionContractAddr := common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6")
es := &expressLaneService{
auctionContractAddr: auctionContractAddr,
initialTimestamp: time.Now(),
roundDuration: time.Second * 10,
auctionClosing: time.Second * 5,
earlySubmissionGrace: time.Second * 2,
chainConfig: &params.ChainConfig{
ChainID: big.NewInt(1),
},
roundControl: lru.NewCache[uint64, *expressLaneControl](8),
}
es.roundControl.Add(0, &expressLaneControl{
controller: crypto.PubkeyToAddress(testPriv.PublicKey),
})
es.roundControl.Add(1, &expressLaneControl{
controller: crypto.PubkeyToAddress(testPriv2.PublicKey),
})

sub1 := buildValidSubmission(t, auctionContractAddr, testPriv, 0)
err := es.validateExpressLaneTx(sub1)
require.NoError(t, err)

// Send req for next round
sub2 := buildValidSubmission(t, auctionContractAddr, testPriv2, 1)
err = es.validateExpressLaneTx(sub2)
require.ErrorIs(t, err, timeboost.ErrBadRoundNumber)

// Sleep til 2 seconds before grace
time.Sleep(time.Second * 6)
err = es.validateExpressLaneTx(sub2)
require.ErrorIs(t, err, timeboost.ErrBadRoundNumber)

// Send req for next round within grace period
time.Sleep(time.Second * 2)
err = es.validateExpressLaneTx(sub2)
require.NoError(t, err)
}

type stubPublisher struct {
els *expressLaneService
publishedTxOrder []uint64
Expand Down Expand Up @@ -463,7 +508,7 @@ func Benchmark_expressLaneService_validateExpressLaneTx(b *testing.B) {
sequence: 1,
controller: addr,
})
sub := buildValidSubmission(b, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv)
sub := buildValidSubmission(b, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv, 0)
b.StartTimer()
for i := 0; i < b.N; i++ {
err := es.validateExpressLaneTx(sub)
Expand Down Expand Up @@ -512,13 +557,14 @@ func buildValidSubmission(
t testing.TB,
auctionContractAddr common.Address,
privKey *ecdsa.PrivateKey,
round uint64,
) *timeboost.ExpressLaneSubmission {
b := &timeboost.ExpressLaneSubmission{
ChainId: big.NewInt(1),
AuctionContractAddress: auctionContractAddr,
Transaction: types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil),
Signature: make([]byte, 65),
Round: 0,
Round: round,
}
data, err := b.ToMessageBytes()
require.NoError(t, err)
Expand Down
16 changes: 15 additions & 1 deletion execution/gethexec/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,17 @@ type TimeboostConfig struct {
AuctionContractAddress string `koanf:"auction-contract-address"`
AuctioneerAddress string `koanf:"auctioneer-address"`
ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"`
SequencerHTTPEndpoint string `koanf:"sequencer-http-endpoint"`
EarlySubmissionGrace time.Duration `koanf:"early-submission-grace"`
}

var DefaultTimeboostConfig = TimeboostConfig{
Enable: false,
AuctionContractAddress: "",
AuctioneerAddress: "",
ExpressLaneAdvantage: time.Millisecond * 200,
SequencerHTTPEndpoint: "http://localhost:8547",
EarlySubmissionGrace: time.Second * 2,
}

func (c *SequencerConfig) Validate() error {
Expand Down Expand Up @@ -188,6 +192,8 @@ func TimeboostAddOptions(prefix string, f *flag.FlagSet) {
f.String(prefix+".auction-contract-address", DefaultTimeboostConfig.AuctionContractAddress, "Address of the proxy pointing to the ExpressLaneAuction contract")
f.String(prefix+".auctioneer-address", DefaultTimeboostConfig.AuctioneerAddress, "Address of the Timeboost Autonomous Auctioneer")
f.Duration(prefix+".express-lane-advantage", DefaultTimeboostConfig.ExpressLaneAdvantage, "specify the express lane advantage")
f.String(prefix+".sequencer-http-endpoint", DefaultTimeboostConfig.SequencerHTTPEndpoint, "this sequencer's http endpoint")
f.Duration(prefix+".early-submission-grace", DefaultTimeboostConfig.EarlySubmissionGrace, "period of time before the next round where submissions for the next round will be queued")
}

type txQueueItem struct {
Expand Down Expand Up @@ -1274,7 +1280,14 @@ func (s *Sequencer) Start(ctxIn context.Context) error {
return nil
}

func (s *Sequencer) StartExpressLane(ctx context.Context, apiBackend *arbitrum.APIBackend, filterSystem *filters.FilterSystem, auctionContractAddr common.Address, auctioneerAddr common.Address) {
func (s *Sequencer) StartExpressLane(
ctx context.Context,
apiBackend *arbitrum.APIBackend,
filterSystem *filters.FilterSystem,
auctionContractAddr common.Address,
auctioneerAddr common.Address,
earlySubmissionGrace time.Duration,
) {
if !s.config().Timeboost.Enable {
log.Crit("Timeboost is not enabled, but StartExpressLane was called")
}
Expand All @@ -1285,6 +1298,7 @@ func (s *Sequencer) StartExpressLane(ctx context.Context, apiBackend *arbitrum.A
filterSystem,
auctionContractAddr,
s.execEngine.bc,
earlySubmissionGrace,
)
if err != nil {
log.Crit("Failed to create express lane service", "err", err, "auctionContractAddr", auctionContractAddr)
Expand Down
2 changes: 1 addition & 1 deletion system_tests/timeboost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ func setupExpressLaneAuction(
// This is hacky- we are manually starting the ExpressLaneService here instead of letting it be started
// by the sequencer. This is due to needing to deploy the auction contract first.
builderSeq.execConfig.Sequencer.Timeboost.Enable = true
builderSeq.L2.ExecNode.Sequencer.StartExpressLane(ctx, builderSeq.L2.ExecNode.Backend.APIBackend(), builderSeq.L2.ExecNode.FilterSystem, proxyAddr, seqInfo.GetAddress("AuctionContract"))
builderSeq.L2.ExecNode.Sequencer.StartExpressLane(ctx, builderSeq.L2.ExecNode.Backend.APIBackend(), builderSeq.L2.ExecNode.FilterSystem, proxyAddr, seqInfo.GetAddress("AuctionContract"), gethexec.DefaultTimeboostConfig.EarlySubmissionGrace)
t.Log("Started express lane service in sequencer")

// Set up an autonomous auction contract service that runs in the background in this test.
Expand Down
16 changes: 14 additions & 2 deletions timeboost/ticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,15 @@ func (t *auctionCloseTicker) start() {

// CurrentRound returns the current round number.
func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) uint64 {
return RoundAtTimestamp(initialRoundTimestamp, time.Now(), roundDuration)
}

// CurrentRound returns the round number as of some timestamp.
func RoundAtTimestamp(initialRoundTimestamp time.Time, currentTime time.Time, roundDuration time.Duration) uint64 {
if roundDuration == 0 {
return 0
}
return arbmath.SaturatingUCast[uint64](time.Since(initialRoundTimestamp) / roundDuration)
return arbmath.SaturatingUCast[uint64](currentTime.Sub(initialRoundTimestamp) / roundDuration)
}

func isAuctionRoundClosed(
Expand Down Expand Up @@ -81,7 +86,14 @@ func timeIntoRound(
func TimeTilNextRound(
initialTimestamp time.Time,
roundDuration time.Duration) time.Duration {
currentRoundNum := CurrentRound(initialTimestamp, roundDuration)
return TimeTilNextRoundAfterTimestamp(initialTimestamp, time.Now(), roundDuration)
}

func TimeTilNextRoundAfterTimestamp(
initialTimestamp time.Time,
currentTime time.Time,
roundDuration time.Duration) time.Duration {
currentRoundNum := RoundAtTimestamp(initialTimestamp, currentTime, roundDuration)
nextRoundStart := initialTimestamp.Add(roundDuration * arbmath.SaturatingCast[time.Duration](currentRoundNum+1))
return time.Until(nextRoundStart)
}

0 comments on commit e1344f7

Please sign in to comment.