From 99253081012393acfaedd602a566c45af51b10c9 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 2 Jul 2024 12:19:47 -0500 Subject: [PATCH 001/109] added in timeboost items --- execution/gethexec/express_lane_service.go | 160 ++ go.mod | 5 +- timeboost/async.go | 22 + timeboost/auction_master.go | 183 +++ timeboost/auction_master_test.go | 39 + timeboost/bidder_client.go | 257 +++ timeboost/bids.go | 164 ++ timeboost/bids_test.go | 73 + timeboost/bindings/expresslaneauction.go | 1645 ++++++++++++++++++++ timeboost/bindings/mockerc20.go | 906 +++++++++++ timeboost/setup_test.go | 195 +++ timeboost/ticker.go | 46 + 12 files changed, 3694 insertions(+), 1 deletion(-) create mode 100644 execution/gethexec/express_lane_service.go create mode 100644 timeboost/async.go create mode 100644 timeboost/auction_master.go create mode 100644 timeboost/auction_master_test.go create mode 100644 timeboost/bidder_client.go create mode 100644 timeboost/bids.go create mode 100644 timeboost/bids_test.go create mode 100644 timeboost/bindings/expresslaneauction.go create mode 100644 timeboost/bindings/mockerc20.go create mode 100644 timeboost/setup_test.go create mode 100644 timeboost/ticker.go diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go new file mode 100644 index 0000000000..eccac5ad5d --- /dev/null +++ b/execution/gethexec/express_lane_service.go @@ -0,0 +1,160 @@ +package gethexec + +import ( + "context" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/timeboost" + "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/offchainlabs/nitro/util/stopwaiter" +) + +var _ expressLaneChecker = &expressLaneService{} + +type expressLaneChecker interface { + isExpressLaneTx(sender common.Address) bool +} + +type expressLaneControl struct { + round uint64 + controller common.Address +} + +type expressLaneService struct { + stopwaiter.StopWaiter + sync.RWMutex + client arbutil.L1Interface + control expressLaneControl + auctionContract *bindings.ExpressLaneAuction + initialTimestamp time.Time + roundDuration time.Duration +} + +func newExpressLaneService( + client arbutil.L1Interface, + auctionContractAddr common.Address, +) (*expressLaneService, error) { + auctionContract, err := bindings.NewExpressLaneAuction(auctionContractAddr, client) + if err != nil { + return nil, err + } + initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + if err != nil { + return nil, err + } + roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) + if err != nil { + return nil, err + } + initialTimestamp := time.Unix(initialRoundTimestamp.Int64(), 0) + currRound := timeboost.CurrentRound(initialTimestamp, time.Duration(roundDurationSeconds)*time.Second) + controller, err := auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(currRound))) + if err != nil { + return nil, err + } + return &expressLaneService{ + auctionContract: auctionContract, + client: client, + initialTimestamp: initialTimestamp, + control: expressLaneControl{ + controller: controller, + round: currRound, + }, + roundDuration: time.Duration(roundDurationSeconds) * time.Second, + }, nil +} + +func (es *expressLaneService) Start(ctxIn context.Context) { + es.StopWaiter.Start(ctxIn, es) + + // Log every new express lane auction round. + es.LaunchThread(func(ctx context.Context) { + log.Info("Watching for new express lane rounds") + now := time.Now() + waitTime := es.roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + time.Sleep(waitTime) + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case t := <-ticker.C: + round := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) + log.Info( + "New express lane auction round", + "round", round, + "timestamp", t, + ) + } + } + }) + es.LaunchThread(func(ctx context.Context) { + log.Info("Monitoring express lane auction contract") + // Monitor for auction resolutions from the auction manager smart contract + // and set the express lane controller for the upcoming round accordingly. + latestBlock, err := es.client.HeaderByNumber(ctx, nil) + if err != nil { + log.Crit("Could not get latest header", "err", err) + } + fromBlock := latestBlock.Number.Uint64() + ticker := time.NewTicker(time.Millisecond * 250) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + latestBlock, err := es.client.HeaderByNumber(ctx, nil) + if err != nil { + log.Error("Could not get latest header", "err", err) + continue + } + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + continue + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil) + if err != nil { + log.Error("Could not filter auction resolutions", "error", err) + continue + } + for it.Next() { + log.Info( + "New express lane controller assigned", + "round", it.Event.WinnerRound, + "controller", it.Event.WinningBidder, + ) + es.Lock() + es.control.round = it.Event.WinnerRound.Uint64() + es.control.controller = it.Event.WinningBidder + es.Unlock() + } + fromBlock = toBlock + } + } + }) + es.LaunchThread(func(ctx context.Context) { + // Monitor for auction cancelations. + // TODO: Implement. + }) +} + +func (es *expressLaneService) isExpressLaneTx(sender common.Address) bool { + es.RLock() + defer es.RUnlock() + round := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) + log.Info("Current round", "round", round, "controller", es.control.controller, "sender", sender) + return round == es.control.round && sender == es.control.controller +} diff --git a/go.mod b/go.mod index 6b350a4008..75a902fd46 100644 --- a/go.mod +++ b/go.mod @@ -39,11 +39,13 @@ require ( github.com/r3labs/diff/v3 v3.0.1 github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/wasmerio/wasmer-go v1.0.4 github.com/wealdtech/go-merkletree v1.0.0 golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.5.0 golang.org/x/sys v0.18.0 golang.org/x/term v0.18.0 golang.org/x/tools v0.16.0 @@ -136,6 +138,7 @@ require ( github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.37.0 // indirect @@ -159,9 +162,9 @@ require ( go.opencensus.io v0.22.5 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sync v0.5.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/timeboost/async.go b/timeboost/async.go new file mode 100644 index 0000000000..c28579ff48 --- /dev/null +++ b/timeboost/async.go @@ -0,0 +1,22 @@ +package timeboost + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" +) + +func receiveAsync[T any](ctx context.Context, channel chan T, f func(context.Context, T) error) { + for { + select { + case item := <-channel: + go func() { + if err := f(ctx, item); err != nil { + log.Error("Error processing item", "error", err) + } + }() + case <-ctx.Done(): + return + } + } +} diff --git a/timeboost/auction_master.go b/timeboost/auction_master.go new file mode 100644 index 0000000000..56d8526e1a --- /dev/null +++ b/timeboost/auction_master.go @@ -0,0 +1,183 @@ +package timeboost + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/pkg/errors" +) + +const defaultAuctionClosingSecondsBeforeRound = 15 // Before the start of the next round. + +type AuctionMasterOpt func(*AuctionMaster) + +func WithAuctionClosingSecondsBeforeRound(d time.Duration) AuctionMasterOpt { + return func(am *AuctionMaster) { + am.auctionClosingDurationBeforeRoundStart = d + } +} + +type AuctionMaster struct { + txOpts *bind.TransactOpts + chainId *big.Int + signatureDomain uint16 + client simulated.Client + auctionContract *bindings.ExpressLaneAuction + bidsReceiver chan *Bid + bidCache *bidCache + initialRoundTimestamp time.Time + roundDuration time.Duration + auctionClosingDurationBeforeRoundStart time.Duration +} + +func NewAuctionMaster( + txOpts *bind.TransactOpts, + chainId *big.Int, + client simulated.Client, + auctionContract *bindings.ExpressLaneAuction, + opts ...AuctionMasterOpt, +) (*AuctionMaster, error) { + initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + if err != nil { + return nil, err + } + roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) + if err != nil { + return nil, err + } + sigDomain, err := auctionContract.BidSignatureDomainValue(&bind.CallOpts{}) + if err != nil { + return nil, err + } + am := &AuctionMaster{ + txOpts: txOpts, + chainId: chainId, + client: client, + signatureDomain: sigDomain, + auctionContract: auctionContract, + bidsReceiver: make(chan *Bid, 100), + bidCache: newBidCache(), + initialRoundTimestamp: time.Unix(initialRoundTimestamp.Int64(), 0), + roundDuration: time.Duration(roundDurationSeconds) * time.Second, + auctionClosingDurationBeforeRoundStart: defaultAuctionClosingSecondsBeforeRound, + } + for _, o := range opts { + o(am) + } + return am, nil +} + +func (am *AuctionMaster) SubmitBid(ctx context.Context, b *Bid) error { + validated, err := am.newValidatedBid(b) + if err != nil { + return err + } + am.bidCache.add(validated) + return nil +} + +func (am *AuctionMaster) Start(ctx context.Context) { + // Receive bids in the background. + go receiveAsync(ctx, am.bidsReceiver, am.SubmitBid) + + // Listen for sequencer health in the background and close upcoming auctions if so. + go am.checkSequencerHealth(ctx) + + // Work on closing auctions. + ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDurationBeforeRoundStart) + go ticker.start() + for { + select { + case <-ctx.Done(): + return + case auctionClosingTime := <-ticker.c: + log.Info("Auction closing", "closingTime", auctionClosingTime) + if err := am.resolveAuctions(ctx); err != nil { + log.Error("Could not resolve auction for round", "error", err) + } + } + } +} + +func (am *AuctionMaster) resolveAuctions(ctx context.Context) error { + upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 + // If we have no winner, then we can cancel the auction. + // Auction master can also subscribe to sequencer feed and + // close auction if sequencer is down. + result := am.bidCache.topTwoBids() + first := result.firstPlace + second := result.secondPlace + var tx *types.Transaction + var err error + hasSingleBid := first != nil && second == nil + hasBothBids := first != nil && second != nil + noBids := first == nil && second == nil + + // TODO: Retry a given number of times in case of flakey connection. + switch { + case hasBothBids: + log.Info("Resolving auctions, received two bids", "round", upcomingRound, "firstRound", first.round, "secondRound", second.round) + tx, err = am.auctionContract.ResolveAuction( + am.txOpts, + bindings.Bid{ + Bidder: first.address, + ChainId: am.chainId, + Round: new(big.Int).SetUint64(first.round), + Amount: first.amount, + Signature: first.signature, + }, + bindings.Bid{ + Bidder: second.address, + ChainId: am.chainId, + Round: new(big.Int).SetUint64(second.round), + Amount: second.amount, + Signature: second.signature, + }, + ) + case hasSingleBid: + log.Info("Resolving auctions, received single bids", "round", upcomingRound) + tx, err = am.auctionContract.ResolveSingleBidAuction( + am.txOpts, + bindings.Bid{ + Bidder: first.address, + ChainId: am.chainId, + Round: new(big.Int).SetUint64(upcomingRound), + Amount: first.amount, + Signature: first.signature, + }, + ) + case noBids: + // TODO: Cancel the upcoming auction. + log.Info("No bids received for auction resolution") + return nil + } + if err != nil { + return err + } + receipt, err := bind.WaitMined(ctx, am.client, tx) + if err != nil { + return err + } + if receipt.Status != types.ReceiptStatusSuccessful { + return errors.New("deposit failed") + } + // Clear the bid cache. + am.bidCache = newBidCache() + return nil +} + +// TODO: Implement. If sequencer is down for some time, cancel the upcoming auction by calling +// the cancel method on the smart contract. +func (am *AuctionMaster) checkSequencerHealth(ctx context.Context) { + +} + +func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) uint64 { + return uint64(time.Since(initialRoundTimestamp) / roundDuration) +} diff --git a/timeboost/auction_master_test.go b/timeboost/auction_master_test.go new file mode 100644 index 0000000000..2aabf1e5f5 --- /dev/null +++ b/timeboost/auction_master_test.go @@ -0,0 +1,39 @@ +package timeboost + +import ( + "testing" +) + +type mockSequencer struct{} + +// TODO: Mock sequencer subscribes to auction resolution events to +// figure out who is the upcoming express lane auction controller and allows +// sequencing of txs from that controller in their given round. + +// Runs a simulation of an express lane auction between different parties, +// with some rounds randomly being canceled due to sequencer downtime. +func TestCompleteAuctionSimulation(t *testing.T) { + // ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10) + // defer cancel() + + // testSetup := setupAuctionTest(t, ctx) + + // // Set up two different bidders. + // alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) + // bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup) + // require.NoError(t, alice.deposit(ctx, big.NewInt(5))) + // require.NoError(t, bob.deposit(ctx, big.NewInt(5))) + + // // Set up a new auction master instance that can validate bids. + // am, err := newAuctionMaster( + // testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, + // ) + // require.NoError(t, err) + // alice.auctionMaster = am + // bob.auctionMaster = am + + // TODO: Start auction master and randomly bid from different bidders in a round. + // Start the sequencer. + // Have the winner of the express lane send txs if they detect they are the winner. + // Auction master will log any deposits that are made to the contract. +} diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go new file mode 100644 index 0000000000..85c83cf747 --- /dev/null +++ b/timeboost/bidder_client.go @@ -0,0 +1,257 @@ +package timeboost + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/pkg/errors" +) + +type sequencerConnection interface { + SendExpressLaneTx(ctx context.Context, tx *types.Transaction) error +} + +type auctionMasterConnection interface { + SubmitBid(ctx context.Context, bid *Bid) error +} + +type BidderClient struct { + chainId uint64 + name string + signatureDomain uint16 + txOpts *bind.TransactOpts + client simulated.Client + privKey *ecdsa.PrivateKey + auctionContract *bindings.ExpressLaneAuction + sequencer sequencerConnection + auctionMaster auctionMasterConnection + initialRoundTimestamp time.Time + roundDuration time.Duration +} + +// TODO: Provide a safer option. +type Wallet struct { + TxOpts *bind.TransactOpts + PrivKey *ecdsa.PrivateKey +} + +func NewBidderClient( + ctx context.Context, + name string, + wallet *Wallet, + client simulated.Client, + auctionContractAddress common.Address, + sequencer sequencerConnection, + auctionMaster auctionMasterConnection, +) (*BidderClient, error) { + chainId, err := client.ChainID(ctx) + if err != nil { + return nil, err + } + auctionContract, err := bindings.NewExpressLaneAuction(auctionContractAddress, client) + if err != nil { + return nil, err + } + sigDomain, err := auctionContract.BidSignatureDomainValue(&bind.CallOpts{}) + if err != nil { + return nil, err + } + initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + if err != nil { + return nil, err + } + roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) + if err != nil { + return nil, err + } + return &BidderClient{ + chainId: chainId.Uint64(), + name: name, + signatureDomain: sigDomain, + client: client, + txOpts: wallet.TxOpts, + privKey: wallet.PrivKey, + auctionContract: auctionContract, + sequencer: sequencer, + auctionMaster: auctionMaster, + initialRoundTimestamp: time.Unix(initialRoundTimestamp.Int64(), 0), + roundDuration: time.Duration(roundDurationSeconds) * time.Second, + }, nil +} + +func (bd *BidderClient) Start(ctx context.Context) { + // Monitor for newly assigned express lane controllers, and if the client's address + // is the controller in order to send express lane txs. + go bd.monitorAuctionResolutions(ctx) + // Monitor for auction closures by the auction master. + go bd.monitorAuctionCancelations(ctx) + // Monitor for express lane control delegations to take over if needed. + go bd.monitorExpressLaneDelegations(ctx) +} + +func (bd *BidderClient) monitorAuctionResolutions(ctx context.Context) { + winningBidders := []common.Address{bd.txOpts.From} + latestBlock, err := bd.client.HeaderByNumber(ctx, nil) + if err != nil { + panic(err) + } + fromBlock := latestBlock.Number.Uint64() + ticker := time.NewTicker(time.Millisecond * 250) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + latestBlock, err := bd.client.HeaderByNumber(ctx, nil) + if err != nil { + log.Error("Could not get latest header", "err", err) + continue + } + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + continue + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := bd.auctionContract.FilterAuctionResolved(filterOpts, winningBidders, nil) + if err != nil { + log.Error("Could not filter auction resolutions", "error", err) + continue + } + for it.Next() { + upcomingRound := CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1 + ev := it.Event + if ev.WinnerRound.Uint64() == upcomingRound { + // TODO: Log the time to next round. + log.Info( + "WON the express lane auction for next round - can send fast lane txs to sequencer", + "winner", ev.WinningBidder, + "upcomingRound", upcomingRound, + "firstPlaceBidAmount", fmt.Sprintf("%#x", ev.WinningBidAmount), + "secondPlaceBidAmount", fmt.Sprintf("%#x", ev.WinningBidAmount), + ) + } + } + fromBlock = toBlock + } + } +} + +func (bd *BidderClient) monitorAuctionCancelations(ctx context.Context) { + // TODO: Implement. +} + +func (bd *BidderClient) monitorExpressLaneDelegations(ctx context.Context) { + delegatedTo := []common.Address{bd.txOpts.From} + latestBlock, err := bd.client.HeaderByNumber(ctx, nil) + if err != nil { + panic(err) + } + fromBlock := latestBlock.Number.Uint64() + ticker := time.NewTicker(time.Millisecond * 250) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + latestBlock, err := bd.client.HeaderByNumber(ctx, nil) + if err != nil { + log.Error("Could not get latest header", "err", err) + continue + } + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + continue + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := bd.auctionContract.FilterExpressLaneControlDelegated(filterOpts, nil, delegatedTo) + if err != nil { + log.Error("Could not filter auction resolutions", "error", err) + continue + } + for it.Next() { + upcomingRound := CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1 + ev := it.Event + // TODO: Log the time to next round. + log.Info( + "Received express lane delegation for next round - can send fast lane txs to sequencer", + "delegatedFrom", ev.From, + "upcomingRound", upcomingRound, + ) + } + fromBlock = toBlock + } + } +} + +func (bd *BidderClient) sendExpressLaneTx(ctx context.Context, tx *types.Transaction) error { + return bd.sequencer.SendExpressLaneTx(ctx, tx) +} + +func (bd *BidderClient) Deposit(ctx context.Context, amount *big.Int) error { + tx, err := bd.auctionContract.SubmitDeposit(bd.txOpts, amount) + if err != nil { + return err + } + receipt, err := bind.WaitMined(ctx, bd.client, tx) + if err != nil { + return err + } + if receipt.Status != types.ReceiptStatusSuccessful { + return errors.New("deposit failed") + } + return nil +} + +func (bd *BidderClient) Bid(ctx context.Context, amount *big.Int) (*Bid, error) { + newBid := &Bid{ + chainId: bd.chainId, + address: bd.txOpts.From, + round: CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1, + amount: amount, + } + packedBidBytes, err := encodeBidValues( + bd.signatureDomain, new(big.Int).SetUint64(newBid.chainId), new(big.Int).SetUint64(newBid.round), amount, + ) + if err != nil { + return nil, err + } + sig, prefixed := sign(packedBidBytes, bd.privKey) + newBid.signature = sig + _ = prefixed + if err = bd.auctionMaster.SubmitBid(ctx, newBid); err != nil { + return nil, err + } + return newBid, nil +} + +func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, []byte) { + hash := crypto.Keccak256(message) + prefixed := crypto.Keccak256([]byte("\x19Ethereum Signed Message:\n32"), hash) + sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) + if err != nil { + panic(err) + } + return sig, prefixed +} diff --git a/timeboost/bids.go b/timeboost/bids.go new file mode 100644 index 0000000000..59cf353ee8 --- /dev/null +++ b/timeboost/bids.go @@ -0,0 +1,164 @@ +package timeboost + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/pkg/errors" +) + +var ( + ErrMalformedData = errors.New("malformed bid data") + ErrNotDepositor = errors.New("not a depositor") + ErrWrongChainId = errors.New("wrong chain id") + ErrWrongSignature = errors.New("wrong signature") + ErrBadRoundNumber = errors.New("bad round number") + ErrInsufficientBalance = errors.New("insufficient balance") +) + +type Bid struct { + chainId uint64 + address common.Address + round uint64 + amount *big.Int + signature []byte +} + +type validatedBid struct { + Bid +} + +func (am *AuctionMaster) newValidatedBid(bid *Bid) (*validatedBid, error) { + // Check basic integrity. + if bid == nil { + return nil, errors.Wrap(ErrMalformedData, "nil bid") + } + if bid.address == (common.Address{}) { + return nil, errors.Wrap(ErrMalformedData, "empty bidder address") + } + // Verify chain id. + if new(big.Int).SetUint64(bid.chainId).Cmp(am.chainId) != 0 { + return nil, errors.Wrapf(ErrWrongChainId, "wanted %#x, got %#x", am.chainId, bid.chainId) + } + // Check if for upcoming round. + upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 + if bid.round != upcomingRound { + return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.round) + } + // Check bid amount. + if bid.amount.Cmp(big.NewInt(0)) <= 0 { + return nil, errors.Wrap(ErrMalformedData, "expected a non-negative, non-zero bid amount") + } + // Validate the signature. + packedBidBytes, err := encodeBidValues( + am.signatureDomain, new(big.Int).SetUint64(bid.chainId), new(big.Int).SetUint64(bid.round), bid.amount, + ) + if err != nil { + return nil, ErrMalformedData + } + // Ethereum signatures contain the recovery id at the last byte + if len(bid.signature) != 65 { + return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") + } + // Recover the public key. + hash := crypto.Keccak256(packedBidBytes) + prefixed := crypto.Keccak256([]byte("\x19Ethereum Signed Message:\n32"), hash) + pubkey, err := crypto.SigToPub(prefixed, bid.signature) + if err != nil { + return nil, err + } + if !verifySignature(pubkey, packedBidBytes, bid.signature) { + return nil, ErrWrongSignature + } + // Validate if the user if a depositor in the contract and has enough balance for the bid. + // TODO: Retry some number of times if flakey connection. + // TODO: Validate reserve price against amount of bid. + depositBal, err := am.auctionContract.DepositBalance(&bind.CallOpts{}, bid.address) + if err != nil { + return nil, err + } + if depositBal.Cmp(new(big.Int)) == 0 { + return nil, ErrNotDepositor + } + if depositBal.Cmp(bid.amount) < 0 { + return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.amount) + } + return &validatedBid{*bid}, nil +} + +type bidCache struct { + sync.RWMutex + latestBidBySender map[common.Address]*validatedBid +} + +func newBidCache() *bidCache { + return &bidCache{ + latestBidBySender: make(map[common.Address]*validatedBid), + } +} + +func (bc *bidCache) add(bid *validatedBid) { + bc.Lock() + defer bc.Unlock() + bc.latestBidBySender[bid.address] = bid +} + +// TwoTopBids returns the top two bids for the given chain ID and round +type auctionResult struct { + firstPlace *validatedBid + secondPlace *validatedBid +} + +func (bc *bidCache) topTwoBids() *auctionResult { + bc.RLock() + defer bc.RUnlock() + result := &auctionResult{} + for _, bid := range bc.latestBidBySender { + if result.firstPlace == nil || bid.amount.Cmp(result.firstPlace.amount) > 0 { + result.secondPlace = result.firstPlace + result.firstPlace = bid + } else if result.secondPlace == nil || bid.amount.Cmp(result.secondPlace.amount) > 0 { + result.secondPlace = bid + } + } + return result +} + +func verifySignature(pubkey *ecdsa.PublicKey, message []byte, sig []byte) bool { + hash := crypto.Keccak256(message) + prefixed := crypto.Keccak256([]byte("\x19Ethereum Signed Message:\n32"), hash) + + return secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sig[:len(sig)-1]) +} + +// Helper function to pad a big integer to 32 bytes +func padBigInt(bi *big.Int) []byte { + bb := bi.Bytes() + padded := make([]byte, 32-len(bb), 32) + padded = append(padded, bb...) + return padded +} + +func encodeBidValues(domainPrefix uint16, chainId, round, amount *big.Int) ([]byte, error) { + buf := new(bytes.Buffer) + + // Encode uint16 - occupies 2 bytes + err := binary.Write(buf, binary.BigEndian, domainPrefix) + if err != nil { + return nil, err + } + + // Encode uint256 values - each occupies 32 bytes + buf.Write(padBigInt(chainId)) + buf.Write(padBigInt(round)) + buf.Write(padBigInt(amount)) + + return buf.Bytes(), nil +} diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go new file mode 100644 index 0000000000..105b77a9ae --- /dev/null +++ b/timeboost/bids_test.go @@ -0,0 +1,73 @@ +package timeboost + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" +) + +func TestWinningBidderBecomesExpressLaneController(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testSetup := setupAuctionTest(t, ctx) + + // Set up two different bidders. + alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) + bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup) + require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) + require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) + + // Set up a new auction master instance that can validate bids. + am, err := NewAuctionMaster( + testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, + ) + require.NoError(t, err) + alice.auctionMaster = am + bob.auctionMaster = am + + // Form two new bids for the round, with Alice being the bigger one. + aliceBid, err := alice.Bid(ctx, big.NewInt(2)) + require.NoError(t, err) + bobBid, err := bob.Bid(ctx, big.NewInt(1)) + require.NoError(t, err) + _, _ = aliceBid, bobBid + + // Resolve the auction. + require.NoError(t, am.resolveAuctions(ctx)) + + // Expect Alice to have become the next express lane controller. + upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 + controller, err := testSetup.auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(upcomingRound))) + require.NoError(t, err) + require.Equal(t, alice.txOpts.From, controller) +} + +func TestSubmitBid_OK(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testSetup := setupAuctionTest(t, ctx) + + // Make a deposit as a bidder into the contract. + bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) + require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) + + // Set up a new auction master instance that can validate bids. + am, err := NewAuctionMaster( + testSetup.accounts[1].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, + ) + require.NoError(t, err) + bc.auctionMaster = am + + // Form a new bid with an amount. + newBid, err := bc.Bid(ctx, big.NewInt(5)) + require.NoError(t, err) + + // Check the bid passes validation. + _, err = am.newValidatedBid(newBid) + require.NoError(t, err) +} diff --git a/timeboost/bindings/expresslaneauction.go b/timeboost/bindings/expresslaneauction.go new file mode 100644 index 0000000000..1de9bc49a9 --- /dev/null +++ b/timeboost/bindings/expresslaneauction.go @@ -0,0 +1,1645 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bindings + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// Bid is an auto generated low-level Go binding around an user-defined struct. +type Bid struct { + Bidder common.Address + ChainId *big.Int + Round *big.Int + Amount *big.Int + Signature []byte +} + +// ExpressLaneAuctionMetaData contains all meta data concerning the ExpressLaneAuction contract. +var ExpressLaneAuctionMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_chainOwnerAddr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_reservePriceSetterAddr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_bidReceiverAddr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_roundLengthSeconds\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_initialTimestamp\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_stakeToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_currentReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_minimalReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"bidReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"bidSignatureDomainValue\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint16\",\"internalType\":\"uint16\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"bidderBalance\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"cancelUpcomingRound\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"chainOwnerAddress\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"currentExpressLaneController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"currentRound\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"delegateExpressLane\",\"inputs\":[{\"name\":\"delegate\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"depositBalance\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"expressLaneControllerByRound\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"finalizeWithdrawal\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getCurrentReservePrice\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getminimalReservePrice\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialRoundTimestamp\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initiateWithdrawal\",\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pendingWithdrawalByBidder\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"submittedRound\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"reservePriceSetterAddress\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"resolveAuction\",\"inputs\":[{\"name\":\"bid1\",\"type\":\"tuple\",\"internalType\":\"structBid\",\"components\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"round\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"bid2\",\"type\":\"tuple\",\"internalType\":\"structBid\",\"components\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"round\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"resolveSingleBidAuction\",\"inputs\":[{\"name\":\"bid\",\"type\":\"tuple\",\"internalType\":\"structBid\",\"components\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"round\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"roundDurationSeconds\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setCurrentReservePrice\",\"inputs\":[{\"name\":\"_currentReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMinimalReservePrice\",\"inputs\":[{\"name\":\"_minimalReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setReservePriceAddresses\",\"inputs\":[{\"name\":\"_reservePriceSetterAddr\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"submitDeposit\",\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"verifySignature\",\"inputs\":[{\"name\":\"signer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"message\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"event\",\"name\":\"AuctionResolved\",\"inputs\":[{\"name\":\"winningBidAmount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"secondPlaceBidAmount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"winningBidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"winnerRound\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"DepositSubmitted\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ExpressLaneControlDelegated\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"round\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WithdrawalFinalized\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WithdrawalInitiated\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"IncorrectBidAmount\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InsufficientBalance\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"LessThanCurrentReservePrice\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"LessThanMinReservePrice\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotChainOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotExpressLaneController\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotReservePriceSetter\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ZeroAmount\",\"inputs\":[]}]", + Bin: "0x60806040526006805461ffff1916600f1790553480156200001f57600080fd5b5060405162001a8838038062001a888339810160408190526200004291620000ef565b600080546001600160a01b03998a166001600160a01b03199182161790915560018054988a169890911697909717909655600280546001600160401b03909516600160a01b026001600160e01b031990951695881695909517939093179093556003556004556005919091556006805491909216620100000262010000600160b01b03199091161790556200018a565b80516001600160a01b0381168114620000ea57600080fd5b919050565b600080600080600080600080610100898b0312156200010d57600080fd5b6200011889620000d2565b97506200012860208a01620000d2565b96506200013860408a01620000d2565b60608a01519096506001600160401b03811681146200015657600080fd5b60808a015190955093506200016e60a08a01620000d2565b60c08a015160e0909a0151989b979a5095989497939692505050565b6118ee806200019a6000396000f3fe608060405234801561001057600080fd5b50600436106101735760003560e01c80638296df03116100de578063cc963d1511610097578063d6e5fb7d11610071578063d6e5fb7d1461037c578063dbeb20121461038f578063f5f754d6146103a2578063f66fda64146103b557600080fd5b8063cc963d151461033e578063cd4abf7114610356578063d6ded1bc1461036957600080fd5b80638296df03146102bd5780638a19c8bc146102e6578063956501bb14610306578063b941ce6e14610326578063c03899791461032e578063c5b6aa2f1461033657600080fd5b80634d1846dc116101305780634d1846dc1461022b5780634f2a9bdb14610233578063574a9b5f1461023b5780635f70f9031461024e57806379a47e291461029b5780637c62b5cd146102ac57600080fd5b806303ba666214610178578063048fae731461018f57806312edde5e146101b857806324e359e7146101cd57806338265efd146101f05780634bc37ea614610206575b600080fd5b6005545b6040519081526020015b60405180910390f35b61017c61019d3660046115a4565b6001600160a01b031660009081526007602052604090205490565b6101cb6101c63660046115c6565b6103c8565b005b6101e06101db366004611681565b61055d565b6040519015158152602001610186565b60065460405161ffff9091168152602001610186565b6002546001600160a01b03165b6040516001600160a01b039091168152602001610186565b6101cb6105e9565b61021361066b565b6101cb6102493660046115c6565b6106a1565b61027e61025c3660046115a4565b600860205260009081526040902080546001909101546001600160401b031682565b604080519283526001600160401b03909116602083015201610186565b6000546001600160a01b0316610213565b6001546001600160a01b0316610213565b6102136102cb3660046115c6565b6009602052600090815260409020546001600160a01b031681565b6102ee6106f4565b6040516001600160401b039091168152602001610186565b61017c6103143660046115a4565b60076020526000908152604090205481565b60045461017c565b60035461017c565b6101cb61073d565b600254600160a01b90046001600160401b03166102ee565b6101cb61036436600461170c565b6108ef565b6101cb6103773660046115c6565b610f31565b6101cb61038a3660046115a4565b610f61565b6101cb61039d3660046115c6565b611024565b6101cb6103b036600461176f565b611122565b6101cb6103c33660046115a4565b611432565b806000036103e957604051631f2a200560e01b815260040160405180910390fd5b3360009081526007602052604090205481111561041957604051631e9acf1760e31b815260040160405180910390fd5b33600090815260086020908152604091829020825180840190935280548084526001909101546001600160401b031691830191909152156104a15760405162461bcd60e51b815260206004820152601c60248201527f7769746864726177616c20616c726561647920696e697469617465640000000060448201526064015b60405180910390fd5b33600090815260076020526040812080548492906104c09084906117c1565b9250508190555060405180604001604052808381526020016104e06106f4565b6001600160401b039081169091523360008181526008602090815260409182902085518155948101516001909501805467ffffffffffffffff19169590941694909417909255905184815290917f6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec64691015b60405180910390a25050565b8151602083012060009060006105c0826040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b905060006105ce828661147f565b6001600160a01b039081169088161493505050509392505050565b60006105f36106f4565b6105fe9060016117d4565b6001600160401b0381166000908152600960205260409020549091506001600160a01b0316338114610643576040516302e001e360e01b815260040160405180910390fd5b506001600160401b0316600090815260096020526040902080546001600160a01b0319169055565b6000600960006106796106f4565b6001600160401b031681526020810191909152604001600020546001600160a01b0316919050565b6001546001600160a01b031633146106cc576040516305fbc41160e01b815260040160405180910390fd5b6005548110156106ef57604051632e99443560e21b815260040160405180910390fd5b600455565b600042600354111561070c57506001600160401b0390565b600254600354600160a01b9091046001600160401b03169061072e90426117c1565b61073891906117fb565b905090565b336000908152600860209081526040808320815180830190925280548083526001909101546001600160401b03169282019290925291036107c05760405162461bcd60e51b815260206004820152601760248201527f6e6f207769746864726177616c20696e697469617465640000000000000000006044820152606401610498565b60006107ca6106f4565b9050816020015160026107dd91906117d4565b6001600160401b0316816001600160401b03161461083d5760405162461bcd60e51b815260206004820152601b60248201527f7769746864726177616c206973206e6f742066696e616c697a656400000000006044820152606401610498565b600654825160405163a9059cbb60e01b81523360048201526024810191909152620100009091046001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610896573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108ba919061181d565b50815160405190815233907f9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda890602001610551565b806020013582602001351461093f5760405162461bcd60e51b81526020600482015260166024820152750c6d0c2d2dc40d2c8e640c8de40dcdee840dac2e8c6d60531b6044820152606401610498565b806040013582604001351461098c5760405162461bcd60e51b81526020600482015260136024820152720e4deeadcc8e640c8de40dcdee840dac2e8c6d606b1b6044820152606401610498565b60006109966106f4565b6109a19060016117d4565b6001600160401b03169050808360400135146109f45760405162461bcd60e51b81526020600482015260126024820152711b9bdd081d5c18dbdb5a5b99c81c9bdd5b9960721b6044820152606401610498565b606083013560076000610a0a60208701876115a4565b6001600160a01b03166001600160a01b03168152602001908152602001600020541015610a4a5760405163017e521960e71b815260040160405180910390fd5b606082013560076000610a6060208601866115a4565b6001600160a01b03166001600160a01b03168152602001908152602001600020541015610aa05760405163017e521960e71b815260040160405180910390fd5b610b44610ab060208501856115a4565b6006546040805160f09290921b6001600160f01b031916602080840191909152870135602283015286013560428201526060860135606282015260820160408051601f19818403018152919052610b0a608087018761183f565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061055d92505050565b610b905760405162461bcd60e51b815260206004820152601f60248201527f696e76616c6964207369676e617475726520666f7220666972737420626964006044820152606401610498565b610bfa610ba060208401846115a4565b6006546040805160f09290921b6001600160f01b031916602080840191909152860135602283015285013560428201526060850135606282015260820160408051601f19818403018152919052610b0a608086018661183f565b610c465760405162461bcd60e51b815260206004820181905260248201527f696e76616c6964207369676e617475726520666f72207365636f6e64206269646044820152606401610498565b816060013583606001351115610dca57606082013560076000610c6c60208701876115a4565b6001600160a01b03166001600160a01b031681526020019081526020016000206000828254610c9b91906117c1565b909155505060065460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526060850135602482015262010000909204169063a9059cbb906044016020604051808303816000875af1158015610cfe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d22919061181d565b50610d3060208401846115a4565b60408481013560009081526009602090815291902080546001600160a01b0319166001600160a01b0393909316929092179091558190610d72908501856115a4565b6001600160a01b03167febab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef085606001358560600135604051610dbd929190918252602082015260400190565b60405180910390a3505050565b606083013560076000610de060208601866115a4565b6001600160a01b03166001600160a01b031681526020019081526020016000206000828254610e0f91906117c1565b909155505060065460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526060860135602482015262010000909204169063a9059cbb906044016020604051808303816000875af1158015610e72573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e96919061181d565b50610ea460208301836115a4565b60408381013560009081526009602090815291902080546001600160a01b0319166001600160a01b0393909316929092179091558190610ee6908401846115a4565b6001600160a01b03167febab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef084606001358660600135604051610dbd929190918252602082015260400190565b6000546001600160a01b03163314610f5c576040516311c29acf60e31b815260040160405180910390fd5b600555565b6000610f6b6106f4565b610f769060016117d4565b6001600160401b0381166000908152600960205260409020549091506001600160a01b0316338114610fbb576040516302e001e360e01b815260040160405180910390fd5b6001600160401b03821660008181526009602090815260409182902080546001600160a01b0319166001600160a01b0388169081179091559151928352909133917fdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede9101610dbd565b8060000361104557604051631f2a200560e01b815260040160405180910390fd5b336000908152600760205260408120805483929061106490849061188c565b90915550506006546040516323b872dd60e01b815233600482015230602482015260448101839052620100009091046001600160a01b0316906323b872dd906064016020604051808303816000875af11580156110c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e9919061181d565b5060405181815233907feafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa7059060200160405180910390a250565b600061112c6106f4565b6111379060016117d4565b9050806001600160401b031682604001351461118a5760405162461bcd60e51b81526020600482015260126024820152711b9bdd081d5c18dbdb5a5b99c81c9bdd5b9960721b6044820152606401610498565b6060820135600760006111a060208601866115a4565b6001600160a01b03166001600160a01b031681526020019081526020016000205410156111e05760405163017e521960e71b815260040160405180910390fd5b600454826060013510156112075760405163e709032960e01b815260040160405180910390fd5b60608201356007600061121d60208601866115a4565b6001600160a01b03166001600160a01b0316815260200190815260200160002054101561125d5760405163017e521960e71b815260040160405180910390fd5b61126d610ba060208401846115a4565b6112b95760405162461bcd60e51b815260206004820152601f60248201527f696e76616c6964207369676e617475726520666f7220666972737420626964006044820152606401610498565b6060820135600760006112cf60208601866115a4565b6001600160a01b03166001600160a01b0316815260200190815260200160002060008282546112fe91906117c1565b909155505060065460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526060850135602482015262010000909204169063a9059cbb906044016020604051808303816000875af1158015611361573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611385919061181d565b5061139360208301836115a4565b60408381013560009081526009602090815291902080546001600160a01b0319166001600160a01b0393909316929092179091556001600160401b038216906113de908401846115a4565b6001600160a01b03167febab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef084606001356000604051611426929190918252602082015260400190565b60405180910390a35050565b6000546001600160a01b0316331461145d576040516311c29acf60e31b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60008060008061148e856114ff565b6040805160008152602081018083528b905260ff8316918101919091526060810184905260808101839052929550909350915060019060a0016020604051602081039080840390855afa1580156114e9573d6000803e3d6000fd5b5050506020604051035193505050505b92915050565b600080600083516041146115555760405162461bcd60e51b815260206004820152601860248201527f696e76616c6964207369676e6174757265206c656e67746800000000000000006044820152606401610498565b50505060208101516040820151606083015160001a601b8110156115815761157e601b8261189f565b90505b9193909250565b80356001600160a01b038116811461159f57600080fd5b919050565b6000602082840312156115b657600080fd5b6115bf82611588565b9392505050565b6000602082840312156115d857600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261160657600080fd5b81356001600160401b0380821115611620576116206115df565b604051601f8301601f19908116603f01168101908282118183101715611648576116486115df565b8160405283815286602085880101111561166157600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561169657600080fd5b61169f84611588565b925060208401356001600160401b03808211156116bb57600080fd5b6116c7878388016115f5565b935060408601359150808211156116dd57600080fd5b506116ea868287016115f5565b9150509250925092565b600060a0828403121561170657600080fd5b50919050565b6000806040838503121561171f57600080fd5b82356001600160401b038082111561173657600080fd5b611742868387016116f4565b9350602085013591508082111561175857600080fd5b50611765858286016116f4565b9150509250929050565b60006020828403121561178157600080fd5b81356001600160401b0381111561179757600080fd5b6117a3848285016116f4565b949350505050565b634e487b7160e01b600052601160045260246000fd5b818103818111156114f9576114f96117ab565b6001600160401b038181168382160190808211156117f4576117f46117ab565b5092915050565b60008261181857634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561182f57600080fd5b815180151581146115bf57600080fd5b6000808335601e1984360301811261185657600080fd5b8301803591506001600160401b0382111561187057600080fd5b60200191503681900382131561188557600080fd5b9250929050565b808201808211156114f9576114f96117ab565b60ff81811683821601908111156114f9576114f96117ab56fea26469706673582212206429478454ed8215ff5008fd2094b9c08b2ac458e30cc85f0f5be4106743765f64736f6c63430008130033", +} + +// ExpressLaneAuctionABI is the input ABI used to generate the binding from. +// Deprecated: Use ExpressLaneAuctionMetaData.ABI instead. +var ExpressLaneAuctionABI = ExpressLaneAuctionMetaData.ABI + +// ExpressLaneAuctionBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ExpressLaneAuctionMetaData.Bin instead. +var ExpressLaneAuctionBin = ExpressLaneAuctionMetaData.Bin + +// DeployExpressLaneAuction deploys a new Ethereum contract, binding an instance of ExpressLaneAuction to it. +func DeployExpressLaneAuction(auth *bind.TransactOpts, backend bind.ContractBackend, _chainOwnerAddr common.Address, _reservePriceSetterAddr common.Address, _bidReceiverAddr common.Address, _roundLengthSeconds uint64, _initialTimestamp *big.Int, _stakeToken common.Address, _currentReservePrice *big.Int, _minimalReservePrice *big.Int) (common.Address, *types.Transaction, *ExpressLaneAuction, error) { + parsed, err := ExpressLaneAuctionMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ExpressLaneAuctionBin), backend, _chainOwnerAddr, _reservePriceSetterAddr, _bidReceiverAddr, _roundLengthSeconds, _initialTimestamp, _stakeToken, _currentReservePrice, _minimalReservePrice) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ExpressLaneAuction{ExpressLaneAuctionCaller: ExpressLaneAuctionCaller{contract: contract}, ExpressLaneAuctionTransactor: ExpressLaneAuctionTransactor{contract: contract}, ExpressLaneAuctionFilterer: ExpressLaneAuctionFilterer{contract: contract}}, nil +} + +// ExpressLaneAuction is an auto generated Go binding around an Ethereum contract. +type ExpressLaneAuction struct { + ExpressLaneAuctionCaller // Read-only binding to the contract + ExpressLaneAuctionTransactor // Write-only binding to the contract + ExpressLaneAuctionFilterer // Log filterer for contract events +} + +// ExpressLaneAuctionCaller is an auto generated read-only Go binding around an Ethereum contract. +type ExpressLaneAuctionCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ExpressLaneAuctionTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ExpressLaneAuctionTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ExpressLaneAuctionFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ExpressLaneAuctionFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ExpressLaneAuctionSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ExpressLaneAuctionSession struct { + Contract *ExpressLaneAuction // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ExpressLaneAuctionCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ExpressLaneAuctionCallerSession struct { + Contract *ExpressLaneAuctionCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ExpressLaneAuctionTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ExpressLaneAuctionTransactorSession struct { + Contract *ExpressLaneAuctionTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ExpressLaneAuctionRaw is an auto generated low-level Go binding around an Ethereum contract. +type ExpressLaneAuctionRaw struct { + Contract *ExpressLaneAuction // Generic contract binding to access the raw methods on +} + +// ExpressLaneAuctionCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ExpressLaneAuctionCallerRaw struct { + Contract *ExpressLaneAuctionCaller // Generic read-only contract binding to access the raw methods on +} + +// ExpressLaneAuctionTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ExpressLaneAuctionTransactorRaw struct { + Contract *ExpressLaneAuctionTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewExpressLaneAuction creates a new instance of ExpressLaneAuction, bound to a specific deployed contract. +func NewExpressLaneAuction(address common.Address, backend bind.ContractBackend) (*ExpressLaneAuction, error) { + contract, err := bindExpressLaneAuction(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ExpressLaneAuction{ExpressLaneAuctionCaller: ExpressLaneAuctionCaller{contract: contract}, ExpressLaneAuctionTransactor: ExpressLaneAuctionTransactor{contract: contract}, ExpressLaneAuctionFilterer: ExpressLaneAuctionFilterer{contract: contract}}, nil +} + +// NewExpressLaneAuctionCaller creates a new read-only instance of ExpressLaneAuction, bound to a specific deployed contract. +func NewExpressLaneAuctionCaller(address common.Address, caller bind.ContractCaller) (*ExpressLaneAuctionCaller, error) { + contract, err := bindExpressLaneAuction(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionCaller{contract: contract}, nil +} + +// NewExpressLaneAuctionTransactor creates a new write-only instance of ExpressLaneAuction, bound to a specific deployed contract. +func NewExpressLaneAuctionTransactor(address common.Address, transactor bind.ContractTransactor) (*ExpressLaneAuctionTransactor, error) { + contract, err := bindExpressLaneAuction(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionTransactor{contract: contract}, nil +} + +// NewExpressLaneAuctionFilterer creates a new log filterer instance of ExpressLaneAuction, bound to a specific deployed contract. +func NewExpressLaneAuctionFilterer(address common.Address, filterer bind.ContractFilterer) (*ExpressLaneAuctionFilterer, error) { + contract, err := bindExpressLaneAuction(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionFilterer{contract: contract}, nil +} + +// bindExpressLaneAuction binds a generic wrapper to an already deployed contract. +func bindExpressLaneAuction(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := ExpressLaneAuctionMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ExpressLaneAuction *ExpressLaneAuctionRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ExpressLaneAuction.Contract.ExpressLaneAuctionCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ExpressLaneAuction *ExpressLaneAuctionRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.ExpressLaneAuctionTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ExpressLaneAuction *ExpressLaneAuctionRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.ExpressLaneAuctionTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ExpressLaneAuction *ExpressLaneAuctionCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ExpressLaneAuction.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.contract.Transact(opts, method, params...) +} + +// BidReceiver is a free data retrieval call binding the contract method 0x4bc37ea6. +// +// Solidity: function bidReceiver() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) BidReceiver(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "bidReceiver") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// BidReceiver is a free data retrieval call binding the contract method 0x4bc37ea6. +// +// Solidity: function bidReceiver() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) BidReceiver() (common.Address, error) { + return _ExpressLaneAuction.Contract.BidReceiver(&_ExpressLaneAuction.CallOpts) +} + +// BidReceiver is a free data retrieval call binding the contract method 0x4bc37ea6. +// +// Solidity: function bidReceiver() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) BidReceiver() (common.Address, error) { + return _ExpressLaneAuction.Contract.BidReceiver(&_ExpressLaneAuction.CallOpts) +} + +// BidSignatureDomainValue is a free data retrieval call binding the contract method 0x38265efd. +// +// Solidity: function bidSignatureDomainValue() view returns(uint16) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) BidSignatureDomainValue(opts *bind.CallOpts) (uint16, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "bidSignatureDomainValue") + + if err != nil { + return *new(uint16), err + } + + out0 := *abi.ConvertType(out[0], new(uint16)).(*uint16) + + return out0, err + +} + +// BidSignatureDomainValue is a free data retrieval call binding the contract method 0x38265efd. +// +// Solidity: function bidSignatureDomainValue() view returns(uint16) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) BidSignatureDomainValue() (uint16, error) { + return _ExpressLaneAuction.Contract.BidSignatureDomainValue(&_ExpressLaneAuction.CallOpts) +} + +// BidSignatureDomainValue is a free data retrieval call binding the contract method 0x38265efd. +// +// Solidity: function bidSignatureDomainValue() view returns(uint16) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) BidSignatureDomainValue() (uint16, error) { + return _ExpressLaneAuction.Contract.BidSignatureDomainValue(&_ExpressLaneAuction.CallOpts) +} + +// BidderBalance is a free data retrieval call binding the contract method 0x048fae73. +// +// Solidity: function bidderBalance(address bidder) view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) BidderBalance(opts *bind.CallOpts, bidder common.Address) (*big.Int, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "bidderBalance", bidder) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BidderBalance is a free data retrieval call binding the contract method 0x048fae73. +// +// Solidity: function bidderBalance(address bidder) view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) BidderBalance(bidder common.Address) (*big.Int, error) { + return _ExpressLaneAuction.Contract.BidderBalance(&_ExpressLaneAuction.CallOpts, bidder) +} + +// BidderBalance is a free data retrieval call binding the contract method 0x048fae73. +// +// Solidity: function bidderBalance(address bidder) view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) BidderBalance(bidder common.Address) (*big.Int, error) { + return _ExpressLaneAuction.Contract.BidderBalance(&_ExpressLaneAuction.CallOpts, bidder) +} + +// ChainOwnerAddress is a free data retrieval call binding the contract method 0x79a47e29. +// +// Solidity: function chainOwnerAddress() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) ChainOwnerAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "chainOwnerAddress") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// ChainOwnerAddress is a free data retrieval call binding the contract method 0x79a47e29. +// +// Solidity: function chainOwnerAddress() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) ChainOwnerAddress() (common.Address, error) { + return _ExpressLaneAuction.Contract.ChainOwnerAddress(&_ExpressLaneAuction.CallOpts) +} + +// ChainOwnerAddress is a free data retrieval call binding the contract method 0x79a47e29. +// +// Solidity: function chainOwnerAddress() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) ChainOwnerAddress() (common.Address, error) { + return _ExpressLaneAuction.Contract.ChainOwnerAddress(&_ExpressLaneAuction.CallOpts) +} + +// CurrentExpressLaneController is a free data retrieval call binding the contract method 0x4f2a9bdb. +// +// Solidity: function currentExpressLaneController() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) CurrentExpressLaneController(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "currentExpressLaneController") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// CurrentExpressLaneController is a free data retrieval call binding the contract method 0x4f2a9bdb. +// +// Solidity: function currentExpressLaneController() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) CurrentExpressLaneController() (common.Address, error) { + return _ExpressLaneAuction.Contract.CurrentExpressLaneController(&_ExpressLaneAuction.CallOpts) +} + +// CurrentExpressLaneController is a free data retrieval call binding the contract method 0x4f2a9bdb. +// +// Solidity: function currentExpressLaneController() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) CurrentExpressLaneController() (common.Address, error) { + return _ExpressLaneAuction.Contract.CurrentExpressLaneController(&_ExpressLaneAuction.CallOpts) +} + +// CurrentRound is a free data retrieval call binding the contract method 0x8a19c8bc. +// +// Solidity: function currentRound() view returns(uint64) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) CurrentRound(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "currentRound") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// CurrentRound is a free data retrieval call binding the contract method 0x8a19c8bc. +// +// Solidity: function currentRound() view returns(uint64) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) CurrentRound() (uint64, error) { + return _ExpressLaneAuction.Contract.CurrentRound(&_ExpressLaneAuction.CallOpts) +} + +// CurrentRound is a free data retrieval call binding the contract method 0x8a19c8bc. +// +// Solidity: function currentRound() view returns(uint64) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) CurrentRound() (uint64, error) { + return _ExpressLaneAuction.Contract.CurrentRound(&_ExpressLaneAuction.CallOpts) +} + +// DepositBalance is a free data retrieval call binding the contract method 0x956501bb. +// +// Solidity: function depositBalance(address ) view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) DepositBalance(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "depositBalance", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// DepositBalance is a free data retrieval call binding the contract method 0x956501bb. +// +// Solidity: function depositBalance(address ) view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) DepositBalance(arg0 common.Address) (*big.Int, error) { + return _ExpressLaneAuction.Contract.DepositBalance(&_ExpressLaneAuction.CallOpts, arg0) +} + +// DepositBalance is a free data retrieval call binding the contract method 0x956501bb. +// +// Solidity: function depositBalance(address ) view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) DepositBalance(arg0 common.Address) (*big.Int, error) { + return _ExpressLaneAuction.Contract.DepositBalance(&_ExpressLaneAuction.CallOpts, arg0) +} + +// ExpressLaneControllerByRound is a free data retrieval call binding the contract method 0x8296df03. +// +// Solidity: function expressLaneControllerByRound(uint256 ) view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) ExpressLaneControllerByRound(opts *bind.CallOpts, arg0 *big.Int) (common.Address, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "expressLaneControllerByRound", arg0) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// ExpressLaneControllerByRound is a free data retrieval call binding the contract method 0x8296df03. +// +// Solidity: function expressLaneControllerByRound(uint256 ) view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) ExpressLaneControllerByRound(arg0 *big.Int) (common.Address, error) { + return _ExpressLaneAuction.Contract.ExpressLaneControllerByRound(&_ExpressLaneAuction.CallOpts, arg0) +} + +// ExpressLaneControllerByRound is a free data retrieval call binding the contract method 0x8296df03. +// +// Solidity: function expressLaneControllerByRound(uint256 ) view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) ExpressLaneControllerByRound(arg0 *big.Int) (common.Address, error) { + return _ExpressLaneAuction.Contract.ExpressLaneControllerByRound(&_ExpressLaneAuction.CallOpts, arg0) +} + +// GetCurrentReservePrice is a free data retrieval call binding the contract method 0xb941ce6e. +// +// Solidity: function getCurrentReservePrice() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) GetCurrentReservePrice(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "getCurrentReservePrice") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetCurrentReservePrice is a free data retrieval call binding the contract method 0xb941ce6e. +// +// Solidity: function getCurrentReservePrice() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) GetCurrentReservePrice() (*big.Int, error) { + return _ExpressLaneAuction.Contract.GetCurrentReservePrice(&_ExpressLaneAuction.CallOpts) +} + +// GetCurrentReservePrice is a free data retrieval call binding the contract method 0xb941ce6e. +// +// Solidity: function getCurrentReservePrice() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) GetCurrentReservePrice() (*big.Int, error) { + return _ExpressLaneAuction.Contract.GetCurrentReservePrice(&_ExpressLaneAuction.CallOpts) +} + +// GetminimalReservePrice is a free data retrieval call binding the contract method 0x03ba6662. +// +// Solidity: function getminimalReservePrice() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) GetminimalReservePrice(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "getminimalReservePrice") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetminimalReservePrice is a free data retrieval call binding the contract method 0x03ba6662. +// +// Solidity: function getminimalReservePrice() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) GetminimalReservePrice() (*big.Int, error) { + return _ExpressLaneAuction.Contract.GetminimalReservePrice(&_ExpressLaneAuction.CallOpts) +} + +// GetminimalReservePrice is a free data retrieval call binding the contract method 0x03ba6662. +// +// Solidity: function getminimalReservePrice() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) GetminimalReservePrice() (*big.Int, error) { + return _ExpressLaneAuction.Contract.GetminimalReservePrice(&_ExpressLaneAuction.CallOpts) +} + +// InitialRoundTimestamp is a free data retrieval call binding the contract method 0xc0389979. +// +// Solidity: function initialRoundTimestamp() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) InitialRoundTimestamp(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "initialRoundTimestamp") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// InitialRoundTimestamp is a free data retrieval call binding the contract method 0xc0389979. +// +// Solidity: function initialRoundTimestamp() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) InitialRoundTimestamp() (*big.Int, error) { + return _ExpressLaneAuction.Contract.InitialRoundTimestamp(&_ExpressLaneAuction.CallOpts) +} + +// InitialRoundTimestamp is a free data retrieval call binding the contract method 0xc0389979. +// +// Solidity: function initialRoundTimestamp() view returns(uint256) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) InitialRoundTimestamp() (*big.Int, error) { + return _ExpressLaneAuction.Contract.InitialRoundTimestamp(&_ExpressLaneAuction.CallOpts) +} + +// PendingWithdrawalByBidder is a free data retrieval call binding the contract method 0x5f70f903. +// +// Solidity: function pendingWithdrawalByBidder(address ) view returns(uint256 amount, uint64 submittedRound) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) PendingWithdrawalByBidder(opts *bind.CallOpts, arg0 common.Address) (struct { + Amount *big.Int + SubmittedRound uint64 +}, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "pendingWithdrawalByBidder", arg0) + + outstruct := new(struct { + Amount *big.Int + SubmittedRound uint64 + }) + if err != nil { + return *outstruct, err + } + + outstruct.Amount = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + outstruct.SubmittedRound = *abi.ConvertType(out[1], new(uint64)).(*uint64) + + return *outstruct, err + +} + +// PendingWithdrawalByBidder is a free data retrieval call binding the contract method 0x5f70f903. +// +// Solidity: function pendingWithdrawalByBidder(address ) view returns(uint256 amount, uint64 submittedRound) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) PendingWithdrawalByBidder(arg0 common.Address) (struct { + Amount *big.Int + SubmittedRound uint64 +}, error) { + return _ExpressLaneAuction.Contract.PendingWithdrawalByBidder(&_ExpressLaneAuction.CallOpts, arg0) +} + +// PendingWithdrawalByBidder is a free data retrieval call binding the contract method 0x5f70f903. +// +// Solidity: function pendingWithdrawalByBidder(address ) view returns(uint256 amount, uint64 submittedRound) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) PendingWithdrawalByBidder(arg0 common.Address) (struct { + Amount *big.Int + SubmittedRound uint64 +}, error) { + return _ExpressLaneAuction.Contract.PendingWithdrawalByBidder(&_ExpressLaneAuction.CallOpts, arg0) +} + +// ReservePriceSetterAddress is a free data retrieval call binding the contract method 0x7c62b5cd. +// +// Solidity: function reservePriceSetterAddress() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) ReservePriceSetterAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "reservePriceSetterAddress") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// ReservePriceSetterAddress is a free data retrieval call binding the contract method 0x7c62b5cd. +// +// Solidity: function reservePriceSetterAddress() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) ReservePriceSetterAddress() (common.Address, error) { + return _ExpressLaneAuction.Contract.ReservePriceSetterAddress(&_ExpressLaneAuction.CallOpts) +} + +// ReservePriceSetterAddress is a free data retrieval call binding the contract method 0x7c62b5cd. +// +// Solidity: function reservePriceSetterAddress() view returns(address) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) ReservePriceSetterAddress() (common.Address, error) { + return _ExpressLaneAuction.Contract.ReservePriceSetterAddress(&_ExpressLaneAuction.CallOpts) +} + +// RoundDurationSeconds is a free data retrieval call binding the contract method 0xcc963d15. +// +// Solidity: function roundDurationSeconds() view returns(uint64) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) RoundDurationSeconds(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "roundDurationSeconds") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// RoundDurationSeconds is a free data retrieval call binding the contract method 0xcc963d15. +// +// Solidity: function roundDurationSeconds() view returns(uint64) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) RoundDurationSeconds() (uint64, error) { + return _ExpressLaneAuction.Contract.RoundDurationSeconds(&_ExpressLaneAuction.CallOpts) +} + +// RoundDurationSeconds is a free data retrieval call binding the contract method 0xcc963d15. +// +// Solidity: function roundDurationSeconds() view returns(uint64) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) RoundDurationSeconds() (uint64, error) { + return _ExpressLaneAuction.Contract.RoundDurationSeconds(&_ExpressLaneAuction.CallOpts) +} + +// VerifySignature is a free data retrieval call binding the contract method 0x24e359e7. +// +// Solidity: function verifySignature(address signer, bytes message, bytes signature) pure returns(bool) +func (_ExpressLaneAuction *ExpressLaneAuctionCaller) VerifySignature(opts *bind.CallOpts, signer common.Address, message []byte, signature []byte) (bool, error) { + var out []interface{} + err := _ExpressLaneAuction.contract.Call(opts, &out, "verifySignature", signer, message, signature) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// VerifySignature is a free data retrieval call binding the contract method 0x24e359e7. +// +// Solidity: function verifySignature(address signer, bytes message, bytes signature) pure returns(bool) +func (_ExpressLaneAuction *ExpressLaneAuctionSession) VerifySignature(signer common.Address, message []byte, signature []byte) (bool, error) { + return _ExpressLaneAuction.Contract.VerifySignature(&_ExpressLaneAuction.CallOpts, signer, message, signature) +} + +// VerifySignature is a free data retrieval call binding the contract method 0x24e359e7. +// +// Solidity: function verifySignature(address signer, bytes message, bytes signature) pure returns(bool) +func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) VerifySignature(signer common.Address, message []byte, signature []byte) (bool, error) { + return _ExpressLaneAuction.Contract.VerifySignature(&_ExpressLaneAuction.CallOpts, signer, message, signature) +} + +// CancelUpcomingRound is a paid mutator transaction binding the contract method 0x4d1846dc. +// +// Solidity: function cancelUpcomingRound() returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) CancelUpcomingRound(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "cancelUpcomingRound") +} + +// CancelUpcomingRound is a paid mutator transaction binding the contract method 0x4d1846dc. +// +// Solidity: function cancelUpcomingRound() returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) CancelUpcomingRound() (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.CancelUpcomingRound(&_ExpressLaneAuction.TransactOpts) +} + +// CancelUpcomingRound is a paid mutator transaction binding the contract method 0x4d1846dc. +// +// Solidity: function cancelUpcomingRound() returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) CancelUpcomingRound() (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.CancelUpcomingRound(&_ExpressLaneAuction.TransactOpts) +} + +// DelegateExpressLane is a paid mutator transaction binding the contract method 0xd6e5fb7d. +// +// Solidity: function delegateExpressLane(address delegate) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) DelegateExpressLane(opts *bind.TransactOpts, delegate common.Address) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "delegateExpressLane", delegate) +} + +// DelegateExpressLane is a paid mutator transaction binding the contract method 0xd6e5fb7d. +// +// Solidity: function delegateExpressLane(address delegate) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) DelegateExpressLane(delegate common.Address) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.DelegateExpressLane(&_ExpressLaneAuction.TransactOpts, delegate) +} + +// DelegateExpressLane is a paid mutator transaction binding the contract method 0xd6e5fb7d. +// +// Solidity: function delegateExpressLane(address delegate) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) DelegateExpressLane(delegate common.Address) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.DelegateExpressLane(&_ExpressLaneAuction.TransactOpts, delegate) +} + +// FinalizeWithdrawal is a paid mutator transaction binding the contract method 0xc5b6aa2f. +// +// Solidity: function finalizeWithdrawal() returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) FinalizeWithdrawal(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "finalizeWithdrawal") +} + +// FinalizeWithdrawal is a paid mutator transaction binding the contract method 0xc5b6aa2f. +// +// Solidity: function finalizeWithdrawal() returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) FinalizeWithdrawal() (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.FinalizeWithdrawal(&_ExpressLaneAuction.TransactOpts) +} + +// FinalizeWithdrawal is a paid mutator transaction binding the contract method 0xc5b6aa2f. +// +// Solidity: function finalizeWithdrawal() returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) FinalizeWithdrawal() (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.FinalizeWithdrawal(&_ExpressLaneAuction.TransactOpts) +} + +// InitiateWithdrawal is a paid mutator transaction binding the contract method 0x12edde5e. +// +// Solidity: function initiateWithdrawal(uint256 amount) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) InitiateWithdrawal(opts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "initiateWithdrawal", amount) +} + +// InitiateWithdrawal is a paid mutator transaction binding the contract method 0x12edde5e. +// +// Solidity: function initiateWithdrawal(uint256 amount) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) InitiateWithdrawal(amount *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.InitiateWithdrawal(&_ExpressLaneAuction.TransactOpts, amount) +} + +// InitiateWithdrawal is a paid mutator transaction binding the contract method 0x12edde5e. +// +// Solidity: function initiateWithdrawal(uint256 amount) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) InitiateWithdrawal(amount *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.InitiateWithdrawal(&_ExpressLaneAuction.TransactOpts, amount) +} + +// ResolveAuction is a paid mutator transaction binding the contract method 0xcd4abf71. +// +// Solidity: function resolveAuction((address,uint256,uint256,uint256,bytes) bid1, (address,uint256,uint256,uint256,bytes) bid2) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) ResolveAuction(opts *bind.TransactOpts, bid1 Bid, bid2 Bid) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "resolveAuction", bid1, bid2) +} + +// ResolveAuction is a paid mutator transaction binding the contract method 0xcd4abf71. +// +// Solidity: function resolveAuction((address,uint256,uint256,uint256,bytes) bid1, (address,uint256,uint256,uint256,bytes) bid2) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) ResolveAuction(bid1 Bid, bid2 Bid) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.ResolveAuction(&_ExpressLaneAuction.TransactOpts, bid1, bid2) +} + +// ResolveAuction is a paid mutator transaction binding the contract method 0xcd4abf71. +// +// Solidity: function resolveAuction((address,uint256,uint256,uint256,bytes) bid1, (address,uint256,uint256,uint256,bytes) bid2) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) ResolveAuction(bid1 Bid, bid2 Bid) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.ResolveAuction(&_ExpressLaneAuction.TransactOpts, bid1, bid2) +} + +// ResolveSingleBidAuction is a paid mutator transaction binding the contract method 0xf5f754d6. +// +// Solidity: function resolveSingleBidAuction((address,uint256,uint256,uint256,bytes) bid) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) ResolveSingleBidAuction(opts *bind.TransactOpts, bid Bid) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "resolveSingleBidAuction", bid) +} + +// ResolveSingleBidAuction is a paid mutator transaction binding the contract method 0xf5f754d6. +// +// Solidity: function resolveSingleBidAuction((address,uint256,uint256,uint256,bytes) bid) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) ResolveSingleBidAuction(bid Bid) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.ResolveSingleBidAuction(&_ExpressLaneAuction.TransactOpts, bid) +} + +// ResolveSingleBidAuction is a paid mutator transaction binding the contract method 0xf5f754d6. +// +// Solidity: function resolveSingleBidAuction((address,uint256,uint256,uint256,bytes) bid) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) ResolveSingleBidAuction(bid Bid) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.ResolveSingleBidAuction(&_ExpressLaneAuction.TransactOpts, bid) +} + +// SetCurrentReservePrice is a paid mutator transaction binding the contract method 0x574a9b5f. +// +// Solidity: function setCurrentReservePrice(uint256 _currentReservePrice) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SetCurrentReservePrice(opts *bind.TransactOpts, _currentReservePrice *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "setCurrentReservePrice", _currentReservePrice) +} + +// SetCurrentReservePrice is a paid mutator transaction binding the contract method 0x574a9b5f. +// +// Solidity: function setCurrentReservePrice(uint256 _currentReservePrice) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) SetCurrentReservePrice(_currentReservePrice *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SetCurrentReservePrice(&_ExpressLaneAuction.TransactOpts, _currentReservePrice) +} + +// SetCurrentReservePrice is a paid mutator transaction binding the contract method 0x574a9b5f. +// +// Solidity: function setCurrentReservePrice(uint256 _currentReservePrice) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SetCurrentReservePrice(_currentReservePrice *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SetCurrentReservePrice(&_ExpressLaneAuction.TransactOpts, _currentReservePrice) +} + +// SetMinimalReservePrice is a paid mutator transaction binding the contract method 0xd6ded1bc. +// +// Solidity: function setMinimalReservePrice(uint256 _minimalReservePrice) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SetMinimalReservePrice(opts *bind.TransactOpts, _minimalReservePrice *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "setMinimalReservePrice", _minimalReservePrice) +} + +// SetMinimalReservePrice is a paid mutator transaction binding the contract method 0xd6ded1bc. +// +// Solidity: function setMinimalReservePrice(uint256 _minimalReservePrice) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) SetMinimalReservePrice(_minimalReservePrice *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SetMinimalReservePrice(&_ExpressLaneAuction.TransactOpts, _minimalReservePrice) +} + +// SetMinimalReservePrice is a paid mutator transaction binding the contract method 0xd6ded1bc. +// +// Solidity: function setMinimalReservePrice(uint256 _minimalReservePrice) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SetMinimalReservePrice(_minimalReservePrice *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SetMinimalReservePrice(&_ExpressLaneAuction.TransactOpts, _minimalReservePrice) +} + +// SetReservePriceAddresses is a paid mutator transaction binding the contract method 0xf66fda64. +// +// Solidity: function setReservePriceAddresses(address _reservePriceSetterAddr) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SetReservePriceAddresses(opts *bind.TransactOpts, _reservePriceSetterAddr common.Address) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "setReservePriceAddresses", _reservePriceSetterAddr) +} + +// SetReservePriceAddresses is a paid mutator transaction binding the contract method 0xf66fda64. +// +// Solidity: function setReservePriceAddresses(address _reservePriceSetterAddr) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) SetReservePriceAddresses(_reservePriceSetterAddr common.Address) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SetReservePriceAddresses(&_ExpressLaneAuction.TransactOpts, _reservePriceSetterAddr) +} + +// SetReservePriceAddresses is a paid mutator transaction binding the contract method 0xf66fda64. +// +// Solidity: function setReservePriceAddresses(address _reservePriceSetterAddr) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SetReservePriceAddresses(_reservePriceSetterAddr common.Address) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SetReservePriceAddresses(&_ExpressLaneAuction.TransactOpts, _reservePriceSetterAddr) +} + +// SubmitDeposit is a paid mutator transaction binding the contract method 0xdbeb2012. +// +// Solidity: function submitDeposit(uint256 amount) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SubmitDeposit(opts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.contract.Transact(opts, "submitDeposit", amount) +} + +// SubmitDeposit is a paid mutator transaction binding the contract method 0xdbeb2012. +// +// Solidity: function submitDeposit(uint256 amount) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionSession) SubmitDeposit(amount *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SubmitDeposit(&_ExpressLaneAuction.TransactOpts, amount) +} + +// SubmitDeposit is a paid mutator transaction binding the contract method 0xdbeb2012. +// +// Solidity: function submitDeposit(uint256 amount) returns() +func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SubmitDeposit(amount *big.Int) (*types.Transaction, error) { + return _ExpressLaneAuction.Contract.SubmitDeposit(&_ExpressLaneAuction.TransactOpts, amount) +} + +// ExpressLaneAuctionAuctionResolvedIterator is returned from FilterAuctionResolved and is used to iterate over the raw logs and unpacked data for AuctionResolved events raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionAuctionResolvedIterator struct { + Event *ExpressLaneAuctionAuctionResolved // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ExpressLaneAuctionAuctionResolvedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionAuctionResolved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionAuctionResolved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ExpressLaneAuctionAuctionResolvedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ExpressLaneAuctionAuctionResolvedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ExpressLaneAuctionAuctionResolved represents a AuctionResolved event raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionAuctionResolved struct { + WinningBidAmount *big.Int + SecondPlaceBidAmount *big.Int + WinningBidder common.Address + WinnerRound *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAuctionResolved is a free log retrieval operation binding the contract event 0xebab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef0. +// +// Solidity: event AuctionResolved(uint256 winningBidAmount, uint256 secondPlaceBidAmount, address indexed winningBidder, uint256 indexed winnerRound) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterAuctionResolved(opts *bind.FilterOpts, winningBidder []common.Address, winnerRound []*big.Int) (*ExpressLaneAuctionAuctionResolvedIterator, error) { + + var winningBidderRule []interface{} + for _, winningBidderItem := range winningBidder { + winningBidderRule = append(winningBidderRule, winningBidderItem) + } + var winnerRoundRule []interface{} + for _, winnerRoundItem := range winnerRound { + winnerRoundRule = append(winnerRoundRule, winnerRoundItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "AuctionResolved", winningBidderRule, winnerRoundRule) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionAuctionResolvedIterator{contract: _ExpressLaneAuction.contract, event: "AuctionResolved", logs: logs, sub: sub}, nil +} + +// WatchAuctionResolved is a free log subscription operation binding the contract event 0xebab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef0. +// +// Solidity: event AuctionResolved(uint256 winningBidAmount, uint256 secondPlaceBidAmount, address indexed winningBidder, uint256 indexed winnerRound) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchAuctionResolved(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionAuctionResolved, winningBidder []common.Address, winnerRound []*big.Int) (event.Subscription, error) { + + var winningBidderRule []interface{} + for _, winningBidderItem := range winningBidder { + winningBidderRule = append(winningBidderRule, winningBidderItem) + } + var winnerRoundRule []interface{} + for _, winnerRoundItem := range winnerRound { + winnerRoundRule = append(winnerRoundRule, winnerRoundItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "AuctionResolved", winningBidderRule, winnerRoundRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ExpressLaneAuctionAuctionResolved) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "AuctionResolved", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAuctionResolved is a log parse operation binding the contract event 0xebab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef0. +// +// Solidity: event AuctionResolved(uint256 winningBidAmount, uint256 secondPlaceBidAmount, address indexed winningBidder, uint256 indexed winnerRound) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseAuctionResolved(log types.Log) (*ExpressLaneAuctionAuctionResolved, error) { + event := new(ExpressLaneAuctionAuctionResolved) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "AuctionResolved", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ExpressLaneAuctionDepositSubmittedIterator is returned from FilterDepositSubmitted and is used to iterate over the raw logs and unpacked data for DepositSubmitted events raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionDepositSubmittedIterator struct { + Event *ExpressLaneAuctionDepositSubmitted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ExpressLaneAuctionDepositSubmittedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionDepositSubmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionDepositSubmitted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ExpressLaneAuctionDepositSubmittedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ExpressLaneAuctionDepositSubmittedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ExpressLaneAuctionDepositSubmitted represents a DepositSubmitted event raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionDepositSubmitted struct { + Bidder common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDepositSubmitted is a free log retrieval operation binding the contract event 0xeafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa705. +// +// Solidity: event DepositSubmitted(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterDepositSubmitted(opts *bind.FilterOpts, bidder []common.Address) (*ExpressLaneAuctionDepositSubmittedIterator, error) { + + var bidderRule []interface{} + for _, bidderItem := range bidder { + bidderRule = append(bidderRule, bidderItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "DepositSubmitted", bidderRule) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionDepositSubmittedIterator{contract: _ExpressLaneAuction.contract, event: "DepositSubmitted", logs: logs, sub: sub}, nil +} + +// WatchDepositSubmitted is a free log subscription operation binding the contract event 0xeafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa705. +// +// Solidity: event DepositSubmitted(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchDepositSubmitted(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionDepositSubmitted, bidder []common.Address) (event.Subscription, error) { + + var bidderRule []interface{} + for _, bidderItem := range bidder { + bidderRule = append(bidderRule, bidderItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "DepositSubmitted", bidderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ExpressLaneAuctionDepositSubmitted) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "DepositSubmitted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseDepositSubmitted is a log parse operation binding the contract event 0xeafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa705. +// +// Solidity: event DepositSubmitted(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseDepositSubmitted(log types.Log) (*ExpressLaneAuctionDepositSubmitted, error) { + event := new(ExpressLaneAuctionDepositSubmitted) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "DepositSubmitted", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ExpressLaneAuctionExpressLaneControlDelegatedIterator is returned from FilterExpressLaneControlDelegated and is used to iterate over the raw logs and unpacked data for ExpressLaneControlDelegated events raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionExpressLaneControlDelegatedIterator struct { + Event *ExpressLaneAuctionExpressLaneControlDelegated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ExpressLaneAuctionExpressLaneControlDelegatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionExpressLaneControlDelegated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionExpressLaneControlDelegated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ExpressLaneAuctionExpressLaneControlDelegatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ExpressLaneAuctionExpressLaneControlDelegatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ExpressLaneAuctionExpressLaneControlDelegated represents a ExpressLaneControlDelegated event raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionExpressLaneControlDelegated struct { + From common.Address + To common.Address + Round uint64 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterExpressLaneControlDelegated is a free log retrieval operation binding the contract event 0xdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede. +// +// Solidity: event ExpressLaneControlDelegated(address indexed from, address indexed to, uint64 round) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterExpressLaneControlDelegated(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ExpressLaneAuctionExpressLaneControlDelegatedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "ExpressLaneControlDelegated", fromRule, toRule) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionExpressLaneControlDelegatedIterator{contract: _ExpressLaneAuction.contract, event: "ExpressLaneControlDelegated", logs: logs, sub: sub}, nil +} + +// WatchExpressLaneControlDelegated is a free log subscription operation binding the contract event 0xdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede. +// +// Solidity: event ExpressLaneControlDelegated(address indexed from, address indexed to, uint64 round) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchExpressLaneControlDelegated(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionExpressLaneControlDelegated, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "ExpressLaneControlDelegated", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ExpressLaneAuctionExpressLaneControlDelegated) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "ExpressLaneControlDelegated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseExpressLaneControlDelegated is a log parse operation binding the contract event 0xdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede. +// +// Solidity: event ExpressLaneControlDelegated(address indexed from, address indexed to, uint64 round) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseExpressLaneControlDelegated(log types.Log) (*ExpressLaneAuctionExpressLaneControlDelegated, error) { + event := new(ExpressLaneAuctionExpressLaneControlDelegated) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "ExpressLaneControlDelegated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ExpressLaneAuctionWithdrawalFinalizedIterator is returned from FilterWithdrawalFinalized and is used to iterate over the raw logs and unpacked data for WithdrawalFinalized events raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionWithdrawalFinalizedIterator struct { + Event *ExpressLaneAuctionWithdrawalFinalized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ExpressLaneAuctionWithdrawalFinalizedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionWithdrawalFinalized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionWithdrawalFinalized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ExpressLaneAuctionWithdrawalFinalizedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ExpressLaneAuctionWithdrawalFinalizedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ExpressLaneAuctionWithdrawalFinalized represents a WithdrawalFinalized event raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionWithdrawalFinalized struct { + Bidder common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterWithdrawalFinalized is a free log retrieval operation binding the contract event 0x9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda8. +// +// Solidity: event WithdrawalFinalized(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterWithdrawalFinalized(opts *bind.FilterOpts, bidder []common.Address) (*ExpressLaneAuctionWithdrawalFinalizedIterator, error) { + + var bidderRule []interface{} + for _, bidderItem := range bidder { + bidderRule = append(bidderRule, bidderItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "WithdrawalFinalized", bidderRule) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionWithdrawalFinalizedIterator{contract: _ExpressLaneAuction.contract, event: "WithdrawalFinalized", logs: logs, sub: sub}, nil +} + +// WatchWithdrawalFinalized is a free log subscription operation binding the contract event 0x9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda8. +// +// Solidity: event WithdrawalFinalized(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchWithdrawalFinalized(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionWithdrawalFinalized, bidder []common.Address) (event.Subscription, error) { + + var bidderRule []interface{} + for _, bidderItem := range bidder { + bidderRule = append(bidderRule, bidderItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "WithdrawalFinalized", bidderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ExpressLaneAuctionWithdrawalFinalized) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalFinalized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseWithdrawalFinalized is a log parse operation binding the contract event 0x9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda8. +// +// Solidity: event WithdrawalFinalized(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseWithdrawalFinalized(log types.Log) (*ExpressLaneAuctionWithdrawalFinalized, error) { + event := new(ExpressLaneAuctionWithdrawalFinalized) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalFinalized", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ExpressLaneAuctionWithdrawalInitiatedIterator is returned from FilterWithdrawalInitiated and is used to iterate over the raw logs and unpacked data for WithdrawalInitiated events raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionWithdrawalInitiatedIterator struct { + Event *ExpressLaneAuctionWithdrawalInitiated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ExpressLaneAuctionWithdrawalInitiatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionWithdrawalInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ExpressLaneAuctionWithdrawalInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ExpressLaneAuctionWithdrawalInitiatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ExpressLaneAuctionWithdrawalInitiatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ExpressLaneAuctionWithdrawalInitiated represents a WithdrawalInitiated event raised by the ExpressLaneAuction contract. +type ExpressLaneAuctionWithdrawalInitiated struct { + Bidder common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterWithdrawalInitiated is a free log retrieval operation binding the contract event 0x6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec646. +// +// Solidity: event WithdrawalInitiated(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterWithdrawalInitiated(opts *bind.FilterOpts, bidder []common.Address) (*ExpressLaneAuctionWithdrawalInitiatedIterator, error) { + + var bidderRule []interface{} + for _, bidderItem := range bidder { + bidderRule = append(bidderRule, bidderItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "WithdrawalInitiated", bidderRule) + if err != nil { + return nil, err + } + return &ExpressLaneAuctionWithdrawalInitiatedIterator{contract: _ExpressLaneAuction.contract, event: "WithdrawalInitiated", logs: logs, sub: sub}, nil +} + +// WatchWithdrawalInitiated is a free log subscription operation binding the contract event 0x6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec646. +// +// Solidity: event WithdrawalInitiated(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchWithdrawalInitiated(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionWithdrawalInitiated, bidder []common.Address) (event.Subscription, error) { + + var bidderRule []interface{} + for _, bidderItem := range bidder { + bidderRule = append(bidderRule, bidderItem) + } + + logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "WithdrawalInitiated", bidderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ExpressLaneAuctionWithdrawalInitiated) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalInitiated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseWithdrawalInitiated is a log parse operation binding the contract event 0x6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec646. +// +// Solidity: event WithdrawalInitiated(address indexed bidder, uint256 amount) +func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseWithdrawalInitiated(log types.Log) (*ExpressLaneAuctionWithdrawalInitiated, error) { + event := new(ExpressLaneAuctionWithdrawalInitiated) + if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalInitiated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/timeboost/bindings/mockerc20.go b/timeboost/bindings/mockerc20.go new file mode 100644 index 0000000000..c65ac35cda --- /dev/null +++ b/timeboost/bindings/mockerc20.go @@ -0,0 +1,906 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bindings + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// MockERC20MetaData contains all meta data concerning the MockERC20 contract. +var MockERC20MetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"DOMAIN_SEPARATOR\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"_burn\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"_mint\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"allowance\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"approve\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"name_\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"symbol_\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"decimals_\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"name\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"nonces\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"permit\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"v\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"r\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"s\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"symbol\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"totalSupply\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transfer\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferFrom\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]", + Bin: "0x608060405234801561001057600080fd5b50610fb2806100206000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80634e6ec2471161009757806395d89b411161006657806395d89b4114610201578063a9059cbb14610209578063d505accf1461021c578063dd62ed3e1461022f57600080fd5b80634e6ec247146101925780636161eb18146101a557806370a08231146101b85780637ecebe00146101e157600080fd5b806318160ddd116100d357806318160ddd1461015057806323b872dd14610162578063313ce567146101755780633644e5151461018a57600080fd5b806306fdde03146100fa578063095ea7b3146101185780631624f6c61461013b575b600080fd5b610102610268565b60405161010f9190610a98565b60405180910390f35b61012b610126366004610b02565b6102fa565b604051901515815260200161010f565b61014e610149366004610be0565b610367565b005b6003545b60405190815260200161010f565b61012b610170366004610c54565b610406565b60025460405160ff909116815260200161010f565b610154610509565b61014e6101a0366004610b02565b61052f565b61014e6101b3366004610b02565b6105ac565b6101546101c6366004610c90565b6001600160a01b031660009081526004602052604090205490565b6101546101ef366004610c90565b60086020526000908152604090205481565b610102610624565b61012b610217366004610b02565b610633565b61014e61022a366004610cab565b6106b8565b61015461023d366004610d15565b6001600160a01b03918216600090815260056020908152604080832093909416825291909152205490565b60606000805461027790610d48565b80601f01602080910402602001604051908101604052809291908181526020018280546102a390610d48565b80156102f05780601f106102c5576101008083540402835291602001916102f0565b820191906000526020600020905b8154815290600101906020018083116102d357829003601f168201915b5050505050905090565b3360008181526005602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103559086815260200190565b60405180910390a35060015b92915050565b60095460ff16156103b55760405162461bcd60e51b81526020600482015260136024820152721053149150511657d253925512505312569151606a1b60448201526064015b60405180910390fd5b60006103c18482610dd1565b5060016103ce8382610dd1565b506002805460ff191660ff83161790556103e6610916565b6006556103f161092f565b60075550506009805460ff1916600117905550565b6001600160a01b038316600090815260056020908152604080832033845290915281205460001981146104625761043d81846109d2565b6001600160a01b03861660009081526005602090815260408083203384529091529020555b6001600160a01b03851660009081526004602052604090205461048590846109d2565b6001600160a01b0380871660009081526004602052604080822093909355908616815220546104b49084610a35565b6001600160a01b038086166000818152600460205260409081902093909355915190871690600080516020610f5d833981519152906104f69087815260200190565b60405180910390a3506001949350505050565b6000600654610516610916565b146105285761052361092f565b905090565b5060075490565b61053b60035482610a35565b6003556001600160a01b0382166000908152600460205260409020546105619082610a35565b6001600160a01b038316600081815260046020526040808220939093559151909190600080516020610f5d833981519152906105a09085815260200190565b60405180910390a35050565b6001600160a01b0382166000908152600460205260409020546105cf90826109d2565b6001600160a01b0383166000908152600460205260409020556003546105f590826109d2565b6003556040518181526000906001600160a01b03841690600080516020610f5d833981519152906020016105a0565b60606001805461027790610d48565b3360009081526004602052604081205461064d90836109d2565b33600090815260046020526040808220929092556001600160a01b038516815220546106799083610a35565b6001600160a01b038416600081815260046020526040908190209290925590513390600080516020610f5d833981519152906103559086815260200190565b428410156107085760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064016103ac565b60006001610714610509565b6001600160a01b038a16600090815260086020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9928d928d928d9290919061076283610ea7565b909155506040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810188905260e001604051602081830303815290604052805190602001206040516020016107db92919061190160f01b81526002810192909252602282015260420190565b60408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610839573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381161580159061086f5750876001600160a01b0316816001600160a01b0316145b6108ac5760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b60448201526064016103ac565b6001600160a01b0381811660009081526005602090815260408083208b8516808552908352928190208a90555189815291928b16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a35050505050505050565b6000610a948061092863ffffffff8216565b9250505090565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516109619190610ec0565b60405180910390207fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6610992610916565b604080516020810195909552840192909252606083015260808201523060a082015260c00160405160208183030381529060405280519060200120905090565b600081831015610a245760405162461bcd60e51b815260206004820152601c60248201527f45524332303a207375627472616374696f6e20756e646572666c6f770000000060448201526064016103ac565b610a2e8284610f36565b9392505050565b600080610a428385610f49565b905083811015610a2e5760405162461bcd60e51b815260206004820152601860248201527f45524332303a206164646974696f6e206f766572666c6f77000000000000000060448201526064016103ac565b4690565b600060208083528351808285015260005b81811015610ac557858101830151858201604001528201610aa9565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610afd57600080fd5b919050565b60008060408385031215610b1557600080fd5b610b1e83610ae6565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112610b5357600080fd5b813567ffffffffffffffff80821115610b6e57610b6e610b2c565b604051601f8301601f19908116603f01168101908282118183101715610b9657610b96610b2c565b81604052838152866020858801011115610baf57600080fd5b836020870160208301376000602085830101528094505050505092915050565b803560ff81168114610afd57600080fd5b600080600060608486031215610bf557600080fd5b833567ffffffffffffffff80821115610c0d57600080fd5b610c1987838801610b42565b94506020860135915080821115610c2f57600080fd5b50610c3c86828701610b42565b925050610c4b60408501610bcf565b90509250925092565b600080600060608486031215610c6957600080fd5b610c7284610ae6565b9250610c8060208501610ae6565b9150604084013590509250925092565b600060208284031215610ca257600080fd5b610a2e82610ae6565b600080600080600080600060e0888a031215610cc657600080fd5b610ccf88610ae6565b9650610cdd60208901610ae6565b95506040880135945060608801359350610cf960808901610bcf565b925060a0880135915060c0880135905092959891949750929550565b60008060408385031215610d2857600080fd5b610d3183610ae6565b9150610d3f60208401610ae6565b90509250929050565b600181811c90821680610d5c57607f821691505b602082108103610d7c57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115610dcc57600081815260208120601f850160051c81016020861015610da95750805b601f850160051c820191505b81811015610dc857828155600101610db5565b5050505b505050565b815167ffffffffffffffff811115610deb57610deb610b2c565b610dff81610df98454610d48565b84610d82565b602080601f831160018114610e345760008415610e1c5750858301515b600019600386901b1c1916600185901b178555610dc8565b600085815260208120601f198616915b82811015610e6357888601518255948401946001909101908401610e44565b5085821015610e815787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052601160045260246000fd5b600060018201610eb957610eb9610e91565b5060010190565b6000808354610ece81610d48565b60018281168015610ee65760018114610efb57610f2a565b60ff1984168752821515830287019450610f2a565b8760005260208060002060005b85811015610f215781548a820152908401908201610f08565b50505082870194505b50929695505050505050565b8181038181111561036157610361610e91565b8082018082111561036157610361610e9156feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212202d566c07dcb56bf37a267c42ca84957e1e7a1464769616ab9c405a2a439c68f264736f6c63430008130033", +} + +// MockERC20ABI is the input ABI used to generate the binding from. +// Deprecated: Use MockERC20MetaData.ABI instead. +var MockERC20ABI = MockERC20MetaData.ABI + +// MockERC20Bin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use MockERC20MetaData.Bin instead. +var MockERC20Bin = MockERC20MetaData.Bin + +// DeployMockERC20 deploys a new Ethereum contract, binding an instance of MockERC20 to it. +func DeployMockERC20(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *MockERC20, error) { + parsed, err := MockERC20MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(MockERC20Bin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &MockERC20{MockERC20Caller: MockERC20Caller{contract: contract}, MockERC20Transactor: MockERC20Transactor{contract: contract}, MockERC20Filterer: MockERC20Filterer{contract: contract}}, nil +} + +// MockERC20 is an auto generated Go binding around an Ethereum contract. +type MockERC20 struct { + MockERC20Caller // Read-only binding to the contract + MockERC20Transactor // Write-only binding to the contract + MockERC20Filterer // Log filterer for contract events +} + +// MockERC20Caller is an auto generated read-only Go binding around an Ethereum contract. +type MockERC20Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MockERC20Transactor is an auto generated write-only Go binding around an Ethereum contract. +type MockERC20Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MockERC20Filterer is an auto generated log filtering Go binding around an Ethereum contract events. +type MockERC20Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MockERC20Session is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type MockERC20Session struct { + Contract *MockERC20 // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MockERC20CallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type MockERC20CallerSession struct { + Contract *MockERC20Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// MockERC20TransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type MockERC20TransactorSession struct { + Contract *MockERC20Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MockERC20Raw is an auto generated low-level Go binding around an Ethereum contract. +type MockERC20Raw struct { + Contract *MockERC20 // Generic contract binding to access the raw methods on +} + +// MockERC20CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type MockERC20CallerRaw struct { + Contract *MockERC20Caller // Generic read-only contract binding to access the raw methods on +} + +// MockERC20TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type MockERC20TransactorRaw struct { + Contract *MockERC20Transactor // Generic write-only contract binding to access the raw methods on +} + +// NewMockERC20 creates a new instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20(address common.Address, backend bind.ContractBackend) (*MockERC20, error) { + contract, err := bindMockERC20(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &MockERC20{MockERC20Caller: MockERC20Caller{contract: contract}, MockERC20Transactor: MockERC20Transactor{contract: contract}, MockERC20Filterer: MockERC20Filterer{contract: contract}}, nil +} + +// NewMockERC20Caller creates a new read-only instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20Caller(address common.Address, caller bind.ContractCaller) (*MockERC20Caller, error) { + contract, err := bindMockERC20(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &MockERC20Caller{contract: contract}, nil +} + +// NewMockERC20Transactor creates a new write-only instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20Transactor(address common.Address, transactor bind.ContractTransactor) (*MockERC20Transactor, error) { + contract, err := bindMockERC20(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &MockERC20Transactor{contract: contract}, nil +} + +// NewMockERC20Filterer creates a new log filterer instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20Filterer(address common.Address, filterer bind.ContractFilterer) (*MockERC20Filterer, error) { + contract, err := bindMockERC20(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &MockERC20Filterer{contract: contract}, nil +} + +// bindMockERC20 binds a generic wrapper to an already deployed contract. +func bindMockERC20(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := MockERC20MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MockERC20 *MockERC20Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MockERC20.Contract.MockERC20Caller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MockERC20 *MockERC20Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MockERC20.Contract.MockERC20Transactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MockERC20 *MockERC20Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MockERC20.Contract.MockERC20Transactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MockERC20 *MockERC20CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MockERC20.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MockERC20 *MockERC20TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MockERC20.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MockERC20 *MockERC20TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MockERC20.Contract.contract.Transact(opts, method, params...) +} + +// DOMAINSEPARATOR is a free data retrieval call binding the contract method 0x3644e515. +// +// Solidity: function DOMAIN_SEPARATOR() view returns(bytes32) +func (_MockERC20 *MockERC20Caller) DOMAINSEPARATOR(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "DOMAIN_SEPARATOR") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// DOMAINSEPARATOR is a free data retrieval call binding the contract method 0x3644e515. +// +// Solidity: function DOMAIN_SEPARATOR() view returns(bytes32) +func (_MockERC20 *MockERC20Session) DOMAINSEPARATOR() ([32]byte, error) { + return _MockERC20.Contract.DOMAINSEPARATOR(&_MockERC20.CallOpts) +} + +// DOMAINSEPARATOR is a free data retrieval call binding the contract method 0x3644e515. +// +// Solidity: function DOMAIN_SEPARATOR() view returns(bytes32) +func (_MockERC20 *MockERC20CallerSession) DOMAINSEPARATOR() ([32]byte, error) { + return _MockERC20.Contract.DOMAINSEPARATOR(&_MockERC20.CallOpts) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_MockERC20 *MockERC20Caller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "allowance", owner, spender) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_MockERC20 *MockERC20Session) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _MockERC20.Contract.Allowance(&_MockERC20.CallOpts, owner, spender) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_MockERC20 *MockERC20CallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _MockERC20.Contract.Allowance(&_MockERC20.CallOpts, owner, spender) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address owner) view returns(uint256) +func (_MockERC20 *MockERC20Caller) BalanceOf(opts *bind.CallOpts, owner common.Address) (*big.Int, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "balanceOf", owner) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address owner) view returns(uint256) +func (_MockERC20 *MockERC20Session) BalanceOf(owner common.Address) (*big.Int, error) { + return _MockERC20.Contract.BalanceOf(&_MockERC20.CallOpts, owner) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address owner) view returns(uint256) +func (_MockERC20 *MockERC20CallerSession) BalanceOf(owner common.Address) (*big.Int, error) { + return _MockERC20.Contract.BalanceOf(&_MockERC20.CallOpts, owner) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_MockERC20 *MockERC20Caller) Decimals(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_MockERC20 *MockERC20Session) Decimals() (uint8, error) { + return _MockERC20.Contract.Decimals(&_MockERC20.CallOpts) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_MockERC20 *MockERC20CallerSession) Decimals() (uint8, error) { + return _MockERC20.Contract.Decimals(&_MockERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_MockERC20 *MockERC20Caller) Name(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "name") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_MockERC20 *MockERC20Session) Name() (string, error) { + return _MockERC20.Contract.Name(&_MockERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_MockERC20 *MockERC20CallerSession) Name() (string, error) { + return _MockERC20.Contract.Name(&_MockERC20.CallOpts) +} + +// Nonces is a free data retrieval call binding the contract method 0x7ecebe00. +// +// Solidity: function nonces(address ) view returns(uint256) +func (_MockERC20 *MockERC20Caller) Nonces(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "nonces", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Nonces is a free data retrieval call binding the contract method 0x7ecebe00. +// +// Solidity: function nonces(address ) view returns(uint256) +func (_MockERC20 *MockERC20Session) Nonces(arg0 common.Address) (*big.Int, error) { + return _MockERC20.Contract.Nonces(&_MockERC20.CallOpts, arg0) +} + +// Nonces is a free data retrieval call binding the contract method 0x7ecebe00. +// +// Solidity: function nonces(address ) view returns(uint256) +func (_MockERC20 *MockERC20CallerSession) Nonces(arg0 common.Address) (*big.Int, error) { + return _MockERC20.Contract.Nonces(&_MockERC20.CallOpts, arg0) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_MockERC20 *MockERC20Caller) Symbol(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "symbol") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_MockERC20 *MockERC20Session) Symbol() (string, error) { + return _MockERC20.Contract.Symbol(&_MockERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_MockERC20 *MockERC20CallerSession) Symbol() (string, error) { + return _MockERC20.Contract.Symbol(&_MockERC20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_MockERC20 *MockERC20Caller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "totalSupply") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_MockERC20 *MockERC20Session) TotalSupply() (*big.Int, error) { + return _MockERC20.Contract.TotalSupply(&_MockERC20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_MockERC20 *MockERC20CallerSession) TotalSupply() (*big.Int, error) { + return _MockERC20.Contract.TotalSupply(&_MockERC20.CallOpts) +} + +// Burn is a paid mutator transaction binding the contract method 0x6161eb18. +// +// Solidity: function _burn(address from, uint256 amount) returns() +func (_MockERC20 *MockERC20Transactor) Burn(opts *bind.TransactOpts, from common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "_burn", from, amount) +} + +// Burn is a paid mutator transaction binding the contract method 0x6161eb18. +// +// Solidity: function _burn(address from, uint256 amount) returns() +func (_MockERC20 *MockERC20Session) Burn(from common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Burn(&_MockERC20.TransactOpts, from, amount) +} + +// Burn is a paid mutator transaction binding the contract method 0x6161eb18. +// +// Solidity: function _burn(address from, uint256 amount) returns() +func (_MockERC20 *MockERC20TransactorSession) Burn(from common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Burn(&_MockERC20.TransactOpts, from, amount) +} + +// Mint is a paid mutator transaction binding the contract method 0x4e6ec247. +// +// Solidity: function _mint(address to, uint256 amount) returns() +func (_MockERC20 *MockERC20Transactor) Mint(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "_mint", to, amount) +} + +// Mint is a paid mutator transaction binding the contract method 0x4e6ec247. +// +// Solidity: function _mint(address to, uint256 amount) returns() +func (_MockERC20 *MockERC20Session) Mint(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Mint(&_MockERC20.TransactOpts, to, amount) +} + +// Mint is a paid mutator transaction binding the contract method 0x4e6ec247. +// +// Solidity: function _mint(address to, uint256 amount) returns() +func (_MockERC20 *MockERC20TransactorSession) Mint(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Mint(&_MockERC20.TransactOpts, to, amount) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "approve", spender, amount) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20Session) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Approve(&_MockERC20.TransactOpts, spender, amount) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20TransactorSession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Approve(&_MockERC20.TransactOpts, spender, amount) +} + +// Initialize is a paid mutator transaction binding the contract method 0x1624f6c6. +// +// Solidity: function initialize(string name_, string symbol_, uint8 decimals_) returns() +func (_MockERC20 *MockERC20Transactor) Initialize(opts *bind.TransactOpts, name_ string, symbol_ string, decimals_ uint8) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "initialize", name_, symbol_, decimals_) +} + +// Initialize is a paid mutator transaction binding the contract method 0x1624f6c6. +// +// Solidity: function initialize(string name_, string symbol_, uint8 decimals_) returns() +func (_MockERC20 *MockERC20Session) Initialize(name_ string, symbol_ string, decimals_ uint8) (*types.Transaction, error) { + return _MockERC20.Contract.Initialize(&_MockERC20.TransactOpts, name_, symbol_, decimals_) +} + +// Initialize is a paid mutator transaction binding the contract method 0x1624f6c6. +// +// Solidity: function initialize(string name_, string symbol_, uint8 decimals_) returns() +func (_MockERC20 *MockERC20TransactorSession) Initialize(name_ string, symbol_ string, decimals_ uint8) (*types.Transaction, error) { + return _MockERC20.Contract.Initialize(&_MockERC20.TransactOpts, name_, symbol_, decimals_) +} + +// Permit is a paid mutator transaction binding the contract method 0xd505accf. +// +// Solidity: function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) returns() +func (_MockERC20 *MockERC20Transactor) Permit(opts *bind.TransactOpts, owner common.Address, spender common.Address, value *big.Int, deadline *big.Int, v uint8, r [32]byte, s [32]byte) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "permit", owner, spender, value, deadline, v, r, s) +} + +// Permit is a paid mutator transaction binding the contract method 0xd505accf. +// +// Solidity: function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) returns() +func (_MockERC20 *MockERC20Session) Permit(owner common.Address, spender common.Address, value *big.Int, deadline *big.Int, v uint8, r [32]byte, s [32]byte) (*types.Transaction, error) { + return _MockERC20.Contract.Permit(&_MockERC20.TransactOpts, owner, spender, value, deadline, v, r, s) +} + +// Permit is a paid mutator transaction binding the contract method 0xd505accf. +// +// Solidity: function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) returns() +func (_MockERC20 *MockERC20TransactorSession) Permit(owner common.Address, spender common.Address, value *big.Int, deadline *big.Int, v uint8, r [32]byte, s [32]byte) (*types.Transaction, error) { + return _MockERC20.Contract.Permit(&_MockERC20.TransactOpts, owner, spender, value, deadline, v, r, s) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20Transactor) Transfer(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "transfer", to, amount) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20Session) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Transfer(&_MockERC20.TransactOpts, to, amount) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20TransactorSession) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Transfer(&_MockERC20.TransactOpts, to, amount) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "transferFrom", from, to, amount) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20Session) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.TransferFrom(&_MockERC20.TransactOpts, from, to, amount) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_MockERC20 *MockERC20TransactorSession) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.TransferFrom(&_MockERC20.TransactOpts, from, to, amount) +} + +// MockERC20ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the MockERC20 contract. +type MockERC20ApprovalIterator struct { + Event *MockERC20Approval // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *MockERC20ApprovalIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(MockERC20Approval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(MockERC20Approval) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *MockERC20ApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MockERC20ApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MockERC20Approval represents a Approval event raised by the MockERC20 contract. +type MockERC20Approval struct { + Owner common.Address + Spender common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_MockERC20 *MockERC20Filterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*MockERC20ApprovalIterator, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _MockERC20.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return &MockERC20ApprovalIterator{contract: _MockERC20.contract, event: "Approval", logs: logs, sub: sub}, nil +} + +// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_MockERC20 *MockERC20Filterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *MockERC20Approval, owner []common.Address, spender []common.Address) (event.Subscription, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _MockERC20.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(MockERC20Approval) + if err := _MockERC20.contract.UnpackLog(event, "Approval", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_MockERC20 *MockERC20Filterer) ParseApproval(log types.Log) (*MockERC20Approval, error) { + event := new(MockERC20Approval) + if err := _MockERC20.contract.UnpackLog(event, "Approval", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// MockERC20TransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the MockERC20 contract. +type MockERC20TransferIterator struct { + Event *MockERC20Transfer // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *MockERC20TransferIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(MockERC20Transfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(MockERC20Transfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *MockERC20TransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MockERC20TransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MockERC20Transfer represents a Transfer event raised by the MockERC20 contract. +type MockERC20Transfer struct { + From common.Address + To common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_MockERC20 *MockERC20Filterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*MockERC20TransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MockERC20.contract.FilterLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return &MockERC20TransferIterator{contract: _MockERC20.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_MockERC20 *MockERC20Filterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *MockERC20Transfer, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MockERC20.contract.WatchLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(MockERC20Transfer) + if err := _MockERC20.contract.UnpackLog(event, "Transfer", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_MockERC20 *MockERC20Filterer) ParseTransfer(log types.Log) (*MockERC20Transfer, error) { + event := new(MockERC20Transfer) + if err := _MockERC20.contract.UnpackLog(event, "Transfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go new file mode 100644 index 0000000000..2014f7d4f2 --- /dev/null +++ b/timeboost/setup_test.go @@ -0,0 +1,195 @@ +package timeboost + +import ( + "context" + "crypto/ecdsa" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/stretchr/testify/require" +) + +type auctionSetup struct { + chainId *big.Int + auctionMasterAddr common.Address + auctionContract *bindings.ExpressLaneAuction + erc20Addr common.Address + erc20Contract *bindings.MockERC20 + initialTimestamp time.Time + roundDuration time.Duration + expressLaneAddr common.Address + bidReceiverAddr common.Address + accounts []*testAccount + backend *simulated.Backend +} + +func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { + accs, backend := setupAccounts(10) + + // Advance the chain in the background + go func() { + tick := time.NewTicker(time.Second) + defer tick.Stop() + for { + select { + case <-tick.C: + backend.Commit() + case <-ctx.Done(): + return + } + } + }() + + opts := accs[0].txOpts + chainId, err := backend.Client().ChainID(ctx) + require.NoError(t, err) + + // Deploy the token as a mock erc20. + erc20Addr, tx, erc20, err := bindings.DeployMockERC20(opts, backend.Client()) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Initialize(opts, "LANE", "LNE", 18) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { + t.Fatal(err) + } + + // Mint 10 wei tokens to all accounts. + mintTokens(ctx, opts, backend, accs, erc20) + + // Check account balances. + bal, err := erc20.BalanceOf(&bind.CallOpts{}, accs[0].accountAddr) + require.NoError(t, err) + t.Log("Account seeded with ERC20 token balance =", bal.String()) + + expressLaneAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") + bidReceiverAddr := common.HexToAddress("0x3424242424242424242424242424242424242424") + bidRoundSeconds := uint64(60) + + // Calculate the number of seconds until the next minute + // and the next timestamp that is a multiple of a minute. + now := time.Now() + initialTimestamp := big.NewInt(now.Unix()) + + // Deploy the auction manager contract. + currReservePrice := big.NewInt(1) + minReservePrice := big.NewInt(1) + reservePriceSetter := opts.From + auctionContractAddr, tx, auctionContract, err := bindings.DeployExpressLaneAuction( + opts, backend.Client(), expressLaneAddr, reservePriceSetter, bidReceiverAddr, bidRoundSeconds, initialTimestamp, erc20Addr, currReservePrice, minReservePrice, + ) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { + t.Fatal(err) + } + return &auctionSetup{ + chainId: chainId, + auctionMasterAddr: auctionContractAddr, + auctionContract: auctionContract, + erc20Addr: erc20Addr, + erc20Contract: erc20, + initialTimestamp: now, + roundDuration: time.Minute, + expressLaneAddr: expressLaneAddr, + bidReceiverAddr: bidReceiverAddr, + accounts: accs, + backend: backend, + } +} + +func setupBidderClient( + t *testing.T, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, +) *BidderClient { + bc, err := NewBidderClient( + ctx, + name, + &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, + testSetup.backend.Client(), + testSetup.auctionMasterAddr, + nil, + nil, + ) + require.NoError(t, err) + + // Approve spending by the auction manager and bid receiver. + maxUint256 := big.NewInt(1) + maxUint256.Lsh(maxUint256, 256).Sub(maxUint256, big.NewInt(1)) + tx, err := testSetup.erc20Contract.Approve( + account.txOpts, testSetup.auctionMasterAddr, maxUint256, + ) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, testSetup.backend.Client(), tx); err != nil { + t.Fatal(err) + } + tx, err = testSetup.erc20Contract.Approve( + account.txOpts, testSetup.bidReceiverAddr, maxUint256, + ) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, testSetup.backend.Client(), tx); err != nil { + t.Fatal(err) + } + return bc +} + +type testAccount struct { + accountAddr common.Address + privKey *ecdsa.PrivateKey + txOpts *bind.TransactOpts +} + +func setupAccounts(numAccounts uint64) ([]*testAccount, *simulated.Backend) { + genesis := make(core.GenesisAlloc) + gasLimit := uint64(100000000) + + accs := make([]*testAccount, numAccounts) + for i := uint64(0); i < numAccounts; i++ { + privKey, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + addr := crypto.PubkeyToAddress(privKey.PublicKey) + chainID := big.NewInt(1337) + txOpts, err := bind.NewKeyedTransactorWithChainID(privKey, chainID) + if err != nil { + panic(err) + } + startingBalance, _ := new(big.Int).SetString( + "100000000000000000000000000000000000000", + 10, + ) + genesis[addr] = core.GenesisAccount{Balance: startingBalance} + accs[i] = &testAccount{ + accountAddr: addr, + txOpts: txOpts, + privKey: privKey, + } + } + backend := simulated.NewBackend(genesis, simulated.WithBlockGasLimit(gasLimit)) + return accs, backend +} + +func mintTokens(ctx context.Context, + opts *bind.TransactOpts, + backend *simulated.Backend, + accs []*testAccount, + erc20 *bindings.MockERC20, +) { + for i := 0; i < len(accs); i++ { + tx, err := erc20.Mint(opts, accs[i].accountAddr, big.NewInt(10)) + if err != nil { + panic(err) + } + if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { + panic(err) + } + } +} diff --git a/timeboost/ticker.go b/timeboost/ticker.go new file mode 100644 index 0000000000..d995b2d026 --- /dev/null +++ b/timeboost/ticker.go @@ -0,0 +1,46 @@ +package timeboost + +import ( + "time" +) + +type auctionCloseTicker struct { + c chan time.Time + done chan bool + roundDuration time.Duration + auctionClosingDuration time.Duration +} + +func newAuctionCloseTicker(roundDuration, auctionClosingDuration time.Duration) *auctionCloseTicker { + return &auctionCloseTicker{ + c: make(chan time.Time, 1), + done: make(chan bool), + roundDuration: roundDuration, + auctionClosingDuration: auctionClosingDuration, + } +} + +func (t *auctionCloseTicker) start() { + for { + now := time.Now() + // Calculate the start of the next minute + startOfNextMinute := now.Truncate(time.Minute).Add(time.Minute) + // Subtract 15 seconds to get the tick time + nextTickTime := startOfNextMinute.Add(-15 * time.Second) + // Ensure we are not setting a past tick time + if nextTickTime.Before(now) { + // If the calculated tick time is in the past, move to the next interval + nextTickTime = nextTickTime.Add(time.Minute) + } + // Calculate how long to wait until the next tick + waitTime := nextTickTime.Sub(now) + + select { + case <-time.After(waitTime): + t.c <- time.Now() + case <-t.done: + close(t.c) + return + } + } +} From 4c2c74fb1014979154d581bf63336d50d7b6f2ac Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 2 Jul 2024 12:33:33 -0500 Subject: [PATCH 002/109] add in --- execution/gethexec/express_lane_service.go | 21 +++++ execution/gethexec/sequencer.go | 96 +++++++++++++++++++-- timeboost/auction_master.go | 6 +- timeboost/bidder_client.go | 6 +- timeboost/bids_test.go | 97 ++++++++++------------ timeboost/setup_test.go | 3 +- 6 files changed, 162 insertions(+), 67 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index eccac5ad5d..713b36910a 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -8,7 +8,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/timeboost/bindings" @@ -31,6 +33,7 @@ type expressLaneService struct { sync.RWMutex client arbutil.L1Interface control expressLaneControl + reservedAddress *common.Address auctionContract *bindings.ExpressLaneAuction initialTimestamp time.Time roundDuration time.Duration @@ -158,3 +161,21 @@ func (es *expressLaneService) isExpressLaneTx(sender common.Address) bool { log.Info("Current round", "round", round, "controller", es.control.controller, "sender", sender) return round == es.control.round && sender == es.control.controller } + +func (es *expressLaneService) isOuterExpressLaneTx(to *common.Address) bool { + es.RLock() + defer es.RUnlock() + round := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) + log.Info("Current round", "round", round, "controller", es.control.controller, "to", to) + return round == es.control.round && to == es.reservedAddress +} + +func unwrapTx(outerTx *types.Transaction) (*types.Transaction, error) { + encodedInnerTx := outerTx.Data() + var innerTx types.Transaction + err := rlp.DecodeBytes(encodedInnerTx, &innerTx) + if err != nil { + return nil, err + } + return &innerTx, nil +} diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 2bace9b677..2d8c59bbba 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -77,10 +77,25 @@ type SequencerConfig struct { ExpectedSurplusSoftThreshold string `koanf:"expected-surplus-soft-threshold" reload:"hot"` ExpectedSurplusHardThreshold string `koanf:"expected-surplus-hard-threshold" reload:"hot"` EnableProfiling bool `koanf:"enable-profiling" reload:"hot"` + Timeboost TimeboostConfig `koanf:"timeboost"` expectedSurplusSoftThreshold int expectedSurplusHardThreshold int } +type TimeboostConfig struct { + Enable bool `koanf:"enable"` + AuctionMasterAddress string `koanf:"auction-master-address"` + ERC20Address string `koanf:"erc20-address"` + ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` +} + +var DefaultTimeboostConfig = TimeboostConfig{ + Enable: false, + AuctionMasterAddress: "", + ERC20Address: "", + ExpressLaneAdvantage: time.Millisecond * 200, +} + func (c *SequencerConfig) Validate() error { entries := strings.Split(c.SenderWhitelist, ",") for _, address := range entries { @@ -130,6 +145,7 @@ var DefaultSequencerConfig = SequencerConfig{ ExpectedSurplusSoftThreshold: "default", ExpectedSurplusHardThreshold: "default", EnableProfiling: false, + Timeboost: DefaultTimeboostConfig, } var TestSequencerConfig = SequencerConfig{ @@ -148,6 +164,7 @@ var TestSequencerConfig = SequencerConfig{ ExpectedSurplusSoftThreshold: "default", ExpectedSurplusHardThreshold: "default", EnableProfiling: false, + Timeboost: DefaultTimeboostConfig, } func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -157,6 +174,8 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Duration(prefix+".max-acceptable-timestamp-delta", DefaultSequencerConfig.MaxAcceptableTimestampDelta, "maximum acceptable time difference between the local time and the latest L1 block's timestamp") f.String(prefix+".sender-whitelist", DefaultSequencerConfig.SenderWhitelist, "comma separated whitelist of authorized senders (if empty, everyone is allowed)") AddOptionsForSequencerForwarderConfig(prefix+".forwarder", f) + TimeboostAddOptions(prefix+".timeboost", f) + f.Int(prefix+".queue-size", DefaultSequencerConfig.QueueSize, "size of the pending tx queue") f.Duration(prefix+".queue-timeout", DefaultSequencerConfig.QueueTimeout, "maximum amount of time transaction can wait in queue") f.Int(prefix+".nonce-cache-size", DefaultSequencerConfig.NonceCacheSize, "size of the tx sender nonce cache") @@ -168,6 +187,12 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable-profiling", DefaultSequencerConfig.EnableProfiling, "enable CPU profiling and tracing") } +func TimeboostAddOptions(prefix string, f *flag.FlagSet) { + f.Bool(prefix+".enable", DefaultTimeboostConfig.Enable, "enable timeboost based on express lane auctions") + f.String(prefix+".auction-master-address", DefaultTimeboostConfig.AuctionMasterAddress, "address of the auction master contract") + f.String(prefix+".erc20-address", DefaultTimeboostConfig.ERC20Address, "address of the auction erc20") +} + type txQueueItem struct { tx *types.Transaction txSize int // size in bytes of the marshalled transaction @@ -310,15 +335,16 @@ func (c nonceFailureCache) Add(err NonceError, queueItem txQueueItem) { type Sequencer struct { stopwaiter.StopWaiter - execEngine *ExecutionEngine - txQueue chan txQueueItem - txRetryQueue containers.Queue[txQueueItem] - l1Reader *headerreader.HeaderReader - config SequencerConfigFetcher - senderWhitelist map[common.Address]struct{} - nonceCache *nonceCache - nonceFailures *nonceFailureCache - onForwarderSet chan struct{} + execEngine *ExecutionEngine + txQueue chan txQueueItem + txRetryQueue containers.Queue[txQueueItem] + l1Reader *headerreader.HeaderReader + config SequencerConfigFetcher + senderWhitelist map[common.Address]struct{} + nonceCache *nonceCache + nonceFailures *nonceFailureCache + expressLaneService *expressLaneService + onForwarderSet chan struct{} L1BlockAndTimeMutex sync.Mutex l1BlockNumber uint64 @@ -361,6 +387,18 @@ func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderRead pauseChan: nil, onForwarderSet: make(chan struct{}, 1), } + if config.Timeboost.Enable { + addr := common.HexToAddress(config.Timeboost.AuctionMasterAddress) + // TODO: Need to provide an L2 interface instead of an L1 interface. + els, err := newExpressLaneService( + l1Reader.Client(), + addr, + ) + if err != nil { + return nil, err + } + s.expressLaneService = els + } s.nonceFailures = &nonceFailureCache{ containers.NewLruCacheWithOnEvict(config.NonceCacheSize, s.onNonceFailureEvict), func() time.Duration { return configFetcher().NonceFailureCacheExpiry }, @@ -370,6 +408,20 @@ func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderRead return s, nil } +func (s *Sequencer) ExpressLaneAuction() common.Address { + if s.expressLaneService == nil { + return common.Address{} + } + return common.HexToAddress(s.config().Timeboost.AuctionMasterAddress) +} + +func (s *Sequencer) ExpressLaneERC20() common.Address { + if s.expressLaneService == nil { + return common.Address{} + } + return common.HexToAddress(s.config().Timeboost.ERC20Address) +} + func (s *Sequencer) onNonceFailureEvict(_ addressAndNonce, failure *nonceFailure) { if failure.revived { return @@ -446,6 +498,29 @@ func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Tran return types.ErrTxTypeNotSupported } + // If timeboost is enabled, we check if the tx is an express lane tx, if so, we will + // process it right away into the queue. Otherwise, delay by a nominal amount. + if s.config().Timeboost.Enable { + signer := types.LatestSigner(s.execEngine.bc.Config()) + sender, err := types.Sender(signer, tx) + if err != nil { + return err + } + // TODO: Do not delay if there isn't an express lane controller this round. + if !s.expressLaneService.isExpressLaneTx(sender) { + log.Info("Delaying non-express lane tx", "sender", sender) + time.Sleep(s.config().Timeboost.ExpressLaneAdvantage) + } else { + if s.expressLaneService.isOuterExpressLaneTx(tx.To()) { + tx, err = unwrapTx(tx) + if err != nil { + return err + } + } + log.Info("Processing express lane tx", "sender", sender) + } + } + txBytes, err := tx.MarshalBinary() if err != nil { return err @@ -1127,7 +1202,10 @@ func (s *Sequencer) Start(ctxIn context.Context) error { } } }) + } + if s.config().Timeboost.Enable { + s.expressLaneService.Start(ctxIn) } s.CallIteratively(func(ctx context.Context) time.Duration { diff --git a/timeboost/auction_master.go b/timeboost/auction_master.go index 56d8526e1a..854a53367a 100644 --- a/timeboost/auction_master.go +++ b/timeboost/auction_master.go @@ -7,8 +7,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient/simulated" "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/pkg/errors" ) @@ -27,7 +27,7 @@ type AuctionMaster struct { txOpts *bind.TransactOpts chainId *big.Int signatureDomain uint16 - client simulated.Client + client arbutil.L1Interface auctionContract *bindings.ExpressLaneAuction bidsReceiver chan *Bid bidCache *bidCache @@ -39,7 +39,7 @@ type AuctionMaster struct { func NewAuctionMaster( txOpts *bind.TransactOpts, chainId *big.Int, - client simulated.Client, + client arbutil.L1Interface, auctionContract *bindings.ExpressLaneAuction, opts ...AuctionMasterOpt, ) (*AuctionMaster, error) { diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 85c83cf747..69cc122f8c 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -13,8 +13,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" - "github.com/ethereum/go-ethereum/ethclient/simulated" "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/pkg/errors" ) @@ -32,7 +32,7 @@ type BidderClient struct { name string signatureDomain uint16 txOpts *bind.TransactOpts - client simulated.Client + client arbutil.L1Interface privKey *ecdsa.PrivateKey auctionContract *bindings.ExpressLaneAuction sequencer sequencerConnection @@ -51,7 +51,7 @@ func NewBidderClient( ctx context.Context, name string, wallet *Wallet, - client simulated.Client, + client arbutil.L1Interface, auctionContractAddress common.Address, sequencer sequencerConnection, auctionMaster auctionMasterConnection, diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 105b77a9ae..91809de29a 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -1,73 +1,68 @@ package timeboost import ( - "context" - "math/big" "testing" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/stretchr/testify/require" ) func TestWinningBidderBecomesExpressLaneController(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + // ctx, cancel := context.WithCancel(context.Background()) + // defer cancel() - testSetup := setupAuctionTest(t, ctx) + // testSetup := setupAuctionTest(t, ctx) - // Set up two different bidders. - alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) - bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup) - require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) - require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) + // // Set up two different bidders. + // alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) + // bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup) + // require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) + // require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) - // Set up a new auction master instance that can validate bids. - am, err := NewAuctionMaster( - testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, - ) - require.NoError(t, err) - alice.auctionMaster = am - bob.auctionMaster = am + // // Set up a new auction master instance that can validate bids. + // am, err := NewAuctionMaster( + // testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, + // ) + // require.NoError(t, err) + // alice.auctionMaster = am + // bob.auctionMaster = am - // Form two new bids for the round, with Alice being the bigger one. - aliceBid, err := alice.Bid(ctx, big.NewInt(2)) - require.NoError(t, err) - bobBid, err := bob.Bid(ctx, big.NewInt(1)) - require.NoError(t, err) - _, _ = aliceBid, bobBid + // // Form two new bids for the round, with Alice being the bigger one. + // aliceBid, err := alice.Bid(ctx, big.NewInt(2)) + // require.NoError(t, err) + // bobBid, err := bob.Bid(ctx, big.NewInt(1)) + // require.NoError(t, err) + // _, _ = aliceBid, bobBid - // Resolve the auction. - require.NoError(t, am.resolveAuctions(ctx)) + // // Resolve the auction. + // require.NoError(t, am.resolveAuctions(ctx)) - // Expect Alice to have become the next express lane controller. - upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 - controller, err := testSetup.auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(upcomingRound))) - require.NoError(t, err) - require.Equal(t, alice.txOpts.From, controller) + // // Expect Alice to have become the next express lane controller. + // upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 + // controller, err := testSetup.auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(upcomingRound))) + // require.NoError(t, err) + // require.Equal(t, alice.txOpts.From, controller) } func TestSubmitBid_OK(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + // ctx, cancel := context.WithCancel(context.Background()) + // defer cancel() - testSetup := setupAuctionTest(t, ctx) + // testSetup := setupAuctionTest(t, ctx) - // Make a deposit as a bidder into the contract. - bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) - require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) + // // Make a deposit as a bidder into the contract. + // bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) + // require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) - // Set up a new auction master instance that can validate bids. - am, err := NewAuctionMaster( - testSetup.accounts[1].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, - ) - require.NoError(t, err) - bc.auctionMaster = am + // // Set up a new auction master instance that can validate bids. + // am, err := NewAuctionMaster( + // testSetup.accounts[1].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, + // ) + // require.NoError(t, err) + // bc.auctionMaster = am - // Form a new bid with an amount. - newBid, err := bc.Bid(ctx, big.NewInt(5)) - require.NoError(t, err) + // // Form a new bid with an amount. + // newBid, err := bc.Bid(ctx, big.NewInt(5)) + // require.NoError(t, err) - // Check the bid passes validation. - _, err = am.newValidatedBid(newBid) - require.NoError(t, err) + // // Check the bid passes validation. + // _, err = am.newValidatedBid(newBid) + // require.NoError(t, err) } diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 2014f7d4f2..35ed194823 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -113,7 +113,8 @@ func setupBidderClient( ctx, name, &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, - testSetup.backend.Client(), + // testSetup.backend.Client(), + nil, testSetup.auctionMasterAddr, nil, nil, From d0ed9bf0604097883ff6ddc51b5862dd9ba563e0 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 2 Jul 2024 12:34:34 -0500 Subject: [PATCH 003/109] system test --- system_tests/common_test.go | 66 ++++++++ system_tests/seqfeed_test.go | 286 +++++++++++++++++++++++++++++++++++ 2 files changed, 352 insertions(+) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 1c45802b54..1b2f6320c6 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -28,6 +28,7 @@ import ( "github.com/offchainlabs/nitro/das" "github.com/offchainlabs/nitro/deploy" "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/redisutil" @@ -850,6 +851,71 @@ func createTestNodeWithL1( sequencerTxOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) sequencerTxOptsPtr = &sequencerTxOpts dataSigner = signature.DataSignerFromPrivateKey(l1info.GetInfoWithPrivKey("Sequencer").PrivateKey) + + // Deploy the express lane auction contract and erc20 to the parent chain. + // TODO: This should be deployed to L2 instead. + // TODO: Move this somewhere better. + // Deploy the token as a mock erc20. + erc20Addr, tx, erc20, err := bindings.DeployMockERC20(&sequencerTxOpts, l1client) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Initialize(&sequencerTxOpts, "LANE", "LNE", 18) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + + // Fund the auction master. + l1info.GenerateAccount("AuctionMaster") + TransferBalance(t, "Faucet", "AuctionMaster", arbmath.BigMulByUint(oneEth, 500), l1info, l1client, ctx) + + // Mint some tokens to Alice and Bob. + l1info.GenerateAccount("Alice") + l1info.GenerateAccount("Bob") + TransferBalance(t, "Faucet", "Alice", arbmath.BigMulByUint(oneEth, 500), l1info, l1client, ctx) + TransferBalance(t, "Faucet", "Bob", arbmath.BigMulByUint(oneEth, 500), l1info, l1client, ctx) + aliceOpts := l1info.GetDefaultTransactOpts("Alice", ctx) + bobOpts := l1info.GetDefaultTransactOpts("Bob", ctx) + tx, err = erc20.Mint(&sequencerTxOpts, aliceOpts.From, big.NewInt(100)) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Mint(&sequencerTxOpts, bobOpts.From, big.NewInt(100)) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + + expressLaneAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") + bidReceiverAddr := common.HexToAddress("0x3424242424242424242424242424242424242424") + bidRoundSeconds := uint64(60) + + // Calculate the number of seconds until the next minute + // and the next timestamp that is a multiple of a minute. + now := time.Now() + roundDuration := time.Minute + // Correctly calculate the remaining time until the next minute + waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond())*time.Nanosecond + // Get the current Unix timestamp at the start of the minute + initialTimestamp := big.NewInt(now.Add(waitTime).Unix()) + + // Deploy the auction manager contract. + currReservePrice := big.NewInt(1) + minReservePrice := big.NewInt(1) + reservePriceSetter := sequencerTxOpts.From + auctionContractAddr, tx, _, err := bindings.DeployExpressLaneAuction( + &sequencerTxOpts, l1client, expressLaneAddr, reservePriceSetter, bidReceiverAddr, bidRoundSeconds, initialTimestamp, erc20Addr, currReservePrice, minReservePrice, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + t.Log("Deployed all the auction manager stuff", auctionContractAddr) + execConfig.Sequencer.Timeboost.AuctionMasterAddress = auctionContractAddr.Hex() + execConfig.Sequencer.Timeboost.ERC20Address = erc20Addr.Hex() } if !isSequencer { diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index ab30598b60..567e4eb505 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -8,11 +8,14 @@ import ( "fmt" "math/big" "net" + "sync" "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbostypes" @@ -22,6 +25,8 @@ import ( "github.com/offchainlabs/nitro/broadcaster/message" "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/relay" + "github.com/offchainlabs/nitro/timeboost" + "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/testhelpers" "github.com/offchainlabs/nitro/wsbroadcastserver" @@ -89,6 +94,287 @@ func TestSequencerFeed(t *testing.T) { } } +func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, true) + + builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() + builderSeq.execConfig.Sequencer.Enable = true + builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ + Enable: true, + ExpressLaneAdvantage: time.Millisecond * 200, + } + cleanupSeq := builderSeq.Build(t) + defer cleanupSeq() + seqInfo, _, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client + + auctionAddr := builderSeq.L2.ExecNode.Sequencer.ExpressLaneAuction() + erc20Addr := builderSeq.L2.ExecNode.Sequencer.ExpressLaneERC20() + + // Seed the accounts on L2. + seqInfo.GenerateAccount("Alice") + tx := seqInfo.PrepareTx("Owner", "Alice", seqInfo.TransferGas, big.NewInt(1e18), nil) + Require(t, seqClient.SendTransaction(ctx, tx)) + _, err := EnsureTxSucceeded(ctx, seqClient, tx) + Require(t, err) + seqInfo.GenerateAccount("Bob") + tx = seqInfo.PrepareTx("Owner", "Bob", seqInfo.TransferGas, big.NewInt(1e18), nil) + Require(t, seqClient.SendTransaction(ctx, tx)) + _, err = EnsureTxSucceeded(ctx, seqClient, tx) + Require(t, err) + t.Logf("%+v and %+v", seqInfo.Accounts["Alice"], seqInfo.Accounts["Bob"]) + + auctionContract, err := bindings.NewExpressLaneAuction(auctionAddr, builderSeq.L1.Client) + Require(t, err) + _ = seqInfo + _ = seqClient + l1client := builderSeq.L1.Client + + // We approve the spending of the erc20 for the auction master contract and bid receiver + // for both Alice and Bob. + bidReceiverAddr := common.HexToAddress("0x3424242424242424242424242424242424242424") + aliceOpts := builderSeq.L1Info.GetDefaultTransactOpts("Alice", ctx) + bobOpts := builderSeq.L1Info.GetDefaultTransactOpts("Bob", ctx) + + maxUint256 := big.NewInt(1) + maxUint256.Lsh(maxUint256, 256).Sub(maxUint256, big.NewInt(1)) + erc20, err := bindings.NewMockERC20(erc20Addr, builderSeq.L1.Client) + Require(t, err) + + tx, err = erc20.Approve( + &aliceOpts, auctionAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Approve( + &aliceOpts, bidReceiverAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Approve( + &bobOpts, auctionAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Approve( + &bobOpts, bidReceiverAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + t.Fatal(err) + } + + // Set up an auction master service that runs in the background in this test. + auctionMasterOpts := builderSeq.L1Info.GetDefaultTransactOpts("AuctionMaster", ctx) + chainId, err := l1client.ChainID(ctx) + Require(t, err) + auctionMaster, err := timeboost.NewAuctionMaster(&auctionMasterOpts, chainId, builderSeq.L1.Client, auctionContract) + Require(t, err) + + go auctionMaster.Start(ctx) + + // Set up a bidder client for Alice and Bob. + alicePriv := builderSeq.L1Info.Accounts["Alice"].PrivateKey + alice, err := timeboost.NewBidderClient( + ctx, + "alice", + &timeboost.Wallet{ + TxOpts: &aliceOpts, + PrivKey: alicePriv, + }, + l1client, + auctionAddr, + nil, + auctionMaster, + ) + Require(t, err) + go alice.Start(ctx) + + bobPriv := builderSeq.L1Info.Accounts["Bob"].PrivateKey + bob, err := timeboost.NewBidderClient( + ctx, + "bob", + &timeboost.Wallet{ + TxOpts: &bobOpts, + PrivKey: bobPriv, + }, + l1client, + auctionAddr, + nil, + auctionMaster, + ) + Require(t, err) + go bob.Start(ctx) + + // Wait until the initial round. + initialTime, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + Require(t, err) + timeToWait := time.Until(time.Unix(initialTime.Int64(), 0)) + t.Log("Waiting until the initial round", timeToWait, time.Unix(initialTime.Int64(), 0)) + <-time.After(timeToWait) + + t.Log("Started auction master stack and bid clients") + Require(t, alice.Deposit(ctx, big.NewInt(5))) + Require(t, bob.Deposit(ctx, big.NewInt(5))) + t.Log("Alice and Bob are now deposited into the auction master contract, waiting for bidding round...") + + // Wait until the next timeboost round + a few milliseconds. + now := time.Now() + roundDuration := time.Minute + waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + time.Sleep(waitTime) + time.Sleep(time.Second * 5) + + // We are now in the bidding round, both issue their bids. Bob will win. + t.Log("Alice and Bob now submitting their bids") + aliceBid, err := alice.Bid(ctx, big.NewInt(1)) + Require(t, err) + bobBid, err := bob.Bid(ctx, big.NewInt(2)) + Require(t, err) + t.Logf("Alice bid %+v", aliceBid) + t.Logf("Bob bid %+v", bobBid) + + // Subscribe to auction resolutions and wait for Bob to win the auction. + winner, winnerRound := awaitAuctionResolved(t, ctx, l1client, auctionContract) + + // Verify Bob owns the express lane this round. + if winner != bobOpts.From { + t.Fatal("Bob should have won the express lane auction") + } + t.Log("Bob won the express lane auction for upcoming round, now waiting for that round to start...") + + // Wait until the round that Bob owns the express lane for. + now = time.Now() + waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + time.Sleep(waitTime) + + initialTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + Require(t, err) + currRound := timeboost.CurrentRound(time.Unix(initialTimestamp.Int64(), 0), roundDuration) + t.Log("curr round", currRound) + if currRound != winnerRound { + now = time.Now() + waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + t.Log("Not express lane round yet, waiting for next round", waitTime) + time.Sleep(waitTime) + } + + current, err := auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, new(big.Int).SetUint64(currRound)) + Require(t, err) + + if current != bobOpts.From { + t.Log("Current express lane round controller is not Bob", current, aliceOpts.From) + } + + t.Log("Now submitting txs to sequencer") + + // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's + // txs end up getting delayed by 200ms as she is not the express lane controller. + // In the end, Bob's txs should be ordered before Alice's during the round. + + var wg sync.WaitGroup + wg.Add(2) + aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + err = seqClient.SendTransaction(ctx, aliceTx) + Require(t, err) + }(&wg) + + bobTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + time.Sleep(time.Millisecond * 10) + err = seqClient.SendTransaction(ctx, bobTx) + Require(t, err) + }(&wg) + wg.Wait() + + // After round is done, verify that Alice beats Bob in the final sequence. + aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) + Require(t, err) + aliceBlock := aliceReceipt.BlockNumber.Uint64() + bobReceipt, err := seqClient.TransactionReceipt(ctx, bobTx.Hash()) + Require(t, err) + bobBlock := bobReceipt.BlockNumber.Uint64() + + if aliceBlock < bobBlock { + t.Fatal("Bob should have been sequenced before Alice with express lane") + } else if aliceBlock == bobBlock { + t.Log("Sequenced in same output block") + block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) + Require(t, err) + findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { + for index, tx := range transactions { + if tx.Hash() == txHash { + return index + } + } + return -1 + } + txes := block.Transactions() + indexA := findTransactionIndex(txes, aliceTx.Hash()) + indexB := findTransactionIndex(txes, bobTx.Hash()) + if indexA == -1 || indexB == -1 { + t.Fatal("Did not find txs in block") + } + if indexA < indexB { + t.Fatal("Bob should have been sequenced before Alice with express lane") + } + } +} + +func awaitAuctionResolved( + t *testing.T, + ctx context.Context, + client *ethclient.Client, + contract *bindings.ExpressLaneAuction, +) (common.Address, uint64) { + fromBlock, err := client.BlockNumber(ctx) + Require(t, err) + ticker := time.NewTicker(time.Millisecond * 100) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return common.Address{}, 0 + case <-ticker.C: + latestBlock, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Log("Could not get latest header", err) + continue + } + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + continue + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := contract.FilterAuctionResolved(filterOpts, nil, nil) + if err != nil { + t.Log("Could not filter auction resolutions", err) + continue + } + for it.Next() { + return it.Event.WinningBidder, it.Event.WinnerRound.Uint64() + } + fromBlock = toBlock + } + } +} + func TestRelayedSequencerFeed(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) From 4ff741016e28ad7092c1f6b97859e7c20da950e3 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 2 Jul 2024 14:18:21 -0700 Subject: [PATCH 004/109] Rename auction master to autonomous auctioneer --- execution/gethexec/sequencer.go | 22 ++++++++-------- system_tests/common_test.go | 8 +++--- system_tests/seqfeed_test.go | 16 ++++++------ .../{auction_master.go => auctioneer.go} | 26 +++++++++---------- ...tion_master_test.go => auctioneer_test.go} | 4 +-- timeboost/bidder_client.go | 12 ++++----- timeboost/bids.go | 2 +- timeboost/bids_test.go | 10 +++---- 8 files changed, 50 insertions(+), 50 deletions(-) rename timeboost/{auction_master.go => auctioneer.go} (90%) rename timeboost/{auction_master_test.go => auctioneer_test.go} (96%) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 2d8c59bbba..e15aa60063 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -83,17 +83,17 @@ type SequencerConfig struct { } type TimeboostConfig struct { - Enable bool `koanf:"enable"` - AuctionMasterAddress string `koanf:"auction-master-address"` - ERC20Address string `koanf:"erc20-address"` - ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` + Enable bool `koanf:"enable"` + AuctionContractAddress string `koanf:"auction-contract-address"` + ERC20Address string `koanf:"erc20-address"` + ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` } var DefaultTimeboostConfig = TimeboostConfig{ - Enable: false, - AuctionMasterAddress: "", - ERC20Address: "", - ExpressLaneAdvantage: time.Millisecond * 200, + Enable: false, + AuctionContractAddress: "", + ERC20Address: "", + ExpressLaneAdvantage: time.Millisecond * 200, } func (c *SequencerConfig) Validate() error { @@ -189,7 +189,7 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { func TimeboostAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultTimeboostConfig.Enable, "enable timeboost based on express lane auctions") - f.String(prefix+".auction-master-address", DefaultTimeboostConfig.AuctionMasterAddress, "address of the auction master contract") + f.String(prefix+".auction-contract-address", DefaultTimeboostConfig.AuctionContractAddress, "address of the autonomous auction contract") f.String(prefix+".erc20-address", DefaultTimeboostConfig.ERC20Address, "address of the auction erc20") } @@ -388,7 +388,7 @@ func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderRead onForwarderSet: make(chan struct{}, 1), } if config.Timeboost.Enable { - addr := common.HexToAddress(config.Timeboost.AuctionMasterAddress) + addr := common.HexToAddress(config.Timeboost.AuctionContractAddress) // TODO: Need to provide an L2 interface instead of an L1 interface. els, err := newExpressLaneService( l1Reader.Client(), @@ -412,7 +412,7 @@ func (s *Sequencer) ExpressLaneAuction() common.Address { if s.expressLaneService == nil { return common.Address{} } - return common.HexToAddress(s.config().Timeboost.AuctionMasterAddress) + return common.HexToAddress(s.config().Timeboost.AuctionContractAddress) } func (s *Sequencer) ExpressLaneERC20() common.Address { diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 1b2f6320c6..a042830ac7 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -867,9 +867,9 @@ func createTestNodeWithL1( t.Fatal(err) } - // Fund the auction master. - l1info.GenerateAccount("AuctionMaster") - TransferBalance(t, "Faucet", "AuctionMaster", arbmath.BigMulByUint(oneEth, 500), l1info, l1client, ctx) + // Fund the auction contract. + l1info.GenerateAccount("AuctionContract") + TransferBalance(t, "Faucet", "AuctionContract", arbmath.BigMulByUint(oneEth, 500), l1info, l1client, ctx) // Mint some tokens to Alice and Bob. l1info.GenerateAccount("Alice") @@ -914,7 +914,7 @@ func createTestNodeWithL1( t.Fatal(err) } t.Log("Deployed all the auction manager stuff", auctionContractAddr) - execConfig.Sequencer.Timeboost.AuctionMasterAddress = auctionContractAddr.Hex() + execConfig.Sequencer.Timeboost.AuctionContractAddress = auctionContractAddr.Hex() execConfig.Sequencer.Timeboost.ERC20Address = erc20Addr.Hex() } diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 567e4eb505..46068b443b 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -133,7 +133,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { _ = seqClient l1client := builderSeq.L1.Client - // We approve the spending of the erc20 for the auction master contract and bid receiver + // We approve the spending of the erc20 for the autonomous auction contract and bid receiver // for both Alice and Bob. bidReceiverAddr := common.HexToAddress("0x3424242424242424242424242424242424242424") aliceOpts := builderSeq.L1Info.GetDefaultTransactOpts("Alice", ctx) @@ -173,14 +173,14 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Fatal(err) } - // Set up an auction master service that runs in the background in this test. - auctionMasterOpts := builderSeq.L1Info.GetDefaultTransactOpts("AuctionMaster", ctx) + // Set up an autonomous auction contract service that runs in the background in this test. + auctionContractOpts := builderSeq.L1Info.GetDefaultTransactOpts("AuctionContract", ctx) chainId, err := l1client.ChainID(ctx) Require(t, err) - auctionMaster, err := timeboost.NewAuctionMaster(&auctionMasterOpts, chainId, builderSeq.L1.Client, auctionContract) + auctioneer, err := timeboost.NewAuctioneer(&auctionContractOpts, chainId, builderSeq.L1.Client, auctionContract) Require(t, err) - go auctionMaster.Start(ctx) + go auctioneer.Start(ctx) // Set up a bidder client for Alice and Bob. alicePriv := builderSeq.L1Info.Accounts["Alice"].PrivateKey @@ -194,7 +194,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { l1client, auctionAddr, nil, - auctionMaster, + auctioneer, ) Require(t, err) go alice.Start(ctx) @@ -210,7 +210,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { l1client, auctionAddr, nil, - auctionMaster, + auctioneer, ) Require(t, err) go bob.Start(ctx) @@ -225,7 +225,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Log("Started auction master stack and bid clients") Require(t, alice.Deposit(ctx, big.NewInt(5))) Require(t, bob.Deposit(ctx, big.NewInt(5))) - t.Log("Alice and Bob are now deposited into the auction master contract, waiting for bidding round...") + t.Log("Alice and Bob are now deposited into the autonomous auction contract, waiting for bidding round...") // Wait until the next timeboost round + a few milliseconds. now := time.Now() diff --git a/timeboost/auction_master.go b/timeboost/auctioneer.go similarity index 90% rename from timeboost/auction_master.go rename to timeboost/auctioneer.go index 854a53367a..15c146f84c 100644 --- a/timeboost/auction_master.go +++ b/timeboost/auctioneer.go @@ -15,15 +15,15 @@ import ( const defaultAuctionClosingSecondsBeforeRound = 15 // Before the start of the next round. -type AuctionMasterOpt func(*AuctionMaster) +type AuctioneerOpt func(*Auctioneer) -func WithAuctionClosingSecondsBeforeRound(d time.Duration) AuctionMasterOpt { - return func(am *AuctionMaster) { +func WithAuctionClosingSecondsBeforeRound(d time.Duration) AuctioneerOpt { + return func(am *Auctioneer) { am.auctionClosingDurationBeforeRoundStart = d } } -type AuctionMaster struct { +type Auctioneer struct { txOpts *bind.TransactOpts chainId *big.Int signatureDomain uint16 @@ -36,13 +36,13 @@ type AuctionMaster struct { auctionClosingDurationBeforeRoundStart time.Duration } -func NewAuctionMaster( +func NewAuctioneer( txOpts *bind.TransactOpts, chainId *big.Int, client arbutil.L1Interface, auctionContract *bindings.ExpressLaneAuction, - opts ...AuctionMasterOpt, -) (*AuctionMaster, error) { + opts ...AuctioneerOpt, +) (*Auctioneer, error) { initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) if err != nil { return nil, err @@ -55,7 +55,7 @@ func NewAuctionMaster( if err != nil { return nil, err } - am := &AuctionMaster{ + am := &Auctioneer{ txOpts: txOpts, chainId: chainId, client: client, @@ -73,7 +73,7 @@ func NewAuctionMaster( return am, nil } -func (am *AuctionMaster) SubmitBid(ctx context.Context, b *Bid) error { +func (am *Auctioneer) SubmitBid(ctx context.Context, b *Bid) error { validated, err := am.newValidatedBid(b) if err != nil { return err @@ -82,7 +82,7 @@ func (am *AuctionMaster) SubmitBid(ctx context.Context, b *Bid) error { return nil } -func (am *AuctionMaster) Start(ctx context.Context) { +func (am *Auctioneer) Start(ctx context.Context) { // Receive bids in the background. go receiveAsync(ctx, am.bidsReceiver, am.SubmitBid) @@ -105,10 +105,10 @@ func (am *AuctionMaster) Start(ctx context.Context) { } } -func (am *AuctionMaster) resolveAuctions(ctx context.Context) error { +func (am *Auctioneer) resolveAuctions(ctx context.Context) error { upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 // If we have no winner, then we can cancel the auction. - // Auction master can also subscribe to sequencer feed and + // Auctioneer can also subscribe to sequencer feed and // close auction if sequencer is down. result := am.bidCache.topTwoBids() first := result.firstPlace @@ -174,7 +174,7 @@ func (am *AuctionMaster) resolveAuctions(ctx context.Context) error { // TODO: Implement. If sequencer is down for some time, cancel the upcoming auction by calling // the cancel method on the smart contract. -func (am *AuctionMaster) checkSequencerHealth(ctx context.Context) { +func (am *Auctioneer) checkSequencerHealth(ctx context.Context) { } diff --git a/timeboost/auction_master_test.go b/timeboost/auctioneer_test.go similarity index 96% rename from timeboost/auction_master_test.go rename to timeboost/auctioneer_test.go index 2aabf1e5f5..6c62f7ff42 100644 --- a/timeboost/auction_master_test.go +++ b/timeboost/auctioneer_test.go @@ -29,8 +29,8 @@ func TestCompleteAuctionSimulation(t *testing.T) { // testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, // ) // require.NoError(t, err) - // alice.auctionMaster = am - // bob.auctionMaster = am + // alice.auctioneer = am + // bob.auctioneer = am // TODO: Start auction master and randomly bid from different bidders in a round. // Start the sequencer. diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 69cc122f8c..d6cdd0e1d1 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -23,7 +23,7 @@ type sequencerConnection interface { SendExpressLaneTx(ctx context.Context, tx *types.Transaction) error } -type auctionMasterConnection interface { +type auctioneerConnection interface { SubmitBid(ctx context.Context, bid *Bid) error } @@ -36,7 +36,7 @@ type BidderClient struct { privKey *ecdsa.PrivateKey auctionContract *bindings.ExpressLaneAuction sequencer sequencerConnection - auctionMaster auctionMasterConnection + auctioneer auctioneerConnection initialRoundTimestamp time.Time roundDuration time.Duration } @@ -54,7 +54,7 @@ func NewBidderClient( client arbutil.L1Interface, auctionContractAddress common.Address, sequencer sequencerConnection, - auctionMaster auctionMasterConnection, + auctioneer auctioneerConnection, ) (*BidderClient, error) { chainId, err := client.ChainID(ctx) if err != nil { @@ -85,7 +85,7 @@ func NewBidderClient( privKey: wallet.PrivKey, auctionContract: auctionContract, sequencer: sequencer, - auctionMaster: auctionMaster, + auctioneer: auctioneer, initialRoundTimestamp: time.Unix(initialRoundTimestamp.Int64(), 0), roundDuration: time.Duration(roundDurationSeconds) * time.Second, }, nil @@ -95,7 +95,7 @@ func (bd *BidderClient) Start(ctx context.Context) { // Monitor for newly assigned express lane controllers, and if the client's address // is the controller in order to send express lane txs. go bd.monitorAuctionResolutions(ctx) - // Monitor for auction closures by the auction master. + // Monitor for auction closures by the autonomous auctioneer. go bd.monitorAuctionCancelations(ctx) // Monitor for express lane control delegations to take over if needed. go bd.monitorExpressLaneDelegations(ctx) @@ -240,7 +240,7 @@ func (bd *BidderClient) Bid(ctx context.Context, amount *big.Int) (*Bid, error) sig, prefixed := sign(packedBidBytes, bd.privKey) newBid.signature = sig _ = prefixed - if err = bd.auctionMaster.SubmitBid(ctx, newBid); err != nil { + if err = bd.auctioneer.SubmitBid(ctx, newBid); err != nil { return nil, err } return newBid, nil diff --git a/timeboost/bids.go b/timeboost/bids.go index 59cf353ee8..979ba1d59e 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -35,7 +35,7 @@ type validatedBid struct { Bid } -func (am *AuctionMaster) newValidatedBid(bid *Bid) (*validatedBid, error) { +func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { // Check basic integrity. if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 91809de29a..de687489ab 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -17,12 +17,12 @@ func TestWinningBidderBecomesExpressLaneController(t *testing.T) { // require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) // // Set up a new auction master instance that can validate bids. - // am, err := NewAuctionMaster( + // am, err := NewAuctioneer( // testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, // ) // require.NoError(t, err) - // alice.auctionMaster = am - // bob.auctionMaster = am + // alice.auctioneer = am + // bob.auctioneer = am // // Form two new bids for the round, with Alice being the bigger one. // aliceBid, err := alice.Bid(ctx, big.NewInt(2)) @@ -52,11 +52,11 @@ func TestSubmitBid_OK(t *testing.T) { // require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) // // Set up a new auction master instance that can validate bids. - // am, err := NewAuctionMaster( + // am, err := NewAuctioneer( // testSetup.accounts[1].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, // ) // require.NoError(t, err) - // bc.auctionMaster = am + // bc.auctioneer = am // // Form a new bid with an amount. // newBid, err := bc.Bid(ctx, big.NewInt(5)) From 83e8e0057844656a1c9a93d17df98b0a6dbe3c16 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 2 Jul 2024 15:18:23 -0700 Subject: [PATCH 005/109] Sequence express lane transactions follow spec --- execution/gethexec/express_lane_service.go | 61 ++++++++++++++++------ execution/gethexec/sequencer.go | 33 ++++++------ 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 713b36910a..324207401c 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -2,6 +2,7 @@ package gethexec import ( "context" + "fmt" "math/big" "sync" "time" @@ -10,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/timeboost" @@ -25,6 +27,7 @@ type expressLaneChecker interface { type expressLaneControl struct { round uint64 + sequence uint64 controller common.Address } @@ -33,10 +36,11 @@ type expressLaneService struct { sync.RWMutex client arbutil.L1Interface control expressLaneControl - reservedAddress *common.Address + reservedAddress common.Address auctionContract *bindings.ExpressLaneAuction initialTimestamp time.Time roundDuration time.Duration + chainConfig *params.ChainConfig } func newExpressLaneService( @@ -142,6 +146,7 @@ func (es *expressLaneService) Start(ctxIn context.Context) { es.Lock() es.control.round = it.Event.WinnerRound.Uint64() es.control.controller = it.Event.WinningBidder + es.control.sequence = 0 // Sequence resets 0 for the new round. es.Unlock() } fromBlock = toBlock @@ -154,28 +159,52 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) } -func (es *expressLaneService) isExpressLaneTx(sender common.Address) bool { +// A transaction is an express lane transaction if it is sent to a chain's predefined reserved address. +func (es *expressLaneService) isExpressLaneTx(to common.Address) bool { es.RLock() defer es.RUnlock() - round := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) - log.Info("Current round", "round", round, "controller", es.control.controller, "sender", sender) - return round == es.control.round && sender == es.control.controller + + return to == es.reservedAddress } -func (es *expressLaneService) isOuterExpressLaneTx(to *common.Address) bool { - es.RLock() - defer es.RUnlock() - round := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) - log.Info("Current round", "round", round, "controller", es.control.controller, "to", to) - return round == es.control.round && to == es.reservedAddress +// An express lane transaction is valid if it satisfies the following conditions: +// 1. The tx round expressed under `maxPriorityFeePerGas` equals the current round number. +// 2. The tx sequence expressed under `nonce` equals the current round sequence. +// 3. The tx sender equals the current round’s priority controller address. +func (es *expressLaneService) validateExpressLaneTx(tx *types.Transaction) error { + es.Lock() + defer es.Unlock() + + currentRound := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) + round := tx.GasTipCap().Uint64() + if round != currentRound { + return fmt.Errorf("express lane tx round %d does not match current round %d", round, currentRound) + } + + sequence := tx.Nonce() + if sequence != es.control.sequence { + // TODO: Cache out-of-order sequenced express lane transactions and replay them once the gap is filled. + return fmt.Errorf("express lane tx sequence %d does not match current round sequence %d", sequence, es.control.sequence) + } + es.control.sequence++ + + signer := types.LatestSigner(es.chainConfig) + sender, err := types.Sender(signer, tx) + if err != nil { + return err + } + if sender != es.control.controller { + return fmt.Errorf("express lane tx sender %s does not match current round controller %s", sender, es.control.controller) + } + return nil } -func unwrapTx(outerTx *types.Transaction) (*types.Transaction, error) { - encodedInnerTx := outerTx.Data() +// unwrapExpressLaneTx extracts the inner "wrapped" transaction from the data field of an express lane transaction. +func unwrapExpressLaneTx(tx *types.Transaction) (*types.Transaction, error) { + encodedInnerTx := tx.Data() var innerTx types.Transaction - err := rlp.DecodeBytes(encodedInnerTx, &innerTx) - if err != nil { - return nil, err + if err := rlp.DecodeBytes(encodedInnerTx, &innerTx); err != nil { + return nil, fmt.Errorf("failed to decode inner transaction: %w", err) } return &innerTx, nil } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 2d8c59bbba..0b310d49e0 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -498,26 +498,27 @@ func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Tran return types.ErrTxTypeNotSupported } - // If timeboost is enabled, we check if the tx is an express lane tx, if so, we will - // process it right away into the queue. Otherwise, delay by a nominal amount. if s.config().Timeboost.Enable { - signer := types.LatestSigner(s.execEngine.bc.Config()) - sender, err := types.Sender(signer, tx) - if err != nil { - return err - } - // TODO: Do not delay if there isn't an express lane controller this round. - if !s.expressLaneService.isExpressLaneTx(sender) { - log.Info("Delaying non-express lane tx", "sender", sender) + // Express lane transaction sequence is defined by the following spec: + // https://github.com/OffchainLabs/timeboost-design-docs/blob/main/research_spec.md + // The express lane transaction is defined by a transaction's `to` address matching a predefined chain's reserved address. + // The express lane transaction will follow verifications for round number, nonce, and sender's address. + // If all pass, the transaction will be sequenced right away. + // Non-express lane transactions will be delayed by ExpressLaneAdvantage. + + if !s.expressLaneService.isExpressLaneTx(*tx.To()) { + log.Info("Delaying non-express lane tx", "hash", tx.Hash()) time.Sleep(s.config().Timeboost.ExpressLaneAdvantage) } else { - if s.expressLaneService.isOuterExpressLaneTx(tx.To()) { - tx, err = unwrapTx(tx) - if err != nil { - return err - } + if err := s.expressLaneService.validateExpressLaneTx(tx); err != nil { + return fmt.Errorf("express lane validation failed: %w", err) + } + unwrappedTx, err := unwrapExpressLaneTx(tx) + if err != nil { + return fmt.Errorf("failed to unwrap express lane tx: %w", err) } - log.Info("Processing express lane tx", "sender", sender) + tx = unwrappedTx + log.Info("Processing express lane tx", "hash", tx.Hash()) } } From 81a237b8313706e6d1b1a3049feffc74ccea962d Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 23 Jul 2024 09:37:02 -0500 Subject: [PATCH 006/109] update contracts repo and bindings --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 61204dd455..46f20c3a34 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 61204dd455966cb678192427a07aa9795ff91c14 +Subproject commit 46f20c3a34eaa841972c2b2597edced9d11e23b2 From 0b7819b4b88455e2ca3671ec7353a5bb7041828d Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 23 Jul 2024 14:24:37 -0500 Subject: [PATCH 007/109] building once more --- system_tests/seqfeed_test.go | 2 +- timeboost/async.go | 1 + timeboost/auctioneer.go | 120 +- timeboost/bidder_client.go | 218 +-- timeboost/bids.go | 76 +- timeboost/bindings/expresslaneauction.go | 1645 ---------------------- timeboost/setup_test.go | 68 +- 7 files changed, 197 insertions(+), 1933 deletions(-) delete mode 100644 timeboost/bindings/expresslaneauction.go diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 50ae65938e..1dc62bf7aa 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -299,7 +299,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { }(&wg) wg.Wait() - // After round is done, verify that Alice beats Bob in the final sequence. + // After round is done, verify that Bob beats Alice in the final sequence. aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) Require(t, err) aliceBlock := aliceReceipt.BlockNumber.Uint64() diff --git a/timeboost/async.go b/timeboost/async.go index c28579ff48..a400051473 100644 --- a/timeboost/async.go +++ b/timeboost/async.go @@ -10,6 +10,7 @@ func receiveAsync[T any](ctx context.Context, channel chan T, f func(context.Con for { select { case item := <-channel: + // TODO: Potential goroutine blow-up here. go func() { if err := f(ctx, item); err != nil { log.Error("Error processing item", "error", err) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 15c146f84c..ed0d9dca49 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -2,70 +2,67 @@ package timeboost import ( "context" + "fmt" "math/big" + "sync" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" ) -const defaultAuctionClosingSecondsBeforeRound = 15 // Before the start of the next round. - type AuctioneerOpt func(*Auctioneer) -func WithAuctionClosingSecondsBeforeRound(d time.Duration) AuctioneerOpt { - return func(am *Auctioneer) { - am.auctionClosingDurationBeforeRoundStart = d - } -} - type Auctioneer struct { - txOpts *bind.TransactOpts - chainId *big.Int - signatureDomain uint16 - client arbutil.L1Interface - auctionContract *bindings.ExpressLaneAuction - bidsReceiver chan *Bid - bidCache *bidCache - initialRoundTimestamp time.Time - roundDuration time.Duration - auctionClosingDurationBeforeRoundStart time.Duration + txOpts *bind.TransactOpts + chainId *big.Int + client arbutil.L1Interface + auctionContract *express_lane_auctiongen.ExpressLaneAuction + bidsReceiver chan *Bid + bidCache *bidCache + initialRoundTimestamp time.Time + roundDuration time.Duration + auctionClosingDuration time.Duration + reserveSubmissionDuration time.Duration + auctionContractAddr common.Address + reservePriceLock sync.RWMutex + reservePrice *big.Int } func NewAuctioneer( txOpts *bind.TransactOpts, chainId *big.Int, client arbutil.L1Interface, - auctionContract *bindings.ExpressLaneAuction, + auctionContractAddr common.Address, + auctionContract *express_lane_auctiongen.ExpressLaneAuction, opts ...AuctioneerOpt, ) (*Auctioneer, error) { - initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) - if err != nil { - return nil, err - } - roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) - if err != nil { - return nil, err - } - sigDomain, err := auctionContract.BidSignatureDomainValue(&bind.CallOpts{}) + roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) if err != nil { return nil, err } + initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) + roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second + reserveSubmissionDuration := time.Duration(roundTimingInfo.ReserveSubmissionSeconds) * time.Second + am := &Auctioneer{ - txOpts: txOpts, - chainId: chainId, - client: client, - signatureDomain: sigDomain, - auctionContract: auctionContract, - bidsReceiver: make(chan *Bid, 100), - bidCache: newBidCache(), - initialRoundTimestamp: time.Unix(initialRoundTimestamp.Int64(), 0), - roundDuration: time.Duration(roundDurationSeconds) * time.Second, - auctionClosingDurationBeforeRoundStart: defaultAuctionClosingSecondsBeforeRound, + txOpts: txOpts, + chainId: chainId, + client: client, + auctionContract: auctionContract, + bidsReceiver: make(chan *Bid, 10_000), + bidCache: newBidCache(), + initialRoundTimestamp: initialTimestamp, + auctionContractAddr: auctionContractAddr, + roundDuration: roundDuration, + auctionClosingDuration: auctionClosingDuration, + reserveSubmissionDuration: reserveSubmissionDuration, } for _, o := range opts { o(am) @@ -73,10 +70,10 @@ func NewAuctioneer( return am, nil } -func (am *Auctioneer) SubmitBid(ctx context.Context, b *Bid) error { +func (am *Auctioneer) ReceiveBid(ctx context.Context, b *Bid) error { validated, err := am.newValidatedBid(b) if err != nil { - return err + return fmt.Errorf("could not validate bid: %v", err) } am.bidCache.add(validated) return nil @@ -84,20 +81,21 @@ func (am *Auctioneer) SubmitBid(ctx context.Context, b *Bid) error { func (am *Auctioneer) Start(ctx context.Context) { // Receive bids in the background. - go receiveAsync(ctx, am.bidsReceiver, am.SubmitBid) + go receiveAsync(ctx, am.bidsReceiver, am.ReceiveBid) // Listen for sequencer health in the background and close upcoming auctions if so. go am.checkSequencerHealth(ctx) // Work on closing auctions. - ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDurationBeforeRoundStart) + ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) go ticker.start() for { select { case <-ctx.Done(): + log.Error("Context closed, autonomous auctioneer shutting down") return case auctionClosingTime := <-ticker.c: - log.Info("Auction closing", "closingTime", auctionClosingTime) + log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", am.bidCache.size()) if err := am.resolveAuctions(ctx); err != nil { log.Error("Could not resolve auction for round", "error", err) } @@ -122,34 +120,28 @@ func (am *Auctioneer) resolveAuctions(ctx context.Context) error { // TODO: Retry a given number of times in case of flakey connection. switch { case hasBothBids: - log.Info("Resolving auctions, received two bids", "round", upcomingRound, "firstRound", first.round, "secondRound", second.round) - tx, err = am.auctionContract.ResolveAuction( + tx, err = am.auctionContract.ResolveMultiBidAuction( am.txOpts, - bindings.Bid{ - Bidder: first.address, - ChainId: am.chainId, - Round: new(big.Int).SetUint64(first.round), - Amount: first.amount, - Signature: first.signature, + express_lane_auctiongen.Bid{ + ExpressLaneController: first.expressLaneController, + Amount: first.amount, + Signature: first.signature, }, - bindings.Bid{ - Bidder: second.address, - ChainId: am.chainId, - Round: new(big.Int).SetUint64(second.round), - Amount: second.amount, - Signature: second.signature, + express_lane_auctiongen.Bid{ + ExpressLaneController: second.expressLaneController, + Amount: second.amount, + Signature: second.signature, }, ) + log.Info("Resolving auctions, received two bids", "round", upcomingRound) case hasSingleBid: log.Info("Resolving auctions, received single bids", "round", upcomingRound) tx, err = am.auctionContract.ResolveSingleBidAuction( am.txOpts, - bindings.Bid{ - Bidder: first.address, - ChainId: am.chainId, - Round: new(big.Int).SetUint64(upcomingRound), - Amount: first.amount, - Signature: first.signature, + express_lane_auctiongen.Bid{ + ExpressLaneController: first.expressLaneController, + Amount: first.amount, + Signature: first.signature, }, ) case noBids: diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index d6cdd0e1d1..0a05b97e74 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -3,7 +3,6 @@ package timeboost import ( "context" "crypto/ecdsa" - "fmt" "math/big" "time" @@ -13,32 +12,26 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" - "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" ) -type sequencerConnection interface { - SendExpressLaneTx(ctx context.Context, tx *types.Transaction) error -} - type auctioneerConnection interface { SubmitBid(ctx context.Context, bid *Bid) error } type BidderClient struct { - chainId uint64 - name string - signatureDomain uint16 - txOpts *bind.TransactOpts - client arbutil.L1Interface - privKey *ecdsa.PrivateKey - auctionContract *bindings.ExpressLaneAuction - sequencer sequencerConnection - auctioneer auctioneerConnection - initialRoundTimestamp time.Time - roundDuration time.Duration + chainId uint64 + name string + auctionContractAddress common.Address + txOpts *bind.TransactOpts + client arbutil.L1Interface + privKey *ecdsa.PrivateKey + auctionContract *express_lane_auctiongen.ExpressLaneAuction + auctioneer auctioneerConnection + initialRoundTimestamp time.Time + roundDuration time.Duration } // TODO: Provide a safer option. @@ -53,164 +46,38 @@ func NewBidderClient( wallet *Wallet, client arbutil.L1Interface, auctionContractAddress common.Address, - sequencer sequencerConnection, auctioneer auctioneerConnection, ) (*BidderClient, error) { chainId, err := client.ChainID(ctx) if err != nil { return nil, err } - auctionContract, err := bindings.NewExpressLaneAuction(auctionContractAddress, client) - if err != nil { - return nil, err - } - sigDomain, err := auctionContract.BidSignatureDomainValue(&bind.CallOpts{}) - if err != nil { - return nil, err - } - initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddress, client) if err != nil { return nil, err } - roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) + roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) if err != nil { return nil, err } + initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) + roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second return &BidderClient{ - chainId: chainId.Uint64(), - name: name, - signatureDomain: sigDomain, - client: client, - txOpts: wallet.TxOpts, - privKey: wallet.PrivKey, - auctionContract: auctionContract, - sequencer: sequencer, - auctioneer: auctioneer, - initialRoundTimestamp: time.Unix(initialRoundTimestamp.Int64(), 0), - roundDuration: time.Duration(roundDurationSeconds) * time.Second, + chainId: chainId.Uint64(), + name: name, + auctionContractAddress: auctionContractAddress, + client: client, + txOpts: wallet.TxOpts, + privKey: wallet.PrivKey, + auctionContract: auctionContract, + auctioneer: auctioneer, + initialRoundTimestamp: initialTimestamp, + roundDuration: roundDuration, }, nil } -func (bd *BidderClient) Start(ctx context.Context) { - // Monitor for newly assigned express lane controllers, and if the client's address - // is the controller in order to send express lane txs. - go bd.monitorAuctionResolutions(ctx) - // Monitor for auction closures by the autonomous auctioneer. - go bd.monitorAuctionCancelations(ctx) - // Monitor for express lane control delegations to take over if needed. - go bd.monitorExpressLaneDelegations(ctx) -} - -func (bd *BidderClient) monitorAuctionResolutions(ctx context.Context) { - winningBidders := []common.Address{bd.txOpts.From} - latestBlock, err := bd.client.HeaderByNumber(ctx, nil) - if err != nil { - panic(err) - } - fromBlock := latestBlock.Number.Uint64() - ticker := time.NewTicker(time.Millisecond * 250) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - latestBlock, err := bd.client.HeaderByNumber(ctx, nil) - if err != nil { - log.Error("Could not get latest header", "err", err) - continue - } - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: fromBlock, - End: &toBlock, - } - it, err := bd.auctionContract.FilterAuctionResolved(filterOpts, winningBidders, nil) - if err != nil { - log.Error("Could not filter auction resolutions", "error", err) - continue - } - for it.Next() { - upcomingRound := CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1 - ev := it.Event - if ev.WinnerRound.Uint64() == upcomingRound { - // TODO: Log the time to next round. - log.Info( - "WON the express lane auction for next round - can send fast lane txs to sequencer", - "winner", ev.WinningBidder, - "upcomingRound", upcomingRound, - "firstPlaceBidAmount", fmt.Sprintf("%#x", ev.WinningBidAmount), - "secondPlaceBidAmount", fmt.Sprintf("%#x", ev.WinningBidAmount), - ) - } - } - fromBlock = toBlock - } - } -} - -func (bd *BidderClient) monitorAuctionCancelations(ctx context.Context) { - // TODO: Implement. -} - -func (bd *BidderClient) monitorExpressLaneDelegations(ctx context.Context) { - delegatedTo := []common.Address{bd.txOpts.From} - latestBlock, err := bd.client.HeaderByNumber(ctx, nil) - if err != nil { - panic(err) - } - fromBlock := latestBlock.Number.Uint64() - ticker := time.NewTicker(time.Millisecond * 250) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - latestBlock, err := bd.client.HeaderByNumber(ctx, nil) - if err != nil { - log.Error("Could not get latest header", "err", err) - continue - } - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: fromBlock, - End: &toBlock, - } - it, err := bd.auctionContract.FilterExpressLaneControlDelegated(filterOpts, nil, delegatedTo) - if err != nil { - log.Error("Could not filter auction resolutions", "error", err) - continue - } - for it.Next() { - upcomingRound := CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1 - ev := it.Event - // TODO: Log the time to next round. - log.Info( - "Received express lane delegation for next round - can send fast lane txs to sequencer", - "delegatedFrom", ev.From, - "upcomingRound", upcomingRound, - ) - } - fromBlock = toBlock - } - } -} - -func (bd *BidderClient) sendExpressLaneTx(ctx context.Context, tx *types.Transaction) error { - return bd.sequencer.SendExpressLaneTx(ctx, tx) -} - func (bd *BidderClient) Deposit(ctx context.Context, amount *big.Int) error { - tx, err := bd.auctionContract.SubmitDeposit(bd.txOpts, amount) + tx, err := bd.auctionContract.Deposit(bd.txOpts, amount) if err != nil { return err } @@ -224,34 +91,45 @@ func (bd *BidderClient) Deposit(ctx context.Context, amount *big.Int) error { return nil } -func (bd *BidderClient) Bid(ctx context.Context, amount *big.Int) (*Bid, error) { +func (bd *BidderClient) Bid( + ctx context.Context, amount *big.Int, expressLaneController common.Address, +) (*Bid, error) { newBid := &Bid{ - chainId: bd.chainId, - address: bd.txOpts.From, - round: CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1, - amount: amount, + chainId: bd.chainId, + expressLaneController: expressLaneController, + auctionContractAddress: bd.auctionContractAddress, + bidder: bd.txOpts.From, + round: CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1, + amount: amount, + signature: nil, } packedBidBytes, err := encodeBidValues( - bd.signatureDomain, new(big.Int).SetUint64(newBid.chainId), new(big.Int).SetUint64(newBid.round), amount, + new(big.Int).SetUint64(newBid.chainId), + bd.auctionContractAddress, + new(big.Int).SetUint64(newBid.round), + amount, + expressLaneController, ) if err != nil { return nil, err } - sig, prefixed := sign(packedBidBytes, bd.privKey) + sig, err := sign(packedBidBytes, bd.privKey) + if err != nil { + return nil, err + } newBid.signature = sig - _ = prefixed if err = bd.auctioneer.SubmitBid(ctx, newBid); err != nil { return nil, err } return newBid, nil } -func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, []byte) { +func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { hash := crypto.Keccak256(message) prefixed := crypto.Keccak256([]byte("\x19Ethereum Signed Message:\n32"), hash) sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) if err != nil { - panic(err) + return nil, err } - return sig, prefixed + return sig, nil } diff --git a/timeboost/bids.go b/timeboost/bids.go index 979ba1d59e..c16cf2e042 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -3,7 +3,6 @@ package timeboost import ( "bytes" "crypto/ecdsa" - "encoding/binary" "math/big" "sync" @@ -24,15 +23,25 @@ var ( ) type Bid struct { - chainId uint64 - address common.Address - round uint64 - amount *big.Int - signature []byte + chainId uint64 + expressLaneController common.Address + bidder common.Address + auctionContractAddress common.Address + round uint64 + amount *big.Int + signature []byte } type validatedBid struct { - Bid + expressLaneController common.Address + amount *big.Int + signature []byte +} + +func (am *Auctioneer) fetchReservePrice() *big.Int { + am.reservePriceLock.RLock() + defer am.reservePriceLock.RUnlock() + return new(big.Int).Set(am.reservePrice) } func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { @@ -40,9 +49,12 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") } - if bid.address == (common.Address{}) { + if bid.bidder == (common.Address{}) { return nil, errors.Wrap(ErrMalformedData, "empty bidder address") } + if bid.expressLaneController == (common.Address{}) { + return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") + } // Verify chain id. if new(big.Int).SetUint64(bid.chainId).Cmp(am.chainId) != 0 { return nil, errors.Wrapf(ErrWrongChainId, "wanted %#x, got %#x", am.chainId, bid.chainId) @@ -53,12 +65,18 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.round) } // Check bid amount. - if bid.amount.Cmp(big.NewInt(0)) <= 0 { - return nil, errors.Wrap(ErrMalformedData, "expected a non-negative, non-zero bid amount") + reservePrice := am.fetchReservePrice() + if bid.amount.Cmp(reservePrice) == -1 { + return nil, errors.Wrap(ErrMalformedData, "expected bid to be at least of reserve price magnitude") } // Validate the signature. + // TODO: Validate the signature against the express lane controller address. packedBidBytes, err := encodeBidValues( - am.signatureDomain, new(big.Int).SetUint64(bid.chainId), new(big.Int).SetUint64(bid.round), bid.amount, + new(big.Int).SetUint64(bid.chainId), + am.auctionContractAddr, + new(big.Int).SetUint64(bid.round), + bid.amount, + bid.expressLaneController, ) if err != nil { return nil, ErrMalformedData @@ -80,7 +98,9 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { // Validate if the user if a depositor in the contract and has enough balance for the bid. // TODO: Retry some number of times if flakey connection. // TODO: Validate reserve price against amount of bid. - depositBal, err := am.auctionContract.DepositBalance(&bind.CallOpts{}, bid.address) + // TODO: No need to do anything expensive if the bid coming is in invalid. + // Cache this if the received time of the bid is too soon. Include the arrival timestamp. + depositBal, err := am.auctionContract.BalanceOf(&bind.CallOpts{}, bid.bidder) if err != nil { return nil, err } @@ -90,24 +110,28 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { if depositBal.Cmp(bid.amount) < 0 { return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.amount) } - return &validatedBid{*bid}, nil + return &validatedBid{ + expressLaneController: bid.expressLaneController, + amount: bid.amount, + signature: bid.signature, + }, nil } type bidCache struct { sync.RWMutex - latestBidBySender map[common.Address]*validatedBid + bidsByExpressLaneControllerAddr map[common.Address]*validatedBid } func newBidCache() *bidCache { return &bidCache{ - latestBidBySender: make(map[common.Address]*validatedBid), + bidsByExpressLaneControllerAddr: make(map[common.Address]*validatedBid), } } func (bc *bidCache) add(bid *validatedBid) { bc.Lock() defer bc.Unlock() - bc.latestBidBySender[bid.address] = bid + bc.bidsByExpressLaneControllerAddr[bid.expressLaneController] = bid } // TwoTopBids returns the top two bids for the given chain ID and round @@ -116,11 +140,19 @@ type auctionResult struct { secondPlace *validatedBid } +func (bc *bidCache) size() int { + bc.RLock() + defer bc.RUnlock() + return len(bc.bidsByExpressLaneControllerAddr) + +} + func (bc *bidCache) topTwoBids() *auctionResult { bc.RLock() defer bc.RUnlock() result := &auctionResult{} - for _, bid := range bc.latestBidBySender { + // TODO: Tiebreaker handle. + for _, bid := range bc.bidsByExpressLaneControllerAddr { if result.firstPlace == nil || bid.amount.Cmp(result.firstPlace.amount) > 0 { result.secondPlace = result.firstPlace result.firstPlace = bid @@ -146,19 +178,15 @@ func padBigInt(bi *big.Int) []byte { return padded } -func encodeBidValues(domainPrefix uint16, chainId, round, amount *big.Int) ([]byte, error) { +func encodeBidValues(chainId *big.Int, auctionContractAddress common.Address, round, amount *big.Int, expressLaneController common.Address) ([]byte, error) { buf := new(bytes.Buffer) - // Encode uint16 - occupies 2 bytes - err := binary.Write(buf, binary.BigEndian, domainPrefix) - if err != nil { - return nil, err - } - // Encode uint256 values - each occupies 32 bytes buf.Write(padBigInt(chainId)) + buf.Write(auctionContractAddress[:]) buf.Write(padBigInt(round)) buf.Write(padBigInt(amount)) + buf.Write(expressLaneController[:]) return buf.Bytes(), nil } diff --git a/timeboost/bindings/expresslaneauction.go b/timeboost/bindings/expresslaneauction.go deleted file mode 100644 index 1de9bc49a9..0000000000 --- a/timeboost/bindings/expresslaneauction.go +++ /dev/null @@ -1,1645 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package bindings - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription - _ = abi.ConvertType -) - -// Bid is an auto generated low-level Go binding around an user-defined struct. -type Bid struct { - Bidder common.Address - ChainId *big.Int - Round *big.Int - Amount *big.Int - Signature []byte -} - -// ExpressLaneAuctionMetaData contains all meta data concerning the ExpressLaneAuction contract. -var ExpressLaneAuctionMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_chainOwnerAddr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_reservePriceSetterAddr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_bidReceiverAddr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_roundLengthSeconds\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_initialTimestamp\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_stakeToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_currentReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_minimalReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"bidReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"bidSignatureDomainValue\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint16\",\"internalType\":\"uint16\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"bidderBalance\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"cancelUpcomingRound\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"chainOwnerAddress\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"currentExpressLaneController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"currentRound\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"delegateExpressLane\",\"inputs\":[{\"name\":\"delegate\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"depositBalance\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"expressLaneControllerByRound\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"finalizeWithdrawal\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getCurrentReservePrice\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getminimalReservePrice\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialRoundTimestamp\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initiateWithdrawal\",\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pendingWithdrawalByBidder\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"submittedRound\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"reservePriceSetterAddress\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"resolveAuction\",\"inputs\":[{\"name\":\"bid1\",\"type\":\"tuple\",\"internalType\":\"structBid\",\"components\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"round\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"bid2\",\"type\":\"tuple\",\"internalType\":\"structBid\",\"components\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"round\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"resolveSingleBidAuction\",\"inputs\":[{\"name\":\"bid\",\"type\":\"tuple\",\"internalType\":\"structBid\",\"components\":[{\"name\":\"bidder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"round\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"roundDurationSeconds\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setCurrentReservePrice\",\"inputs\":[{\"name\":\"_currentReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMinimalReservePrice\",\"inputs\":[{\"name\":\"_minimalReservePrice\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setReservePriceAddresses\",\"inputs\":[{\"name\":\"_reservePriceSetterAddr\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"submitDeposit\",\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"verifySignature\",\"inputs\":[{\"name\":\"signer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"message\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"event\",\"name\":\"AuctionResolved\",\"inputs\":[{\"name\":\"winningBidAmount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"secondPlaceBidAmount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"winningBidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"winnerRound\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"DepositSubmitted\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ExpressLaneControlDelegated\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"round\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WithdrawalFinalized\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WithdrawalInitiated\",\"inputs\":[{\"name\":\"bidder\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"IncorrectBidAmount\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InsufficientBalance\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"LessThanCurrentReservePrice\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"LessThanMinReservePrice\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotChainOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotExpressLaneController\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotReservePriceSetter\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ZeroAmount\",\"inputs\":[]}]", - Bin: "0x60806040526006805461ffff1916600f1790553480156200001f57600080fd5b5060405162001a8838038062001a888339810160408190526200004291620000ef565b600080546001600160a01b03998a166001600160a01b03199182161790915560018054988a169890911697909717909655600280546001600160401b03909516600160a01b026001600160e01b031990951695881695909517939093179093556003556004556005919091556006805491909216620100000262010000600160b01b03199091161790556200018a565b80516001600160a01b0381168114620000ea57600080fd5b919050565b600080600080600080600080610100898b0312156200010d57600080fd5b6200011889620000d2565b97506200012860208a01620000d2565b96506200013860408a01620000d2565b60608a01519096506001600160401b03811681146200015657600080fd5b60808a015190955093506200016e60a08a01620000d2565b60c08a015160e0909a0151989b979a5095989497939692505050565b6118ee806200019a6000396000f3fe608060405234801561001057600080fd5b50600436106101735760003560e01c80638296df03116100de578063cc963d1511610097578063d6e5fb7d11610071578063d6e5fb7d1461037c578063dbeb20121461038f578063f5f754d6146103a2578063f66fda64146103b557600080fd5b8063cc963d151461033e578063cd4abf7114610356578063d6ded1bc1461036957600080fd5b80638296df03146102bd5780638a19c8bc146102e6578063956501bb14610306578063b941ce6e14610326578063c03899791461032e578063c5b6aa2f1461033657600080fd5b80634d1846dc116101305780634d1846dc1461022b5780634f2a9bdb14610233578063574a9b5f1461023b5780635f70f9031461024e57806379a47e291461029b5780637c62b5cd146102ac57600080fd5b806303ba666214610178578063048fae731461018f57806312edde5e146101b857806324e359e7146101cd57806338265efd146101f05780634bc37ea614610206575b600080fd5b6005545b6040519081526020015b60405180910390f35b61017c61019d3660046115a4565b6001600160a01b031660009081526007602052604090205490565b6101cb6101c63660046115c6565b6103c8565b005b6101e06101db366004611681565b61055d565b6040519015158152602001610186565b60065460405161ffff9091168152602001610186565b6002546001600160a01b03165b6040516001600160a01b039091168152602001610186565b6101cb6105e9565b61021361066b565b6101cb6102493660046115c6565b6106a1565b61027e61025c3660046115a4565b600860205260009081526040902080546001909101546001600160401b031682565b604080519283526001600160401b03909116602083015201610186565b6000546001600160a01b0316610213565b6001546001600160a01b0316610213565b6102136102cb3660046115c6565b6009602052600090815260409020546001600160a01b031681565b6102ee6106f4565b6040516001600160401b039091168152602001610186565b61017c6103143660046115a4565b60076020526000908152604090205481565b60045461017c565b60035461017c565b6101cb61073d565b600254600160a01b90046001600160401b03166102ee565b6101cb61036436600461170c565b6108ef565b6101cb6103773660046115c6565b610f31565b6101cb61038a3660046115a4565b610f61565b6101cb61039d3660046115c6565b611024565b6101cb6103b036600461176f565b611122565b6101cb6103c33660046115a4565b611432565b806000036103e957604051631f2a200560e01b815260040160405180910390fd5b3360009081526007602052604090205481111561041957604051631e9acf1760e31b815260040160405180910390fd5b33600090815260086020908152604091829020825180840190935280548084526001909101546001600160401b031691830191909152156104a15760405162461bcd60e51b815260206004820152601c60248201527f7769746864726177616c20616c726561647920696e697469617465640000000060448201526064015b60405180910390fd5b33600090815260076020526040812080548492906104c09084906117c1565b9250508190555060405180604001604052808381526020016104e06106f4565b6001600160401b039081169091523360008181526008602090815260409182902085518155948101516001909501805467ffffffffffffffff19169590941694909417909255905184815290917f6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec64691015b60405180910390a25050565b8151602083012060009060006105c0826040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b905060006105ce828661147f565b6001600160a01b039081169088161493505050509392505050565b60006105f36106f4565b6105fe9060016117d4565b6001600160401b0381166000908152600960205260409020549091506001600160a01b0316338114610643576040516302e001e360e01b815260040160405180910390fd5b506001600160401b0316600090815260096020526040902080546001600160a01b0319169055565b6000600960006106796106f4565b6001600160401b031681526020810191909152604001600020546001600160a01b0316919050565b6001546001600160a01b031633146106cc576040516305fbc41160e01b815260040160405180910390fd5b6005548110156106ef57604051632e99443560e21b815260040160405180910390fd5b600455565b600042600354111561070c57506001600160401b0390565b600254600354600160a01b9091046001600160401b03169061072e90426117c1565b61073891906117fb565b905090565b336000908152600860209081526040808320815180830190925280548083526001909101546001600160401b03169282019290925291036107c05760405162461bcd60e51b815260206004820152601760248201527f6e6f207769746864726177616c20696e697469617465640000000000000000006044820152606401610498565b60006107ca6106f4565b9050816020015160026107dd91906117d4565b6001600160401b0316816001600160401b03161461083d5760405162461bcd60e51b815260206004820152601b60248201527f7769746864726177616c206973206e6f742066696e616c697a656400000000006044820152606401610498565b600654825160405163a9059cbb60e01b81523360048201526024810191909152620100009091046001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610896573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108ba919061181d565b50815160405190815233907f9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda890602001610551565b806020013582602001351461093f5760405162461bcd60e51b81526020600482015260166024820152750c6d0c2d2dc40d2c8e640c8de40dcdee840dac2e8c6d60531b6044820152606401610498565b806040013582604001351461098c5760405162461bcd60e51b81526020600482015260136024820152720e4deeadcc8e640c8de40dcdee840dac2e8c6d606b1b6044820152606401610498565b60006109966106f4565b6109a19060016117d4565b6001600160401b03169050808360400135146109f45760405162461bcd60e51b81526020600482015260126024820152711b9bdd081d5c18dbdb5a5b99c81c9bdd5b9960721b6044820152606401610498565b606083013560076000610a0a60208701876115a4565b6001600160a01b03166001600160a01b03168152602001908152602001600020541015610a4a5760405163017e521960e71b815260040160405180910390fd5b606082013560076000610a6060208601866115a4565b6001600160a01b03166001600160a01b03168152602001908152602001600020541015610aa05760405163017e521960e71b815260040160405180910390fd5b610b44610ab060208501856115a4565b6006546040805160f09290921b6001600160f01b031916602080840191909152870135602283015286013560428201526060860135606282015260820160408051601f19818403018152919052610b0a608087018761183f565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061055d92505050565b610b905760405162461bcd60e51b815260206004820152601f60248201527f696e76616c6964207369676e617475726520666f7220666972737420626964006044820152606401610498565b610bfa610ba060208401846115a4565b6006546040805160f09290921b6001600160f01b031916602080840191909152860135602283015285013560428201526060850135606282015260820160408051601f19818403018152919052610b0a608086018661183f565b610c465760405162461bcd60e51b815260206004820181905260248201527f696e76616c6964207369676e617475726520666f72207365636f6e64206269646044820152606401610498565b816060013583606001351115610dca57606082013560076000610c6c60208701876115a4565b6001600160a01b03166001600160a01b031681526020019081526020016000206000828254610c9b91906117c1565b909155505060065460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526060850135602482015262010000909204169063a9059cbb906044016020604051808303816000875af1158015610cfe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d22919061181d565b50610d3060208401846115a4565b60408481013560009081526009602090815291902080546001600160a01b0319166001600160a01b0393909316929092179091558190610d72908501856115a4565b6001600160a01b03167febab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef085606001358560600135604051610dbd929190918252602082015260400190565b60405180910390a3505050565b606083013560076000610de060208601866115a4565b6001600160a01b03166001600160a01b031681526020019081526020016000206000828254610e0f91906117c1565b909155505060065460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526060860135602482015262010000909204169063a9059cbb906044016020604051808303816000875af1158015610e72573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e96919061181d565b50610ea460208301836115a4565b60408381013560009081526009602090815291902080546001600160a01b0319166001600160a01b0393909316929092179091558190610ee6908401846115a4565b6001600160a01b03167febab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef084606001358660600135604051610dbd929190918252602082015260400190565b6000546001600160a01b03163314610f5c576040516311c29acf60e31b815260040160405180910390fd5b600555565b6000610f6b6106f4565b610f769060016117d4565b6001600160401b0381166000908152600960205260409020549091506001600160a01b0316338114610fbb576040516302e001e360e01b815260040160405180910390fd5b6001600160401b03821660008181526009602090815260409182902080546001600160a01b0319166001600160a01b0388169081179091559151928352909133917fdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede9101610dbd565b8060000361104557604051631f2a200560e01b815260040160405180910390fd5b336000908152600760205260408120805483929061106490849061188c565b90915550506006546040516323b872dd60e01b815233600482015230602482015260448101839052620100009091046001600160a01b0316906323b872dd906064016020604051808303816000875af11580156110c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110e9919061181d565b5060405181815233907feafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa7059060200160405180910390a250565b600061112c6106f4565b6111379060016117d4565b9050806001600160401b031682604001351461118a5760405162461bcd60e51b81526020600482015260126024820152711b9bdd081d5c18dbdb5a5b99c81c9bdd5b9960721b6044820152606401610498565b6060820135600760006111a060208601866115a4565b6001600160a01b03166001600160a01b031681526020019081526020016000205410156111e05760405163017e521960e71b815260040160405180910390fd5b600454826060013510156112075760405163e709032960e01b815260040160405180910390fd5b60608201356007600061121d60208601866115a4565b6001600160a01b03166001600160a01b0316815260200190815260200160002054101561125d5760405163017e521960e71b815260040160405180910390fd5b61126d610ba060208401846115a4565b6112b95760405162461bcd60e51b815260206004820152601f60248201527f696e76616c6964207369676e617475726520666f7220666972737420626964006044820152606401610498565b6060820135600760006112cf60208601866115a4565b6001600160a01b03166001600160a01b0316815260200190815260200160002060008282546112fe91906117c1565b909155505060065460025460405163a9059cbb60e01b81526001600160a01b0391821660048201526060850135602482015262010000909204169063a9059cbb906044016020604051808303816000875af1158015611361573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611385919061181d565b5061139360208301836115a4565b60408381013560009081526009602090815291902080546001600160a01b0319166001600160a01b0393909316929092179091556001600160401b038216906113de908401846115a4565b6001600160a01b03167febab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef084606001356000604051611426929190918252602082015260400190565b60405180910390a35050565b6000546001600160a01b0316331461145d576040516311c29acf60e31b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60008060008061148e856114ff565b6040805160008152602081018083528b905260ff8316918101919091526060810184905260808101839052929550909350915060019060a0016020604051602081039080840390855afa1580156114e9573d6000803e3d6000fd5b5050506020604051035193505050505b92915050565b600080600083516041146115555760405162461bcd60e51b815260206004820152601860248201527f696e76616c6964207369676e6174757265206c656e67746800000000000000006044820152606401610498565b50505060208101516040820151606083015160001a601b8110156115815761157e601b8261189f565b90505b9193909250565b80356001600160a01b038116811461159f57600080fd5b919050565b6000602082840312156115b657600080fd5b6115bf82611588565b9392505050565b6000602082840312156115d857600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261160657600080fd5b81356001600160401b0380821115611620576116206115df565b604051601f8301601f19908116603f01168101908282118183101715611648576116486115df565b8160405283815286602085880101111561166157600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561169657600080fd5b61169f84611588565b925060208401356001600160401b03808211156116bb57600080fd5b6116c7878388016115f5565b935060408601359150808211156116dd57600080fd5b506116ea868287016115f5565b9150509250925092565b600060a0828403121561170657600080fd5b50919050565b6000806040838503121561171f57600080fd5b82356001600160401b038082111561173657600080fd5b611742868387016116f4565b9350602085013591508082111561175857600080fd5b50611765858286016116f4565b9150509250929050565b60006020828403121561178157600080fd5b81356001600160401b0381111561179757600080fd5b6117a3848285016116f4565b949350505050565b634e487b7160e01b600052601160045260246000fd5b818103818111156114f9576114f96117ab565b6001600160401b038181168382160190808211156117f4576117f46117ab565b5092915050565b60008261181857634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561182f57600080fd5b815180151581146115bf57600080fd5b6000808335601e1984360301811261185657600080fd5b8301803591506001600160401b0382111561187057600080fd5b60200191503681900382131561188557600080fd5b9250929050565b808201808211156114f9576114f96117ab565b60ff81811683821601908111156114f9576114f96117ab56fea26469706673582212206429478454ed8215ff5008fd2094b9c08b2ac458e30cc85f0f5be4106743765f64736f6c63430008130033", -} - -// ExpressLaneAuctionABI is the input ABI used to generate the binding from. -// Deprecated: Use ExpressLaneAuctionMetaData.ABI instead. -var ExpressLaneAuctionABI = ExpressLaneAuctionMetaData.ABI - -// ExpressLaneAuctionBin is the compiled bytecode used for deploying new contracts. -// Deprecated: Use ExpressLaneAuctionMetaData.Bin instead. -var ExpressLaneAuctionBin = ExpressLaneAuctionMetaData.Bin - -// DeployExpressLaneAuction deploys a new Ethereum contract, binding an instance of ExpressLaneAuction to it. -func DeployExpressLaneAuction(auth *bind.TransactOpts, backend bind.ContractBackend, _chainOwnerAddr common.Address, _reservePriceSetterAddr common.Address, _bidReceiverAddr common.Address, _roundLengthSeconds uint64, _initialTimestamp *big.Int, _stakeToken common.Address, _currentReservePrice *big.Int, _minimalReservePrice *big.Int) (common.Address, *types.Transaction, *ExpressLaneAuction, error) { - parsed, err := ExpressLaneAuctionMetaData.GetAbi() - if err != nil { - return common.Address{}, nil, nil, err - } - if parsed == nil { - return common.Address{}, nil, nil, errors.New("GetABI returned nil") - } - - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ExpressLaneAuctionBin), backend, _chainOwnerAddr, _reservePriceSetterAddr, _bidReceiverAddr, _roundLengthSeconds, _initialTimestamp, _stakeToken, _currentReservePrice, _minimalReservePrice) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &ExpressLaneAuction{ExpressLaneAuctionCaller: ExpressLaneAuctionCaller{contract: contract}, ExpressLaneAuctionTransactor: ExpressLaneAuctionTransactor{contract: contract}, ExpressLaneAuctionFilterer: ExpressLaneAuctionFilterer{contract: contract}}, nil -} - -// ExpressLaneAuction is an auto generated Go binding around an Ethereum contract. -type ExpressLaneAuction struct { - ExpressLaneAuctionCaller // Read-only binding to the contract - ExpressLaneAuctionTransactor // Write-only binding to the contract - ExpressLaneAuctionFilterer // Log filterer for contract events -} - -// ExpressLaneAuctionCaller is an auto generated read-only Go binding around an Ethereum contract. -type ExpressLaneAuctionCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ExpressLaneAuctionTransactor is an auto generated write-only Go binding around an Ethereum contract. -type ExpressLaneAuctionTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ExpressLaneAuctionFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type ExpressLaneAuctionFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ExpressLaneAuctionSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type ExpressLaneAuctionSession struct { - Contract *ExpressLaneAuction // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ExpressLaneAuctionCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type ExpressLaneAuctionCallerSession struct { - Contract *ExpressLaneAuctionCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// ExpressLaneAuctionTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type ExpressLaneAuctionTransactorSession struct { - Contract *ExpressLaneAuctionTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ExpressLaneAuctionRaw is an auto generated low-level Go binding around an Ethereum contract. -type ExpressLaneAuctionRaw struct { - Contract *ExpressLaneAuction // Generic contract binding to access the raw methods on -} - -// ExpressLaneAuctionCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type ExpressLaneAuctionCallerRaw struct { - Contract *ExpressLaneAuctionCaller // Generic read-only contract binding to access the raw methods on -} - -// ExpressLaneAuctionTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type ExpressLaneAuctionTransactorRaw struct { - Contract *ExpressLaneAuctionTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewExpressLaneAuction creates a new instance of ExpressLaneAuction, bound to a specific deployed contract. -func NewExpressLaneAuction(address common.Address, backend bind.ContractBackend) (*ExpressLaneAuction, error) { - contract, err := bindExpressLaneAuction(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &ExpressLaneAuction{ExpressLaneAuctionCaller: ExpressLaneAuctionCaller{contract: contract}, ExpressLaneAuctionTransactor: ExpressLaneAuctionTransactor{contract: contract}, ExpressLaneAuctionFilterer: ExpressLaneAuctionFilterer{contract: contract}}, nil -} - -// NewExpressLaneAuctionCaller creates a new read-only instance of ExpressLaneAuction, bound to a specific deployed contract. -func NewExpressLaneAuctionCaller(address common.Address, caller bind.ContractCaller) (*ExpressLaneAuctionCaller, error) { - contract, err := bindExpressLaneAuction(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionCaller{contract: contract}, nil -} - -// NewExpressLaneAuctionTransactor creates a new write-only instance of ExpressLaneAuction, bound to a specific deployed contract. -func NewExpressLaneAuctionTransactor(address common.Address, transactor bind.ContractTransactor) (*ExpressLaneAuctionTransactor, error) { - contract, err := bindExpressLaneAuction(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionTransactor{contract: contract}, nil -} - -// NewExpressLaneAuctionFilterer creates a new log filterer instance of ExpressLaneAuction, bound to a specific deployed contract. -func NewExpressLaneAuctionFilterer(address common.Address, filterer bind.ContractFilterer) (*ExpressLaneAuctionFilterer, error) { - contract, err := bindExpressLaneAuction(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionFilterer{contract: contract}, nil -} - -// bindExpressLaneAuction binds a generic wrapper to an already deployed contract. -func bindExpressLaneAuction(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := ExpressLaneAuctionMetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_ExpressLaneAuction *ExpressLaneAuctionRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _ExpressLaneAuction.Contract.ExpressLaneAuctionCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_ExpressLaneAuction *ExpressLaneAuctionRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.ExpressLaneAuctionTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_ExpressLaneAuction *ExpressLaneAuctionRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.ExpressLaneAuctionTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_ExpressLaneAuction *ExpressLaneAuctionCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _ExpressLaneAuction.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.contract.Transact(opts, method, params...) -} - -// BidReceiver is a free data retrieval call binding the contract method 0x4bc37ea6. -// -// Solidity: function bidReceiver() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) BidReceiver(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "bidReceiver") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// BidReceiver is a free data retrieval call binding the contract method 0x4bc37ea6. -// -// Solidity: function bidReceiver() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) BidReceiver() (common.Address, error) { - return _ExpressLaneAuction.Contract.BidReceiver(&_ExpressLaneAuction.CallOpts) -} - -// BidReceiver is a free data retrieval call binding the contract method 0x4bc37ea6. -// -// Solidity: function bidReceiver() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) BidReceiver() (common.Address, error) { - return _ExpressLaneAuction.Contract.BidReceiver(&_ExpressLaneAuction.CallOpts) -} - -// BidSignatureDomainValue is a free data retrieval call binding the contract method 0x38265efd. -// -// Solidity: function bidSignatureDomainValue() view returns(uint16) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) BidSignatureDomainValue(opts *bind.CallOpts) (uint16, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "bidSignatureDomainValue") - - if err != nil { - return *new(uint16), err - } - - out0 := *abi.ConvertType(out[0], new(uint16)).(*uint16) - - return out0, err - -} - -// BidSignatureDomainValue is a free data retrieval call binding the contract method 0x38265efd. -// -// Solidity: function bidSignatureDomainValue() view returns(uint16) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) BidSignatureDomainValue() (uint16, error) { - return _ExpressLaneAuction.Contract.BidSignatureDomainValue(&_ExpressLaneAuction.CallOpts) -} - -// BidSignatureDomainValue is a free data retrieval call binding the contract method 0x38265efd. -// -// Solidity: function bidSignatureDomainValue() view returns(uint16) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) BidSignatureDomainValue() (uint16, error) { - return _ExpressLaneAuction.Contract.BidSignatureDomainValue(&_ExpressLaneAuction.CallOpts) -} - -// BidderBalance is a free data retrieval call binding the contract method 0x048fae73. -// -// Solidity: function bidderBalance(address bidder) view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) BidderBalance(opts *bind.CallOpts, bidder common.Address) (*big.Int, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "bidderBalance", bidder) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// BidderBalance is a free data retrieval call binding the contract method 0x048fae73. -// -// Solidity: function bidderBalance(address bidder) view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) BidderBalance(bidder common.Address) (*big.Int, error) { - return _ExpressLaneAuction.Contract.BidderBalance(&_ExpressLaneAuction.CallOpts, bidder) -} - -// BidderBalance is a free data retrieval call binding the contract method 0x048fae73. -// -// Solidity: function bidderBalance(address bidder) view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) BidderBalance(bidder common.Address) (*big.Int, error) { - return _ExpressLaneAuction.Contract.BidderBalance(&_ExpressLaneAuction.CallOpts, bidder) -} - -// ChainOwnerAddress is a free data retrieval call binding the contract method 0x79a47e29. -// -// Solidity: function chainOwnerAddress() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) ChainOwnerAddress(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "chainOwnerAddress") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// ChainOwnerAddress is a free data retrieval call binding the contract method 0x79a47e29. -// -// Solidity: function chainOwnerAddress() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) ChainOwnerAddress() (common.Address, error) { - return _ExpressLaneAuction.Contract.ChainOwnerAddress(&_ExpressLaneAuction.CallOpts) -} - -// ChainOwnerAddress is a free data retrieval call binding the contract method 0x79a47e29. -// -// Solidity: function chainOwnerAddress() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) ChainOwnerAddress() (common.Address, error) { - return _ExpressLaneAuction.Contract.ChainOwnerAddress(&_ExpressLaneAuction.CallOpts) -} - -// CurrentExpressLaneController is a free data retrieval call binding the contract method 0x4f2a9bdb. -// -// Solidity: function currentExpressLaneController() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) CurrentExpressLaneController(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "currentExpressLaneController") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// CurrentExpressLaneController is a free data retrieval call binding the contract method 0x4f2a9bdb. -// -// Solidity: function currentExpressLaneController() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) CurrentExpressLaneController() (common.Address, error) { - return _ExpressLaneAuction.Contract.CurrentExpressLaneController(&_ExpressLaneAuction.CallOpts) -} - -// CurrentExpressLaneController is a free data retrieval call binding the contract method 0x4f2a9bdb. -// -// Solidity: function currentExpressLaneController() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) CurrentExpressLaneController() (common.Address, error) { - return _ExpressLaneAuction.Contract.CurrentExpressLaneController(&_ExpressLaneAuction.CallOpts) -} - -// CurrentRound is a free data retrieval call binding the contract method 0x8a19c8bc. -// -// Solidity: function currentRound() view returns(uint64) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) CurrentRound(opts *bind.CallOpts) (uint64, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "currentRound") - - if err != nil { - return *new(uint64), err - } - - out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) - - return out0, err - -} - -// CurrentRound is a free data retrieval call binding the contract method 0x8a19c8bc. -// -// Solidity: function currentRound() view returns(uint64) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) CurrentRound() (uint64, error) { - return _ExpressLaneAuction.Contract.CurrentRound(&_ExpressLaneAuction.CallOpts) -} - -// CurrentRound is a free data retrieval call binding the contract method 0x8a19c8bc. -// -// Solidity: function currentRound() view returns(uint64) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) CurrentRound() (uint64, error) { - return _ExpressLaneAuction.Contract.CurrentRound(&_ExpressLaneAuction.CallOpts) -} - -// DepositBalance is a free data retrieval call binding the contract method 0x956501bb. -// -// Solidity: function depositBalance(address ) view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) DepositBalance(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "depositBalance", arg0) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// DepositBalance is a free data retrieval call binding the contract method 0x956501bb. -// -// Solidity: function depositBalance(address ) view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) DepositBalance(arg0 common.Address) (*big.Int, error) { - return _ExpressLaneAuction.Contract.DepositBalance(&_ExpressLaneAuction.CallOpts, arg0) -} - -// DepositBalance is a free data retrieval call binding the contract method 0x956501bb. -// -// Solidity: function depositBalance(address ) view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) DepositBalance(arg0 common.Address) (*big.Int, error) { - return _ExpressLaneAuction.Contract.DepositBalance(&_ExpressLaneAuction.CallOpts, arg0) -} - -// ExpressLaneControllerByRound is a free data retrieval call binding the contract method 0x8296df03. -// -// Solidity: function expressLaneControllerByRound(uint256 ) view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) ExpressLaneControllerByRound(opts *bind.CallOpts, arg0 *big.Int) (common.Address, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "expressLaneControllerByRound", arg0) - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// ExpressLaneControllerByRound is a free data retrieval call binding the contract method 0x8296df03. -// -// Solidity: function expressLaneControllerByRound(uint256 ) view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) ExpressLaneControllerByRound(arg0 *big.Int) (common.Address, error) { - return _ExpressLaneAuction.Contract.ExpressLaneControllerByRound(&_ExpressLaneAuction.CallOpts, arg0) -} - -// ExpressLaneControllerByRound is a free data retrieval call binding the contract method 0x8296df03. -// -// Solidity: function expressLaneControllerByRound(uint256 ) view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) ExpressLaneControllerByRound(arg0 *big.Int) (common.Address, error) { - return _ExpressLaneAuction.Contract.ExpressLaneControllerByRound(&_ExpressLaneAuction.CallOpts, arg0) -} - -// GetCurrentReservePrice is a free data retrieval call binding the contract method 0xb941ce6e. -// -// Solidity: function getCurrentReservePrice() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) GetCurrentReservePrice(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "getCurrentReservePrice") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// GetCurrentReservePrice is a free data retrieval call binding the contract method 0xb941ce6e. -// -// Solidity: function getCurrentReservePrice() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) GetCurrentReservePrice() (*big.Int, error) { - return _ExpressLaneAuction.Contract.GetCurrentReservePrice(&_ExpressLaneAuction.CallOpts) -} - -// GetCurrentReservePrice is a free data retrieval call binding the contract method 0xb941ce6e. -// -// Solidity: function getCurrentReservePrice() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) GetCurrentReservePrice() (*big.Int, error) { - return _ExpressLaneAuction.Contract.GetCurrentReservePrice(&_ExpressLaneAuction.CallOpts) -} - -// GetminimalReservePrice is a free data retrieval call binding the contract method 0x03ba6662. -// -// Solidity: function getminimalReservePrice() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) GetminimalReservePrice(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "getminimalReservePrice") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// GetminimalReservePrice is a free data retrieval call binding the contract method 0x03ba6662. -// -// Solidity: function getminimalReservePrice() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) GetminimalReservePrice() (*big.Int, error) { - return _ExpressLaneAuction.Contract.GetminimalReservePrice(&_ExpressLaneAuction.CallOpts) -} - -// GetminimalReservePrice is a free data retrieval call binding the contract method 0x03ba6662. -// -// Solidity: function getminimalReservePrice() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) GetminimalReservePrice() (*big.Int, error) { - return _ExpressLaneAuction.Contract.GetminimalReservePrice(&_ExpressLaneAuction.CallOpts) -} - -// InitialRoundTimestamp is a free data retrieval call binding the contract method 0xc0389979. -// -// Solidity: function initialRoundTimestamp() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) InitialRoundTimestamp(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "initialRoundTimestamp") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// InitialRoundTimestamp is a free data retrieval call binding the contract method 0xc0389979. -// -// Solidity: function initialRoundTimestamp() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) InitialRoundTimestamp() (*big.Int, error) { - return _ExpressLaneAuction.Contract.InitialRoundTimestamp(&_ExpressLaneAuction.CallOpts) -} - -// InitialRoundTimestamp is a free data retrieval call binding the contract method 0xc0389979. -// -// Solidity: function initialRoundTimestamp() view returns(uint256) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) InitialRoundTimestamp() (*big.Int, error) { - return _ExpressLaneAuction.Contract.InitialRoundTimestamp(&_ExpressLaneAuction.CallOpts) -} - -// PendingWithdrawalByBidder is a free data retrieval call binding the contract method 0x5f70f903. -// -// Solidity: function pendingWithdrawalByBidder(address ) view returns(uint256 amount, uint64 submittedRound) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) PendingWithdrawalByBidder(opts *bind.CallOpts, arg0 common.Address) (struct { - Amount *big.Int - SubmittedRound uint64 -}, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "pendingWithdrawalByBidder", arg0) - - outstruct := new(struct { - Amount *big.Int - SubmittedRound uint64 - }) - if err != nil { - return *outstruct, err - } - - outstruct.Amount = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - outstruct.SubmittedRound = *abi.ConvertType(out[1], new(uint64)).(*uint64) - - return *outstruct, err - -} - -// PendingWithdrawalByBidder is a free data retrieval call binding the contract method 0x5f70f903. -// -// Solidity: function pendingWithdrawalByBidder(address ) view returns(uint256 amount, uint64 submittedRound) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) PendingWithdrawalByBidder(arg0 common.Address) (struct { - Amount *big.Int - SubmittedRound uint64 -}, error) { - return _ExpressLaneAuction.Contract.PendingWithdrawalByBidder(&_ExpressLaneAuction.CallOpts, arg0) -} - -// PendingWithdrawalByBidder is a free data retrieval call binding the contract method 0x5f70f903. -// -// Solidity: function pendingWithdrawalByBidder(address ) view returns(uint256 amount, uint64 submittedRound) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) PendingWithdrawalByBidder(arg0 common.Address) (struct { - Amount *big.Int - SubmittedRound uint64 -}, error) { - return _ExpressLaneAuction.Contract.PendingWithdrawalByBidder(&_ExpressLaneAuction.CallOpts, arg0) -} - -// ReservePriceSetterAddress is a free data retrieval call binding the contract method 0x7c62b5cd. -// -// Solidity: function reservePriceSetterAddress() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) ReservePriceSetterAddress(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "reservePriceSetterAddress") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// ReservePriceSetterAddress is a free data retrieval call binding the contract method 0x7c62b5cd. -// -// Solidity: function reservePriceSetterAddress() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) ReservePriceSetterAddress() (common.Address, error) { - return _ExpressLaneAuction.Contract.ReservePriceSetterAddress(&_ExpressLaneAuction.CallOpts) -} - -// ReservePriceSetterAddress is a free data retrieval call binding the contract method 0x7c62b5cd. -// -// Solidity: function reservePriceSetterAddress() view returns(address) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) ReservePriceSetterAddress() (common.Address, error) { - return _ExpressLaneAuction.Contract.ReservePriceSetterAddress(&_ExpressLaneAuction.CallOpts) -} - -// RoundDurationSeconds is a free data retrieval call binding the contract method 0xcc963d15. -// -// Solidity: function roundDurationSeconds() view returns(uint64) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) RoundDurationSeconds(opts *bind.CallOpts) (uint64, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "roundDurationSeconds") - - if err != nil { - return *new(uint64), err - } - - out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) - - return out0, err - -} - -// RoundDurationSeconds is a free data retrieval call binding the contract method 0xcc963d15. -// -// Solidity: function roundDurationSeconds() view returns(uint64) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) RoundDurationSeconds() (uint64, error) { - return _ExpressLaneAuction.Contract.RoundDurationSeconds(&_ExpressLaneAuction.CallOpts) -} - -// RoundDurationSeconds is a free data retrieval call binding the contract method 0xcc963d15. -// -// Solidity: function roundDurationSeconds() view returns(uint64) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) RoundDurationSeconds() (uint64, error) { - return _ExpressLaneAuction.Contract.RoundDurationSeconds(&_ExpressLaneAuction.CallOpts) -} - -// VerifySignature is a free data retrieval call binding the contract method 0x24e359e7. -// -// Solidity: function verifySignature(address signer, bytes message, bytes signature) pure returns(bool) -func (_ExpressLaneAuction *ExpressLaneAuctionCaller) VerifySignature(opts *bind.CallOpts, signer common.Address, message []byte, signature []byte) (bool, error) { - var out []interface{} - err := _ExpressLaneAuction.contract.Call(opts, &out, "verifySignature", signer, message, signature) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// VerifySignature is a free data retrieval call binding the contract method 0x24e359e7. -// -// Solidity: function verifySignature(address signer, bytes message, bytes signature) pure returns(bool) -func (_ExpressLaneAuction *ExpressLaneAuctionSession) VerifySignature(signer common.Address, message []byte, signature []byte) (bool, error) { - return _ExpressLaneAuction.Contract.VerifySignature(&_ExpressLaneAuction.CallOpts, signer, message, signature) -} - -// VerifySignature is a free data retrieval call binding the contract method 0x24e359e7. -// -// Solidity: function verifySignature(address signer, bytes message, bytes signature) pure returns(bool) -func (_ExpressLaneAuction *ExpressLaneAuctionCallerSession) VerifySignature(signer common.Address, message []byte, signature []byte) (bool, error) { - return _ExpressLaneAuction.Contract.VerifySignature(&_ExpressLaneAuction.CallOpts, signer, message, signature) -} - -// CancelUpcomingRound is a paid mutator transaction binding the contract method 0x4d1846dc. -// -// Solidity: function cancelUpcomingRound() returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) CancelUpcomingRound(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "cancelUpcomingRound") -} - -// CancelUpcomingRound is a paid mutator transaction binding the contract method 0x4d1846dc. -// -// Solidity: function cancelUpcomingRound() returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) CancelUpcomingRound() (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.CancelUpcomingRound(&_ExpressLaneAuction.TransactOpts) -} - -// CancelUpcomingRound is a paid mutator transaction binding the contract method 0x4d1846dc. -// -// Solidity: function cancelUpcomingRound() returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) CancelUpcomingRound() (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.CancelUpcomingRound(&_ExpressLaneAuction.TransactOpts) -} - -// DelegateExpressLane is a paid mutator transaction binding the contract method 0xd6e5fb7d. -// -// Solidity: function delegateExpressLane(address delegate) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) DelegateExpressLane(opts *bind.TransactOpts, delegate common.Address) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "delegateExpressLane", delegate) -} - -// DelegateExpressLane is a paid mutator transaction binding the contract method 0xd6e5fb7d. -// -// Solidity: function delegateExpressLane(address delegate) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) DelegateExpressLane(delegate common.Address) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.DelegateExpressLane(&_ExpressLaneAuction.TransactOpts, delegate) -} - -// DelegateExpressLane is a paid mutator transaction binding the contract method 0xd6e5fb7d. -// -// Solidity: function delegateExpressLane(address delegate) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) DelegateExpressLane(delegate common.Address) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.DelegateExpressLane(&_ExpressLaneAuction.TransactOpts, delegate) -} - -// FinalizeWithdrawal is a paid mutator transaction binding the contract method 0xc5b6aa2f. -// -// Solidity: function finalizeWithdrawal() returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) FinalizeWithdrawal(opts *bind.TransactOpts) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "finalizeWithdrawal") -} - -// FinalizeWithdrawal is a paid mutator transaction binding the contract method 0xc5b6aa2f. -// -// Solidity: function finalizeWithdrawal() returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) FinalizeWithdrawal() (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.FinalizeWithdrawal(&_ExpressLaneAuction.TransactOpts) -} - -// FinalizeWithdrawal is a paid mutator transaction binding the contract method 0xc5b6aa2f. -// -// Solidity: function finalizeWithdrawal() returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) FinalizeWithdrawal() (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.FinalizeWithdrawal(&_ExpressLaneAuction.TransactOpts) -} - -// InitiateWithdrawal is a paid mutator transaction binding the contract method 0x12edde5e. -// -// Solidity: function initiateWithdrawal(uint256 amount) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) InitiateWithdrawal(opts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "initiateWithdrawal", amount) -} - -// InitiateWithdrawal is a paid mutator transaction binding the contract method 0x12edde5e. -// -// Solidity: function initiateWithdrawal(uint256 amount) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) InitiateWithdrawal(amount *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.InitiateWithdrawal(&_ExpressLaneAuction.TransactOpts, amount) -} - -// InitiateWithdrawal is a paid mutator transaction binding the contract method 0x12edde5e. -// -// Solidity: function initiateWithdrawal(uint256 amount) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) InitiateWithdrawal(amount *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.InitiateWithdrawal(&_ExpressLaneAuction.TransactOpts, amount) -} - -// ResolveAuction is a paid mutator transaction binding the contract method 0xcd4abf71. -// -// Solidity: function resolveAuction((address,uint256,uint256,uint256,bytes) bid1, (address,uint256,uint256,uint256,bytes) bid2) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) ResolveAuction(opts *bind.TransactOpts, bid1 Bid, bid2 Bid) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "resolveAuction", bid1, bid2) -} - -// ResolveAuction is a paid mutator transaction binding the contract method 0xcd4abf71. -// -// Solidity: function resolveAuction((address,uint256,uint256,uint256,bytes) bid1, (address,uint256,uint256,uint256,bytes) bid2) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) ResolveAuction(bid1 Bid, bid2 Bid) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.ResolveAuction(&_ExpressLaneAuction.TransactOpts, bid1, bid2) -} - -// ResolveAuction is a paid mutator transaction binding the contract method 0xcd4abf71. -// -// Solidity: function resolveAuction((address,uint256,uint256,uint256,bytes) bid1, (address,uint256,uint256,uint256,bytes) bid2) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) ResolveAuction(bid1 Bid, bid2 Bid) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.ResolveAuction(&_ExpressLaneAuction.TransactOpts, bid1, bid2) -} - -// ResolveSingleBidAuction is a paid mutator transaction binding the contract method 0xf5f754d6. -// -// Solidity: function resolveSingleBidAuction((address,uint256,uint256,uint256,bytes) bid) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) ResolveSingleBidAuction(opts *bind.TransactOpts, bid Bid) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "resolveSingleBidAuction", bid) -} - -// ResolveSingleBidAuction is a paid mutator transaction binding the contract method 0xf5f754d6. -// -// Solidity: function resolveSingleBidAuction((address,uint256,uint256,uint256,bytes) bid) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) ResolveSingleBidAuction(bid Bid) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.ResolveSingleBidAuction(&_ExpressLaneAuction.TransactOpts, bid) -} - -// ResolveSingleBidAuction is a paid mutator transaction binding the contract method 0xf5f754d6. -// -// Solidity: function resolveSingleBidAuction((address,uint256,uint256,uint256,bytes) bid) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) ResolveSingleBidAuction(bid Bid) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.ResolveSingleBidAuction(&_ExpressLaneAuction.TransactOpts, bid) -} - -// SetCurrentReservePrice is a paid mutator transaction binding the contract method 0x574a9b5f. -// -// Solidity: function setCurrentReservePrice(uint256 _currentReservePrice) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SetCurrentReservePrice(opts *bind.TransactOpts, _currentReservePrice *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "setCurrentReservePrice", _currentReservePrice) -} - -// SetCurrentReservePrice is a paid mutator transaction binding the contract method 0x574a9b5f. -// -// Solidity: function setCurrentReservePrice(uint256 _currentReservePrice) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) SetCurrentReservePrice(_currentReservePrice *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SetCurrentReservePrice(&_ExpressLaneAuction.TransactOpts, _currentReservePrice) -} - -// SetCurrentReservePrice is a paid mutator transaction binding the contract method 0x574a9b5f. -// -// Solidity: function setCurrentReservePrice(uint256 _currentReservePrice) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SetCurrentReservePrice(_currentReservePrice *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SetCurrentReservePrice(&_ExpressLaneAuction.TransactOpts, _currentReservePrice) -} - -// SetMinimalReservePrice is a paid mutator transaction binding the contract method 0xd6ded1bc. -// -// Solidity: function setMinimalReservePrice(uint256 _minimalReservePrice) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SetMinimalReservePrice(opts *bind.TransactOpts, _minimalReservePrice *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "setMinimalReservePrice", _minimalReservePrice) -} - -// SetMinimalReservePrice is a paid mutator transaction binding the contract method 0xd6ded1bc. -// -// Solidity: function setMinimalReservePrice(uint256 _minimalReservePrice) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) SetMinimalReservePrice(_minimalReservePrice *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SetMinimalReservePrice(&_ExpressLaneAuction.TransactOpts, _minimalReservePrice) -} - -// SetMinimalReservePrice is a paid mutator transaction binding the contract method 0xd6ded1bc. -// -// Solidity: function setMinimalReservePrice(uint256 _minimalReservePrice) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SetMinimalReservePrice(_minimalReservePrice *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SetMinimalReservePrice(&_ExpressLaneAuction.TransactOpts, _minimalReservePrice) -} - -// SetReservePriceAddresses is a paid mutator transaction binding the contract method 0xf66fda64. -// -// Solidity: function setReservePriceAddresses(address _reservePriceSetterAddr) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SetReservePriceAddresses(opts *bind.TransactOpts, _reservePriceSetterAddr common.Address) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "setReservePriceAddresses", _reservePriceSetterAddr) -} - -// SetReservePriceAddresses is a paid mutator transaction binding the contract method 0xf66fda64. -// -// Solidity: function setReservePriceAddresses(address _reservePriceSetterAddr) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) SetReservePriceAddresses(_reservePriceSetterAddr common.Address) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SetReservePriceAddresses(&_ExpressLaneAuction.TransactOpts, _reservePriceSetterAddr) -} - -// SetReservePriceAddresses is a paid mutator transaction binding the contract method 0xf66fda64. -// -// Solidity: function setReservePriceAddresses(address _reservePriceSetterAddr) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SetReservePriceAddresses(_reservePriceSetterAddr common.Address) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SetReservePriceAddresses(&_ExpressLaneAuction.TransactOpts, _reservePriceSetterAddr) -} - -// SubmitDeposit is a paid mutator transaction binding the contract method 0xdbeb2012. -// -// Solidity: function submitDeposit(uint256 amount) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactor) SubmitDeposit(opts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.contract.Transact(opts, "submitDeposit", amount) -} - -// SubmitDeposit is a paid mutator transaction binding the contract method 0xdbeb2012. -// -// Solidity: function submitDeposit(uint256 amount) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionSession) SubmitDeposit(amount *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SubmitDeposit(&_ExpressLaneAuction.TransactOpts, amount) -} - -// SubmitDeposit is a paid mutator transaction binding the contract method 0xdbeb2012. -// -// Solidity: function submitDeposit(uint256 amount) returns() -func (_ExpressLaneAuction *ExpressLaneAuctionTransactorSession) SubmitDeposit(amount *big.Int) (*types.Transaction, error) { - return _ExpressLaneAuction.Contract.SubmitDeposit(&_ExpressLaneAuction.TransactOpts, amount) -} - -// ExpressLaneAuctionAuctionResolvedIterator is returned from FilterAuctionResolved and is used to iterate over the raw logs and unpacked data for AuctionResolved events raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionAuctionResolvedIterator struct { - Event *ExpressLaneAuctionAuctionResolved // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ExpressLaneAuctionAuctionResolvedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionAuctionResolved) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionAuctionResolved) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ExpressLaneAuctionAuctionResolvedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ExpressLaneAuctionAuctionResolvedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ExpressLaneAuctionAuctionResolved represents a AuctionResolved event raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionAuctionResolved struct { - WinningBidAmount *big.Int - SecondPlaceBidAmount *big.Int - WinningBidder common.Address - WinnerRound *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterAuctionResolved is a free log retrieval operation binding the contract event 0xebab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef0. -// -// Solidity: event AuctionResolved(uint256 winningBidAmount, uint256 secondPlaceBidAmount, address indexed winningBidder, uint256 indexed winnerRound) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterAuctionResolved(opts *bind.FilterOpts, winningBidder []common.Address, winnerRound []*big.Int) (*ExpressLaneAuctionAuctionResolvedIterator, error) { - - var winningBidderRule []interface{} - for _, winningBidderItem := range winningBidder { - winningBidderRule = append(winningBidderRule, winningBidderItem) - } - var winnerRoundRule []interface{} - for _, winnerRoundItem := range winnerRound { - winnerRoundRule = append(winnerRoundRule, winnerRoundItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "AuctionResolved", winningBidderRule, winnerRoundRule) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionAuctionResolvedIterator{contract: _ExpressLaneAuction.contract, event: "AuctionResolved", logs: logs, sub: sub}, nil -} - -// WatchAuctionResolved is a free log subscription operation binding the contract event 0xebab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef0. -// -// Solidity: event AuctionResolved(uint256 winningBidAmount, uint256 secondPlaceBidAmount, address indexed winningBidder, uint256 indexed winnerRound) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchAuctionResolved(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionAuctionResolved, winningBidder []common.Address, winnerRound []*big.Int) (event.Subscription, error) { - - var winningBidderRule []interface{} - for _, winningBidderItem := range winningBidder { - winningBidderRule = append(winningBidderRule, winningBidderItem) - } - var winnerRoundRule []interface{} - for _, winnerRoundItem := range winnerRound { - winnerRoundRule = append(winnerRoundRule, winnerRoundItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "AuctionResolved", winningBidderRule, winnerRoundRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ExpressLaneAuctionAuctionResolved) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "AuctionResolved", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseAuctionResolved is a log parse operation binding the contract event 0xebab47201515f7ff99c665889a24e3ea116be175b1504243f6711d4734655ef0. -// -// Solidity: event AuctionResolved(uint256 winningBidAmount, uint256 secondPlaceBidAmount, address indexed winningBidder, uint256 indexed winnerRound) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseAuctionResolved(log types.Log) (*ExpressLaneAuctionAuctionResolved, error) { - event := new(ExpressLaneAuctionAuctionResolved) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "AuctionResolved", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ExpressLaneAuctionDepositSubmittedIterator is returned from FilterDepositSubmitted and is used to iterate over the raw logs and unpacked data for DepositSubmitted events raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionDepositSubmittedIterator struct { - Event *ExpressLaneAuctionDepositSubmitted // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ExpressLaneAuctionDepositSubmittedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionDepositSubmitted) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionDepositSubmitted) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ExpressLaneAuctionDepositSubmittedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ExpressLaneAuctionDepositSubmittedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ExpressLaneAuctionDepositSubmitted represents a DepositSubmitted event raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionDepositSubmitted struct { - Bidder common.Address - Amount *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterDepositSubmitted is a free log retrieval operation binding the contract event 0xeafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa705. -// -// Solidity: event DepositSubmitted(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterDepositSubmitted(opts *bind.FilterOpts, bidder []common.Address) (*ExpressLaneAuctionDepositSubmittedIterator, error) { - - var bidderRule []interface{} - for _, bidderItem := range bidder { - bidderRule = append(bidderRule, bidderItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "DepositSubmitted", bidderRule) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionDepositSubmittedIterator{contract: _ExpressLaneAuction.contract, event: "DepositSubmitted", logs: logs, sub: sub}, nil -} - -// WatchDepositSubmitted is a free log subscription operation binding the contract event 0xeafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa705. -// -// Solidity: event DepositSubmitted(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchDepositSubmitted(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionDepositSubmitted, bidder []common.Address) (event.Subscription, error) { - - var bidderRule []interface{} - for _, bidderItem := range bidder { - bidderRule = append(bidderRule, bidderItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "DepositSubmitted", bidderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ExpressLaneAuctionDepositSubmitted) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "DepositSubmitted", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseDepositSubmitted is a log parse operation binding the contract event 0xeafda908ad84599c76a83ab100b99811f430e25afb46e42febfe5552aeafa705. -// -// Solidity: event DepositSubmitted(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseDepositSubmitted(log types.Log) (*ExpressLaneAuctionDepositSubmitted, error) { - event := new(ExpressLaneAuctionDepositSubmitted) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "DepositSubmitted", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ExpressLaneAuctionExpressLaneControlDelegatedIterator is returned from FilterExpressLaneControlDelegated and is used to iterate over the raw logs and unpacked data for ExpressLaneControlDelegated events raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionExpressLaneControlDelegatedIterator struct { - Event *ExpressLaneAuctionExpressLaneControlDelegated // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ExpressLaneAuctionExpressLaneControlDelegatedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionExpressLaneControlDelegated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionExpressLaneControlDelegated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ExpressLaneAuctionExpressLaneControlDelegatedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ExpressLaneAuctionExpressLaneControlDelegatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ExpressLaneAuctionExpressLaneControlDelegated represents a ExpressLaneControlDelegated event raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionExpressLaneControlDelegated struct { - From common.Address - To common.Address - Round uint64 - Raw types.Log // Blockchain specific contextual infos -} - -// FilterExpressLaneControlDelegated is a free log retrieval operation binding the contract event 0xdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede. -// -// Solidity: event ExpressLaneControlDelegated(address indexed from, address indexed to, uint64 round) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterExpressLaneControlDelegated(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ExpressLaneAuctionExpressLaneControlDelegatedIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "ExpressLaneControlDelegated", fromRule, toRule) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionExpressLaneControlDelegatedIterator{contract: _ExpressLaneAuction.contract, event: "ExpressLaneControlDelegated", logs: logs, sub: sub}, nil -} - -// WatchExpressLaneControlDelegated is a free log subscription operation binding the contract event 0xdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede. -// -// Solidity: event ExpressLaneControlDelegated(address indexed from, address indexed to, uint64 round) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchExpressLaneControlDelegated(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionExpressLaneControlDelegated, from []common.Address, to []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "ExpressLaneControlDelegated", fromRule, toRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ExpressLaneAuctionExpressLaneControlDelegated) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "ExpressLaneControlDelegated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseExpressLaneControlDelegated is a log parse operation binding the contract event 0xdf423ef3c0bf417d64c30754b79583ec212ba0b1bd0f6f9cc2a7819c0844bede. -// -// Solidity: event ExpressLaneControlDelegated(address indexed from, address indexed to, uint64 round) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseExpressLaneControlDelegated(log types.Log) (*ExpressLaneAuctionExpressLaneControlDelegated, error) { - event := new(ExpressLaneAuctionExpressLaneControlDelegated) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "ExpressLaneControlDelegated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ExpressLaneAuctionWithdrawalFinalizedIterator is returned from FilterWithdrawalFinalized and is used to iterate over the raw logs and unpacked data for WithdrawalFinalized events raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionWithdrawalFinalizedIterator struct { - Event *ExpressLaneAuctionWithdrawalFinalized // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ExpressLaneAuctionWithdrawalFinalizedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionWithdrawalFinalized) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionWithdrawalFinalized) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ExpressLaneAuctionWithdrawalFinalizedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ExpressLaneAuctionWithdrawalFinalizedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ExpressLaneAuctionWithdrawalFinalized represents a WithdrawalFinalized event raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionWithdrawalFinalized struct { - Bidder common.Address - Amount *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterWithdrawalFinalized is a free log retrieval operation binding the contract event 0x9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda8. -// -// Solidity: event WithdrawalFinalized(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterWithdrawalFinalized(opts *bind.FilterOpts, bidder []common.Address) (*ExpressLaneAuctionWithdrawalFinalizedIterator, error) { - - var bidderRule []interface{} - for _, bidderItem := range bidder { - bidderRule = append(bidderRule, bidderItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "WithdrawalFinalized", bidderRule) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionWithdrawalFinalizedIterator{contract: _ExpressLaneAuction.contract, event: "WithdrawalFinalized", logs: logs, sub: sub}, nil -} - -// WatchWithdrawalFinalized is a free log subscription operation binding the contract event 0x9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda8. -// -// Solidity: event WithdrawalFinalized(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchWithdrawalFinalized(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionWithdrawalFinalized, bidder []common.Address) (event.Subscription, error) { - - var bidderRule []interface{} - for _, bidderItem := range bidder { - bidderRule = append(bidderRule, bidderItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "WithdrawalFinalized", bidderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ExpressLaneAuctionWithdrawalFinalized) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalFinalized", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseWithdrawalFinalized is a log parse operation binding the contract event 0x9e5c4f9f4e46b8629d3dda85f43a69194f50254404a72dc62b9e932d9c94eda8. -// -// Solidity: event WithdrawalFinalized(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseWithdrawalFinalized(log types.Log) (*ExpressLaneAuctionWithdrawalFinalized, error) { - event := new(ExpressLaneAuctionWithdrawalFinalized) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalFinalized", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ExpressLaneAuctionWithdrawalInitiatedIterator is returned from FilterWithdrawalInitiated and is used to iterate over the raw logs and unpacked data for WithdrawalInitiated events raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionWithdrawalInitiatedIterator struct { - Event *ExpressLaneAuctionWithdrawalInitiated // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ExpressLaneAuctionWithdrawalInitiatedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionWithdrawalInitiated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ExpressLaneAuctionWithdrawalInitiated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ExpressLaneAuctionWithdrawalInitiatedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ExpressLaneAuctionWithdrawalInitiatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ExpressLaneAuctionWithdrawalInitiated represents a WithdrawalInitiated event raised by the ExpressLaneAuction contract. -type ExpressLaneAuctionWithdrawalInitiated struct { - Bidder common.Address - Amount *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterWithdrawalInitiated is a free log retrieval operation binding the contract event 0x6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec646. -// -// Solidity: event WithdrawalInitiated(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) FilterWithdrawalInitiated(opts *bind.FilterOpts, bidder []common.Address) (*ExpressLaneAuctionWithdrawalInitiatedIterator, error) { - - var bidderRule []interface{} - for _, bidderItem := range bidder { - bidderRule = append(bidderRule, bidderItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.FilterLogs(opts, "WithdrawalInitiated", bidderRule) - if err != nil { - return nil, err - } - return &ExpressLaneAuctionWithdrawalInitiatedIterator{contract: _ExpressLaneAuction.contract, event: "WithdrawalInitiated", logs: logs, sub: sub}, nil -} - -// WatchWithdrawalInitiated is a free log subscription operation binding the contract event 0x6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec646. -// -// Solidity: event WithdrawalInitiated(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) WatchWithdrawalInitiated(opts *bind.WatchOpts, sink chan<- *ExpressLaneAuctionWithdrawalInitiated, bidder []common.Address) (event.Subscription, error) { - - var bidderRule []interface{} - for _, bidderItem := range bidder { - bidderRule = append(bidderRule, bidderItem) - } - - logs, sub, err := _ExpressLaneAuction.contract.WatchLogs(opts, "WithdrawalInitiated", bidderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ExpressLaneAuctionWithdrawalInitiated) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalInitiated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseWithdrawalInitiated is a log parse operation binding the contract event 0x6d92f7d3303f995bf21956bb0c51b388bae348eaf45c23debd2cfa3fcd9ec646. -// -// Solidity: event WithdrawalInitiated(address indexed bidder, uint256 amount) -func (_ExpressLaneAuction *ExpressLaneAuctionFilterer) ParseWithdrawalInitiated(log types.Log) (*ExpressLaneAuctionWithdrawalInitiated, error) { - event := new(ExpressLaneAuctionWithdrawalInitiated) - if err := _ExpressLaneAuction.contract.UnpackLog(event, "WithdrawalInitiated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 35ed194823..da743ccc10 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -12,22 +12,23 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/stretchr/testify/require" ) type auctionSetup struct { - chainId *big.Int - auctionMasterAddr common.Address - auctionContract *bindings.ExpressLaneAuction - erc20Addr common.Address - erc20Contract *bindings.MockERC20 - initialTimestamp time.Time - roundDuration time.Duration - expressLaneAddr common.Address - bidReceiverAddr common.Address - accounts []*testAccount - backend *simulated.Backend + chainId *big.Int + expressLaneAuctionAddr common.Address + expressLaneAuction *express_lane_auctiongen.ExpressLaneAuction + erc20Addr common.Address + erc20Contract *bindings.MockERC20 + initialTimestamp time.Time + roundDuration time.Duration + expressLaneAddr common.Address + bidReceiverAddr common.Address + accounts []*testAccount + backend *simulated.Backend } func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { @@ -81,28 +82,38 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { initialTimestamp := big.NewInt(now.Unix()) // Deploy the auction manager contract. - currReservePrice := big.NewInt(1) - minReservePrice := big.NewInt(1) + // currReservePrice := big.NewInt(1) + // minReservePrice := big.NewInt(1) reservePriceSetter := opts.From - auctionContractAddr, tx, auctionContract, err := bindings.DeployExpressLaneAuction( - opts, backend.Client(), expressLaneAddr, reservePriceSetter, bidReceiverAddr, bidRoundSeconds, initialTimestamp, erc20Addr, currReservePrice, minReservePrice, + auctionContractAddr, tx, auctionContract, err := express_lane_auctiongen.DeployExpressLaneAuction( + opts, backend.Client(), ) require.NoError(t, err) if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { t.Fatal(err) } + tx, err = auctionContract.Initialize(opts, opts.From, bidReceiverAddr, erc20Addr, express_lane_auctiongen.RoundTimingInfo{ + OffsetTimestamp: initialTimestamp.Uint64(), + RoundDurationSeconds: bidRoundSeconds, + }, big.NewInt(0), opts.From, reservePriceSetter, reservePriceSetter, reservePriceSetter) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { + t.Fatal(err) + } + + // TODO: Set the reserve price here. return &auctionSetup{ - chainId: chainId, - auctionMasterAddr: auctionContractAddr, - auctionContract: auctionContract, - erc20Addr: erc20Addr, - erc20Contract: erc20, - initialTimestamp: now, - roundDuration: time.Minute, - expressLaneAddr: expressLaneAddr, - bidReceiverAddr: bidReceiverAddr, - accounts: accs, - backend: backend, + chainId: chainId, + expressLaneAuctionAddr: auctionContractAddr, + expressLaneAuction: auctionContract, + erc20Addr: erc20Addr, + erc20Contract: erc20, + initialTimestamp: now, + roundDuration: time.Minute, + expressLaneAddr: expressLaneAddr, + bidReceiverAddr: bidReceiverAddr, + accounts: accs, + backend: backend, } } @@ -115,8 +126,7 @@ func setupBidderClient( &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, // testSetup.backend.Client(), nil, - testSetup.auctionMasterAddr, - nil, + testSetup.expressLaneAuctionAddr, nil, ) require.NoError(t, err) @@ -125,7 +135,7 @@ func setupBidderClient( maxUint256 := big.NewInt(1) maxUint256.Lsh(maxUint256, 256).Sub(maxUint256, big.NewInt(1)) tx, err := testSetup.erc20Contract.Approve( - account.txOpts, testSetup.auctionMasterAddr, maxUint256, + account.txOpts, testSetup.expressLaneAuctionAddr, maxUint256, ) require.NoError(t, err) if _, err = bind.WaitMined(ctx, testSetup.backend.Client(), tx); err != nil { From b829d9bff5cb41f99e1ecc11537951f79bbe8a2a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 23 Jul 2024 14:29:52 -0500 Subject: [PATCH 008/109] builds --- execution/gethexec/express_lane_service.go | 53 +++++++++++----------- system_tests/seqfeed_test.go | 39 +++++++--------- 2 files changed, 43 insertions(+), 49 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 324207401c..6e0a1afffb 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -3,7 +3,6 @@ package gethexec import ( "context" "fmt" - "math/big" "sync" "time" @@ -14,8 +13,8 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost" - "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/stopwaiter" ) @@ -37,7 +36,7 @@ type expressLaneService struct { client arbutil.L1Interface control expressLaneControl reservedAddress common.Address - auctionContract *bindings.ExpressLaneAuction + auctionContract *express_lane_auctiongen.ExpressLaneAuction initialTimestamp time.Time roundDuration time.Duration chainConfig *params.ChainConfig @@ -47,33 +46,33 @@ func newExpressLaneService( client arbutil.L1Interface, auctionContractAddr common.Address, ) (*expressLaneService, error) { - auctionContract, err := bindings.NewExpressLaneAuction(auctionContractAddr, client) - if err != nil { - return nil, err - } - initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) - if err != nil { - return nil, err - } - roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) - if err != nil { - return nil, err - } - initialTimestamp := time.Unix(initialRoundTimestamp.Int64(), 0) - currRound := timeboost.CurrentRound(initialTimestamp, time.Duration(roundDurationSeconds)*time.Second) - controller, err := auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(currRound))) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) if err != nil { return nil, err } + // initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + // if err != nil { + // return nil, err + // } + // roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) + // if err != nil { + // return nil, err + // } + // initialTimestamp := time.Unix(initialRoundTimestamp.Int64(), 0) + // currRound := timeboost.CurrentRound(initialTimestamp, time.Duration(roundDurationSeconds)*time.Second) + // controller, err := auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(currRound))) + // if err != nil { + // return nil, err + // } return &expressLaneService{ auctionContract: auctionContract, client: client, - initialTimestamp: initialTimestamp, + initialTimestamp: time.Now(), control: expressLaneControl{ - controller: controller, - round: currRound, + controller: common.Address{}, + round: 0, }, - roundDuration: time.Duration(roundDurationSeconds) * time.Second, + roundDuration: time.Second, }, nil } @@ -132,7 +131,7 @@ func (es *expressLaneService) Start(ctxIn context.Context) { Start: fromBlock, End: &toBlock, } - it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil) + it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) if err != nil { log.Error("Could not filter auction resolutions", "error", err) continue @@ -140,12 +139,12 @@ func (es *expressLaneService) Start(ctxIn context.Context) { for it.Next() { log.Info( "New express lane controller assigned", - "round", it.Event.WinnerRound, - "controller", it.Event.WinningBidder, + "round", it.Event.Round, + "controller", it.Event.FirstPriceExpressLaneController, ) es.Lock() - es.control.round = it.Event.WinnerRound.Uint64() - es.control.controller = it.Event.WinningBidder + es.control.round = it.Event.Round + es.control.controller = it.Event.FirstPriceExpressLaneController es.control.sequence = 0 // Sequence resets 0 for the new round. es.Unlock() } diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 1dc62bf7aa..36a84bbe21 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -25,6 +25,7 @@ import ( "github.com/offchainlabs/nitro/broadcaster/message" "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/relay" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/signature" @@ -127,7 +128,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { Require(t, err) t.Logf("%+v and %+v", seqInfo.Accounts["Alice"], seqInfo.Accounts["Bob"]) - auctionContract, err := bindings.NewExpressLaneAuction(auctionAddr, builderSeq.L1.Client) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionAddr, builderSeq.L1.Client) Require(t, err) _ = seqInfo _ = seqClient @@ -177,7 +178,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { auctionContractOpts := builderSeq.L1Info.GetDefaultTransactOpts("AuctionContract", ctx) chainId, err := l1client.ChainID(ctx) Require(t, err) - auctioneer, err := timeboost.NewAuctioneer(&auctionContractOpts, chainId, builderSeq.L1.Client, auctionContract) + auctioneer, err := timeboost.NewAuctioneer(&auctionContractOpts, chainId, builderSeq.L1.Client, auctionAddr, auctionContract) Require(t, err) go auctioneer.Start(ctx) @@ -194,10 +195,8 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { l1client, auctionAddr, nil, - auctioneer, ) Require(t, err) - go alice.Start(ctx) bobPriv := builderSeq.L1Info.Accounts["Bob"].PrivateKey bob, err := timeboost.NewBidderClient( @@ -210,16 +209,14 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { l1client, auctionAddr, nil, - auctioneer, ) Require(t, err) - go bob.Start(ctx) // Wait until the initial round. - initialTime, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) + info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) Require(t, err) - timeToWait := time.Until(time.Unix(initialTime.Int64(), 0)) - t.Log("Waiting until the initial round", timeToWait, time.Unix(initialTime.Int64(), 0)) + timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) + t.Log("Waiting until the initial round", timeToWait, time.Unix(int64(info.OffsetTimestamp), 0)) <-time.After(timeToWait) t.Log("Started auction master stack and bid clients") @@ -236,9 +233,9 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { // We are now in the bidding round, both issue their bids. Bob will win. t.Log("Alice and Bob now submitting their bids") - aliceBid, err := alice.Bid(ctx, big.NewInt(1)) + aliceBid, err := alice.Bid(ctx, big.NewInt(1), aliceOpts.From) Require(t, err) - bobBid, err := bob.Bid(ctx, big.NewInt(2)) + bobBid, err := bob.Bid(ctx, big.NewInt(2), bobOpts.From) Require(t, err) t.Logf("Alice bid %+v", aliceBid) t.Logf("Bob bid %+v", bobBid) @@ -257,9 +254,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) time.Sleep(waitTime) - initialTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) - Require(t, err) - currRound := timeboost.CurrentRound(time.Unix(initialTimestamp.Int64(), 0), roundDuration) + currRound := timeboost.CurrentRound(time.Unix(int64(info.OffsetTimestamp), 0), roundDuration) t.Log("curr round", currRound) if currRound != winnerRound { now = time.Now() @@ -268,12 +263,12 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { time.Sleep(waitTime) } - current, err := auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, new(big.Int).SetUint64(currRound)) - Require(t, err) + // current, err := auctionContract.(&bind.CallOpts{}, new(big.Int).SetUint64(currRound)) + // Require(t, err) - if current != bobOpts.From { - t.Log("Current express lane round controller is not Bob", current, aliceOpts.From) - } + // if current != bobOpts.From { + // t.Log("Current express lane round controller is not Bob", current, aliceOpts.From) + // } t.Log("Now submitting txs to sequencer") @@ -337,7 +332,7 @@ func awaitAuctionResolved( t *testing.T, ctx context.Context, client *ethclient.Client, - contract *bindings.ExpressLaneAuction, + contract *express_lane_auctiongen.ExpressLaneAuction, ) (common.Address, uint64) { fromBlock, err := client.BlockNumber(ctx) Require(t, err) @@ -362,13 +357,13 @@ func awaitAuctionResolved( Start: fromBlock, End: &toBlock, } - it, err := contract.FilterAuctionResolved(filterOpts, nil, nil) + it, err := contract.FilterAuctionResolved(filterOpts, nil, nil, nil) if err != nil { t.Log("Could not filter auction resolutions", err) continue } for it.Next() { - return it.Event.WinningBidder, it.Event.WinnerRound.Uint64() + return it.Event.FirstPriceBidder, it.Event.Round } fromBlock = toBlock } From 94c5653d6d734aa51cd9bc7c3bebc91957e13a6a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 23 Jul 2024 14:39:26 -0500 Subject: [PATCH 009/109] autonomous auctioneer bin --- cmd/autonomous-auctioneer/main.go | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 cmd/autonomous-auctioneer/main.go diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go new file mode 100644 index 0000000000..a82b65af67 --- /dev/null +++ b/cmd/autonomous-auctioneer/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +func main() { + os.Exit(mainImpl()) +} + +// Checks metrics and PProf flag, runs them if enabled. +// Note: they are separate so one can enable/disable them as they wish, the only +// requirement is that they can't run on the same address and port. +func startMetrics() error { + // mAddr := fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port) + // pAddr := fmt.Sprintf("%v:%v", cfg.PprofCfg.Addr, cfg.PprofCfg.Port) + // if cfg.Metrics && !metrics.Enabled { + // return fmt.Errorf("metrics must be enabled via command line by adding --metrics, json config has no effect") + // } + // if cfg.Metrics && cfg.PProf && mAddr == pAddr { + // return fmt.Errorf("metrics and pprof cannot be enabled on the same address:port: %s", mAddr) + // } + // if cfg.Metrics { + go metrics.CollectProcessMetrics(time.Second) + // exp.Setup(fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port)) + // } + // if cfg.PProf { + // genericconf.StartPprof(pAddr) + // } + return nil +} + +func mainImpl() int { + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + _ = ctx + + if err := startMetrics(); err != nil { + log.Error("Error starting metrics", "error", err) + return 1 + } + + fatalErrChan := make(chan error, 10) + sigint := make(chan os.Signal, 1) + signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) + + exitCode := 0 + select { + case err := <-fatalErrChan: + log.Error("shutting down due to fatal error", "err", err) + defer log.Error("shut down due to fatal error", "err", err) + exitCode = 1 + case <-sigint: + log.Info("shutting down because of sigint") + } + // cause future ctrl+c's to panic + close(sigint) + return exitCode +} From 676b89e67994d062ec024bc80d445d5436697ea1 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 23 Jul 2024 22:41:09 -0500 Subject: [PATCH 010/109] receive bid test passing --- timeboost/auctioneer.go | 10 +++-- timeboost/bidder_client.go | 15 +++++--- timeboost/bids_test.go | 41 ++++++++++---------- timeboost/setup_test.go | 76 ++++++++++++++++++++++++-------------- 4 files changed, 88 insertions(+), 54 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index ed0d9dca49..6692a0480a 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" ) @@ -21,7 +20,7 @@ type AuctioneerOpt func(*Auctioneer) type Auctioneer struct { txOpts *bind.TransactOpts chainId *big.Int - client arbutil.L1Interface + client Client auctionContract *express_lane_auctiongen.ExpressLaneAuction bidsReceiver chan *Bid bidCache *bidCache @@ -37,7 +36,7 @@ type Auctioneer struct { func NewAuctioneer( txOpts *bind.TransactOpts, chainId *big.Int, - client arbutil.L1Interface, + client Client, auctionContractAddr common.Address, auctionContract *express_lane_auctiongen.ExpressLaneAuction, opts ...AuctioneerOpt, @@ -51,6 +50,10 @@ func NewAuctioneer( auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second reserveSubmissionDuration := time.Duration(roundTimingInfo.ReserveSubmissionSeconds) * time.Second + minReservePrice, err := auctionContract.MinReservePrice(&bind.CallOpts{}) + if err != nil { + return nil, err + } am := &Auctioneer{ txOpts: txOpts, chainId: chainId, @@ -61,6 +64,7 @@ func NewAuctioneer( initialRoundTimestamp: initialTimestamp, auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, + reservePrice: minReservePrice, auctionClosingDuration: auctionClosingDuration, reserveSubmissionDuration: reserveSubmissionDuration, } diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 0a05b97e74..1d1c8e294f 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -12,13 +12,18 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" ) +type Client interface { + bind.ContractBackend + bind.DeployBackend + ChainID(ctx context.Context) (*big.Int, error) +} + type auctioneerConnection interface { - SubmitBid(ctx context.Context, bid *Bid) error + ReceiveBid(ctx context.Context, bid *Bid) error } type BidderClient struct { @@ -26,7 +31,7 @@ type BidderClient struct { name string auctionContractAddress common.Address txOpts *bind.TransactOpts - client arbutil.L1Interface + client Client privKey *ecdsa.PrivateKey auctionContract *express_lane_auctiongen.ExpressLaneAuction auctioneer auctioneerConnection @@ -44,7 +49,7 @@ func NewBidderClient( ctx context.Context, name string, wallet *Wallet, - client arbutil.L1Interface, + client Client, auctionContractAddress common.Address, auctioneer auctioneerConnection, ) (*BidderClient, error) { @@ -118,7 +123,7 @@ func (bd *BidderClient) Bid( return nil, err } newBid.signature = sig - if err = bd.auctioneer.SubmitBid(ctx, newBid); err != nil { + if err = bd.auctioneer.ReceiveBid(ctx, newBid); err != nil { return nil, err } return newBid, nil diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index de687489ab..8d366cf92b 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -1,7 +1,11 @@ package timeboost import ( + "context" + "math/big" "testing" + + "github.com/stretchr/testify/require" ) func TestWinningBidderBecomesExpressLaneController(t *testing.T) { @@ -41,28 +45,27 @@ func TestWinningBidderBecomesExpressLaneController(t *testing.T) { // require.Equal(t, alice.txOpts.From, controller) } -func TestSubmitBid_OK(t *testing.T) { - // ctx, cancel := context.WithCancel(context.Background()) - // defer cancel() +func TestReceiveBid_OK(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - // testSetup := setupAuctionTest(t, ctx) + testSetup := setupAuctionTest(t, ctx) - // // Make a deposit as a bidder into the contract. - // bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) - // require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) + // Set up a new auction master instance that can validate bids. + am, err := NewAuctioneer( + testSetup.accounts[1].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, + ) + require.NoError(t, err) - // // Set up a new auction master instance that can validate bids. - // am, err := NewAuctioneer( - // testSetup.accounts[1].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, - // ) - // require.NoError(t, err) - // bc.auctioneer = am + // Make a deposit as a bidder into the contract. + bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) + require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) - // // Form a new bid with an amount. - // newBid, err := bc.Bid(ctx, big.NewInt(5)) - // require.NoError(t, err) + // Form a new bid with an amount. + newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) + require.NoError(t, err) - // // Check the bid passes validation. - // _, err = am.newValidatedBid(newBid) - // require.NoError(t, err) + // Check the bid passes validation. + _, err = am.newValidatedBid(newBid) + require.NoError(t, err) } diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index da743ccc10..6fd705adfa 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -26,7 +26,7 @@ type auctionSetup struct { initialTimestamp time.Time roundDuration time.Duration expressLaneAddr common.Address - bidReceiverAddr common.Address + beneficiaryAddr common.Address accounts []*testAccount backend *simulated.Backend } @@ -34,9 +34,9 @@ type auctionSetup struct { func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { accs, backend := setupAccounts(10) - // Advance the chain in the background + // Advance the chain in the background at Arbitrum One's block time of 250ms. go func() { - tick := time.NewTicker(time.Second) + tick := time.NewTicker(time.Millisecond * 250) defer tick.Stop() for { select { @@ -72,36 +72,59 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { require.NoError(t, err) t.Log("Account seeded with ERC20 token balance =", bal.String()) + // Deploy the express lane auction contract. + auctionContractAddr, tx, auctionContract, err := express_lane_auctiongen.DeployExpressLaneAuction( + opts, backend.Client(), + ) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { + t.Fatal(err) + } + expressLaneAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") - bidReceiverAddr := common.HexToAddress("0x3424242424242424242424242424242424242424") - bidRoundSeconds := uint64(60) // Calculate the number of seconds until the next minute // and the next timestamp that is a multiple of a minute. now := time.Now() - initialTimestamp := big.NewInt(now.Unix()) + roundDuration := time.Minute + waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + initialTime := now.Add(waitTime) + initialTimestamp := big.NewInt(initialTime.Unix()) + t.Logf("Initial timestamp: %v", initialTime) // Deploy the auction manager contract. - // currReservePrice := big.NewInt(1) - // minReservePrice := big.NewInt(1) + auctioneer := opts.From + beneficiary := opts.From + biddingToken := erc20Addr + bidRoundSeconds := uint64(60) + auctionClosingSeconds := uint64(15) + reserveSubmissionSeconds := uint64(15) + minReservePrice := big.NewInt(1) // 1 wei. + roleAdmin := opts.From + minReservePriceSetter := opts.From reservePriceSetter := opts.From - auctionContractAddr, tx, auctionContract, err := express_lane_auctiongen.DeployExpressLaneAuction( - opts, backend.Client(), + beneficiarySetter := opts.From + tx, err = auctionContract.Initialize( + opts, + auctioneer, + beneficiary, + biddingToken, + express_lane_auctiongen.RoundTimingInfo{ + OffsetTimestamp: initialTimestamp.Uint64(), + RoundDurationSeconds: bidRoundSeconds, + AuctionClosingSeconds: auctionClosingSeconds, + ReserveSubmissionSeconds: reserveSubmissionSeconds, + }, + minReservePrice, + roleAdmin, + minReservePriceSetter, + reservePriceSetter, + beneficiarySetter, ) require.NoError(t, err) if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { t.Fatal(err) } - tx, err = auctionContract.Initialize(opts, opts.From, bidReceiverAddr, erc20Addr, express_lane_auctiongen.RoundTimingInfo{ - OffsetTimestamp: initialTimestamp.Uint64(), - RoundDurationSeconds: bidRoundSeconds, - }, big.NewInt(0), opts.From, reservePriceSetter, reservePriceSetter, reservePriceSetter) - require.NoError(t, err) - if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { - t.Fatal(err) - } - - // TODO: Set the reserve price here. return &auctionSetup{ chainId: chainId, expressLaneAuctionAddr: auctionContractAddr, @@ -111,27 +134,26 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { initialTimestamp: now, roundDuration: time.Minute, expressLaneAddr: expressLaneAddr, - bidReceiverAddr: bidReceiverAddr, + beneficiaryAddr: beneficiary, accounts: accs, backend: backend, } } func setupBidderClient( - t *testing.T, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, + t *testing.T, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, conn auctioneerConnection, ) *BidderClient { bc, err := NewBidderClient( ctx, name, &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, - // testSetup.backend.Client(), - nil, + testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, - nil, + conn, ) require.NoError(t, err) - // Approve spending by the auction manager and bid receiver. + // Approve spending by the express lane auction contract and beneficiary. maxUint256 := big.NewInt(1) maxUint256.Lsh(maxUint256, 256).Sub(maxUint256, big.NewInt(1)) tx, err := testSetup.erc20Contract.Approve( @@ -142,7 +164,7 @@ func setupBidderClient( t.Fatal(err) } tx, err = testSetup.erc20Contract.Approve( - account.txOpts, testSetup.bidReceiverAddr, maxUint256, + account.txOpts, testSetup.beneficiaryAddr, maxUint256, ) require.NoError(t, err) if _, err = bind.WaitMined(ctx, testSetup.backend.Client(), tx); err != nil { From d7a91a858fbacdf17d2ae77e9e5bb80679c00d33 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 24 Jul 2024 09:53:10 -0500 Subject: [PATCH 011/109] edits and fix sig --- contracts | 2 +- timeboost/auctioneer.go | 4 +-- timeboost/auctioneer_test.go | 39 ----------------------- timeboost/bidder_client.go | 2 +- timeboost/bids.go | 17 +++++----- timeboost/bids_test.go | 62 ++++++++++++++++++++++-------------- timeboost/setup_test.go | 14 ++++++-- 7 files changed, 62 insertions(+), 78 deletions(-) delete mode 100644 timeboost/auctioneer_test.go diff --git a/contracts b/contracts index 46f20c3a34..8f434d48cc 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 46f20c3a34eaa841972c2b2597edced9d11e23b2 +Subproject commit 8f434d48ccb5e8ba03f7b9108467702c56348b0a diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 6692a0480a..3d7172052a 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -100,14 +100,14 @@ func (am *Auctioneer) Start(ctx context.Context) { return case auctionClosingTime := <-ticker.c: log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", am.bidCache.size()) - if err := am.resolveAuctions(ctx); err != nil { + if err := am.resolveAuction(ctx); err != nil { log.Error("Could not resolve auction for round", "error", err) } } } } -func (am *Auctioneer) resolveAuctions(ctx context.Context) error { +func (am *Auctioneer) resolveAuction(ctx context.Context) error { upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 // If we have no winner, then we can cancel the auction. // Auctioneer can also subscribe to sequencer feed and diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go deleted file mode 100644 index 6c62f7ff42..0000000000 --- a/timeboost/auctioneer_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package timeboost - -import ( - "testing" -) - -type mockSequencer struct{} - -// TODO: Mock sequencer subscribes to auction resolution events to -// figure out who is the upcoming express lane auction controller and allows -// sequencing of txs from that controller in their given round. - -// Runs a simulation of an express lane auction between different parties, -// with some rounds randomly being canceled due to sequencer downtime. -func TestCompleteAuctionSimulation(t *testing.T) { - // ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10) - // defer cancel() - - // testSetup := setupAuctionTest(t, ctx) - - // // Set up two different bidders. - // alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) - // bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup) - // require.NoError(t, alice.deposit(ctx, big.NewInt(5))) - // require.NoError(t, bob.deposit(ctx, big.NewInt(5))) - - // // Set up a new auction master instance that can validate bids. - // am, err := newAuctionMaster( - // testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, - // ) - // require.NoError(t, err) - // alice.auctioneer = am - // bob.auctioneer = am - - // TODO: Start auction master and randomly bid from different bidders in a round. - // Start the sequencer. - // Have the winner of the express lane send txs if they detect they are the winner. - // Auction master will log any deposits that are made to the contract. -} diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 1d1c8e294f..a8ab8db402 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -111,7 +111,7 @@ func (bd *BidderClient) Bid( packedBidBytes, err := encodeBidValues( new(big.Int).SetUint64(newBid.chainId), bd.auctionContractAddress, - new(big.Int).SetUint64(newBid.round), + newBid.round, amount, expressLaneController, ) diff --git a/timeboost/bids.go b/timeboost/bids.go index c16cf2e042..2deb90bb9b 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -3,6 +3,7 @@ package timeboost import ( "bytes" "crypto/ecdsa" + "encoding/binary" "math/big" "sync" @@ -74,7 +75,7 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { packedBidBytes, err := encodeBidValues( new(big.Int).SetUint64(bid.chainId), am.auctionContractAddr, - new(big.Int).SetUint64(bid.round), + bid.round, bid.amount, bid.expressLaneController, ) @@ -86,11 +87,10 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") } // Recover the public key. - hash := crypto.Keccak256(packedBidBytes) - prefixed := crypto.Keccak256([]byte("\x19Ethereum Signed Message:\n32"), hash) + prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), packedBidBytes...)) pubkey, err := crypto.SigToPub(prefixed, bid.signature) if err != nil { - return nil, err + return nil, ErrMalformedData } if !verifySignature(pubkey, packedBidBytes, bid.signature) { return nil, ErrWrongSignature @@ -164,8 +164,7 @@ func (bc *bidCache) topTwoBids() *auctionResult { } func verifySignature(pubkey *ecdsa.PublicKey, message []byte, sig []byte) bool { - hash := crypto.Keccak256(message) - prefixed := crypto.Keccak256([]byte("\x19Ethereum Signed Message:\n32"), hash) + prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), message...)) return secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sig[:len(sig)-1]) } @@ -178,13 +177,15 @@ func padBigInt(bi *big.Int) []byte { return padded } -func encodeBidValues(chainId *big.Int, auctionContractAddress common.Address, round, amount *big.Int, expressLaneController common.Address) ([]byte, error) { +func encodeBidValues(chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { buf := new(bytes.Buffer) // Encode uint256 values - each occupies 32 bytes buf.Write(padBigInt(chainId)) buf.Write(auctionContractAddress[:]) - buf.Write(padBigInt(round)) + roundBuf := make([]byte, 8) + binary.BigEndian.PutUint64(roundBuf, round) + buf.Write(roundBuf) buf.Write(padBigInt(amount)) buf.Write(expressLaneController[:]) diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 8d366cf92b..392f51b3b7 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -5,39 +5,53 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" ) -func TestWinningBidderBecomesExpressLaneController(t *testing.T) { - // ctx, cancel := context.WithCancel(context.Background()) - // defer cancel() +func TestResolveAuction(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - // testSetup := setupAuctionTest(t, ctx) + testSetup := setupAuctionTest(t, ctx) - // // Set up two different bidders. - // alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup) - // bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup) - // require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) - // require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) + // Set up a new auction master instance that can validate bids. + am, err := NewAuctioneer( + testSetup.accounts[0].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, + ) + require.NoError(t, err) - // // Set up a new auction master instance that can validate bids. - // am, err := NewAuctioneer( - // testSetup.accounts[2].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.auctionContract, - // ) - // require.NoError(t, err) - // alice.auctioneer = am - // bob.auctioneer = am + // Set up two different bidders. + alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) + bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, am) + require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) + require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) - // // Form two new bids for the round, with Alice being the bigger one. - // aliceBid, err := alice.Bid(ctx, big.NewInt(2)) - // require.NoError(t, err) - // bobBid, err := bob.Bid(ctx, big.NewInt(1)) - // require.NoError(t, err) - // _, _ = aliceBid, bobBid + // Form two new bids for the round, with Alice being the bigger one. + aliceBid, err := alice.Bid(ctx, big.NewInt(2), testSetup.accounts[0].txOpts.From) + require.NoError(t, err) + _, err = bob.Bid(ctx, big.NewInt(1), testSetup.accounts[1].txOpts.From) + require.NoError(t, err) + + // Check the encoded bid bytes are as expected. + bidBytes, err := alice.auctionContract.GetBidBytes(&bind.CallOpts{}, aliceBid.round, aliceBid.amount, aliceBid.expressLaneController) + require.NoError(t, err) + encoded, err := encodeBidValues(new(big.Int).SetUint64(alice.chainId), alice.auctionContractAddress, aliceBid.round, aliceBid.amount, aliceBid.expressLaneController) + require.NoError(t, err) + require.Equal(t, bidBytes, encoded) + + // Attempt to resolve the auction before it is closed and receive an error. + require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") - // // Resolve the auction. - // require.NoError(t, am.resolveAuctions(ctx)) + // // Await resolution. + // t.Log(time.Now()) + // ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) + // go ticker.start() + // <-ticker.c + // t.Log(time.Now()) + // require.NoError(t, am.resolveAuction(ctx)) + t.Fatal(1) // // Expect Alice to have become the next express lane controller. // upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 // controller, err := testSetup.auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(upcomingRound))) diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 6fd705adfa..959e16a919 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient/simulated" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/stretchr/testify/require" ) @@ -73,13 +74,20 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { t.Log("Account seeded with ERC20 token balance =", bal.String()) // Deploy the express lane auction contract. - auctionContractAddr, tx, auctionContract, err := express_lane_auctiongen.DeployExpressLaneAuction( + auctionContractAddr, tx, _, err := express_lane_auctiongen.DeployExpressLaneAuction( opts, backend.Client(), ) require.NoError(t, err) if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { t.Fatal(err) } + proxyAddr, tx, _, err := mocksgen.DeploySimpleProxy(opts, backend.Client(), auctionContractAddr) + require.NoError(t, err) + if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { + t.Fatal(err) + } + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(proxyAddr, backend.Client()) + require.NoError(t, err) expressLaneAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") @@ -90,7 +98,7 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) initialTime := now.Add(waitTime) initialTimestamp := big.NewInt(initialTime.Unix()) - t.Logf("Initial timestamp: %v", initialTime) + t.Logf("Initial timestamp for express lane auctions: %v", initialTime) // Deploy the auction manager contract. auctioneer := opts.From @@ -127,7 +135,7 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { } return &auctionSetup{ chainId: chainId, - expressLaneAuctionAddr: auctionContractAddr, + expressLaneAuctionAddr: proxyAddr, expressLaneAuction: auctionContract, erc20Addr: erc20Addr, erc20Contract: erc20, From c5fad5ce8fa111d975582bcdad11280707acaf5f Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 24 Jul 2024 12:54:23 -0500 Subject: [PATCH 012/109] passing test --- timeboost/auctioneer.go | 1 + timeboost/bidder_client.go | 4 +-- timeboost/bids.go | 11 +++++--- timeboost/bids_test.go | 53 ++++++++++++++++++++++---------------- timeboost/setup_test.go | 2 +- 5 files changed, 42 insertions(+), 29 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 3d7172052a..e7e83c35be 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -124,6 +124,7 @@ func (am *Auctioneer) resolveAuction(ctx context.Context) error { // TODO: Retry a given number of times in case of flakey connection. switch { case hasBothBids: + fmt.Printf("First express lane controller: %#x\n", first.expressLaneController) tx, err = am.auctionContract.ResolveMultiBidAuction( am.txOpts, express_lane_auctiongen.Bid{ diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index a8ab8db402..30aa86d7a9 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -130,11 +130,11 @@ func (bd *BidderClient) Bid( } func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { - hash := crypto.Keccak256(message) - prefixed := crypto.Keccak256([]byte("\x19Ethereum Signed Message:\n32"), hash) + prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), message...)) sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) if err != nil { return nil, err } + sig[64] += 27 return sig, nil } diff --git a/timeboost/bids.go b/timeboost/bids.go index 2deb90bb9b..de220c3b70 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -71,7 +71,6 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { return nil, errors.Wrap(ErrMalformedData, "expected bid to be at least of reserve price magnitude") } // Validate the signature. - // TODO: Validate the signature against the express lane controller address. packedBidBytes, err := encodeBidValues( new(big.Int).SetUint64(bid.chainId), am.auctionContractAddr, @@ -82,17 +81,21 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { if err != nil { return nil, ErrMalformedData } - // Ethereum signatures contain the recovery id at the last byte if len(bid.signature) != 65 { return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") } // Recover the public key. prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), packedBidBytes...)) - pubkey, err := crypto.SigToPub(prefixed, bid.signature) + sigItem := make([]byte, len(bid.signature)) + copy(sigItem, bid.signature) + if sigItem[len(sigItem)-1] >= 27 { + sigItem[len(sigItem)-1] -= 27 + } + pubkey, err := crypto.SigToPub(prefixed, sigItem) if err != nil { return nil, ErrMalformedData } - if !verifySignature(pubkey, packedBidBytes, bid.signature) { + if !verifySignature(pubkey, packedBidBytes, sigItem) { return nil, ErrWrongSignature } // Validate if the user if a depositor in the contract and has enough balance for the bid. diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 392f51b3b7..0bcccbf90c 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -4,6 +4,7 @@ import ( "context" "math/big" "testing" + "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" @@ -27,36 +28,44 @@ func TestResolveAuction(t *testing.T) { require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) - // Form two new bids for the round, with Alice being the bigger one. - aliceBid, err := alice.Bid(ctx, big.NewInt(2), testSetup.accounts[0].txOpts.From) - require.NoError(t, err) - _, err = bob.Bid(ctx, big.NewInt(1), testSetup.accounts[1].txOpts.From) + // Wait until the initial round. + info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) require.NoError(t, err) + timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) + <-time.After(timeToWait) + time.Sleep(time.Second) // Add a second of wait so that we are within a round. - // Check the encoded bid bytes are as expected. - bidBytes, err := alice.auctionContract.GetBidBytes(&bind.CallOpts{}, aliceBid.round, aliceBid.amount, aliceBid.expressLaneController) + // Form two new bids for the round, with Alice being the bigger one. + _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) require.NoError(t, err) - encoded, err := encodeBidValues(new(big.Int).SetUint64(alice.chainId), alice.auctionContractAddress, aliceBid.round, aliceBid.amount, aliceBid.expressLaneController) + _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) require.NoError(t, err) - require.Equal(t, bidBytes, encoded) // Attempt to resolve the auction before it is closed and receive an error. require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") - // // Await resolution. - // t.Log(time.Now()) - // ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) - // go ticker.start() - // <-ticker.c - // t.Log(time.Now()) - - // require.NoError(t, am.resolveAuction(ctx)) - t.Fatal(1) - // // Expect Alice to have become the next express lane controller. - // upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 - // controller, err := testSetup.auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(upcomingRound))) - // require.NoError(t, err) - // require.Equal(t, alice.txOpts.From, controller) + // Await resolution. + t.Log(time.Now()) + ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) + go ticker.start() + <-ticker.c + require.NoError(t, am.resolveAuction(ctx)) + // Expect Alice to have become the next express lane controller. + + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: 0, + End: nil, + } + it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) + require.NoError(t, err) + aliceWon := false + for it.Next() { + if it.Event.FirstPriceBidder == alice.txOpts.From { + aliceWon = true + } + } + require.True(t, aliceWon) } func TestReceiveBid_OK(t *testing.T) { diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 959e16a919..e72fa33022 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -37,7 +37,7 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { // Advance the chain in the background at Arbitrum One's block time of 250ms. go func() { - tick := time.NewTicker(time.Millisecond * 250) + tick := time.NewTicker(time.Second) defer tick.Stop() for { select { From f54ac508921d5c389dced29a8992473e61eb4096 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 24 Jul 2024 13:10:07 -0500 Subject: [PATCH 013/109] add to seq test --- system_tests/common_test.go | 101 +++++++++++++++++++++++++++++++++++ system_tests/seqfeed_test.go | 4 +- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index ff184340ab..5da8c8e8bd 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -29,6 +29,7 @@ import ( "github.com/offchainlabs/nitro/das" "github.com/offchainlabs/nitro/deploy" "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/redisutil" @@ -70,6 +71,7 @@ import ( "github.com/offchainlabs/nitro/arbutil" _ "github.com/offchainlabs/nitro/execution/nodeInterface" "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" @@ -281,6 +283,105 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { sequencerTxOpts := b.L1Info.GetDefaultTransactOpts("Sequencer", b.ctx) sequencerTxOptsPtr = &sequencerTxOpts dataSigner = signature.DataSignerFromPrivateKey(b.L1Info.GetInfoWithPrivKey("Sequencer").PrivateKey) + + // Deploy the express lane auction contract and erc20 to the parent chain. + // TODO: This should be deployed to L2 instead. + // TODO: Move this somewhere better. + // Deploy the token as a mock erc20. + erc20Addr, tx, erc20, err := bindings.DeployMockERC20(&sequencerTxOpts, b.L1.Client) + Require(t, err) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Initialize(&sequencerTxOpts, "LANE", "LNE", 18) + Require(t, err) + if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { + t.Fatal(err) + } + + // Fund the auction contract. + b.L1Info.GenerateAccount("AuctionContract") + TransferBalance(t, "Faucet", "AuctionContract", arbmath.BigMulByUint(oneEth, 500), b.L1Info, b.L1.Client, ctx) + + // Mint some tokens to Alice and Bob. + b.L1Info.GenerateAccount("Alice") + b.L1Info.GenerateAccount("Bob") + TransferBalance(t, "Faucet", "Alice", arbmath.BigMulByUint(oneEth, 500), b.L1Info, b.L1.Client, ctx) + TransferBalance(t, "Faucet", "Bob", arbmath.BigMulByUint(oneEth, 500), b.L1Info, b.L1.Client, ctx) + aliceOpts := b.L1Info.GetDefaultTransactOpts("Alice", ctx) + bobOpts := b.L1Info.GetDefaultTransactOpts("Bob", ctx) + tx, err = erc20.Mint(&sequencerTxOpts, aliceOpts.From, big.NewInt(100)) + Require(t, err) + if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Mint(&sequencerTxOpts, bobOpts.From, big.NewInt(100)) + Require(t, err) + if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { + t.Fatal(err) + } + + // Calculate the number of seconds until the next minute + // and the next timestamp that is a multiple of a minute. + now := time.Now() + roundDuration := time.Minute + // Correctly calculate the remaining time until the next minute + waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond())*time.Nanosecond + // Get the current Unix timestamp at the start of the minute + initialTimestamp := big.NewInt(now.Add(waitTime).Unix()) + + // Deploy the auction manager contract. + auctionContractAddr, tx, _, err := express_lane_auctiongen.DeployExpressLaneAuction(&sequencerTxOpts, b.L1.Client) + Require(t, err) + if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { + t.Fatal(err) + } + + proxyAddr, tx, _, err := mocksgen.DeploySimpleProxy(&sequencerTxOpts, b.L1.Client, auctionContractAddr) + Require(t, err) + if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { + t.Fatal(err) + } + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(proxyAddr, b.L1.Client) + Require(t, err) + + auctioneer := sequencerTxOpts.From + beneficiary := sequencerTxOpts.From + biddingToken := erc20Addr + bidRoundSeconds := uint64(60) + auctionClosingSeconds := uint64(15) + reserveSubmissionSeconds := uint64(15) + minReservePrice := big.NewInt(1) // 1 wei. + roleAdmin := sequencerTxOpts.From + minReservePriceSetter := sequencerTxOpts.From + reservePriceSetter := sequencerTxOpts.From + beneficiarySetter := sequencerTxOpts.From + tx, err = auctionContract.Initialize( + &sequencerTxOpts, + auctioneer, + beneficiary, + biddingToken, + express_lane_auctiongen.RoundTimingInfo{ + OffsetTimestamp: initialTimestamp.Uint64(), + RoundDurationSeconds: bidRoundSeconds, + AuctionClosingSeconds: auctionClosingSeconds, + ReserveSubmissionSeconds: reserveSubmissionSeconds, + }, + minReservePrice, + roleAdmin, + minReservePriceSetter, + reservePriceSetter, + beneficiarySetter, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { + t.Fatal(err) + } + t.Log("Deployed all the auction manager stuff", auctionContractAddr) + b.execConfig.Sequencer.Timeboost.AuctionContractAddress = auctionContractAddr.Hex() + b.execConfig.Sequencer.Timeboost.ERC20Address = erc20Addr.Hex() } else { b.nodeConfig.BatchPoster.Enable = false b.nodeConfig.Sequencer = false diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 36a84bbe21..14737f37cb 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -194,7 +194,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { }, l1client, auctionAddr, - nil, + auctioneer, ) Require(t, err) @@ -208,7 +208,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { }, l1client, auctionAddr, - nil, + auctioneer, ) Require(t, err) From 4b7a910c7a2b1d326850fea90a5e584f18e5f9c4 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 24 Jul 2024 14:27:02 -0500 Subject: [PATCH 014/109] system test --- execution/gethexec/express_lane_service.go | 39 +++++++--------- execution/gethexec/sequencer.go | 1 + system_tests/common_test.go | 14 +++--- system_tests/seqfeed_test.go | 43 ++++++++++++----- timeboost/bidder_client.go | 20 ++++---- timeboost/bids.go | 54 +++++++++++----------- 6 files changed, 93 insertions(+), 78 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 6e0a1afffb..4eb1a6f892 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost" @@ -35,7 +34,7 @@ type expressLaneService struct { sync.RWMutex client arbutil.L1Interface control expressLaneControl - reservedAddress common.Address + expressLaneAddr common.Address auctionContract *express_lane_auctiongen.ExpressLaneAuction initialTimestamp time.Time roundDuration time.Duration @@ -45,34 +44,29 @@ type expressLaneService struct { func newExpressLaneService( client arbutil.L1Interface, auctionContractAddr common.Address, + chainConfig *params.ChainConfig, ) (*expressLaneService, error) { auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) if err != nil { return nil, err } - // initialRoundTimestamp, err := auctionContract.InitialRoundTimestamp(&bind.CallOpts{}) - // if err != nil { - // return nil, err - // } - // roundDurationSeconds, err := auctionContract.RoundDurationSeconds(&bind.CallOpts{}) - // if err != nil { - // return nil, err - // } - // initialTimestamp := time.Unix(initialRoundTimestamp.Int64(), 0) - // currRound := timeboost.CurrentRound(initialTimestamp, time.Duration(roundDurationSeconds)*time.Second) - // controller, err := auctionContract.ExpressLaneControllerByRound(&bind.CallOpts{}, big.NewInt(int64(currRound))) - // if err != nil { - // return nil, err - // } + roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + if err != nil { + return nil, err + } + initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) + roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second return &expressLaneService{ auctionContract: auctionContract, client: client, - initialTimestamp: time.Now(), + chainConfig: chainConfig, + initialTimestamp: initialTimestamp, control: expressLaneControl{ controller: common.Address{}, round: 0, }, - roundDuration: time.Second, + expressLaneAddr: common.HexToAddress("0x2424242424242424242424242424242424242424"), + roundDuration: roundDuration, }, nil } @@ -163,7 +157,7 @@ func (es *expressLaneService) isExpressLaneTx(to common.Address) bool { es.RLock() defer es.RUnlock() - return to == es.reservedAddress + return to == es.expressLaneAddr } // An express lane transaction is valid if it satisfies the following conditions: @@ -201,9 +195,10 @@ func (es *expressLaneService) validateExpressLaneTx(tx *types.Transaction) error // unwrapExpressLaneTx extracts the inner "wrapped" transaction from the data field of an express lane transaction. func unwrapExpressLaneTx(tx *types.Transaction) (*types.Transaction, error) { encodedInnerTx := tx.Data() - var innerTx types.Transaction - if err := rlp.DecodeBytes(encodedInnerTx, &innerTx); err != nil { + fmt.Printf("Inner in decoding: %#x\n", encodedInnerTx) + innerTx := &types.Transaction{} + if err := innerTx.UnmarshalBinary(encodedInnerTx); err != nil { return nil, fmt.Errorf("failed to decode inner transaction: %w", err) } - return &innerTx, nil + return innerTx, nil } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index a37f38a28f..22cb7dc72a 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -392,6 +392,7 @@ func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderRead els, err := newExpressLaneService( l1Reader.Client(), addr, + s.execEngine.bc.Config(), ) if err != nil { return nil, err diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 5da8c8e8bd..f7c1b9e03c 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -347,17 +347,17 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(proxyAddr, b.L1.Client) Require(t, err) - auctioneer := sequencerTxOpts.From - beneficiary := sequencerTxOpts.From + auctioneer := b.L1Info.GetDefaultTransactOpts("AuctionContract", b.ctx).From + beneficiary := auctioneer biddingToken := erc20Addr bidRoundSeconds := uint64(60) auctionClosingSeconds := uint64(15) reserveSubmissionSeconds := uint64(15) minReservePrice := big.NewInt(1) // 1 wei. - roleAdmin := sequencerTxOpts.From - minReservePriceSetter := sequencerTxOpts.From - reservePriceSetter := sequencerTxOpts.From - beneficiarySetter := sequencerTxOpts.From + roleAdmin := auctioneer + minReservePriceSetter := auctioneer + reservePriceSetter := auctioneer + beneficiarySetter := auctioneer tx, err = auctionContract.Initialize( &sequencerTxOpts, auctioneer, @@ -380,7 +380,7 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { t.Fatal(err) } t.Log("Deployed all the auction manager stuff", auctionContractAddr) - b.execConfig.Sequencer.Timeboost.AuctionContractAddress = auctionContractAddr.Hex() + b.execConfig.Sequencer.Timeboost.AuctionContractAddress = proxyAddr.Hex() b.execConfig.Sequencer.Timeboost.ERC20Address = erc20Addr.Hex() } else { b.nodeConfig.BatchPoster.Enable = false diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 14737f37cb..6933041be4 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -126,7 +126,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { Require(t, seqClient.SendTransaction(ctx, tx)) _, err = EnsureTxSucceeded(ctx, seqClient, tx) Require(t, err) - t.Logf("%+v and %+v", seqInfo.Accounts["Alice"], seqInfo.Accounts["Bob"]) + t.Logf("Alice %+v and Bob %+v", seqInfo.Accounts["Alice"], seqInfo.Accounts["Bob"]) auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionAddr, builderSeq.L1.Client) Require(t, err) @@ -222,12 +222,12 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Log("Started auction master stack and bid clients") Require(t, alice.Deposit(ctx, big.NewInt(5))) Require(t, bob.Deposit(ctx, big.NewInt(5))) - t.Log("Alice and Bob are now deposited into the autonomous auction contract, waiting for bidding round...") // Wait until the next timeboost round + a few milliseconds. now := time.Now() roundDuration := time.Minute waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + t.Logf("Alice and Bob are now deposited into the autonomous auction contract, waiting %v for bidding round...", waitTime) time.Sleep(waitTime) time.Sleep(time.Second * 5) @@ -262,22 +262,31 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Log("Not express lane round yet, waiting for next round", waitTime) time.Sleep(waitTime) } - - // current, err := auctionContract.(&bind.CallOpts{}, new(big.Int).SetUint64(currRound)) - // Require(t, err) - - // if current != bobOpts.From { - // t.Log("Current express lane round controller is not Bob", current, aliceOpts.From) - // } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: 0, + End: nil, + } + it, err := auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) + Require(t, err) + bobWon := false + for it.Next() { + if it.Event.FirstPriceBidder == bobOpts.From { + bobWon = true + } + } + if !bobWon { + t.Fatal("Bob should have won the auction") + } t.Log("Now submitting txs to sequencer") // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's // txs end up getting delayed by 200ms as she is not the express lane controller. // In the end, Bob's txs should be ordered before Alice's during the round. - var wg sync.WaitGroup wg.Add(2) + expressLaneAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) go func(w *sync.WaitGroup) { defer w.Done() @@ -285,7 +294,17 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { Require(t, err) }(&wg) - bobTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + bobBoostableTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + bobBoostableTxData, err := bobBoostableTx.MarshalBinary() + Require(t, err) + t.Logf("Typed transaction inner is %#x", bobBoostableTxData) + txData := &types.DynamicFeeTx{ + To: &expressLaneAddr, + GasTipCap: new(big.Int).SetUint64(bobBid.Round), + Nonce: 0, + Data: bobBoostableTxData, + } + bobTx := seqInfo.SignTxAs("Bob", txData) go func(w *sync.WaitGroup) { defer w.Done() time.Sleep(time.Millisecond * 10) @@ -298,7 +317,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) Require(t, err) aliceBlock := aliceReceipt.BlockNumber.Uint64() - bobReceipt, err := seqClient.TransactionReceipt(ctx, bobTx.Hash()) + bobReceipt, err := seqClient.TransactionReceipt(ctx, bobBoostableTx.Hash()) Require(t, err) bobBlock := bobReceipt.BlockNumber.Uint64() diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 30aa86d7a9..324cac75fb 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -100,18 +100,18 @@ func (bd *BidderClient) Bid( ctx context.Context, amount *big.Int, expressLaneController common.Address, ) (*Bid, error) { newBid := &Bid{ - chainId: bd.chainId, - expressLaneController: expressLaneController, - auctionContractAddress: bd.auctionContractAddress, - bidder: bd.txOpts.From, - round: CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1, - amount: amount, - signature: nil, + ChainId: bd.chainId, + ExpressLaneController: expressLaneController, + AuctionContractAddress: bd.auctionContractAddress, + Bidder: bd.txOpts.From, + Round: CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1, + Amount: amount, + Signature: nil, } packedBidBytes, err := encodeBidValues( - new(big.Int).SetUint64(newBid.chainId), + new(big.Int).SetUint64(newBid.ChainId), bd.auctionContractAddress, - newBid.round, + newBid.Round, amount, expressLaneController, ) @@ -122,7 +122,7 @@ func (bd *BidderClient) Bid( if err != nil { return nil, err } - newBid.signature = sig + newBid.Signature = sig if err = bd.auctioneer.ReceiveBid(ctx, newBid); err != nil { return nil, err } diff --git a/timeboost/bids.go b/timeboost/bids.go index de220c3b70..1363593937 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -24,13 +24,13 @@ var ( ) type Bid struct { - chainId uint64 - expressLaneController common.Address - bidder common.Address - auctionContractAddress common.Address - round uint64 - amount *big.Int - signature []byte + ChainId uint64 + ExpressLaneController common.Address + Bidder common.Address + AuctionContractAddress common.Address + Round uint64 + Amount *big.Int + Signature []byte } type validatedBid struct { @@ -50,44 +50,44 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") } - if bid.bidder == (common.Address{}) { + if bid.Bidder == (common.Address{}) { return nil, errors.Wrap(ErrMalformedData, "empty bidder address") } - if bid.expressLaneController == (common.Address{}) { + if bid.ExpressLaneController == (common.Address{}) { return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") } // Verify chain id. - if new(big.Int).SetUint64(bid.chainId).Cmp(am.chainId) != 0 { - return nil, errors.Wrapf(ErrWrongChainId, "wanted %#x, got %#x", am.chainId, bid.chainId) + if new(big.Int).SetUint64(bid.ChainId).Cmp(am.chainId) != 0 { + return nil, errors.Wrapf(ErrWrongChainId, "wanted %#x, got %#x", am.chainId, bid.ChainId) } // Check if for upcoming round. upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 - if bid.round != upcomingRound { - return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.round) + if bid.Round != upcomingRound { + return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.Round) } // Check bid amount. reservePrice := am.fetchReservePrice() - if bid.amount.Cmp(reservePrice) == -1 { + if bid.Amount.Cmp(reservePrice) == -1 { return nil, errors.Wrap(ErrMalformedData, "expected bid to be at least of reserve price magnitude") } // Validate the signature. packedBidBytes, err := encodeBidValues( - new(big.Int).SetUint64(bid.chainId), + new(big.Int).SetUint64(bid.ChainId), am.auctionContractAddr, - bid.round, - bid.amount, - bid.expressLaneController, + bid.Round, + bid.Amount, + bid.ExpressLaneController, ) if err != nil { return nil, ErrMalformedData } - if len(bid.signature) != 65 { + if len(bid.Signature) != 65 { return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") } // Recover the public key. prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), packedBidBytes...)) - sigItem := make([]byte, len(bid.signature)) - copy(sigItem, bid.signature) + sigItem := make([]byte, len(bid.Signature)) + copy(sigItem, bid.Signature) if sigItem[len(sigItem)-1] >= 27 { sigItem[len(sigItem)-1] -= 27 } @@ -103,20 +103,20 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { // TODO: Validate reserve price against amount of bid. // TODO: No need to do anything expensive if the bid coming is in invalid. // Cache this if the received time of the bid is too soon. Include the arrival timestamp. - depositBal, err := am.auctionContract.BalanceOf(&bind.CallOpts{}, bid.bidder) + depositBal, err := am.auctionContract.BalanceOf(&bind.CallOpts{}, bid.Bidder) if err != nil { return nil, err } if depositBal.Cmp(new(big.Int)) == 0 { return nil, ErrNotDepositor } - if depositBal.Cmp(bid.amount) < 0 { - return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.amount) + if depositBal.Cmp(bid.Amount) < 0 { + return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.Amount) } return &validatedBid{ - expressLaneController: bid.expressLaneController, - amount: bid.amount, - signature: bid.signature, + expressLaneController: bid.ExpressLaneController, + amount: bid.Amount, + signature: bid.Signature, }, nil } From 9681909417771f7144b0a51c895be0bf41e5de23 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 24 Jul 2024 10:17:34 -0700 Subject: [PATCH 015/109] Updated auctioneer with research spec --- system_tests/seqfeed_test.go | 2 +- timeboost/auctioneer.go | 65 +++++++++++++++++++++++------------- timeboost/bidder_client.go | 9 +++++ timeboost/bids.go | 52 ++++++++++++++++++++--------- timeboost/bids_test.go | 4 +-- 5 files changed, 90 insertions(+), 42 deletions(-) diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 6933041be4..8d225366ec 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -178,7 +178,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { auctionContractOpts := builderSeq.L1Info.GetDefaultTransactOpts("AuctionContract", ctx) chainId, err := l1client.ChainID(ctx) Require(t, err) - auctioneer, err := timeboost.NewAuctioneer(&auctionContractOpts, chainId, builderSeq.L1.Client, auctionAddr, auctionContract) + auctioneer, err := timeboost.NewAuctioneer(&auctionContractOpts, []uint64{chainId.Uint64()}, builderSeq.L1.Client, auctionAddr, auctionContract) Require(t, err) go auctioneer.Start(ctx) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index e7e83c35be..dcaa7cebdd 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -13,13 +13,15 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" + "golang.org/x/crypto/sha3" ) type AuctioneerOpt func(*Auctioneer) type Auctioneer struct { txOpts *bind.TransactOpts - chainId *big.Int + chainId []uint64 // Auctioneer could handle auctions on multiple chains. + domainValue []byte client Client auctionContract *express_lane_auctiongen.ExpressLaneAuction bidsReceiver chan *Bid @@ -31,11 +33,13 @@ type Auctioneer struct { auctionContractAddr common.Address reservePriceLock sync.RWMutex reservePrice *big.Int + minReservePriceLock sync.RWMutex + minReservePrice *big.Int // TODO(Terence): Do we need to keep min reserve price? assuming contract will automatically update reserve price. } func NewAuctioneer( txOpts *bind.TransactOpts, - chainId *big.Int, + chainId []uint64, client Client, auctionContractAddr common.Address, auctionContract *express_lane_auctiongen.ExpressLaneAuction, @@ -54,6 +58,15 @@ func NewAuctioneer( if err != nil { return nil, err } + reservePrice, err := auctionContract.ReservePrice(&bind.CallOpts{}) + if err != nil { + return nil, err + } + + hash := sha3.NewLegacyKeccak256() + hash.Write([]byte("TIMEBOOST_BID")) + domainValue := hash.Sum(nil) + am := &Auctioneer{ txOpts: txOpts, chainId: chainId, @@ -64,9 +77,11 @@ func NewAuctioneer( initialRoundTimestamp: initialTimestamp, auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, - reservePrice: minReservePrice, auctionClosingDuration: auctionClosingDuration, reserveSubmissionDuration: reserveSubmissionDuration, + reservePrice: reservePrice, + minReservePrice: minReservePrice, + domainValue: domainValue, } for _, o := range opts { o(am) @@ -74,24 +89,24 @@ func NewAuctioneer( return am, nil } -func (am *Auctioneer) ReceiveBid(ctx context.Context, b *Bid) error { - validated, err := am.newValidatedBid(b) +func (a *Auctioneer) ReceiveBid(ctx context.Context, b *Bid) error { + validated, err := a.newValidatedBid(b) if err != nil { return fmt.Errorf("could not validate bid: %v", err) } - am.bidCache.add(validated) + a.bidCache.add(validated) return nil } -func (am *Auctioneer) Start(ctx context.Context) { +func (a *Auctioneer) Start(ctx context.Context) { // Receive bids in the background. - go receiveAsync(ctx, am.bidsReceiver, am.ReceiveBid) + go receiveAsync(ctx, a.bidsReceiver, a.ReceiveBid) // Listen for sequencer health in the background and close upcoming auctions if so. - go am.checkSequencerHealth(ctx) + go a.checkSequencerHealth(ctx) // Work on closing auctions. - ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) + ticker := newAuctionCloseTicker(a.roundDuration, a.auctionClosingDuration) go ticker.start() for { select { @@ -99,20 +114,20 @@ func (am *Auctioneer) Start(ctx context.Context) { log.Error("Context closed, autonomous auctioneer shutting down") return case auctionClosingTime := <-ticker.c: - log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", am.bidCache.size()) - if err := am.resolveAuction(ctx); err != nil { + log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", a.bidCache.size()) + if err := a.resolveAuction(ctx); err != nil { log.Error("Could not resolve auction for round", "error", err) } } } } -func (am *Auctioneer) resolveAuction(ctx context.Context) error { - upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 +func (a *Auctioneer) resolveAuction(ctx context.Context) error { + upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 // If we have no winner, then we can cancel the auction. // Auctioneer can also subscribe to sequencer feed and // close auction if sequencer is down. - result := am.bidCache.topTwoBids() + result := a.bidCache.topTwoBids() first := result.firstPlace second := result.secondPlace var tx *types.Transaction @@ -124,9 +139,8 @@ func (am *Auctioneer) resolveAuction(ctx context.Context) error { // TODO: Retry a given number of times in case of flakey connection. switch { case hasBothBids: - fmt.Printf("First express lane controller: %#x\n", first.expressLaneController) - tx, err = am.auctionContract.ResolveMultiBidAuction( - am.txOpts, + tx, err = a.auctionContract.ResolveMultiBidAuction( + a.txOpts, express_lane_auctiongen.Bid{ ExpressLaneController: first.expressLaneController, Amount: first.amount, @@ -141,8 +155,8 @@ func (am *Auctioneer) resolveAuction(ctx context.Context) error { log.Info("Resolving auctions, received two bids", "round", upcomingRound) case hasSingleBid: log.Info("Resolving auctions, received single bids", "round", upcomingRound) - tx, err = am.auctionContract.ResolveSingleBidAuction( - am.txOpts, + tx, err = a.auctionContract.ResolveSingleBidAuction( + a.txOpts, express_lane_auctiongen.Bid{ ExpressLaneController: first.expressLaneController, Amount: first.amount, @@ -157,7 +171,7 @@ func (am *Auctioneer) resolveAuction(ctx context.Context) error { if err != nil { return err } - receipt, err := bind.WaitMined(ctx, am.client, tx) + receipt, err := bind.WaitMined(ctx, a.client, tx) if err != nil { return err } @@ -165,16 +179,21 @@ func (am *Auctioneer) resolveAuction(ctx context.Context) error { return errors.New("deposit failed") } // Clear the bid cache. - am.bidCache = newBidCache() + a.bidCache = newBidCache() return nil } // TODO: Implement. If sequencer is down for some time, cancel the upcoming auction by calling // the cancel method on the smart contract. -func (am *Auctioneer) checkSequencerHealth(ctx context.Context) { +func (a *Auctioneer) checkSequencerHealth(ctx context.Context) { } func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) uint64 { return uint64(time.Since(initialRoundTimestamp) / roundDuration) } + +func AuctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { + d := time.Since(initialRoundTimestamp) % roundDuration + return d, d > auctionClosingDuration +} diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 324cac75fb..74106d4e61 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" + "golang.org/x/crypto/sha3" ) type Client interface { @@ -37,6 +38,7 @@ type BidderClient struct { auctioneer auctioneerConnection initialRoundTimestamp time.Time roundDuration time.Duration + domainValue []byte } // TODO: Provide a safer option. @@ -67,6 +69,11 @@ func NewBidderClient( } initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + + hash := sha3.NewLegacyKeccak256() + hash.Write([]byte("TIMEBOOST_BID")) + domainValue := hash.Sum(nil) + return &BidderClient{ chainId: chainId.Uint64(), name: name, @@ -78,6 +85,7 @@ func NewBidderClient( auctioneer: auctioneer, initialRoundTimestamp: initialTimestamp, roundDuration: roundDuration, + domainValue: domainValue, }, nil } @@ -109,6 +117,7 @@ func (bd *BidderClient) Bid( Signature: nil, } packedBidBytes, err := encodeBidValues( + bd.domainValue, new(big.Int).SetUint64(newBid.ChainId), bd.auctionContractAddress, newBid.Round, diff --git a/timeboost/bids.go b/timeboost/bids.go index 1363593937..1ca12dbd70 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/ecdsa" "encoding/binary" + "fmt" "math/big" "sync" @@ -21,6 +22,7 @@ var ( ErrWrongSignature = errors.New("wrong signature") ErrBadRoundNumber = errors.New("bad round number") ErrInsufficientBalance = errors.New("insufficient balance") + ErrInsufficientBid = errors.New("insufficient bid") ) type Bid struct { @@ -39,13 +41,13 @@ type validatedBid struct { signature []byte } -func (am *Auctioneer) fetchReservePrice() *big.Int { - am.reservePriceLock.RLock() - defer am.reservePriceLock.RUnlock() - return new(big.Int).Set(am.reservePrice) +func (a *Auctioneer) fetchReservePrice() *big.Int { + a.reservePriceLock.RLock() + defer a.reservePriceLock.RUnlock() + return new(big.Int).Set(a.reservePrice) } -func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { +func (a *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { // Check basic integrity. if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") @@ -56,24 +58,41 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { if bid.ExpressLaneController == (common.Address{}) { return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") } - // Verify chain id. - if new(big.Int).SetUint64(bid.ChainId).Cmp(am.chainId) != 0 { - return nil, errors.Wrapf(ErrWrongChainId, "wanted %#x, got %#x", am.chainId, bid.ChainId) + + // Check if the chain ID is valid. + chainIdOk := false + for _, id := range a.chainId { + if bid.ChainId == id { + chainIdOk = true + break + } } - // Check if for upcoming round. - upcomingRound := CurrentRound(am.initialRoundTimestamp, am.roundDuration) + 1 + if !chainIdOk { + return nil, errors.Wrapf(ErrWrongChainId, "can not aucution for chain id: %d", bid.ChainId) + } + + // Check if the bid is intended for upcoming round. + upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 if bid.Round != upcomingRound { return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.Round) } - // Check bid amount. - reservePrice := am.fetchReservePrice() + + // Check if the auction is closed. + if d, closed := AuctionClosed(a.initialRoundTimestamp, a.roundDuration, a.auctionClosingDuration); closed { + return nil, fmt.Errorf("auction is closed, %d seconds into the round", d) + } + + // Check bid is higher than reserve price. + reservePrice := a.fetchReservePrice() if bid.Amount.Cmp(reservePrice) == -1 { - return nil, errors.Wrap(ErrMalformedData, "expected bid to be at least of reserve price magnitude") + return nil, errors.Wrapf(ErrInsufficientBid, "reserve price %s, bid %s", reservePrice, bid.Amount) } + // Validate the signature. packedBidBytes, err := encodeBidValues( + a.domainValue, new(big.Int).SetUint64(bid.ChainId), - am.auctionContractAddr, + bid.AuctionContractAddress, bid.Round, bid.Amount, bid.ExpressLaneController, @@ -103,7 +122,7 @@ func (am *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { // TODO: Validate reserve price against amount of bid. // TODO: No need to do anything expensive if the bid coming is in invalid. // Cache this if the received time of the bid is too soon. Include the arrival timestamp. - depositBal, err := am.auctionContract.BalanceOf(&bind.CallOpts{}, bid.Bidder) + depositBal, err := a.auctionContract.BalanceOf(&bind.CallOpts{}, bid.Bidder) if err != nil { return nil, err } @@ -180,10 +199,11 @@ func padBigInt(bi *big.Int) []byte { return padded } -func encodeBidValues(chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { +func encodeBidValues(domainValue []byte, chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { buf := new(bytes.Buffer) // Encode uint256 values - each occupies 32 bytes + buf.Write(domainValue) buf.Write(padBigInt(chainId)) buf.Write(auctionContractAddress[:]) roundBuf := make([]byte, 8) diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 0bcccbf90c..d62db0f7cd 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -18,7 +18,7 @@ func TestResolveAuction(t *testing.T) { // Set up a new auction master instance that can validate bids. am, err := NewAuctioneer( - testSetup.accounts[0].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, + testSetup.accounts[0].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, ) require.NoError(t, err) @@ -76,7 +76,7 @@ func TestReceiveBid_OK(t *testing.T) { // Set up a new auction master instance that can validate bids. am, err := NewAuctioneer( - testSetup.accounts[1].txOpts, testSetup.chainId, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, + testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, ) require.NoError(t, err) From 2fd33271e90055eaa62ccddd104c8903bff60b99 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 24 Jul 2024 17:44:09 -0500 Subject: [PATCH 016/109] begin adding sequencer endpoint --- execution/gethexec/api.go | 14 ++++++++++++++ execution/gethexec/arb_interface.go | 5 +++++ execution/gethexec/forwarder.go | 16 ++++++++++++++++ execution/gethexec/node.go | 6 ++++++ execution/gethexec/sequencer.go | 4 ++++ execution/gethexec/tx_pre_checker.go | 4 ++++ system_tests/seqfeed_test.go | 4 +++- 7 files changed, 52 insertions(+), 1 deletion(-) diff --git a/execution/gethexec/api.go b/execution/gethexec/api.go index c19072ae77..54e89d205e 100644 --- a/execution/gethexec/api.go +++ b/execution/gethexec/api.go @@ -35,6 +35,20 @@ func (a *ArbAPI) CheckPublisherHealth(ctx context.Context) error { return a.txPublisher.CheckHealth(ctx) } +type ArbTimeboostAPI struct { + txPublisher TransactionPublisher +} + +func NewArbTimeboostAPI(publisher TransactionPublisher) *ArbTimeboostAPI { + return &ArbTimeboostAPI{publisher} +} + +func (a *ArbTimeboostAPI) SendExpressLaneTransaction(ctx context.Context, tx *types.Transaction) error { + fmt.Println("hit the endpoint") + return nil + // return a.txPublisher.PublishTransaction(ctx) +} + type ArbDebugAPI struct { blockchain *core.BlockChain blockRangeBound uint64 diff --git a/execution/gethexec/arb_interface.go b/execution/gethexec/arb_interface.go index dbf9c24015..04261d6dc6 100644 --- a/execution/gethexec/arb_interface.go +++ b/execution/gethexec/arb_interface.go @@ -12,6 +12,7 @@ import ( ) type TransactionPublisher interface { + PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error PublishTransaction(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error CheckHealth(ctx context.Context) error Initialize(context.Context) error @@ -41,6 +42,10 @@ func (a *ArbInterface) PublishTransaction(ctx context.Context, tx *types.Transac return a.txPublisher.PublishTransaction(ctx, tx, options) } +func (a *ArbInterface) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { + return a.txPublisher.PublishExpressLaneTransaction(ctx, msg) +} + // might be used before Initialize func (a *ArbInterface) BlockChain() *core.BlockChain { return a.blockchain diff --git a/execution/gethexec/forwarder.go b/execution/gethexec/forwarder.go index 984c7224e8..945cd5e944 100644 --- a/execution/gethexec/forwarder.go +++ b/execution/gethexec/forwarder.go @@ -156,6 +156,10 @@ func (f *TxForwarder) PublishTransaction(inctx context.Context, tx *types.Transa return errors.New("failed to publish transaction to any of the forwarding targets") } +func (f *TxForwarder) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { + return nil +} + const cacheUpstreamHealth = 2 * time.Second const maxHealthTimeout = 10 * time.Second @@ -252,6 +256,10 @@ func (f *TxDropper) PublishTransaction(ctx context.Context, tx *types.Transactio return txDropperErr } +func (f *TxDropper) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { + return txDropperErr +} + func (f *TxDropper) CheckHealth(ctx context.Context) error { return txDropperErr } @@ -295,6 +303,14 @@ func (f *RedisTxForwarder) PublishTransaction(ctx context.Context, tx *types.Tra return forwarder.PublishTransaction(ctx, tx, options) } +func (f *RedisTxForwarder) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { + forwarder := f.getForwarder() + if forwarder == nil { + return ErrNoSequencer + } + return forwarder.PublishExpressLaneTransaction(ctx, msg) +} + func (f *RedisTxForwarder) CheckHealth(ctx context.Context) error { forwarder := f.getForwarder() if forwarder == nil { diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 8ee16095d9..01e7f7e5ac 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -233,6 +233,12 @@ func CreateExecutionNode( Service: NewArbAPI(txPublisher), Public: false, }} + apis = append(apis, rpc.API{ + Namespace: "timeboost", + Version: "1.0", + Service: NewArbTimeboostAPI(txPublisher), + Public: false, + }) apis = append(apis, rpc.API{ Namespace: "arbdebug", Version: "1.0", diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 22cb7dc72a..4aef050217 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -566,6 +566,10 @@ func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Tran } } +func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { + return nil +} + func (s *Sequencer) preTxFilter(_ *params.ChainConfig, header *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, sender common.Address, l1Info *arbos.L1Info) error { if s.nonceCache.Caching() { stateNonce := s.nonceCache.Get(header, statedb, sender) diff --git a/execution/gethexec/tx_pre_checker.go b/execution/gethexec/tx_pre_checker.go index dacfd32e81..ba469df55c 100644 --- a/execution/gethexec/tx_pre_checker.go +++ b/execution/gethexec/tx_pre_checker.go @@ -221,3 +221,7 @@ func (c *TxPreChecker) PublishTransaction(ctx context.Context, tx *types.Transac } return c.TransactionPublisher.PublishTransaction(ctx, tx, options) } + +func (c *TxPreChecker) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { + return nil +} diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 6933041be4..e8f79fc221 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -110,7 +110,8 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { } cleanupSeq := builderSeq.Build(t) defer cleanupSeq() - seqInfo, _, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client + seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client + t.Logf("Sequencer endpoint %s", seqNode.Stack.HTTPEndpoint()) auctionAddr := builderSeq.L2.ExecNode.Sequencer.ExpressLaneAuction() erc20Addr := builderSeq.L2.ExecNode.Sequencer.ExpressLaneERC20() @@ -345,6 +346,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Fatal("Bob should have been sequenced before Alice with express lane") } } + time.Sleep(time.Hour) } func awaitAuctionResolved( From dcb552589739ee8db7129fcef608fde30c5c2ba7 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 25 Jul 2024 09:07:55 -0700 Subject: [PATCH 017/109] Clean up auctioneer part 1 --- system_tests/seqfeed_test.go | 2 +- timeboost/auctioneer.go | 189 +++++++++++++++++++++++++++-------- timeboost/bidder_client.go | 9 +- timeboost/bids.go | 107 ++------------------ timeboost/bids_test.go | 2 +- 5 files changed, 158 insertions(+), 151 deletions(-) diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 8d225366ec..38b80bf962 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -254,7 +254,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) time.Sleep(waitTime) - currRound := timeboost.CurrentRound(time.Unix(int64(info.OffsetTimestamp), 0), roundDuration) + currRound := timeboost.currentRound(time.Unix(int64(info.OffsetTimestamp), 0), roundDuration) t.Log("curr round", currRound) if currRound != winnerRound { now = time.Now() diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index dcaa7cebdd..4821caef9a 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -10,14 +10,26 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" - "golang.org/x/crypto/sha3" ) +// domainValue is the Keccak256 hash of the string "TIMEBOOST_BID". +// This variable represents a fixed domain identifier used in the express lane auction. +var domainValue = []byte{ + 0xc7, 0xf4, 0x5f, 0x6f, 0x1b, 0x1e, 0x1d, 0xfc, + 0x22, 0xe1, 0xb9, 0xf6, 0x9c, 0xda, 0x8e, 0x4e, + 0x86, 0xf4, 0x84, 0x81, 0xf0, 0xc5, 0xe0, 0x19, + 0x7c, 0x3f, 0x09, 0x1b, 0x89, 0xe8, 0xeb, 0x12, +} + type AuctioneerOpt func(*Auctioneer) +// Auctioneer is a struct that represents an autonomous auctioneer. +// It is responsible for receiving bids, validating them, and resolving auctions. +// Spec: https://github.com/OffchainLabs/timeboost-design/tree/main type Auctioneer struct { txOpts *bind.TransactOpts chainId []uint64 // Auctioneer could handle auctions on multiple chains. @@ -30,18 +42,15 @@ type Auctioneer struct { roundDuration time.Duration auctionClosingDuration time.Duration reserveSubmissionDuration time.Duration - auctionContractAddr common.Address reservePriceLock sync.RWMutex reservePrice *big.Int - minReservePriceLock sync.RWMutex - minReservePrice *big.Int // TODO(Terence): Do we need to keep min reserve price? assuming contract will automatically update reserve price. } +// NewAuctioneer creates a new autonomous auctioneer struct. func NewAuctioneer( txOpts *bind.TransactOpts, chainId []uint64, client Client, - auctionContractAddr common.Address, auctionContract *express_lane_auctiongen.ExpressLaneAuction, opts ...AuctioneerOpt, ) (*Auctioneer, error) { @@ -54,33 +63,23 @@ func NewAuctioneer( auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second reserveSubmissionDuration := time.Duration(roundTimingInfo.ReserveSubmissionSeconds) * time.Second - minReservePrice, err := auctionContract.MinReservePrice(&bind.CallOpts{}) - if err != nil { - return nil, err - } reservePrice, err := auctionContract.ReservePrice(&bind.CallOpts{}) if err != nil { return nil, err } - hash := sha3.NewLegacyKeccak256() - hash.Write([]byte("TIMEBOOST_BID")) - domainValue := hash.Sum(nil) - am := &Auctioneer{ txOpts: txOpts, chainId: chainId, client: client, auctionContract: auctionContract, - bidsReceiver: make(chan *Bid, 10_000), + bidsReceiver: make(chan *Bid, 10_000), // TODO(Terence): Is 10000 enough? Make this configurable? bidCache: newBidCache(), initialRoundTimestamp: initialTimestamp, - auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, auctionClosingDuration: auctionClosingDuration, reserveSubmissionDuration: reserveSubmissionDuration, reservePrice: reservePrice, - minReservePrice: minReservePrice, domainValue: domainValue, } for _, o := range opts { @@ -89,18 +88,20 @@ func NewAuctioneer( return am, nil } -func (a *Auctioneer) ReceiveBid(ctx context.Context, b *Bid) error { - validated, err := a.newValidatedBid(b) +// ReceiveBid validates and adds a bid to the bid cache. +func (a *Auctioneer) receiveBid(ctx context.Context, b *Bid) error { + vb, err := a.validateBid(b) if err != nil { - return fmt.Errorf("could not validate bid: %v", err) + return errors.Wrap(err, "could not validate bid") } - a.bidCache.add(validated) + a.bidCache.add(vb) return nil } +// Start starts the autonomous auctioneer. func (a *Auctioneer) Start(ctx context.Context) { // Receive bids in the background. - go receiveAsync(ctx, a.bidsReceiver, a.ReceiveBid) + go receiveAsync(ctx, a.bidsReceiver, a.receiveBid) // Listen for sequencer health in the background and close upcoming auctions if so. go a.checkSequencerHealth(ctx) @@ -118,27 +119,22 @@ func (a *Auctioneer) Start(ctx context.Context) { if err := a.resolveAuction(ctx); err != nil { log.Error("Could not resolve auction for round", "error", err) } + // Clear the bid cache. + a.bidCache = newBidCache() } } } +// resolveAuction resolves the auction by calling the smart contract with the top two bids. func (a *Auctioneer) resolveAuction(ctx context.Context) error { upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 - // If we have no winner, then we can cancel the auction. - // Auctioneer can also subscribe to sequencer feed and - // close auction if sequencer is down. result := a.bidCache.topTwoBids() first := result.firstPlace second := result.secondPlace var tx *types.Transaction var err error - hasSingleBid := first != nil && second == nil - hasBothBids := first != nil && second != nil - noBids := first == nil && second == nil - - // TODO: Retry a given number of times in case of flakey connection. switch { - case hasBothBids: + case first != nil && second != nil: // Both bids are present tx, err = a.auctionContract.ResolveMultiBidAuction( a.txOpts, express_lane_auctiongen.Bid{ @@ -152,9 +148,9 @@ func (a *Auctioneer) resolveAuction(ctx context.Context) error { Signature: second.signature, }, ) - log.Info("Resolving auctions, received two bids", "round", upcomingRound) - case hasSingleBid: - log.Info("Resolving auctions, received single bids", "round", upcomingRound) + log.Info("Resolving auction with two bids", "round", upcomingRound) + + case first != nil: // Single bid is present tx, err = a.auctionContract.ResolveSingleBidAuction( a.txOpts, express_lane_auctiongen.Bid{ @@ -163,23 +159,32 @@ func (a *Auctioneer) resolveAuction(ctx context.Context) error { Signature: first.signature, }, ) - case noBids: - // TODO: Cancel the upcoming auction. - log.Info("No bids received for auction resolution") + log.Info("Resolving auction with single bid", "round", upcomingRound) + + case second == nil: // No bids received + log.Info("No bids received for auction resolution", "round", upcomingRound) return nil } + if err != nil { + log.Error("Error resolving auction", "error", err) return err } + receipt, err := bind.WaitMined(ctx, a.client, tx) if err != nil { + log.Error("Error waiting for transaction to be mined", "error", err) return err } - if receipt.Status != types.ReceiptStatusSuccessful { - return errors.New("deposit failed") + + if tx == nil || receipt == nil || receipt.Status != types.ReceiptStatusSuccessful { + if tx != nil { + log.Error("Transaction failed or did not finalize successfully", "txHash", tx.Hash().Hex()) + } + return errors.New("transaction failed or did not finalize successfully") } - // Clear the bid cache. - a.bidCache = newBidCache() + + log.Info("Auction resolved successfully", "txHash", tx.Hash().Hex()) return nil } @@ -189,11 +194,113 @@ func (a *Auctioneer) checkSequencerHealth(ctx context.Context) { } +// TODO(Terence): Set reserve price from the contract. + +func (a *Auctioneer) fetchReservePrice() *big.Int { + a.reservePriceLock.RLock() + defer a.reservePriceLock.RUnlock() + return new(big.Int).Set(a.reservePrice) +} + +func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { + // Check basic integrity. + if bid == nil { + return nil, errors.Wrap(ErrMalformedData, "nil bid") + } + if bid.Bidder == (common.Address{}) { + return nil, errors.Wrap(ErrMalformedData, "empty bidder address") + } + if bid.ExpressLaneController == (common.Address{}) { + return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") + } + + // Check if the chain ID is valid. + chainIdOk := false + for _, id := range a.chainId { + if bid.ChainId == id { + chainIdOk = true + break + } + } + if !chainIdOk { + return nil, errors.Wrapf(ErrWrongChainId, "can not aucution for chain id: %d", bid.ChainId) + } + + // Check if the bid is intended for upcoming round. + upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 + if bid.Round != upcomingRound { + return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.Round) + } + + // Check if the auction is closed. + if d, closed := auctionClosed(a.initialRoundTimestamp, a.roundDuration, a.auctionClosingDuration); closed { + return nil, fmt.Errorf("auction is closed, %d seconds into the round", d) + } + + // Check bid is higher than reserve price. + reservePrice := a.fetchReservePrice() + if bid.Amount.Cmp(reservePrice) == -1 { + return nil, errors.Wrapf(ErrInsufficientBid, "reserve price %s, bid %s", reservePrice, bid.Amount) + } + + // Validate the signature. + packedBidBytes, err := encodeBidValues( + a.domainValue, + new(big.Int).SetUint64(bid.ChainId), + bid.AuctionContractAddress, + bid.Round, + bid.Amount, + bid.ExpressLaneController, + ) + if err != nil { + return nil, ErrMalformedData + } + if len(bid.Signature) != 65 { + return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") + } + // Recover the public key. + prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), packedBidBytes...)) + sigItem := make([]byte, len(bid.Signature)) + copy(sigItem, bid.Signature) + if sigItem[len(sigItem)-1] >= 27 { + sigItem[len(sigItem)-1] -= 27 + } + pubkey, err := crypto.SigToPub(prefixed, sigItem) + if err != nil { + return nil, ErrMalformedData + } + if !verifySignature(pubkey, packedBidBytes, sigItem) { + return nil, ErrWrongSignature + } + // Validate if the user if a depositor in the contract and has enough balance for the bid. + // TODO: Retry some number of times if flakey connection. + // TODO: Validate reserve price against amount of bid. + // TODO: No need to do anything expensive if the bid coming is in invalid. + // Cache this if the received time of the bid is too soon. Include the arrival timestamp. + depositBal, err := a.auctionContract.BalanceOf(&bind.CallOpts{}, bid.Bidder) + if err != nil { + return nil, err + } + if depositBal.Cmp(new(big.Int)) == 0 { + return nil, ErrNotDepositor + } + if depositBal.Cmp(bid.Amount) < 0 { + return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.Amount) + } + return &validatedBid{ + expressLaneController: bid.ExpressLaneController, + amount: bid.Amount, + signature: bid.Signature, + }, nil +} + +// CurrentRound returns the current round number. func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) uint64 { return uint64(time.Since(initialRoundTimestamp) / roundDuration) } -func AuctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { +// auctionClosed returns the time since auction was closed and whether the auction is closed. +func auctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { d := time.Since(initialRoundTimestamp) % roundDuration return d, d > auctionClosingDuration } diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 74106d4e61..af9980737d 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" - "golang.org/x/crypto/sha3" ) type Client interface { @@ -24,7 +23,7 @@ type Client interface { } type auctioneerConnection interface { - ReceiveBid(ctx context.Context, bid *Bid) error + receiveBid(ctx context.Context, bid *Bid) error } type BidderClient struct { @@ -70,10 +69,6 @@ func NewBidderClient( initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second - hash := sha3.NewLegacyKeccak256() - hash.Write([]byte("TIMEBOOST_BID")) - domainValue := hash.Sum(nil) - return &BidderClient{ chainId: chainId.Uint64(), name: name, @@ -132,7 +127,7 @@ func (bd *BidderClient) Bid( return nil, err } newBid.Signature = sig - if err = bd.auctioneer.ReceiveBid(ctx, newBid); err != nil { + if err = bd.auctioneer.receiveBid(ctx, newBid); err != nil { return nil, err } return newBid, nil diff --git a/timeboost/bids.go b/timeboost/bids.go index 1ca12dbd70..c3a106d046 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -4,11 +4,9 @@ import ( "bytes" "crypto/ecdsa" "encoding/binary" - "fmt" "math/big" "sync" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" @@ -41,104 +39,6 @@ type validatedBid struct { signature []byte } -func (a *Auctioneer) fetchReservePrice() *big.Int { - a.reservePriceLock.RLock() - defer a.reservePriceLock.RUnlock() - return new(big.Int).Set(a.reservePrice) -} - -func (a *Auctioneer) newValidatedBid(bid *Bid) (*validatedBid, error) { - // Check basic integrity. - if bid == nil { - return nil, errors.Wrap(ErrMalformedData, "nil bid") - } - if bid.Bidder == (common.Address{}) { - return nil, errors.Wrap(ErrMalformedData, "empty bidder address") - } - if bid.ExpressLaneController == (common.Address{}) { - return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") - } - - // Check if the chain ID is valid. - chainIdOk := false - for _, id := range a.chainId { - if bid.ChainId == id { - chainIdOk = true - break - } - } - if !chainIdOk { - return nil, errors.Wrapf(ErrWrongChainId, "can not aucution for chain id: %d", bid.ChainId) - } - - // Check if the bid is intended for upcoming round. - upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 - if bid.Round != upcomingRound { - return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.Round) - } - - // Check if the auction is closed. - if d, closed := AuctionClosed(a.initialRoundTimestamp, a.roundDuration, a.auctionClosingDuration); closed { - return nil, fmt.Errorf("auction is closed, %d seconds into the round", d) - } - - // Check bid is higher than reserve price. - reservePrice := a.fetchReservePrice() - if bid.Amount.Cmp(reservePrice) == -1 { - return nil, errors.Wrapf(ErrInsufficientBid, "reserve price %s, bid %s", reservePrice, bid.Amount) - } - - // Validate the signature. - packedBidBytes, err := encodeBidValues( - a.domainValue, - new(big.Int).SetUint64(bid.ChainId), - bid.AuctionContractAddress, - bid.Round, - bid.Amount, - bid.ExpressLaneController, - ) - if err != nil { - return nil, ErrMalformedData - } - if len(bid.Signature) != 65 { - return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") - } - // Recover the public key. - prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), packedBidBytes...)) - sigItem := make([]byte, len(bid.Signature)) - copy(sigItem, bid.Signature) - if sigItem[len(sigItem)-1] >= 27 { - sigItem[len(sigItem)-1] -= 27 - } - pubkey, err := crypto.SigToPub(prefixed, sigItem) - if err != nil { - return nil, ErrMalformedData - } - if !verifySignature(pubkey, packedBidBytes, sigItem) { - return nil, ErrWrongSignature - } - // Validate if the user if a depositor in the contract and has enough balance for the bid. - // TODO: Retry some number of times if flakey connection. - // TODO: Validate reserve price against amount of bid. - // TODO: No need to do anything expensive if the bid coming is in invalid. - // Cache this if the received time of the bid is too soon. Include the arrival timestamp. - depositBal, err := a.auctionContract.BalanceOf(&bind.CallOpts{}, bid.Bidder) - if err != nil { - return nil, err - } - if depositBal.Cmp(new(big.Int)) == 0 { - return nil, ErrNotDepositor - } - if depositBal.Cmp(bid.Amount) < 0 { - return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.Amount) - } - return &validatedBid{ - expressLaneController: bid.ExpressLaneController, - amount: bid.Amount, - signature: bid.Signature, - }, nil -} - type bidCache struct { sync.RWMutex bidsByExpressLaneControllerAddr map[common.Address]*validatedBid @@ -169,19 +69,24 @@ func (bc *bidCache) size() int { } +// topTwoBids returns the top two bids in the cache. func (bc *bidCache) topTwoBids() *auctionResult { bc.RLock() defer bc.RUnlock() + result := &auctionResult{} - // TODO: Tiebreaker handle. + for _, bid := range bc.bidsByExpressLaneControllerAddr { + // If first place is empty or bid is higher than the current first place if result.firstPlace == nil || bid.amount.Cmp(result.firstPlace.amount) > 0 { result.secondPlace = result.firstPlace result.firstPlace = bid } else if result.secondPlace == nil || bid.amount.Cmp(result.secondPlace.amount) > 0 { + // If second place is empty or bid is higher than current second place result.secondPlace = bid } } + return result } diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index d62db0f7cd..9ee7b720ae 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -89,6 +89,6 @@ func TestReceiveBid_OK(t *testing.T) { require.NoError(t, err) // Check the bid passes validation. - _, err = am.newValidatedBid(newBid) + _, err = am.validateBid(newBid) require.NoError(t, err) } From 32e5ffdb6cfdc5557ca3cca16001f0ab4ae5c654 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 25 Jul 2024 10:21:07 -0700 Subject: [PATCH 018/109] Use init for domain value --- timeboost/auctioneer.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 4821caef9a..02654cf6bb 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -14,15 +14,17 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" + "golang.org/x/crypto/sha3" ) -// domainValue is the Keccak256 hash of the string "TIMEBOOST_BID". -// This variable represents a fixed domain identifier used in the express lane auction. -var domainValue = []byte{ - 0xc7, 0xf4, 0x5f, 0x6f, 0x1b, 0x1e, 0x1d, 0xfc, - 0x22, 0xe1, 0xb9, 0xf6, 0x9c, 0xda, 0x8e, 0x4e, - 0x86, 0xf4, 0x84, 0x81, 0xf0, 0xc5, 0xe0, 0x19, - 0x7c, 0x3f, 0x09, 0x1b, 0x89, 0xe8, 0xeb, 0x12, +// domainValue holds the Keccak256 hash of the string "TIMEBOOST_BID". +// It is intended to be immutable after initialization. +var domainValue []byte + +func init() { + hash := sha3.NewLegacyKeccak256() + hash.Write([]byte("TIMEBOOST_BID")) + domainValue = hash.Sum(nil) } type AuctioneerOpt func(*Auctioneer) From 6fd97f4be0e9629bed1006cbde1c2f5f4205f67a Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 25 Jul 2024 12:22:07 -0700 Subject: [PATCH 019/109] Tie break bids and unit tests --- timeboost/auctioneer.go | 10 +++- timeboost/bids.go | 44 +++++++++++++- timeboost/bids_test.go | 129 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 175 insertions(+), 8 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 02654cf6bb..67ffc9c4fc 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -290,9 +290,13 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.Amount) } return &validatedBid{ - expressLaneController: bid.ExpressLaneController, - amount: bid.Amount, - signature: bid.Signature, + expressLaneController: bid.ExpressLaneController, + amount: bid.Amount, + signature: bid.Signature, + chainId: bid.ChainId, + auctionContractAddress: bid.AuctionContractAddress, + round: bid.Round, + bidder: bid.Bidder, }, nil } diff --git a/timeboost/bids.go b/timeboost/bids.go index c3a106d046..b8b25b0c5a 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -3,7 +3,9 @@ package timeboost import ( "bytes" "crypto/ecdsa" + "crypto/sha256" "encoding/binary" + "fmt" "math/big" "sync" @@ -37,6 +39,11 @@ type validatedBid struct { expressLaneController common.Address amount *big.Int signature []byte + // For tie breaking + chainId uint64 + auctionContractAddress common.Address + round uint64 + bidder common.Address } type bidCache struct { @@ -77,19 +84,50 @@ func (bc *bidCache) topTwoBids() *auctionResult { result := &auctionResult{} for _, bid := range bc.bidsByExpressLaneControllerAddr { - // If first place is empty or bid is higher than the current first place - if result.firstPlace == nil || bid.amount.Cmp(result.firstPlace.amount) > 0 { + if result.firstPlace == nil { + result.firstPlace = bid + } else if bid.amount.Cmp(result.firstPlace.amount) > 0 { result.secondPlace = result.firstPlace result.firstPlace = bid + } else if bid.amount.Cmp(result.firstPlace.amount) == 0 { + if hashBid(bid) > hashBid(result.firstPlace) { + result.secondPlace = result.firstPlace + result.firstPlace = bid + } else if result.secondPlace == nil || hashBid(bid) > hashBid(result.secondPlace) { + result.secondPlace = bid + } } else if result.secondPlace == nil || bid.amount.Cmp(result.secondPlace.amount) > 0 { - // If second place is empty or bid is higher than current second place result.secondPlace = bid + } else if bid.amount.Cmp(result.secondPlace.amount) == 0 { + if hashBid(bid) > hashBid(result.secondPlace) { + result.secondPlace = bid + } } } return result } +// hashBid hashes the bidder address concatenated with the respective byte-string representation of the bid using the Keccak256 hashing scheme. +func hashBid(bid *validatedBid) string { + chainIdBytes := make([]byte, 8) + binary.BigEndian.PutUint64(chainIdBytes, bid.chainId) + roundBytes := make([]byte, 8) + binary.BigEndian.PutUint64(roundBytes, bid.round) + + // Concatenate the bidder address and the byte representation of the bid + data := append(bid.bidder.Bytes(), chainIdBytes...) + data = append(data, bid.auctionContractAddress.Bytes()...) + data = append(data, roundBytes...) + data = append(data, bid.amount.Bytes()...) + data = append(data, bid.expressLaneController.Bytes()...) + + hash := sha256.Sum256(data) + + // Return the hash as a hexadecimal string + return fmt.Sprintf("%x", hash) +} + func verifySignature(pubkey *ecdsa.PublicKey, message []byte, sig []byte) bool { prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), message...)) diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 9ee7b720ae..a49be08960 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -18,7 +19,7 @@ func TestResolveAuction(t *testing.T) { // Set up a new auction master instance that can validate bids. am, err := NewAuctioneer( - testSetup.accounts[0].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, + testSetup.accounts[0].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, ) require.NoError(t, err) @@ -76,7 +77,7 @@ func TestReceiveBid_OK(t *testing.T) { // Set up a new auction master instance that can validate bids. am, err := NewAuctioneer( - testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, testSetup.expressLaneAuction, + testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, ) require.NoError(t, err) @@ -92,3 +93,127 @@ func TestReceiveBid_OK(t *testing.T) { _, err = am.validateBid(newBid) require.NoError(t, err) } + +func TestTopTwoBids(t *testing.T) { + tests := []struct { + name string + bids map[common.Address]*validatedBid + expected *auctionResult + }{ + { + name: "Single Bid", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: nil, + }, + }, + { + name: "Two Bids with Different Amounts", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, + secondPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, + }, + }, + { + name: "Two Bids with Same Amount and Different Hashes", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + }, + }, + { + name: "More Than Two Bids, All Unique Amounts", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, + }, + }, + { + name: "More Than Two Bids, Some with Same Amounts", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, + common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, + }, + }, + { + name: "More Than Two Bids, Tied for Second Place", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, + }, + }, + { + name: "All Bids with the Same Amount", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, + secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + }, + }, + { + name: "No Bids", + bids: nil, + expected: &auctionResult{firstPlace: nil, secondPlace: nil}, + }, + { + name: "Identical Bids", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bc := &bidCache{ + bidsByExpressLaneControllerAddr: tt.bids, + } + result := bc.topTwoBids() + if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { + t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) + } + if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { + t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) + } + if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { + t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) + } + }) + } +} From d54f89b11ff5f5ffd861d6987945c26e7e5aca77 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Fri, 26 Jul 2024 20:20:44 -0700 Subject: [PATCH 020/109] Test resolve bids --- timeboost/auctioneer.go | 15 ++-- timeboost/auctioneer_test.go | 162 +++++++++++++++++++++++++++++++++++ timeboost/bidder_client.go | 2 +- timeboost/bids.go | 8 +- 4 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 timeboost/auctioneer_test.go diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 67ffc9c4fc..b1ccf71425 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -2,7 +2,6 @@ package timeboost import ( "context" - "fmt" "math/big" "sync" "time" @@ -225,7 +224,7 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { } } if !chainIdOk { - return nil, errors.Wrapf(ErrWrongChainId, "can not aucution for chain id: %d", bid.ChainId) + return nil, errors.Wrapf(ErrWrongChainId, "can not auction for chain id: %d", bid.ChainId) } // Check if the bid is intended for upcoming round. @@ -236,19 +235,19 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { // Check if the auction is closed. if d, closed := auctionClosed(a.initialRoundTimestamp, a.roundDuration, a.auctionClosingDuration); closed { - return nil, fmt.Errorf("auction is closed, %d seconds into the round", d) + return nil, errors.Wrapf(ErrBadRoundNumber, "auction is closed, %s since closing", d) } // Check bid is higher than reserve price. reservePrice := a.fetchReservePrice() if bid.Amount.Cmp(reservePrice) == -1 { - return nil, errors.Wrapf(ErrInsufficientBid, "reserve price %s, bid %s", reservePrice, bid.Amount) + return nil, errors.Wrapf(ErrInsufficientBid, "reserve price %s, bid %s", reservePrice.String(), bid.Amount.String()) } // Validate the signature. packedBidBytes, err := encodeBidValues( a.domainValue, - new(big.Int).SetUint64(bid.ChainId), + bid.ChainId, bid.AuctionContractAddress, bid.Round, bid.Amount, @@ -302,11 +301,17 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { // CurrentRound returns the current round number. func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) uint64 { + if roundDuration == 0 { + return 0 + } return uint64(time.Since(initialRoundTimestamp) / roundDuration) } // auctionClosed returns the time since auction was closed and whether the auction is closed. func auctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { + if roundDuration == 0 { + return 0, true + } d := time.Since(initialRoundTimestamp) % roundDuration return d, d > auctionClosingDuration } diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go new file mode 100644 index 0000000000..b35e017919 --- /dev/null +++ b/timeboost/auctioneer_test.go @@ -0,0 +1,162 @@ +package timeboost + +import ( + "context" + "crypto/ecdsa" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func TestAuctioneer_validateBid(t *testing.T) { + tests := []struct { + name string + bid *Bid + expectedErr error + errMsg string + auctionClosed bool + }{ + { + name: "Nil bid", + bid: nil, + expectedErr: ErrMalformedData, + errMsg: "nil bid", + }, + { + name: "Empty bidder address", + bid: &Bid{}, + expectedErr: ErrMalformedData, + errMsg: "empty bidder address", + }, + { + name: "Empty express lane controller address", + bid: &Bid{Bidder: common.Address{'a'}}, + expectedErr: ErrMalformedData, + errMsg: "empty express lane controller address", + }, + { + name: "Incorrect chain id", + bid: &Bid{ + Bidder: common.Address{'a'}, + ExpressLaneController: common.Address{'b'}, + }, + expectedErr: ErrWrongChainId, + errMsg: "can not auction for chain id: 0", + }, + { + name: "Incorrect round", + bid: &Bid{ + Bidder: common.Address{'a'}, + ExpressLaneController: common.Address{'b'}, + ChainId: 1, + }, + expectedErr: ErrBadRoundNumber, + errMsg: "wanted 1, got 0", + }, + { + name: "Auction is closed", + bid: &Bid{ + Bidder: common.Address{'a'}, + ExpressLaneController: common.Address{'b'}, + ChainId: 1, + Round: 1, + }, + expectedErr: ErrBadRoundNumber, + errMsg: "auction is closed", + auctionClosed: true, + }, + { + name: "Lower than reserved price", + bid: &Bid{ + Bidder: common.Address{'a'}, + ExpressLaneController: common.Address{'b'}, + ChainId: 1, + Round: 1, + Amount: big.NewInt(1), + }, + expectedErr: ErrInsufficientBid, + errMsg: "reserve price 2, bid 1", + }, + { + name: "incorrect signature", + bid: &Bid{ + Bidder: common.Address{'a'}, + ExpressLaneController: common.Address{'b'}, + ChainId: 1, + Round: 1, + Amount: big.NewInt(3), + Signature: []byte{'a'}, + }, + expectedErr: ErrMalformedData, + errMsg: "signature length is not 65", + }, + { + name: "not a depositor", + bid: buildValidBid(t), + expectedErr: ErrNotDepositor, + }, + } + + setup := setupAuctionTest(t, context.Background()) + + for _, tt := range tests { + a := Auctioneer{ + chainId: []uint64{1}, + initialRoundTimestamp: time.Now().Add(-time.Second), + reservePrice: big.NewInt(2), + roundDuration: time.Minute, + auctionClosingDuration: 45 * time.Second, + auctionContract: setup.expressLaneAuction, + } + if tt.auctionClosed { + a.roundDuration = 0 + } + t.Run(tt.name, func(t *testing.T) { + _, err := a.validateBid(tt.bid) + require.ErrorIs(t, err, tt.expectedErr) + require.Contains(t, err.Error(), tt.errMsg) + }) + } +} + +func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { + prefixedData := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), data...)) + signature, err := crypto.Sign(prefixedData, privateKey) + if err != nil { + return nil, err + } + return signature, nil +} + +func buildValidBid(t *testing.T) *Bid { + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + require.True(t, ok) + bidderAddress := crypto.PubkeyToAddress(*publicKeyECDSA) + + b := &Bid{ + Bidder: bidderAddress, + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: common.Address{'c'}, + ChainId: 1, + Round: 1, + Amount: big.NewInt(3), + Signature: []byte{'a'}, + } + + bidValues, err := encodeBidValues(domainValue, b.ChainId, b.AuctionContractAddress, b.Round, b.Amount, b.ExpressLaneController) + require.NoError(t, err) + + signature, err := buildSignature(privateKey, bidValues) + require.NoError(t, err) + + b.Signature = signature + + return b +} diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index af9980737d..de3f97bb50 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -113,7 +113,7 @@ func (bd *BidderClient) Bid( } packedBidBytes, err := encodeBidValues( bd.domainValue, - new(big.Int).SetUint64(newBid.ChainId), + newBid.ChainId, bd.auctionContractAddress, newBid.Round, amount, diff --git a/timeboost/bids.go b/timeboost/bids.go index b8b25b0c5a..f934796964 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -142,14 +142,16 @@ func padBigInt(bi *big.Int) []byte { return padded } -func encodeBidValues(domainValue []byte, chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { +func encodeBidValues(domainValue []byte, chainId uint64, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { buf := new(bytes.Buffer) // Encode uint256 values - each occupies 32 bytes buf.Write(domainValue) - buf.Write(padBigInt(chainId)) - buf.Write(auctionContractAddress[:]) roundBuf := make([]byte, 8) + binary.BigEndian.PutUint64(roundBuf, chainId) + buf.Write(roundBuf) + buf.Write(auctionContractAddress[:]) + roundBuf = make([]byte, 8) binary.BigEndian.PutUint64(roundBuf, round) buf.Write(roundBuf) buf.Write(padBigInt(amount)) From c3d8c01ac3ca2022fddaaa3ca9f1506f1613211a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 09:32:21 -0500 Subject: [PATCH 021/109] begin adding in endpoints --- execution/gethexec/api.go | 6 +-- execution/gethexec/express_lane_service.go | 49 ++++++++------------- execution/gethexec/sequencer.go | 51 +++++++++++----------- system_tests/seqfeed_test.go | 3 ++ 4 files changed, 49 insertions(+), 60 deletions(-) diff --git a/execution/gethexec/api.go b/execution/gethexec/api.go index 54e89d205e..057d45ba4a 100644 --- a/execution/gethexec/api.go +++ b/execution/gethexec/api.go @@ -14,6 +14,7 @@ import ( "time" "github.com/ethereum/go-ethereum/arbitrum" + "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -43,10 +44,9 @@ func NewArbTimeboostAPI(publisher TransactionPublisher) *ArbTimeboostAPI { return &ArbTimeboostAPI{publisher} } -func (a *ArbTimeboostAPI) SendExpressLaneTransaction(ctx context.Context, tx *types.Transaction) error { +func (a *ArbTimeboostAPI) SendExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { fmt.Println("hit the endpoint") - return nil - // return a.txPublisher.PublishTransaction(ctx) + return a.txPublisher.PublishExpressLaneTransaction(ctx, msg) } type ArbDebugAPI struct { diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 4eb1a6f892..934d176f1e 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -17,12 +18,6 @@ import ( "github.com/offchainlabs/nitro/util/stopwaiter" ) -var _ expressLaneChecker = &expressLaneService{} - -type expressLaneChecker interface { - isExpressLaneTx(sender common.Address) bool -} - type expressLaneControl struct { round uint64 sequence uint64 @@ -152,43 +147,33 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) } -// A transaction is an express lane transaction if it is sent to a chain's predefined reserved address. -func (es *expressLaneService) isExpressLaneTx(to common.Address) bool { - es.RLock() - defer es.RUnlock() - - return to == es.expressLaneAddr +func (es *expressLaneService) currentRoundHasController() bool { + es.Lock() + defer es.Unlock() + return es.control.controller != (common.Address{}) } // An express lane transaction is valid if it satisfies the following conditions: // 1. The tx round expressed under `maxPriorityFeePerGas` equals the current round number. // 2. The tx sequence expressed under `nonce` equals the current round sequence. // 3. The tx sender equals the current round’s priority controller address. -func (es *expressLaneService) validateExpressLaneTx(tx *types.Transaction) error { +func (es *expressLaneService) validateExpressLaneTx(msg *arbitrum_types.ExpressLaneSubmission) error { es.Lock() defer es.Unlock() currentRound := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) - round := tx.GasTipCap().Uint64() - if round != currentRound { - return fmt.Errorf("express lane tx round %d does not match current round %d", round, currentRound) - } - - sequence := tx.Nonce() - if sequence != es.control.sequence { - // TODO: Cache out-of-order sequenced express lane transactions and replay them once the gap is filled. - return fmt.Errorf("express lane tx sequence %d does not match current round sequence %d", sequence, es.control.sequence) - } - es.control.sequence++ - - signer := types.LatestSigner(es.chainConfig) - sender, err := types.Sender(signer, tx) - if err != nil { - return err - } - if sender != es.control.controller { - return fmt.Errorf("express lane tx sender %s does not match current round controller %s", sender, es.control.controller) + if msg.Round != currentRound { + return fmt.Errorf("express lane tx round %d does not match current round %d", msg.Round, currentRound) } + // TODO: recover the sender from the signature and message bytes that are being signed over. + // signer := types.LatestSigner(es.chainConfig) + // sender, err := types.Sender(signer, tx) + // if err != nil { + // return err + // } + // if sender != es.control.controller { + // return fmt.Errorf("express lane tx sender %s does not match current round controller %s", sender, es.control.controller) + // } return nil } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 4aef050217..660b435972 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -458,7 +458,23 @@ func ctxWithTimeout(ctx context.Context, timeout time.Duration) (context.Context return context.WithTimeout(ctx, timeout) } +type PublishTxConfig struct { + delayTransaction bool +} + +type TimeboostOpt func(p *PublishTxConfig) + +func WithExpressLane() TimeboostOpt { + return func(p *PublishTxConfig) { + p.delayTransaction = false + } +} + func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { + return s.publishTransactionImpl(parentCtx, tx, options, true) +} + +func (s *Sequencer) publishTransactionImpl(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { config := s.config() // Only try to acquire Rlock and check for hard threshold if l1reader is not nil // And hard threshold was enabled, this prevents spamming of read locks when not needed @@ -498,35 +514,17 @@ func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Tran return types.ErrTxTypeNotSupported } - if s.config().Timeboost.Enable { - // Express lane transaction sequence is defined by the following spec: - // https://github.com/OffchainLabs/timeboost-design-docs/blob/main/research_spec.md - // The express lane transaction is defined by a transaction's `to` address matching a predefined chain's reserved address. - // The express lane transaction will follow verifications for round number, nonce, and sender's address. - // If all pass, the transaction will be sequenced right away. - // Non-express lane transactions will be delayed by ExpressLaneAdvantage. - - if !s.expressLaneService.isExpressLaneTx(*tx.To()) { - log.Info("Delaying non-express lane tx", "hash", tx.Hash()) - time.Sleep(s.config().Timeboost.ExpressLaneAdvantage) - } else { - if err := s.expressLaneService.validateExpressLaneTx(tx); err != nil { - return fmt.Errorf("express lane validation failed: %w", err) - } - unwrappedTx, err := unwrapExpressLaneTx(tx) - if err != nil { - return fmt.Errorf("failed to unwrap express lane tx: %w", err) - } - tx = unwrappedTx - log.Info("Processing express lane tx", "hash", tx.Hash()) - } - } - txBytes, err := tx.MarshalBinary() if err != nil { return err } + if s.config().Timeboost.Enable { + if delay && s.expressLaneService.currentRoundHasController() { + time.Sleep(s.config().Timeboost.ExpressLaneAdvantage) + } + } + queueTimeout := config.QueueTimeout queueCtx, cancelFunc := ctxWithTimeout(parentCtx, queueTimeout) defer cancelFunc() @@ -567,7 +565,10 @@ func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Tran } func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { - return nil + if err := s.expressLaneService.validateExpressLaneTx(msg); err != nil { + return err + } + return s.publishTransactionImpl(ctx, msg.Transaction, nil, false) } func (s *Sequencer) preTxFilter(_ *params.ChainConfig, header *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, sender common.Address, l1Info *arbos.L1Info) error { diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index e8f79fc221..f6baa511af 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -102,6 +102,9 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, true) + builderSeq.l2StackConfig.HTTPHost = "localhost" + builderSeq.l2StackConfig.HTTPPort = 9567 + builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost"} builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() builderSeq.execConfig.Sequencer.Enable = true builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ From 02bdf4564fb4a8bd9817cf44824b8776de4a8565 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 10:33:53 -0500 Subject: [PATCH 022/109] validate express lane tx submission in sequencer --- cmd/autonomous-auctioneer/main.go | 57 +++++++++++++ execution/gethexec/api.go | 7 +- execution/gethexec/arb_interface.go | 7 +- execution/gethexec/express_lane_service.go | 83 ++++++++++++------- execution/gethexec/forwarder.go | 7 +- execution/gethexec/sequencer.go | 7 +- execution/gethexec/tx_pre_checker.go | 3 +- timeboost/auctioneer.go | 29 ++++++- timeboost/auctioneer_api.go | 96 ++++++++++++++++++++++ timeboost/auctioneer_test.go | 2 +- timeboost/bids.go | 18 ++-- 11 files changed, 263 insertions(+), 53 deletions(-) create mode 100644 timeboost/auctioneer_api.go diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go index a82b65af67..139a0a8efe 100644 --- a/cmd/autonomous-auctioneer/main.go +++ b/cmd/autonomous-auctioneer/main.go @@ -48,6 +48,63 @@ func mainImpl() int { return 1 } + // stackConf := DefaultValidationNodeStackConfig + // stackConf.DataDir = "" // ephemeral + // nodeConfig.HTTP.Apply(&stackConf) + // nodeConfig.WS.Apply(&stackConf) + // nodeConfig.Auth.Apply(&stackConf) + // nodeConfig.IPC.Apply(&stackConf) + // stackConf.P2P.ListenAddr = "" + // stackConf.P2P.NoDial = true + // stackConf.P2P.NoDiscovery = true + // vcsRevision, strippedRevision, vcsTime := confighelpers.GetVersion() + // stackConf.Version = strippedRevision + + // pathResolver := func(workdir string) func(string) string { + // if workdir == "" { + // workdir, err = os.Getwd() + // if err != nil { + // log.Warn("Failed to get workdir", "err", err) + // } + // } + // return func(path string) string { + // if filepath.IsAbs(path) { + // return path + // } + // return filepath.Join(workdir, path) + // } + // } + + // err = genericconf.InitLog(nodeConfig.LogType, nodeConfig.LogLevel, &nodeConfig.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) + // if err != nil { + // fmt.Fprintf(os.Stderr, "Error initializing logging: %v\n", err) + // return 1 + // } + // if stackConf.JWTSecret == "" && stackConf.AuthAddr != "" { + // filename := pathResolver(nodeConfig.Persistent.GlobalConfig)("jwtsecret") + // if err := genericconf.TryCreatingJWTSecret(filename); err != nil { + // log.Error("Failed to prepare jwt secret file", "err", err) + // return 1 + // } + // stackConf.JWTSecret = filename + // } + + // log.Info("Running Arbitrum nitro validation node", "revision", vcsRevision, "vcs.time", vcsTime) + + // liveNodeConfig := genericconf.NewLiveConfig[*ValidationNodeConfig](args, nodeConfig, ParseNode) + // liveNodeConfig.SetOnReloadHook(func(oldCfg *ValidationNodeConfig, newCfg *ValidationNodeConfig) error { + + // return genericconf.InitLog(newCfg.LogType, newCfg.LogLevel, &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) + // }) + + // valnode.EnsureValidationExposedViaAuthRPC(&stackConf) + + // stack, err := node.New(&stackConf) + // if err != nil { + // flag.Usage() + // log.Crit("failed to initialize geth stack", "err", err) + // } + fatalErrChan := make(chan error, 10) sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) diff --git a/execution/gethexec/api.go b/execution/gethexec/api.go index 057d45ba4a..cdad9003a5 100644 --- a/execution/gethexec/api.go +++ b/execution/gethexec/api.go @@ -14,13 +14,13 @@ import ( "time" "github.com/ethereum/go-ethereum/arbitrum" - "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/retryables" + "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -44,9 +44,8 @@ func NewArbTimeboostAPI(publisher TransactionPublisher) *ArbTimeboostAPI { return &ArbTimeboostAPI{publisher} } -func (a *ArbTimeboostAPI) SendExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { - fmt.Println("hit the endpoint") - return a.txPublisher.PublishExpressLaneTransaction(ctx, msg) +func (a *ArbTimeboostAPI) SendExpressLaneTransaction(ctx context.Context, msg *timeboost.JsonExpressLaneSubmission) error { + return a.txPublisher.PublishExpressLaneTransaction(ctx, timeboost.JsonSubmissionToGo(msg)) } type ArbDebugAPI struct { diff --git a/execution/gethexec/arb_interface.go b/execution/gethexec/arb_interface.go index 04261d6dc6..7678bbf202 100644 --- a/execution/gethexec/arb_interface.go +++ b/execution/gethexec/arb_interface.go @@ -9,10 +9,11 @@ import ( "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/offchainlabs/nitro/timeboost" ) type TransactionPublisher interface { - PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error + PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error PublishTransaction(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error CheckHealth(ctx context.Context) error Initialize(context.Context) error @@ -42,8 +43,8 @@ func (a *ArbInterface) PublishTransaction(ctx context.Context, tx *types.Transac return a.txPublisher.PublishTransaction(ctx, tx, options) } -func (a *ArbInterface) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { - return a.txPublisher.PublishExpressLaneTransaction(ctx, msg) +func (a *ArbInterface) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.JsonExpressLaneSubmission) error { + return a.txPublisher.PublishExpressLaneTransaction(ctx, timeboost.JsonSubmissionToGo(msg)) } // might be used before Initialize diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 934d176f1e..aac0d36d5f 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -7,15 +7,17 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/pkg/errors" ) type expressLaneControl struct { @@ -27,13 +29,13 @@ type expressLaneControl struct { type expressLaneService struct { stopwaiter.StopWaiter sync.RWMutex - client arbutil.L1Interface - control expressLaneControl - expressLaneAddr common.Address - auctionContract *express_lane_auctiongen.ExpressLaneAuction - initialTimestamp time.Time - roundDuration time.Duration - chainConfig *params.ChainConfig + client arbutil.L1Interface + control expressLaneControl + auctionContractAddr common.Address + auctionContract *express_lane_auctiongen.ExpressLaneAuction + initialTimestamp time.Time + roundDuration time.Duration + chainConfig *params.ChainConfig } func newExpressLaneService( @@ -60,8 +62,8 @@ func newExpressLaneService( controller: common.Address{}, round: 0, }, - expressLaneAddr: common.HexToAddress("0x2424242424242424242424242424242424242424"), - roundDuration: roundDuration, + auctionContractAddr: auctionContractAddr, + roundDuration: roundDuration, }, nil } @@ -153,27 +155,52 @@ func (es *expressLaneService) currentRoundHasController() bool { return es.control.controller != (common.Address{}) } -// An express lane transaction is valid if it satisfies the following conditions: -// 1. The tx round expressed under `maxPriorityFeePerGas` equals the current round number. -// 2. The tx sequence expressed under `nonce` equals the current round sequence. -// 3. The tx sender equals the current round’s priority controller address. -func (es *expressLaneService) validateExpressLaneTx(msg *arbitrum_types.ExpressLaneSubmission) error { - es.Lock() - defer es.Unlock() - +func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSubmission) error { + if msg.Transaction == nil || msg.Signature == nil { + return timeboost.ErrMalformedData + } + if msg.AuctionContractAddress != es.auctionContractAddr { + return timeboost.ErrWrongAuctionContract + } + if !es.currentRoundHasController() { + return timeboost.ErrNoOnchainController + } + // TODO: Careful with chain id not being uint64. + if msg.ChainId != es.chainConfig.ChainID.Uint64() { + return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, es.chainConfig.ChainID.Uint64()) + } currentRound := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) if msg.Round != currentRound { - return fmt.Errorf("express lane tx round %d does not match current round %d", msg.Round, currentRound) + return errors.Wrapf(timeboost.ErrBadRoundNumber, "express lane tx round %d does not match current round %d", msg.Round, currentRound) + } + es.Lock() + defer es.Unlock() + // Reconstruct the message being signed over and recover the sender address. + signingMessage, err := msg.ToMessageBytes() + if err != nil { + return timeboost.ErrMalformedData + } + if len(msg.Signature) != 65 { + return errors.Wrap(timeboost.ErrMalformedData, "signature length is not 65") + } + // Recover the public key. + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(signingMessage))), signingMessage...)) + sigItem := make([]byte, len(msg.Signature)) + copy(sigItem, msg.Signature) + if sigItem[len(sigItem)-1] >= 27 { + sigItem[len(sigItem)-1] -= 27 + } + pubkey, err := crypto.SigToPub(prefixed, sigItem) + if err != nil { + return timeboost.ErrMalformedData + } + if !secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sigItem[:len(sigItem)-1]) { + return timeboost.ErrWrongSignature + } + sender := crypto.PubkeyToAddress(*pubkey) + if sender != es.control.controller { + return timeboost.ErrNotExpressLaneController } - // TODO: recover the sender from the signature and message bytes that are being signed over. - // signer := types.LatestSigner(es.chainConfig) - // sender, err := types.Sender(signer, tx) - // if err != nil { - // return err - // } - // if sender != es.control.controller { - // return fmt.Errorf("express lane tx sender %s does not match current round controller %s", sender, es.control.controller) - // } return nil } diff --git a/execution/gethexec/forwarder.go b/execution/gethexec/forwarder.go index 945cd5e944..e35d0b759f 100644 --- a/execution/gethexec/forwarder.go +++ b/execution/gethexec/forwarder.go @@ -14,6 +14,7 @@ import ( "sync/atomic" "time" + "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" flag "github.com/spf13/pflag" @@ -156,7 +157,7 @@ func (f *TxForwarder) PublishTransaction(inctx context.Context, tx *types.Transa return errors.New("failed to publish transaction to any of the forwarding targets") } -func (f *TxForwarder) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { +func (f *TxForwarder) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { return nil } @@ -256,7 +257,7 @@ func (f *TxDropper) PublishTransaction(ctx context.Context, tx *types.Transactio return txDropperErr } -func (f *TxDropper) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { +func (f *TxDropper) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.JsonExpressLaneSubmission) error { return txDropperErr } @@ -303,7 +304,7 @@ func (f *RedisTxForwarder) PublishTransaction(ctx context.Context, tx *types.Tra return forwarder.PublishTransaction(ctx, tx, options) } -func (f *RedisTxForwarder) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { +func (f *RedisTxForwarder) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { forwarder := f.getForwarder() if forwarder == nil { return ErrNoSequencer diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 660b435972..ba849d988f 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -18,6 +18,7 @@ import ( "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/execution" + "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/headerreader" @@ -471,7 +472,7 @@ func WithExpressLane() TimeboostOpt { } func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { - return s.publishTransactionImpl(parentCtx, tx, options, true) + return s.publishTransactionImpl(parentCtx, tx, options, true /* delay tx if express lane is active */) } func (s *Sequencer) publishTransactionImpl(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { @@ -564,11 +565,11 @@ func (s *Sequencer) publishTransactionImpl(parentCtx context.Context, tx *types. } } -func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { +func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { if err := s.expressLaneService.validateExpressLaneTx(msg); err != nil { return err } - return s.publishTransactionImpl(ctx, msg.Transaction, nil, false) + return s.publishTransactionImpl(ctx, msg.Transaction, nil, false /* no delay, as this is an express lane tx */) } func (s *Sequencer) preTxFilter(_ *params.ChainConfig, header *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, sender common.Address, l1Info *arbos.L1Info) error { diff --git a/execution/gethexec/tx_pre_checker.go b/execution/gethexec/tx_pre_checker.go index ba469df55c..5f3bed82a9 100644 --- a/execution/gethexec/tx_pre_checker.go +++ b/execution/gethexec/tx_pre_checker.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" flag "github.com/spf13/pflag" @@ -222,6 +223,6 @@ func (c *TxPreChecker) PublishTransaction(ctx context.Context, tx *types.Transac return c.TransactionPublisher.PublishTransaction(ctx, tx, options) } -func (c *TxPreChecker) PublishExpressLaneTransaction(ctx context.Context, msg *arbitrum_types.ExpressLaneSubmission) error { +func (c *TxPreChecker) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { return nil } diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index b1ccf71425..92c17abb95 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" "golang.org/x/crypto/sha3" @@ -47,6 +48,19 @@ type Auctioneer struct { reservePrice *big.Int } +func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { + // found := false + // for _, module := range stackConf.AuthModules { + // if module == server_api.Namespace { + // found = true + // break + // } + // } + // if !found { + // stackConf.AuthModules = append(stackConf.AuthModules, server_api.Namespace) + // } +} + // NewAuctioneer creates a new autonomous auctioneer struct. func NewAuctioneer( txOpts *bind.TransactOpts, @@ -69,6 +83,17 @@ func NewAuctioneer( return nil, err } + node := &node.Node{} + _ = node + // valAPIs := []rpc.API{{ + // Namespace: server_api.Namespace, + // Version: "1.0", + // Service: serverAPI, + // Public: config.ApiPublic, + // Authenticated: config.ApiAuth, + // }} + // stack.RegisterAPIs(valAPIs) + am := &Auctioneer{ txOpts: txOpts, chainId: chainId, @@ -93,7 +118,7 @@ func NewAuctioneer( func (a *Auctioneer) receiveBid(ctx context.Context, b *Bid) error { vb, err := a.validateBid(b) if err != nil { - return errors.Wrap(err, "could not validate bid") + return err } a.bidCache.add(vb) return nil @@ -241,7 +266,7 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { // Check bid is higher than reserve price. reservePrice := a.fetchReservePrice() if bid.Amount.Cmp(reservePrice) == -1 { - return nil, errors.Wrapf(ErrInsufficientBid, "reserve price %s, bid %s", reservePrice.String(), bid.Amount.String()) + return nil, errors.Wrapf(ErrReservePriceNotMet, "reserve price %s, bid %s", reservePrice.String(), bid.Amount.String()) } // Validate the signature. diff --git a/timeboost/auctioneer_api.go b/timeboost/auctioneer_api.go new file mode 100644 index 0000000000..8938dd50be --- /dev/null +++ b/timeboost/auctioneer_api.go @@ -0,0 +1,96 @@ +package timeboost + +import ( + "bytes" + "context" + "encoding/binary" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type AuctioneerAPI struct { + *Auctioneer +} + +type JsonBid struct { + ChainId uint64 `json:"chainId"` + ExpressLaneController common.Address `json:"expressLaneController"` + Bidder common.Address `json:"bidder"` + AuctionContractAddress common.Address `json:"auctionContractAddress"` + Round uint64 `json:"round"` + Amount *big.Int `json:"amount"` + Signature string `json:"signature"` +} + +type JsonExpressLaneSubmission struct { + ChainId uint64 `json:"chainId"` + Round uint64 `json:"round"` + AuctionContractAddress common.Address `json:"auctionContractAddress"` + Transaction *types.Transaction `json:"transaction"` + Signature string `json:"signature"` +} + +type ExpressLaneSubmission struct { + ChainId uint64 + Round uint64 + AuctionContractAddress common.Address + Transaction *types.Transaction + Signature []byte +} + +func JsonSubmissionToGo(submission *JsonExpressLaneSubmission) *ExpressLaneSubmission { + return &ExpressLaneSubmission{ + ChainId: submission.ChainId, + Round: submission.Round, + AuctionContractAddress: submission.AuctionContractAddress, + Transaction: submission.Transaction, + Signature: common.Hex2Bytes(submission.Signature), + } +} + +func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { + return encodeExpressLaneSubmission( + domainValue, + els.ChainId, + els.AuctionContractAddress, + els.Round, + els.Transaction, + ) +} + +func encodeExpressLaneSubmission( + domainValue []byte, chainId uint64, + auctionContractAddress common.Address, + round uint64, + tx *types.Transaction, +) ([]byte, error) { + buf := new(bytes.Buffer) + buf.Write(domainValue) + roundBuf := make([]byte, 8) + binary.BigEndian.PutUint64(roundBuf, chainId) + buf.Write(roundBuf) + buf.Write(auctionContractAddress[:]) + roundBuf = make([]byte, 8) + binary.BigEndian.PutUint64(roundBuf, round) + buf.Write(roundBuf) + rlpTx, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + buf.Write(rlpTx) + return buf.Bytes(), nil +} + +func (a *AuctioneerAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { + return a.receiveBid(ctx, &Bid{ + ChainId: bid.ChainId, + ExpressLaneController: bid.ExpressLaneController, + Bidder: bid.Bidder, + AuctionContractAddress: bid.AuctionContractAddress, + Round: bid.Round, + Amount: bid.Amount, + Signature: common.Hex2Bytes(bid.Signature), + }) +} diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index b35e017919..4c10e88565 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -78,7 +78,7 @@ func TestAuctioneer_validateBid(t *testing.T) { Round: 1, Amount: big.NewInt(1), }, - expectedErr: ErrInsufficientBid, + expectedErr: ErrReservePriceNotMet, errMsg: "reserve price 2, bid 1", }, { diff --git a/timeboost/bids.go b/timeboost/bids.go index f934796964..4ff2e3babb 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -16,13 +16,16 @@ import ( ) var ( - ErrMalformedData = errors.New("malformed bid data") - ErrNotDepositor = errors.New("not a depositor") - ErrWrongChainId = errors.New("wrong chain id") - ErrWrongSignature = errors.New("wrong signature") - ErrBadRoundNumber = errors.New("bad round number") - ErrInsufficientBalance = errors.New("insufficient balance") - ErrInsufficientBid = errors.New("insufficient bid") + ErrMalformedData = errors.New("MALFORMED_DATA") + ErrNotDepositor = errors.New("NOT_DEPOSITOR") + ErrWrongChainId = errors.New("WRONG_CHAIN_ID") + ErrWrongSignature = errors.New("WRONG_SIGNATURE") + ErrBadRoundNumber = errors.New("BAD_ROUND_NUMBER") + ErrInsufficientBalance = errors.New("INSUFFICIENT_BALANCE") + ErrReservePriceNotMet = errors.New("RESERVE_PRICE_NOT_MET") + ErrNoOnchainController = errors.New("NO_ONCHAIN_CONTROLLER") + ErrWrongAuctionContract = errors.New("WRONG_AUCTION_CONTRACT") + ErrNotExpressLaneController = errors.New("NOT_EXPRESS_LANE_CONTROLLER") ) type Bid struct { @@ -45,7 +48,6 @@ type validatedBid struct { round uint64 bidder common.Address } - type bidCache struct { sync.RWMutex bidsByExpressLaneControllerAddr map[common.Address]*validatedBid From d7eb164ae7eb809a9e4c87f59f1cd72f7d56f1f4 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 10:52:54 -0500 Subject: [PATCH 023/109] express lane client send transaction --- timeboost/auctioneer.go | 45 ++++++++++++------------ timeboost/express_lane_client.go | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 timeboost/express_lane_client.go diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 92c17abb95..b65d6a0002 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" "golang.org/x/crypto/sha3" @@ -21,6 +22,8 @@ import ( // It is intended to be immutable after initialization. var domainValue []byte +const AuctioneerNamespace = "auctioneer" + func init() { hash := sha3.NewLegacyKeccak256() hash.Write([]byte("TIMEBOOST_BID")) @@ -49,22 +52,23 @@ type Auctioneer struct { } func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { - // found := false - // for _, module := range stackConf.AuthModules { - // if module == server_api.Namespace { - // found = true - // break - // } - // } - // if !found { - // stackConf.AuthModules = append(stackConf.AuthModules, server_api.Namespace) - // } + found := false + for _, module := range stackConf.AuthModules { + if module == AuctioneerNamespace { + found = true + break + } + } + if !found { + stackConf.AuthModules = append(stackConf.AuthModules, AuctioneerNamespace) + } } // NewAuctioneer creates a new autonomous auctioneer struct. func NewAuctioneer( txOpts *bind.TransactOpts, chainId []uint64, + stack *node.Node, client Client, auctionContract *express_lane_auctiongen.ExpressLaneAuction, opts ...AuctioneerOpt, @@ -82,18 +86,6 @@ func NewAuctioneer( if err != nil { return nil, err } - - node := &node.Node{} - _ = node - // valAPIs := []rpc.API{{ - // Namespace: server_api.Namespace, - // Version: "1.0", - // Service: serverAPI, - // Public: config.ApiPublic, - // Authenticated: config.ApiAuth, - // }} - // stack.RegisterAPIs(valAPIs) - am := &Auctioneer{ txOpts: txOpts, chainId: chainId, @@ -111,6 +103,14 @@ func NewAuctioneer( for _, o := range opts { o(am) } + auctioneerApi := &AuctioneerAPI{am} + valAPIs := []rpc.API{{ + Namespace: AuctioneerNamespace, + Version: "1.0", + Service: auctioneerApi, + Public: true, + }} + stack.RegisterAPIs(valAPIs) return am, nil } @@ -221,7 +221,6 @@ func (a *Auctioneer) checkSequencerHealth(ctx context.Context) { } // TODO(Terence): Set reserve price from the contract. - func (a *Auctioneer) fetchReservePrice() *big.Int { a.reservePriceLock.RLock() defer a.reservePriceLock.RUnlock() diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go new file mode 100644 index 0000000000..3b6eeb37e7 --- /dev/null +++ b/timeboost/express_lane_client.go @@ -0,0 +1,59 @@ +package timeboost + +import ( + "context" + "crypto/ecdsa" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/stopwaiter" +) + +type ExpressLaneClient struct { + stopwaiter.StopWaiter + privKey *ecdsa.PrivateKey + chainId uint64 + initialRoundTimestamp time.Time + roundDuration time.Duration + auctionContractAddr common.Address + client *rpc.Client +} + +func (elc *ExpressLaneClient) SendTransaction(transaction *types.Transaction) containers.PromiseInterface[struct{}] { + return stopwaiter.LaunchPromiseThread[struct{}](elc, func(ctx context.Context) (struct{}, error) { + msg := &ExpressLaneSubmission{ + ChainId: elc.chainId, + Round: CurrentRound(elc.initialRoundTimestamp, elc.roundDuration), + AuctionContractAddress: elc.auctionContractAddr, + Transaction: transaction, + } + signingMsg, err := msg.ToMessageBytes() + if err != nil { + return struct{}{}, err + } + signature, err := signSubmission(signingMsg, elc.privKey) + if err != nil { + return struct{}{}, err + } + msg.Signature = signature + err = elc.client.CallContext(ctx, nil, "timeboost_newExpressLaneSubmission", msg) + return struct{}{}, err + }) +} + +func signSubmission(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) + sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) + if err != nil { + return nil, err + } + sig[64] += 27 + return sig, nil +} From 4ad6fcb10556f3e5e73f925e930894fd16856757 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 12:04:52 -0500 Subject: [PATCH 024/109] adding in and building --- execution/gethexec/forwarder.go | 2 +- timeboost/bids_test.go | 434 ++++++++++++++++---------------- 2 files changed, 218 insertions(+), 218 deletions(-) diff --git a/execution/gethexec/forwarder.go b/execution/gethexec/forwarder.go index e35d0b759f..9a6f41f90c 100644 --- a/execution/gethexec/forwarder.go +++ b/execution/gethexec/forwarder.go @@ -257,7 +257,7 @@ func (f *TxDropper) PublishTransaction(ctx context.Context, tx *types.Transactio return txDropperErr } -func (f *TxDropper) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.JsonExpressLaneSubmission) error { +func (f *TxDropper) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { return txDropperErr } diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index a49be08960..15caeef031 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -1,219 +1,219 @@ package timeboost -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -func TestResolveAuction(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - testSetup := setupAuctionTest(t, ctx) - - // Set up a new auction master instance that can validate bids. - am, err := NewAuctioneer( - testSetup.accounts[0].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, - ) - require.NoError(t, err) - - // Set up two different bidders. - alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) - bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, am) - require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) - require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) - - // Wait until the initial round. - info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) - require.NoError(t, err) - timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) - <-time.After(timeToWait) - time.Sleep(time.Second) // Add a second of wait so that we are within a round. - - // Form two new bids for the round, with Alice being the bigger one. - _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) - require.NoError(t, err) - _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) - require.NoError(t, err) - - // Attempt to resolve the auction before it is closed and receive an error. - require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") - - // Await resolution. - t.Log(time.Now()) - ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) - go ticker.start() - <-ticker.c - require.NoError(t, am.resolveAuction(ctx)) - // Expect Alice to have become the next express lane controller. - - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: 0, - End: nil, - } - it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) - require.NoError(t, err) - aliceWon := false - for it.Next() { - if it.Event.FirstPriceBidder == alice.txOpts.From { - aliceWon = true - } - } - require.True(t, aliceWon) -} - -func TestReceiveBid_OK(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - testSetup := setupAuctionTest(t, ctx) - - // Set up a new auction master instance that can validate bids. - am, err := NewAuctioneer( - testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, - ) - require.NoError(t, err) - - // Make a deposit as a bidder into the contract. - bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) - require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) - - // Form a new bid with an amount. - newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) - require.NoError(t, err) - - // Check the bid passes validation. - _, err = am.validateBid(newBid) - require.NoError(t, err) -} - -func TestTopTwoBids(t *testing.T) { - tests := []struct { - name string - bids map[common.Address]*validatedBid - expected *auctionResult - }{ - { - name: "Single Bid", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: nil, - }, - }, - { - name: "Two Bids with Different Amounts", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, - secondPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, - }, - }, - { - name: "Two Bids with Same Amount and Different Hashes", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - }, - }, - { - name: "More Than Two Bids, All Unique Amounts", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, - }, - }, - { - name: "More Than Two Bids, Some with Same Amounts", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, - common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, - }, - }, - { - name: "More Than Two Bids, Tied for Second Place", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, - }, - }, - { - name: "All Bids with the Same Amount", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, - secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - }, - }, - { - name: "No Bids", - bids: nil, - expected: &auctionResult{firstPlace: nil, secondPlace: nil}, - }, - { - name: "Identical Bids", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bc := &bidCache{ - bidsByExpressLaneControllerAddr: tt.bids, - } - result := bc.topTwoBids() - if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { - t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) - } - if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { - t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) - } - if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { - t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) - } - }) - } -} +// import ( +// "context" +// "math/big" +// "testing" +// "time" + +// "github.com/ethereum/go-ethereum/accounts/abi/bind" +// "github.com/ethereum/go-ethereum/common" +// "github.com/stretchr/testify/require" +// ) + +// func TestResolveAuction(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() + +// testSetup := setupAuctionTest(t, ctx) + +// // Set up a new auction master instance that can validate bids. +// am, err := NewAuctioneer( +// testSetup.accounts[0].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, +// ) +// require.NoError(t, err) + +// // Set up two different bidders. +// alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) +// bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, am) +// require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) +// require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) + +// // Wait until the initial round. +// info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) +// require.NoError(t, err) +// timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) +// <-time.After(timeToWait) +// time.Sleep(time.Second) // Add a second of wait so that we are within a round. + +// // Form two new bids for the round, with Alice being the bigger one. +// _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) +// require.NoError(t, err) +// _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) +// require.NoError(t, err) + +// // Attempt to resolve the auction before it is closed and receive an error. +// require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") + +// // Await resolution. +// t.Log(time.Now()) +// ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) +// go ticker.start() +// <-ticker.c +// require.NoError(t, am.resolveAuction(ctx)) +// // Expect Alice to have become the next express lane controller. + +// filterOpts := &bind.FilterOpts{ +// Context: ctx, +// Start: 0, +// End: nil, +// } +// it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) +// require.NoError(t, err) +// aliceWon := false +// for it.Next() { +// if it.Event.FirstPriceBidder == alice.txOpts.From { +// aliceWon = true +// } +// } +// require.True(t, aliceWon) +// } + +// func TestReceiveBid_OK(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() + +// testSetup := setupAuctionTest(t, ctx) + +// // Set up a new auction master instance that can validate bids. +// am, err := NewAuctioneer( +// testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, +// ) +// require.NoError(t, err) + +// // Make a deposit as a bidder into the contract. +// bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) +// require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) + +// // Form a new bid with an amount. +// newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) +// require.NoError(t, err) + +// // Check the bid passes validation. +// _, err = am.validateBid(newBid) +// require.NoError(t, err) +// } + +// func TestTopTwoBids(t *testing.T) { +// tests := []struct { +// name string +// bids map[common.Address]*validatedBid +// expected *auctionResult +// }{ +// { +// name: "Single Bid", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: nil, +// }, +// }, +// { +// name: "Two Bids with Different Amounts", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, +// }, +// }, +// { +// name: "Two Bids with Same Amount and Different Hashes", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// }, +// }, +// { +// name: "More Than Two Bids, All Unique Amounts", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, +// }, +// }, +// { +// name: "More Than Two Bids, Some with Same Amounts", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, +// common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, +// }, +// }, +// { +// name: "More Than Two Bids, Tied for Second Place", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, +// }, +// }, +// { +// name: "All Bids with the Same Amount", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// }, +// { +// name: "No Bids", +// bids: nil, +// expected: &auctionResult{firstPlace: nil, secondPlace: nil}, +// }, +// { +// name: "Identical Bids", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// bc := &bidCache{ +// bidsByExpressLaneControllerAddr: tt.bids, +// } +// result := bc.topTwoBids() +// if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { +// t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) +// } +// if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { +// t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) +// } +// if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { +// t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) +// } +// }) +// } +// } From ca24d8b988c1861e600af29816ad3002eeeeecc3 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 12:36:08 -0500 Subject: [PATCH 025/109] auctioneer binary config --- cmd/autonomous-auctioneer/config.go | 148 +++++++++++++++++++++ execution/gethexec/express_lane_service.go | 16 +-- system_tests/seqfeed_test.go | 56 +++++--- timeboost/auctioneer_test.go | 14 +- timeboost/express_lane_client.go | 24 +++- 5 files changed, 219 insertions(+), 39 deletions(-) create mode 100644 cmd/autonomous-auctioneer/config.go diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go new file mode 100644 index 0000000000..beaacffb07 --- /dev/null +++ b/cmd/autonomous-auctioneer/config.go @@ -0,0 +1,148 @@ +package main + +import ( + "fmt" + + "reflect" + "time" + + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/cmd/conf" + "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/timeboost" + "github.com/offchainlabs/nitro/util/colors" + flag "github.com/spf13/pflag" +) + +type AuctioneerConfig struct { + Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` + LogLevel string `koanf:"log-level" reload:"hot"` + LogType string `koanf:"log-type" reload:"hot"` + FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` + HTTP genericconf.HTTPConfig `koanf:"http"` + WS genericconf.WSConfig `koanf:"ws"` + IPC genericconf.IPCConfig `koanf:"ipc"` + Metrics bool `koanf:"metrics"` + MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` + PProf bool `koanf:"pprof"` + PprofCfg genericconf.PProf `koanf:"pprof-cfg"` +} + +var HTTPConfigDefault = genericconf.HTTPConfig{ + Addr: "", + Port: genericconf.HTTPConfigDefault.Port, + API: []string{}, + RPCPrefix: genericconf.HTTPConfigDefault.RPCPrefix, + CORSDomain: genericconf.HTTPConfigDefault.CORSDomain, + VHosts: genericconf.HTTPConfigDefault.VHosts, + ServerTimeouts: genericconf.HTTPConfigDefault.ServerTimeouts, +} + +var WSConfigDefault = genericconf.WSConfig{ + Addr: "", + Port: genericconf.WSConfigDefault.Port, + API: []string{}, + RPCPrefix: genericconf.WSConfigDefault.RPCPrefix, + Origins: genericconf.WSConfigDefault.Origins, + ExposeAll: genericconf.WSConfigDefault.ExposeAll, +} + +var IPCConfigDefault = genericconf.IPCConfig{ + Path: "", +} + +var AuctioneerConfigDefault = AuctioneerConfig{ + Conf: genericconf.ConfConfigDefault, + LogLevel: "INFO", + LogType: "plaintext", + HTTP: HTTPConfigDefault, + WS: WSConfigDefault, + IPC: IPCConfigDefault, + Metrics: false, + MetricsServer: genericconf.MetricsServerConfigDefault, + PProf: false, + PprofCfg: genericconf.PProfDefault, +} + +func ValidationNodeConfigAddOptions(f *flag.FlagSet) { + genericconf.ConfConfigAddOptions("conf", f) + f.String("log-level", AuctioneerConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") + f.String("log-type", AuctioneerConfigDefault.LogType, "log type (plaintext or json)") + genericconf.FileLoggingConfigAddOptions("file-logging", f) + conf.PersistentConfigAddOptions("persistent", f) + genericconf.HTTPConfigAddOptions("http", f) + genericconf.WSConfigAddOptions("ws", f) + genericconf.IPCConfigAddOptions("ipc", f) + genericconf.AuthRPCConfigAddOptions("auth", f) + f.Bool("metrics", AuctioneerConfigDefault.Metrics, "enable metrics") + genericconf.MetricsServerAddOptions("metrics-server", f) + f.Bool("pprof", AuctioneerConfigDefault.PProf, "enable pprof") + genericconf.PProfAddOptions("pprof-cfg", f) +} + +func (c *AuctioneerConfig) ShallowClone() *AuctioneerConfig { + config := &AuctioneerConfig{} + *config = *c + return config +} + +func (c *AuctioneerConfig) CanReload(new *AuctioneerConfig) error { + var check func(node, other reflect.Value, path string) + var err error + + check = func(node, value reflect.Value, path string) { + if node.Kind() != reflect.Struct { + return + } + + for i := 0; i < node.NumField(); i++ { + fieldTy := node.Type().Field(i) + if !fieldTy.IsExported() { + continue + } + hot := fieldTy.Tag.Get("reload") == "hot" + dot := path + "." + fieldTy.Name + + first := node.Field(i).Interface() + other := value.Field(i).Interface() + + if !hot && !reflect.DeepEqual(first, other) { + err = fmt.Errorf("illegal change to %v%v%v", colors.Red, dot, colors.Clear) + } else { + check(node.Field(i), value.Field(i), dot) + } + } + } + + check(reflect.ValueOf(c).Elem(), reflect.ValueOf(new).Elem(), "config") + return err +} + +func (c *AuctioneerConfig) GetReloadInterval() time.Duration { + return c.Conf.ReloadInterval +} + +func (c *AuctioneerConfig) Validate() error { + return nil +} + +var DefaultAuctioneerStackConfig = node.Config{ + DataDir: node.DefaultDataDir(), + HTTPPort: node.DefaultHTTPPort, + AuthAddr: node.DefaultAuthHost, + AuthPort: node.DefaultAuthPort, + AuthVirtualHosts: node.DefaultAuthVhosts, + HTTPModules: []string{timeboost.AuctioneerNamespace}, + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: node.DefaultWSPort, + WSModules: []string{timeboost.AuctioneerNamespace}, + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDiscovery: true, + NoDial: true, + }, +} diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index aac0d36d5f..267ee19639 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/log" @@ -173,8 +172,6 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu if msg.Round != currentRound { return errors.Wrapf(timeboost.ErrBadRoundNumber, "express lane tx round %d does not match current round %d", msg.Round, currentRound) } - es.Lock() - defer es.Unlock() // Reconstruct the message being signed over and recover the sender address. signingMessage, err := msg.ToMessageBytes() if err != nil { @@ -198,19 +195,10 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu return timeboost.ErrWrongSignature } sender := crypto.PubkeyToAddress(*pubkey) + es.Lock() + defer es.Unlock() if sender != es.control.controller { return timeboost.ErrNotExpressLaneController } return nil } - -// unwrapExpressLaneTx extracts the inner "wrapped" transaction from the data field of an express lane transaction. -func unwrapExpressLaneTx(tx *types.Transaction) (*types.Transaction, error) { - encodedInnerTx := tx.Data() - fmt.Printf("Inner in decoding: %#x\n", encodedInnerTx) - innerTx := &types.Transaction{} - if err := innerTx.UnmarshalBinary(encodedInnerTx); err != nil { - return nil, fmt.Errorf("failed to decode inner transaction: %w", err) - } - return innerTx, nil -} diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index fc21f37329..0f6d79944b 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -17,6 +17,9 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l1pricing" @@ -182,7 +185,28 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { auctionContractOpts := builderSeq.L1Info.GetDefaultTransactOpts("AuctionContract", ctx) chainId, err := l1client.ChainID(ctx) Require(t, err) - auctioneer, err := timeboost.NewAuctioneer(&auctionContractOpts, []uint64{chainId.Uint64()}, builderSeq.L1.Client, auctionAddr, auctionContract) + + // Set up the auctioneer RPC service. + stackConf := node.Config{ + DataDir: "", // ephemeral. + HTTPPort: 9372, + HTTPModules: []string{timeboost.AuctioneerNamespace}, + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: 9373, + WSModules: []string{timeboost.AuctioneerNamespace}, + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDial: true, + NoDiscovery: true, + }, + } + stack, err := node.New(&stackConf) + Require(t, err) + auctioneer, err := timeboost.NewAuctioneer( + &auctionContractOpts, []uint64{chainId.Uint64()}, stack, builderSeq.L1.Client, auctionContract, + ) Require(t, err) go auctioneer.Start(ctx) @@ -258,7 +282,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) time.Sleep(waitTime) - currRound := timeboost.currentRound(time.Unix(int64(info.OffsetTimestamp), 0), roundDuration) + currRound := timeboost.CurrentRound(time.Unix(int64(info.OffsetTimestamp), 0), roundDuration) t.Log("curr round", currRound) if currRound != winnerRound { now = time.Now() @@ -285,12 +309,23 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Log("Now submitting txs to sequencer") + // Prepare a client that can submit txs to the sequencer via the express lane. + seqDial, err := rpc.Dial("http://localhost:9567") + Require(t, err) + expressLaneClient := timeboost.NewExpressLaneClient( + bobPriv, + chainId.Uint64(), + time.Unix(int64(info.OffsetTimestamp), 0), + roundDuration, + auctionAddr, + seqDial, + ) + // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's // txs end up getting delayed by 200ms as she is not the express lane controller. // In the end, Bob's txs should be ordered before Alice's during the round. var wg sync.WaitGroup wg.Add(2) - expressLaneAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) go func(w *sync.WaitGroup) { defer w.Done() @@ -299,20 +334,11 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { }(&wg) bobBoostableTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) - bobBoostableTxData, err := bobBoostableTx.MarshalBinary() - Require(t, err) - t.Logf("Typed transaction inner is %#x", bobBoostableTxData) - txData := &types.DynamicFeeTx{ - To: &expressLaneAddr, - GasTipCap: new(big.Int).SetUint64(bobBid.Round), - Nonce: 0, - Data: bobBoostableTxData, - } - bobTx := seqInfo.SignTxAs("Bob", txData) go func(w *sync.WaitGroup) { defer w.Done() time.Sleep(time.Millisecond * 10) - err = seqClient.SendTransaction(ctx, bobTx) + res := expressLaneClient.SendTransaction(ctx, bobBoostableTx) + _, err = res.Await(ctx) Require(t, err) }(&wg) wg.Wait() @@ -341,7 +367,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { } txes := block.Transactions() indexA := findTransactionIndex(txes, aliceTx.Hash()) - indexB := findTransactionIndex(txes, bobTx.Hash()) + indexB := findTransactionIndex(txes, bobBoostableTx.Hash()) if indexA == -1 || indexB == -1 { t.Fatal("Did not find txs in block") } diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 4c10e88565..1e6da9b2d4 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -21,25 +21,25 @@ func TestAuctioneer_validateBid(t *testing.T) { auctionClosed bool }{ { - name: "Nil bid", + name: "nil bid", bid: nil, expectedErr: ErrMalformedData, errMsg: "nil bid", }, { - name: "Empty bidder address", + name: "empty bidder address", bid: &Bid{}, expectedErr: ErrMalformedData, errMsg: "empty bidder address", }, { - name: "Empty express lane controller address", + name: "empty express lane controller address", bid: &Bid{Bidder: common.Address{'a'}}, expectedErr: ErrMalformedData, errMsg: "empty express lane controller address", }, { - name: "Incorrect chain id", + name: "incorrect chain id", bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, @@ -48,7 +48,7 @@ func TestAuctioneer_validateBid(t *testing.T) { errMsg: "can not auction for chain id: 0", }, { - name: "Incorrect round", + name: "incorrect round", bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, @@ -58,7 +58,7 @@ func TestAuctioneer_validateBid(t *testing.T) { errMsg: "wanted 1, got 0", }, { - name: "Auction is closed", + name: "auction is closed", bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, @@ -70,7 +70,7 @@ func TestAuctioneer_validateBid(t *testing.T) { auctionClosed: true, }, { - name: "Lower than reserved price", + name: "lower than reserved price", bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go index 3b6eeb37e7..d2fe810742 100644 --- a/timeboost/express_lane_client.go +++ b/timeboost/express_lane_client.go @@ -26,8 +26,26 @@ type ExpressLaneClient struct { client *rpc.Client } -func (elc *ExpressLaneClient) SendTransaction(transaction *types.Transaction) containers.PromiseInterface[struct{}] { - return stopwaiter.LaunchPromiseThread[struct{}](elc, func(ctx context.Context) (struct{}, error) { +func NewExpressLaneClient( + privKey *ecdsa.PrivateKey, + chainId uint64, + initialRoundTimestamp time.Time, + roundDuration time.Duration, + auctionContractAddr common.Address, + client *rpc.Client, +) *ExpressLaneClient { + return &ExpressLaneClient{ + privKey: privKey, + chainId: chainId, + initialRoundTimestamp: initialRoundTimestamp, + roundDuration: roundDuration, + auctionContractAddr: auctionContractAddr, + client: client, + } +} + +func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) containers.PromiseInterface[struct{}] { + return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { msg := &ExpressLaneSubmission{ ChainId: elc.chainId, Round: CurrentRound(elc.initialRoundTimestamp, elc.roundDuration), @@ -43,7 +61,7 @@ func (elc *ExpressLaneClient) SendTransaction(transaction *types.Transaction) co return struct{}{}, err } msg.Signature = signature - err = elc.client.CallContext(ctx, nil, "timeboost_newExpressLaneSubmission", msg) + err = elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) return struct{}{}, err }) } From 083caf02b263ad905dddb208fea1543136dd1cd2 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 12:42:27 -0500 Subject: [PATCH 026/109] add back domain --- contracts | 2 +- timeboost/auctioneer_test.go | 3 ++- timeboost/bidder_client.go | 3 ++- timeboost/bids.go | 10 +++++----- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts b/contracts index 8f434d48cc..dca59ed7ba 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 8f434d48ccb5e8ba03f7b9108467702c56348b0a +Subproject commit dca59ed7ba4aef52211b771c11d6bfd9fd144d8f diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index b35e017919..1a502f9e7a 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -3,6 +3,7 @@ package timeboost import ( "context" "crypto/ecdsa" + "fmt" "math/big" "testing" "time" @@ -124,7 +125,7 @@ func TestAuctioneer_validateBid(t *testing.T) { } func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { - prefixedData := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), data...)) + prefixedData := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(data))), data...)) signature, err := crypto.Sign(prefixedData, privateKey) if err != nil { return nil, err diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index de3f97bb50..ad081e30cd 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -3,6 +3,7 @@ package timeboost import ( "context" "crypto/ecdsa" + "fmt" "math/big" "time" @@ -134,7 +135,7 @@ func (bd *BidderClient) Bid( } func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { - prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), message...)) + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) if err != nil { return nil, err diff --git a/timeboost/bids.go b/timeboost/bids.go index f934796964..822d6c64f8 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -129,7 +129,7 @@ func hashBid(bid *validatedBid) string { } func verifySignature(pubkey *ecdsa.PublicKey, message []byte, sig []byte) bool { - prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), message...)) + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) return secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sig[:len(sig)-1]) } @@ -147,11 +147,11 @@ func encodeBidValues(domainValue []byte, chainId uint64, auctionContractAddress // Encode uint256 values - each occupies 32 bytes buf.Write(domainValue) - roundBuf := make([]byte, 8) - binary.BigEndian.PutUint64(roundBuf, chainId) - buf.Write(roundBuf) + chainIdBuf := make([]byte, 8) + binary.BigEndian.PutUint64(chainIdBuf, chainId) + buf.Write(chainIdBuf) buf.Write(auctionContractAddress[:]) - roundBuf = make([]byte, 8) + roundBuf := make([]byte, 8) binary.BigEndian.PutUint64(roundBuf, round) buf.Write(roundBuf) buf.Write(padBigInt(amount)) From 0c6e15ba5f2084b58381c4c0be011b05d536d6f1 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 12:50:12 -0500 Subject: [PATCH 027/109] do not use 112 --- timeboost/auctioneer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index b65d6a0002..0d05cba08c 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -2,6 +2,7 @@ package timeboost import ( "context" + "fmt" "math/big" "sync" "time" @@ -284,7 +285,7 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") } // Recover the public key. - prefixed := crypto.Keccak256(append([]byte("\x19Ethereum Signed Message:\n112"), packedBidBytes...)) + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(packedBidBytes))), packedBidBytes...)) sigItem := make([]byte, len(bid.Signature)) copy(sigItem, bid.Signature) if sigItem[len(sigItem)-1] >= 27 { From 98f8dcb93d0b4c57388358bcab112c7323f0e2ed Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 29 Jul 2024 13:11:43 -0500 Subject: [PATCH 028/109] fix test --- timeboost/bidder_client.go | 2 + timeboost/bids_test.go | 75 ++++++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index ad081e30cd..6c8395954f 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -123,6 +123,8 @@ func (bd *BidderClient) Bid( if err != nil { return nil, err } + fmt.Println("Packed bid bytes locally") + fmt.Printf("%#x\n", packedBidBytes) sig, err := sign(packedBidBytes, bd.privKey) if err != nil { return nil, err diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 15caeef031..55baf0e418 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -1,5 +1,18 @@ package timeboost +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + // import ( // "context" // "math/big" @@ -69,30 +82,54 @@ package timeboost // require.True(t, aliceWon) // } -// func TestReceiveBid_OK(t *testing.T) { -// ctx, cancel := context.WithCancel(context.Background()) -// defer cancel() +func TestReceiveBid_OK(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() -// testSetup := setupAuctionTest(t, ctx) + testSetup := setupAuctionTest(t, ctx) -// // Set up a new auction master instance that can validate bids. -// am, err := NewAuctioneer( -// testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, -// ) -// require.NoError(t, err) + // Set up a new auction master instance that can validate bids. + // Set up the auctioneer RPC service. + stackConf := node.Config{ + DataDir: "", // ephemeral. + HTTPPort: 9372, + HTTPModules: []string{AuctioneerNamespace}, + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: 9373, + WSModules: []string{AuctioneerNamespace}, + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDial: true, + NoDiscovery: true, + }, + } + stack, err := node.New(&stackConf) + require.NoError(t, err) + am, err := NewAuctioneer( + testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, stack, testSetup.backend.Client(), testSetup.expressLaneAuction, + ) + require.NoError(t, err) -// // Make a deposit as a bidder into the contract. -// bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) -// require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) + // Make a deposit as a bidder into the contract. + bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) + require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) -// // Form a new bid with an amount. -// newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) -// require.NoError(t, err) + // Form a new bid with an amount. + newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) + require.NoError(t, err) -// // Check the bid passes validation. -// _, err = am.validateBid(newBid) -// require.NoError(t, err) -// } + rawBytes, err := testSetup.expressLaneAuction.GetBidBytes(&bind.CallOpts{}, newBid.Round, newBid.Amount, newBid.ExpressLaneController) + require.NoError(t, err) + fmt.Println("Onchain bytes") + fmt.Printf("%#x\n", rawBytes) + + // Check the bid passes validation. + _, err = am.validateBid(newBid) + require.NoError(t, err) + t.Fatal(1) +} // func TestTopTwoBids(t *testing.T) { // tests := []struct { From 93ba2cf13a00d822750fd8c60662be2113da577d Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 30 Jul 2024 10:16:43 -0500 Subject: [PATCH 029/109] wire up the forwarder --- execution/gethexec/forwarder.go | 20 +++++++++++-- execution/gethexec/sequencer.go | 3 ++ execution/gethexec/tx_pre_checker.go | 18 +++++++++++- system_tests/seqfeed_test.go | 6 ++-- timeboost/auctioneer_api.go | 25 ++++++++++++---- timeboost/bidder_client.go | 2 -- timeboost/bids.go | 4 +-- timeboost/bids_test.go | 8 ------ timeboost/express_lane_client.go | 43 ++++++++++++++-------------- 9 files changed, 85 insertions(+), 44 deletions(-) diff --git a/execution/gethexec/forwarder.go b/execution/gethexec/forwarder.go index 9a6f41f90c..962a4d0fff 100644 --- a/execution/gethexec/forwarder.go +++ b/execution/gethexec/forwarder.go @@ -157,8 +157,24 @@ func (f *TxForwarder) PublishTransaction(inctx context.Context, tx *types.Transa return errors.New("failed to publish transaction to any of the forwarding targets") } -func (f *TxForwarder) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { - return nil +func (f *TxForwarder) PublishExpressLaneTransaction(inctx context.Context, msg *timeboost.ExpressLaneSubmission) error { + if !f.enabled.Load() { + return ErrNoSequencer + } + ctx, cancelFunc := f.ctxWithTimeout() + defer cancelFunc() + for pos, rpcClient := range f.rpcClients { + err := sendExpressLaneTransactionRPC(ctx, rpcClient, msg) + if err == nil || !f.tryNewForwarderErrors.MatchString(err.Error()) { + return err + } + log.Warn("error forwarding transaction to a backup target", "target", f.targets[pos], "err", err) + } + return errors.New("failed to publish transaction to any of the forwarding targets") +} + +func sendExpressLaneTransactionRPC(ctx context.Context, rpcClient *rpc.Client, msg *timeboost.ExpressLaneSubmission) error { + return rpcClient.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg.ToJson()) } const cacheUpstreamHealth = 2 * time.Second diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index ba849d988f..c64b64aec5 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -566,9 +566,12 @@ func (s *Sequencer) publishTransactionImpl(parentCtx context.Context, tx *types. } func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { + log.Info("Got the express lane tx in sequencer") if err := s.expressLaneService.validateExpressLaneTx(msg); err != nil { + log.Error("Express lane tx validation failed", "err", err) return err } + log.Info("Yay the tx verification passed in sequencer") return s.publishTransactionImpl(ctx, msg.Transaction, nil, false /* no delay, as this is an express lane tx */) } diff --git a/execution/gethexec/tx_pre_checker.go b/execution/gethexec/tx_pre_checker.go index 5f3bed82a9..1820b3c63e 100644 --- a/execution/gethexec/tx_pre_checker.go +++ b/execution/gethexec/tx_pre_checker.go @@ -224,5 +224,21 @@ func (c *TxPreChecker) PublishTransaction(ctx context.Context, tx *types.Transac } func (c *TxPreChecker) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { - return nil + if msg == nil || msg.Transaction == nil { + return timeboost.ErrMalformedData + } + block := c.bc.CurrentBlock() + statedb, err := c.bc.StateAt(block.Root) + if err != nil { + return err + } + arbos, err := arbosState.OpenSystemArbosState(statedb, nil, true) + if err != nil { + return err + } + err = PreCheckTx(c.bc, c.bc.Config(), block, statedb, arbos, msg.Transaction, msg.Options, c.config()) + if err != nil { + return err + } + return c.TransactionPublisher.PublishExpressLaneTransaction(ctx, msg) } diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 0f6d79944b..ad81e44207 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -320,6 +320,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { auctionAddr, seqDial, ) + expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's // txs end up getting delayed by 200ms as she is not the express lane controller. @@ -337,12 +338,13 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { go func(w *sync.WaitGroup) { defer w.Done() time.Sleep(time.Millisecond * 10) - res := expressLaneClient.SendTransaction(ctx, bobBoostableTx) - _, err = res.Await(ctx) + err = expressLaneClient.SendTransaction(ctx, bobBoostableTx) Require(t, err) }(&wg) wg.Wait() + time.Sleep(time.Minute) + // After round is done, verify that Bob beats Alice in the final sequence. aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) Require(t, err) diff --git a/timeboost/auctioneer_api.go b/timeboost/auctioneer_api.go index 8938dd50be..f5523ccfcd 100644 --- a/timeboost/auctioneer_api.go +++ b/timeboost/auctioneer_api.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "math/big" + "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -25,11 +26,12 @@ type JsonBid struct { } type JsonExpressLaneSubmission struct { - ChainId uint64 `json:"chainId"` - Round uint64 `json:"round"` - AuctionContractAddress common.Address `json:"auctionContractAddress"` - Transaction *types.Transaction `json:"transaction"` - Signature string `json:"signature"` + ChainId uint64 `json:"chainId"` + Round uint64 `json:"round"` + AuctionContractAddress common.Address `json:"auctionContractAddress"` + Transaction *types.Transaction `json:"transaction"` + Options *arbitrum_types.ConditionalOptions `json:"options"` + Signature string `json:"signature"` } type ExpressLaneSubmission struct { @@ -37,6 +39,7 @@ type ExpressLaneSubmission struct { Round uint64 AuctionContractAddress common.Address Transaction *types.Transaction + Options *arbitrum_types.ConditionalOptions `json:"options"` Signature []byte } @@ -46,10 +49,22 @@ func JsonSubmissionToGo(submission *JsonExpressLaneSubmission) *ExpressLaneSubmi Round: submission.Round, AuctionContractAddress: submission.AuctionContractAddress, Transaction: submission.Transaction, + Options: submission.Options, Signature: common.Hex2Bytes(submission.Signature), } } +func (els *ExpressLaneSubmission) ToJson() *JsonExpressLaneSubmission { + return &JsonExpressLaneSubmission{ + ChainId: els.ChainId, + Round: els.Round, + AuctionContractAddress: els.AuctionContractAddress, + Transaction: els.Transaction, + Options: els.Options, + Signature: common.Bytes2Hex(els.Signature), + } +} + func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { return encodeExpressLaneSubmission( domainValue, diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 6c8395954f..ad081e30cd 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -123,8 +123,6 @@ func (bd *BidderClient) Bid( if err != nil { return nil, err } - fmt.Println("Packed bid bytes locally") - fmt.Printf("%#x\n", packedBidBytes) sig, err := sign(packedBidBytes, bd.privKey) if err != nil { return nil, err diff --git a/timeboost/bids.go b/timeboost/bids.go index d9fb9b57e8..57c26ba6ce 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -149,9 +149,7 @@ func encodeBidValues(domainValue []byte, chainId uint64, auctionContractAddress // Encode uint256 values - each occupies 32 bytes buf.Write(domainValue) - chainIdBuf := make([]byte, 8) - binary.BigEndian.PutUint64(chainIdBuf, chainId) - buf.Write(chainIdBuf) + buf.Write(padBigInt(new(big.Int).SetUint64(chainId))) buf.Write(auctionContractAddress[:]) roundBuf := make([]byte, 8) binary.BigEndian.PutUint64(roundBuf, round) diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 55baf0e418..50230fcc4b 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -2,11 +2,9 @@ package timeboost import ( "context" - "fmt" "math/big" "testing" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" @@ -120,15 +118,9 @@ func TestReceiveBid_OK(t *testing.T) { newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) require.NoError(t, err) - rawBytes, err := testSetup.expressLaneAuction.GetBidBytes(&bind.CallOpts{}, newBid.Round, newBid.Amount, newBid.ExpressLaneController) - require.NoError(t, err) - fmt.Println("Onchain bytes") - fmt.Printf("%#x\n", rawBytes) - // Check the bid passes validation. _, err = am.validateBid(newBid) require.NoError(t, err) - t.Fatal(1) } // func TestTopTwoBids(t *testing.T) { diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go index d2fe810742..eeb74d39be 100644 --- a/timeboost/express_lane_client.go +++ b/timeboost/express_lane_client.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" ) @@ -44,26 +43,28 @@ func NewExpressLaneClient( } } -func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) containers.PromiseInterface[struct{}] { - return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { - msg := &ExpressLaneSubmission{ - ChainId: elc.chainId, - Round: CurrentRound(elc.initialRoundTimestamp, elc.roundDuration), - AuctionContractAddress: elc.auctionContractAddr, - Transaction: transaction, - } - signingMsg, err := msg.ToMessageBytes() - if err != nil { - return struct{}{}, err - } - signature, err := signSubmission(signingMsg, elc.privKey) - if err != nil { - return struct{}{}, err - } - msg.Signature = signature - err = elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) - return struct{}{}, err - }) +func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) error { + // return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { + msg := &JsonExpressLaneSubmission{ + ChainId: elc.chainId, + Round: CurrentRound(elc.initialRoundTimestamp, elc.roundDuration), + AuctionContractAddress: elc.auctionContractAddr, + Transaction: transaction, + Signature: "00", + } + msgGo := JsonSubmissionToGo(msg) + signingMsg, err := msgGo.ToMessageBytes() + if err != nil { + return err + } + signature, err := signSubmission(signingMsg, elc.privKey) + if err != nil { + return err + } + msg.Signature = fmt.Sprintf("%x", signature) + fmt.Println("Right here before we send the express lane tx") + err = elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) + return err } func signSubmission(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { From 8f037de817498f142ac3a1583e9f1c083965cf58 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 30 Jul 2024 10:45:18 -0500 Subject: [PATCH 030/109] passing --- execution/gethexec/sequencer.go | 3 --- system_tests/seqfeed_test.go | 9 ++++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index c64b64aec5..ba849d988f 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -566,12 +566,9 @@ func (s *Sequencer) publishTransactionImpl(parentCtx context.Context, tx *types. } func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { - log.Info("Got the express lane tx in sequencer") if err := s.expressLaneService.validateExpressLaneTx(msg); err != nil { - log.Error("Express lane tx validation failed", "err", err) return err } - log.Info("Yay the tx verification passed in sequencer") return s.publishTransactionImpl(ctx, msg.Transaction, nil, false /* no delay, as this is an express lane tx */) } diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index ad81e44207..20de7260ac 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -310,11 +310,13 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Log("Now submitting txs to sequencer") // Prepare a client that can submit txs to the sequencer via the express lane. - seqDial, err := rpc.Dial("http://localhost:9567") + seqDial, err := rpc.Dial(seqNode.Stack.HTTPEndpoint()) + Require(t, err) + seqChainId, err := seqClient.ChainID(ctx) Require(t, err) expressLaneClient := timeboost.NewExpressLaneClient( bobPriv, - chainId.Uint64(), + seqChainId.Uint64(), time.Unix(int64(info.OffsetTimestamp), 0), roundDuration, auctionAddr, @@ -343,8 +345,6 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { }(&wg) wg.Wait() - time.Sleep(time.Minute) - // After round is done, verify that Bob beats Alice in the final sequence. aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) Require(t, err) @@ -377,7 +377,6 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Fatal("Bob should have been sequenced before Alice with express lane") } } - time.Sleep(time.Hour) } func awaitAuctionResolved( From 396c6e93ae555c65a8e9db6efbbd949a3335cbb0 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 30 Jul 2024 13:07:26 -0500 Subject: [PATCH 031/109] auction is now happening on L2 --- execution/gethexec/express_lane_service.go | 119 ++++++++------- execution/gethexec/sequencer.go | 79 +++++----- system_tests/common_test.go | 101 ------------- system_tests/seqfeed_test.go | 160 +++++++++++++++------ 4 files changed, 211 insertions(+), 248 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 267ee19639..b2eaa2a42f 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -3,22 +3,37 @@ package gethexec import ( "context" "fmt" + "slices" "sync" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" ) +var auctionResolvedEvent common.Hash + +func init() { + auctionAbi, err := express_lane_auctiongen.ExpressLaneAuctionMetaData.GetAbi() + if err != nil { + panic(err) + } + auctionResolvedEventData, ok := auctionAbi.Events["AuctionResolved"] + if !ok { + panic("RollupCore ABI missing AssertionCreated event") + } + auctionResolvedEvent = auctionResolvedEventData.ID +} + type expressLaneControl struct { round uint64 sequence uint64 @@ -28,41 +43,34 @@ type expressLaneControl struct { type expressLaneService struct { stopwaiter.StopWaiter sync.RWMutex - client arbutil.L1Interface control expressLaneControl auctionContractAddr common.Address auctionContract *express_lane_auctiongen.ExpressLaneAuction initialTimestamp time.Time roundDuration time.Duration chainConfig *params.ChainConfig + logs chan []*types.Log + bc *core.BlockChain } func newExpressLaneService( - client arbutil.L1Interface, auctionContractAddr common.Address, - chainConfig *params.ChainConfig, + initialRoundTimestamp uint64, + roundDuration time.Duration, + bc *core.BlockChain, ) (*expressLaneService, error) { - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) - if err != nil { - return nil, err - } - roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) - if err != nil { - return nil, err - } - initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) - roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + chainConfig := bc.Config() return &expressLaneService{ - auctionContract: auctionContract, - client: client, + bc: bc, chainConfig: chainConfig, - initialTimestamp: initialTimestamp, + initialTimestamp: time.Unix(int64(initialRoundTimestamp), 0), control: expressLaneControl{ controller: common.Address{}, round: 0, }, auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, + logs: make(chan []*types.Log, 10_000), }, nil } @@ -93,52 +101,22 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) es.LaunchThread(func(ctx context.Context) { log.Info("Monitoring express lane auction contract") - // Monitor for auction resolutions from the auction manager smart contract - // and set the express lane controller for the upcoming round accordingly. - latestBlock, err := es.client.HeaderByNumber(ctx, nil) - if err != nil { - log.Crit("Could not get latest header", "err", err) - } - fromBlock := latestBlock.Number.Uint64() - ticker := time.NewTicker(time.Millisecond * 250) - defer ticker.Stop() + sub := es.bc.SubscribeLogsEvent(es.logs) + defer sub.Unsubscribe() for { select { + case evs := <-es.logs: + for _, ev := range evs { + if ev.Address != es.auctionContractAddr { + continue + } + go es.processAuctionContractEvent(ctx, ev) + } case <-ctx.Done(): return - case <-ticker.C: - latestBlock, err := es.client.HeaderByNumber(ctx, nil) - if err != nil { - log.Error("Could not get latest header", "err", err) - continue - } - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: fromBlock, - End: &toBlock, - } - it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) - if err != nil { - log.Error("Could not filter auction resolutions", "error", err) - continue - } - for it.Next() { - log.Info( - "New express lane controller assigned", - "round", it.Event.Round, - "controller", it.Event.FirstPriceExpressLaneController, - ) - es.Lock() - es.control.round = it.Event.Round - es.control.controller = it.Event.FirstPriceExpressLaneController - es.control.sequence = 0 // Sequence resets 0 for the new round. - es.Unlock() - } - fromBlock = toBlock + case err := <-sub.Err(): + log.Error("Subscriber failed", "err", err) + return } } }) @@ -148,6 +126,27 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) } +func (es *expressLaneService) processAuctionContractEvent(_ context.Context, rawLog *types.Log) { + if !slices.Contains(rawLog.Topics, auctionResolvedEvent) { + return + } + ev, err := es.auctionContract.ParseAuctionResolved(*rawLog) + if err != nil { + log.Error("Failed to parse AuctionResolved event", "err", err) + return + } + log.Info( + "New express lane controller assigned", + "round", ev.Round, + "controller", ev.FirstPriceExpressLaneController, + ) + es.Lock() + es.control.round = ev.Round + es.control.controller = ev.FirstPriceExpressLaneController + es.control.sequence = 0 // Sequence resets 0 for the new round. + es.Unlock() +} + func (es *expressLaneService) currentRoundHasController() bool { es.Lock() defer es.Unlock() diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index ba849d988f..ccac335139 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -88,6 +88,8 @@ type TimeboostConfig struct { AuctionContractAddress string `koanf:"auction-contract-address"` ERC20Address string `koanf:"erc20-address"` ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` + RoundDuration time.Duration `koanf:"round-duration"` + InitialRoundTimestamp uint64 `koanf:"initial-round-timestamp"` } var DefaultTimeboostConfig = TimeboostConfig{ @@ -95,6 +97,8 @@ var DefaultTimeboostConfig = TimeboostConfig{ AuctionContractAddress: "", ERC20Address: "", ExpressLaneAdvantage: time.Millisecond * 200, + RoundDuration: time.Second, + InitialRoundTimestamp: uint64(time.Unix(0, 0).Unix()), } func (c *SequencerConfig) Validate() error { @@ -192,6 +196,8 @@ func TimeboostAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultTimeboostConfig.Enable, "enable timeboost based on express lane auctions") f.String(prefix+".auction-contract-address", DefaultTimeboostConfig.AuctionContractAddress, "address of the autonomous auction contract") f.String(prefix+".erc20-address", DefaultTimeboostConfig.ERC20Address, "address of the auction erc20") + f.Uint64(prefix+".initial-round-timestamp", DefaultTimeboostConfig.InitialRoundTimestamp, "initial timestamp for auctions") + f.Duration(prefix+".round-duration", DefaultTimeboostConfig.RoundDuration, "round duration") } type txQueueItem struct { @@ -387,19 +393,6 @@ func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderRead pauseChan: nil, onForwarderSet: make(chan struct{}, 1), } - if config.Timeboost.Enable { - addr := common.HexToAddress(config.Timeboost.AuctionContractAddress) - // TODO: Need to provide an L2 interface instead of an L1 interface. - els, err := newExpressLaneService( - l1Reader.Client(), - addr, - s.execEngine.bc.Config(), - ) - if err != nil { - return nil, err - } - s.expressLaneService = els - } s.nonceFailures = &nonceFailureCache{ containers.NewLruCacheWithOnEvict(config.NonceCacheSize, s.onNonceFailureEvict), func() time.Duration { return configFetcher().NonceFailureCacheExpiry }, @@ -409,20 +402,6 @@ func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderRead return s, nil } -func (s *Sequencer) ExpressLaneAuction() common.Address { - if s.expressLaneService == nil { - return common.Address{} - } - return common.HexToAddress(s.config().Timeboost.AuctionContractAddress) -} - -func (s *Sequencer) ExpressLaneERC20() common.Address { - if s.expressLaneService == nil { - return common.Address{} - } - return common.HexToAddress(s.config().Timeboost.ERC20Address) -} - func (s *Sequencer) onNonceFailureEvict(_ addressAndNonce, failure *nonceFailure) { if failure.revived { return @@ -459,18 +438,6 @@ func ctxWithTimeout(ctx context.Context, timeout time.Duration) (context.Context return context.WithTimeout(ctx, timeout) } -type PublishTxConfig struct { - delayTransaction bool -} - -type TimeboostOpt func(p *PublishTxConfig) - -func WithExpressLane() TimeboostOpt { - return func(p *PublishTxConfig) { - p.delayTransaction = false - } -} - func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { return s.publishTransactionImpl(parentCtx, tx, options, true /* delay tx if express lane is active */) } @@ -520,7 +487,7 @@ func (s *Sequencer) publishTransactionImpl(parentCtx context.Context, tx *types. return err } - if s.config().Timeboost.Enable { + if s.config().Timeboost.Enable && s.expressLaneService != nil { if delay && s.expressLaneService.currentRoundHasController() { time.Sleep(s.config().Timeboost.ExpressLaneAdvantage) } @@ -566,6 +533,12 @@ func (s *Sequencer) publishTransactionImpl(parentCtx context.Context, tx *types. } func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error { + if !s.config().Timeboost.Enable { + return errors.New("timeboost not enabled") + } + if s.expressLaneService == nil { + return errors.New("express lane service not enabled") + } if err := s.expressLaneService.validateExpressLaneTx(msg); err != nil { return err } @@ -1211,10 +1184,6 @@ func (s *Sequencer) Start(ctxIn context.Context) error { }) } - if s.config().Timeboost.Enable { - s.expressLaneService.Start(ctxIn) - } - s.CallIteratively(func(ctx context.Context) time.Duration { nextBlock := time.Now().Add(s.config().MaxBlockSpeed) if s.createBlock(ctx) { @@ -1228,6 +1197,28 @@ func (s *Sequencer) Start(ctxIn context.Context) error { return nil } +// TODO: This is needed because there is no way to currently set the initial timestamp of the express lane service +// without starting the sequencer. This is a temporary solution until we have a better way to handle this. +func (s *Sequencer) StartExpressLaneService( + ctx context.Context, initialTimestamp uint64, auctionContractAddr common.Address, +) { + if s.config().Timeboost.Enable { + return + } + els, err := newExpressLaneService( + auctionContractAddr, + initialTimestamp, + s.config().Timeboost.RoundDuration, + s.execEngine.bc, + ) + if err != nil { + log.Error("Failed to start express lane service", "err", err) + return + } + s.expressLaneService = els + s.expressLaneService.Start(ctx) +} + func (s *Sequencer) StopAndWait() { s.StopWaiter.StopAndWait() if s.txRetryQueue.Len() == 0 && len(s.txQueue) == 0 && s.nonceFailures.Len() == 0 { diff --git a/system_tests/common_test.go b/system_tests/common_test.go index f7c1b9e03c..ff184340ab 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -29,7 +29,6 @@ import ( "github.com/offchainlabs/nitro/das" "github.com/offchainlabs/nitro/deploy" "github.com/offchainlabs/nitro/execution/gethexec" - "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/redisutil" @@ -71,7 +70,6 @@ import ( "github.com/offchainlabs/nitro/arbutil" _ "github.com/offchainlabs/nitro/execution/nodeInterface" "github.com/offchainlabs/nitro/solgen/go/bridgegen" - "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" @@ -283,105 +281,6 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { sequencerTxOpts := b.L1Info.GetDefaultTransactOpts("Sequencer", b.ctx) sequencerTxOptsPtr = &sequencerTxOpts dataSigner = signature.DataSignerFromPrivateKey(b.L1Info.GetInfoWithPrivKey("Sequencer").PrivateKey) - - // Deploy the express lane auction contract and erc20 to the parent chain. - // TODO: This should be deployed to L2 instead. - // TODO: Move this somewhere better. - // Deploy the token as a mock erc20. - erc20Addr, tx, erc20, err := bindings.DeployMockERC20(&sequencerTxOpts, b.L1.Client) - Require(t, err) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { - t.Fatal(err) - } - tx, err = erc20.Initialize(&sequencerTxOpts, "LANE", "LNE", 18) - Require(t, err) - if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { - t.Fatal(err) - } - - // Fund the auction contract. - b.L1Info.GenerateAccount("AuctionContract") - TransferBalance(t, "Faucet", "AuctionContract", arbmath.BigMulByUint(oneEth, 500), b.L1Info, b.L1.Client, ctx) - - // Mint some tokens to Alice and Bob. - b.L1Info.GenerateAccount("Alice") - b.L1Info.GenerateAccount("Bob") - TransferBalance(t, "Faucet", "Alice", arbmath.BigMulByUint(oneEth, 500), b.L1Info, b.L1.Client, ctx) - TransferBalance(t, "Faucet", "Bob", arbmath.BigMulByUint(oneEth, 500), b.L1Info, b.L1.Client, ctx) - aliceOpts := b.L1Info.GetDefaultTransactOpts("Alice", ctx) - bobOpts := b.L1Info.GetDefaultTransactOpts("Bob", ctx) - tx, err = erc20.Mint(&sequencerTxOpts, aliceOpts.From, big.NewInt(100)) - Require(t, err) - if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { - t.Fatal(err) - } - tx, err = erc20.Mint(&sequencerTxOpts, bobOpts.From, big.NewInt(100)) - Require(t, err) - if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { - t.Fatal(err) - } - - // Calculate the number of seconds until the next minute - // and the next timestamp that is a multiple of a minute. - now := time.Now() - roundDuration := time.Minute - // Correctly calculate the remaining time until the next minute - waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond())*time.Nanosecond - // Get the current Unix timestamp at the start of the minute - initialTimestamp := big.NewInt(now.Add(waitTime).Unix()) - - // Deploy the auction manager contract. - auctionContractAddr, tx, _, err := express_lane_auctiongen.DeployExpressLaneAuction(&sequencerTxOpts, b.L1.Client) - Require(t, err) - if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { - t.Fatal(err) - } - - proxyAddr, tx, _, err := mocksgen.DeploySimpleProxy(&sequencerTxOpts, b.L1.Client, auctionContractAddr) - Require(t, err) - if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { - t.Fatal(err) - } - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(proxyAddr, b.L1.Client) - Require(t, err) - - auctioneer := b.L1Info.GetDefaultTransactOpts("AuctionContract", b.ctx).From - beneficiary := auctioneer - biddingToken := erc20Addr - bidRoundSeconds := uint64(60) - auctionClosingSeconds := uint64(15) - reserveSubmissionSeconds := uint64(15) - minReservePrice := big.NewInt(1) // 1 wei. - roleAdmin := auctioneer - minReservePriceSetter := auctioneer - reservePriceSetter := auctioneer - beneficiarySetter := auctioneer - tx, err = auctionContract.Initialize( - &sequencerTxOpts, - auctioneer, - beneficiary, - biddingToken, - express_lane_auctiongen.RoundTimingInfo{ - OffsetTimestamp: initialTimestamp.Uint64(), - RoundDurationSeconds: bidRoundSeconds, - AuctionClosingSeconds: auctionClosingSeconds, - ReserveSubmissionSeconds: reserveSubmissionSeconds, - }, - minReservePrice, - roleAdmin, - minReservePriceSetter, - reservePriceSetter, - beneficiarySetter, - ) - Require(t, err) - if _, err = bind.WaitMined(ctx, b.L1.Client, tx); err != nil { - t.Fatal(err) - } - t.Log("Deployed all the auction manager stuff", auctionContractAddr) - b.execConfig.Sequencer.Timeboost.AuctionContractAddress = proxyAddr.Hex() - b.execConfig.Sequencer.Timeboost.ERC20Address = erc20Addr.Hex() } else { b.nodeConfig.BatchPoster.Enable = false b.nodeConfig.Sequencer = false diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 20de7260ac..271d47553d 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -29,8 +29,10 @@ import ( "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/relay" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/testhelpers" "github.com/offchainlabs/nitro/wsbroadcastserver" @@ -117,73 +119,149 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { cleanupSeq := builderSeq.Build(t) defer cleanupSeq() seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client - t.Logf("Sequencer endpoint %s", seqNode.Stack.HTTPEndpoint()) - auctionAddr := builderSeq.L2.ExecNode.Sequencer.ExpressLaneAuction() - erc20Addr := builderSeq.L2.ExecNode.Sequencer.ExpressLaneERC20() + // Set up the auction contracts on L2. + // Deploy the express lane auction contract and erc20 to the parent chain. + ownerOpts := seqInfo.GetDefaultTransactOpts("Owner", ctx) + erc20Addr, tx, erc20, err := bindings.DeployMockERC20(&ownerOpts, seqClient) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Initialize(&ownerOpts, "LANE", "LNE", 18) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } - // Seed the accounts on L2. + // Fund the auction contract. + seqInfo.GenerateAccount("AuctionContract") + TransferBalance(t, "Owner", "AuctionContract", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + + // Mint some tokens to Alice and Bob. seqInfo.GenerateAccount("Alice") - tx := seqInfo.PrepareTx("Owner", "Alice", seqInfo.TransferGas, big.NewInt(1e18), nil) - Require(t, seqClient.SendTransaction(ctx, tx)) - _, err := EnsureTxSucceeded(ctx, seqClient, tx) - Require(t, err) seqInfo.GenerateAccount("Bob") - tx = seqInfo.PrepareTx("Owner", "Bob", seqInfo.TransferGas, big.NewInt(1e18), nil) - Require(t, seqClient.SendTransaction(ctx, tx)) - _, err = EnsureTxSucceeded(ctx, seqClient, tx) + TransferBalance(t, "Faucet", "Alice", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + TransferBalance(t, "Faucet", "Bob", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + aliceOpts := seqInfo.GetDefaultTransactOpts("Alice", ctx) + bobOpts := seqInfo.GetDefaultTransactOpts("Bob", ctx) + tx, err = erc20.Mint(&ownerOpts, aliceOpts.From, big.NewInt(100)) Require(t, err) - t.Logf("Alice %+v and Bob %+v", seqInfo.Accounts["Alice"], seqInfo.Accounts["Bob"]) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Mint(&ownerOpts, bobOpts.From, big.NewInt(100)) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + + // Calculate the number of seconds until the next minute + // and the next timestamp that is a multiple of a minute. + now := time.Now() + roundDuration := time.Minute + // Correctly calculate the remaining time until the next minute + waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond())*time.Nanosecond + // Get the current Unix timestamp at the start of the minute + initialTimestamp := big.NewInt(now.Add(waitTime).Unix()) + + // Deploy the auction manager contract. + auctionContractAddr, tx, _, err := express_lane_auctiongen.DeployExpressLaneAuction(&ownerOpts, seqClient) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionAddr, builderSeq.L1.Client) + proxyAddr, tx, _, err := mocksgen.DeploySimpleProxy(&ownerOpts, seqClient, auctionContractAddr) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(proxyAddr, seqClient) + Require(t, err) + + auctioneerAddr := seqInfo.GetDefaultTransactOpts("AuctionContract", ctx).From + beneficiary := auctioneerAddr + biddingToken := erc20Addr + bidRoundSeconds := uint64(60) + auctionClosingSeconds := uint64(15) + reserveSubmissionSeconds := uint64(15) + minReservePrice := big.NewInt(1) // 1 wei. + roleAdmin := auctioneerAddr + minReservePriceSetter := auctioneerAddr + reservePriceSetter := auctioneerAddr + beneficiarySetter := auctioneerAddr + tx, err = auctionContract.Initialize( + &ownerOpts, + auctioneerAddr, + beneficiary, + biddingToken, + express_lane_auctiongen.RoundTimingInfo{ + OffsetTimestamp: initialTimestamp.Uint64(), + RoundDurationSeconds: bidRoundSeconds, + AuctionClosingSeconds: auctionClosingSeconds, + ReserveSubmissionSeconds: reserveSubmissionSeconds, + }, + minReservePrice, + roleAdmin, + minReservePriceSetter, + reservePriceSetter, + beneficiarySetter, + ) Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + t.Log("Deployed all the auction manager stuff", auctionContractAddr) + + // Seed the accounts on L2. + t.Logf("Alice %+v and Bob %+v", seqInfo.Accounts["Alice"], seqInfo.Accounts["Bob"]) _ = seqInfo _ = seqClient - l1client := builderSeq.L1.Client // We approve the spending of the erc20 for the autonomous auction contract and bid receiver // for both Alice and Bob. - bidReceiverAddr := common.HexToAddress("0x3424242424242424242424242424242424242424") - aliceOpts := builderSeq.L1Info.GetDefaultTransactOpts("Alice", ctx) - bobOpts := builderSeq.L1Info.GetDefaultTransactOpts("Bob", ctx) - + bidReceiverAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") maxUint256 := big.NewInt(1) maxUint256.Lsh(maxUint256, 256).Sub(maxUint256, big.NewInt(1)) - erc20, err := bindings.NewMockERC20(erc20Addr, builderSeq.L1.Client) - Require(t, err) tx, err = erc20.Approve( - &aliceOpts, auctionAddr, maxUint256, + &aliceOpts, proxyAddr, maxUint256, ) Require(t, err) - if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { t.Fatal(err) } tx, err = erc20.Approve( &aliceOpts, bidReceiverAddr, maxUint256, ) Require(t, err) - if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { t.Fatal(err) } tx, err = erc20.Approve( - &bobOpts, auctionAddr, maxUint256, + &bobOpts, proxyAddr, maxUint256, ) Require(t, err) - if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { t.Fatal(err) } tx, err = erc20.Approve( &bobOpts, bidReceiverAddr, maxUint256, ) Require(t, err) - if _, err = bind.WaitMined(ctx, l1client, tx); err != nil { + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { t.Fatal(err) } + // We start the sequencer's express lane auction service. + builderSeq.L2.ExecNode.Sequencer.StartExpressLaneService(ctx, initialTimestamp.Uint64(), proxyAddr) + + t.Log("Started express lane service in sequencer") + // Set up an autonomous auction contract service that runs in the background in this test. - auctionContractOpts := builderSeq.L1Info.GetDefaultTransactOpts("AuctionContract", ctx) - chainId, err := l1client.ChainID(ctx) + auctionContractOpts := seqInfo.GetDefaultTransactOpts("AuctionContract", ctx) + chainId, err := seqClient.ChainID(ctx) Require(t, err) // Set up the auctioneer RPC service. @@ -205,14 +283,14 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { stack, err := node.New(&stackConf) Require(t, err) auctioneer, err := timeboost.NewAuctioneer( - &auctionContractOpts, []uint64{chainId.Uint64()}, stack, builderSeq.L1.Client, auctionContract, + &auctionContractOpts, []uint64{chainId.Uint64()}, stack, seqClient, auctionContract, ) Require(t, err) go auctioneer.Start(ctx) // Set up a bidder client for Alice and Bob. - alicePriv := builderSeq.L1Info.Accounts["Alice"].PrivateKey + alicePriv := seqInfo.Accounts["Alice"].PrivateKey alice, err := timeboost.NewBidderClient( ctx, "alice", @@ -220,13 +298,13 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { TxOpts: &aliceOpts, PrivKey: alicePriv, }, - l1client, - auctionAddr, + seqClient, + proxyAddr, auctioneer, ) Require(t, err) - bobPriv := builderSeq.L1Info.Accounts["Bob"].PrivateKey + bobPriv := seqInfo.Accounts["Bob"].PrivateKey bob, err := timeboost.NewBidderClient( ctx, "bob", @@ -234,8 +312,8 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { TxOpts: &bobOpts, PrivKey: bobPriv, }, - l1client, - auctionAddr, + seqClient, + proxyAddr, auctioneer, ) Require(t, err) @@ -252,9 +330,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { Require(t, bob.Deposit(ctx, big.NewInt(5))) // Wait until the next timeboost round + a few milliseconds. - now := time.Now() - roundDuration := time.Minute - waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) t.Logf("Alice and Bob are now deposited into the autonomous auction contract, waiting %v for bidding round...", waitTime) time.Sleep(waitTime) time.Sleep(time.Second * 5) @@ -269,7 +345,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Logf("Bob bid %+v", bobBid) // Subscribe to auction resolutions and wait for Bob to win the auction. - winner, winnerRound := awaitAuctionResolved(t, ctx, l1client, auctionContract) + winner, winnerRound := awaitAuctionResolved(t, ctx, seqClient, auctionContract) // Verify Bob owns the express lane this round. if winner != bobOpts.From { @@ -312,14 +388,12 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { // Prepare a client that can submit txs to the sequencer via the express lane. seqDial, err := rpc.Dial(seqNode.Stack.HTTPEndpoint()) Require(t, err) - seqChainId, err := seqClient.ChainID(ctx) - Require(t, err) expressLaneClient := timeboost.NewExpressLaneClient( bobPriv, - seqChainId.Uint64(), + chainId.Uint64(), time.Unix(int64(info.OffsetTimestamp), 0), roundDuration, - auctionAddr, + proxyAddr, seqDial, ) expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) From 65e0dfcb291c7e0117474ca0760ed965660eb267 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 30 Jul 2024 13:29:10 -0500 Subject: [PATCH 032/109] big int chain id --- execution/gethexec/api.go | 6 +- execution/gethexec/arb_interface.go | 6 +- execution/gethexec/express_lane_service.go | 5 +- execution/gethexec/forwarder.go | 6 +- execution/gethexec/sequencer.go | 6 +- system_tests/seqfeed_test.go | 4 +- timeboost/auctioneer.go | 6 +- timeboost/auctioneer_api.go | 68 ++++++++++++---------- timeboost/auctioneer_test.go | 12 ++-- timeboost/bidder_client.go | 4 +- timeboost/bids.go | 17 +++--- timeboost/bids_test.go | 2 +- timeboost/express_lane_client.go | 26 ++++++--- 13 files changed, 96 insertions(+), 72 deletions(-) diff --git a/execution/gethexec/api.go b/execution/gethexec/api.go index cdad9003a5..27116a50e0 100644 --- a/execution/gethexec/api.go +++ b/execution/gethexec/api.go @@ -45,7 +45,11 @@ func NewArbTimeboostAPI(publisher TransactionPublisher) *ArbTimeboostAPI { } func (a *ArbTimeboostAPI) SendExpressLaneTransaction(ctx context.Context, msg *timeboost.JsonExpressLaneSubmission) error { - return a.txPublisher.PublishExpressLaneTransaction(ctx, timeboost.JsonSubmissionToGo(msg)) + goMsg, err := timeboost.JsonSubmissionToGo(msg) + if err != nil { + return err + } + return a.txPublisher.PublishExpressLaneTransaction(ctx, goMsg) } type ArbDebugAPI struct { diff --git a/execution/gethexec/arb_interface.go b/execution/gethexec/arb_interface.go index 7678bbf202..de0cacbd98 100644 --- a/execution/gethexec/arb_interface.go +++ b/execution/gethexec/arb_interface.go @@ -44,7 +44,11 @@ func (a *ArbInterface) PublishTransaction(ctx context.Context, tx *types.Transac } func (a *ArbInterface) PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.JsonExpressLaneSubmission) error { - return a.txPublisher.PublishExpressLaneTransaction(ctx, timeboost.JsonSubmissionToGo(msg)) + goMsg, err := timeboost.JsonSubmissionToGo(msg) + if err != nil { + return err + } + return a.txPublisher.PublishExpressLaneTransaction(ctx, goMsg) } // might be used before Initialize diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index b2eaa2a42f..6d46334602 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -163,9 +163,8 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu if !es.currentRoundHasController() { return timeboost.ErrNoOnchainController } - // TODO: Careful with chain id not being uint64. - if msg.ChainId != es.chainConfig.ChainID.Uint64() { - return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, es.chainConfig.ChainID.Uint64()) + if msg.ChainId.Cmp(es.chainConfig.ChainID) != 0 { + return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, es.chainConfig.ChainID) } currentRound := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) if msg.Round != currentRound { diff --git a/execution/gethexec/forwarder.go b/execution/gethexec/forwarder.go index 962a4d0fff..3f1ad7c5d3 100644 --- a/execution/gethexec/forwarder.go +++ b/execution/gethexec/forwarder.go @@ -174,7 +174,11 @@ func (f *TxForwarder) PublishExpressLaneTransaction(inctx context.Context, msg * } func sendExpressLaneTransactionRPC(ctx context.Context, rpcClient *rpc.Client, msg *timeboost.ExpressLaneSubmission) error { - return rpcClient.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg.ToJson()) + jsonMsg, err := msg.ToJson() + if err != nil { + return err + } + return rpcClient.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", jsonMsg) } const cacheUpstreamHealth = 2 * time.Second diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index ccac335139..c9f1c9dd1b 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -96,8 +96,8 @@ var DefaultTimeboostConfig = TimeboostConfig{ Enable: false, AuctionContractAddress: "", ERC20Address: "", - ExpressLaneAdvantage: time.Millisecond * 200, - RoundDuration: time.Second, + ExpressLaneAdvantage: time.Millisecond * 250, + RoundDuration: time.Minute, InitialRoundTimestamp: uint64(time.Unix(0, 0).Unix()), } @@ -1202,7 +1202,7 @@ func (s *Sequencer) Start(ctxIn context.Context) error { func (s *Sequencer) StartExpressLaneService( ctx context.Context, initialTimestamp uint64, auctionContractAddr common.Address, ) { - if s.config().Timeboost.Enable { + if !s.config().Timeboost.Enable { return } els, err := newExpressLaneService( diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 271d47553d..900e7eea58 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -283,7 +283,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { stack, err := node.New(&stackConf) Require(t, err) auctioneer, err := timeboost.NewAuctioneer( - &auctionContractOpts, []uint64{chainId.Uint64()}, stack, seqClient, auctionContract, + &auctionContractOpts, []*big.Int{chainId}, stack, seqClient, auctionContract, ) Require(t, err) @@ -390,7 +390,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { Require(t, err) expressLaneClient := timeboost.NewExpressLaneClient( bobPriv, - chainId.Uint64(), + chainId, time.Unix(int64(info.OffsetTimestamp), 0), roundDuration, proxyAddr, diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 0d05cba08c..dd2a41cb7e 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -38,7 +38,7 @@ type AuctioneerOpt func(*Auctioneer) // Spec: https://github.com/OffchainLabs/timeboost-design/tree/main type Auctioneer struct { txOpts *bind.TransactOpts - chainId []uint64 // Auctioneer could handle auctions on multiple chains. + chainId []*big.Int // Auctioneer could handle auctions on multiple chains. domainValue []byte client Client auctionContract *express_lane_auctiongen.ExpressLaneAuction @@ -68,7 +68,7 @@ func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { // NewAuctioneer creates a new autonomous auctioneer struct. func NewAuctioneer( txOpts *bind.TransactOpts, - chainId []uint64, + chainId []*big.Int, stack *node.Node, client Client, auctionContract *express_lane_auctiongen.ExpressLaneAuction, @@ -243,7 +243,7 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { // Check if the chain ID is valid. chainIdOk := false for _, id := range a.chainId { - if bid.ChainId == id { + if bid.ChainId.Cmp(id) == 0 { chainIdOk = true break } diff --git a/timeboost/auctioneer_api.go b/timeboost/auctioneer_api.go index f5523ccfcd..41b9ba9813 100644 --- a/timeboost/auctioneer_api.go +++ b/timeboost/auctioneer_api.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" ) @@ -16,26 +17,26 @@ type AuctioneerAPI struct { } type JsonBid struct { - ChainId uint64 `json:"chainId"` + ChainId *hexutil.Big `json:"chainId"` ExpressLaneController common.Address `json:"expressLaneController"` Bidder common.Address `json:"bidder"` AuctionContractAddress common.Address `json:"auctionContractAddress"` - Round uint64 `json:"round"` - Amount *big.Int `json:"amount"` - Signature string `json:"signature"` + Round hexutil.Uint64 `json:"round"` + Amount *hexutil.Big `json:"amount"` + Signature hexutil.Bytes `json:"signature"` } type JsonExpressLaneSubmission struct { - ChainId uint64 `json:"chainId"` - Round uint64 `json:"round"` + ChainId *hexutil.Big `json:"chainId"` + Round hexutil.Uint64 `json:"round"` AuctionContractAddress common.Address `json:"auctionContractAddress"` - Transaction *types.Transaction `json:"transaction"` + Transaction hexutil.Bytes `json:"transaction"` Options *arbitrum_types.ConditionalOptions `json:"options"` - Signature string `json:"signature"` + Signature hexutil.Bytes `json:"signature"` } type ExpressLaneSubmission struct { - ChainId uint64 + ChainId *big.Int Round uint64 AuctionContractAddress common.Address Transaction *types.Transaction @@ -43,26 +44,34 @@ type ExpressLaneSubmission struct { Signature []byte } -func JsonSubmissionToGo(submission *JsonExpressLaneSubmission) *ExpressLaneSubmission { +func JsonSubmissionToGo(submission *JsonExpressLaneSubmission) (*ExpressLaneSubmission, error) { + var tx *types.Transaction + if err := tx.UnmarshalBinary(submission.Transaction); err != nil { + return nil, err + } return &ExpressLaneSubmission{ - ChainId: submission.ChainId, - Round: submission.Round, + ChainId: submission.ChainId.ToInt(), + Round: uint64(submission.Round), AuctionContractAddress: submission.AuctionContractAddress, - Transaction: submission.Transaction, + Transaction: tx, Options: submission.Options, - Signature: common.Hex2Bytes(submission.Signature), - } + Signature: submission.Signature, + }, nil } -func (els *ExpressLaneSubmission) ToJson() *JsonExpressLaneSubmission { +func (els *ExpressLaneSubmission) ToJson() (*JsonExpressLaneSubmission, error) { + encoded, err := els.Transaction.MarshalBinary() + if err != nil { + return nil, err + } return &JsonExpressLaneSubmission{ - ChainId: els.ChainId, - Round: els.Round, + ChainId: (*hexutil.Big)(els.ChainId), + Round: hexutil.Uint64(els.Round), AuctionContractAddress: els.AuctionContractAddress, - Transaction: els.Transaction, + Transaction: encoded, Options: els.Options, - Signature: common.Bytes2Hex(els.Signature), - } + Signature: els.Signature, + }, nil } func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { @@ -76,18 +85,17 @@ func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { } func encodeExpressLaneSubmission( - domainValue []byte, chainId uint64, + domainValue []byte, + chainId *big.Int, auctionContractAddress common.Address, round uint64, tx *types.Transaction, ) ([]byte, error) { buf := new(bytes.Buffer) buf.Write(domainValue) - roundBuf := make([]byte, 8) - binary.BigEndian.PutUint64(roundBuf, chainId) - buf.Write(roundBuf) + buf.Write(padBigInt(chainId)) buf.Write(auctionContractAddress[:]) - roundBuf = make([]byte, 8) + roundBuf := make([]byte, 8) binary.BigEndian.PutUint64(roundBuf, round) buf.Write(roundBuf) rlpTx, err := tx.MarshalBinary() @@ -100,12 +108,12 @@ func encodeExpressLaneSubmission( func (a *AuctioneerAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return a.receiveBid(ctx, &Bid{ - ChainId: bid.ChainId, + ChainId: bid.ChainId.ToInt(), ExpressLaneController: bid.ExpressLaneController, Bidder: bid.Bidder, AuctionContractAddress: bid.AuctionContractAddress, - Round: bid.Round, - Amount: bid.Amount, - Signature: common.Hex2Bytes(bid.Signature), + Round: uint64(bid.Round), + Amount: bid.Amount.ToInt(), + Signature: bid.Signature, }) } diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index cdb976a8c3..a08d054c0f 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -53,7 +53,7 @@ func TestAuctioneer_validateBid(t *testing.T) { bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, - ChainId: 1, + ChainId: big.NewInt(1), }, expectedErr: ErrBadRoundNumber, errMsg: "wanted 1, got 0", @@ -63,7 +63,7 @@ func TestAuctioneer_validateBid(t *testing.T) { bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, - ChainId: 1, + ChainId: big.NewInt(1), Round: 1, }, expectedErr: ErrBadRoundNumber, @@ -75,7 +75,7 @@ func TestAuctioneer_validateBid(t *testing.T) { bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, - ChainId: 1, + ChainId: big.NewInt(1), Round: 1, Amount: big.NewInt(1), }, @@ -87,7 +87,7 @@ func TestAuctioneer_validateBid(t *testing.T) { bid: &Bid{ Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, - ChainId: 1, + ChainId: big.NewInt(1), Round: 1, Amount: big.NewInt(3), Signature: []byte{'a'}, @@ -106,7 +106,7 @@ func TestAuctioneer_validateBid(t *testing.T) { for _, tt := range tests { a := Auctioneer{ - chainId: []uint64{1}, + chainId: []*big.Int{big.NewInt(1)}, initialRoundTimestamp: time.Now().Add(-time.Second), reservePrice: big.NewInt(2), roundDuration: time.Minute, @@ -145,7 +145,7 @@ func buildValidBid(t *testing.T) *Bid { Bidder: bidderAddress, ExpressLaneController: common.Address{'b'}, AuctionContractAddress: common.Address{'c'}, - ChainId: 1, + ChainId: big.NewInt(1), Round: 1, Amount: big.NewInt(3), Signature: []byte{'a'}, diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index ad081e30cd..1cb71ac172 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -28,7 +28,7 @@ type auctioneerConnection interface { } type BidderClient struct { - chainId uint64 + chainId *big.Int name string auctionContractAddress common.Address txOpts *bind.TransactOpts @@ -71,7 +71,7 @@ func NewBidderClient( roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second return &BidderClient{ - chainId: chainId.Uint64(), + chainId: chainId, name: name, auctionContractAddress: auctionContractAddress, client: client, diff --git a/timeboost/bids.go b/timeboost/bids.go index 57c26ba6ce..2fae07e662 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -29,7 +29,7 @@ var ( ) type Bid struct { - ChainId uint64 + ChainId *big.Int ExpressLaneController common.Address Bidder common.Address AuctionContractAddress common.Address @@ -43,7 +43,7 @@ type validatedBid struct { amount *big.Int signature []byte // For tie breaking - chainId uint64 + chainId *big.Int auctionContractAddress common.Address round uint64 bidder common.Address @@ -112,14 +112,11 @@ func (bc *bidCache) topTwoBids() *auctionResult { // hashBid hashes the bidder address concatenated with the respective byte-string representation of the bid using the Keccak256 hashing scheme. func hashBid(bid *validatedBid) string { - chainIdBytes := make([]byte, 8) - binary.BigEndian.PutUint64(chainIdBytes, bid.chainId) - roundBytes := make([]byte, 8) - binary.BigEndian.PutUint64(roundBytes, bid.round) - // Concatenate the bidder address and the byte representation of the bid - data := append(bid.bidder.Bytes(), chainIdBytes...) + data := append(bid.bidder.Bytes(), padBigInt(bid.chainId)...) data = append(data, bid.auctionContractAddress.Bytes()...) + roundBytes := make([]byte, 8) + binary.BigEndian.PutUint64(roundBytes, bid.round) data = append(data, roundBytes...) data = append(data, bid.amount.Bytes()...) data = append(data, bid.expressLaneController.Bytes()...) @@ -144,12 +141,12 @@ func padBigInt(bi *big.Int) []byte { return padded } -func encodeBidValues(domainValue []byte, chainId uint64, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { +func encodeBidValues(domainValue []byte, chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { buf := new(bytes.Buffer) // Encode uint256 values - each occupies 32 bytes buf.Write(domainValue) - buf.Write(padBigInt(new(big.Int).SetUint64(chainId))) + buf.Write(padBigInt(chainId)) buf.Write(auctionContractAddress[:]) roundBuf := make([]byte, 8) binary.BigEndian.PutUint64(roundBuf, round) diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 50230fcc4b..3d90d0eea5 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -106,7 +106,7 @@ func TestReceiveBid_OK(t *testing.T) { stack, err := node.New(&stackConf) require.NoError(t, err) am, err := NewAuctioneer( - testSetup.accounts[1].txOpts, []uint64{testSetup.chainId.Uint64()}, stack, testSetup.backend.Client(), testSetup.expressLaneAuction, + testSetup.accounts[1].txOpts, []*big.Int{testSetup.chainId}, stack, testSetup.backend.Client(), testSetup.expressLaneAuction, ) require.NoError(t, err) diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go index eeb74d39be..a91a937f6b 100644 --- a/timeboost/express_lane_client.go +++ b/timeboost/express_lane_client.go @@ -4,9 +4,11 @@ import ( "context" "crypto/ecdsa" "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -18,7 +20,7 @@ import ( type ExpressLaneClient struct { stopwaiter.StopWaiter privKey *ecdsa.PrivateKey - chainId uint64 + chainId *big.Int initialRoundTimestamp time.Time roundDuration time.Duration auctionContractAddr common.Address @@ -27,7 +29,7 @@ type ExpressLaneClient struct { func NewExpressLaneClient( privKey *ecdsa.PrivateKey, - chainId uint64, + chainId *big.Int, initialRoundTimestamp time.Time, roundDuration time.Duration, auctionContractAddr common.Address, @@ -45,14 +47,21 @@ func NewExpressLaneClient( func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) error { // return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { + encodedTx, err := transaction.MarshalBinary() + if err != nil { + return err + } msg := &JsonExpressLaneSubmission{ - ChainId: elc.chainId, - Round: CurrentRound(elc.initialRoundTimestamp, elc.roundDuration), + ChainId: (*hexutil.Big)(elc.chainId), + Round: hexutil.Uint64(CurrentRound(elc.initialRoundTimestamp, elc.roundDuration)), AuctionContractAddress: elc.auctionContractAddr, - Transaction: transaction, - Signature: "00", + Transaction: encodedTx, + Signature: hexutil.Bytes{}, + } + msgGo, err := JsonSubmissionToGo(msg) + if err != nil { + return err } - msgGo := JsonSubmissionToGo(msg) signingMsg, err := msgGo.ToMessageBytes() if err != nil { return err @@ -61,8 +70,7 @@ func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction * if err != nil { return err } - msg.Signature = fmt.Sprintf("%x", signature) - fmt.Println("Right here before we send the express lane tx") + msg.Signature = signature err = elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) return err } From d317fa48fe88c61e5aa941c597c7340b120d48cc Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 31 Jul 2024 12:28:43 -0500 Subject: [PATCH 033/109] contracts on L2 passing test --- execution/gethexec/express_lane_service.go | 121 +++++++++++---------- execution/gethexec/sequencer.go | 21 ++-- system_tests/seqfeed_test.go | 15 +-- timeboost/auctioneer.go | 8 +- timeboost/auctioneer_api.go | 4 +- timeboost/bidder_client.go | 1 - timeboost/bids.go | 1 - 7 files changed, 87 insertions(+), 84 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 6d46334602..06be45c217 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -3,15 +3,16 @@ package gethexec import ( "context" "fmt" - "slices" "sync" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" @@ -20,20 +21,6 @@ import ( "github.com/pkg/errors" ) -var auctionResolvedEvent common.Hash - -func init() { - auctionAbi, err := express_lane_auctiongen.ExpressLaneAuctionMetaData.GetAbi() - if err != nil { - panic(err) - } - auctionResolvedEventData, ok := auctionAbi.Events["AuctionResolved"] - if !ok { - panic("RollupCore ABI missing AssertionCreated event") - } - auctionResolvedEvent = auctionResolvedEventData.ID -} - type expressLaneControl struct { round uint64 sequence uint64 @@ -45,31 +32,41 @@ type expressLaneService struct { sync.RWMutex control expressLaneControl auctionContractAddr common.Address - auctionContract *express_lane_auctiongen.ExpressLaneAuction initialTimestamp time.Time roundDuration time.Duration chainConfig *params.ChainConfig logs chan []*types.Log - bc *core.BlockChain + seqClient *ethclient.Client + auctionContract *express_lane_auctiongen.ExpressLaneAuction } func newExpressLaneService( auctionContractAddr common.Address, - initialRoundTimestamp uint64, - roundDuration time.Duration, + sequencerClient *ethclient.Client, bc *core.BlockChain, ) (*expressLaneService, error) { chainConfig := bc.Config() + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, sequencerClient) + if err != nil { + return nil, err + } + roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + if err != nil { + return nil, err + } + initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) + roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second return &expressLaneService{ - bc: bc, + auctionContract: auctionContract, chainConfig: chainConfig, - initialTimestamp: time.Unix(int64(initialRoundTimestamp), 0), + initialTimestamp: initialTimestamp, control: expressLaneControl{ controller: common.Address{}, round: 0, }, auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, + seqClient: sequencerClient, logs: make(chan []*types.Log, 10_000), }, nil } @@ -101,22 +98,52 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) es.LaunchThread(func(ctx context.Context) { log.Info("Monitoring express lane auction contract") - sub := es.bc.SubscribeLogsEvent(es.logs) - defer sub.Unsubscribe() + // Monitor for auction resolutions from the auction manager smart contract + // and set the express lane controller for the upcoming round accordingly. + latestBlock, err := es.seqClient.HeaderByNumber(ctx, nil) + if err != nil { + log.Crit("Could not get latest header", "err", err) + } + fromBlock := latestBlock.Number.Uint64() + ticker := time.NewTicker(time.Millisecond * 250) + defer ticker.Stop() for { select { - case evs := <-es.logs: - for _, ev := range evs { - if ev.Address != es.auctionContractAddr { - continue - } - go es.processAuctionContractEvent(ctx, ev) - } case <-ctx.Done(): return - case err := <-sub.Err(): - log.Error("Subscriber failed", "err", err) - return + case <-ticker.C: + latestBlock, err := es.seqClient.HeaderByNumber(ctx, nil) + if err != nil { + log.Error("Could not get latest header", "err", err) + continue + } + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + continue + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) + if err != nil { + log.Error("Could not filter auction resolutions", "error", err) + continue + } + for it.Next() { + log.Info( + "New express lane controller assigned", + "round", it.Event.Round, + "controller", it.Event.FirstPriceExpressLaneController, + ) + es.Lock() + es.control.round = it.Event.Round + es.control.controller = it.Event.FirstPriceExpressLaneController + es.control.sequence = 0 // Sequence resets 0 for the new round. + es.Unlock() + } + fromBlock = toBlock } } }) @@ -126,46 +153,26 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) } -func (es *expressLaneService) processAuctionContractEvent(_ context.Context, rawLog *types.Log) { - if !slices.Contains(rawLog.Topics, auctionResolvedEvent) { - return - } - ev, err := es.auctionContract.ParseAuctionResolved(*rawLog) - if err != nil { - log.Error("Failed to parse AuctionResolved event", "err", err) - return - } - log.Info( - "New express lane controller assigned", - "round", ev.Round, - "controller", ev.FirstPriceExpressLaneController, - ) - es.Lock() - es.control.round = ev.Round - es.control.controller = ev.FirstPriceExpressLaneController - es.control.sequence = 0 // Sequence resets 0 for the new round. - es.Unlock() -} - func (es *expressLaneService) currentRoundHasController() bool { es.Lock() defer es.Unlock() return es.control.controller != (common.Address{}) } +// TODO: Validate sequence numbers are correct. func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSubmission) error { if msg.Transaction == nil || msg.Signature == nil { return timeboost.ErrMalformedData } + if msg.ChainId.Cmp(es.chainConfig.ChainID) != 0 { + return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, es.chainConfig.ChainID) + } if msg.AuctionContractAddress != es.auctionContractAddr { return timeboost.ErrWrongAuctionContract } if !es.currentRoundHasController() { return timeboost.ErrNoOnchainController } - if msg.ChainId.Cmp(es.chainConfig.ChainID) != 0 { - return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, es.chainConfig.ChainID) - } 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) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index c9f1c9dd1b..b24880b7c2 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -33,9 +33,11 @@ import ( "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/arbostypes" @@ -1197,23 +1199,22 @@ func (s *Sequencer) Start(ctxIn context.Context) error { return nil } -// TODO: This is needed because there is no way to currently set the initial timestamp of the express lane service -// without starting the sequencer. This is a temporary solution until we have a better way to handle this. -func (s *Sequencer) StartExpressLaneService( - ctx context.Context, initialTimestamp uint64, auctionContractAddr common.Address, -) { +func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr common.Address) { if !s.config().Timeboost.Enable { - return + log.Crit("Timeboost is not enabled, but StartExpressLane was called") + } + rpcClient, err := rpc.DialContext(ctx, "http://localhost:9567") + if err != nil { + log.Crit("Failed to connect to RPC client", "err", err) } + seqClient := ethclient.NewClient(rpcClient) els, err := newExpressLaneService( auctionContractAddr, - initialTimestamp, - s.config().Timeboost.RoundDuration, + seqClient, s.execEngine.bc, ) if err != nil { - log.Error("Failed to start express lane service", "err", err) - return + log.Crit("Failed to create express lane service", "err", err) } s.expressLaneService = els s.expressLaneService.Start(ctx) diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 900e7eea58..74718df9cb 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -164,6 +164,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond())*time.Nanosecond // Get the current Unix timestamp at the start of the minute initialTimestamp := big.NewInt(now.Add(waitTime).Unix()) + initialTimestampUnix := time.Unix(initialTimestamp.Int64(), 0) // Deploy the auction manager contract. auctionContractAddr, tx, _, err := express_lane_auctiongen.DeployExpressLaneAuction(&ownerOpts, seqClient) @@ -254,9 +255,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Fatal(err) } - // We start the sequencer's express lane auction service. - builderSeq.L2.ExecNode.Sequencer.StartExpressLaneService(ctx, initialTimestamp.Uint64(), proxyAddr) - + builderSeq.L2.ExecNode.Sequencer.StartExpressLane(ctx, proxyAddr) t.Log("Started express lane service in sequencer") // Set up an autonomous auction contract service that runs in the background in this test. @@ -321,8 +320,8 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { // Wait until the initial round. info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) Require(t, err) - timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) - t.Log("Waiting until the initial round", timeToWait, time.Unix(int64(info.OffsetTimestamp), 0)) + timeToWait := time.Until(initialTimestampUnix) + t.Logf("Waiting until the initial round %v and %v, current time %v", timeToWait, initialTimestampUnix, time.Now()) <-time.After(timeToWait) t.Log("Started auction master stack and bid clients") @@ -330,13 +329,15 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { Require(t, bob.Deposit(ctx, big.NewInt(5))) // Wait until the next timeboost round + a few milliseconds. + now = time.Now() waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) - t.Logf("Alice and Bob are now deposited into the autonomous auction contract, waiting %v for bidding round...", waitTime) + t.Logf("Alice and Bob are now deposited into the autonomous auction contract, waiting %v for bidding round..., timestamp %v", waitTime, time.Now()) time.Sleep(waitTime) + t.Logf("Reached the bidding round at %v", time.Now()) time.Sleep(time.Second * 5) // We are now in the bidding round, both issue their bids. Bob will win. - t.Log("Alice and Bob now submitting their bids") + t.Logf("Alice and Bob now submitting their bids at %v", time.Now()) aliceBid, err := alice.Bid(ctx, big.NewInt(1), aliceOpts.From) Require(t, err) bobBid, err := bob.Bid(ctx, big.NewInt(2), bobOpts.From) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index dd2a41cb7e..eafd4b2860 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -233,9 +233,6 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") } - if bid.Bidder == (common.Address{}) { - return nil, errors.Wrap(ErrMalformedData, "empty bidder address") - } if bid.ExpressLaneController == (common.Address{}) { return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") } @@ -298,12 +295,13 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { if !verifySignature(pubkey, packedBidBytes, sigItem) { return nil, ErrWrongSignature } + bidder := crypto.PubkeyToAddress(*pubkey) // Validate if the user if a depositor in the contract and has enough balance for the bid. // TODO: Retry some number of times if flakey connection. // TODO: Validate reserve price against amount of bid. // TODO: No need to do anything expensive if the bid coming is in invalid. // Cache this if the received time of the bid is too soon. Include the arrival timestamp. - depositBal, err := a.auctionContract.BalanceOf(&bind.CallOpts{}, bid.Bidder) + depositBal, err := a.auctionContract.BalanceOf(&bind.CallOpts{}, bidder) if err != nil { return nil, err } @@ -320,7 +318,7 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { chainId: bid.ChainId, auctionContractAddress: bid.AuctionContractAddress, round: bid.Round, - bidder: bid.Bidder, + bidder: bidder, }, nil } diff --git a/timeboost/auctioneer_api.go b/timeboost/auctioneer_api.go index 41b9ba9813..8d56b53b16 100644 --- a/timeboost/auctioneer_api.go +++ b/timeboost/auctioneer_api.go @@ -19,7 +19,6 @@ type AuctioneerAPI struct { type JsonBid struct { ChainId *hexutil.Big `json:"chainId"` ExpressLaneController common.Address `json:"expressLaneController"` - Bidder common.Address `json:"bidder"` AuctionContractAddress common.Address `json:"auctionContractAddress"` Round hexutil.Uint64 `json:"round"` Amount *hexutil.Big `json:"amount"` @@ -45,7 +44,7 @@ type ExpressLaneSubmission struct { } func JsonSubmissionToGo(submission *JsonExpressLaneSubmission) (*ExpressLaneSubmission, error) { - var tx *types.Transaction + tx := &types.Transaction{} if err := tx.UnmarshalBinary(submission.Transaction); err != nil { return nil, err } @@ -110,7 +109,6 @@ func (a *AuctioneerAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return a.receiveBid(ctx, &Bid{ ChainId: bid.ChainId.ToInt(), ExpressLaneController: bid.ExpressLaneController, - Bidder: bid.Bidder, AuctionContractAddress: bid.AuctionContractAddress, Round: uint64(bid.Round), Amount: bid.Amount.ToInt(), diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 1cb71ac172..23d1e2ec9b 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -107,7 +107,6 @@ func (bd *BidderClient) Bid( ChainId: bd.chainId, ExpressLaneController: expressLaneController, AuctionContractAddress: bd.auctionContractAddress, - Bidder: bd.txOpts.From, Round: CurrentRound(bd.initialRoundTimestamp, bd.roundDuration) + 1, Amount: amount, Signature: nil, diff --git a/timeboost/bids.go b/timeboost/bids.go index 2fae07e662..6c600cfa89 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -31,7 +31,6 @@ var ( type Bid struct { ChainId *big.Int ExpressLaneController common.Address - Bidder common.Address AuctionContractAddress common.Address Round uint64 Amount *big.Int From e7a0788c943cf88c46694e2776d491981a82f3e0 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 1 Aug 2024 09:23:00 -0500 Subject: [PATCH 034/109] pass resolve auction --- timeboost/auctioneer.go | 14 +- timeboost/auctioneer_test.go | 19 +- timeboost/bidder_client.go | 22 +- timeboost/bids.go | 12 ++ timeboost/bids_test.go | 407 +++++++++++++++++------------------ timeboost/setup_test.go | 4 +- 6 files changed, 244 insertions(+), 234 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index eafd4b2860..4c84f01e36 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -42,6 +42,7 @@ type Auctioneer struct { domainValue []byte client Client auctionContract *express_lane_auctiongen.ExpressLaneAuction + auctionContractAddr common.Address bidsReceiver chan *Bid bidCache *bidCache initialRoundTimestamp time.Time @@ -71,9 +72,13 @@ func NewAuctioneer( chainId []*big.Int, stack *node.Node, client Client, - auctionContract *express_lane_auctiongen.ExpressLaneAuction, + auctionContractAddr common.Address, opts ...AuctioneerOpt, ) (*Auctioneer, error) { + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) + if err != nil { + return nil, err + } roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) if err != nil { return nil, err @@ -92,6 +97,7 @@ func NewAuctioneer( chainId: chainId, client: client, auctionContract: auctionContract, + auctionContractAddr: auctionContractAddr, bidsReceiver: make(chan *Bid, 10_000), // TODO(Terence): Is 10000 enough? Make this configurable? bidCache: newBidCache(), initialRoundTimestamp: initialTimestamp, @@ -233,9 +239,15 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") } + if bid.AuctionContractAddress != a.auctionContractAddr { + return nil, errors.Wrap(ErrMalformedData, "incorrect auction contract address") + } if bid.ExpressLaneController == (common.Address{}) { return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") } + if bid.ChainId == nil { + return nil, errors.Wrap(ErrMalformedData, "empty chain id") + } // Check if the chain ID is valid. chainIdOk := false diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index a08d054c0f..5880061566 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -27,22 +27,15 @@ func TestAuctioneer_validateBid(t *testing.T) { expectedErr: ErrMalformedData, errMsg: "nil bid", }, - { - name: "empty bidder address", - bid: &Bid{}, - expectedErr: ErrMalformedData, - errMsg: "empty bidder address", - }, { name: "empty express lane controller address", - bid: &Bid{Bidder: common.Address{'a'}}, + bid: &Bid{}, expectedErr: ErrMalformedData, errMsg: "empty express lane controller address", }, { name: "incorrect chain id", bid: &Bid{ - Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, }, expectedErr: ErrWrongChainId, @@ -51,7 +44,6 @@ func TestAuctioneer_validateBid(t *testing.T) { { name: "incorrect round", bid: &Bid{ - Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, ChainId: big.NewInt(1), }, @@ -61,7 +53,6 @@ func TestAuctioneer_validateBid(t *testing.T) { { name: "auction is closed", bid: &Bid{ - Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, ChainId: big.NewInt(1), Round: 1, @@ -73,7 +64,6 @@ func TestAuctioneer_validateBid(t *testing.T) { { name: "lower than reserved price", bid: &Bid{ - Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, ChainId: big.NewInt(1), Round: 1, @@ -85,7 +75,6 @@ func TestAuctioneer_validateBid(t *testing.T) { { name: "incorrect signature", bid: &Bid{ - Bidder: common.Address{'a'}, ExpressLaneController: common.Address{'b'}, ChainId: big.NewInt(1), Round: 1, @@ -136,13 +125,7 @@ func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { func buildValidBid(t *testing.T) *Bid { privateKey, err := crypto.GenerateKey() require.NoError(t, err) - publicKey := privateKey.Public() - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - require.True(t, ok) - bidderAddress := crypto.PubkeyToAddress(*publicKeyECDSA) - b := &Bid{ - Bidder: bidderAddress, ExpressLaneController: common.Address{'b'}, AuctionContractAddress: common.Address{'c'}, ChainId: big.NewInt(1), diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 23d1e2ec9b..ab143cc81f 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" ) @@ -23,10 +24,6 @@ type Client interface { ChainID(ctx context.Context) (*big.Int, error) } -type auctioneerConnection interface { - receiveBid(ctx context.Context, bid *Bid) error -} - type BidderClient struct { chainId *big.Int name string @@ -35,7 +32,7 @@ type BidderClient struct { client Client privKey *ecdsa.PrivateKey auctionContract *express_lane_auctiongen.ExpressLaneAuction - auctioneer auctioneerConnection + auctioneerClient *rpc.Client initialRoundTimestamp time.Time roundDuration time.Duration domainValue []byte @@ -53,7 +50,7 @@ func NewBidderClient( wallet *Wallet, client Client, auctionContractAddress common.Address, - auctioneer auctioneerConnection, + auctioneerEndpoint string, ) (*BidderClient, error) { chainId, err := client.ChainID(ctx) if err != nil { @@ -70,6 +67,10 @@ func NewBidderClient( initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + auctioneerClient, err := rpc.DialContext(ctx, auctioneerEndpoint) + if err != nil { + return nil, err + } return &BidderClient{ chainId: chainId, name: name, @@ -78,7 +79,7 @@ func NewBidderClient( txOpts: wallet.TxOpts, privKey: wallet.PrivKey, auctionContract: auctionContract, - auctioneer: auctioneer, + auctioneerClient: auctioneerClient, initialRoundTimestamp: initialTimestamp, roundDuration: roundDuration, domainValue: domainValue, @@ -127,12 +128,17 @@ func (bd *BidderClient) Bid( return nil, err } newBid.Signature = sig - if err = bd.auctioneer.receiveBid(ctx, newBid); err != nil { + if err = bd.submitBid(ctx, newBid); err != nil { return nil, err } return newBid, nil } +func (bd *BidderClient) submitBid(ctx context.Context, bid *Bid) error { + err := bd.auctioneerClient.CallContext(ctx, nil, "auctioneer_submitBid", bid.ToJson()) + return err +} + func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) diff --git a/timeboost/bids.go b/timeboost/bids.go index 6c600cfa89..2d7af893db 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -10,6 +10,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/pkg/errors" @@ -37,6 +38,17 @@ type Bid struct { Signature []byte } +func (b *Bid) ToJson() *JsonBid { + return &JsonBid{ + ChainId: (*hexutil.Big)(b.ChainId), + ExpressLaneController: b.ExpressLaneController, + AuctionContractAddress: b.AuctionContractAddress, + Round: hexutil.Uint64(b.Round), + Amount: (*hexutil.Big)(b.Amount), + Signature: b.Signature, + } +} + type validatedBid struct { expressLaneController common.Address amount *big.Int diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 3d90d0eea5..d632af1f00 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -4,98 +4,228 @@ import ( "context" "math/big" "testing" + "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) -// import ( -// "context" -// "math/big" -// "testing" -// "time" - -// "github.com/ethereum/go-ethereum/accounts/abi/bind" -// "github.com/ethereum/go-ethereum/common" -// "github.com/stretchr/testify/require" -// ) - -// func TestResolveAuction(t *testing.T) { -// ctx, cancel := context.WithCancel(context.Background()) -// defer cancel() - -// testSetup := setupAuctionTest(t, ctx) - -// // Set up a new auction master instance that can validate bids. -// am, err := NewAuctioneer( -// testSetup.accounts[0].txOpts, []uint64{testSetup.chainId.Uint64()}, testSetup.backend.Client(), testSetup.expressLaneAuction, -// ) -// require.NoError(t, err) - -// // Set up two different bidders. -// alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) -// bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, am) -// require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) -// require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) - -// // Wait until the initial round. -// info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) -// require.NoError(t, err) -// timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) -// <-time.After(timeToWait) -// time.Sleep(time.Second) // Add a second of wait so that we are within a round. - -// // Form two new bids for the round, with Alice being the bigger one. -// _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) -// require.NoError(t, err) -// _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) -// require.NoError(t, err) - -// // Attempt to resolve the auction before it is closed and receive an error. -// require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") - -// // Await resolution. -// t.Log(time.Now()) -// ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) -// go ticker.start() -// <-ticker.c -// require.NoError(t, am.resolveAuction(ctx)) -// // Expect Alice to have become the next express lane controller. - -// filterOpts := &bind.FilterOpts{ -// Context: ctx, -// Start: 0, -// End: nil, -// } -// it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) -// require.NoError(t, err) -// aliceWon := false -// for it.Next() { -// if it.Event.FirstPriceBidder == alice.txOpts.From { -// aliceWon = true -// } -// } -// require.True(t, aliceWon) -// } +func TestResolveAuction(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testSetup := setupAuctionTest(t, ctx) + am, endpoint := setupAuctioneer(t, ctx, testSetup) + + // Set up two different bidders. + alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) + bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, endpoint) + require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) + require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) + + // Wait until the initial round. + info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) + require.NoError(t, err) + timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) + <-time.After(timeToWait) + time.Sleep(time.Second) // Add a second of wait so that we are within a round. + + // Form two new bids for the round, with Alice being the bigger one. + _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) + require.NoError(t, err) + _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) + require.NoError(t, err) + + // Attempt to resolve the auction before it is closed and receive an error. + require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") + + // Await resolution. + t.Log(time.Now()) + ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) + go ticker.start() + <-ticker.c + require.NoError(t, am.resolveAuction(ctx)) + + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: 0, + End: nil, + } + it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) + require.NoError(t, err) + aliceWon := false + for it.Next() { + // Expect Alice to have become the next express lane controller. + if it.Event.FirstPriceBidder == alice.txOpts.From { + aliceWon = true + } + } + require.True(t, aliceWon) +} func TestReceiveBid_OK(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() testSetup := setupAuctionTest(t, ctx) + am, endpoint := setupAuctioneer(t, ctx, testSetup) + bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) + require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) + // Form a new bid with an amount. + newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) + require.NoError(t, err) + + // Check the bid passes validation. + _, err = am.validateBid(newBid) + require.NoError(t, err) + + topTwoBids := am.bidCache.topTwoBids() + require.True(t, topTwoBids.secondPlace == nil) + require.True(t, topTwoBids.firstPlace.expressLaneController == newBid.ExpressLaneController) +} + +func TestTopTwoBids(t *testing.T) { + tests := []struct { + name string + bids map[common.Address]*validatedBid + expected *auctionResult + }{ + { + name: "Single Bid", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: nil, + }, + }, + { + name: "Two Bids with Different Amounts", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, + secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + }, + }, + { + name: "Two Bids with Same Amount and Different Hashes", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + }, + }, + { + name: "More Than Two Bids, All Unique Amounts", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, + }, + }, + { + name: "More Than Two Bids, Some with Same Amounts", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, + common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, + }, + }, + { + name: "More Than Two Bids, Tied for Second Place", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, + }, + }, + { + name: "All Bids with the Same Amount", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, + secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, + }, + }, + { + name: "No Bids", + bids: nil, + expected: &auctionResult{firstPlace: nil, secondPlace: nil}, + }, + { + name: "Identical Bids", + bids: map[common.Address]*validatedBid{ + common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, + secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bc := &bidCache{ + bidsByExpressLaneControllerAddr: tt.bids, + } + result := bc.topTwoBids() + if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { + t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) + } + if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { + t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) + } + if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { + t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) + } + }) + } +} + +func setupAuctioneer(t *testing.T, ctx context.Context, testSetup *auctionSetup) (*Auctioneer, string) { // Set up a new auction master instance that can validate bids. // Set up the auctioneer RPC service. stackConf := node.Config{ DataDir: "", // ephemeral. HTTPPort: 9372, HTTPModules: []string{AuctioneerNamespace}, + HTTPHost: "localhost", HTTPVirtualHosts: []string{"localhost"}, HTTPTimeouts: rpc.DefaultHTTPTimeouts, WSPort: 9373, WSModules: []string{AuctioneerNamespace}, + WSHost: "localhost", GraphQLVirtualHosts: []string{"localhost"}, P2P: p2p.Config{ ListenAddr: "", @@ -106,143 +236,10 @@ func TestReceiveBid_OK(t *testing.T) { stack, err := node.New(&stackConf) require.NoError(t, err) am, err := NewAuctioneer( - testSetup.accounts[1].txOpts, []*big.Int{testSetup.chainId}, stack, testSetup.backend.Client(), testSetup.expressLaneAuction, + testSetup.accounts[0].txOpts, []*big.Int{testSetup.chainId}, stack, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, ) require.NoError(t, err) - - // Make a deposit as a bidder into the contract. - bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, am) - require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) - - // Form a new bid with an amount. - newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) - require.NoError(t, err) - - // Check the bid passes validation. - _, err = am.validateBid(newBid) - require.NoError(t, err) + go am.Start(ctx) + require.NoError(t, stack.Start()) + return am, "http://localhost:9372" } - -// func TestTopTwoBids(t *testing.T) { -// tests := []struct { -// name string -// bids map[common.Address]*validatedBid -// expected *auctionResult -// }{ -// { -// name: "Single Bid", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: nil, -// }, -// }, -// { -// name: "Two Bids with Different Amounts", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x1")}, -// }, -// }, -// { -// name: "Two Bids with Same Amount and Different Hashes", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// }, -// }, -// { -// name: "More Than Two Bids, All Unique Amounts", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, -// }, -// }, -// { -// name: "More Than Two Bids, Some with Same Amounts", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x3")}, -// common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, -// }, -// }, -// { -// name: "More Than Two Bids, Tied for Second Place", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(200), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(300), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, -// }, -// }, -// { -// name: "All Bids with the Same Amount", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 3, bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 2, bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// }, -// { -// name: "No Bids", -// bids: nil, -// expected: &auctionResult{firstPlace: nil, secondPlace: nil}, -// }, -// { -// name: "Identical Bids", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: 1, bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// bc := &bidCache{ -// bidsByExpressLaneControllerAddr: tt.bids, -// } -// result := bc.topTwoBids() -// if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { -// t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) -// } -// if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { -// t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) -// } -// if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { -// t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) -// } -// }) -// } -// } diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index e72fa33022..38c36c3b95 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -149,7 +149,7 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { } func setupBidderClient( - t *testing.T, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, conn auctioneerConnection, + t *testing.T, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, auctioneerEndpoint string, ) *BidderClient { bc, err := NewBidderClient( ctx, @@ -157,7 +157,7 @@ func setupBidderClient( &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, - conn, + auctioneerEndpoint, ) require.NoError(t, err) From 889d567397463e6da21763621d6ccd7d809e8bda Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 1 Aug 2024 10:33:20 -0500 Subject: [PATCH 035/109] tests and benchmark --- execution/gethexec/express_lane_service.go | 3 +- .../gethexec/express_lane_service_test.go | 278 ++++++++++++++++++ timeboost/auctioneer_api.go | 3 + timeboost/bids_test.go | 38 ++- timeboost/setup_test.go | 4 +- 5 files changed, 318 insertions(+), 8 deletions(-) create mode 100644 execution/gethexec/express_lane_service_test.go diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 06be45c217..e971e0eccc 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -159,9 +159,8 @@ func (es *expressLaneService) currentRoundHasController() bool { return es.control.controller != (common.Address{}) } -// TODO: Validate sequence numbers are correct. func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSubmission) error { - if msg.Transaction == nil || msg.Signature == nil { + if msg == nil || msg.Transaction == nil || msg.Signature == nil { return timeboost.ErrMalformedData } if msg.ChainId.Cmp(es.chainConfig.ChainID) != 0 { diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go new file mode 100644 index 0000000000..413472ef25 --- /dev/null +++ b/execution/gethexec/express_lane_service_test.go @@ -0,0 +1,278 @@ +package gethexec + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/timeboost" + "github.com/stretchr/testify/require" +) + +func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { + privKey, err := crypto.HexToECDSA("93be75cc4df7acbb636b6abe6de2c0446235ac1dc7da9f290a70d83f088b486d") + require.NoError(t, err) + addr := crypto.PubkeyToAddress(privKey.PublicKey) + tests := []struct { + name string + es *expressLaneService + sub *timeboost.ExpressLaneSubmission + expectedErr error + controller common.Address + valid bool + }{ + { + name: "nil msg", + sub: nil, + expectedErr: timeboost.ErrMalformedData, + }, + { + name: "nil tx", + sub: &timeboost.ExpressLaneSubmission{}, + expectedErr: timeboost.ErrMalformedData, + }, + { + name: "nil sig", + sub: &timeboost.ExpressLaneSubmission{ + Transaction: &types.Transaction{}, + }, + expectedErr: timeboost.ErrMalformedData, + }, + { + name: "wrong chain id", + es: &expressLaneService{ + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + }, + sub: &timeboost.ExpressLaneSubmission{ + ChainId: big.NewInt(2), + Transaction: &types.Transaction{}, + Signature: []byte{'a'}, + }, + expectedErr: timeboost.ErrWrongChainId, + }, + { + name: "wrong auction contract", + es: &expressLaneService{ + auctionContractAddr: common.Address{'a'}, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + }, + sub: &timeboost.ExpressLaneSubmission{ + ChainId: big.NewInt(1), + AuctionContractAddress: common.Address{'b'}, + Transaction: &types.Transaction{}, + Signature: []byte{'b'}, + }, + expectedErr: timeboost.ErrWrongAuctionContract, + }, + { + name: "no onchain controller", + es: &expressLaneService{ + auctionContractAddr: common.Address{'a'}, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + }, + sub: &timeboost.ExpressLaneSubmission{ + ChainId: big.NewInt(1), + AuctionContractAddress: common.Address{'a'}, + Transaction: &types.Transaction{}, + Signature: []byte{'b'}, + }, + expectedErr: timeboost.ErrNoOnchainController, + }, + { + name: "bad round number", + es: &expressLaneService{ + auctionContractAddr: common.Address{'a'}, + initialTimestamp: time.Now(), + roundDuration: time.Minute, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, + }, + }, + sub: &timeboost.ExpressLaneSubmission{ + ChainId: big.NewInt(1), + AuctionContractAddress: common.Address{'a'}, + Transaction: &types.Transaction{}, + Signature: []byte{'b'}, + Round: 100, + }, + expectedErr: timeboost.ErrBadRoundNumber, + }, + { + name: "malformed signature", + es: &expressLaneService{ + auctionContractAddr: common.Address{'a'}, + initialTimestamp: time.Now(), + roundDuration: time.Minute, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, + }, + }, + sub: &timeboost.ExpressLaneSubmission{ + ChainId: big.NewInt(1), + AuctionContractAddress: common.Address{'a'}, + Transaction: types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), + Signature: []byte{'b'}, + Round: 0, + }, + expectedErr: timeboost.ErrMalformedData, + }, + { + name: "wrong signature", + es: &expressLaneService{ + auctionContractAddr: common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), + initialTimestamp: time.Now(), + roundDuration: time.Minute, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, + }, + }, + sub: buildInvalidSignatureSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6")), + expectedErr: timeboost.ErrNotExpressLaneController, + }, + { + name: "not express lane controller", + es: &expressLaneService{ + auctionContractAddr: common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), + initialTimestamp: time.Now(), + roundDuration: time.Minute, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, + }, + }, + sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), privKey), + expectedErr: timeboost.ErrNotExpressLaneController, + }, + { + name: "OK", + es: &expressLaneService{ + auctionContractAddr: common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), + initialTimestamp: time.Now(), + roundDuration: time.Minute, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + control: expressLaneControl{ + controller: addr, + }, + }, + sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), privKey), + valid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.es.validateExpressLaneTx(tt.sub) + if tt.valid { + require.NoError(t, err) + return + } + require.ErrorIs(t, err, tt.expectedErr) + }) + } +} + +func Benchmark_expressLaneService_validateExpressLaneTx(b *testing.B) { + b.StopTimer() + privKey, err := crypto.HexToECDSA("93be75cc4df7acbb636b6abe6de2c0446235ac1dc7da9f290a70d83f088b486d") + require.NoError(b, err) + addr := crypto.PubkeyToAddress(privKey.PublicKey) + es := &expressLaneService{ + auctionContractAddr: common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), + initialTimestamp: time.Now(), + roundDuration: time.Minute, + chainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + }, + control: expressLaneControl{ + controller: addr, + }, + } + sub := buildValidSubmission(b, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), privKey) + b.StartTimer() + for i := 0; i < b.N; i++ { + err := es.validateExpressLaneTx(sub) + require.NoError(b, err) + } +} + +func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { + prefixedData := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(data))), data...)) + signature, err := crypto.Sign(prefixedData, privateKey) + if err != nil { + return nil, err + } + return signature, nil +} + +func buildInvalidSignatureSubmission( + t *testing.T, + auctionContractAddr common.Address, +) *timeboost.ExpressLaneSubmission { + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + 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, + } + other := &timeboost.ExpressLaneSubmission{ + ChainId: big.NewInt(2), + AuctionContractAddress: auctionContractAddr, + Transaction: types.NewTransaction(320, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil), + Signature: make([]byte, 65), + Round: 30, + } + otherData, err := other.ToMessageBytes() + require.NoError(t, err) + signature, err := buildSignature(privateKey, otherData) + require.NoError(t, err) + b.Signature = signature + return b +} + +func buildValidSubmission( + t testing.TB, + auctionContractAddr common.Address, + privKey *ecdsa.PrivateKey, +) *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, + } + data, err := b.ToMessageBytes() + require.NoError(t, err) + signature, err := buildSignature(privKey, data) + require.NoError(t, err) + b.Signature = signature + return b +} diff --git a/timeboost/auctioneer_api.go b/timeboost/auctioneer_api.go index 8d56b53b16..3906422f7c 100644 --- a/timeboost/auctioneer_api.go +++ b/timeboost/auctioneer_api.go @@ -105,6 +105,9 @@ func encodeExpressLaneSubmission( return buf.Bytes(), nil } +type AuctionResolutionSubmission struct { +} + func (a *AuctioneerAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return a.receiveBid(ctx, &Bid{ ChainId: bid.ChainId.ToInt(), diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index d632af1f00..d52155922f 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -2,7 +2,9 @@ package timeboost import ( "context" + "fmt" "math/big" + "net" "testing" "time" @@ -213,17 +215,38 @@ func TestTopTwoBids(t *testing.T) { } } -func setupAuctioneer(t *testing.T, ctx context.Context, testSetup *auctionSetup) (*Auctioneer, string) { +func BenchmarkBidValidation(b *testing.B) { + b.StopTimer() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testSetup := setupAuctionTest(b, ctx) + am, endpoint := setupAuctioneer(b, ctx, testSetup) + bc := setupBidderClient(b, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) + require.NoError(b, bc.Deposit(ctx, big.NewInt(5))) + + // Form a valid bid. + newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) + require.NoError(b, err) + + b.StartTimer() + for i := 0; i < b.N; i++ { + am.validateBid(newBid) + } +} + +func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*Auctioneer, string) { // Set up a new auction master instance that can validate bids. // Set up the auctioneer RPC service. + randHttp := getRandomPort(t) stackConf := node.Config{ DataDir: "", // ephemeral. - HTTPPort: 9372, + HTTPPort: randHttp, HTTPModules: []string{AuctioneerNamespace}, HTTPHost: "localhost", HTTPVirtualHosts: []string{"localhost"}, HTTPTimeouts: rpc.DefaultHTTPTimeouts, - WSPort: 9373, + WSPort: getRandomPort(t), WSModules: []string{AuctioneerNamespace}, WSHost: "localhost", GraphQLVirtualHosts: []string{"localhost"}, @@ -241,5 +264,12 @@ func setupAuctioneer(t *testing.T, ctx context.Context, testSetup *auctionSetup) require.NoError(t, err) go am.Start(ctx) require.NoError(t, stack.Start()) - return am, "http://localhost:9372" + return am, fmt.Sprintf("http://localhost:%d", randHttp) +} + +func getRandomPort(t testing.TB) int { + listener, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + defer listener.Close() + return listener.Addr().(*net.TCPAddr).Port } diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 38c36c3b95..ca0562c8c1 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -32,7 +32,7 @@ type auctionSetup struct { backend *simulated.Backend } -func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { +func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { accs, backend := setupAccounts(10) // Advance the chain in the background at Arbitrum One's block time of 250ms. @@ -149,7 +149,7 @@ func setupAuctionTest(t *testing.T, ctx context.Context) *auctionSetup { } func setupBidderClient( - t *testing.T, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, auctioneerEndpoint string, + t testing.TB, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, auctioneerEndpoint string, ) *BidderClient { bc, err := NewBidderClient( ctx, From a33cb28eb6f227b1e0256c1a083b36bdc250b393 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Fri, 2 Aug 2024 09:43:24 -0500 Subject: [PATCH 036/109] add sequence number management for express lane submissions --- execution/gethexec/express_lane_service.go | 81 +++++++-- .../gethexec/express_lane_service_test.go | 167 ++++++++++++++++-- execution/gethexec/sequencer.go | 2 +- timeboost/auctioneer_api.go | 4 +- timeboost/bids.go | 2 + 5 files changed, 230 insertions(+), 26 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index e971e0eccc..83ad4f9716 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -30,14 +31,15 @@ type expressLaneControl struct { type expressLaneService struct { stopwaiter.StopWaiter sync.RWMutex - control expressLaneControl - auctionContractAddr common.Address - initialTimestamp time.Time - roundDuration time.Duration - chainConfig *params.ChainConfig - logs chan []*types.Log - seqClient *ethclient.Client - auctionContract *express_lane_auctiongen.ExpressLaneAuction + control expressLaneControl + auctionContractAddr common.Address + initialTimestamp time.Time + roundDuration time.Duration + chainConfig *params.ChainConfig + logs chan []*types.Log + seqClient *ethclient.Client + auctionContract *express_lane_auctiongen.ExpressLaneAuction + messagesBySequenceNumber map[uint64]*timeboost.ExpressLaneSubmission } func newExpressLaneService( @@ -64,10 +66,11 @@ func newExpressLaneService( controller: common.Address{}, round: 0, }, - auctionContractAddr: auctionContractAddr, - roundDuration: roundDuration, - seqClient: sequencerClient, - logs: make(chan []*types.Log, 10_000), + auctionContractAddr: auctionContractAddr, + roundDuration: roundDuration, + seqClient: sequencerClient, + logs: make(chan []*types.Log, 10_000), + messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), }, nil } @@ -93,6 +96,10 @@ func (es *expressLaneService) Start(ctxIn context.Context) { "round", round, "timestamp", t, ) + es.Lock() + // Reset the sequence numbers map for the new round. + es.messagesBySequenceNumber = make(map[uint64]*timeboost.ExpressLaneSubmission) + es.Unlock() } } }) @@ -137,6 +144,7 @@ func (es *expressLaneService) Start(ctxIn context.Context) { "round", it.Event.Round, "controller", it.Event.FirstPriceExpressLaneController, ) + // TODO: This is wrong because it ovewrites the upcoming round... es.Lock() es.control.round = it.Event.Round es.control.controller = it.Event.FirstPriceExpressLaneController @@ -159,6 +167,55 @@ func (es *expressLaneService) currentRoundHasController() bool { return es.control.controller != (common.Address{}) } +func (es *expressLaneService) sequenceExpressLaneSubmission( + ctx context.Context, + msg *timeboost.ExpressLaneSubmission, + publishTxFn func( + parentCtx context.Context, + tx *types.Transaction, + options *arbitrum_types.ConditionalOptions, + delay bool, + ) error, +) error { + es.Lock() + defer es.Unlock() + // Check if the submission nonce is too low. + if msg.Sequence < es.control.sequence { + return timeboost.ErrSequenceNumberTooLow + } + // Check if a duplicate submission exists already, and reject if so. + if _, exists := es.messagesBySequenceNumber[msg.Sequence]; exists { + return timeboost.ErrDuplicateSequenceNumber + } + // Log an informational warning if the message's sequence number is in the future. + if msg.Sequence > es.control.sequence { + log.Warn("Received express lane submission with future sequence number", "sequence", msg.Sequence) + } + // Put into the the sequence number map. + es.messagesBySequenceNumber[msg.Sequence] = msg + + for { + // Get the next message in the sequence. + nextMsg, exists := es.messagesBySequenceNumber[es.control.sequence] + if !exists { + break + } + if err := publishTxFn( + ctx, + nextMsg.Transaction, + msg.Options, + false, /* no delay, as it should go through express lane */ + ); err != nil { + // If the tx failed, clear it from the sequence map. + delete(es.messagesBySequenceNumber, msg.Sequence) + return err + } + // Increase the global round sequence number. + es.control.sequence += 1 + } + return nil +} + func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSubmission) error { if msg == nil || msg.Transaction == nil || msg.Signature == nil { return timeboost.ErrMalformedData diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 413472ef25..11975781a6 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -1,12 +1,15 @@ package gethexec import ( + "context" "crypto/ecdsa" + "errors" "fmt" "math/big" "testing" "time" + "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -15,10 +18,17 @@ import ( "github.com/stretchr/testify/require" ) -func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { +var testPriv *ecdsa.PrivateKey + +func init() { privKey, err := crypto.HexToECDSA("93be75cc4df7acbb636b6abe6de2c0446235ac1dc7da9f290a70d83f088b486d") - require.NoError(t, err) - addr := crypto.PubkeyToAddress(privKey.PublicKey) + if err != nil { + panic(err) + } + testPriv = privKey +} + +func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { tests := []struct { name string es *expressLaneService @@ -163,7 +173,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { controller: common.Address{'b'}, }, }, - sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), privKey), + sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv), expectedErr: timeboost.ErrNotExpressLaneController, }, { @@ -176,10 +186,10 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { ChainID: big.NewInt(1), }, control: expressLaneControl{ - controller: addr, + controller: crypto.PubkeyToAddress(testPriv.PublicKey), }, }, - sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), privKey), + sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv), valid: true, }, } @@ -196,11 +206,148 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { } } +func Test_expressLaneService_sequenceExpressLaneSubmission_nonceTooLow(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + els := &expressLaneService{ + control: expressLaneControl{ + sequence: 1, + }, + messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), + } + msg := &timeboost.ExpressLaneSubmission{ + Sequence: 0, + } + publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { + return nil + } + err := els.sequenceExpressLaneSubmission(ctx, msg, publishFn) + require.ErrorIs(t, err, timeboost.ErrSequenceNumberTooLow) +} + +func Test_expressLaneService_sequenceExpressLaneSubmission_duplicateNonce(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + els := &expressLaneService{ + control: expressLaneControl{ + sequence: 1, + }, + messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), + } + msg := &timeboost.ExpressLaneSubmission{ + Sequence: 2, + } + numPublished := 0 + publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { + numPublished += 1 + return nil + } + err := els.sequenceExpressLaneSubmission(ctx, msg, publishFn) + require.NoError(t, err) + // Because the message is for a future sequence number, it + // should get queued, but not yet published. + require.Equal(t, 0, numPublished) + // Sending it again should give us an error. + err = els.sequenceExpressLaneSubmission(ctx, msg, publishFn) + require.ErrorIs(t, err, timeboost.ErrDuplicateSequenceNumber) +} + +func Test_expressLaneService_sequenceExpressLaneSubmission_outOfOrder(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + els := &expressLaneService{ + control: expressLaneControl{ + sequence: 1, + }, + messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), + } + numPublished := 0 + publishedTxOrder := make([]uint64, 0) + publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { + numPublished += 1 + publishedTxOrder = append(publishedTxOrder, els.control.sequence) + return nil + } + messages := []*timeboost.ExpressLaneSubmission{ + { + Sequence: 10, + }, + { + Sequence: 5, + }, + { + Sequence: 1, + }, + { + Sequence: 4, + }, + { + Sequence: 2, + }, + } + for _, msg := range messages { + err := els.sequenceExpressLaneSubmission(ctx, msg, publishFn) + require.NoError(t, err) + } + // We should have only published 2, as we are missing sequence number 3. + require.Equal(t, 2, numPublished) + require.Equal(t, len(messages), len(els.messagesBySequenceNumber)) +} + +func Test_expressLaneService_sequenceExpressLaneSubmission_erroredTx(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + els := &expressLaneService{ + control: expressLaneControl{ + sequence: 1, + }, + messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), + } + numPublished := 0 + publishedTxOrder := make([]uint64, 0) + publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { + if tx == nil { + return errors.New("oops, bad tx") + } + numPublished += 1 + publishedTxOrder = append(publishedTxOrder, els.control.sequence) + return nil + } + messages := []*timeboost.ExpressLaneSubmission{ + { + Sequence: 1, + Transaction: &types.Transaction{}, + }, + { + Sequence: 3, + Transaction: &types.Transaction{}, + }, + { + Sequence: 2, + Transaction: nil, + }, + { + Sequence: 2, + Transaction: &types.Transaction{}, + }, + } + for _, msg := range messages { + if msg.Transaction == nil { + err := els.sequenceExpressLaneSubmission(ctx, msg, publishFn) + require.ErrorContains(t, err, "oops, bad tx") + } else { + err := els.sequenceExpressLaneSubmission(ctx, msg, publishFn) + require.NoError(t, err) + } + } + // One tx out of the four should have failed, so we should have only published 3. + require.Equal(t, 3, numPublished) + require.Equal(t, []uint64{1, 2, 3}, publishedTxOrder) +} + func Benchmark_expressLaneService_validateExpressLaneTx(b *testing.B) { b.StopTimer() - privKey, err := crypto.HexToECDSA("93be75cc4df7acbb636b6abe6de2c0446235ac1dc7da9f290a70d83f088b486d") - require.NoError(b, err) - addr := crypto.PubkeyToAddress(privKey.PublicKey) + addr := crypto.PubkeyToAddress(testPriv.PublicKey) es := &expressLaneService{ auctionContractAddr: common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), initialTimestamp: time.Now(), @@ -212,7 +359,7 @@ func Benchmark_expressLaneService_validateExpressLaneTx(b *testing.B) { controller: addr, }, } - sub := buildValidSubmission(b, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), privKey) + sub := buildValidSubmission(b, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv) b.StartTimer() for i := 0; i < b.N; i++ { err := es.validateExpressLaneTx(sub) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index b24880b7c2..f8116134b0 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -544,7 +544,7 @@ func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *time if err := s.expressLaneService.validateExpressLaneTx(msg); err != nil { return err } - return s.publishTransactionImpl(ctx, msg.Transaction, nil, false /* no delay, as this is an express lane tx */) + return s.expressLaneService.sequenceExpressLaneSubmission(ctx, msg, s.publishTransactionImpl) } func (s *Sequencer) preTxFilter(_ *params.ChainConfig, header *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, sender common.Address, l1Info *arbos.L1Info) error { diff --git a/timeboost/auctioneer_api.go b/timeboost/auctioneer_api.go index 3906422f7c..aa819d4f13 100644 --- a/timeboost/auctioneer_api.go +++ b/timeboost/auctioneer_api.go @@ -40,6 +40,7 @@ type ExpressLaneSubmission struct { AuctionContractAddress common.Address Transaction *types.Transaction Options *arbitrum_types.ConditionalOptions `json:"options"` + Sequence uint64 Signature []byte } @@ -105,9 +106,6 @@ func encodeExpressLaneSubmission( return buf.Bytes(), nil } -type AuctionResolutionSubmission struct { -} - func (a *AuctioneerAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return a.receiveBid(ctx, &Bid{ ChainId: bid.ChainId.ToInt(), diff --git a/timeboost/bids.go b/timeboost/bids.go index 2d7af893db..32afe15a92 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -27,6 +27,8 @@ var ( ErrNoOnchainController = errors.New("NO_ONCHAIN_CONTROLLER") ErrWrongAuctionContract = errors.New("WRONG_AUCTION_CONTRACT") ErrNotExpressLaneController = errors.New("NOT_EXPRESS_LANE_CONTROLLER") + ErrDuplicateSequenceNumber = errors.New("SUBMISSION_NONCE_ALREADY_SEEN") + ErrSequenceNumberTooLow = errors.New("SUBMISSION_NONCE_TOO_LOW") ) type Bid struct { From 52bbc58eab0445a430a37bb7aade16c95ee039c6 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Fri, 2 Aug 2024 11:34:27 -0500 Subject: [PATCH 037/109] add nonce precedence test --- execution/gethexec/express_lane_service.go | 2 +- system_tests/seqfeed_test.go | 286 +++++++++++++++------ timeboost/auctioneer_api.go | 10 +- timeboost/express_lane_client.go | 14 +- 4 files changed, 227 insertions(+), 85 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 83ad4f9716..4e8f0abc90 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -224,7 +224,7 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, es.chainConfig.ChainID) } if msg.AuctionContractAddress != es.auctionContractAddr { - return timeboost.ErrWrongAuctionContract + return errors.Wrapf(timeboost.ErrWrongAuctionContract, "msg auction contract address %s does not match sequencer auction contract address %s", msg.AuctionContractAddress, es.auctionContractAddr) } if !es.currentRoundHasController() { return timeboost.ErrNoOnchainController diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 74718df9cb..edce80e771 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -100,11 +100,207 @@ func TestSequencerFeed(t *testing.T) { } } -func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { +func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() + _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) + defer cleanupSeq() + chainId, err := seqClient.ChainID(ctx) + Require(t, err) + + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, seqClient) + Require(t, err) + info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + Require(t, err) + bobPriv := seqInfo.Accounts["Bob"].PrivateKey + + // Prepare a client that can submit txs to the sequencer via the express lane. + seqDial, err := rpc.Dial("http://localhost:9567") + Require(t, err) + expressLaneClient := timeboost.NewExpressLaneClient( + bobPriv, + chainId, + time.Unix(int64(info.OffsetTimestamp), 0), + time.Duration(info.RoundDurationSeconds)*time.Second, + auctionContractAddr, + seqDial, + ) + expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) + + // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's + // txs end up getting delayed by 200ms as she is not the express lane controller. + // In the end, Bob's txs should be ordered before Alice's during the round. + var wg sync.WaitGroup + wg.Add(2) + aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + err = seqClient.SendTransaction(ctx, aliceTx) + Require(t, err) + }(&wg) + + bobBoostableTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + time.Sleep(time.Millisecond * 10) + err = expressLaneClient.SendTransaction(ctx, bobBoostableTx) + Require(t, err) + }(&wg) + wg.Wait() + + // After round is done, verify that Bob beats Alice in the final sequence. + aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) + Require(t, err) + aliceBlock := aliceReceipt.BlockNumber.Uint64() + bobReceipt, err := seqClient.TransactionReceipt(ctx, bobBoostableTx.Hash()) + Require(t, err) + bobBlock := bobReceipt.BlockNumber.Uint64() + + if aliceBlock < bobBlock { + t.Fatal("Bob should have been sequenced before Alice with express lane") + } else if aliceBlock == bobBlock { + t.Log("Sequenced in same output block") + block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) + Require(t, err) + findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { + for index, tx := range transactions { + if tx.Hash() == txHash { + return index + } + } + return -1 + } + txes := block.Transactions() + indexA := findTransactionIndex(txes, aliceTx.Hash()) + indexB := findTransactionIndex(txes, bobBoostableTx.Hash()) + if indexA == -1 || indexB == -1 { + t.Fatal("Did not find txs in block") + } + if indexA < indexB { + t.Fatal("Bob should have been sequenced before Alice with express lane") + } + } +} + +func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) + defer cleanupSeq() + chainId, err := seqClient.ChainID(ctx) + Require(t, err) + + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, seqClient) + Require(t, err) + info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + Require(t, err) + bobPriv := seqInfo.Accounts["Bob"].PrivateKey + + // Prepare a client that can submit txs to the sequencer via the express lane. + seqDial, err := rpc.Dial("http://localhost:9567") + Require(t, err) + expressLaneClient := timeboost.NewExpressLaneClient( + bobPriv, + chainId, + time.Unix(int64(info.OffsetTimestamp), 0), + time.Duration(info.RoundDurationSeconds)*time.Second, + auctionContractAddr, + seqDial, + ) + expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) + + // We first generate an account for Charlie and transfer some balance to him. + seqInfo.GenerateAccount("Charlie") + TransferBalance(t, "Owner", "Charlie", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + + // During the express lane, Bob sends txs that do not belong to him, but he is the express lane controller so they + // will go through the express lane. + // These tx payloads are sent with nonces out of order, and those with nonces too high should fail. + var wg sync.WaitGroup + wg.Add(2) + aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + err = seqClient.SendTransaction(ctx, aliceTx) + Require(t, err) + }(&wg) + + ownerAddr := seqInfo.GetAddress("Owner") + txData := &types.DynamicFeeTx{ + To: &ownerAddr, + Gas: seqInfo.TransferGas, + Value: big.NewInt(1e12), + Nonce: 1, + GasFeeCap: aliceTx.GasFeeCap(), + Data: nil, + } + charlie1 := seqInfo.SignTxAs("Charlie", txData) + txData = &types.DynamicFeeTx{ + To: &ownerAddr, + Gas: seqInfo.TransferGas, + Value: big.NewInt(1e12), + Nonce: 0, + GasFeeCap: aliceTx.GasFeeCap(), + Data: nil, + } + charlie0 := seqInfo.SignTxAs("Charlie", txData) + var err2 error + go func(w *sync.WaitGroup) { + defer w.Done() + time.Sleep(time.Millisecond * 10) + // Send the express lane txs with nonces out of order + err2 = expressLaneClient.SendTransaction(ctx, charlie1) + err = expressLaneClient.SendTransaction(ctx, charlie0) + Require(t, err) + }(&wg) + wg.Wait() + if err2 == nil { + t.Fatal("Charlie should not be able to send tx with nonce 2") + } + // After round is done, verify that Charlie beats Alice in the final sequence, and that the emitted txs + // for Charlie are correct. + aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) + Require(t, err) + aliceBlock := aliceReceipt.BlockNumber.Uint64() + charlieReceipt, err := seqClient.TransactionReceipt(ctx, charlie0.Hash()) + Require(t, err) + charlieBlock := charlieReceipt.BlockNumber.Uint64() + + if aliceBlock < charlieBlock { + t.Fatal("Charlie should have been sequenced before Alice with express lane") + } else if aliceBlock == charlieBlock { + t.Log("Sequenced in same output block") + block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) + Require(t, err) + findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { + for index, tx := range transactions { + if tx.Hash() == txHash { + return index + } + } + return -1 + } + txes := block.Transactions() + indexA := findTransactionIndex(txes, aliceTx.Hash()) + indexB := findTransactionIndex(txes, charlie0.Hash()) + if indexA == -1 || indexB == -1 { + t.Fatal("Did not find txs in block") + } + if indexA < indexB { + t.Fatal("Charlie should have been sequenced before Alice with express lane") + } + } +} + +func setupExpressLaneAuction( + t *testing.T, + ctx context.Context, +) (*arbnode.Node, *ethclient.Client, *BlockchainTestInfo, common.Address, func()) { + builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, true) builderSeq.l2StackConfig.HTTPHost = "localhost" @@ -114,10 +310,9 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { builderSeq.execConfig.Sequencer.Enable = true builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ Enable: true, - ExpressLaneAdvantage: time.Millisecond * 200, + ExpressLaneAdvantage: time.Second * 5, } cleanupSeq := builderSeq.Build(t) - defer cleanupSeq() seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client // Set up the auction contracts on L2. @@ -214,12 +409,6 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { t.Fatal(err) } t.Log("Deployed all the auction manager stuff", auctionContractAddr) - - // Seed the accounts on L2. - t.Logf("Alice %+v and Bob %+v", seqInfo.Accounts["Alice"], seqInfo.Accounts["Bob"]) - _ = seqInfo - _ = seqClient - // We approve the spending of the erc20 for the autonomous auction contract and bid receiver // for both Alice and Bob. bidReceiverAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") @@ -267,9 +456,11 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { stackConf := node.Config{ DataDir: "", // ephemeral. HTTPPort: 9372, + HTTPHost: "localhost", HTTPModules: []string{timeboost.AuctioneerNamespace}, HTTPVirtualHosts: []string{"localhost"}, HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSHost: "localhost", WSPort: 9373, WSModules: []string{timeboost.AuctioneerNamespace}, GraphQLVirtualHosts: []string{"localhost"}, @@ -282,11 +473,12 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { stack, err := node.New(&stackConf) Require(t, err) auctioneer, err := timeboost.NewAuctioneer( - &auctionContractOpts, []*big.Int{chainId}, stack, seqClient, auctionContract, + &auctionContractOpts, []*big.Int{chainId}, stack, seqClient, proxyAddr, ) Require(t, err) go auctioneer.Start(ctx) + Require(t, stack.Start()) // Set up a bidder client for Alice and Bob. alicePriv := seqInfo.Accounts["Alice"].PrivateKey @@ -299,7 +491,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { }, seqClient, proxyAddr, - auctioneer, + "http://localhost:9372", ) Require(t, err) @@ -313,7 +505,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { }, seqClient, proxyAddr, - auctioneer, + "http://localhost:9372", ) Require(t, err) @@ -383,75 +575,7 @@ func TestSequencerFeed_ExpressLaneAuction(t *testing.T) { if !bobWon { t.Fatal("Bob should have won the auction") } - - t.Log("Now submitting txs to sequencer") - - // Prepare a client that can submit txs to the sequencer via the express lane. - seqDial, err := rpc.Dial(seqNode.Stack.HTTPEndpoint()) - Require(t, err) - expressLaneClient := timeboost.NewExpressLaneClient( - bobPriv, - chainId, - time.Unix(int64(info.OffsetTimestamp), 0), - roundDuration, - proxyAddr, - seqDial, - ) - expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) - - // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's - // txs end up getting delayed by 200ms as she is not the express lane controller. - // In the end, Bob's txs should be ordered before Alice's during the round. - var wg sync.WaitGroup - wg.Add(2) - aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) - go func(w *sync.WaitGroup) { - defer w.Done() - err = seqClient.SendTransaction(ctx, aliceTx) - Require(t, err) - }(&wg) - - bobBoostableTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) - go func(w *sync.WaitGroup) { - defer w.Done() - time.Sleep(time.Millisecond * 10) - err = expressLaneClient.SendTransaction(ctx, bobBoostableTx) - Require(t, err) - }(&wg) - wg.Wait() - - // After round is done, verify that Bob beats Alice in the final sequence. - aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) - Require(t, err) - aliceBlock := aliceReceipt.BlockNumber.Uint64() - bobReceipt, err := seqClient.TransactionReceipt(ctx, bobBoostableTx.Hash()) - Require(t, err) - bobBlock := bobReceipt.BlockNumber.Uint64() - - if aliceBlock < bobBlock { - t.Fatal("Bob should have been sequenced before Alice with express lane") - } else if aliceBlock == bobBlock { - t.Log("Sequenced in same output block") - block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) - Require(t, err) - findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { - for index, tx := range transactions { - if tx.Hash() == txHash { - return index - } - } - return -1 - } - txes := block.Transactions() - indexA := findTransactionIndex(txes, aliceTx.Hash()) - indexB := findTransactionIndex(txes, bobBoostableTx.Hash()) - if indexA == -1 || indexB == -1 { - t.Fatal("Did not find txs in block") - } - if indexA < indexB { - t.Fatal("Bob should have been sequenced before Alice with express lane") - } - } + return seqNode, seqClient, seqInfo, proxyAddr, cleanupSeq } func awaitAuctionResolved( diff --git a/timeboost/auctioneer_api.go b/timeboost/auctioneer_api.go index aa819d4f13..71902fc7bd 100644 --- a/timeboost/auctioneer_api.go +++ b/timeboost/auctioneer_api.go @@ -31,7 +31,8 @@ type JsonExpressLaneSubmission struct { AuctionContractAddress common.Address `json:"auctionContractAddress"` Transaction hexutil.Bytes `json:"transaction"` Options *arbitrum_types.ConditionalOptions `json:"options"` - Signature hexutil.Bytes `json:"signature"` + Sequence hexutil.Uint64 + Signature hexutil.Bytes `json:"signature"` } type ExpressLaneSubmission struct { @@ -55,6 +56,7 @@ func JsonSubmissionToGo(submission *JsonExpressLaneSubmission) (*ExpressLaneSubm AuctionContractAddress: submission.AuctionContractAddress, Transaction: tx, Options: submission.Options, + Sequence: uint64(submission.Sequence), Signature: submission.Signature, }, nil } @@ -70,6 +72,7 @@ func (els *ExpressLaneSubmission) ToJson() (*JsonExpressLaneSubmission, error) { AuctionContractAddress: els.AuctionContractAddress, Transaction: encoded, Options: els.Options, + Sequence: hexutil.Uint64(els.Sequence), Signature: els.Signature, }, nil } @@ -78,6 +81,7 @@ func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { return encodeExpressLaneSubmission( domainValue, els.ChainId, + els.Sequence, els.AuctionContractAddress, els.Round, els.Transaction, @@ -87,6 +91,7 @@ func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { func encodeExpressLaneSubmission( domainValue []byte, chainId *big.Int, + sequence uint64, auctionContractAddress common.Address, round uint64, tx *types.Transaction, @@ -94,6 +99,9 @@ func encodeExpressLaneSubmission( buf := new(bytes.Buffer) buf.Write(domainValue) buf.Write(padBigInt(chainId)) + seqBuf := make([]byte, 8) + binary.BigEndian.PutUint64(seqBuf, sequence) + buf.Write(seqBuf) buf.Write(auctionContractAddress[:]) roundBuf := make([]byte, 8) binary.BigEndian.PutUint64(roundBuf, round) diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go index a91a937f6b..b26251a153 100644 --- a/timeboost/express_lane_client.go +++ b/timeboost/express_lane_client.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "fmt" "math/big" + "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -19,12 +20,14 @@ import ( type ExpressLaneClient struct { stopwaiter.StopWaiter + sync.Mutex privKey *ecdsa.PrivateKey chainId *big.Int initialRoundTimestamp time.Time roundDuration time.Duration auctionContractAddr common.Address client *rpc.Client + sequence uint64 } func NewExpressLaneClient( @@ -42,10 +45,13 @@ func NewExpressLaneClient( roundDuration: roundDuration, auctionContractAddr: auctionContractAddr, client: client, + sequence: 0, } } func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) error { + elc.Lock() + defer elc.Unlock() // return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { encodedTx, err := transaction.MarshalBinary() if err != nil { @@ -56,6 +62,7 @@ func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction * Round: hexutil.Uint64(CurrentRound(elc.initialRoundTimestamp, elc.roundDuration)), AuctionContractAddress: elc.auctionContractAddr, Transaction: encodedTx, + Sequence: hexutil.Uint64(elc.sequence), Signature: hexutil.Bytes{}, } msgGo, err := JsonSubmissionToGo(msg) @@ -71,8 +78,11 @@ func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction * return err } msg.Signature = signature - err = elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) - return err + if err = elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg); err != nil { + return err + } + elc.sequence += 1 + return nil } func signSubmission(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { From 471d79eff8e675108895d470167eb7086d56412e Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Fri, 2 Aug 2024 12:49:39 -0500 Subject: [PATCH 038/109] add test for too many bids --- execution/gethexec/express_lane_service.go | 57 ++++++---- .../gethexec/express_lane_service_test.go | 105 +++++++++++------- timeboost/auctioneer.go | 35 ++++-- timeboost/auctioneer_test.go | 61 ++++++++-- timeboost/bids.go | 1 + timeboost/bids_test.go | 4 +- 6 files changed, 186 insertions(+), 77 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 4e8f0abc90..3d62f5b6c5 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -23,7 +24,6 @@ import ( ) type expressLaneControl struct { - round uint64 sequence uint64 controller common.Address } @@ -31,7 +31,6 @@ type expressLaneControl struct { type expressLaneService struct { stopwaiter.StopWaiter sync.RWMutex - control expressLaneControl auctionContractAddr common.Address initialTimestamp time.Time roundDuration time.Duration @@ -39,6 +38,7 @@ type expressLaneService struct { logs chan []*types.Log seqClient *ethclient.Client auctionContract *express_lane_auctiongen.ExpressLaneAuction + roundControl lru.BasicLRU[uint64, *expressLaneControl] messagesBySequenceNumber map[uint64]*timeboost.ExpressLaneSubmission } @@ -59,13 +59,10 @@ func newExpressLaneService( initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second return &expressLaneService{ - auctionContract: auctionContract, - chainConfig: chainConfig, - initialTimestamp: initialTimestamp, - control: expressLaneControl{ - controller: common.Address{}, - round: 0, - }, + auctionContract: auctionContract, + chainConfig: chainConfig, + initialTimestamp: initialTimestamp, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), // Keep 8 rounds cached. auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, seqClient: sequencerClient, @@ -99,6 +96,10 @@ func (es *expressLaneService) Start(ctxIn context.Context) { es.Lock() // Reset the sequence numbers map for the new round. es.messagesBySequenceNumber = make(map[uint64]*timeboost.ExpressLaneSubmission) + es.roundControl.Add(round, &expressLaneControl{ + controller: common.Address{}, + sequence: 0, + }) es.Unlock() } } @@ -144,11 +145,11 @@ func (es *expressLaneService) Start(ctxIn context.Context) { "round", it.Event.Round, "controller", it.Event.FirstPriceExpressLaneController, ) - // TODO: This is wrong because it ovewrites the upcoming round... es.Lock() - es.control.round = it.Event.Round - es.control.controller = it.Event.FirstPriceExpressLaneController - es.control.sequence = 0 // Sequence resets 0 for the new round. + es.roundControl.Add(it.Event.Round, &expressLaneControl{ + controller: it.Event.FirstPriceExpressLaneController, + sequence: 0, + }) es.Unlock() } fromBlock = toBlock @@ -164,7 +165,12 @@ func (es *expressLaneService) Start(ctxIn context.Context) { func (es *expressLaneService) currentRoundHasController() bool { es.Lock() defer es.Unlock() - return es.control.controller != (common.Address{}) + currRound := timeboost.CurrentRound(es.initialTimestamp, es.roundDuration) + control, ok := es.roundControl.Get(currRound) + if !ok { + return false + } + return control.controller != (common.Address{}) } func (es *expressLaneService) sequenceExpressLaneSubmission( @@ -179,8 +185,12 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( ) error { es.Lock() defer es.Unlock() + control, ok := es.roundControl.Get(msg.Round) + if !ok { + return timeboost.ErrNoOnchainController + } // Check if the submission nonce is too low. - if msg.Sequence < es.control.sequence { + if msg.Sequence < control.sequence { return timeboost.ErrSequenceNumberTooLow } // Check if a duplicate submission exists already, and reject if so. @@ -188,7 +198,7 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( return timeboost.ErrDuplicateSequenceNumber } // Log an informational warning if the message's sequence number is in the future. - if msg.Sequence > es.control.sequence { + if msg.Sequence > control.sequence { log.Warn("Received express lane submission with future sequence number", "sequence", msg.Sequence) } // Put into the the sequence number map. @@ -196,7 +206,7 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( for { // Get the next message in the sequence. - nextMsg, exists := es.messagesBySequenceNumber[es.control.sequence] + nextMsg, exists := es.messagesBySequenceNumber[control.sequence] if !exists { break } @@ -211,8 +221,9 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( return err } // Increase the global round sequence number. - es.control.sequence += 1 + control.sequence += 1 } + es.roundControl.Add(msg.Round, control) return nil } @@ -256,9 +267,13 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu return timeboost.ErrWrongSignature } sender := crypto.PubkeyToAddress(*pubkey) - es.Lock() - defer es.Unlock() - if sender != es.control.controller { + es.RLock() + defer es.RUnlock() + control, ok := es.roundControl.Get(msg.Round) + if !ok { + return timeboost.ErrNoOnchainController + } + if sender != control.controller { return timeboost.ErrNotExpressLaneController } return nil diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 11975781a6..eba15dc635 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" @@ -34,17 +35,23 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { es *expressLaneService sub *timeboost.ExpressLaneSubmission expectedErr error - controller common.Address + control expressLaneControl valid bool }{ { - name: "nil msg", - sub: nil, + name: "nil msg", + sub: nil, + es: &expressLaneService{ + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, expectedErr: timeboost.ErrMalformedData, }, { - name: "nil tx", - sub: &timeboost.ExpressLaneSubmission{}, + name: "nil tx", + sub: &timeboost.ExpressLaneSubmission{}, + es: &expressLaneService{ + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, expectedErr: timeboost.ErrMalformedData, }, { @@ -52,6 +59,9 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { sub: &timeboost.ExpressLaneSubmission{ Transaction: &types.Transaction{}, }, + es: &expressLaneService{ + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, expectedErr: timeboost.ErrMalformedData, }, { @@ -60,6 +70,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(2), @@ -75,6 +86,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(1), @@ -91,6 +103,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(1), @@ -109,9 +122,10 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - control: expressLaneControl{ - controller: common.Address{'b'}, - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(1), @@ -120,7 +134,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { Signature: []byte{'b'}, Round: 100, }, - expectedErr: timeboost.ErrBadRoundNumber, + expectedErr: timeboost.ErrNoOnchainController, }, { name: "malformed signature", @@ -131,9 +145,10 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - control: expressLaneControl{ - controller: common.Address{'b'}, - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(1), @@ -153,9 +168,10 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - control: expressLaneControl{ - controller: common.Address{'b'}, - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, }, sub: buildInvalidSignatureSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6")), expectedErr: timeboost.ErrNotExpressLaneController, @@ -169,9 +185,10 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - control: expressLaneControl{ - controller: common.Address{'b'}, - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, + control: expressLaneControl{ + controller: common.Address{'b'}, }, sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv), expectedErr: timeboost.ErrNotExpressLaneController, @@ -185,9 +202,10 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - control: expressLaneControl{ - controller: crypto.PubkeyToAddress(testPriv.PublicKey), - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + }, + control: expressLaneControl{ + controller: crypto.PubkeyToAddress(testPriv.PublicKey), }, sub: buildValidSubmission(t, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv), valid: true, @@ -196,6 +214,9 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.sub != nil { + tt.es.roundControl.Add(tt.sub.Round, &tt.control) + } err := tt.es.validateExpressLaneTx(tt.sub) if tt.valid { require.NoError(t, err) @@ -210,11 +231,12 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_nonceTooLow(t *testin ctx, cancel := context.WithCancel(context.Background()) defer cancel() els := &expressLaneService{ - control: expressLaneControl{ - sequence: 1, - }, messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), } + els.roundControl.Add(0, &expressLaneControl{ + sequence: 1, + }) msg := &timeboost.ExpressLaneSubmission{ Sequence: 0, } @@ -229,11 +251,12 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_duplicateNonce(t *tes ctx, cancel := context.WithCancel(context.Background()) defer cancel() els := &expressLaneService{ - control: expressLaneControl{ - sequence: 1, - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), } + els.roundControl.Add(0, &expressLaneControl{ + sequence: 1, + }) msg := &timeboost.ExpressLaneSubmission{ Sequence: 2, } @@ -256,16 +279,18 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_outOfOrder(t *testing ctx, cancel := context.WithCancel(context.Background()) defer cancel() els := &expressLaneService{ - control: expressLaneControl{ - sequence: 1, - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), } + els.roundControl.Add(0, &expressLaneControl{ + sequence: 1, + }) numPublished := 0 publishedTxOrder := make([]uint64, 0) + control, _ := els.roundControl.Get(0) publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { numPublished += 1 - publishedTxOrder = append(publishedTxOrder, els.control.sequence) + publishedTxOrder = append(publishedTxOrder, control.sequence) return nil } messages := []*timeboost.ExpressLaneSubmission{ @@ -298,19 +323,21 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_erroredTx(t *testing. ctx, cancel := context.WithCancel(context.Background()) defer cancel() els := &expressLaneService{ - control: expressLaneControl{ - sequence: 1, - }, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), } + els.roundControl.Add(0, &expressLaneControl{ + sequence: 1, + }) numPublished := 0 publishedTxOrder := make([]uint64, 0) + control, _ := els.roundControl.Get(0) publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { if tx == nil { return errors.New("oops, bad tx") } numPublished += 1 - publishedTxOrder = append(publishedTxOrder, els.control.sequence) + publishedTxOrder = append(publishedTxOrder, control.sequence) return nil } messages := []*timeboost.ExpressLaneSubmission{ @@ -352,13 +379,15 @@ func Benchmark_expressLaneService_validateExpressLaneTx(b *testing.B) { auctionContractAddr: common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), initialTimestamp: time.Now(), roundDuration: time.Minute, + roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - control: expressLaneControl{ - controller: addr, - }, } + es.roundControl.Add(0, &expressLaneControl{ + sequence: 1, + controller: addr, + }) sub := buildValidSubmission(b, common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), testPriv) b.StartTimer() for i := 0; i < b.N; i++ { diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 4c84f01e36..bde6793153 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -51,6 +51,9 @@ type Auctioneer struct { reserveSubmissionDuration time.Duration reservePriceLock sync.RWMutex reservePrice *big.Int + sync.RWMutex + bidsPerSenderInRound map[common.Address]uint8 + maxBidsPerSenderInRound uint8 } func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { @@ -106,6 +109,8 @@ func NewAuctioneer( reserveSubmissionDuration: reserveSubmissionDuration, reservePrice: reservePrice, domainValue: domainValue, + bidsPerSenderInRound: make(map[common.Address]uint8), + maxBidsPerSenderInRound: 5, // 5 max bids per sender address in a round. } for _, o := range opts { o(am) @@ -123,7 +128,7 @@ func NewAuctioneer( // ReceiveBid validates and adds a bid to the bid cache. func (a *Auctioneer) receiveBid(ctx context.Context, b *Bid) error { - vb, err := a.validateBid(b) + vb, err := a.validateBid(b, a.auctionContract.BalanceOf, a.fetchReservePrice) if err != nil { return err } @@ -234,7 +239,11 @@ func (a *Auctioneer) fetchReservePrice() *big.Int { return new(big.Int).Set(a.reservePrice) } -func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { +func (a *Auctioneer) validateBid( + bid *Bid, + balanceCheckerFn func(opts *bind.CallOpts, addr common.Address) (*big.Int, error), + fetchReservePriceFn func() *big.Int, +) (*validatedBid, error) { // Check basic integrity. if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") @@ -273,7 +282,7 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { } // Check bid is higher than reserve price. - reservePrice := a.fetchReservePrice() + reservePrice := fetchReservePriceFn() if bid.Amount.Cmp(reservePrice) == -1 { return nil, errors.Wrapf(ErrReservePriceNotMet, "reserve price %s, bid %s", reservePrice.String(), bid.Amount.String()) } @@ -307,13 +316,21 @@ func (a *Auctioneer) validateBid(bid *Bid) (*validatedBid, error) { if !verifySignature(pubkey, packedBidBytes, sigItem) { return nil, ErrWrongSignature } + // Check how many bids the bidder has sent in this round and cap according to a limit. bidder := crypto.PubkeyToAddress(*pubkey) - // Validate if the user if a depositor in the contract and has enough balance for the bid. - // TODO: Retry some number of times if flakey connection. - // TODO: Validate reserve price against amount of bid. - // TODO: No need to do anything expensive if the bid coming is in invalid. - // Cache this if the received time of the bid is too soon. Include the arrival timestamp. - depositBal, err := a.auctionContract.BalanceOf(&bind.CallOpts{}, bidder) + a.Lock() + numBids, ok := a.bidsPerSenderInRound[bidder] + if !ok { + a.bidsPerSenderInRound[bidder] = 1 + } + if numBids >= a.maxBidsPerSenderInRound { + a.Unlock() + return nil, errors.Wrapf(ErrTooManyBids, "bidder %s has already sent the maximum allowed bids = %d in this round", bidder.Hex(), numBids) + } + a.bidsPerSenderInRound[bidder]++ + a.Unlock() + + depositBal, err := balanceCheckerFn(&bind.CallOpts{}, bidder) if err != nil { return nil, err } diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 5880061566..3486bc47a8 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -95,24 +96,70 @@ func TestAuctioneer_validateBid(t *testing.T) { for _, tt := range tests { a := Auctioneer{ - chainId: []*big.Int{big.NewInt(1)}, - initialRoundTimestamp: time.Now().Add(-time.Second), - reservePrice: big.NewInt(2), - roundDuration: time.Minute, - auctionClosingDuration: 45 * time.Second, - auctionContract: setup.expressLaneAuction, + chainId: []*big.Int{big.NewInt(1)}, + initialRoundTimestamp: time.Now().Add(-time.Second), + reservePrice: big.NewInt(2), + roundDuration: time.Minute, + auctionClosingDuration: 45 * time.Second, + auctionContract: setup.expressLaneAuction, + bidsPerSenderInRound: make(map[common.Address]uint8), + maxBidsPerSenderInRound: 5, } if tt.auctionClosed { a.roundDuration = 0 } t.Run(tt.name, func(t *testing.T) { - _, err := a.validateBid(tt.bid) + _, err := a.validateBid(tt.bid, setup.expressLaneAuction.BalanceOf, a.fetchReservePrice) require.ErrorIs(t, err, tt.expectedErr) require.Contains(t, err.Error(), tt.errMsg) }) } } +func TestAuctioneer_validateBid_perRoundBidLimitReached(t *testing.T) { + balanceCheckerFn := func(_ *bind.CallOpts, _ common.Address) (*big.Int, error) { + return big.NewInt(10), nil + } + fetchReservePriceFn := func() *big.Int { + return big.NewInt(0) + } + auctionContractAddr := common.Address{'a'} + a := Auctioneer{ + chainId: []*big.Int{big.NewInt(1)}, + initialRoundTimestamp: time.Now().Add(-time.Second), + reservePrice: big.NewInt(2), + roundDuration: time.Minute, + auctionClosingDuration: 45 * time.Second, + bidsPerSenderInRound: make(map[common.Address]uint8), + maxBidsPerSenderInRound: 5, + auctionContractAddr: auctionContractAddr, + } + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + bid := &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: auctionContractAddr, + ChainId: big.NewInt(1), + Round: 1, + Amount: big.NewInt(3), + Signature: []byte{'a'}, + } + bidValues, err := encodeBidValues(domainValue, bid.ChainId, bid.AuctionContractAddress, bid.Round, bid.Amount, bid.ExpressLaneController) + require.NoError(t, err) + + signature, err := buildSignature(privateKey, bidValues) + require.NoError(t, err) + + bid.Signature = signature + for i := 0; i < int(a.maxBidsPerSenderInRound)-1; i++ { + _, err := a.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) + require.NoError(t, err) + } + _, err = a.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) + require.ErrorIs(t, err, ErrTooManyBids) + +} + func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { prefixedData := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(data))), data...)) signature, err := crypto.Sign(prefixedData, privateKey) diff --git a/timeboost/bids.go b/timeboost/bids.go index 32afe15a92..37d3fbb089 100644 --- a/timeboost/bids.go +++ b/timeboost/bids.go @@ -29,6 +29,7 @@ var ( ErrNotExpressLaneController = errors.New("NOT_EXPRESS_LANE_CONTROLLER") ErrDuplicateSequenceNumber = errors.New("SUBMISSION_NONCE_ALREADY_SEEN") ErrSequenceNumberTooLow = errors.New("SUBMISSION_NONCE_TOO_LOW") + ErrTooManyBids = errors.New("PER_ROUND_BID_LIMIT_REACHED") ) type Bid struct { diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index d52155922f..9d38bda4ff 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -83,7 +83,7 @@ func TestReceiveBid_OK(t *testing.T) { require.NoError(t, err) // Check the bid passes validation. - _, err = am.validateBid(newBid) + _, err = am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) require.NoError(t, err) topTwoBids := am.bidCache.topTwoBids() @@ -231,7 +231,7 @@ func BenchmarkBidValidation(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - am.validateBid(newBid) + am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) } } From 3b8e6a88ad58a18a59f793ca2bffe34809a1fd8a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Fri, 2 Aug 2024 12:56:33 -0500 Subject: [PATCH 039/109] validate bids passing --- timeboost/auctioneer_test.go | 50 ++++++++++++++++++++---------------- timeboost/bids_test.go | 18 ++++++------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 3486bc47a8..042e82d240 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -15,6 +15,7 @@ import ( ) func TestAuctioneer_validateBid(t *testing.T) { + setup := setupAuctionTest(t, context.Background()) tests := []struct { name string bid *Bid @@ -32,21 +33,24 @@ func TestAuctioneer_validateBid(t *testing.T) { name: "empty express lane controller address", bid: &Bid{}, expectedErr: ErrMalformedData, - errMsg: "empty express lane controller address", + errMsg: "incorrect auction contract address", }, { name: "incorrect chain id", bid: &Bid{ - ExpressLaneController: common.Address{'b'}, + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(50), }, expectedErr: ErrWrongChainId, - errMsg: "can not auction for chain id: 0", + errMsg: "can not auction for chain id: 50", }, { name: "incorrect round", bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - ChainId: big.NewInt(1), + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), }, expectedErr: ErrBadRoundNumber, errMsg: "wanted 1, got 0", @@ -54,9 +58,10 @@ func TestAuctioneer_validateBid(t *testing.T) { { name: "auction is closed", bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - ChainId: big.NewInt(1), - Round: 1, + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), + Round: 1, }, expectedErr: ErrBadRoundNumber, errMsg: "auction is closed", @@ -65,10 +70,11 @@ func TestAuctioneer_validateBid(t *testing.T) { { name: "lower than reserved price", bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - ChainId: big.NewInt(1), - Round: 1, - Amount: big.NewInt(1), + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), + Round: 1, + Amount: big.NewInt(1), }, expectedErr: ErrReservePriceNotMet, errMsg: "reserve price 2, bid 1", @@ -76,24 +82,23 @@ func TestAuctioneer_validateBid(t *testing.T) { { name: "incorrect signature", bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - ChainId: big.NewInt(1), - Round: 1, - Amount: big.NewInt(3), - Signature: []byte{'a'}, + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), + Round: 1, + Amount: big.NewInt(3), + Signature: []byte{'a'}, }, expectedErr: ErrMalformedData, errMsg: "signature length is not 65", }, { name: "not a depositor", - bid: buildValidBid(t), + bid: buildValidBid(t, setup.expressLaneAuctionAddr), expectedErr: ErrNotDepositor, }, } - setup := setupAuctionTest(t, context.Background()) - for _, tt := range tests { a := Auctioneer{ chainId: []*big.Int{big.NewInt(1)}, @@ -102,6 +107,7 @@ func TestAuctioneer_validateBid(t *testing.T) { roundDuration: time.Minute, auctionClosingDuration: 45 * time.Second, auctionContract: setup.expressLaneAuction, + auctionContractAddr: setup.expressLaneAuctionAddr, bidsPerSenderInRound: make(map[common.Address]uint8), maxBidsPerSenderInRound: 5, } @@ -169,12 +175,12 @@ func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { return signature, nil } -func buildValidBid(t *testing.T) *Bid { +func buildValidBid(t *testing.T, auctionContractAddr common.Address) *Bid { privateKey, err := crypto.GenerateKey() require.NoError(t, err) b := &Bid{ ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: common.Address{'c'}, + AuctionContractAddress: auctionContractAddr, ChainId: big.NewInt(1), Round: 1, Amount: big.NewInt(3), diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go index 9d38bda4ff..a32f42995d 100644 --- a/timeboost/bids_test.go +++ b/timeboost/bids_test.go @@ -98,7 +98,7 @@ func TestTopTwoBids(t *testing.T) { expected *auctionResult }{ { - name: "Single Bid", + name: "single bid", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, }, @@ -108,7 +108,7 @@ func TestTopTwoBids(t *testing.T) { }, }, { - name: "Two Bids with Different Amounts", + name: "two bids with different amounts", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, @@ -119,7 +119,7 @@ func TestTopTwoBids(t *testing.T) { }, }, { - name: "Two Bids with Same Amount and Different Hashes", + name: "two bids same amount but different hashes", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, @@ -130,7 +130,7 @@ func TestTopTwoBids(t *testing.T) { }, }, { - name: "More Than Two Bids, All Unique Amounts", + name: "many bids but all same amount", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, @@ -142,7 +142,7 @@ func TestTopTwoBids(t *testing.T) { }, }, { - name: "More Than Two Bids, Some with Same Amounts", + name: "many bids with some tied and others with different amounts", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, @@ -155,7 +155,7 @@ func TestTopTwoBids(t *testing.T) { }, }, { - name: "More Than Two Bids, Tied for Second Place", + name: "many bids and tied for second place", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, @@ -167,7 +167,7 @@ func TestTopTwoBids(t *testing.T) { }, }, { - name: "All Bids with the Same Amount", + name: "all bids with the same amount", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, @@ -179,12 +179,12 @@ func TestTopTwoBids(t *testing.T) { }, }, { - name: "No Bids", + name: "no bids", bids: nil, expected: &auctionResult{firstPlace: nil, secondPlace: nil}, }, { - name: "Identical Bids", + name: "identical bids", bids: map[common.Address]*validatedBid{ common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, From 79dc1efeffbe41aeb5044574112341ee72a604ab Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Fri, 2 Aug 2024 13:01:23 -0500 Subject: [PATCH 040/109] resolve auction --- cmd/autonomous-auctioneer/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go index beaacffb07..c2c2e93ae8 100644 --- a/cmd/autonomous-auctioneer/config.go +++ b/cmd/autonomous-auctioneer/config.go @@ -66,7 +66,7 @@ var AuctioneerConfigDefault = AuctioneerConfig{ PprofCfg: genericconf.PProfDefault, } -func ValidationNodeConfigAddOptions(f *flag.FlagSet) { +func AuctioneerConfigAddOptions(f *flag.FlagSet) { genericconf.ConfConfigAddOptions("conf", f) f.String("log-level", AuctioneerConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") f.String("log-type", AuctioneerConfigDefault.LogType, "log type (plaintext or json)") @@ -135,8 +135,10 @@ var DefaultAuctioneerStackConfig = node.Config{ AuthPort: node.DefaultAuthPort, AuthVirtualHosts: node.DefaultAuthVhosts, HTTPModules: []string{timeboost.AuctioneerNamespace}, + HTTPHost: "localhost", HTTPVirtualHosts: []string{"localhost"}, HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSHost: "localhost", WSPort: node.DefaultWSPort, WSModules: []string{timeboost.AuctioneerNamespace}, GraphQLVirtualHosts: []string{"localhost"}, From 91a9188055ff79f05ff99078df9aa3483e49c933 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Fri, 2 Aug 2024 11:19:34 -0700 Subject: [PATCH 041/109] Add sqlite db for bids --- go.mod | 8 +++- go.sum | 10 +++++ timeboost/db/db.go | 79 +++++++++++++++++++++++++++++++++++++++ timeboost/db/db_test.go | 82 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 timeboost/db/db.go create mode 100644 timeboost/db/db_test.go diff --git a/go.mod b/go.mod index 4120765df8..a7ea6373ea 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,12 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) -require github.com/google/go-querystring v1.1.0 // indirect +require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect +) require ( github.com/DataDog/zstd v1.4.5 // indirect @@ -165,7 +170,6 @@ require ( go.opencensus.io v0.22.5 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sync v0.5.0 golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index ff4726b22f..7fa19235fb 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= @@ -38,6 +39,8 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= @@ -277,6 +280,7 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -445,6 +449,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -478,6 +484,7 @@ github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7 github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= @@ -503,6 +510,7 @@ github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awS github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -526,6 +534,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= diff --git a/timeboost/db/db.go b/timeboost/db/db.go new file mode 100644 index 0000000000..b20d669933 --- /dev/null +++ b/timeboost/db/db.go @@ -0,0 +1,79 @@ +package db + +import ( + "os" + + "github.com/jmoiron/sqlx" + "github.com/offchainlabs/nitro/timeboost" +) + +type Database interface { + SaveBids(bids []*timeboost.Bid) error + DeleteBids(round uint64) +} + +type BidOption func(b *BidQuery) + +type BidQuery struct { + filters []string + args []interface{} + startRound int + endRound int +} + +type Db struct { + db *sqlx.DB +} + +func NewDb(path string) (*Db, error) { + //#nosec G304 + if _, err := os.Stat(path); err != nil { + _, err = os.Create(path) + if err != nil { + return nil, err + } + } + db, err := sqlx.Open("sqlite3", path) + if err != nil { + return nil, err + } + return &Db{ + db: db, + }, nil +} + +func (d *Db) InsertBids(bids []*timeboost.Bid) error { + for _, b := range bids { + if err := d.InsertBid(b); err != nil { + return err + } + } + return nil +} + +func (d *Db) InsertBid(b *timeboost.Bid) error { + query := `INSERT INTO Bids ( + ChainID, ExpressLaneController, AuctionContractAddress, Round, Amount, Signature + ) VALUES ( + :ChainID, :ExpressLaneController, :AuctionContractAddress, :Round, :Amount, :Signature + )` + params := map[string]interface{}{ + "ChainID": b.ChainId.String(), + "ExpressLaneController": b.ExpressLaneController.Hex(), + "AuctionContractAddress": b.AuctionContractAddress.Hex(), + "Round": b.Round, + "Amount": b.Amount.String(), + "Signature": b.Signature, + } + _, err := d.db.NamedExec(query, params) + if err != nil { + return err + } + return nil +} + +func (d *Db) DeleteBids(round uint64) error { + query := `DELETE FROM Bids WHERE Round < ?` + _, err := d.db.Exec(query, round) + return err +} diff --git a/timeboost/db/db_test.go b/timeboost/db/db_test.go new file mode 100644 index 0000000000..065430fbc2 --- /dev/null +++ b/timeboost/db/db_test.go @@ -0,0 +1,82 @@ +package db + +import ( + "math/big" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/ethereum/go-ethereum/common" + "github.com/jmoiron/sqlx" + "github.com/offchainlabs/nitro/timeboost" + "github.com/stretchr/testify/assert" +) + +func TestInsertBids(t *testing.T) { + db, mock, err := sqlmock.New() + assert.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + + d := &Db{db: sqlxDB} + + bids := []*timeboost.Bid{ + { + ChainId: big.NewInt(1), + ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000001"), + AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"), + Round: 1, + Amount: big.NewInt(100), + Signature: []byte("signature1"), + }, + { + ChainId: big.NewInt(2), + ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000003"), + AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000004"), + Round: 2, + Amount: big.NewInt(200), + Signature: []byte("signature2"), + }, + } + + for _, bid := range bids { + mock.ExpectExec("INSERT INTO Bids").WithArgs( + bid.ChainId.String(), + bid.ExpressLaneController.Hex(), + bid.AuctionContractAddress.Hex(), + bid.Round, + bid.Amount.String(), + bid.Signature, + ).WillReturnResult(sqlmock.NewResult(1, 1)) + } + + err = d.InsertBids(bids) + assert.NoError(t, err) + + err = mock.ExpectationsWereMet() + assert.NoError(t, err) +} + +func TestDeleteBidsLowerThanRound(t *testing.T) { + db, mock, err := sqlmock.New() + assert.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + + d := &Db{ + db: sqlxDB, + } + + round := uint64(10) + + mock.ExpectExec("DELETE FROM Bids WHERE Round < ?"). + WithArgs(round). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err = d.DeleteBids(round) + assert.NoError(t, err) + + err = mock.ExpectationsWereMet() + assert.NoError(t, err) +} From 60bbb9fbd61ad46b75d2b39407af8247aa0a1744 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 6 Aug 2024 12:18:46 -0500 Subject: [PATCH 042/109] using redis streams --- cmd/autonomous-auctioneer/config.go | 31 +- cmd/autonomous-auctioneer/main.go | 214 +++++++--- system_tests/express_lane_timeboost_test.go | 15 + system_tests/seqfeed_test.go | 2 +- timeboost/auctioneer.go | 447 +++++++++----------- timeboost/auctioneer_test.go | 289 +++++-------- timeboost/bid_cache.go | 69 +++ timeboost/bid_cache_test.go | 272 ++++++++++++ timeboost/bid_validator.go | 301 +++++++++++++ timeboost/bid_validator_test.go | 199 +++++++++ timeboost/bids.go | 172 -------- timeboost/bids_test.go | 275 ------------ timeboost/errors.go | 19 + timeboost/setup_test.go | 3 +- timeboost/ticker.go | 17 + timeboost/{auctioneer_api.go => types.go} | 122 +++++- 16 files changed, 1473 insertions(+), 974 deletions(-) create mode 100644 system_tests/express_lane_timeboost_test.go create mode 100644 timeboost/bid_cache.go create mode 100644 timeboost/bid_cache_test.go create mode 100644 timeboost/bid_validator.go create mode 100644 timeboost/bid_validator_test.go delete mode 100644 timeboost/bids.go delete mode 100644 timeboost/bids_test.go create mode 100644 timeboost/errors.go rename timeboost/{auctioneer_api.go => types.go} (51%) diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go index c2c2e93ae8..3e1e76d782 100644 --- a/cmd/autonomous-auctioneer/config.go +++ b/cmd/autonomous-auctioneer/config.go @@ -16,7 +16,14 @@ import ( flag "github.com/spf13/pflag" ) -type AuctioneerConfig struct { +const ( + bidValidatorMode = "bid-validator" + autonomousAuctioneerMode = "autonomous-auctioneer" +) + +type AutonomousAuctioneerConfig struct { + Mode string `koanf:"mode"` + Persistent conf.PersistentConfig `koanf:"persistent"` Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` LogLevel string `koanf:"log-level" reload:"hot"` LogType string `koanf:"log-type" reload:"hot"` @@ -53,8 +60,9 @@ var IPCConfigDefault = genericconf.IPCConfig{ Path: "", } -var AuctioneerConfigDefault = AuctioneerConfig{ +var AutonomousAuctioneerConfigDefault = AutonomousAuctioneerConfig{ Conf: genericconf.ConfConfigDefault, + Mode: autonomousAuctioneerMode, LogLevel: "INFO", LogType: "plaintext", HTTP: HTTPConfigDefault, @@ -63,32 +71,33 @@ var AuctioneerConfigDefault = AuctioneerConfig{ Metrics: false, MetricsServer: genericconf.MetricsServerConfigDefault, PProf: false, + Persistent: conf.PersistentConfigDefault, PprofCfg: genericconf.PProfDefault, } func AuctioneerConfigAddOptions(f *flag.FlagSet) { genericconf.ConfConfigAddOptions("conf", f) - f.String("log-level", AuctioneerConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") - f.String("log-type", AuctioneerConfigDefault.LogType, "log type (plaintext or json)") + f.String("log-level", AutonomousAuctioneerConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") + f.String("log-type", AutonomousAuctioneerConfigDefault.LogType, "log type (plaintext or json)") genericconf.FileLoggingConfigAddOptions("file-logging", f) conf.PersistentConfigAddOptions("persistent", f) genericconf.HTTPConfigAddOptions("http", f) genericconf.WSConfigAddOptions("ws", f) genericconf.IPCConfigAddOptions("ipc", f) genericconf.AuthRPCConfigAddOptions("auth", f) - f.Bool("metrics", AuctioneerConfigDefault.Metrics, "enable metrics") + f.Bool("metrics", AutonomousAuctioneerConfigDefault.Metrics, "enable metrics") genericconf.MetricsServerAddOptions("metrics-server", f) - f.Bool("pprof", AuctioneerConfigDefault.PProf, "enable pprof") + f.Bool("pprof", AutonomousAuctioneerConfigDefault.PProf, "enable pprof") genericconf.PProfAddOptions("pprof-cfg", f) } -func (c *AuctioneerConfig) ShallowClone() *AuctioneerConfig { - config := &AuctioneerConfig{} +func (c *AutonomousAuctioneerConfig) ShallowClone() *AutonomousAuctioneerConfig { + config := &AutonomousAuctioneerConfig{} *config = *c return config } -func (c *AuctioneerConfig) CanReload(new *AuctioneerConfig) error { +func (c *AutonomousAuctioneerConfig) CanReload(new *AutonomousAuctioneerConfig) error { var check func(node, other reflect.Value, path string) var err error @@ -120,11 +129,11 @@ func (c *AuctioneerConfig) CanReload(new *AuctioneerConfig) error { return err } -func (c *AuctioneerConfig) GetReloadInterval() time.Duration { +func (c *AutonomousAuctioneerConfig) GetReloadInterval() time.Duration { return c.Conf.ReloadInterval } -func (c *AuctioneerConfig) Validate() error { +func (c *AutonomousAuctioneerConfig) Validate() error { return nil } diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go index 139a0a8efe..ee464d2365 100644 --- a/cmd/autonomous-auctioneer/main.go +++ b/cmd/autonomous-auctioneer/main.go @@ -2,13 +2,22 @@ package main import ( "context" + "fmt" "os" "os/signal" + "path/filepath" "syscall" "time" + flag "github.com/spf13/pflag" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/metrics/exp" + "github.com/ethereum/go-ethereum/node" + "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/cmd/util/confighelpers" + "github.com/offchainlabs/nitro/validator/valnode" ) func main() { @@ -18,22 +27,22 @@ func main() { // Checks metrics and PProf flag, runs them if enabled. // Note: they are separate so one can enable/disable them as they wish, the only // requirement is that they can't run on the same address and port. -func startMetrics() error { - // mAddr := fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port) - // pAddr := fmt.Sprintf("%v:%v", cfg.PprofCfg.Addr, cfg.PprofCfg.Port) - // if cfg.Metrics && !metrics.Enabled { - // return fmt.Errorf("metrics must be enabled via command line by adding --metrics, json config has no effect") - // } - // if cfg.Metrics && cfg.PProf && mAddr == pAddr { - // return fmt.Errorf("metrics and pprof cannot be enabled on the same address:port: %s", mAddr) - // } - // if cfg.Metrics { - go metrics.CollectProcessMetrics(time.Second) - // exp.Setup(fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port)) - // } - // if cfg.PProf { - // genericconf.StartPprof(pAddr) - // } +func startMetrics(cfg *AutonomousAuctioneerConfig) error { + mAddr := fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port) + pAddr := fmt.Sprintf("%v:%v", cfg.PprofCfg.Addr, cfg.PprofCfg.Port) + if cfg.Metrics && !metrics.Enabled { + return fmt.Errorf("metrics must be enabled via command line by adding --metrics, json config has no effect") + } + if cfg.Metrics && cfg.PProf && mAddr == pAddr { + return fmt.Errorf("metrics and pprof cannot be enabled on the same address:port: %s", mAddr) + } + if cfg.Metrics { + go metrics.CollectProcessMetrics(time.Second) + exp.Setup(fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port)) + } + if cfg.PProf { + genericconf.StartPprof(pAddr) + } return nil } @@ -41,71 +50,99 @@ func mainImpl() int { ctx, cancelFunc := context.WithCancel(context.Background()) defer cancelFunc() - _ = ctx + args := os.Args[1:] + nodeConfig, err := parseAuctioneerArgs(ctx, args) + if err != nil { + // confighelpers.PrintErrorAndExit(err, printSampleUsage) + panic(err) + } + stackConf := DefaultAuctioneerStackConfig + stackConf.DataDir = "" // ephemeral + nodeConfig.HTTP.Apply(&stackConf) + nodeConfig.WS.Apply(&stackConf) + nodeConfig.IPC.Apply(&stackConf) + stackConf.P2P.ListenAddr = "" + stackConf.P2P.NoDial = true + stackConf.P2P.NoDiscovery = true + vcsRevision, strippedRevision, vcsTime := confighelpers.GetVersion() + stackConf.Version = strippedRevision + + pathResolver := func(workdir string) func(string) string { + if workdir == "" { + workdir, err = os.Getwd() + if err != nil { + log.Warn("Failed to get workdir", "err", err) + } + } + return func(path string) string { + if filepath.IsAbs(path) { + return path + } + return filepath.Join(workdir, path) + } + } - if err := startMetrics(); err != nil { - log.Error("Error starting metrics", "error", err) + err = genericconf.InitLog(nodeConfig.LogType, nodeConfig.LogLevel, &nodeConfig.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error initializing logging: %v\n", err) return 1 } + if stackConf.JWTSecret == "" && stackConf.AuthAddr != "" { + filename := pathResolver(nodeConfig.Persistent.GlobalConfig)("jwtsecret") + if err := genericconf.TryCreatingJWTSecret(filename); err != nil { + log.Error("Failed to prepare jwt secret file", "err", err) + return 1 + } + stackConf.JWTSecret = filename + } - // stackConf := DefaultValidationNodeStackConfig - // stackConf.DataDir = "" // ephemeral - // nodeConfig.HTTP.Apply(&stackConf) - // nodeConfig.WS.Apply(&stackConf) - // nodeConfig.Auth.Apply(&stackConf) - // nodeConfig.IPC.Apply(&stackConf) - // stackConf.P2P.ListenAddr = "" - // stackConf.P2P.NoDial = true - // stackConf.P2P.NoDiscovery = true - // vcsRevision, strippedRevision, vcsTime := confighelpers.GetVersion() - // stackConf.Version = strippedRevision - - // pathResolver := func(workdir string) func(string) string { - // if workdir == "" { - // workdir, err = os.Getwd() - // if err != nil { - // log.Warn("Failed to get workdir", "err", err) - // } - // } - // return func(path string) string { - // if filepath.IsAbs(path) { - // return path - // } - // return filepath.Join(workdir, path) - // } - // } + log.Info("Running Arbitrum nitro validation node", "revision", vcsRevision, "vcs.time", vcsTime) - // err = genericconf.InitLog(nodeConfig.LogType, nodeConfig.LogLevel, &nodeConfig.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) - // if err != nil { - // fmt.Fprintf(os.Stderr, "Error initializing logging: %v\n", err) - // return 1 - // } - // if stackConf.JWTSecret == "" && stackConf.AuthAddr != "" { - // filename := pathResolver(nodeConfig.Persistent.GlobalConfig)("jwtsecret") - // if err := genericconf.TryCreatingJWTSecret(filename); err != nil { - // log.Error("Failed to prepare jwt secret file", "err", err) - // return 1 - // } - // stackConf.JWTSecret = filename - // } + liveNodeConfig := genericconf.NewLiveConfig[*AutonomousAuctioneerConfig](args, nodeConfig, parseAuctioneerArgs) + liveNodeConfig.SetOnReloadHook(func(oldCfg *AutonomousAuctioneerConfig, newCfg *AutonomousAuctioneerConfig) error { - // log.Info("Running Arbitrum nitro validation node", "revision", vcsRevision, "vcs.time", vcsTime) + return genericconf.InitLog(newCfg.LogType, newCfg.LogLevel, &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) + }) - // liveNodeConfig := genericconf.NewLiveConfig[*ValidationNodeConfig](args, nodeConfig, ParseNode) - // liveNodeConfig.SetOnReloadHook(func(oldCfg *ValidationNodeConfig, newCfg *ValidationNodeConfig) error { + valnode.EnsureValidationExposedViaAuthRPC(&stackConf) - // return genericconf.InitLog(newCfg.LogType, newCfg.LogLevel, &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) - // }) + stack, err := node.New(&stackConf) + if err != nil { + flag.Usage() + log.Crit("failed to initialize geth stack", "err", err) + } - // valnode.EnsureValidationExposedViaAuthRPC(&stackConf) + if err := startMetrics(nodeConfig); err != nil { + log.Error("Error starting metrics", "error", err) + return 1 + } - // stack, err := node.New(&stackConf) + fatalErrChan := make(chan error, 10) + + // valNode, err := valnode.CreateValidationNode( + // func() *valnode.Config { return &liveNodeConfig.Get().Validation }, + // stack, + // fatalErrChan, + // ) // if err != nil { - // flag.Usage() - // log.Crit("failed to initialize geth stack", "err", err) + // log.Error("couldn't init validation node", "err", err) + // return 1 // } - fatalErrChan := make(chan error, 10) + // err = valNode.Start(ctx) + // if err != nil { + // log.Error("error starting validator node", "err", err) + // return 1 + // } + err = stack.Start() + if err != nil { + fatalErrChan <- fmt.Errorf("error starting stack: %w", err) + } + defer stack.Close() + + liveNodeConfig.Start(ctx) + defer liveNodeConfig.StopAndWait() + sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) @@ -122,3 +159,44 @@ func mainImpl() int { close(sigint) return exitCode } + +func parseAuctioneerArgs(ctx context.Context, args []string) (*AutonomousAuctioneerConfig, error) { + f := flag.NewFlagSet("", flag.ContinueOnError) + + // ValidationNodeConfigAddOptions(f) + + k, err := confighelpers.BeginCommonParse(f, args) + if err != nil { + return nil, err + } + + err = confighelpers.ApplyOverrides(f, k) + if err != nil { + return nil, err + } + + var cfg AutonomousAuctioneerConfig + if err := confighelpers.EndCommonParse(k, &cfg); err != nil { + return nil, err + } + + // Don't print wallet passwords + if cfg.Conf.Dump { + err = confighelpers.DumpConfig(k, map[string]interface{}{ + "l1.wallet.password": "", + "l1.wallet.private-key": "", + "l2.dev-wallet.password": "", + "l2.dev-wallet.private-key": "", + }) + if err != nil { + return nil, err + } + } + + // Don't pass around wallet contents with normal configuration + err = cfg.Validate() + if err != nil { + return nil, err + } + return &cfg, nil +} diff --git a/system_tests/express_lane_timeboost_test.go b/system_tests/express_lane_timeboost_test.go new file mode 100644 index 0000000000..88bc2cced1 --- /dev/null +++ b/system_tests/express_lane_timeboost_test.go @@ -0,0 +1,15 @@ +package arbtest + +import ( + "context" + "testing" + + "github.com/offchainlabs/nitro/util/redisutil" +) + +func TestBidValidatorAuctioneerRedisStream(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + redisURL := redisutil.CreateTestRedis(ctx, t) + _ = redisURL +} diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index edce80e771..7a8fca07b5 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -473,7 +473,7 @@ func setupExpressLaneAuction( stack, err := node.New(&stackConf) Require(t, err) auctioneer, err := timeboost.NewAuctioneer( - &auctionContractOpts, []*big.Int{chainId}, stack, seqClient, proxyAddr, + &auctionContractOpts, []*big.Int{chainId}, seqClient, proxyAddr, "", nil, ) Require(t, err) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index bde6793153..74fe4f3b81 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -4,18 +4,18 @@ import ( "context" "fmt" "math/big" - "sync" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" + "github.com/spf13/pflag" "golang.org/x/crypto/sha3" ) @@ -23,7 +23,10 @@ import ( // It is intended to be immutable after initialization. var domainValue []byte -const AuctioneerNamespace = "auctioneer" +const ( + AuctioneerNamespace = "auctioneer" + validatedBidsRedisStream = "validated_bid_stream" +) func init() { hash := sha3.NewLegacyKeccak256() @@ -31,139 +34,209 @@ func init() { domainValue = hash.Sum(nil) } -type AuctioneerOpt func(*Auctioneer) +type AuctioneerConfig struct { + RedisURL string `koanf:"redis-url"` + ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` + // Timeout on polling for existence of each redis stream. + StreamTimeout time.Duration `koanf:"stream-timeout"` + StreamPrefix string `koanf:"stream-prefix"` +} + +var DefaultAuctioneerConfig = AuctioneerConfig{ + RedisURL: "", + StreamPrefix: "", + ConsumerConfig: pubsub.DefaultConsumerConfig, + StreamTimeout: 10 * time.Minute, +} + +var TestAuctioneerConfig = AuctioneerConfig{ + RedisURL: "", + StreamPrefix: "test-", + ConsumerConfig: pubsub.TestConsumerConfig, + StreamTimeout: time.Minute, +} + +func AuctioneerConfigAddOptions(prefix string, f *pflag.FlagSet) { + pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + f.String(prefix+".redis-url", DefaultAuctioneerConfig.RedisURL, "url of redis server") + f.String(prefix+".stream-prefix", DefaultAuctioneerConfig.StreamPrefix, "prefix for stream name") + f.Duration(prefix+".stream-timeout", DefaultAuctioneerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") +} + +func (cfg *AuctioneerConfig) Enabled() bool { + return cfg.RedisURL != "" +} // Auctioneer is a struct that represents an autonomous auctioneer. // It is responsible for receiving bids, validating them, and resolving auctions. -// Spec: https://github.com/OffchainLabs/timeboost-design/tree/main type Auctioneer struct { - txOpts *bind.TransactOpts - chainId []*big.Int // Auctioneer could handle auctions on multiple chains. - domainValue []byte - client Client - auctionContract *express_lane_auctiongen.ExpressLaneAuction - auctionContractAddr common.Address - bidsReceiver chan *Bid - bidCache *bidCache - initialRoundTimestamp time.Time - roundDuration time.Duration - auctionClosingDuration time.Duration - reserveSubmissionDuration time.Duration - reservePriceLock sync.RWMutex - reservePrice *big.Int - sync.RWMutex - bidsPerSenderInRound map[common.Address]uint8 - maxBidsPerSenderInRound uint8 -} - -func EnsureValidationExposedViaAuthRPC(stackConf *node.Config) { - found := false - for _, module := range stackConf.AuthModules { - if module == AuctioneerNamespace { - found = true - break - } - } - if !found { - stackConf.AuthModules = append(stackConf.AuthModules, AuctioneerNamespace) - } + stopwaiter.StopWaiter + consumer *pubsub.Consumer[*JsonValidatedBid, error] + txOpts *bind.TransactOpts + client Client + auctionContract *express_lane_auctiongen.ExpressLaneAuction + auctionContractAddr common.Address + bidsReceiver chan *JsonValidatedBid + bidCache *bidCache + initialRoundTimestamp time.Time + auctionClosingDuration time.Duration + roundDuration time.Duration + streamTimeout time.Duration } // NewAuctioneer creates a new autonomous auctioneer struct. func NewAuctioneer( txOpts *bind.TransactOpts, chainId []*big.Int, - stack *node.Node, client Client, auctionContractAddr common.Address, - opts ...AuctioneerOpt, + redisURL string, + consumerCfg *pubsub.ConsumerConfig, ) (*Auctioneer, error) { - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) + if redisURL == "" { + return nil, fmt.Errorf("redis url cannot be empty") + } + redisClient, err := redisutil.RedisClientFromURL(redisURL) if err != nil { return nil, err } - roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + c, err := pubsub.NewConsumer[*JsonValidatedBid, error](redisClient, validatedBidsRedisStream, consumerCfg) if err != nil { - return nil, err + return nil, fmt.Errorf("creating consumer for validation: %w", err) } - initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) - roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second - auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second - reserveSubmissionDuration := time.Duration(roundTimingInfo.ReserveSubmissionSeconds) * time.Second - - reservePrice, err := auctionContract.ReservePrice(&bind.CallOpts{}) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) if err != nil { return nil, err } - am := &Auctioneer{ - txOpts: txOpts, - chainId: chainId, - client: client, - auctionContract: auctionContract, - auctionContractAddr: auctionContractAddr, - bidsReceiver: make(chan *Bid, 10_000), // TODO(Terence): Is 10000 enough? Make this configurable? - bidCache: newBidCache(), - initialRoundTimestamp: initialTimestamp, - roundDuration: roundDuration, - auctionClosingDuration: auctionClosingDuration, - reserveSubmissionDuration: reserveSubmissionDuration, - reservePrice: reservePrice, - domainValue: domainValue, - bidsPerSenderInRound: make(map[common.Address]uint8), - maxBidsPerSenderInRound: 5, // 5 max bids per sender address in a round. - } - for _, o := range opts { - o(am) - } - auctioneerApi := &AuctioneerAPI{am} - valAPIs := []rpc.API{{ - Namespace: AuctioneerNamespace, - Version: "1.0", - Service: auctioneerApi, - Public: true, - }} - stack.RegisterAPIs(valAPIs) - return am, nil -} - -// ReceiveBid validates and adds a bid to the bid cache. -func (a *Auctioneer) receiveBid(ctx context.Context, b *Bid) error { - vb, err := a.validateBid(b, a.auctionContract.BalanceOf, a.fetchReservePrice) + roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) if err != nil { - return err + return nil, err } - a.bidCache.add(vb) - return nil + auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second + initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) + roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + return &Auctioneer{ + txOpts: txOpts, + client: client, + consumer: c, + auctionContract: auctionContract, + auctionContractAddr: auctionContractAddr, + bidsReceiver: make(chan *JsonValidatedBid, 100_000), // TODO(Terence): Is 100k enough? Make this configurable? + bidCache: newBidCache(), + initialRoundTimestamp: initialTimestamp, + auctionClosingDuration: auctionClosingDuration, + roundDuration: roundDuration, + }, nil } - -// Start starts the autonomous auctioneer. -func (a *Auctioneer) Start(ctx context.Context) { - // Receive bids in the background. - go receiveAsync(ctx, a.bidsReceiver, a.receiveBid) - - // Listen for sequencer health in the background and close upcoming auctions if so. - go a.checkSequencerHealth(ctx) - - // Work on closing auctions. - ticker := newAuctionCloseTicker(a.roundDuration, a.auctionClosingDuration) - go ticker.start() - for { +func (a *Auctioneer) Start(ctx_in context.Context) { + a.StopWaiter.Start(ctx_in, a) + // Channel that consumer uses to indicate its readiness. + readyStream := make(chan struct{}, 1) + a.consumer.Start(ctx_in) + // Channel for single consumer, once readiness is indicated in this, + // consumer will start consuming iteratively. + ready := make(chan struct{}, 1) + a.StopWaiter.LaunchThread(func(ctx context.Context) { + for { + if pubsub.StreamExists(ctx, a.consumer.StreamName(), a.consumer.RedisClient()) { + ready <- struct{}{} + readyStream <- struct{}{} + return + } + select { + case <-ctx.Done(): + log.Info("Context done while checking redis stream existance", "error", ctx.Err().Error()) + return + case <-time.After(time.Millisecond * 100): + } + } + }) + a.StopWaiter.LaunchThread(func(ctx context.Context) { select { case <-ctx.Done(): - log.Error("Context closed, autonomous auctioneer shutting down") + log.Info("Context done while waiting a redis stream to be ready", "error", ctx.Err().Error()) return - case auctionClosingTime := <-ticker.c: - log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", a.bidCache.size()) - if err := a.resolveAuction(ctx); err != nil { - log.Error("Could not resolve auction for round", "error", err) + case <-ready: // Wait until the stream exists and start consuming iteratively. + } + log.Info("Stream exists, now attempting to consume data from it") + a.StopWaiter.CallIteratively(func(ctx context.Context) time.Duration { + req, err := a.consumer.Consume(ctx) + if err != nil { + log.Error("Consuming request", "error", err) + return 0 + } + if req == nil { + // There's nothing in the queue. + return time.Second // TODO: Make this faster? + } + log.Info("Auctioneer received") + // Forward the message over a channel for processing elsewhere in + // another thread, so as to not block this consumption thread. + a.bidsReceiver <- req.Value + + // We received the message, then we ack with a nil error. + if err := a.consumer.SetResult(ctx, req.ID, nil); err != nil { + log.Error("Error setting result for request", "id", req.ID, "result", nil, "error", err) + return 0 + } + return 0 + }) + }) + a.StopWaiter.LaunchThread(func(ctx context.Context) { + for { + select { + case <-readyStream: + log.Trace("At least one stream is ready") + return // Don't block Start if at least one of the stream is ready. + case <-time.After(a.streamTimeout): + log.Error("Waiting for redis streams timed out") + return + case <-ctx.Done(): + log.Info("Context done while waiting redis streams to be ready, failed to start") + return } - // Clear the bid cache. - a.bidCache = newBidCache() } - } + }) + // TODO: Check sequencer health. + // a.StopWaiter.LaunchThread(func(ctx context.Context) { + // }) + + // Bid receiver thread. + a.StopWaiter.LaunchThread(func(ctx context.Context) { + for { + select { + case bid := <-a.bidsReceiver: + log.Info("Processed validated bid", "bidder", bid.Bidder, "amount", bid.Amount, "round", bid.Round) + a.bidCache.add(JsonValidatedBidToGo(bid)) + case <-ctx.Done(): + log.Info("Context done while waiting redis streams to be ready, failed to start") + return + } + } + }) + + // Auction resolution thread. + a.StopWaiter.LaunchThread(func(ctx context.Context) { + ticker := newAuctionCloseTicker(a.roundDuration, a.auctionClosingDuration) + go ticker.start() + for { + select { + case <-ctx.Done(): + log.Error("Context closed, autonomous auctioneer shutting down") + return + case auctionClosingTime := <-ticker.c: + log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", a.bidCache.size()) + if err := a.resolveAuction(ctx); err != nil { + log.Error("Could not resolve auction for round", "error", err) + } + // Clear the bid cache. + a.bidCache = newBidCache() + } + } + }) } -// resolveAuction resolves the auction by calling the smart contract with the top two bids. +// Resolves the auction by calling the smart contract with the top two bids. func (a *Auctioneer) resolveAuction(ctx context.Context) error { upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 result := a.bidCache.topTwoBids() @@ -176,14 +249,14 @@ func (a *Auctioneer) resolveAuction(ctx context.Context) error { tx, err = a.auctionContract.ResolveMultiBidAuction( a.txOpts, express_lane_auctiongen.Bid{ - ExpressLaneController: first.expressLaneController, - Amount: first.amount, - Signature: first.signature, + ExpressLaneController: first.ExpressLaneController, + Amount: first.Amount, + Signature: first.Signature, }, express_lane_auctiongen.Bid{ - ExpressLaneController: second.expressLaneController, - Amount: second.amount, - Signature: second.signature, + ExpressLaneController: second.ExpressLaneController, + Amount: second.Amount, + Signature: second.Signature, }, ) log.Info("Resolving auction with two bids", "round", upcomingRound) @@ -192,9 +265,9 @@ func (a *Auctioneer) resolveAuction(ctx context.Context) error { tx, err = a.auctionContract.ResolveSingleBidAuction( a.txOpts, express_lane_auctiongen.Bid{ - ExpressLaneController: first.expressLaneController, - Amount: first.amount, - Signature: first.signature, + ExpressLaneController: first.ExpressLaneController, + Amount: first.Amount, + Signature: first.Signature, }, ) log.Info("Resolving auction with single bid", "round", upcomingRound) @@ -225,145 +298,3 @@ func (a *Auctioneer) resolveAuction(ctx context.Context) error { log.Info("Auction resolved successfully", "txHash", tx.Hash().Hex()) return nil } - -// TODO: Implement. If sequencer is down for some time, cancel the upcoming auction by calling -// the cancel method on the smart contract. -func (a *Auctioneer) checkSequencerHealth(ctx context.Context) { - -} - -// TODO(Terence): Set reserve price from the contract. -func (a *Auctioneer) fetchReservePrice() *big.Int { - a.reservePriceLock.RLock() - defer a.reservePriceLock.RUnlock() - return new(big.Int).Set(a.reservePrice) -} - -func (a *Auctioneer) validateBid( - bid *Bid, - balanceCheckerFn func(opts *bind.CallOpts, addr common.Address) (*big.Int, error), - fetchReservePriceFn func() *big.Int, -) (*validatedBid, error) { - // Check basic integrity. - if bid == nil { - return nil, errors.Wrap(ErrMalformedData, "nil bid") - } - if bid.AuctionContractAddress != a.auctionContractAddr { - return nil, errors.Wrap(ErrMalformedData, "incorrect auction contract address") - } - if bid.ExpressLaneController == (common.Address{}) { - return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") - } - if bid.ChainId == nil { - return nil, errors.Wrap(ErrMalformedData, "empty chain id") - } - - // Check if the chain ID is valid. - chainIdOk := false - for _, id := range a.chainId { - if bid.ChainId.Cmp(id) == 0 { - chainIdOk = true - break - } - } - if !chainIdOk { - return nil, errors.Wrapf(ErrWrongChainId, "can not auction for chain id: %d", bid.ChainId) - } - - // Check if the bid is intended for upcoming round. - upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 - if bid.Round != upcomingRound { - return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.Round) - } - - // Check if the auction is closed. - if d, closed := auctionClosed(a.initialRoundTimestamp, a.roundDuration, a.auctionClosingDuration); closed { - return nil, errors.Wrapf(ErrBadRoundNumber, "auction is closed, %s since closing", d) - } - - // Check bid is higher than reserve price. - reservePrice := fetchReservePriceFn() - if bid.Amount.Cmp(reservePrice) == -1 { - return nil, errors.Wrapf(ErrReservePriceNotMet, "reserve price %s, bid %s", reservePrice.String(), bid.Amount.String()) - } - - // Validate the signature. - packedBidBytes, err := encodeBidValues( - a.domainValue, - bid.ChainId, - bid.AuctionContractAddress, - bid.Round, - bid.Amount, - bid.ExpressLaneController, - ) - if err != nil { - return nil, ErrMalformedData - } - if len(bid.Signature) != 65 { - return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") - } - // Recover the public key. - prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(packedBidBytes))), packedBidBytes...)) - sigItem := make([]byte, len(bid.Signature)) - copy(sigItem, bid.Signature) - if sigItem[len(sigItem)-1] >= 27 { - sigItem[len(sigItem)-1] -= 27 - } - pubkey, err := crypto.SigToPub(prefixed, sigItem) - if err != nil { - return nil, ErrMalformedData - } - if !verifySignature(pubkey, packedBidBytes, sigItem) { - return nil, ErrWrongSignature - } - // Check how many bids the bidder has sent in this round and cap according to a limit. - bidder := crypto.PubkeyToAddress(*pubkey) - a.Lock() - numBids, ok := a.bidsPerSenderInRound[bidder] - if !ok { - a.bidsPerSenderInRound[bidder] = 1 - } - if numBids >= a.maxBidsPerSenderInRound { - a.Unlock() - return nil, errors.Wrapf(ErrTooManyBids, "bidder %s has already sent the maximum allowed bids = %d in this round", bidder.Hex(), numBids) - } - a.bidsPerSenderInRound[bidder]++ - a.Unlock() - - depositBal, err := balanceCheckerFn(&bind.CallOpts{}, bidder) - if err != nil { - return nil, err - } - if depositBal.Cmp(new(big.Int)) == 0 { - return nil, ErrNotDepositor - } - if depositBal.Cmp(bid.Amount) < 0 { - return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.Amount) - } - return &validatedBid{ - expressLaneController: bid.ExpressLaneController, - amount: bid.Amount, - signature: bid.Signature, - chainId: bid.ChainId, - auctionContractAddress: bid.AuctionContractAddress, - round: bid.Round, - bidder: bidder, - }, nil -} - -// CurrentRound returns the current round number. -func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) uint64 { - if roundDuration == 0 { - return 0 - } - return uint64(time.Since(initialRoundTimestamp) / roundDuration) -} - -// auctionClosed returns the time since auction was closed and whether the auction is closed. -func auctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { - if roundDuration == 0 { - return 0, true - } - d := time.Since(initialRoundTimestamp) % roundDuration - return d, d > auctionClosingDuration -} diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 042e82d240..ccbf3cddce 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -2,198 +2,137 @@ package timeboost import ( "context" - "crypto/ecdsa" - "fmt" "math/big" + "sync" "testing" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/redisutil" "github.com/stretchr/testify/require" ) -func TestAuctioneer_validateBid(t *testing.T) { - setup := setupAuctionTest(t, context.Background()) - tests := []struct { - name string - bid *Bid - expectedErr error - errMsg string - auctionClosed bool - }{ - { - name: "nil bid", - bid: nil, - expectedErr: ErrMalformedData, - errMsg: "nil bid", - }, - { - name: "empty express lane controller address", - bid: &Bid{}, - expectedErr: ErrMalformedData, - errMsg: "incorrect auction contract address", - }, - { - name: "incorrect chain id", - bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: setup.expressLaneAuctionAddr, - ChainId: big.NewInt(50), - }, - expectedErr: ErrWrongChainId, - errMsg: "can not auction for chain id: 50", - }, - { - name: "incorrect round", - bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: setup.expressLaneAuctionAddr, - ChainId: big.NewInt(1), - }, - expectedErr: ErrBadRoundNumber, - errMsg: "wanted 1, got 0", - }, - { - name: "auction is closed", - bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: setup.expressLaneAuctionAddr, - ChainId: big.NewInt(1), - Round: 1, - }, - expectedErr: ErrBadRoundNumber, - errMsg: "auction is closed", - auctionClosed: true, - }, - { - name: "lower than reserved price", - bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: setup.expressLaneAuctionAddr, - ChainId: big.NewInt(1), - Round: 1, - Amount: big.NewInt(1), - }, - expectedErr: ErrReservePriceNotMet, - errMsg: "reserve price 2, bid 1", - }, - { - name: "incorrect signature", - bid: &Bid{ - ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: setup.expressLaneAuctionAddr, - ChainId: big.NewInt(1), - Round: 1, - Amount: big.NewInt(3), - Signature: []byte{'a'}, - }, - expectedErr: ErrMalformedData, - errMsg: "signature length is not 65", - }, - { - name: "not a depositor", - bid: buildValidBid(t, setup.expressLaneAuctionAddr), - expectedErr: ErrNotDepositor, - }, - } +func TestBidValidatorAuctioneerRedisStream(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + testSetup := setupAuctionTest(t, ctx) + redisURL := redisutil.CreateTestRedis(ctx, t) - for _, tt := range tests { - a := Auctioneer{ - chainId: []*big.Int{big.NewInt(1)}, - initialRoundTimestamp: time.Now().Add(-time.Second), - reservePrice: big.NewInt(2), - roundDuration: time.Minute, - auctionClosingDuration: 45 * time.Second, - auctionContract: setup.expressLaneAuction, - auctionContractAddr: setup.expressLaneAuctionAddr, - bidsPerSenderInRound: make(map[common.Address]uint8), - maxBidsPerSenderInRound: 5, - } - if tt.auctionClosed { - a.roundDuration = 0 + // Set up multiple bid validators that will receive bids via RPC using a bidder client. + // They inject their validated bids into a Redis stream that a single auctioneer instance + // will then consume. + numBidValidators := 3 + bidValidators := make([]*BidValidator, numBidValidators) + chainIds := []*big.Int{testSetup.chainId} + for i := 0; i < numBidValidators; i++ { + randHttp := getRandomPort(t) + stackConf := node.Config{ + DataDir: "", // ephemeral. + HTTPPort: randHttp, + HTTPModules: []string{AuctioneerNamespace}, + HTTPHost: "localhost", + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: getRandomPort(t), + WSModules: []string{AuctioneerNamespace}, + WSHost: "localhost", + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDial: true, + NoDiscovery: true, + }, } - t.Run(tt.name, func(t *testing.T) { - _, err := a.validateBid(tt.bid, setup.expressLaneAuction.BalanceOf, a.fetchReservePrice) - require.ErrorIs(t, err, tt.expectedErr) - require.Contains(t, err.Error(), tt.errMsg) - }) - } -} - -func TestAuctioneer_validateBid_perRoundBidLimitReached(t *testing.T) { - balanceCheckerFn := func(_ *bind.CallOpts, _ common.Address) (*big.Int, error) { - return big.NewInt(10), nil - } - fetchReservePriceFn := func() *big.Int { - return big.NewInt(0) - } - auctionContractAddr := common.Address{'a'} - a := Auctioneer{ - chainId: []*big.Int{big.NewInt(1)}, - initialRoundTimestamp: time.Now().Add(-time.Second), - reservePrice: big.NewInt(2), - roundDuration: time.Minute, - auctionClosingDuration: 45 * time.Second, - bidsPerSenderInRound: make(map[common.Address]uint8), - maxBidsPerSenderInRound: 5, - auctionContractAddr: auctionContractAddr, - } - privateKey, err := crypto.GenerateKey() - require.NoError(t, err) - bid := &Bid{ - ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: auctionContractAddr, - ChainId: big.NewInt(1), - Round: 1, - Amount: big.NewInt(3), - Signature: []byte{'a'}, - } - bidValues, err := encodeBidValues(domainValue, bid.ChainId, bid.AuctionContractAddress, bid.Round, bid.Amount, bid.ExpressLaneController) - require.NoError(t, err) - - signature, err := buildSignature(privateKey, bidValues) - require.NoError(t, err) - - bid.Signature = signature - for i := 0; i < int(a.maxBidsPerSenderInRound)-1; i++ { - _, err := a.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) + stack, err := node.New(&stackConf) require.NoError(t, err) + bidValidator, err := NewBidValidator( + chainIds, + stack, + testSetup.backend.Client(), + testSetup.expressLaneAuctionAddr, + redisURL, + &pubsub.TestProducerConfig, + ) + require.NoError(t, err) + require.NoError(t, bidValidator.Initialize(ctx)) + bidValidator.Start(ctx) + bidValidators[i] = bidValidator } - _, err = a.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) - require.ErrorIs(t, err, ErrTooManyBids) - -} - -func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { - prefixedData := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(data))), data...)) - signature, err := crypto.Sign(prefixedData, privateKey) - if err != nil { - return nil, err - } - return signature, nil -} + t.Log("Started multiple bid validators") -func buildValidBid(t *testing.T, auctionContractAddr common.Address) *Bid { - privateKey, err := crypto.GenerateKey() + // Set up a single auctioneer instance that can consume messages produced + // by the bid validator from a redis stream. + am, err := NewAuctioneer( + testSetup.accounts[0].txOpts, + chainIds, + testSetup.backend.Client(), + testSetup.expressLaneAuctionAddr, + redisURL, + &pubsub.TestConsumerConfig, + ) require.NoError(t, err) - b := &Bid{ - ExpressLaneController: common.Address{'b'}, - AuctionContractAddress: auctionContractAddr, - ChainId: big.NewInt(1), - Round: 1, - Amount: big.NewInt(3), - Signature: []byte{'a'}, - } + am.Start(ctx) + t.Log("Started auctioneer") - bidValues, err := encodeBidValues(domainValue, b.ChainId, b.AuctionContractAddress, b.Round, b.Amount, b.ExpressLaneController) - require.NoError(t, err) + // Now, we set up bidder clients for Alice, Bob, and Charlie. + aliceAddr := testSetup.accounts[1].txOpts.From + bobAddr := testSetup.accounts[2].txOpts.From + charlieAddr := testSetup.accounts[3].txOpts.From + alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[1], testSetup, bidValidators[0].stack.HTTPEndpoint()) + bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[2], testSetup, bidValidators[1].stack.HTTPEndpoint()) + charlie := setupBidderClient(t, ctx, "charlie", testSetup.accounts[3], testSetup, bidValidators[2].stack.HTTPEndpoint()) + require.NoError(t, alice.Deposit(ctx, big.NewInt(20))) + require.NoError(t, bob.Deposit(ctx, big.NewInt(20))) + require.NoError(t, charlie.Deposit(ctx, big.NewInt(20))) - signature, err := buildSignature(privateKey, bidValues) + info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) require.NoError(t, err) + timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) + t.Logf("Waiting for %v to start the bidding round, %v", timeToWait, time.Now()) + <-time.After(timeToWait) + time.Sleep(time.Millisecond * 250) // Add 1/4 of a second of wait so that we are definitely within a round. + + // Alice, Bob, and Charlie will submit bids to the three different bid validators. + var wg sync.WaitGroup + start := time.Now() + for i := 1; i <= 4; i++ { + wg.Add(3) + go func(w *sync.WaitGroup, ii int) { + defer w.Done() + alice.Bid(ctx, big.NewInt(int64(ii)), aliceAddr) + }(&wg, i) + go func(w *sync.WaitGroup, ii int) { + defer w.Done() + bob.Bid(ctx, big.NewInt(int64(ii)+1), bobAddr) // Bob bids 1 wei higher than Alice. + }(&wg, i) + go func(w *sync.WaitGroup, ii int) { + defer w.Done() + charlie.Bid(ctx, big.NewInt(int64(ii)+2), charlieAddr) // Charlie bids 2 wei higher than the Bob. + }(&wg, i) + } + wg.Wait() + // We expect that a final submission from each fails, as the bid limit is exceeded. + _, err = alice.Bid(ctx, big.NewInt(6), aliceAddr) + require.ErrorContains(t, err, ErrTooManyBids.Error()) + _, err = bob.Bid(ctx, big.NewInt(7), bobAddr) // Bob bids 1 wei higher than Alice. + require.ErrorContains(t, err, ErrTooManyBids.Error()) + _, err = charlie.Bid(ctx, big.NewInt(8), charlieAddr) // Charlie bids 2 wei higher than the Bob. + require.ErrorContains(t, err, ErrTooManyBids.Error()) - b.Signature = signature + t.Log("Submitted bids", time.Now(), time.Since(start)) + time.Sleep(time.Second * 15) - return b + // We verify that the auctioneer has received bids from the single Redis stream. + // We also verify the top two bids are those we expect. + require.Equal(t, 3, len(am.bidCache.bidsByExpressLaneControllerAddr)) + result := am.bidCache.topTwoBids() + require.Equal(t, result.firstPlace.Amount, big.NewInt(6)) + require.Equal(t, result.firstPlace.Bidder, charlieAddr) + require.Equal(t, result.secondPlace.Amount, big.NewInt(5)) + require.Equal(t, result.secondPlace.Bidder, bobAddr) } diff --git a/timeboost/bid_cache.go b/timeboost/bid_cache.go new file mode 100644 index 0000000000..f48011e80c --- /dev/null +++ b/timeboost/bid_cache.go @@ -0,0 +1,69 @@ +package timeboost + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" +) + +type bidCache struct { + sync.RWMutex + bidsByExpressLaneControllerAddr map[common.Address]*ValidatedBid +} + +func newBidCache() *bidCache { + return &bidCache{ + bidsByExpressLaneControllerAddr: make(map[common.Address]*ValidatedBid), + } +} + +func (bc *bidCache) add(bid *ValidatedBid) { + bc.Lock() + defer bc.Unlock() + bc.bidsByExpressLaneControllerAddr[bid.ExpressLaneController] = bid +} + +// TwoTopBids returns the top two bids for the given chain ID and round +type auctionResult struct { + firstPlace *ValidatedBid + secondPlace *ValidatedBid +} + +func (bc *bidCache) size() int { + bc.RLock() + defer bc.RUnlock() + return len(bc.bidsByExpressLaneControllerAddr) + +} + +// topTwoBids returns the top two bids in the cache. +func (bc *bidCache) topTwoBids() *auctionResult { + bc.RLock() + defer bc.RUnlock() + + result := &auctionResult{} + + for _, bid := range bc.bidsByExpressLaneControllerAddr { + if result.firstPlace == nil { + result.firstPlace = bid + } else if bid.Amount.Cmp(result.firstPlace.Amount) > 0 { + result.secondPlace = result.firstPlace + result.firstPlace = bid + } else if bid.Amount.Cmp(result.firstPlace.Amount) == 0 { + if bid.Hash() > result.firstPlace.Hash() { + result.secondPlace = result.firstPlace + result.firstPlace = bid + } else if result.secondPlace == nil || bid.Hash() > result.secondPlace.Hash() { + result.secondPlace = bid + } + } else if result.secondPlace == nil || bid.Amount.Cmp(result.secondPlace.Amount) > 0 { + result.secondPlace = bid + } else if bid.Amount.Cmp(result.secondPlace.Amount) == 0 { + if bid.Hash() > result.secondPlace.Hash() { + result.secondPlace = bid + } + } + } + + return result +} diff --git a/timeboost/bid_cache_test.go b/timeboost/bid_cache_test.go new file mode 100644 index 0000000000..5c0fda2f3a --- /dev/null +++ b/timeboost/bid_cache_test.go @@ -0,0 +1,272 @@ +package timeboost + +import ( + "context" + "fmt" + "math/big" + "net" + "testing" + + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" +) + +// func TestResolveAuction(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() + +// testSetup := setupAuctionTest(t, ctx) +// am, endpoint := setupAuctioneer(t, ctx, testSetup) + +// // Set up two different bidders. +// alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) +// bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, endpoint) +// require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) +// require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) + +// // Wait until the initial round. +// info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) +// require.NoError(t, err) +// timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) +// <-time.After(timeToWait) +// time.Sleep(time.Second) // Add a second of wait so that we are within a round. + +// // Form two new bids for the round, with Alice being the bigger one. +// _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) +// require.NoError(t, err) +// _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) +// require.NoError(t, err) + +// // Attempt to resolve the auction before it is closed and receive an error. +// require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") + +// // Await resolution. +// t.Log(time.Now()) +// ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) +// go ticker.start() +// <-ticker.c +// require.NoError(t, am.resolveAuction(ctx)) + +// filterOpts := &bind.FilterOpts{ +// Context: ctx, +// Start: 0, +// End: nil, +// } +// it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) +// require.NoError(t, err) +// aliceWon := false +// for it.Next() { +// // Expect Alice to have become the next express lane controller. +// if it.Event.FirstPriceBidder == alice.txOpts.From { +// aliceWon = true +// } +// } +// require.True(t, aliceWon) +// } + +// func TestReceiveBid_OK(t *testing.T) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() + +// testSetup := setupAuctionTest(t, ctx) +// am, endpoint := setupAuctioneer(t, ctx, testSetup) +// bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) +// require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) + +// // Form a new bid with an amount. +// newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) +// require.NoError(t, err) + +// // Check the bid passes validation. +// _, err = am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) +// require.NoError(t, err) + +// topTwoBids := am.bidCache.topTwoBids() +// require.True(t, topTwoBids.secondPlace == nil) +// require.True(t, topTwoBids.firstPlace.expressLaneController == newBid.ExpressLaneController) +// } + +// func TestTopTwoBids(t *testing.T) { +// tests := []struct { +// name string +// bids map[common.Address]*validatedBid +// expected *auctionResult +// }{ +// { +// name: "single bid", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: nil, +// }, +// }, +// { +// name: "two bids with different amounts", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// }, +// }, +// { +// name: "two bids same amount but different hashes", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// }, +// }, +// { +// name: "many bids but all same amount", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, +// }, +// }, +// { +// name: "many bids with some tied and others with different amounts", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, +// common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, +// }, +// }, +// { +// name: "many bids and tied for second place", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, +// }, +// }, +// { +// name: "all bids with the same amount", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// }, +// { +// name: "no bids", +// bids: nil, +// expected: &auctionResult{firstPlace: nil, secondPlace: nil}, +// }, +// { +// name: "identical bids", +// bids: map[common.Address]*validatedBid{ +// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// expected: &auctionResult{ +// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, +// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, +// }, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// bc := &bidCache{ +// bidsByExpressLaneControllerAddr: tt.bids, +// } +// result := bc.topTwoBids() +// if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { +// t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) +// } +// if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { +// t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) +// } +// if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { +// t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) +// } +// }) +// } +// } + +// func BenchmarkBidValidation(b *testing.B) { +// b.StopTimer() +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() + +// testSetup := setupAuctionTest(b, ctx) +// am, endpoint := setupAuctioneer(b, ctx, testSetup) +// bc := setupBidderClient(b, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) +// require.NoError(b, bc.Deposit(ctx, big.NewInt(5))) + +// // Form a valid bid. +// newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) +// require.NoError(b, err) + +// b.StartTimer() +// for i := 0; i < b.N; i++ { +// am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) +// } +// } + +func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*Auctioneer, string) { + // Set up a new auctioneer instance that can validate bids. + // Set up the auctioneer RPC service. + randHttp := getRandomPort(t) + stackConf := node.Config{ + DataDir: "", // ephemeral. + HTTPPort: randHttp, + HTTPModules: []string{AuctioneerNamespace}, + HTTPHost: "localhost", + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: getRandomPort(t), + WSModules: []string{AuctioneerNamespace}, + WSHost: "localhost", + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDial: true, + NoDiscovery: true, + }, + } + stack, err := node.New(&stackConf) + require.NoError(t, err) + am, err := NewAuctioneer( + testSetup.accounts[0].txOpts, []*big.Int{testSetup.chainId}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, "", nil, + ) + require.NoError(t, err) + go am.Start(ctx) + require.NoError(t, stack.Start()) + return am, fmt.Sprintf("http://localhost:%d", randHttp) +} + +func getRandomPort(t testing.TB) int { + listener, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + defer listener.Close() + return listener.Addr().(*net.TCPAddr).Port +} diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go new file mode 100644 index 0000000000..960d449cf9 --- /dev/null +++ b/timeboost/bid_validator.go @@ -0,0 +1,301 @@ +package timeboost + +import ( + "context" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + "github.com/go-redis/redis/v8" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/pkg/errors" +) + +type BidValidator struct { + stopwaiter.StopWaiter + sync.RWMutex + reservePriceLock sync.RWMutex + chainId []*big.Int // Auctioneer could handle auctions on multiple chains. + stack *node.Node + producerCfg *pubsub.ProducerConfig + producer *pubsub.Producer[*JsonValidatedBid, error] + redisClient redis.UniversalClient + domainValue []byte + client Client + auctionContract *express_lane_auctiongen.ExpressLaneAuction + auctionContractAddr common.Address + bidsReceiver chan *Bid + bidCache *bidCache + initialRoundTimestamp time.Time + roundDuration time.Duration + auctionClosingDuration time.Duration + reserveSubmissionDuration time.Duration + reservePrice *big.Int + bidsPerSenderInRound map[common.Address]uint8 + maxBidsPerSenderInRound uint8 +} + +func NewBidValidator( + chainId []*big.Int, + stack *node.Node, + client Client, + auctionContractAddr common.Address, + redisURL string, + producerCfg *pubsub.ProducerConfig, +) (*BidValidator, error) { + if redisURL == "" { + return nil, fmt.Errorf("redis url cannot be empty") + } + redisClient, err := redisutil.RedisClientFromURL(redisURL) + if err != nil { + return nil, err + } + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) + if err != nil { + return nil, err + } + roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + if err != nil { + return nil, err + } + initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) + roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second + reserveSubmissionDuration := time.Duration(roundTimingInfo.ReserveSubmissionSeconds) * time.Second + + reservePrice, err := auctionContract.ReservePrice(&bind.CallOpts{}) + if err != nil { + return nil, err + } + bidValidator := &BidValidator{ + chainId: chainId, + client: client, + redisClient: redisClient, + stack: stack, + auctionContract: auctionContract, + auctionContractAddr: auctionContractAddr, + bidsReceiver: make(chan *Bid, 10_000), + bidCache: newBidCache(), + initialRoundTimestamp: initialTimestamp, + roundDuration: roundDuration, + auctionClosingDuration: auctionClosingDuration, + reserveSubmissionDuration: reserveSubmissionDuration, + reservePrice: reservePrice, + domainValue: domainValue, + bidsPerSenderInRound: make(map[common.Address]uint8), + maxBidsPerSenderInRound: 5, // 5 max bids per sender address in a round. + producerCfg: producerCfg, + } + api := &BidValidatorAPI{bidValidator} + valAPIs := []rpc.API{{ + Namespace: AuctioneerNamespace, + Version: "1.0", + Service: api, + Public: true, + }} + stack.RegisterAPIs(valAPIs) + return bidValidator, nil +} + +func EnsureBidValidatorExposedViaRPC(stackConf *node.Config) { + found := false + for _, module := range stackConf.HTTPModules { + if module == AuctioneerNamespace { + found = true + break + } + } + if !found { + stackConf.HTTPModules = append(stackConf.HTTPModules, AuctioneerNamespace) + } +} + +func (bv *BidValidator) Initialize(ctx context.Context) error { + if err := pubsub.CreateStream( + ctx, + validatedBidsRedisStream, + bv.redisClient, + ); err != nil { + return fmt.Errorf("creating redis stream: %w", err) + } + p, err := pubsub.NewProducer[*JsonValidatedBid, error]( + bv.redisClient, validatedBidsRedisStream, bv.producerCfg, + ) + if err != nil { + return fmt.Errorf("failed to init redis in bid validator: %w", err) + } + bv.producer = p + return nil +} + +func (bv *BidValidator) Start(ctx_in context.Context) { + if bv.producer == nil { + log.Crit("Bid validator not yet initialized by calling Initialize(ctx)") + } + bv.producer.Start(ctx_in) + if err := bv.stack.Start(); err != nil { + log.Crit("Failed to start bid validator", "error", err) + } +} + +type BidValidatorAPI struct { + *BidValidator +} + +func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { + // Validate the received bid. + start := time.Now() + validatedBid, err := bv.validateBid( + &Bid{ + ChainId: bid.ChainId.ToInt(), + ExpressLaneController: bid.ExpressLaneController, + AuctionContractAddress: bid.AuctionContractAddress, + Round: uint64(bid.Round), + Amount: bid.Amount.ToInt(), + Signature: bid.Signature, + }, + bv.auctionContract.BalanceOf, + bv.fetchReservePrice, + ) + if err != nil { + return err + } + log.Info("Validated bid", "bidder", validatedBid.Bidder.Hex(), "amount", validatedBid.Amount.String(), "round", validatedBid.Round, "elapsed", time.Since(start)) + start = time.Now() + _, err = bv.producer.Produce(ctx, validatedBid) + if err != nil { + return err + } + log.Info("producer", "elapsed", time.Since(start)) + return nil +} + +// TODO(Terence): Set reserve price from the contract. +func (bv *BidValidator) fetchReservePrice() *big.Int { + bv.reservePriceLock.RLock() + defer bv.reservePriceLock.RUnlock() + return new(big.Int).Set(bv.reservePrice) +} + +func (bv *BidValidator) validateBid( + bid *Bid, + balanceCheckerFn func(opts *bind.CallOpts, addr common.Address) (*big.Int, error), + fetchReservePriceFn func() *big.Int, +) (*JsonValidatedBid, error) { + // Check basic integrity. + if bid == nil { + return nil, errors.Wrap(ErrMalformedData, "nil bid") + } + if bid.AuctionContractAddress != bv.auctionContractAddr { + return nil, errors.Wrap(ErrMalformedData, "incorrect auction contract address") + } + if bid.ExpressLaneController == (common.Address{}) { + return nil, errors.Wrap(ErrMalformedData, "empty express lane controller address") + } + if bid.ChainId == nil { + return nil, errors.Wrap(ErrMalformedData, "empty chain id") + } + + // Check if the chain ID is valid. + chainIdOk := false + for _, id := range bv.chainId { + if bid.ChainId.Cmp(id) == 0 { + chainIdOk = true + break + } + } + if !chainIdOk { + return nil, errors.Wrapf(ErrWrongChainId, "can not auction for chain id: %d", bid.ChainId) + } + + // Check if the bid is intended for upcoming round. + upcomingRound := CurrentRound(bv.initialRoundTimestamp, bv.roundDuration) + 1 + if bid.Round != upcomingRound { + return nil, errors.Wrapf(ErrBadRoundNumber, "wanted %d, got %d", upcomingRound, bid.Round) + } + + // Check if the auction is closed. + if d, closed := auctionClosed(bv.initialRoundTimestamp, bv.roundDuration, bv.auctionClosingDuration); closed { + return nil, errors.Wrapf(ErrBadRoundNumber, "auction is closed, %s since closing", d) + } + + // Check bid is higher than reserve price. + reservePrice := fetchReservePriceFn() + if bid.Amount.Cmp(reservePrice) == -1 { + return nil, errors.Wrapf(ErrReservePriceNotMet, "reserve price %s, bid %s", reservePrice.String(), bid.Amount.String()) + } + + // Validate the signature. + packedBidBytes, err := encodeBidValues( + domainValue, + bid.ChainId, + bid.AuctionContractAddress, + bid.Round, + bid.Amount, + bid.ExpressLaneController, + ) + if err != nil { + return nil, ErrMalformedData + } + if len(bid.Signature) != 65 { + return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") + } + // Recover the public key. + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(packedBidBytes))), packedBidBytes...)) + sigItem := make([]byte, len(bid.Signature)) + copy(sigItem, bid.Signature) + if sigItem[len(sigItem)-1] >= 27 { + sigItem[len(sigItem)-1] -= 27 + } + pubkey, err := crypto.SigToPub(prefixed, sigItem) + if err != nil { + return nil, ErrMalformedData + } + if !verifySignature(pubkey, packedBidBytes, sigItem) { + return nil, ErrWrongSignature + } + // Check how many bids the bidder has sent in this round and cap according to a limit. + bidder := crypto.PubkeyToAddress(*pubkey) + bv.Lock() + numBids, ok := bv.bidsPerSenderInRound[bidder] + if !ok { + bv.bidsPerSenderInRound[bidder] = 1 + } + if numBids >= bv.maxBidsPerSenderInRound { + bv.Unlock() + return nil, errors.Wrapf(ErrTooManyBids, "bidder %s has already sent the maximum allowed bids = %d in this round", bidder.Hex(), numBids) + } + bv.bidsPerSenderInRound[bidder]++ + bv.Unlock() + + depositBal, err := balanceCheckerFn(&bind.CallOpts{}, bidder) + if err != nil { + return nil, err + } + if depositBal.Cmp(new(big.Int)) == 0 { + return nil, ErrNotDepositor + } + if depositBal.Cmp(bid.Amount) < 0 { + return nil, errors.Wrapf(ErrInsufficientBalance, "onchain balance %#x, bid amount %#x", depositBal, bid.Amount) + } + vb := &ValidatedBid{ + ExpressLaneController: bid.ExpressLaneController, + Amount: bid.Amount, + Signature: bid.Signature, + ChainId: bid.ChainId, + AuctionContractAddress: bid.AuctionContractAddress, + Round: bid.Round, + Bidder: bidder, + } + return vb.ToJson(), nil +} diff --git a/timeboost/bid_validator_test.go b/timeboost/bid_validator_test.go new file mode 100644 index 0000000000..04c770b80f --- /dev/null +++ b/timeboost/bid_validator_test.go @@ -0,0 +1,199 @@ +package timeboost + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func TestBidValidator_validateBid(t *testing.T) { + setup := setupAuctionTest(t, context.Background()) + tests := []struct { + name string + bid *Bid + expectedErr error + errMsg string + auctionClosed bool + }{ + { + name: "nil bid", + bid: nil, + expectedErr: ErrMalformedData, + errMsg: "nil bid", + }, + { + name: "empty express lane controller address", + bid: &Bid{}, + expectedErr: ErrMalformedData, + errMsg: "incorrect auction contract address", + }, + { + name: "incorrect chain id", + bid: &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(50), + }, + expectedErr: ErrWrongChainId, + errMsg: "can not auction for chain id: 50", + }, + { + name: "incorrect round", + bid: &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), + }, + expectedErr: ErrBadRoundNumber, + errMsg: "wanted 1, got 0", + }, + { + name: "auction is closed", + bid: &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), + Round: 1, + }, + expectedErr: ErrBadRoundNumber, + errMsg: "auction is closed", + auctionClosed: true, + }, + { + name: "lower than reserved price", + bid: &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), + Round: 1, + Amount: big.NewInt(1), + }, + expectedErr: ErrReservePriceNotMet, + errMsg: "reserve price 2, bid 1", + }, + { + name: "incorrect signature", + bid: &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: setup.expressLaneAuctionAddr, + ChainId: big.NewInt(1), + Round: 1, + Amount: big.NewInt(3), + Signature: []byte{'a'}, + }, + expectedErr: ErrMalformedData, + errMsg: "signature length is not 65", + }, + { + name: "not a depositor", + bid: buildValidBid(t, setup.expressLaneAuctionAddr), + expectedErr: ErrNotDepositor, + }, + } + + for _, tt := range tests { + bv := BidValidator{ + chainId: []*big.Int{big.NewInt(1)}, + initialRoundTimestamp: time.Now().Add(-time.Second), + reservePrice: big.NewInt(2), + roundDuration: time.Minute, + auctionClosingDuration: 45 * time.Second, + auctionContract: setup.expressLaneAuction, + auctionContractAddr: setup.expressLaneAuctionAddr, + bidsPerSenderInRound: make(map[common.Address]uint8), + maxBidsPerSenderInRound: 5, + } + if tt.auctionClosed { + bv.roundDuration = 0 + } + t.Run(tt.name, func(t *testing.T) { + _, err := bv.validateBid(tt.bid, setup.expressLaneAuction.BalanceOf, bv.fetchReservePrice) + require.ErrorIs(t, err, tt.expectedErr) + require.Contains(t, err.Error(), tt.errMsg) + }) + } +} + +func TestBidValidator_validateBid_perRoundBidLimitReached(t *testing.T) { + balanceCheckerFn := func(_ *bind.CallOpts, _ common.Address) (*big.Int, error) { + return big.NewInt(10), nil + } + fetchReservePriceFn := func() *big.Int { + return big.NewInt(0) + } + auctionContractAddr := common.Address{'a'} + bv := BidValidator{ + chainId: []*big.Int{big.NewInt(1)}, + initialRoundTimestamp: time.Now().Add(-time.Second), + reservePrice: big.NewInt(2), + roundDuration: time.Minute, + auctionClosingDuration: 45 * time.Second, + bidsPerSenderInRound: make(map[common.Address]uint8), + maxBidsPerSenderInRound: 5, + auctionContractAddr: auctionContractAddr, + } + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + bid := &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: auctionContractAddr, + ChainId: big.NewInt(1), + Round: 1, + Amount: big.NewInt(3), + Signature: []byte{'a'}, + } + bidValues, err := encodeBidValues(domainValue, bid.ChainId, bid.AuctionContractAddress, bid.Round, bid.Amount, bid.ExpressLaneController) + require.NoError(t, err) + + signature, err := buildSignature(privateKey, bidValues) + require.NoError(t, err) + + bid.Signature = signature + for i := 0; i < int(bv.maxBidsPerSenderInRound)-1; i++ { + _, err := bv.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) + require.NoError(t, err) + } + _, err = bv.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) + require.ErrorIs(t, err, ErrTooManyBids) + +} + +func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { + prefixedData := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(data))), data...)) + signature, err := crypto.Sign(prefixedData, privateKey) + if err != nil { + return nil, err + } + return signature, nil +} + +func buildValidBid(t *testing.T, auctionContractAddr common.Address) *Bid { + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + b := &Bid{ + ExpressLaneController: common.Address{'b'}, + AuctionContractAddress: auctionContractAddr, + ChainId: big.NewInt(1), + Round: 1, + Amount: big.NewInt(3), + Signature: []byte{'a'}, + } + + bidValues, err := encodeBidValues(domainValue, b.ChainId, b.AuctionContractAddress, b.Round, b.Amount, b.ExpressLaneController) + require.NoError(t, err) + + signature, err := buildSignature(privateKey, bidValues) + require.NoError(t, err) + + b.Signature = signature + + return b +} diff --git a/timeboost/bids.go b/timeboost/bids.go deleted file mode 100644 index 37d3fbb089..0000000000 --- a/timeboost/bids.go +++ /dev/null @@ -1,172 +0,0 @@ -package timeboost - -import ( - "bytes" - "crypto/ecdsa" - "crypto/sha256" - "encoding/binary" - "fmt" - "math/big" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/secp256k1" - "github.com/pkg/errors" -) - -var ( - ErrMalformedData = errors.New("MALFORMED_DATA") - ErrNotDepositor = errors.New("NOT_DEPOSITOR") - ErrWrongChainId = errors.New("WRONG_CHAIN_ID") - ErrWrongSignature = errors.New("WRONG_SIGNATURE") - ErrBadRoundNumber = errors.New("BAD_ROUND_NUMBER") - ErrInsufficientBalance = errors.New("INSUFFICIENT_BALANCE") - ErrReservePriceNotMet = errors.New("RESERVE_PRICE_NOT_MET") - ErrNoOnchainController = errors.New("NO_ONCHAIN_CONTROLLER") - ErrWrongAuctionContract = errors.New("WRONG_AUCTION_CONTRACT") - ErrNotExpressLaneController = errors.New("NOT_EXPRESS_LANE_CONTROLLER") - ErrDuplicateSequenceNumber = errors.New("SUBMISSION_NONCE_ALREADY_SEEN") - ErrSequenceNumberTooLow = errors.New("SUBMISSION_NONCE_TOO_LOW") - ErrTooManyBids = errors.New("PER_ROUND_BID_LIMIT_REACHED") -) - -type Bid struct { - ChainId *big.Int - ExpressLaneController common.Address - AuctionContractAddress common.Address - Round uint64 - Amount *big.Int - Signature []byte -} - -func (b *Bid) ToJson() *JsonBid { - return &JsonBid{ - ChainId: (*hexutil.Big)(b.ChainId), - ExpressLaneController: b.ExpressLaneController, - AuctionContractAddress: b.AuctionContractAddress, - Round: hexutil.Uint64(b.Round), - Amount: (*hexutil.Big)(b.Amount), - Signature: b.Signature, - } -} - -type validatedBid struct { - expressLaneController common.Address - amount *big.Int - signature []byte - // For tie breaking - chainId *big.Int - auctionContractAddress common.Address - round uint64 - bidder common.Address -} -type bidCache struct { - sync.RWMutex - bidsByExpressLaneControllerAddr map[common.Address]*validatedBid -} - -func newBidCache() *bidCache { - return &bidCache{ - bidsByExpressLaneControllerAddr: make(map[common.Address]*validatedBid), - } -} - -func (bc *bidCache) add(bid *validatedBid) { - bc.Lock() - defer bc.Unlock() - bc.bidsByExpressLaneControllerAddr[bid.expressLaneController] = bid -} - -// TwoTopBids returns the top two bids for the given chain ID and round -type auctionResult struct { - firstPlace *validatedBid - secondPlace *validatedBid -} - -func (bc *bidCache) size() int { - bc.RLock() - defer bc.RUnlock() - return len(bc.bidsByExpressLaneControllerAddr) - -} - -// topTwoBids returns the top two bids in the cache. -func (bc *bidCache) topTwoBids() *auctionResult { - bc.RLock() - defer bc.RUnlock() - - result := &auctionResult{} - - for _, bid := range bc.bidsByExpressLaneControllerAddr { - if result.firstPlace == nil { - result.firstPlace = bid - } else if bid.amount.Cmp(result.firstPlace.amount) > 0 { - result.secondPlace = result.firstPlace - result.firstPlace = bid - } else if bid.amount.Cmp(result.firstPlace.amount) == 0 { - if hashBid(bid) > hashBid(result.firstPlace) { - result.secondPlace = result.firstPlace - result.firstPlace = bid - } else if result.secondPlace == nil || hashBid(bid) > hashBid(result.secondPlace) { - result.secondPlace = bid - } - } else if result.secondPlace == nil || bid.amount.Cmp(result.secondPlace.amount) > 0 { - result.secondPlace = bid - } else if bid.amount.Cmp(result.secondPlace.amount) == 0 { - if hashBid(bid) > hashBid(result.secondPlace) { - result.secondPlace = bid - } - } - } - - return result -} - -// hashBid hashes the bidder address concatenated with the respective byte-string representation of the bid using the Keccak256 hashing scheme. -func hashBid(bid *validatedBid) string { - // Concatenate the bidder address and the byte representation of the bid - data := append(bid.bidder.Bytes(), padBigInt(bid.chainId)...) - data = append(data, bid.auctionContractAddress.Bytes()...) - roundBytes := make([]byte, 8) - binary.BigEndian.PutUint64(roundBytes, bid.round) - data = append(data, roundBytes...) - data = append(data, bid.amount.Bytes()...) - data = append(data, bid.expressLaneController.Bytes()...) - - hash := sha256.Sum256(data) - - // Return the hash as a hexadecimal string - return fmt.Sprintf("%x", hash) -} - -func verifySignature(pubkey *ecdsa.PublicKey, message []byte, sig []byte) bool { - prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) - - return secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sig[:len(sig)-1]) -} - -// Helper function to pad a big integer to 32 bytes -func padBigInt(bi *big.Int) []byte { - bb := bi.Bytes() - padded := make([]byte, 32-len(bb), 32) - padded = append(padded, bb...) - return padded -} - -func encodeBidValues(domainValue []byte, chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { - buf := new(bytes.Buffer) - - // Encode uint256 values - each occupies 32 bytes - buf.Write(domainValue) - buf.Write(padBigInt(chainId)) - buf.Write(auctionContractAddress[:]) - roundBuf := make([]byte, 8) - binary.BigEndian.PutUint64(roundBuf, round) - buf.Write(roundBuf) - buf.Write(padBigInt(amount)) - buf.Write(expressLaneController[:]) - - return buf.Bytes(), nil -} diff --git a/timeboost/bids_test.go b/timeboost/bids_test.go deleted file mode 100644 index a32f42995d..0000000000 --- a/timeboost/bids_test.go +++ /dev/null @@ -1,275 +0,0 @@ -package timeboost - -import ( - "context" - "fmt" - "math/big" - "net" - "testing" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/require" -) - -func TestResolveAuction(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - testSetup := setupAuctionTest(t, ctx) - am, endpoint := setupAuctioneer(t, ctx, testSetup) - - // Set up two different bidders. - alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) - bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, endpoint) - require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) - require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) - - // Wait until the initial round. - info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) - require.NoError(t, err) - timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) - <-time.After(timeToWait) - time.Sleep(time.Second) // Add a second of wait so that we are within a round. - - // Form two new bids for the round, with Alice being the bigger one. - _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) - require.NoError(t, err) - _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) - require.NoError(t, err) - - // Attempt to resolve the auction before it is closed and receive an error. - require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") - - // Await resolution. - t.Log(time.Now()) - ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) - go ticker.start() - <-ticker.c - require.NoError(t, am.resolveAuction(ctx)) - - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: 0, - End: nil, - } - it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) - require.NoError(t, err) - aliceWon := false - for it.Next() { - // Expect Alice to have become the next express lane controller. - if it.Event.FirstPriceBidder == alice.txOpts.From { - aliceWon = true - } - } - require.True(t, aliceWon) -} - -func TestReceiveBid_OK(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - testSetup := setupAuctionTest(t, ctx) - am, endpoint := setupAuctioneer(t, ctx, testSetup) - bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) - require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) - - // Form a new bid with an amount. - newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) - require.NoError(t, err) - - // Check the bid passes validation. - _, err = am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) - require.NoError(t, err) - - topTwoBids := am.bidCache.topTwoBids() - require.True(t, topTwoBids.secondPlace == nil) - require.True(t, topTwoBids.firstPlace.expressLaneController == newBid.ExpressLaneController) -} - -func TestTopTwoBids(t *testing.T) { - tests := []struct { - name string - bids map[common.Address]*validatedBid - expected *auctionResult - }{ - { - name: "single bid", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: nil, - }, - }, - { - name: "two bids with different amounts", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, - secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - }, - }, - { - name: "two bids same amount but different hashes", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - }, - }, - { - name: "many bids but all same amount", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, - }, - }, - { - name: "many bids with some tied and others with different amounts", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, - common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, - }, - }, - { - name: "many bids and tied for second place", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, - }, - }, - { - name: "all bids with the same amount", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, - secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, - }, - }, - { - name: "no bids", - bids: nil, - expected: &auctionResult{firstPlace: nil, secondPlace: nil}, - }, - { - name: "identical bids", - bids: map[common.Address]*validatedBid{ - common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, - }, - expected: &auctionResult{ - firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, - secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - bc := &bidCache{ - bidsByExpressLaneControllerAddr: tt.bids, - } - result := bc.topTwoBids() - if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { - t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) - } - if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { - t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) - } - if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { - t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) - } - }) - } -} - -func BenchmarkBidValidation(b *testing.B) { - b.StopTimer() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - testSetup := setupAuctionTest(b, ctx) - am, endpoint := setupAuctioneer(b, ctx, testSetup) - bc := setupBidderClient(b, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) - require.NoError(b, bc.Deposit(ctx, big.NewInt(5))) - - // Form a valid bid. - newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) - require.NoError(b, err) - - b.StartTimer() - for i := 0; i < b.N; i++ { - am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) - } -} - -func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*Auctioneer, string) { - // Set up a new auction master instance that can validate bids. - // Set up the auctioneer RPC service. - randHttp := getRandomPort(t) - stackConf := node.Config{ - DataDir: "", // ephemeral. - HTTPPort: randHttp, - HTTPModules: []string{AuctioneerNamespace}, - HTTPHost: "localhost", - HTTPVirtualHosts: []string{"localhost"}, - HTTPTimeouts: rpc.DefaultHTTPTimeouts, - WSPort: getRandomPort(t), - WSModules: []string{AuctioneerNamespace}, - WSHost: "localhost", - GraphQLVirtualHosts: []string{"localhost"}, - P2P: p2p.Config{ - ListenAddr: "", - NoDial: true, - NoDiscovery: true, - }, - } - stack, err := node.New(&stackConf) - require.NoError(t, err) - am, err := NewAuctioneer( - testSetup.accounts[0].txOpts, []*big.Int{testSetup.chainId}, stack, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, - ) - require.NoError(t, err) - go am.Start(ctx) - require.NoError(t, stack.Start()) - return am, fmt.Sprintf("http://localhost:%d", randHttp) -} - -func getRandomPort(t testing.TB) int { - listener, err := net.Listen("tcp", "localhost:0") - require.NoError(t, err) - defer listener.Close() - return listener.Addr().(*net.TCPAddr).Port -} diff --git a/timeboost/errors.go b/timeboost/errors.go new file mode 100644 index 0000000000..ef8dc2c8dc --- /dev/null +++ b/timeboost/errors.go @@ -0,0 +1,19 @@ +package timeboost + +import "github.com/pkg/errors" + +var ( + ErrMalformedData = errors.New("MALFORMED_DATA") + ErrNotDepositor = errors.New("NOT_DEPOSITOR") + ErrWrongChainId = errors.New("WRONG_CHAIN_ID") + ErrWrongSignature = errors.New("WRONG_SIGNATURE") + ErrBadRoundNumber = errors.New("BAD_ROUND_NUMBER") + ErrInsufficientBalance = errors.New("INSUFFICIENT_BALANCE") + ErrReservePriceNotMet = errors.New("RESERVE_PRICE_NOT_MET") + ErrNoOnchainController = errors.New("NO_ONCHAIN_CONTROLLER") + ErrWrongAuctionContract = errors.New("WRONG_AUCTION_CONTRACT") + ErrNotExpressLaneController = errors.New("NOT_EXPRESS_LANE_CONTROLLER") + ErrDuplicateSequenceNumber = errors.New("SUBMISSION_NONCE_ALREADY_SEEN") + ErrSequenceNumberTooLow = errors.New("SUBMISSION_NONCE_TOO_LOW") + ErrTooManyBids = errors.New("PER_ROUND_BID_LIMIT_REACHED") +) diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index ca0562c8c1..bca30e1327 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -35,7 +35,6 @@ type auctionSetup struct { func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { accs, backend := setupAccounts(10) - // Advance the chain in the background at Arbitrum One's block time of 250ms. go func() { tick := time.NewTicker(time.Second) defer tick.Stop() @@ -225,7 +224,7 @@ func mintTokens(ctx context.Context, erc20 *bindings.MockERC20, ) { for i := 0; i < len(accs); i++ { - tx, err := erc20.Mint(opts, accs[i].accountAddr, big.NewInt(10)) + tx, err := erc20.Mint(opts, accs[i].accountAddr, big.NewInt(100)) if err != nil { panic(err) } diff --git a/timeboost/ticker.go b/timeboost/ticker.go index d995b2d026..f04ff82a46 100644 --- a/timeboost/ticker.go +++ b/timeboost/ticker.go @@ -44,3 +44,20 @@ func (t *auctionCloseTicker) start() { } } } + +// CurrentRound returns the current round number. +func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) uint64 { + if roundDuration == 0 { + return 0 + } + return uint64(time.Since(initialRoundTimestamp) / roundDuration) +} + +// auctionClosed returns the time since auction was closed and whether the auction is closed. +func auctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { + if roundDuration == 0 { + return 0, true + } + d := time.Since(initialRoundTimestamp) % roundDuration + return d, d > auctionClosingDuration +} diff --git a/timeboost/auctioneer_api.go b/timeboost/types.go similarity index 51% rename from timeboost/auctioneer_api.go rename to timeboost/types.go index 71902fc7bd..22ca660ccc 100644 --- a/timeboost/auctioneer_api.go +++ b/timeboost/types.go @@ -2,18 +2,38 @@ package timeboost import ( "bytes" - "context" + "crypto/ecdsa" + "crypto/sha256" "encoding/binary" + "fmt" "math/big" "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" ) -type AuctioneerAPI struct { - *Auctioneer +type Bid struct { + ChainId *big.Int + ExpressLaneController common.Address + AuctionContractAddress common.Address + Round uint64 + Amount *big.Int + Signature []byte +} + +func (b *Bid) ToJson() *JsonBid { + return &JsonBid{ + ChainId: (*hexutil.Big)(b.ChainId), + ExpressLaneController: b.ExpressLaneController, + AuctionContractAddress: b.AuctionContractAddress, + Round: hexutil.Uint64(b.Round), + Amount: (*hexutil.Big)(b.Amount), + Signature: b.Signature, + } } type JsonBid struct { @@ -25,6 +45,66 @@ type JsonBid struct { Signature hexutil.Bytes `json:"signature"` } +type ValidatedBid struct { + ExpressLaneController common.Address + Amount *big.Int + Signature []byte + // For tie breaking + ChainId *big.Int + AuctionContractAddress common.Address + Round uint64 + Bidder common.Address +} + +func (v *ValidatedBid) Hash() string { + // Concatenate the bidder address and the byte representation of the bid + data := append(v.Bidder.Bytes(), padBigInt(v.ChainId)...) + data = append(data, v.AuctionContractAddress.Bytes()...) + roundBytes := make([]byte, 8) + binary.BigEndian.PutUint64(roundBytes, v.Round) + data = append(data, roundBytes...) + data = append(data, v.Amount.Bytes()...) + data = append(data, v.ExpressLaneController.Bytes()...) + + hash := sha256.Sum256(data) + // Return the hash as a hexadecimal string + return fmt.Sprintf("%x", hash) +} + +func (v *ValidatedBid) ToJson() *JsonValidatedBid { + return &JsonValidatedBid{ + ExpressLaneController: v.ExpressLaneController, + Amount: (*hexutil.Big)(v.Amount), + Signature: v.Signature, + ChainId: (*hexutil.Big)(v.ChainId), + AuctionContractAddress: v.AuctionContractAddress, + Round: hexutil.Uint64(v.Round), + Bidder: v.Bidder, + } +} + +type JsonValidatedBid struct { + ExpressLaneController common.Address `json:"expressLaneController"` + Amount *hexutil.Big `json:"amount"` + Signature hexutil.Bytes `json:"signature"` + ChainId *hexutil.Big `json:"chainId"` + AuctionContractAddress common.Address `json:"auctionContractAddress"` + Round hexutil.Uint64 `json:"round"` + Bidder common.Address `json:"bidder"` +} + +func JsonValidatedBidToGo(bid *JsonValidatedBid) *ValidatedBid { + return &ValidatedBid{ + ExpressLaneController: bid.ExpressLaneController, + Amount: bid.Amount.ToInt(), + Signature: bid.Signature, + ChainId: bid.ChainId.ToInt(), + AuctionContractAddress: bid.AuctionContractAddress, + Round: uint64(bid.Round), + Bidder: bid.Bidder, + } +} + type JsonExpressLaneSubmission struct { ChainId *hexutil.Big `json:"chainId"` Round hexutil.Uint64 `json:"round"` @@ -114,13 +194,31 @@ func encodeExpressLaneSubmission( return buf.Bytes(), nil } -func (a *AuctioneerAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { - return a.receiveBid(ctx, &Bid{ - ChainId: bid.ChainId.ToInt(), - ExpressLaneController: bid.ExpressLaneController, - AuctionContractAddress: bid.AuctionContractAddress, - Round: uint64(bid.Round), - Amount: bid.Amount.ToInt(), - Signature: bid.Signature, - }) +func verifySignature(pubkey *ecdsa.PublicKey, message []byte, sig []byte) bool { + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) + return secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sig[:len(sig)-1]) +} + +// Helper function to pad a big integer to 32 bytes +func padBigInt(bi *big.Int) []byte { + bb := bi.Bytes() + padded := make([]byte, 32-len(bb), 32) + padded = append(padded, bb...) + return padded +} + +func encodeBidValues(domainValue []byte, chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { + buf := new(bytes.Buffer) + + // Encode uint256 values - each occupies 32 bytes + buf.Write(domainValue) + buf.Write(padBigInt(chainId)) + buf.Write(auctionContractAddress[:]) + roundBuf := make([]byte, 8) + binary.BigEndian.PutUint64(roundBuf, round) + buf.Write(roundBuf) + buf.Write(padBigInt(amount)) + buf.Write(expressLaneController[:]) + + return buf.Bytes(), nil } From 82860e6bd1f8e797612454c6f17775b89e6715ff Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 6 Aug 2024 12:46:02 -0500 Subject: [PATCH 043/109] auctioneer server binary --- cmd/autonomous-auctioneer/config.go | 33 +++++++++------------ cmd/autonomous-auctioneer/main.go | 46 +++++++++++++++++++++++++---- system_tests/seqfeed_test.go | 2 +- timeboost/auctioneer.go | 30 +++++++++---------- timeboost/auctioneer_test.go | 2 +- timeboost/bid_cache_test.go | 4 +-- timeboost/bid_validator.go | 3 -- 7 files changed, 74 insertions(+), 46 deletions(-) diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go index 3e1e76d782..37a54e1968 100644 --- a/cmd/autonomous-auctioneer/config.go +++ b/cmd/autonomous-auctioneer/config.go @@ -16,25 +16,21 @@ import ( flag "github.com/spf13/pflag" ) -const ( - bidValidatorMode = "bid-validator" - autonomousAuctioneerMode = "autonomous-auctioneer" -) - type AutonomousAuctioneerConfig struct { - Mode string `koanf:"mode"` - Persistent conf.PersistentConfig `koanf:"persistent"` - Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` - LogLevel string `koanf:"log-level" reload:"hot"` - LogType string `koanf:"log-type" reload:"hot"` - FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` - HTTP genericconf.HTTPConfig `koanf:"http"` - WS genericconf.WSConfig `koanf:"ws"` - IPC genericconf.IPCConfig `koanf:"ipc"` - Metrics bool `koanf:"metrics"` - MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` - PProf bool `koanf:"pprof"` - PprofCfg genericconf.PProf `koanf:"pprof-cfg"` + Persistent conf.PersistentConfig `koanf:"persistent"` + Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` + LogLevel string `koanf:"log-level" reload:"hot"` + LogType string `koanf:"log-type" reload:"hot"` + FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` + HTTP genericconf.HTTPConfig `koanf:"http"` + WS genericconf.WSConfig `koanf:"ws"` + IPC genericconf.IPCConfig `koanf:"ipc"` + Metrics bool `koanf:"metrics"` + MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` + PProf bool `koanf:"pprof"` + PprofCfg genericconf.PProf `koanf:"pprof-cfg"` + ParentChainWallet genericconf.WalletConfig `koanf:"wallet"` // TODO: Move into auctioneer config. + } var HTTPConfigDefault = genericconf.HTTPConfig{ @@ -62,7 +58,6 @@ var IPCConfigDefault = genericconf.IPCConfig{ var AutonomousAuctioneerConfigDefault = AutonomousAuctioneerConfig{ Conf: genericconf.ConfConfigDefault, - Mode: autonomousAuctioneerMode, LogLevel: "INFO", LogType: "plaintext", HTTP: HTTPConfigDefault, diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go index ee464d2365..9d940579ec 100644 --- a/cmd/autonomous-auctioneer/main.go +++ b/cmd/autonomous-auctioneer/main.go @@ -11,13 +11,14 @@ import ( flag "github.com/spf13/pflag" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" "github.com/ethereum/go-ethereum/node" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/cmd/util/confighelpers" - "github.com/offchainlabs/nitro/validator/valnode" + "github.com/offchainlabs/nitro/timeboost" ) func main() { @@ -96,15 +97,13 @@ func mainImpl() int { stackConf.JWTSecret = filename } - log.Info("Running Arbitrum nitro validation node", "revision", vcsRevision, "vcs.time", vcsTime) - liveNodeConfig := genericconf.NewLiveConfig[*AutonomousAuctioneerConfig](args, nodeConfig, parseAuctioneerArgs) liveNodeConfig.SetOnReloadHook(func(oldCfg *AutonomousAuctioneerConfig, newCfg *AutonomousAuctioneerConfig) error { return genericconf.InitLog(newCfg.LogType, newCfg.LogLevel, &newCfg.FileLogging, pathResolver(nodeConfig.Persistent.LogDir)) }) - valnode.EnsureValidationExposedViaAuthRPC(&stackConf) + timeboost.EnsureBidValidatorExposedViaRPC(&stackConf) stack, err := node.New(&stackConf) if err != nil { @@ -118,7 +117,7 @@ func mainImpl() int { } fatalErrChan := make(chan error, 10) - + log.Info("Running Arbitrum ", "revision", vcsRevision, "vcs.time", vcsTime) // valNode, err := valnode.CreateValidationNode( // func() *valnode.Config { return &liveNodeConfig.Get().Validation }, // stack, @@ -140,6 +139,43 @@ func mainImpl() int { } defer stack.Close() + if nodeConfig.Mode == autonomousAuctioneerMode { + auctioneer, err := timeboost.NewAuctioneerServer( + nil, + nil, + nil, + common.Address{}, + "", + nil, + ) + if err != nil { + log.Error("Error creating new auctioneer", "error", err) + return 1 + } + auctioneer.Start(ctx) + } else if nodeConfig.Mode == bidValidatorMode { + bidValidator, err := timeboost.NewBidValidator( + nil, + nil, + nil, + common.Address{}, + "", + nil, + ) + if err != nil { + log.Error("Error creating new auctioneer", "error", err) + return 1 + } + if err = bidValidator.Initialize(ctx); err != nil { + log.Error("error initializing bid validator", "err", err) + return 1 + } + bidValidator.Start(ctx) + } else { + log.Crit("Unknown mode, should be either autonomous-auctioneer or bid-validator", "mode", nodeConfig.Mode) + + } + liveNodeConfig.Start(ctx) defer liveNodeConfig.StopAndWait() diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 7a8fca07b5..12b29d3e5e 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -472,7 +472,7 @@ func setupExpressLaneAuction( } stack, err := node.New(&stackConf) Require(t, err) - auctioneer, err := timeboost.NewAuctioneer( + auctioneer, err := timeboost.NewAuctioneerServer( &auctionContractOpts, []*big.Int{chainId}, seqClient, proxyAddr, "", nil, ) Require(t, err) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 74fe4f3b81..259ef6ce42 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -34,7 +34,7 @@ func init() { domainValue = hash.Sum(nil) } -type AuctioneerConfig struct { +type AuctioneerServerConfig struct { RedisURL string `koanf:"redis-url"` ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` // Timeout on polling for existence of each redis stream. @@ -42,14 +42,14 @@ type AuctioneerConfig struct { StreamPrefix string `koanf:"stream-prefix"` } -var DefaultAuctioneerConfig = AuctioneerConfig{ +var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ RedisURL: "", StreamPrefix: "", ConsumerConfig: pubsub.DefaultConsumerConfig, StreamTimeout: 10 * time.Minute, } -var TestAuctioneerConfig = AuctioneerConfig{ +var TestAuctioneerServerConfig = AuctioneerServerConfig{ RedisURL: "", StreamPrefix: "test-", ConsumerConfig: pubsub.TestConsumerConfig, @@ -58,18 +58,18 @@ var TestAuctioneerConfig = AuctioneerConfig{ func AuctioneerConfigAddOptions(prefix string, f *pflag.FlagSet) { pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) - f.String(prefix+".redis-url", DefaultAuctioneerConfig.RedisURL, "url of redis server") - f.String(prefix+".stream-prefix", DefaultAuctioneerConfig.StreamPrefix, "prefix for stream name") - f.Duration(prefix+".stream-timeout", DefaultAuctioneerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") + f.String(prefix+".redis-url", DefaultAuctioneerServerConfig.RedisURL, "url of redis server") + f.String(prefix+".stream-prefix", DefaultAuctioneerServerConfig.StreamPrefix, "prefix for stream name") + f.Duration(prefix+".stream-timeout", DefaultAuctioneerServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") } -func (cfg *AuctioneerConfig) Enabled() bool { +func (cfg *AuctioneerServerConfig) Enabled() bool { return cfg.RedisURL != "" } -// Auctioneer is a struct that represents an autonomous auctioneer. +// AuctioneerServer is a struct that represents an autonomous auctioneer. // It is responsible for receiving bids, validating them, and resolving auctions. -type Auctioneer struct { +type AuctioneerServer struct { stopwaiter.StopWaiter consumer *pubsub.Consumer[*JsonValidatedBid, error] txOpts *bind.TransactOpts @@ -84,15 +84,15 @@ type Auctioneer struct { streamTimeout time.Duration } -// NewAuctioneer creates a new autonomous auctioneer struct. -func NewAuctioneer( +// NewAuctioneerServer creates a new autonomous auctioneer struct. +func NewAuctioneerServer( txOpts *bind.TransactOpts, chainId []*big.Int, client Client, auctionContractAddr common.Address, redisURL string, consumerCfg *pubsub.ConsumerConfig, -) (*Auctioneer, error) { +) (*AuctioneerServer, error) { if redisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } @@ -115,7 +115,7 @@ func NewAuctioneer( auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second - return &Auctioneer{ + return &AuctioneerServer{ txOpts: txOpts, client: client, consumer: c, @@ -128,7 +128,7 @@ func NewAuctioneer( roundDuration: roundDuration, }, nil } -func (a *Auctioneer) Start(ctx_in context.Context) { +func (a *AuctioneerServer) Start(ctx_in context.Context) { a.StopWaiter.Start(ctx_in, a) // Channel that consumer uses to indicate its readiness. readyStream := make(chan struct{}, 1) @@ -237,7 +237,7 @@ func (a *Auctioneer) Start(ctx_in context.Context) { } // Resolves the auction by calling the smart contract with the top two bids. -func (a *Auctioneer) resolveAuction(ctx context.Context) error { +func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 result := a.bidCache.topTwoBids() first := result.firstPlace diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index ccbf3cddce..ba095f2bbb 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -67,7 +67,7 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { // Set up a single auctioneer instance that can consume messages produced // by the bid validator from a redis stream. - am, err := NewAuctioneer( + am, err := NewAuctioneerServer( testSetup.accounts[0].txOpts, chainIds, testSetup.backend.Client(), diff --git a/timeboost/bid_cache_test.go b/timeboost/bid_cache_test.go index 5c0fda2f3a..db763dd39b 100644 --- a/timeboost/bid_cache_test.go +++ b/timeboost/bid_cache_test.go @@ -232,7 +232,7 @@ import ( // } // } -func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*Auctioneer, string) { +func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*AuctioneerServer, string) { // Set up a new auctioneer instance that can validate bids. // Set up the auctioneer RPC service. randHttp := getRandomPort(t) @@ -255,7 +255,7 @@ func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) } stack, err := node.New(&stackConf) require.NoError(t, err) - am, err := NewAuctioneer( + am, err := NewAuctioneerServer( testSetup.accounts[0].txOpts, []*big.Int{testSetup.chainId}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, "", nil, ) require.NoError(t, err) diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 960d449cf9..885a0ff1a8 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -143,9 +143,6 @@ func (bv *BidValidator) Start(ctx_in context.Context) { log.Crit("Bid validator not yet initialized by calling Initialize(ctx)") } bv.producer.Start(ctx_in) - if err := bv.stack.Start(); err != nil { - log.Crit("Failed to start bid validator", "error", err) - } } type BidValidatorAPI struct { From ca1b91388f07ae4f0db84db894799ab876a95a3d Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 6 Aug 2024 12:49:55 -0500 Subject: [PATCH 044/109] add configs for binary --- cmd/autonomous-auctioneer/config.go | 28 +++++++++++++----------- timeboost/auctioneer.go | 8 +++---- timeboost/bid_validator.go | 34 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go index 37a54e1968..a7c7ed1e17 100644 --- a/cmd/autonomous-auctioneer/config.go +++ b/cmd/autonomous-auctioneer/config.go @@ -17,19 +17,21 @@ import ( ) type AutonomousAuctioneerConfig struct { - Persistent conf.PersistentConfig `koanf:"persistent"` - Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` - LogLevel string `koanf:"log-level" reload:"hot"` - LogType string `koanf:"log-type" reload:"hot"` - FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` - HTTP genericconf.HTTPConfig `koanf:"http"` - WS genericconf.WSConfig `koanf:"ws"` - IPC genericconf.IPCConfig `koanf:"ipc"` - Metrics bool `koanf:"metrics"` - MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` - PProf bool `koanf:"pprof"` - PprofCfg genericconf.PProf `koanf:"pprof-cfg"` - ParentChainWallet genericconf.WalletConfig `koanf:"wallet"` // TODO: Move into auctioneer config. + AuctioneerServer timeboost.AuctioneerServerConfig `koanf:"auctioneer-server"` + BidValidator timeboost.BidValidatorConfig `koanf:"bid-validator"` + Persistent conf.PersistentConfig `koanf:"persistent"` + Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` + LogLevel string `koanf:"log-level" reload:"hot"` + LogType string `koanf:"log-type" reload:"hot"` + FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` + HTTP genericconf.HTTPConfig `koanf:"http"` + WS genericconf.WSConfig `koanf:"ws"` + IPC genericconf.IPCConfig `koanf:"ipc"` + Metrics bool `koanf:"metrics"` + MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` + PProf bool `koanf:"pprof"` + PprofCfg genericconf.PProf `koanf:"pprof-cfg"` + ParentChainWallet genericconf.WalletConfig `koanf:"wallet"` // TODO: Move into auctioneer config. } diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 259ef6ce42..e77104d31f 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -35,6 +35,7 @@ func init() { } type AuctioneerServerConfig struct { + Enabled bool `koanf:"enabled"` RedisURL string `koanf:"redis-url"` ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` // Timeout on polling for existence of each redis stream. @@ -43,6 +44,7 @@ type AuctioneerServerConfig struct { } var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ + Enabled: true, RedisURL: "", StreamPrefix: "", ConsumerConfig: pubsub.DefaultConsumerConfig, @@ -50,6 +52,7 @@ var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ } var TestAuctioneerServerConfig = AuctioneerServerConfig{ + Enabled: true, RedisURL: "", StreamPrefix: "test-", ConsumerConfig: pubsub.TestConsumerConfig, @@ -57,16 +60,13 @@ var TestAuctioneerServerConfig = AuctioneerServerConfig{ } func AuctioneerConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".enabled", DefaultAuctioneerServerConfig.Enabled, "enable auctioneer server") pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) f.String(prefix+".redis-url", DefaultAuctioneerServerConfig.RedisURL, "url of redis server") f.String(prefix+".stream-prefix", DefaultAuctioneerServerConfig.StreamPrefix, "prefix for stream name") f.Duration(prefix+".stream-timeout", DefaultAuctioneerServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") } -func (cfg *AuctioneerServerConfig) Enabled() bool { - return cfg.RedisURL != "" -} - // AuctioneerServer is a struct that represents an autonomous auctioneer. // It is responsible for receiving bids, validating them, and resolving auctions. type AuctioneerServer struct { diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 885a0ff1a8..3dc429b942 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -19,8 +19,42 @@ import ( "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" + "github.com/spf13/pflag" ) +type BidValidatorConfig struct { + Enabled bool `koanf:"enabled"` + RedisURL string `koanf:"redis-url"` + ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` + // Timeout on polling for existence of each redis stream. + StreamTimeout time.Duration `koanf:"stream-timeout"` + StreamPrefix string `koanf:"stream-prefix"` +} + +var DefaultBidValidatorConfig = BidValidatorConfig{ + Enabled: true, + RedisURL: "", + StreamPrefix: "", + ConsumerConfig: pubsub.DefaultConsumerConfig, + StreamTimeout: 10 * time.Minute, +} + +var TestBidValidatorConfig = BidValidatorConfig{ + Enabled: true, + RedisURL: "", + StreamPrefix: "test-", + ConsumerConfig: pubsub.TestConsumerConfig, + StreamTimeout: time.Minute, +} + +func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".enabled", DefaultBidValidatorConfig.Enabled, "enable bid validator") + pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + f.String(prefix+".redis-url", DefaultBidValidatorConfig.RedisURL, "url of redis server") + f.String(prefix+".stream-prefix", DefaultBidValidatorConfig.StreamPrefix, "prefix for stream name") + f.Duration(prefix+".stream-timeout", DefaultBidValidatorConfig.StreamTimeout, "Timeout on polling for existence of redis streams") +} + type BidValidator struct { stopwaiter.StopWaiter sync.RWMutex From 8a81d3ea4b82a5524fcf8a4626ab14e4a8201ee8 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 6 Aug 2024 13:12:59 -0500 Subject: [PATCH 045/109] use single config --- cmd/autonomous-auctioneer/config.go | 30 ++++++------ cmd/autonomous-auctioneer/main.go | 73 +++++++++++------------------ timeboost/auctioneer.go | 59 +++++++++++++++-------- timeboost/bid_validator.go | 62 +++++++++++++----------- 4 files changed, 116 insertions(+), 108 deletions(-) diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go index a7c7ed1e17..afbb513bf1 100644 --- a/cmd/autonomous-auctioneer/config.go +++ b/cmd/autonomous-auctioneer/config.go @@ -17,22 +17,20 @@ import ( ) type AutonomousAuctioneerConfig struct { - AuctioneerServer timeboost.AuctioneerServerConfig `koanf:"auctioneer-server"` - BidValidator timeboost.BidValidatorConfig `koanf:"bid-validator"` - Persistent conf.PersistentConfig `koanf:"persistent"` - Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` - LogLevel string `koanf:"log-level" reload:"hot"` - LogType string `koanf:"log-type" reload:"hot"` - FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` - HTTP genericconf.HTTPConfig `koanf:"http"` - WS genericconf.WSConfig `koanf:"ws"` - IPC genericconf.IPCConfig `koanf:"ipc"` - Metrics bool `koanf:"metrics"` - MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` - PProf bool `koanf:"pprof"` - PprofCfg genericconf.PProf `koanf:"pprof-cfg"` - ParentChainWallet genericconf.WalletConfig `koanf:"wallet"` // TODO: Move into auctioneer config. - + AuctioneerServer timeboost.AuctioneerServerConfig `koanf:"auctioneer-server"` + BidValidator timeboost.BidValidatorConfig `koanf:"bid-validator"` + Persistent conf.PersistentConfig `koanf:"persistent"` + Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` + LogLevel string `koanf:"log-level" reload:"hot"` + LogType string `koanf:"log-type" reload:"hot"` + FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` + HTTP genericconf.HTTPConfig `koanf:"http"` + WS genericconf.WSConfig `koanf:"ws"` + IPC genericconf.IPCConfig `koanf:"ipc"` + Metrics bool `koanf:"metrics"` + MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` + PProf bool `koanf:"pprof"` + PprofCfg genericconf.PProf `koanf:"pprof-cfg"` } var HTTPConfigDefault = genericconf.HTTPConfig{ diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go index 9d940579ec..d4b0a71986 100644 --- a/cmd/autonomous-auctioneer/main.go +++ b/cmd/autonomous-auctioneer/main.go @@ -11,7 +11,6 @@ import ( flag "github.com/spf13/pflag" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" @@ -21,6 +20,10 @@ import ( "github.com/offchainlabs/nitro/timeboost" ) +func printSampleUsage(name string) { + fmt.Printf("Sample usage: %s --help \n", name) +} + func main() { os.Exit(mainImpl()) } @@ -54,7 +57,7 @@ func mainImpl() int { args := os.Args[1:] nodeConfig, err := parseAuctioneerArgs(ctx, args) if err != nil { - // confighelpers.PrintErrorAndExit(err, printSampleUsage) + confighelpers.PrintErrorAndExit(err, printSampleUsage) panic(err) } stackConf := DefaultAuctioneerStackConfig @@ -105,62 +108,45 @@ func mainImpl() int { timeboost.EnsureBidValidatorExposedViaRPC(&stackConf) - stack, err := node.New(&stackConf) - if err != nil { - flag.Usage() - log.Crit("failed to initialize geth stack", "err", err) - } - if err := startMetrics(nodeConfig); err != nil { log.Error("Error starting metrics", "error", err) return 1 } fatalErrChan := make(chan error, 10) - log.Info("Running Arbitrum ", "revision", vcsRevision, "vcs.time", vcsTime) - // valNode, err := valnode.CreateValidationNode( - // func() *valnode.Config { return &liveNodeConfig.Get().Validation }, - // stack, - // fatalErrChan, - // ) - // if err != nil { - // log.Error("couldn't init validation node", "err", err) - // return 1 - // } - - // err = valNode.Start(ctx) - // if err != nil { - // log.Error("error starting validator node", "err", err) - // return 1 - // } - err = stack.Start() - if err != nil { - fatalErrChan <- fmt.Errorf("error starting stack: %w", err) + + if nodeConfig.AuctioneerServer.Enable && nodeConfig.BidValidator.Enable { + log.Crit("Both auctioneer and bid validator are enabled, only one can be enabled at a time") + return 1 } - defer stack.Close() - if nodeConfig.Mode == autonomousAuctioneerMode { + if nodeConfig.AuctioneerServer.Enable { + log.Info("Running Arbitrum express lane auctioneer", "revision", vcsRevision, "vcs.time", vcsTime) auctioneer, err := timeboost.NewAuctioneerServer( - nil, - nil, - nil, - common.Address{}, - "", - nil, + ctx, + func() *timeboost.AuctioneerServerConfig { return &liveNodeConfig.Get().AuctioneerServer }, ) if err != nil { log.Error("Error creating new auctioneer", "error", err) return 1 } auctioneer.Start(ctx) - } else if nodeConfig.Mode == bidValidatorMode { + } else if nodeConfig.BidValidator.Enable { + log.Info("Running Arbitrum express lane bid validator", "revision", vcsRevision, "vcs.time", vcsTime) + stack, err := node.New(&stackConf) + if err != nil { + flag.Usage() + log.Crit("failed to initialize geth stack", "err", err) + } + err = stack.Start() + if err != nil { + fatalErrChan <- fmt.Errorf("error starting stack: %w", err) + } + defer stack.Close() bidValidator, err := timeboost.NewBidValidator( - nil, - nil, - nil, - common.Address{}, - "", - nil, + ctx, + stack, + func() *timeboost.BidValidatorConfig { return &liveNodeConfig.Get().BidValidator }, ) if err != nil { log.Error("Error creating new auctioneer", "error", err) @@ -171,9 +157,6 @@ func mainImpl() int { return 1 } bidValidator.Start(ctx) - } else { - log.Crit("Unknown mode, should be either autonomous-auctioneer or bid-validator", "mode", nodeConfig.Mode) - } liveNodeConfig.Start(ctx) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index e77104d31f..d3d2188047 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -3,13 +3,16 @@ package timeboost import ( "context" "fmt" - "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/cmd/util" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/util/redisutil" @@ -34,17 +37,22 @@ func init() { domainValue = hash.Sum(nil) } +type AuctioneerServerConfigFetcher func() *AuctioneerServerConfig + type AuctioneerServerConfig struct { - Enabled bool `koanf:"enabled"` + Enable bool `koanf:"enable"` RedisURL string `koanf:"redis-url"` ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` // Timeout on polling for existence of each redis stream. - StreamTimeout time.Duration `koanf:"stream-timeout"` - StreamPrefix string `koanf:"stream-prefix"` + StreamTimeout time.Duration `koanf:"stream-timeout"` + StreamPrefix string `koanf:"stream-prefix"` + Wallet genericconf.WalletConfig `koanf:"wallet"` + SequencerEndpoint string `koanf:"sequencer-endpoint"` + AuctionContractAddress string `koanf:"auction-contract-address"` } var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ - Enabled: true, + Enable: true, RedisURL: "", StreamPrefix: "", ConsumerConfig: pubsub.DefaultConsumerConfig, @@ -52,7 +60,7 @@ var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ } var TestAuctioneerServerConfig = AuctioneerServerConfig{ - Enabled: true, + Enable: true, RedisURL: "", StreamPrefix: "test-", ConsumerConfig: pubsub.TestConsumerConfig, @@ -60,11 +68,14 @@ var TestAuctioneerServerConfig = AuctioneerServerConfig{ } func AuctioneerConfigAddOptions(prefix string, f *pflag.FlagSet) { - f.Bool(prefix+".enabled", DefaultAuctioneerServerConfig.Enabled, "enable auctioneer server") + f.Bool(prefix+".enable", DefaultAuctioneerServerConfig.Enable, "enable auctioneer server") pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) f.String(prefix+".redis-url", DefaultAuctioneerServerConfig.RedisURL, "url of redis server") f.String(prefix+".stream-prefix", DefaultAuctioneerServerConfig.StreamPrefix, "prefix for stream name") f.Duration(prefix+".stream-timeout", DefaultAuctioneerServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") + genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") + f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") + f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") } // AuctioneerServer is a struct that represents an autonomous auctioneer. @@ -85,26 +96,33 @@ type AuctioneerServer struct { } // NewAuctioneerServer creates a new autonomous auctioneer struct. -func NewAuctioneerServer( - txOpts *bind.TransactOpts, - chainId []*big.Int, - client Client, - auctionContractAddr common.Address, - redisURL string, - consumerCfg *pubsub.ConsumerConfig, -) (*AuctioneerServer, error) { - if redisURL == "" { +func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConfigFetcher) (*AuctioneerServer, error) { + cfg := configFetcher() + if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } - redisClient, err := redisutil.RedisClientFromURL(redisURL) + if cfg.AuctionContractAddress == "" { + return nil, fmt.Errorf("auction contract address cannot be empty") + } + txOpts, _, err := util.OpenWallet("auctioneer-server", &cfg.Wallet, nil) + if err != nil { + return nil, err + } + auctionContractAddr := common.HexToAddress(cfg.AuctionContractAddress) + redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) if err != nil { return nil, err } - c, err := pubsub.NewConsumer[*JsonValidatedBid, error](redisClient, validatedBidsRedisStream, consumerCfg) + c, err := pubsub.NewConsumer[*JsonValidatedBid, error](redisClient, validatedBidsRedisStream, &cfg.ConsumerConfig) if err != nil { return nil, fmt.Errorf("creating consumer for validation: %w", err) } - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) + client, err := rpc.DialContext(ctx, cfg.SequencerEndpoint) + if err != nil { + return nil, err + } + sequencerClient := ethclient.NewClient(client) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, sequencerClient) if err != nil { return nil, err } @@ -117,7 +135,7 @@ func NewAuctioneerServer( roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second return &AuctioneerServer{ txOpts: txOpts, - client: client, + client: sequencerClient, consumer: c, auctionContract: auctionContract, auctionContractAddr: auctionContractAddr, @@ -128,6 +146,7 @@ func NewAuctioneerServer( roundDuration: roundDuration, }, nil } + func (a *AuctioneerServer) Start(ctx_in context.Context) { a.StopWaiter.Start(ctx_in, a) // Channel that consumer uses to indicate its readiness. diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 3dc429b942..cb2b6e44a5 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" @@ -22,37 +23,35 @@ import ( "github.com/spf13/pflag" ) +type BidValidatorConfigFetcher func() *BidValidatorConfig + type BidValidatorConfig struct { - Enabled bool `koanf:"enabled"` + Enable bool `koanf:"enable"` RedisURL string `koanf:"redis-url"` - ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` + ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Timeout on polling for existence of each redis stream. - StreamTimeout time.Duration `koanf:"stream-timeout"` - StreamPrefix string `koanf:"stream-prefix"` + SequencerEndpoint string `koanf:"sequencer-endpoint"` + AuctionContractAddress string `koanf:"auction-contract-address"` } var DefaultBidValidatorConfig = BidValidatorConfig{ - Enabled: true, + Enable: true, RedisURL: "", - StreamPrefix: "", - ConsumerConfig: pubsub.DefaultConsumerConfig, - StreamTimeout: 10 * time.Minute, + ProducerConfig: pubsub.DefaultProducerConfig, } var TestBidValidatorConfig = BidValidatorConfig{ - Enabled: true, + Enable: true, RedisURL: "", - StreamPrefix: "test-", - ConsumerConfig: pubsub.TestConsumerConfig, - StreamTimeout: time.Minute, + ProducerConfig: pubsub.TestProducerConfig, } func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { - f.Bool(prefix+".enabled", DefaultBidValidatorConfig.Enabled, "enable bid validator") - pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + f.Bool(prefix+".enable", DefaultBidValidatorConfig.Enable, "enable bid validator") + pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.String(prefix+".redis-url", DefaultBidValidatorConfig.RedisURL, "url of redis server") - f.String(prefix+".stream-prefix", DefaultBidValidatorConfig.StreamPrefix, "prefix for stream name") - f.Duration(prefix+".stream-timeout", DefaultBidValidatorConfig.StreamTimeout, "Timeout on polling for existence of redis streams") + f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") + f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") } type BidValidator struct { @@ -80,21 +79,29 @@ type BidValidator struct { } func NewBidValidator( - chainId []*big.Int, + ctx context.Context, stack *node.Node, - client Client, - auctionContractAddr common.Address, - redisURL string, - producerCfg *pubsub.ProducerConfig, + configFetcher BidValidatorConfigFetcher, ) (*BidValidator, error) { - if redisURL == "" { + cfg := configFetcher() + if cfg.RedisURL == "" { return nil, fmt.Errorf("redis url cannot be empty") } - redisClient, err := redisutil.RedisClientFromURL(redisURL) + if cfg.AuctionContractAddress == "" { + return nil, fmt.Errorf("auction contract address cannot be empty") + } + auctionContractAddr := common.HexToAddress(cfg.AuctionContractAddress) + redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) + if err != nil { + return nil, err + } + + client, err := rpc.DialContext(ctx, cfg.SequencerEndpoint) if err != nil { return nil, err } - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, client) + sequencerClient := ethclient.NewClient(client) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, sequencerClient) if err != nil { return nil, err } @@ -111,9 +118,10 @@ func NewBidValidator( if err != nil { return nil, err } + chainIds := []*big.Int{big.NewInt(1)} bidValidator := &BidValidator{ - chainId: chainId, - client: client, + chainId: chainIds, + client: sequencerClient, redisClient: redisClient, stack: stack, auctionContract: auctionContract, @@ -128,7 +136,7 @@ func NewBidValidator( domainValue: domainValue, bidsPerSenderInRound: make(map[common.Address]uint8), maxBidsPerSenderInRound: 5, // 5 max bids per sender address in a round. - producerCfg: producerCfg, + producerCfg: &cfg.ProducerConfig, } api := &BidValidatorAPI{bidValidator} valAPIs := []rpc.API{{ From 4e6b1e6ed4faaef8103c245751aa2c2142460de5 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 6 Aug 2024 19:19:25 -0500 Subject: [PATCH 046/109] test passing for redis stream --- Dockerfile | 1 + Makefile | 5 ++- timeboost/auctioneer_test.go | 73 +++++++++++++++++++----------------- timeboost/bid_cache_test.go | 68 +++++++++++++++------------------ timeboost/bid_validator.go | 16 +++++++- timeboost/setup_test.go | 19 ++++++++-- 6 files changed, 104 insertions(+), 78 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22ac262f52..721afa37d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -286,6 +286,7 @@ FROM nitro-node-slim AS nitro-node USER root COPY --from=prover-export /bin/jit /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/daserver /usr/local/bin/ +COPY --from=node-builder /workspace/target/bin/autonomous-auctioneer /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/datool /usr/local/bin/ COPY --from=nitro-legacy /home/user/target/machines /home/user/nitro-legacy/machines RUN rm -rf /workspace/target/legacy-machines/latest diff --git a/Makefile b/Makefile index b0d8116c97..ba3758262a 100644 --- a/Makefile +++ b/Makefile @@ -153,7 +153,7 @@ push: lint test-go .make/fmt all: build build-replay-env test-gen-proofs @touch .make/all -build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daserver datool seq-coordinator-invalidate nitro-val seq-coordinator-manager) +build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daserver autonomous-auctioneer datool seq-coordinator-invalidate nitro-val seq-coordinator-manager) @printf $(done) build-node-deps: $(go_source) build-prover-header build-prover-lib build-jit .make/solgen .make/cbrotli-lib @@ -255,6 +255,9 @@ $(output_root)/bin/relay: $(DEP_PREDICATE) build-node-deps $(output_root)/bin/daserver: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/daserver" +$(output_root)/bin/autonomous-auctioneer: $(DEP_PREDICATE) build-node-deps + go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/autonomous-auctioneer" + $(output_root)/bin/datool: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/datool" diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index ba095f2bbb..080f964c79 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -2,8 +2,8 @@ package timeboost import ( "context" + "fmt" "math/big" - "sync" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/util/redisutil" "github.com/stretchr/testify/require" @@ -28,7 +29,6 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { // will then consume. numBidValidators := 3 bidValidators := make([]*BidValidator, numBidValidators) - chainIds := []*big.Int{testSetup.chainId} for i := 0; i < numBidValidators; i++ { randHttp := getRandomPort(t) stackConf := node.Config{ @@ -50,30 +50,46 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { } stack, err := node.New(&stackConf) require.NoError(t, err) + cfg := &BidValidatorConfig{ + ChainIds: []string{fmt.Sprintf("%d", testSetup.chainId.Uint64())}, + SequencerEndpoint: testSetup.endpoint, + AuctionContractAddress: testSetup.expressLaneAuctionAddr.Hex(), + RedisURL: redisURL, + ProducerConfig: pubsub.TestProducerConfig, + } + fetcher := func() *BidValidatorConfig { + return cfg + } bidValidator, err := NewBidValidator( - chainIds, + ctx, stack, - testSetup.backend.Client(), - testSetup.expressLaneAuctionAddr, - redisURL, - &pubsub.TestProducerConfig, + fetcher, ) require.NoError(t, err) require.NoError(t, bidValidator.Initialize(ctx)) + require.NoError(t, stack.Start()) bidValidator.Start(ctx) bidValidators[i] = bidValidator } t.Log("Started multiple bid validators") // Set up a single auctioneer instance that can consume messages produced - // by the bid validator from a redis stream. + // by the bid validators from a redis stream. + cfg := &AuctioneerServerConfig{ + SequencerEndpoint: testSetup.endpoint, + AuctionContractAddress: testSetup.expressLaneAuctionAddr.Hex(), + RedisURL: redisURL, + ConsumerConfig: pubsub.TestConsumerConfig, + Wallet: genericconf.WalletConfig{ + PrivateKey: fmt.Sprintf("%x", testSetup.accounts[0].privKey.D.Bytes()), + }, + } + fetcher := func() *AuctioneerServerConfig { + return cfg + } am, err := NewAuctioneerServer( - testSetup.accounts[0].txOpts, - chainIds, - testSetup.backend.Client(), - testSetup.expressLaneAuctionAddr, - redisURL, - &pubsub.TestConsumerConfig, + ctx, + fetcher, ) require.NoError(t, err) am.Start(ctx) @@ -97,25 +113,14 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { <-time.After(timeToWait) time.Sleep(time.Millisecond * 250) // Add 1/4 of a second of wait so that we are definitely within a round. - // Alice, Bob, and Charlie will submit bids to the three different bid validators. - var wg sync.WaitGroup + // Alice, Bob, and Charlie will submit bids to the three different bid validators instances. start := time.Now() - for i := 1; i <= 4; i++ { - wg.Add(3) - go func(w *sync.WaitGroup, ii int) { - defer w.Done() - alice.Bid(ctx, big.NewInt(int64(ii)), aliceAddr) - }(&wg, i) - go func(w *sync.WaitGroup, ii int) { - defer w.Done() - bob.Bid(ctx, big.NewInt(int64(ii)+1), bobAddr) // Bob bids 1 wei higher than Alice. - }(&wg, i) - go func(w *sync.WaitGroup, ii int) { - defer w.Done() - charlie.Bid(ctx, big.NewInt(int64(ii)+2), charlieAddr) // Charlie bids 2 wei higher than the Bob. - }(&wg, i) + for i := 1; i <= 5; i++ { + alice.Bid(ctx, big.NewInt(int64(i)), aliceAddr) + bob.Bid(ctx, big.NewInt(int64(i)+1), bobAddr) // Bob bids 1 wei higher than Alice. + charlie.Bid(ctx, big.NewInt(int64(i)+2), charlieAddr) // Charlie bids 2 wei higher than the Bob. } - wg.Wait() + // We expect that a final submission from each fails, as the bid limit is exceeded. _, err = alice.Bid(ctx, big.NewInt(6), aliceAddr) require.ErrorContains(t, err, ErrTooManyBids.Error()) @@ -127,12 +132,12 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { t.Log("Submitted bids", time.Now(), time.Since(start)) time.Sleep(time.Second * 15) - // We verify that the auctioneer has received bids from the single Redis stream. + // We verify that the auctioneer has consumed all validated bids from the single Redis stream. // We also verify the top two bids are those we expect. require.Equal(t, 3, len(am.bidCache.bidsByExpressLaneControllerAddr)) result := am.bidCache.topTwoBids() - require.Equal(t, result.firstPlace.Amount, big.NewInt(6)) + require.Equal(t, result.firstPlace.Amount, big.NewInt(7)) require.Equal(t, result.firstPlace.Bidder, charlieAddr) - require.Equal(t, result.secondPlace.Amount, big.NewInt(5)) + require.Equal(t, result.secondPlace.Amount, big.NewInt(6)) require.Equal(t, result.secondPlace.Bidder, bobAddr) } diff --git a/timeboost/bid_cache_test.go b/timeboost/bid_cache_test.go index db763dd39b..2db8d10d2f 100644 --- a/timeboost/bid_cache_test.go +++ b/timeboost/bid_cache_test.go @@ -1,15 +1,9 @@ package timeboost import ( - "context" - "fmt" - "math/big" "net" "testing" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) @@ -232,37 +226,37 @@ import ( // } // } -func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*AuctioneerServer, string) { - // Set up a new auctioneer instance that can validate bids. - // Set up the auctioneer RPC service. - randHttp := getRandomPort(t) - stackConf := node.Config{ - DataDir: "", // ephemeral. - HTTPPort: randHttp, - HTTPModules: []string{AuctioneerNamespace}, - HTTPHost: "localhost", - HTTPVirtualHosts: []string{"localhost"}, - HTTPTimeouts: rpc.DefaultHTTPTimeouts, - WSPort: getRandomPort(t), - WSModules: []string{AuctioneerNamespace}, - WSHost: "localhost", - GraphQLVirtualHosts: []string{"localhost"}, - P2P: p2p.Config{ - ListenAddr: "", - NoDial: true, - NoDiscovery: true, - }, - } - stack, err := node.New(&stackConf) - require.NoError(t, err) - am, err := NewAuctioneerServer( - testSetup.accounts[0].txOpts, []*big.Int{testSetup.chainId}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, "", nil, - ) - require.NoError(t, err) - go am.Start(ctx) - require.NoError(t, stack.Start()) - return am, fmt.Sprintf("http://localhost:%d", randHttp) -} +// func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*AuctioneerServer, string) { +// // Set up a new auctioneer instance that can validate bids. +// // Set up the auctioneer RPC service. +// randHttp := getRandomPort(t) +// stackConf := node.Config{ +// DataDir: "", // ephemeral. +// HTTPPort: randHttp, +// HTTPModules: []string{AuctioneerNamespace}, +// HTTPHost: "localhost", +// HTTPVirtualHosts: []string{"localhost"}, +// HTTPTimeouts: rpc.DefaultHTTPTimeouts, +// WSPort: getRandomPort(t), +// WSModules: []string{AuctioneerNamespace}, +// WSHost: "localhost", +// GraphQLVirtualHosts: []string{"localhost"}, +// P2P: p2p.Config{ +// ListenAddr: "", +// NoDial: true, +// NoDiscovery: true, +// }, +// } +// stack, err := node.New(&stackConf) +// require.NoError(t, err) +// am, err := NewAuctioneerServer( +// testSetup.accounts[0].txOpts, []*big.Int{testSetup.chainId}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, "", nil, +// ) +// require.NoError(t, err) +// go am.Start(ctx) +// require.NoError(t, stack.Start()) +// return am, fmt.Sprintf("http://localhost:%d", randHttp) +// } func getRandomPort(t testing.TB) int { listener, err := net.Listen("tcp", "localhost:0") diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index cb2b6e44a5..02c55de7c3 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -28,6 +28,7 @@ type BidValidatorConfigFetcher func() *BidValidatorConfig type BidValidatorConfig struct { Enable bool `koanf:"enable"` RedisURL string `koanf:"redis-url"` + ChainIds []string `koanf:"chain-ids"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Timeout on polling for existence of each redis stream. SequencerEndpoint string `koanf:"sequencer-endpoint"` @@ -50,6 +51,7 @@ func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBidValidatorConfig.Enable, "enable bid validator") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.String(prefix+".redis-url", DefaultBidValidatorConfig.RedisURL, "url of redis server") + f.StringSlice(prefix+".chain-ids", DefaultBidValidatorConfig.ChainIds, "chain ids to support") f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") } @@ -90,6 +92,17 @@ func NewBidValidator( if cfg.AuctionContractAddress == "" { return nil, fmt.Errorf("auction contract address cannot be empty") } + if len(cfg.ChainIds) == 0 { + return nil, fmt.Errorf("expected at least one chain id") + } + chainIds := make([]*big.Int, len(cfg.ChainIds)) + for i, cidStr := range cfg.ChainIds { + id, ok := new(big.Int).SetString(cidStr, 10) + if !ok { + return nil, fmt.Errorf("could not parse chain id into big int base 10 %s", cidStr) + } + chainIds[i] = id + } auctionContractAddr := common.HexToAddress(cfg.AuctionContractAddress) redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) if err != nil { @@ -118,7 +131,6 @@ func NewBidValidator( if err != nil { return nil, err } - chainIds := []*big.Int{big.NewInt(1)} bidValidator := &BidValidator{ chainId: chainIds, client: sequencerClient, @@ -310,7 +322,7 @@ func (bv *BidValidator) validateBid( if !ok { bv.bidsPerSenderInRound[bidder] = 1 } - if numBids >= bv.maxBidsPerSenderInRound { + if numBids > bv.maxBidsPerSenderInRound { bv.Unlock() return nil, errors.Wrapf(ErrTooManyBids, "bidder %s has already sent the maximum allowed bids = %d in this round", bidder.Hex(), numBids) } diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index bca30e1327..ad80c26a50 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -3,6 +3,7 @@ package timeboost import ( "context" "crypto/ecdsa" + "fmt" "math/big" "testing" "time" @@ -11,7 +12,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/node" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/timeboost/bindings" @@ -30,10 +33,11 @@ type auctionSetup struct { beneficiaryAddr common.Address accounts []*testAccount backend *simulated.Backend + endpoint string } func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { - accs, backend := setupAccounts(10) + accs, backend, endpoint := setupAccounts(t, 10) go func() { tick := time.NewTicker(time.Second) @@ -144,6 +148,7 @@ func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { beneficiaryAddr: beneficiary, accounts: accs, backend: backend, + endpoint: endpoint, } } @@ -186,7 +191,7 @@ type testAccount struct { txOpts *bind.TransactOpts } -func setupAccounts(numAccounts uint64) ([]*testAccount, *simulated.Backend) { +func setupAccounts(t testing.TB, numAccounts uint64) ([]*testAccount, *simulated.Backend, string) { genesis := make(core.GenesisAlloc) gasLimit := uint64(100000000) @@ -213,8 +218,14 @@ func setupAccounts(numAccounts uint64) ([]*testAccount, *simulated.Backend) { privKey: privKey, } } - backend := simulated.NewBackend(genesis, simulated.WithBlockGasLimit(gasLimit)) - return accs, backend + randPort := getRandomPort(t) + withRPC := func(n *node.Config, _ *ethconfig.Config) { + n.HTTPHost = "localhost" + n.HTTPPort = randPort + n.HTTPModules = []string{"eth", "net", "web3", "debug", "personal"} + } + backend := simulated.NewBackend(genesis, simulated.WithBlockGasLimit(gasLimit), withRPC) + return accs, backend, fmt.Sprintf("http://localhost:%d", randPort) } func mintTokens(ctx context.Context, From 49ca6fa8d4f2983774db660d32d6264138c42536 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 6 Aug 2024 21:49:32 -0500 Subject: [PATCH 047/109] specify the privileged sequencer endpoint for auctioneer --- execution/gethexec/api.go | 12 ++++ execution/gethexec/arb_interface.go | 5 ++ execution/gethexec/forwarder.go | 32 +++++++++++ execution/gethexec/node.go | 7 +++ execution/gethexec/sequencer.go | 84 +++++++++++++++++++++------- execution/gethexec/tx_pre_checker.go | 17 ++++++ timeboost/auctioneer.go | 48 ++++++++++++++-- timeboost/bidder_client.go | 11 +--- 8 files changed, 185 insertions(+), 31 deletions(-) diff --git a/execution/gethexec/api.go b/execution/gethexec/api.go index 27116a50e0..c32e0c0064 100644 --- a/execution/gethexec/api.go +++ b/execution/gethexec/api.go @@ -36,6 +36,18 @@ func (a *ArbAPI) CheckPublisherHealth(ctx context.Context) error { return a.txPublisher.CheckHealth(ctx) } +type ArbTimeboostAuctioneerAPI struct { + txPublisher TransactionPublisher +} + +func NewArbTimeboostAuctioneerAPI(publisher TransactionPublisher) *ArbTimeboostAuctioneerAPI { + return &ArbTimeboostAuctioneerAPI{publisher} +} + +func (a *ArbTimeboostAuctioneerAPI) SubmitAuctionResolutionTransaction(ctx context.Context, tx *types.Transaction) error { + return a.txPublisher.PublishAuctionResolutionTransaction(ctx, tx) +} + type ArbTimeboostAPI struct { txPublisher TransactionPublisher } diff --git a/execution/gethexec/arb_interface.go b/execution/gethexec/arb_interface.go index de0cacbd98..7e43338f08 100644 --- a/execution/gethexec/arb_interface.go +++ b/execution/gethexec/arb_interface.go @@ -13,6 +13,7 @@ import ( ) type TransactionPublisher interface { + PublishAuctionResolutionTransaction(ctx context.Context, tx *types.Transaction) error PublishExpressLaneTransaction(ctx context.Context, msg *timeboost.ExpressLaneSubmission) error PublishTransaction(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error CheckHealth(ctx context.Context) error @@ -51,6 +52,10 @@ func (a *ArbInterface) PublishExpressLaneTransaction(ctx context.Context, msg *t return a.txPublisher.PublishExpressLaneTransaction(ctx, goMsg) } +func (a *ArbInterface) PublishAuctionResolutionTransaction(ctx context.Context, tx *types.Transaction) error { + return a.txPublisher.PublishAuctionResolutionTransaction(ctx, tx) +} + // might be used before Initialize func (a *ArbInterface) BlockChain() *core.BlockChain { return a.blockchain diff --git a/execution/gethexec/forwarder.go b/execution/gethexec/forwarder.go index 3f1ad7c5d3..021c9d4660 100644 --- a/execution/gethexec/forwarder.go +++ b/execution/gethexec/forwarder.go @@ -181,6 +181,26 @@ func sendExpressLaneTransactionRPC(ctx context.Context, rpcClient *rpc.Client, m return rpcClient.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", jsonMsg) } +func (f *TxForwarder) PublishAuctionResolutionTransaction(inctx context.Context, tx *types.Transaction) error { + if !f.enabled.Load() { + return ErrNoSequencer + } + ctx, cancelFunc := f.ctxWithTimeout() + defer cancelFunc() + for pos, rpcClient := range f.rpcClients { + err := sendAuctionResolutionTransactionRPC(ctx, rpcClient, tx) + if err == nil || !f.tryNewForwarderErrors.MatchString(err.Error()) { + return err + } + log.Warn("error forwarding transaction to a backup target", "target", f.targets[pos], "err", err) + } + return errors.New("failed to publish transaction to any of the forwarding targets") +} + +func sendAuctionResolutionTransactionRPC(ctx context.Context, rpcClient *rpc.Client, tx *types.Transaction) error { + return rpcClient.CallContext(ctx, nil, "auctioneer_submitAuctionResolutionTransaction", tx) +} + const cacheUpstreamHealth = 2 * time.Second const maxHealthTimeout = 10 * time.Second @@ -281,6 +301,10 @@ func (f *TxDropper) PublishExpressLaneTransaction(ctx context.Context, msg *time return txDropperErr } +func (f *TxDropper) PublishAuctionResolutionTransaction(ctx context.Context, tx *types.Transaction) error { + return txDropperErr +} + func (f *TxDropper) CheckHealth(ctx context.Context) error { return txDropperErr } @@ -332,6 +356,14 @@ func (f *RedisTxForwarder) PublishExpressLaneTransaction(ctx context.Context, ms return forwarder.PublishExpressLaneTransaction(ctx, msg) } +func (f *RedisTxForwarder) PublishAuctionResolutionTransaction(ctx context.Context, tx *types.Transaction) error { + forwarder := f.getForwarder() + if forwarder == nil { + return ErrNoSequencer + } + return forwarder.PublishAuctionResolutionTransaction(ctx, tx) +} + func (f *RedisTxForwarder) CheckHealth(ctx context.Context) error { forwarder := f.getForwarder() if forwarder == nil { diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 01e7f7e5ac..f78dde5646 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -233,6 +233,13 @@ func CreateExecutionNode( Service: NewArbAPI(txPublisher), Public: false, }} + apis = append(apis, rpc.API{ + Namespace: "auctioneer", + Version: "1.0", + Service: NewArbTimeboostAuctioneerAPI(txPublisher), + Public: false, + // Authenticated: true, /* Only exposed via JWT Auth */ + }) apis = append(apis, rpc.API{ Namespace: "timeboost", Version: "1.0", diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index f8116134b0..1b4b376dbb 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -86,21 +86,14 @@ type SequencerConfig struct { } type TimeboostConfig struct { - Enable bool `koanf:"enable"` - AuctionContractAddress string `koanf:"auction-contract-address"` - ERC20Address string `koanf:"erc20-address"` - ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` - RoundDuration time.Duration `koanf:"round-duration"` - InitialRoundTimestamp uint64 `koanf:"initial-round-timestamp"` + Enable bool `koanf:"enable"` + ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` + AuctioneerAddress string `koanf:"auctioneer-address"` } var DefaultTimeboostConfig = TimeboostConfig{ - Enable: false, - AuctionContractAddress: "", - ERC20Address: "", - ExpressLaneAdvantage: time.Millisecond * 250, - RoundDuration: time.Minute, - InitialRoundTimestamp: uint64(time.Unix(0, 0).Unix()), + Enable: false, + ExpressLaneAdvantage: time.Millisecond * 250, } func (c *SequencerConfig) Validate() error { @@ -196,10 +189,7 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { func TimeboostAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultTimeboostConfig.Enable, "enable timeboost based on express lane auctions") - f.String(prefix+".auction-contract-address", DefaultTimeboostConfig.AuctionContractAddress, "address of the autonomous auction contract") - f.String(prefix+".erc20-address", DefaultTimeboostConfig.ERC20Address, "address of the auction erc20") - f.Uint64(prefix+".initial-round-timestamp", DefaultTimeboostConfig.InitialRoundTimestamp, "initial timestamp for auctions") - f.Duration(prefix+".round-duration", DefaultTimeboostConfig.RoundDuration, "round duration") + f.Duration(prefix+".express-lane-advantage", DefaultTimeboostConfig.ExpressLaneAdvantage, "specify the express lane advantage") } type txQueueItem struct { @@ -366,9 +356,11 @@ type Sequencer struct { pauseChan chan struct{} forwarder *TxForwarder - expectedSurplusMutex sync.RWMutex - expectedSurplus int64 - expectedSurplusUpdated bool + expectedSurplusMutex sync.RWMutex + expectedSurplus int64 + expectedSurplusUpdated bool + timeboostLock sync.Mutex + timeboostAuctionResolutionTx *types.Transaction } func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderReader, configFetcher SequencerConfigFetcher) (*Sequencer, error) { @@ -547,6 +539,37 @@ func (s *Sequencer) PublishExpressLaneTransaction(ctx context.Context, msg *time return s.expressLaneService.sequenceExpressLaneSubmission(ctx, msg, s.publishTransactionImpl) } +func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx *types.Transaction) error { + if !s.config().Timeboost.Enable { + return errors.New("timeboost not enabled") + } + if s.config().Timeboost.AuctioneerAddress == "" { + return errors.New("auctioneer address not set") + } + auctionerAddr := common.HexToAddress(s.config().Timeboost.AuctioneerAddress) + if auctionerAddr == (common.Address{}) { + return errors.New("invalid auctioneer address") + } + signer := types.LatestSigner(s.execEngine.bc.Config()) + sender, err := types.Sender(signer, tx) + if err != nil { + return err + } + if sender != auctionerAddr { + return fmt.Errorf("sender %#x is not the auctioneer address %#x", sender, auctionerAddr) + } + // TODO: Authenticate it is within the resolution window. + s.timeboostLock.Lock() + defer s.timeboostLock.Unlock() + // Set it as a value that will be consumed first in `createBlock` + if s.timeboostAuctionResolutionTx != nil { + return errors.New("auction resolution tx for round already received") + } + log.Info("Received auction resolution tx") + s.timeboostAuctionResolutionTx = tx + return nil +} + func (s *Sequencer) preTxFilter(_ *params.ChainConfig, header *types.Header, statedb *state.StateDB, _ *arbosState.ArbosState, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, sender common.Address, l1Info *arbos.L1Info) error { if s.nonceCache.Caching() { stateNonce := s.nonceCache.Get(header, statedb, sender) @@ -927,6 +950,29 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { s.nonceCache.Resize(config.NonceCacheSize) // Would probably be better in a config hook but this is basically free s.nonceCache.BeginNewBlock() + if s.config().Timeboost.Enable { + s.timeboostLock.Lock() + if s.timeboostAuctionResolutionTx != nil { + txBytes, err := s.timeboostAuctionResolutionTx.MarshalBinary() + if err != nil { + s.timeboostLock.Unlock() + log.Error("Failed to marshal timeboost auction resolution tx", "err", err) + return false + } + queueItems = append([]txQueueItem{ + { + tx: s.timeboostAuctionResolutionTx, + txSize: len(txBytes), + options: nil, + resultChan: make(chan error, 1), + returnedResult: &atomic.Bool{}, + ctx: ctx, + firstAppearance: time.Now(), + }, + }, queueItems...) + } + s.timeboostLock.Unlock() + } queueItems = s.precheckNonces(queueItems, totalBlockSize) txes := make([]*types.Transaction, len(queueItems)) hooks := s.makeSequencingHooks() diff --git a/execution/gethexec/tx_pre_checker.go b/execution/gethexec/tx_pre_checker.go index 1820b3c63e..37095a412f 100644 --- a/execution/gethexec/tx_pre_checker.go +++ b/execution/gethexec/tx_pre_checker.go @@ -242,3 +242,20 @@ func (c *TxPreChecker) PublishExpressLaneTransaction(ctx context.Context, msg *t } return c.TransactionPublisher.PublishExpressLaneTransaction(ctx, msg) } + +func (c *TxPreChecker) PublishAuctionResolutionTransaction(ctx context.Context, tx *types.Transaction) error { + block := c.bc.CurrentBlock() + statedb, err := c.bc.StateAt(block.Root) + if err != nil { + return err + } + arbos, err := arbosState.OpenSystemArbosState(statedb, nil, true) + if err != nil { + return err + } + err = PreCheckTx(c.bc, c.bc.Config(), block, statedb, arbos, tx, nil, c.config()) + if err != nil { + return err + } + return c.TransactionPublisher.PublishAuctionResolutionTransaction(ctx, tx) +} diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index d3d2188047..b55567399d 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -3,6 +3,7 @@ package timeboost import ( "context" "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -84,7 +85,8 @@ type AuctioneerServer struct { stopwaiter.StopWaiter consumer *pubsub.Consumer[*JsonValidatedBid, error] txOpts *bind.TransactOpts - client Client + sequencerRpc *rpc.Client + client *ethclient.Client auctionContract *express_lane_auctiongen.ExpressLaneAuction auctionContractAddr common.Address bidsReceiver chan *JsonValidatedBid @@ -135,6 +137,7 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second return &AuctioneerServer{ txOpts: txOpts, + sequencerRpc: client, client: sequencerClient, consumer: c, auctionContract: auctionContract, @@ -263,10 +266,12 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { second := result.secondPlace var tx *types.Transaction var err error + opts := copyTxOpts(a.txOpts) + opts.NoSend = true switch { case first != nil && second != nil: // Both bids are present tx, err = a.auctionContract.ResolveMultiBidAuction( - a.txOpts, + opts, express_lane_auctiongen.Bid{ ExpressLaneController: first.ExpressLaneController, Amount: first.Amount, @@ -282,7 +287,7 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { case first != nil: // Single bid is present tx, err = a.auctionContract.ResolveSingleBidAuction( - a.txOpts, + opts, express_lane_auctiongen.Bid{ ExpressLaneController: first.ExpressLaneController, Amount: first.Amount, @@ -295,12 +300,16 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { log.Info("No bids received for auction resolution", "round", upcomingRound) return nil } - if err != nil { log.Error("Error resolving auction", "error", err) return err } + if err = a.sendAuctionResolutionTransactionRPC(ctx, tx); err != nil { + log.Error("Error submitting auction resolution to privileged sequencer endpoint", "error", err) + return err + } + receipt, err := bind.WaitMined(ctx, a.client, tx) if err != nil { log.Error("Error waiting for transaction to be mined", "error", err) @@ -317,3 +326,34 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { log.Info("Auction resolved successfully", "txHash", tx.Hash().Hex()) return nil } + +func (a *AuctioneerServer) sendAuctionResolutionTransactionRPC(ctx context.Context, tx *types.Transaction) error { + return a.sequencerRpc.CallContext(ctx, nil, "timeboost_submitAuctionResolutionTransaction", tx) +} + +func copyTxOpts(opts *bind.TransactOpts) *bind.TransactOpts { + copied := &bind.TransactOpts{ + From: opts.From, + Context: opts.Context, + NoSend: opts.NoSend, + Signer: opts.Signer, + GasLimit: opts.GasLimit, + } + + if opts.Nonce != nil { + copied.Nonce = new(big.Int).Set(opts.Nonce) + } + if opts.Value != nil { + copied.Value = new(big.Int).Set(opts.Value) + } + if opts.GasPrice != nil { + copied.GasPrice = new(big.Int).Set(opts.GasPrice) + } + if opts.GasFeeCap != nil { + copied.GasFeeCap = new(big.Int).Set(opts.GasFeeCap) + } + if opts.GasTipCap != nil { + copied.GasTipCap = new(big.Int).Set(opts.GasTipCap) + } + return copied +} diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index ab143cc81f..9698075343 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -13,23 +13,18 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/pkg/errors" ) -type Client interface { - bind.ContractBackend - bind.DeployBackend - ChainID(ctx context.Context) (*big.Int, error) -} - type BidderClient struct { chainId *big.Int name string auctionContractAddress common.Address txOpts *bind.TransactOpts - client Client + client *ethclient.Client privKey *ecdsa.PrivateKey auctionContract *express_lane_auctiongen.ExpressLaneAuction auctioneerClient *rpc.Client @@ -48,7 +43,7 @@ func NewBidderClient( ctx context.Context, name string, wallet *Wallet, - client Client, + client *ethclient.Client, auctionContractAddress common.Address, auctioneerEndpoint string, ) (*BidderClient, error) { From 840be36d8b55f450d4bdfcef738bd9247980ce83 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 09:07:58 -0500 Subject: [PATCH 048/109] privileged endpoint --- execution/gethexec/sequencer.go | 24 ++++++++--------- system_tests/seqfeed_test.go | 47 ++++++++++++++++++++++++++------- timeboost/auctioneer.go | 26 ++++++++++++------ timeboost/auctioneer_test.go | 1 - timeboost/bid_validator.go | 34 ++++++------------------ timeboost/bid_validator_test.go | 4 +-- timeboost/db/db.go | 9 ------- timeboost/setup_test.go | 2 +- 8 files changed, 79 insertions(+), 68 deletions(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 1b4b376dbb..0fcfe8e822 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -88,7 +88,6 @@ type SequencerConfig struct { type TimeboostConfig struct { Enable bool `koanf:"enable"` ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` - AuctioneerAddress string `koanf:"auctioneer-address"` } var DefaultTimeboostConfig = TimeboostConfig{ @@ -359,6 +358,7 @@ type Sequencer struct { expectedSurplusMutex sync.RWMutex expectedSurplus int64 expectedSurplusUpdated bool + auctioneerAddr common.Address timeboostLock sync.Mutex timeboostAuctionResolutionTx *types.Transaction } @@ -543,11 +543,8 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx if !s.config().Timeboost.Enable { return errors.New("timeboost not enabled") } - if s.config().Timeboost.AuctioneerAddress == "" { - return errors.New("auctioneer address not set") - } - auctionerAddr := common.HexToAddress(s.config().Timeboost.AuctioneerAddress) - if auctionerAddr == (common.Address{}) { + auctioneerAddr := s.auctioneerAddr + if auctioneerAddr == (common.Address{}) { return errors.New("invalid auctioneer address") } signer := types.LatestSigner(s.execEngine.bc.Config()) @@ -555,18 +552,20 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx if err != nil { return err } - if sender != auctionerAddr { - return fmt.Errorf("sender %#x is not the auctioneer address %#x", sender, auctionerAddr) + if sender != auctioneerAddr { + return fmt.Errorf("sender %#x is not the auctioneer address %#x", sender, auctioneerAddr) } - // TODO: Authenticate it is within the resolution window. + // TODO: Check it is within the resolution window. s.timeboostLock.Lock() - defer s.timeboostLock.Unlock() // Set it as a value that will be consumed first in `createBlock` if s.timeboostAuctionResolutionTx != nil { + s.timeboostLock.Unlock() return errors.New("auction resolution tx for round already received") } - log.Info("Received auction resolution tx") s.timeboostAuctionResolutionTx = tx + s.timeboostLock.Unlock() + log.Info("Creating auction resolution tx") + s.createBlock(ctx) return nil } @@ -1245,7 +1244,7 @@ func (s *Sequencer) Start(ctxIn context.Context) error { return nil } -func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr common.Address) { +func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr common.Address, auctioneerAddr common.Address) { if !s.config().Timeboost.Enable { log.Crit("Timeboost is not enabled, but StartExpressLane was called") } @@ -1262,6 +1261,7 @@ func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr co if err != nil { log.Crit("Failed to create express lane service", "err", err) } + s.auctioneerAddr = auctioneerAddr s.expressLaneService = els s.expressLaneService.Start(ctx) } diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 12b29d3e5e..9d57b95669 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -26,13 +26,16 @@ import ( "github.com/offchainlabs/nitro/broadcastclient" "github.com/offchainlabs/nitro/broadcaster/backlog" "github.com/offchainlabs/nitro/broadcaster/message" + "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/relay" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/testhelpers" "github.com/offchainlabs/nitro/wsbroadcastserver" @@ -305,7 +308,7 @@ func setupExpressLaneAuction( builderSeq.l2StackConfig.HTTPHost = "localhost" builderSeq.l2StackConfig.HTTPPort = 9567 - builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost"} + builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost", "auctioneer"} builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() builderSeq.execConfig.Sequencer.Enable = true builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ @@ -444,13 +447,11 @@ func setupExpressLaneAuction( t.Fatal(err) } - builderSeq.L2.ExecNode.Sequencer.StartExpressLane(ctx, proxyAddr) + builderSeq.L2.ExecNode.Sequencer.StartExpressLane(ctx, proxyAddr, seqInfo.GetAddress("AuctionContract")) t.Log("Started express lane service in sequencer") // Set up an autonomous auction contract service that runs in the background in this test. - auctionContractOpts := seqInfo.GetDefaultTransactOpts("AuctionContract", ctx) - chainId, err := seqClient.ChainID(ctx) - Require(t, err) + redisURL := redisutil.CreateTestRedis(ctx, t) // Set up the auctioneer RPC service. stackConf := node.Config{ @@ -472,13 +473,41 @@ func setupExpressLaneAuction( } stack, err := node.New(&stackConf) Require(t, err) - auctioneer, err := timeboost.NewAuctioneerServer( - &auctionContractOpts, []*big.Int{chainId}, seqClient, proxyAddr, "", nil, + cfg := &timeboost.BidValidatorConfig{ + SequencerEndpoint: "http://localhost:9567", + AuctionContractAddress: proxyAddr.Hex(), + RedisURL: redisURL, + ProducerConfig: pubsub.TestProducerConfig, + } + fetcher := func() *timeboost.BidValidatorConfig { + return cfg + } + bidValidator, err := timeboost.NewBidValidator( + ctx, stack, fetcher, ) Require(t, err) - - go auctioneer.Start(ctx) Require(t, stack.Start()) + Require(t, bidValidator.Initialize(ctx)) + bidValidator.Start(ctx) + + auctioneerCfg := &timeboost.AuctioneerServerConfig{ + SequencerEndpoint: "http://localhost:9567", + AuctionContractAddress: proxyAddr.Hex(), + RedisURL: redisURL, + ConsumerConfig: pubsub.TestConsumerConfig, + Wallet: genericconf.WalletConfig{ + PrivateKey: fmt.Sprintf("00%x", seqInfo.Accounts["AuctionContract"].PrivateKey.D.Bytes()), + }, + } + auctioneerFetcher := func() *timeboost.AuctioneerServerConfig { + return auctioneerCfg + } + am, err := timeboost.NewAuctioneerServer( + ctx, + auctioneerFetcher, + ) + Require(t, err) + am.Start(ctx) // Set up a bidder client for Alice and Bob. alicePriv := seqInfo.Accounts["Alice"].PrivateKey diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index b55567399d..e5254a1b12 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -29,7 +29,7 @@ var domainValue []byte const ( AuctioneerNamespace = "auctioneer" - validatedBidsRedisStream = "validated_bid_stream" + validatedBidsRedisStream = "validated_bids" ) func init() { @@ -85,6 +85,7 @@ type AuctioneerServer struct { stopwaiter.StopWaiter consumer *pubsub.Consumer[*JsonValidatedBid, error] txOpts *bind.TransactOpts + chainId *big.Int sequencerRpc *rpc.Client client *ethclient.Client auctionContract *express_lane_auctiongen.ExpressLaneAuction @@ -106,10 +107,6 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf if cfg.AuctionContractAddress == "" { return nil, fmt.Errorf("auction contract address cannot be empty") } - txOpts, _, err := util.OpenWallet("auctioneer-server", &cfg.Wallet, nil) - if err != nil { - return nil, err - } auctionContractAddr := common.HexToAddress(cfg.AuctionContractAddress) redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) if err != nil { @@ -124,6 +121,14 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf return nil, err } sequencerClient := ethclient.NewClient(client) + chainId, err := sequencerClient.ChainID(ctx) + if err != nil { + return nil, err + } + txOpts, _, err := util.OpenWallet("auctioneer-server", &cfg.Wallet, chainId) + if err != nil { + return nil, errors.Wrap(err, "opening wallet") + } auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, sequencerClient) if err != nil { return nil, err @@ -138,6 +143,7 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf return &AuctioneerServer{ txOpts: txOpts, sequencerRpc: client, + chainId: chainId, client: sequencerClient, consumer: c, auctionContract: auctionContract, @@ -191,7 +197,6 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { // There's nothing in the queue. return time.Second // TODO: Make this faster? } - log.Info("Auctioneer received") // Forward the message over a channel for processing elsewhere in // another thread, so as to not block this consumption thread. a.bidsReceiver <- req.Value @@ -228,7 +233,7 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { for { select { case bid := <-a.bidsReceiver: - log.Info("Processed validated bid", "bidder", bid.Bidder, "amount", bid.Amount, "round", bid.Round) + log.Info("Consumed validated bid", "bidder", bid.Bidder, "amount", bid.Amount, "round", bid.Round) a.bidCache.add(JsonValidatedBidToGo(bid)) case <-ctx.Done(): log.Info("Context done while waiting redis streams to be ready, failed to start") @@ -328,10 +333,15 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { } func (a *AuctioneerServer) sendAuctionResolutionTransactionRPC(ctx context.Context, tx *types.Transaction) error { - return a.sequencerRpc.CallContext(ctx, nil, "timeboost_submitAuctionResolutionTransaction", tx) + // TODO: Retry a few times if fails. + return a.sequencerRpc.CallContext(ctx, nil, "auctioneer_submitAuctionResolutionTransaction", tx) } func copyTxOpts(opts *bind.TransactOpts) *bind.TransactOpts { + if opts == nil { + fmt.Println("nil opts") + return nil + } copied := &bind.TransactOpts{ From: opts.From, Context: opts.Context, diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 080f964c79..02344601fe 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -51,7 +51,6 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { stack, err := node.New(&stackConf) require.NoError(t, err) cfg := &BidValidatorConfig{ - ChainIds: []string{fmt.Sprintf("%d", testSetup.chainId.Uint64())}, SequencerEndpoint: testSetup.endpoint, AuctionContractAddress: testSetup.expressLaneAuctionAddr.Hex(), RedisURL: redisURL, diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 02c55de7c3..f6579c8c70 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -28,7 +28,6 @@ type BidValidatorConfigFetcher func() *BidValidatorConfig type BidValidatorConfig struct { Enable bool `koanf:"enable"` RedisURL string `koanf:"redis-url"` - ChainIds []string `koanf:"chain-ids"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Timeout on polling for existence of each redis stream. SequencerEndpoint string `koanf:"sequencer-endpoint"` @@ -51,7 +50,6 @@ func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBidValidatorConfig.Enable, "enable bid validator") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.String(prefix+".redis-url", DefaultBidValidatorConfig.RedisURL, "url of redis server") - f.StringSlice(prefix+".chain-ids", DefaultBidValidatorConfig.ChainIds, "chain ids to support") f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") } @@ -60,13 +58,13 @@ type BidValidator struct { stopwaiter.StopWaiter sync.RWMutex reservePriceLock sync.RWMutex - chainId []*big.Int // Auctioneer could handle auctions on multiple chains. + chainId *big.Int stack *node.Node producerCfg *pubsub.ProducerConfig producer *pubsub.Producer[*JsonValidatedBid, error] redisClient redis.UniversalClient domainValue []byte - client Client + client *ethclient.Client auctionContract *express_lane_auctiongen.ExpressLaneAuction auctionContractAddr common.Address bidsReceiver chan *Bid @@ -92,17 +90,6 @@ func NewBidValidator( if cfg.AuctionContractAddress == "" { return nil, fmt.Errorf("auction contract address cannot be empty") } - if len(cfg.ChainIds) == 0 { - return nil, fmt.Errorf("expected at least one chain id") - } - chainIds := make([]*big.Int, len(cfg.ChainIds)) - for i, cidStr := range cfg.ChainIds { - id, ok := new(big.Int).SetString(cidStr, 10) - if !ok { - return nil, fmt.Errorf("could not parse chain id into big int base 10 %s", cidStr) - } - chainIds[i] = id - } auctionContractAddr := common.HexToAddress(cfg.AuctionContractAddress) redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) if err != nil { @@ -114,6 +101,10 @@ func NewBidValidator( return nil, err } sequencerClient := ethclient.NewClient(client) + chainId, err := sequencerClient.ChainID(ctx) + if err != nil { + return nil, err + } auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, sequencerClient) if err != nil { return nil, err @@ -132,7 +123,7 @@ func NewBidValidator( return nil, err } bidValidator := &BidValidator{ - chainId: chainIds, + chainId: chainId, client: sequencerClient, redisClient: redisClient, stack: stack, @@ -222,12 +213,10 @@ func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return err } log.Info("Validated bid", "bidder", validatedBid.Bidder.Hex(), "amount", validatedBid.Amount.String(), "round", validatedBid.Round, "elapsed", time.Since(start)) - start = time.Now() _, err = bv.producer.Produce(ctx, validatedBid) if err != nil { return err } - log.Info("producer", "elapsed", time.Since(start)) return nil } @@ -258,14 +247,7 @@ func (bv *BidValidator) validateBid( } // Check if the chain ID is valid. - chainIdOk := false - for _, id := range bv.chainId { - if bid.ChainId.Cmp(id) == 0 { - chainIdOk = true - break - } - } - if !chainIdOk { + if bid.ChainId.Cmp(bv.chainId) != 0 { return nil, errors.Wrapf(ErrWrongChainId, "can not auction for chain id: %d", bid.ChainId) } diff --git a/timeboost/bid_validator_test.go b/timeboost/bid_validator_test.go index 04c770b80f..69ede89e00 100644 --- a/timeboost/bid_validator_test.go +++ b/timeboost/bid_validator_test.go @@ -101,7 +101,7 @@ func TestBidValidator_validateBid(t *testing.T) { for _, tt := range tests { bv := BidValidator{ - chainId: []*big.Int{big.NewInt(1)}, + chainId: big.NewInt(1), initialRoundTimestamp: time.Now().Add(-time.Second), reservePrice: big.NewInt(2), roundDuration: time.Minute, @@ -131,7 +131,7 @@ func TestBidValidator_validateBid_perRoundBidLimitReached(t *testing.T) { } auctionContractAddr := common.Address{'a'} bv := BidValidator{ - chainId: []*big.Int{big.NewInt(1)}, + chainId: big.NewInt(1), initialRoundTimestamp: time.Now().Add(-time.Second), reservePrice: big.NewInt(2), roundDuration: time.Minute, diff --git a/timeboost/db/db.go b/timeboost/db/db.go index b20d669933..0a3e3f18dc 100644 --- a/timeboost/db/db.go +++ b/timeboost/db/db.go @@ -12,15 +12,6 @@ type Database interface { DeleteBids(round uint64) } -type BidOption func(b *BidQuery) - -type BidQuery struct { - filters []string - args []interface{} - startRound int - endRound int -} - type Db struct { db *sqlx.DB } diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index ad80c26a50..98fc5a35db 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -159,7 +159,7 @@ func setupBidderClient( ctx, name, &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, - testSetup.backend.Client(), + nil, testSetup.expressLaneAuctionAddr, auctioneerEndpoint, ) From 84ab0d1bd93d1b9802e25bea474b63b30d07bb3b Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 09:48:23 -0500 Subject: [PATCH 049/109] move system test --- execution/gethexec/express_lane_service.go | 4 - execution/gethexec/sequencer.go | 13 + system_tests/seqfeed_test.go | 560 -------------------- system_tests/timeboost_test.go | 574 +++++++++++++++++++++ timeboost/bid_cache_test.go | 246 ++++----- 5 files changed, 711 insertions(+), 686 deletions(-) create mode 100644 system_tests/timeboost_test.go diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 3d62f5b6c5..3145995c97 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -96,10 +96,6 @@ func (es *expressLaneService) Start(ctxIn context.Context) { es.Lock() // Reset the sequence numbers map for the new round. es.messagesBySequenceNumber = make(map[uint64]*timeboost.ExpressLaneSubmission) - es.roundControl.Add(round, &expressLaneControl{ - controller: common.Address{}, - sequence: 0, - }) es.Unlock() } } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 0fcfe8e822..f80334f770 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -555,6 +555,10 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx if sender != auctioneerAddr { return fmt.Errorf("sender %#x is not the auctioneer address %#x", sender, auctioneerAddr) } + txBytes, err := tx.MarshalBinary() + if err != nil { + return err + } // TODO: Check it is within the resolution window. s.timeboostLock.Lock() // Set it as a value that will be consumed first in `createBlock` @@ -565,6 +569,15 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx s.timeboostAuctionResolutionTx = tx s.timeboostLock.Unlock() log.Info("Creating auction resolution tx") + s.txQueue <- txQueueItem{ + tx: tx, + txSize: len(txBytes), + options: nil, + resultChan: make(chan error, 1), + returnedResult: &atomic.Bool{}, + ctx: ctx, + firstAppearance: time.Now(), + } s.createBlock(ctx) return nil } diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 9d57b95669..e3a98b4961 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -8,34 +8,20 @@ import ( "fmt" "math/big" "net" - "sync" "testing" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/broadcastclient" "github.com/offchainlabs/nitro/broadcaster/backlog" "github.com/offchainlabs/nitro/broadcaster/message" - "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/execution/gethexec" - "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/relay" - "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" - "github.com/offchainlabs/nitro/solgen/go/mocksgen" - "github.com/offchainlabs/nitro/timeboost" - "github.com/offchainlabs/nitro/timeboost/bindings" - "github.com/offchainlabs/nitro/util/arbmath" - "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/testhelpers" "github.com/offchainlabs/nitro/wsbroadcastserver" @@ -103,552 +89,6 @@ func TestSequencerFeed(t *testing.T) { } } -func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) - defer cleanupSeq() - chainId, err := seqClient.ChainID(ctx) - Require(t, err) - - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, seqClient) - Require(t, err) - info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) - Require(t, err) - bobPriv := seqInfo.Accounts["Bob"].PrivateKey - - // Prepare a client that can submit txs to the sequencer via the express lane. - seqDial, err := rpc.Dial("http://localhost:9567") - Require(t, err) - expressLaneClient := timeboost.NewExpressLaneClient( - bobPriv, - chainId, - time.Unix(int64(info.OffsetTimestamp), 0), - time.Duration(info.RoundDurationSeconds)*time.Second, - auctionContractAddr, - seqDial, - ) - expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) - - // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's - // txs end up getting delayed by 200ms as she is not the express lane controller. - // In the end, Bob's txs should be ordered before Alice's during the round. - var wg sync.WaitGroup - wg.Add(2) - aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) - go func(w *sync.WaitGroup) { - defer w.Done() - err = seqClient.SendTransaction(ctx, aliceTx) - Require(t, err) - }(&wg) - - bobBoostableTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) - go func(w *sync.WaitGroup) { - defer w.Done() - time.Sleep(time.Millisecond * 10) - err = expressLaneClient.SendTransaction(ctx, bobBoostableTx) - Require(t, err) - }(&wg) - wg.Wait() - - // After round is done, verify that Bob beats Alice in the final sequence. - aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) - Require(t, err) - aliceBlock := aliceReceipt.BlockNumber.Uint64() - bobReceipt, err := seqClient.TransactionReceipt(ctx, bobBoostableTx.Hash()) - Require(t, err) - bobBlock := bobReceipt.BlockNumber.Uint64() - - if aliceBlock < bobBlock { - t.Fatal("Bob should have been sequenced before Alice with express lane") - } else if aliceBlock == bobBlock { - t.Log("Sequenced in same output block") - block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) - Require(t, err) - findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { - for index, tx := range transactions { - if tx.Hash() == txHash { - return index - } - } - return -1 - } - txes := block.Transactions() - indexA := findTransactionIndex(txes, aliceTx.Hash()) - indexB := findTransactionIndex(txes, bobBoostableTx.Hash()) - if indexA == -1 || indexB == -1 { - t.Fatal("Did not find txs in block") - } - if indexA < indexB { - t.Fatal("Bob should have been sequenced before Alice with express lane") - } - } -} - -func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) - defer cleanupSeq() - chainId, err := seqClient.ChainID(ctx) - Require(t, err) - - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, seqClient) - Require(t, err) - info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) - Require(t, err) - bobPriv := seqInfo.Accounts["Bob"].PrivateKey - - // Prepare a client that can submit txs to the sequencer via the express lane. - seqDial, err := rpc.Dial("http://localhost:9567") - Require(t, err) - expressLaneClient := timeboost.NewExpressLaneClient( - bobPriv, - chainId, - time.Unix(int64(info.OffsetTimestamp), 0), - time.Duration(info.RoundDurationSeconds)*time.Second, - auctionContractAddr, - seqDial, - ) - expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) - - // We first generate an account for Charlie and transfer some balance to him. - seqInfo.GenerateAccount("Charlie") - TransferBalance(t, "Owner", "Charlie", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) - - // During the express lane, Bob sends txs that do not belong to him, but he is the express lane controller so they - // will go through the express lane. - // These tx payloads are sent with nonces out of order, and those with nonces too high should fail. - var wg sync.WaitGroup - wg.Add(2) - aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) - go func(w *sync.WaitGroup) { - defer w.Done() - err = seqClient.SendTransaction(ctx, aliceTx) - Require(t, err) - }(&wg) - - ownerAddr := seqInfo.GetAddress("Owner") - txData := &types.DynamicFeeTx{ - To: &ownerAddr, - Gas: seqInfo.TransferGas, - Value: big.NewInt(1e12), - Nonce: 1, - GasFeeCap: aliceTx.GasFeeCap(), - Data: nil, - } - charlie1 := seqInfo.SignTxAs("Charlie", txData) - txData = &types.DynamicFeeTx{ - To: &ownerAddr, - Gas: seqInfo.TransferGas, - Value: big.NewInt(1e12), - Nonce: 0, - GasFeeCap: aliceTx.GasFeeCap(), - Data: nil, - } - charlie0 := seqInfo.SignTxAs("Charlie", txData) - var err2 error - go func(w *sync.WaitGroup) { - defer w.Done() - time.Sleep(time.Millisecond * 10) - // Send the express lane txs with nonces out of order - err2 = expressLaneClient.SendTransaction(ctx, charlie1) - err = expressLaneClient.SendTransaction(ctx, charlie0) - Require(t, err) - }(&wg) - wg.Wait() - if err2 == nil { - t.Fatal("Charlie should not be able to send tx with nonce 2") - } - // After round is done, verify that Charlie beats Alice in the final sequence, and that the emitted txs - // for Charlie are correct. - aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) - Require(t, err) - aliceBlock := aliceReceipt.BlockNumber.Uint64() - charlieReceipt, err := seqClient.TransactionReceipt(ctx, charlie0.Hash()) - Require(t, err) - charlieBlock := charlieReceipt.BlockNumber.Uint64() - - if aliceBlock < charlieBlock { - t.Fatal("Charlie should have been sequenced before Alice with express lane") - } else if aliceBlock == charlieBlock { - t.Log("Sequenced in same output block") - block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) - Require(t, err) - findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { - for index, tx := range transactions { - if tx.Hash() == txHash { - return index - } - } - return -1 - } - txes := block.Transactions() - indexA := findTransactionIndex(txes, aliceTx.Hash()) - indexB := findTransactionIndex(txes, charlie0.Hash()) - if indexA == -1 || indexB == -1 { - t.Fatal("Did not find txs in block") - } - if indexA < indexB { - t.Fatal("Charlie should have been sequenced before Alice with express lane") - } - } -} - -func setupExpressLaneAuction( - t *testing.T, - ctx context.Context, -) (*arbnode.Node, *ethclient.Client, *BlockchainTestInfo, common.Address, func()) { - - builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, true) - - builderSeq.l2StackConfig.HTTPHost = "localhost" - builderSeq.l2StackConfig.HTTPPort = 9567 - builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost", "auctioneer"} - builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() - builderSeq.execConfig.Sequencer.Enable = true - builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ - Enable: true, - ExpressLaneAdvantage: time.Second * 5, - } - cleanupSeq := builderSeq.Build(t) - seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client - - // Set up the auction contracts on L2. - // Deploy the express lane auction contract and erc20 to the parent chain. - ownerOpts := seqInfo.GetDefaultTransactOpts("Owner", ctx) - erc20Addr, tx, erc20, err := bindings.DeployMockERC20(&ownerOpts, seqClient) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - tx, err = erc20.Initialize(&ownerOpts, "LANE", "LNE", 18) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - - // Fund the auction contract. - seqInfo.GenerateAccount("AuctionContract") - TransferBalance(t, "Owner", "AuctionContract", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) - - // Mint some tokens to Alice and Bob. - seqInfo.GenerateAccount("Alice") - seqInfo.GenerateAccount("Bob") - TransferBalance(t, "Faucet", "Alice", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) - TransferBalance(t, "Faucet", "Bob", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) - aliceOpts := seqInfo.GetDefaultTransactOpts("Alice", ctx) - bobOpts := seqInfo.GetDefaultTransactOpts("Bob", ctx) - tx, err = erc20.Mint(&ownerOpts, aliceOpts.From, big.NewInt(100)) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - tx, err = erc20.Mint(&ownerOpts, bobOpts.From, big.NewInt(100)) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - - // Calculate the number of seconds until the next minute - // and the next timestamp that is a multiple of a minute. - now := time.Now() - roundDuration := time.Minute - // Correctly calculate the remaining time until the next minute - waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond())*time.Nanosecond - // Get the current Unix timestamp at the start of the minute - initialTimestamp := big.NewInt(now.Add(waitTime).Unix()) - initialTimestampUnix := time.Unix(initialTimestamp.Int64(), 0) - - // Deploy the auction manager contract. - auctionContractAddr, tx, _, err := express_lane_auctiongen.DeployExpressLaneAuction(&ownerOpts, seqClient) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - - proxyAddr, tx, _, err := mocksgen.DeploySimpleProxy(&ownerOpts, seqClient, auctionContractAddr) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(proxyAddr, seqClient) - Require(t, err) - - auctioneerAddr := seqInfo.GetDefaultTransactOpts("AuctionContract", ctx).From - beneficiary := auctioneerAddr - biddingToken := erc20Addr - bidRoundSeconds := uint64(60) - auctionClosingSeconds := uint64(15) - reserveSubmissionSeconds := uint64(15) - minReservePrice := big.NewInt(1) // 1 wei. - roleAdmin := auctioneerAddr - minReservePriceSetter := auctioneerAddr - reservePriceSetter := auctioneerAddr - beneficiarySetter := auctioneerAddr - tx, err = auctionContract.Initialize( - &ownerOpts, - auctioneerAddr, - beneficiary, - biddingToken, - express_lane_auctiongen.RoundTimingInfo{ - OffsetTimestamp: initialTimestamp.Uint64(), - RoundDurationSeconds: bidRoundSeconds, - AuctionClosingSeconds: auctionClosingSeconds, - ReserveSubmissionSeconds: reserveSubmissionSeconds, - }, - minReservePrice, - roleAdmin, - minReservePriceSetter, - reservePriceSetter, - beneficiarySetter, - ) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - t.Log("Deployed all the auction manager stuff", auctionContractAddr) - // We approve the spending of the erc20 for the autonomous auction contract and bid receiver - // for both Alice and Bob. - bidReceiverAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") - maxUint256 := big.NewInt(1) - maxUint256.Lsh(maxUint256, 256).Sub(maxUint256, big.NewInt(1)) - - tx, err = erc20.Approve( - &aliceOpts, proxyAddr, maxUint256, - ) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - tx, err = erc20.Approve( - &aliceOpts, bidReceiverAddr, maxUint256, - ) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - tx, err = erc20.Approve( - &bobOpts, proxyAddr, maxUint256, - ) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - tx, err = erc20.Approve( - &bobOpts, bidReceiverAddr, maxUint256, - ) - Require(t, err) - if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { - t.Fatal(err) - } - - builderSeq.L2.ExecNode.Sequencer.StartExpressLane(ctx, proxyAddr, seqInfo.GetAddress("AuctionContract")) - t.Log("Started express lane service in sequencer") - - // Set up an autonomous auction contract service that runs in the background in this test. - redisURL := redisutil.CreateTestRedis(ctx, t) - - // Set up the auctioneer RPC service. - stackConf := node.Config{ - DataDir: "", // ephemeral. - HTTPPort: 9372, - HTTPHost: "localhost", - HTTPModules: []string{timeboost.AuctioneerNamespace}, - HTTPVirtualHosts: []string{"localhost"}, - HTTPTimeouts: rpc.DefaultHTTPTimeouts, - WSHost: "localhost", - WSPort: 9373, - WSModules: []string{timeboost.AuctioneerNamespace}, - GraphQLVirtualHosts: []string{"localhost"}, - P2P: p2p.Config{ - ListenAddr: "", - NoDial: true, - NoDiscovery: true, - }, - } - stack, err := node.New(&stackConf) - Require(t, err) - cfg := &timeboost.BidValidatorConfig{ - SequencerEndpoint: "http://localhost:9567", - AuctionContractAddress: proxyAddr.Hex(), - RedisURL: redisURL, - ProducerConfig: pubsub.TestProducerConfig, - } - fetcher := func() *timeboost.BidValidatorConfig { - return cfg - } - bidValidator, err := timeboost.NewBidValidator( - ctx, stack, fetcher, - ) - Require(t, err) - Require(t, stack.Start()) - Require(t, bidValidator.Initialize(ctx)) - bidValidator.Start(ctx) - - auctioneerCfg := &timeboost.AuctioneerServerConfig{ - SequencerEndpoint: "http://localhost:9567", - AuctionContractAddress: proxyAddr.Hex(), - RedisURL: redisURL, - ConsumerConfig: pubsub.TestConsumerConfig, - Wallet: genericconf.WalletConfig{ - PrivateKey: fmt.Sprintf("00%x", seqInfo.Accounts["AuctionContract"].PrivateKey.D.Bytes()), - }, - } - auctioneerFetcher := func() *timeboost.AuctioneerServerConfig { - return auctioneerCfg - } - am, err := timeboost.NewAuctioneerServer( - ctx, - auctioneerFetcher, - ) - Require(t, err) - am.Start(ctx) - - // Set up a bidder client for Alice and Bob. - alicePriv := seqInfo.Accounts["Alice"].PrivateKey - alice, err := timeboost.NewBidderClient( - ctx, - "alice", - &timeboost.Wallet{ - TxOpts: &aliceOpts, - PrivKey: alicePriv, - }, - seqClient, - proxyAddr, - "http://localhost:9372", - ) - Require(t, err) - - bobPriv := seqInfo.Accounts["Bob"].PrivateKey - bob, err := timeboost.NewBidderClient( - ctx, - "bob", - &timeboost.Wallet{ - TxOpts: &bobOpts, - PrivKey: bobPriv, - }, - seqClient, - proxyAddr, - "http://localhost:9372", - ) - Require(t, err) - - // Wait until the initial round. - info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) - Require(t, err) - timeToWait := time.Until(initialTimestampUnix) - t.Logf("Waiting until the initial round %v and %v, current time %v", timeToWait, initialTimestampUnix, time.Now()) - <-time.After(timeToWait) - - t.Log("Started auction master stack and bid clients") - Require(t, alice.Deposit(ctx, big.NewInt(5))) - Require(t, bob.Deposit(ctx, big.NewInt(5))) - - // Wait until the next timeboost round + a few milliseconds. - now = time.Now() - waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) - t.Logf("Alice and Bob are now deposited into the autonomous auction contract, waiting %v for bidding round..., timestamp %v", waitTime, time.Now()) - time.Sleep(waitTime) - t.Logf("Reached the bidding round at %v", time.Now()) - time.Sleep(time.Second * 5) - - // We are now in the bidding round, both issue their bids. Bob will win. - t.Logf("Alice and Bob now submitting their bids at %v", time.Now()) - aliceBid, err := alice.Bid(ctx, big.NewInt(1), aliceOpts.From) - Require(t, err) - bobBid, err := bob.Bid(ctx, big.NewInt(2), bobOpts.From) - Require(t, err) - t.Logf("Alice bid %+v", aliceBid) - t.Logf("Bob bid %+v", bobBid) - - // Subscribe to auction resolutions and wait for Bob to win the auction. - winner, winnerRound := awaitAuctionResolved(t, ctx, seqClient, auctionContract) - - // Verify Bob owns the express lane this round. - if winner != bobOpts.From { - t.Fatal("Bob should have won the express lane auction") - } - t.Log("Bob won the express lane auction for upcoming round, now waiting for that round to start...") - - // Wait until the round that Bob owns the express lane for. - now = time.Now() - waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) - time.Sleep(waitTime) - - currRound := timeboost.CurrentRound(time.Unix(int64(info.OffsetTimestamp), 0), roundDuration) - t.Log("curr round", currRound) - if currRound != winnerRound { - now = time.Now() - waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) - t.Log("Not express lane round yet, waiting for next round", waitTime) - time.Sleep(waitTime) - } - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: 0, - End: nil, - } - it, err := auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) - Require(t, err) - bobWon := false - for it.Next() { - if it.Event.FirstPriceBidder == bobOpts.From { - bobWon = true - } - } - if !bobWon { - t.Fatal("Bob should have won the auction") - } - return seqNode, seqClient, seqInfo, proxyAddr, cleanupSeq -} - -func awaitAuctionResolved( - t *testing.T, - ctx context.Context, - client *ethclient.Client, - contract *express_lane_auctiongen.ExpressLaneAuction, -) (common.Address, uint64) { - fromBlock, err := client.BlockNumber(ctx) - Require(t, err) - ticker := time.NewTicker(time.Millisecond * 100) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return common.Address{}, 0 - case <-ticker.C: - latestBlock, err := client.HeaderByNumber(ctx, nil) - if err != nil { - t.Log("Could not get latest header", err) - continue - } - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: fromBlock, - End: &toBlock, - } - it, err := contract.FilterAuctionResolved(filterOpts, nil, nil, nil) - if err != nil { - t.Log("Could not filter auction resolutions", err) - continue - } - for it.Next() { - return it.Event.FirstPriceBidder, it.Event.Round - } - fromBlock = toBlock - } - } -} - func TestRelayedSequencerFeed(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go new file mode 100644 index 0000000000..c739e27b48 --- /dev/null +++ b/system_tests/timeboost_test.go @@ -0,0 +1,574 @@ +package arbtest + +import ( + "context" + "fmt" + "math/big" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/timeboost" + "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/redisutil" +) + +func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) + defer cleanupSeq() + chainId, err := seqClient.ChainID(ctx) + Require(t, err) + + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, seqClient) + Require(t, err) + info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + Require(t, err) + bobPriv := seqInfo.Accounts["Bob"].PrivateKey + + // Prepare a client that can submit txs to the sequencer via the express lane. + seqDial, err := rpc.Dial("http://localhost:9567") + Require(t, err) + expressLaneClient := timeboost.NewExpressLaneClient( + bobPriv, + chainId, + time.Unix(int64(info.OffsetTimestamp), 0), + time.Duration(info.RoundDurationSeconds)*time.Second, + auctionContractAddr, + seqDial, + ) + expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) + + // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's + // txs end up getting delayed by 200ms as she is not the express lane controller. + // In the end, Bob's txs should be ordered before Alice's during the round. + var wg sync.WaitGroup + wg.Add(2) + aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + err = seqClient.SendTransaction(ctx, aliceTx) + Require(t, err) + }(&wg) + + bobBoostableTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + time.Sleep(time.Millisecond * 10) + err = expressLaneClient.SendTransaction(ctx, bobBoostableTx) + Require(t, err) + }(&wg) + wg.Wait() + + // After round is done, verify that Bob beats Alice in the final sequence. + aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) + Require(t, err) + aliceBlock := aliceReceipt.BlockNumber.Uint64() + bobReceipt, err := seqClient.TransactionReceipt(ctx, bobBoostableTx.Hash()) + Require(t, err) + bobBlock := bobReceipt.BlockNumber.Uint64() + + if aliceBlock < bobBlock { + t.Fatal("Bob should have been sequenced before Alice with express lane") + } else if aliceBlock == bobBlock { + t.Log("Sequenced in same output block") + block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) + Require(t, err) + findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { + for index, tx := range transactions { + if tx.Hash() == txHash { + return index + } + } + return -1 + } + txes := block.Transactions() + indexA := findTransactionIndex(txes, aliceTx.Hash()) + indexB := findTransactionIndex(txes, bobBoostableTx.Hash()) + if indexA == -1 || indexB == -1 { + t.Fatal("Did not find txs in block") + } + if indexA < indexB { + t.Fatal("Bob should have been sequenced before Alice with express lane") + } + } +} + +func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) + defer cleanupSeq() + chainId, err := seqClient.ChainID(ctx) + Require(t, err) + + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, seqClient) + Require(t, err) + info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + Require(t, err) + bobPriv := seqInfo.Accounts["Bob"].PrivateKey + + // Prepare a client that can submit txs to the sequencer via the express lane. + seqDial, err := rpc.Dial("http://localhost:9567") + Require(t, err) + expressLaneClient := timeboost.NewExpressLaneClient( + bobPriv, + chainId, + time.Unix(int64(info.OffsetTimestamp), 0), + time.Duration(info.RoundDurationSeconds)*time.Second, + auctionContractAddr, + seqDial, + ) + expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) + + // We first generate an account for Charlie and transfer some balance to him. + seqInfo.GenerateAccount("Charlie") + TransferBalance(t, "Owner", "Charlie", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + + // During the express lane, Bob sends txs that do not belong to him, but he is the express lane controller so they + // will go through the express lane. + // These tx payloads are sent with nonces out of order, and those with nonces too high should fail. + var wg sync.WaitGroup + wg.Add(2) + aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + go func(w *sync.WaitGroup) { + defer w.Done() + err = seqClient.SendTransaction(ctx, aliceTx) + Require(t, err) + }(&wg) + + ownerAddr := seqInfo.GetAddress("Owner") + txData := &types.DynamicFeeTx{ + To: &ownerAddr, + Gas: seqInfo.TransferGas, + Value: big.NewInt(1e12), + Nonce: 1, + GasFeeCap: aliceTx.GasFeeCap(), + Data: nil, + } + charlie1 := seqInfo.SignTxAs("Charlie", txData) + txData = &types.DynamicFeeTx{ + To: &ownerAddr, + Gas: seqInfo.TransferGas, + Value: big.NewInt(1e12), + Nonce: 0, + GasFeeCap: aliceTx.GasFeeCap(), + Data: nil, + } + charlie0 := seqInfo.SignTxAs("Charlie", txData) + var err2 error + go func(w *sync.WaitGroup) { + defer w.Done() + time.Sleep(time.Millisecond * 10) + // Send the express lane txs with nonces out of order + err2 = expressLaneClient.SendTransaction(ctx, charlie1) + err = expressLaneClient.SendTransaction(ctx, charlie0) + Require(t, err) + }(&wg) + wg.Wait() + if err2 == nil { + t.Fatal("Charlie should not be able to send tx with nonce 2") + } + // After round is done, verify that Charlie beats Alice in the final sequence, and that the emitted txs + // for Charlie are correct. + aliceReceipt, err := seqClient.TransactionReceipt(ctx, aliceTx.Hash()) + Require(t, err) + aliceBlock := aliceReceipt.BlockNumber.Uint64() + charlieReceipt, err := seqClient.TransactionReceipt(ctx, charlie0.Hash()) + Require(t, err) + charlieBlock := charlieReceipt.BlockNumber.Uint64() + + if aliceBlock < charlieBlock { + t.Fatal("Charlie should have been sequenced before Alice with express lane") + } else if aliceBlock == charlieBlock { + t.Log("Sequenced in same output block") + block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) + Require(t, err) + findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { + for index, tx := range transactions { + if tx.Hash() == txHash { + return index + } + } + return -1 + } + txes := block.Transactions() + indexA := findTransactionIndex(txes, aliceTx.Hash()) + indexB := findTransactionIndex(txes, charlie0.Hash()) + if indexA == -1 || indexB == -1 { + t.Fatal("Did not find txs in block") + } + if indexA < indexB { + t.Fatal("Charlie should have been sequenced before Alice with express lane") + } + } +} + +func setupExpressLaneAuction( + t *testing.T, + ctx context.Context, +) (*arbnode.Node, *ethclient.Client, *BlockchainTestInfo, common.Address, func()) { + + builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, true) + + builderSeq.l2StackConfig.HTTPHost = "localhost" + builderSeq.l2StackConfig.HTTPPort = 9567 + builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost", "auctioneer"} + builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() + builderSeq.execConfig.Sequencer.Enable = true + builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ + Enable: true, + ExpressLaneAdvantage: time.Second * 5, + } + cleanupSeq := builderSeq.Build(t) + seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client + + // Set up the auction contracts on L2. + // Deploy the express lane auction contract and erc20 to the parent chain. + ownerOpts := seqInfo.GetDefaultTransactOpts("Owner", ctx) + erc20Addr, tx, erc20, err := bindings.DeployMockERC20(&ownerOpts, seqClient) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Initialize(&ownerOpts, "LANE", "LNE", 18) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + + // Fund the auction contract. + seqInfo.GenerateAccount("AuctionContract") + TransferBalance(t, "Owner", "AuctionContract", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + + // Mint some tokens to Alice and Bob. + seqInfo.GenerateAccount("Alice") + seqInfo.GenerateAccount("Bob") + TransferBalance(t, "Faucet", "Alice", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + TransferBalance(t, "Faucet", "Bob", arbmath.BigMulByUint(oneEth, 500), seqInfo, seqClient, ctx) + aliceOpts := seqInfo.GetDefaultTransactOpts("Alice", ctx) + bobOpts := seqInfo.GetDefaultTransactOpts("Bob", ctx) + tx, err = erc20.Mint(&ownerOpts, aliceOpts.From, big.NewInt(100)) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Mint(&ownerOpts, bobOpts.From, big.NewInt(100)) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + + // Calculate the number of seconds until the next minute + // and the next timestamp that is a multiple of a minute. + now := time.Now() + roundDuration := time.Minute + // Correctly calculate the remaining time until the next minute + waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond())*time.Nanosecond + // Get the current Unix timestamp at the start of the minute + initialTimestamp := big.NewInt(now.Add(waitTime).Unix()) + initialTimestampUnix := time.Unix(initialTimestamp.Int64(), 0) + + // Deploy the auction manager contract. + auctionContractAddr, tx, _, err := express_lane_auctiongen.DeployExpressLaneAuction(&ownerOpts, seqClient) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + + proxyAddr, tx, _, err := mocksgen.DeploySimpleProxy(&ownerOpts, seqClient, auctionContractAddr) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(proxyAddr, seqClient) + Require(t, err) + + auctioneerAddr := seqInfo.GetDefaultTransactOpts("AuctionContract", ctx).From + beneficiary := auctioneerAddr + biddingToken := erc20Addr + bidRoundSeconds := uint64(60) + auctionClosingSeconds := uint64(15) + reserveSubmissionSeconds := uint64(15) + minReservePrice := big.NewInt(1) // 1 wei. + roleAdmin := auctioneerAddr + minReservePriceSetter := auctioneerAddr + reservePriceSetter := auctioneerAddr + beneficiarySetter := auctioneerAddr + tx, err = auctionContract.Initialize( + &ownerOpts, + auctioneerAddr, + beneficiary, + biddingToken, + express_lane_auctiongen.RoundTimingInfo{ + OffsetTimestamp: initialTimestamp.Uint64(), + RoundDurationSeconds: bidRoundSeconds, + AuctionClosingSeconds: auctionClosingSeconds, + ReserveSubmissionSeconds: reserveSubmissionSeconds, + }, + minReservePrice, + roleAdmin, + minReservePriceSetter, + reservePriceSetter, + beneficiarySetter, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + t.Log("Deployed all the auction manager stuff", auctionContractAddr) + // We approve the spending of the erc20 for the autonomous auction contract and bid receiver + // for both Alice and Bob. + bidReceiverAddr := common.HexToAddress("0x2424242424242424242424242424242424242424") + maxUint256 := big.NewInt(1) + maxUint256.Lsh(maxUint256, 256).Sub(maxUint256, big.NewInt(1)) + + tx, err = erc20.Approve( + &aliceOpts, proxyAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Approve( + &aliceOpts, bidReceiverAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Approve( + &bobOpts, proxyAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + tx, err = erc20.Approve( + &bobOpts, bidReceiverAddr, maxUint256, + ) + Require(t, err) + if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { + t.Fatal(err) + } + + builderSeq.L2.ExecNode.Sequencer.StartExpressLane(ctx, proxyAddr, seqInfo.GetAddress("AuctionContract")) + t.Log("Started express lane service in sequencer") + + // Set up an autonomous auction contract service that runs in the background in this test. + redisURL := redisutil.CreateTestRedis(ctx, t) + + // Set up the auctioneer RPC service. + stackConf := node.Config{ + DataDir: "", // ephemeral. + HTTPPort: 9372, + HTTPHost: "localhost", + HTTPModules: []string{timeboost.AuctioneerNamespace}, + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSHost: "localhost", + WSPort: 9373, + WSModules: []string{timeboost.AuctioneerNamespace}, + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDial: true, + NoDiscovery: true, + }, + } + stack, err := node.New(&stackConf) + Require(t, err) + cfg := &timeboost.BidValidatorConfig{ + SequencerEndpoint: "http://localhost:9567", + AuctionContractAddress: proxyAddr.Hex(), + RedisURL: redisURL, + ProducerConfig: pubsub.TestProducerConfig, + } + fetcher := func() *timeboost.BidValidatorConfig { + return cfg + } + bidValidator, err := timeboost.NewBidValidator( + ctx, stack, fetcher, + ) + Require(t, err) + Require(t, stack.Start()) + Require(t, bidValidator.Initialize(ctx)) + bidValidator.Start(ctx) + + auctioneerCfg := &timeboost.AuctioneerServerConfig{ + SequencerEndpoint: "http://localhost:9567", + AuctionContractAddress: proxyAddr.Hex(), + RedisURL: redisURL, + ConsumerConfig: pubsub.TestConsumerConfig, + Wallet: genericconf.WalletConfig{ + PrivateKey: fmt.Sprintf("00%x", seqInfo.Accounts["AuctionContract"].PrivateKey.D.Bytes()), + }, + } + auctioneerFetcher := func() *timeboost.AuctioneerServerConfig { + return auctioneerCfg + } + am, err := timeboost.NewAuctioneerServer( + ctx, + auctioneerFetcher, + ) + Require(t, err) + am.Start(ctx) + + // Set up a bidder client for Alice and Bob. + alicePriv := seqInfo.Accounts["Alice"].PrivateKey + alice, err := timeboost.NewBidderClient( + ctx, + "alice", + &timeboost.Wallet{ + TxOpts: &aliceOpts, + PrivKey: alicePriv, + }, + seqClient, + proxyAddr, + "http://localhost:9372", + ) + Require(t, err) + + bobPriv := seqInfo.Accounts["Bob"].PrivateKey + bob, err := timeboost.NewBidderClient( + ctx, + "bob", + &timeboost.Wallet{ + TxOpts: &bobOpts, + PrivKey: bobPriv, + }, + seqClient, + proxyAddr, + "http://localhost:9372", + ) + Require(t, err) + + // Wait until the initial round. + info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + Require(t, err) + timeToWait := time.Until(initialTimestampUnix) + t.Logf("Waiting until the initial round %v and %v, current time %v", timeToWait, initialTimestampUnix, time.Now()) + <-time.After(timeToWait) + + t.Log("Started auction master stack and bid clients") + Require(t, alice.Deposit(ctx, big.NewInt(5))) + Require(t, bob.Deposit(ctx, big.NewInt(5))) + + // Wait until the next timeboost round + a few milliseconds. + now = time.Now() + waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + t.Logf("Alice and Bob are now deposited into the autonomous auction contract, waiting %v for bidding round..., timestamp %v", waitTime, time.Now()) + time.Sleep(waitTime) + t.Logf("Reached the bidding round at %v", time.Now()) + time.Sleep(time.Second * 5) + + // We are now in the bidding round, both issue their bids. Bob will win. + t.Logf("Alice and Bob now submitting their bids at %v", time.Now()) + aliceBid, err := alice.Bid(ctx, big.NewInt(1), aliceOpts.From) + Require(t, err) + bobBid, err := bob.Bid(ctx, big.NewInt(2), bobOpts.From) + Require(t, err) + t.Logf("Alice bid %+v", aliceBid) + t.Logf("Bob bid %+v", bobBid) + + // Subscribe to auction resolutions and wait for Bob to win the auction. + winner, winnerRound := awaitAuctionResolved(t, ctx, seqClient, auctionContract) + + // Verify Bob owns the express lane this round. + if winner != bobOpts.From { + t.Fatal("Bob should have won the express lane auction") + } + t.Log("Bob won the express lane auction for upcoming round, now waiting for that round to start...") + + // Wait until the round that Bob owns the express lane for. + now = time.Now() + waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + time.Sleep(waitTime) + + currRound := timeboost.CurrentRound(time.Unix(int64(info.OffsetTimestamp), 0), roundDuration) + t.Log("curr round", currRound) + if currRound != winnerRound { + now = time.Now() + waitTime = roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + t.Log("Not express lane round yet, waiting for next round", waitTime) + time.Sleep(waitTime) + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: 0, + End: nil, + } + it, err := auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) + Require(t, err) + bobWon := false + for it.Next() { + if it.Event.FirstPriceBidder == bobOpts.From { + bobWon = true + } + } + if !bobWon { + t.Fatal("Bob should have won the auction") + } + return seqNode, seqClient, seqInfo, proxyAddr, cleanupSeq +} + +func awaitAuctionResolved( + t *testing.T, + ctx context.Context, + client *ethclient.Client, + contract *express_lane_auctiongen.ExpressLaneAuction, +) (common.Address, uint64) { + fromBlock, err := client.BlockNumber(ctx) + Require(t, err) + ticker := time.NewTicker(time.Millisecond * 100) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return common.Address{}, 0 + case <-ticker.C: + latestBlock, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Log("Could not get latest header", err) + continue + } + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + continue + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := contract.FilterAuctionResolved(filterOpts, nil, nil, nil) + if err != nil { + t.Log("Could not filter auction resolutions", err) + continue + } + for it.Next() { + return it.Event.FirstPriceBidder, it.Event.Round + } + fromBlock = toBlock + } + } +} diff --git a/timeboost/bid_cache_test.go b/timeboost/bid_cache_test.go index 2db8d10d2f..9cb75d8c9e 100644 --- a/timeboost/bid_cache_test.go +++ b/timeboost/bid_cache_test.go @@ -1,9 +1,11 @@ package timeboost import ( + "math/big" "net" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -82,129 +84,129 @@ import ( // require.True(t, topTwoBids.firstPlace.expressLaneController == newBid.ExpressLaneController) // } -// func TestTopTwoBids(t *testing.T) { -// tests := []struct { -// name string -// bids map[common.Address]*validatedBid -// expected *auctionResult -// }{ -// { -// name: "single bid", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: nil, -// }, -// }, -// { -// name: "two bids with different amounts", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// }, -// }, -// { -// name: "two bids same amount but different hashes", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// }, -// }, -// { -// name: "many bids but all same amount", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, -// }, -// }, -// { -// name: "many bids with some tied and others with different amounts", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x3")}, -// common.HexToAddress("0x4"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x4")}, -// }, -// }, -// { -// name: "many bids and tied for second place", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(200), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(300), chainId: big.NewInt(1), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(200), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x3")}, -// }, -// }, -// { -// name: "all bids with the same amount", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// common.HexToAddress("0x3"): {amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(3), bidder: common.HexToAddress("0x3"), expressLaneController: common.HexToAddress("0x3")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(2), bidder: common.HexToAddress("0x2"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// }, -// { -// name: "no bids", -// bids: nil, -// expected: &auctionResult{firstPlace: nil, secondPlace: nil}, -// }, -// { -// name: "identical bids", -// bids: map[common.Address]*validatedBid{ -// common.HexToAddress("0x1"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// common.HexToAddress("0x2"): {amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// expected: &auctionResult{ -// firstPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x1")}, -// secondPlace: &validatedBid{amount: big.NewInt(100), chainId: big.NewInt(1), bidder: common.HexToAddress("0x1"), expressLaneController: common.HexToAddress("0x2")}, -// }, -// }, -// } +func TestTopTwoBids(t *testing.T) { + tests := []struct { + name string + bids map[common.Address]*ValidatedBid + expected *auctionResult + }{ + { + name: "single bid", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + secondPlace: nil, + }, + }, + { + name: "two bids with different amounts", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {Amount: big.NewInt(200), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(200), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x2")}, + secondPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + }, + }, + { + name: "two bids same amount but different hashes", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {Amount: big.NewInt(100), ChainId: big.NewInt(2), Bidder: common.HexToAddress("0x2"), ExpressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(2), Bidder: common.HexToAddress("0x2"), ExpressLaneController: common.HexToAddress("0x2")}, + secondPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x1")}, + }, + }, + { + name: "many bids but all same amount", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(300), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {Amount: big.NewInt(200), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(300), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + secondPlace: &ValidatedBid{Amount: big.NewInt(200), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x3")}, + }, + }, + { + name: "many bids with some tied and others with different amounts", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(300), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {Amount: big.NewInt(200), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x3")}, + common.HexToAddress("0x4"): {Amount: big.NewInt(200), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x4")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(300), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + secondPlace: &ValidatedBid{Amount: big.NewInt(200), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x4")}, + }, + }, + { + name: "many bids and tied for second place", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(300), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {Amount: big.NewInt(200), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {Amount: big.NewInt(200), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(300), ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x1")}, + secondPlace: &ValidatedBid{Amount: big.NewInt(200), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x3")}, + }, + }, + { + name: "all bids with the same amount", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {Amount: big.NewInt(100), ChainId: big.NewInt(2), Bidder: common.HexToAddress("0x2"), ExpressLaneController: common.HexToAddress("0x2")}, + common.HexToAddress("0x3"): {Amount: big.NewInt(100), ChainId: big.NewInt(3), Bidder: common.HexToAddress("0x3"), ExpressLaneController: common.HexToAddress("0x3")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(3), Bidder: common.HexToAddress("0x3"), ExpressLaneController: common.HexToAddress("0x3")}, + secondPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(2), Bidder: common.HexToAddress("0x2"), ExpressLaneController: common.HexToAddress("0x2")}, + }, + }, + { + name: "no bids", + bids: nil, + expected: &auctionResult{firstPlace: nil, secondPlace: nil}, + }, + { + name: "identical bids", + bids: map[common.Address]*ValidatedBid{ + common.HexToAddress("0x1"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x1")}, + common.HexToAddress("0x2"): {Amount: big.NewInt(100), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x2")}, + }, + expected: &auctionResult{ + firstPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x1")}, + secondPlace: &ValidatedBid{Amount: big.NewInt(100), ChainId: big.NewInt(1), Bidder: common.HexToAddress("0x1"), ExpressLaneController: common.HexToAddress("0x2")}, + }, + }, + } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// bc := &bidCache{ -// bidsByExpressLaneControllerAddr: tt.bids, -// } -// result := bc.topTwoBids() -// if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { -// t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) -// } -// if result.firstPlace != nil && result.firstPlace.amount.Cmp(tt.expected.firstPlace.amount) != 0 { -// t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.amount, result.firstPlace.amount) -// } -// if result.secondPlace != nil && result.secondPlace.amount.Cmp(tt.expected.secondPlace.amount) != 0 { -// t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.amount, result.secondPlace.amount) -// } -// }) -// } -// } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bc := &bidCache{ + bidsByExpressLaneControllerAddr: tt.bids, + } + result := bc.topTwoBids() + if (result.firstPlace == nil) != (tt.expected.firstPlace == nil) || (result.secondPlace == nil) != (tt.expected.secondPlace == nil) { + t.Fatalf("expected firstPlace: %v, secondPlace: %v, got firstPlace: %v, secondPlace: %v", tt.expected.firstPlace, tt.expected.secondPlace, result.firstPlace, result.secondPlace) + } + if result.firstPlace != nil && result.firstPlace.Amount.Cmp(tt.expected.firstPlace.Amount) != 0 { + t.Errorf("expected firstPlace amount: %v, got: %v", tt.expected.firstPlace.Amount, result.firstPlace.Amount) + } + if result.secondPlace != nil && result.secondPlace.Amount.Cmp(tt.expected.secondPlace.Amount) != 0 { + t.Errorf("expected secondPlace amount: %v, got: %v", tt.expected.secondPlace.Amount, result.secondPlace.Amount) + } + }) + } +} // func BenchmarkBidValidation(b *testing.B) { // b.StopTimer() From 8fccf2cccb3065eb74f4c759627224720d9d114e Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 10:03:36 -0500 Subject: [PATCH 050/109] use stopwaiter --- system_tests/timeboost_test.go | 7 +++-- timeboost/async.go | 23 -------------- timeboost/bid_validator.go | 52 +++++++++++++++++--------------- timeboost/bidder_client.go | 18 ++++++++--- timeboost/express_lane_client.go | 16 ++++++++-- timeboost/setup_test.go | 1 + 6 files changed, 62 insertions(+), 55 deletions(-) delete mode 100644 timeboost/async.go diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index c739e27b48..cd9a75eccb 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -54,7 +54,7 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing auctionContractAddr, seqDial, ) - expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) + expressLaneClient.Start(ctx) // During the express lane around, Bob sends txs always 150ms later than Alice, but Alice's // txs end up getting delayed by 200ms as she is not the express lane controller. @@ -138,7 +138,7 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test auctionContractAddr, seqDial, ) - expressLaneClient.StopWaiter.Start(ctx, expressLaneClient) + expressLaneClient.Start(ctx) // We first generate an account for Charlie and transfer some balance to him. seqInfo.GenerateAccount("Charlie") @@ -462,6 +462,9 @@ func setupExpressLaneAuction( ) Require(t, err) + alice.Start(ctx) + bob.Start(ctx) + // Wait until the initial round. info, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) Require(t, err) diff --git a/timeboost/async.go b/timeboost/async.go deleted file mode 100644 index a400051473..0000000000 --- a/timeboost/async.go +++ /dev/null @@ -1,23 +0,0 @@ -package timeboost - -import ( - "context" - - "github.com/ethereum/go-ethereum/log" -) - -func receiveAsync[T any](ctx context.Context, channel chan T, f func(context.Context, T) error) { - for { - select { - case item := <-channel: - // TODO: Potential goroutine blow-up here. - go func() { - if err := f(ctx, item); err != nil { - log.Error("Error processing item", "error", err) - } - }() - case <-ctx.Done(): - return - } - } -} diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index f6579c8c70..94d22cb65a 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -17,6 +17,7 @@ import ( "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" @@ -184,6 +185,7 @@ func (bv *BidValidator) Initialize(ctx context.Context) error { } func (bv *BidValidator) Start(ctx_in context.Context) { + bv.StopWaiter.Start(ctx_in, bv) if bv.producer == nil { log.Crit("Bid validator not yet initialized by calling Initialize(ctx)") } @@ -194,30 +196,32 @@ type BidValidatorAPI struct { *BidValidator } -func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { - // Validate the received bid. - start := time.Now() - validatedBid, err := bv.validateBid( - &Bid{ - ChainId: bid.ChainId.ToInt(), - ExpressLaneController: bid.ExpressLaneController, - AuctionContractAddress: bid.AuctionContractAddress, - Round: uint64(bid.Round), - Amount: bid.Amount.ToInt(), - Signature: bid.Signature, - }, - bv.auctionContract.BalanceOf, - bv.fetchReservePrice, - ) - if err != nil { - return err - } - log.Info("Validated bid", "bidder", validatedBid.Bidder.Hex(), "amount", validatedBid.Amount.String(), "round", validatedBid.Round, "elapsed", time.Since(start)) - _, err = bv.producer.Produce(ctx, validatedBid) - if err != nil { - return err - } - return nil +func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) containers.PromiseInterface[struct{}] { + return stopwaiter.LaunchPromiseThread[struct{}](bv, func(ctx context.Context) (struct{}, error) { + // Validate the received bid. + start := time.Now() + validatedBid, err := bv.validateBid( + &Bid{ + ChainId: bid.ChainId.ToInt(), + ExpressLaneController: bid.ExpressLaneController, + AuctionContractAddress: bid.AuctionContractAddress, + Round: uint64(bid.Round), + Amount: bid.Amount.ToInt(), + Signature: bid.Signature, + }, + bv.auctionContract.BalanceOf, + bv.fetchReservePrice, + ) + if err != nil { + return struct{}{}, err + } + log.Info("Validated bid", "bidder", validatedBid.Bidder.Hex(), "amount", validatedBid.Amount.String(), "round", validatedBid.Round, "elapsed", time.Since(start)) + _, err = bv.producer.Produce(ctx, validatedBid) + if err != nil { + return struct{}{}, err + } + return struct{}{}, err + }) } // TODO(Terence): Set reserve price from the contract. diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 9698075343..03b99d1971 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -16,10 +16,13 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" ) type BidderClient struct { + stopwaiter.StopWaiter chainId *big.Int name string auctionContractAddress common.Address @@ -81,6 +84,10 @@ func NewBidderClient( }, nil } +func (bd *BidderClient) Start(ctx_in context.Context) { + bd.StopWaiter.Start(ctx_in, bd) +} + func (bd *BidderClient) Deposit(ctx context.Context, amount *big.Int) error { tx, err := bd.auctionContract.Deposit(bd.txOpts, amount) if err != nil { @@ -123,15 +130,18 @@ func (bd *BidderClient) Bid( return nil, err } newBid.Signature = sig - if err = bd.submitBid(ctx, newBid); err != nil { + promise := bd.submitBid(ctx, newBid) + if _, err := promise.Await(ctx); err != nil { return nil, err } return newBid, nil } -func (bd *BidderClient) submitBid(ctx context.Context, bid *Bid) error { - err := bd.auctioneerClient.CallContext(ctx, nil, "auctioneer_submitBid", bid.ToJson()) - return err +func (bd *BidderClient) submitBid(ctx context.Context, bid *Bid) containers.PromiseInterface[struct{}] { + return stopwaiter.LaunchPromiseThread[struct{}](bd, func(ctx context.Context) (struct{}, error) { + err := bd.auctioneerClient.CallContext(ctx, nil, "auctioneer_submitBid", bid.ToJson()) + return struct{}{}, err + }) } func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go index b26251a153..da6a366dfa 100644 --- a/timeboost/express_lane_client.go +++ b/timeboost/express_lane_client.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" ) @@ -49,10 +50,13 @@ func NewExpressLaneClient( } } +func (elc *ExpressLaneClient) Start(ctxIn context.Context) { + elc.StopWaiter.Start(ctxIn, elc) +} + func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) error { elc.Lock() defer elc.Unlock() - // return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { encodedTx, err := transaction.MarshalBinary() if err != nil { return err @@ -78,13 +82,21 @@ func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction * return err } msg.Signature = signature - if err = elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg); err != nil { + promise := elc.sendExpressLaneRPC(ctx, msg) + if _, err := promise.Await(ctx); err != nil { return err } elc.sequence += 1 return nil } +func (elc *ExpressLaneClient) sendExpressLaneRPC(ctx context.Context, msg *JsonExpressLaneSubmission) containers.PromiseInterface[struct{}] { + return stopwaiter.LaunchPromiseThread[struct{}](elc, func(ctx context.Context) (struct{}, error) { + err := elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) + return struct{}{}, err + }) +} + func signSubmission(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 98fc5a35db..648f5adea6 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -164,6 +164,7 @@ func setupBidderClient( auctioneerEndpoint, ) require.NoError(t, err) + bc.Start(ctx) // Approve spending by the express lane auction contract and beneficiary. maxUint256 := big.NewInt(1) From 7bb8e3ba255d73c7b97287b258f734472af054e6 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 10:25:08 -0500 Subject: [PATCH 051/109] edit test --- timeboost/bid_validator.go | 55 +++++++++++++++++---------------- timeboost/bid_validator_test.go | 2 +- timeboost/bidder_client.go | 5 +-- timeboost/db/schema.go | 40 ++++++++++++++++++++++++ timeboost/setup_test.go | 7 ++++- 5 files changed, 78 insertions(+), 31 deletions(-) create mode 100644 timeboost/db/schema.go diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 94d22cb65a..d36d9a000f 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -17,7 +17,6 @@ import ( "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" - "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" @@ -196,32 +195,32 @@ type BidValidatorAPI struct { *BidValidator } -func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) containers.PromiseInterface[struct{}] { - return stopwaiter.LaunchPromiseThread[struct{}](bv, func(ctx context.Context) (struct{}, error) { - // Validate the received bid. - start := time.Now() - validatedBid, err := bv.validateBid( - &Bid{ - ChainId: bid.ChainId.ToInt(), - ExpressLaneController: bid.ExpressLaneController, - AuctionContractAddress: bid.AuctionContractAddress, - Round: uint64(bid.Round), - Amount: bid.Amount.ToInt(), - Signature: bid.Signature, - }, - bv.auctionContract.BalanceOf, - bv.fetchReservePrice, - ) - if err != nil { - return struct{}{}, err - } - log.Info("Validated bid", "bidder", validatedBid.Bidder.Hex(), "amount", validatedBid.Amount.String(), "round", validatedBid.Round, "elapsed", time.Since(start)) - _, err = bv.producer.Produce(ctx, validatedBid) - if err != nil { - return struct{}{}, err - } - return struct{}{}, err - }) +func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { + // return stopwaiter.LaunchPromiseThread[struct{}](bv, func(ctx context.Context) (struct{}, error) { + // Validate the received bid. + start := time.Now() + validatedBid, err := bv.validateBid( + &Bid{ + ChainId: bid.ChainId.ToInt(), + ExpressLaneController: bid.ExpressLaneController, + AuctionContractAddress: bid.AuctionContractAddress, + Round: uint64(bid.Round), + Amount: bid.Amount.ToInt(), + Signature: bid.Signature, + }, + bv.auctionContract.BalanceOf, + bv.fetchReservePrice, + ) + if err != nil { + return err + } + log.Info("Validated bid", "bidder", validatedBid.Bidder.Hex(), "amount", validatedBid.Amount.String(), "round", validatedBid.Round, "elapsed", time.Since(start)) + _, err = bv.producer.Produce(ctx, validatedBid) + if err != nil { + return err + } + return nil + // }) } // TODO(Terence): Set reserve price from the contract. @@ -308,8 +307,10 @@ func (bv *BidValidator) validateBid( if !ok { bv.bidsPerSenderInRound[bidder] = 1 } + fmt.Println(numBids) if numBids > bv.maxBidsPerSenderInRound { bv.Unlock() + fmt.Println("Reached limit") return nil, errors.Wrapf(ErrTooManyBids, "bidder %s has already sent the maximum allowed bids = %d in this round", bidder.Hex(), numBids) } bv.bidsPerSenderInRound[bidder]++ diff --git a/timeboost/bid_validator_test.go b/timeboost/bid_validator_test.go index 69ede89e00..004272f974 100644 --- a/timeboost/bid_validator_test.go +++ b/timeboost/bid_validator_test.go @@ -157,7 +157,7 @@ func TestBidValidator_validateBid_perRoundBidLimitReached(t *testing.T) { require.NoError(t, err) bid.Signature = signature - for i := 0; i < int(bv.maxBidsPerSenderInRound)-1; i++ { + for i := 0; i < int(bv.maxBidsPerSenderInRound); i++ { _, err := bv.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) require.NoError(t, err) } diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 03b99d1971..d07bde5710 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -130,16 +130,17 @@ func (bd *BidderClient) Bid( return nil, err } newBid.Signature = sig - promise := bd.submitBid(ctx, newBid) + promise := bd.submitBid(newBid) if _, err := promise.Await(ctx); err != nil { return nil, err } return newBid, nil } -func (bd *BidderClient) submitBid(ctx context.Context, bid *Bid) containers.PromiseInterface[struct{}] { +func (bd *BidderClient) submitBid(bid *Bid) containers.PromiseInterface[struct{}] { return stopwaiter.LaunchPromiseThread[struct{}](bd, func(ctx context.Context) (struct{}, error) { err := bd.auctioneerClient.CallContext(ctx, nil, "auctioneer_submitBid", bid.ToJson()) + fmt.Println(err) return struct{}{}, err }) } diff --git a/timeboost/db/schema.go b/timeboost/db/schema.go new file mode 100644 index 0000000000..7680f32c56 --- /dev/null +++ b/timeboost/db/schema.go @@ -0,0 +1,40 @@ +package db + +var ( + flagSetup = ` +CREATE TABLE IF NOT EXISTS Flags ( + FlagName TEXT NOT NULL PRIMARY KEY, + FlagValue INTEGER NOT NULL +); +INSERT INTO Flags (FlagName, FlagValue) VALUES ('CurrentVersion', 0); +` + version1 = ` +CREATE TABLE IF NOT EXISTS Edges ( + Id TEXT NOT NULL PRIMARY KEY, + ChallengeLevel INTEGER NOT NULL, + OriginId TEXT NOT NULL, + StartHistoryRoot TEXT NOT NULL, + StartHeight INTEGER NOT NULL, + EndHistoryRoot TEXT NOT NULL, + EndHeight INTEGER NOT NULL, + CreatedAtBlock INTEGER NOT NULL, + MutualId TEXT NOT NULL, + ClaimId TEXT NOT NULL, + MiniStaker TEXT NOT NULL, + AssertionHash TEXT NOT NULL, + HasChildren BOOLEAN NOT NULL, + LowerChildId TEXT NOT NULL, + UpperChildId TEXT NOT NULL, + HasRival BOOLEAN NOT NULL, + Status TEXT NOT NULL, + HasLengthOneRival BOOLEAN NOT NULL, + IsRoyal BOOLEAN NOT NULL, + RawAncestors TEXT NOT NULL, + LastUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(LowerChildID) REFERENCES Edges(Id), + FOREIGN KEY(ClaimId) REFERENCES EdgeClaims(ClaimId), + FOREIGN KEY(UpperChildID) REFERENCES Edges(Id), + FOREIGN KEY(AssertionHash) REFERENCES Challenges(Hash) +); +` +) diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 648f5adea6..7ee83b986a 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -13,8 +13,10 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient/simulated" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/timeboost/bindings" @@ -155,11 +157,14 @@ func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { func setupBidderClient( t testing.TB, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, auctioneerEndpoint string, ) *BidderClient { + rpcClient, err := rpc.Dial(testSetup.endpoint) + require.NoError(t, err) + client := ethclient.NewClient(rpcClient) bc, err := NewBidderClient( ctx, name, &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, - nil, + client, testSetup.expressLaneAuctionAddr, auctioneerEndpoint, ) From 1a41c2504696dcaeb4ee1d6a34b01170f004f609 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 14:28:03 -0500 Subject: [PATCH 052/109] fix up db and store bids correctly --- timeboost/db/db.go | 98 ++++++++++++++++++++++++++++++++++++----- timeboost/db/db_test.go | 56 +++++++++++++++++++++-- timeboost/db/schema.go | 35 ++++----------- timeboost/types.go | 13 +++--- 4 files changed, 157 insertions(+), 45 deletions(-) diff --git a/timeboost/db/db.go b/timeboost/db/db.go index 0a3e3f18dc..e2867192b8 100644 --- a/timeboost/db/db.go +++ b/timeboost/db/db.go @@ -1,9 +1,13 @@ package db import ( + "fmt" "os" + "strings" + "sync" "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" "github.com/offchainlabs/nitro/timeboost" ) @@ -12,11 +16,13 @@ type Database interface { DeleteBids(round uint64) } -type Db struct { - db *sqlx.DB +type SqliteDatabase struct { + sqlDB *sqlx.DB + lock sync.Mutex + currentTableVersion int } -func NewDb(path string) (*Db, error) { +func NewDatabase(path string) (*SqliteDatabase, error) { //#nosec G304 if _, err := os.Stat(path); err != nil { _, err = os.Create(path) @@ -28,12 +34,80 @@ func NewDb(path string) (*Db, error) { if err != nil { return nil, err } - return &Db{ - db: db, + err = dbInit(db, schemaList) + if err != nil { + return nil, err + } + return &SqliteDatabase{ + sqlDB: db, + currentTableVersion: -1, }, nil } -func (d *Db) InsertBids(bids []*timeboost.Bid) error { +func dbInit(db *sqlx.DB, schemaList []string) error { + version, err := fetchVersion(db) + if err != nil { + return err + } + for index, schema := range schemaList { + // If the current version is less than the version of the schema, update the database + if index+1 > version { + err = executeSchema(db, schema, index+1) + if err != nil { + return err + } + } + } + return nil +} + +func fetchVersion(db *sqlx.DB) (int, error) { + flagValue := make([]int, 0) + // Fetch the current version of the database + err := db.Select(&flagValue, "SELECT FlagValue FROM Flags WHERE FlagName = 'CurrentVersion'") + if err != nil { + if !strings.Contains(err.Error(), "no such table") { + return 0, err + } + // If the table doesn't exist, create it + _, err = db.Exec(flagSetup) + if err != nil { + return 0, err + } + // Fetch the current version of the database + err = db.Select(&flagValue, "SELECT FlagValue FROM Flags WHERE FlagName = 'CurrentVersion'") + if err != nil { + return 0, err + } + } + if len(flagValue) > 0 { + return flagValue[0], nil + } else { + return 0, fmt.Errorf("no version found") + } +} + +func executeSchema(db *sqlx.DB, schema string, version int) error { + // Begin a transaction, so that we update the version and execute the schema atomically + tx, err := db.Beginx() + if err != nil { + return err + } + + // Execute the schema + _, err = tx.Exec(schema) + if err != nil { + return err + } + // Update the version of the database + _, err = tx.Exec(fmt.Sprintf("UPDATE Flags SET FlagValue = %d WHERE FlagName = 'CurrentVersion'", version)) + if err != nil { + return err + } + return tx.Commit() +} + +func (d *SqliteDatabase) InsertBids(bids []*timeboost.Bid) error { for _, b := range bids { if err := d.InsertBid(b); err != nil { return err @@ -42,7 +116,9 @@ func (d *Db) InsertBids(bids []*timeboost.Bid) error { return nil } -func (d *Db) InsertBid(b *timeboost.Bid) error { +func (d *SqliteDatabase) InsertBid(b *timeboost.Bid) error { + d.lock.Lock() + defer d.lock.Unlock() query := `INSERT INTO Bids ( ChainID, ExpressLaneController, AuctionContractAddress, Round, Amount, Signature ) VALUES ( @@ -56,15 +132,17 @@ func (d *Db) InsertBid(b *timeboost.Bid) error { "Amount": b.Amount.String(), "Signature": b.Signature, } - _, err := d.db.NamedExec(query, params) + _, err := d.sqlDB.NamedExec(query, params) if err != nil { return err } return nil } -func (d *Db) DeleteBids(round uint64) error { +func (d *SqliteDatabase) DeleteBids(round uint64) error { + d.lock.Lock() + defer d.lock.Unlock() query := `DELETE FROM Bids WHERE Round < ?` - _, err := d.db.Exec(query, round) + _, err := d.sqlDB.Exec(query, round) return err } diff --git a/timeboost/db/db_test.go b/timeboost/db/db_test.go index 065430fbc2..e8c23ba901 100644 --- a/timeboost/db/db_test.go +++ b/timeboost/db/db_test.go @@ -2,6 +2,8 @@ package db import ( "math/big" + "os" + "path/filepath" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -9,8 +11,55 @@ import ( "github.com/jmoiron/sqlx" "github.com/offchainlabs/nitro/timeboost" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +type DatabaseBid struct { + Id uint64 `db:"Id"` + ChainId string `db:"ChainId"` + ExpressLaneController string `db:"ExpressLaneController"` + AuctionContractAddress string `db:"AuctionContractAddress"` + Round uint64 `db:"Round"` + Amount string `db:"Amount"` + Signature string `db:"Signature"` +} + +func TestInsertAndFetchBids(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "*") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }) + tmpFile := filepath.Join(tmpDir, "database.sql?_journal_mode=WAL") + db, err := NewDatabase(tmpFile) + require.NoError(t, err) + + bids := []*timeboost.Bid{ + { + ChainId: big.NewInt(1), + ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000001"), + AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"), + Round: 1, + Amount: big.NewInt(100), + Signature: []byte("signature1"), + }, + { + ChainId: big.NewInt(2), + ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000003"), + AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000004"), + Round: 2, + Amount: big.NewInt(200), + Signature: []byte("signature2"), + }, + } + require.NoError(t, db.InsertBids(bids)) + gotBids := make([]*DatabaseBid, 2) + err = db.sqlDB.Select(&gotBids, "SELECT * FROM Bids ORDER BY Id") + require.NoError(t, err) + require.Equal(t, bids[0].Amount.String(), gotBids[0].Amount) + require.Equal(t, bids[1].Amount.String(), gotBids[1].Amount) +} + func TestInsertBids(t *testing.T) { db, mock, err := sqlmock.New() assert.NoError(t, err) @@ -18,7 +67,7 @@ func TestInsertBids(t *testing.T) { sqlxDB := sqlx.NewDb(db, "sqlmock") - d := &Db{db: sqlxDB} + d := &SqliteDatabase{sqlDB: sqlxDB, currentTableVersion: -1} bids := []*timeboost.Bid{ { @@ -64,8 +113,9 @@ func TestDeleteBidsLowerThanRound(t *testing.T) { sqlxDB := sqlx.NewDb(db, "sqlmock") - d := &Db{ - db: sqlxDB, + d := &SqliteDatabase{ + sqlDB: sqlxDB, + currentTableVersion: -1, } round := uint64(10) diff --git a/timeboost/db/schema.go b/timeboost/db/schema.go index 7680f32c56..2c18c84ae8 100644 --- a/timeboost/db/schema.go +++ b/timeboost/db/schema.go @@ -9,32 +9,15 @@ CREATE TABLE IF NOT EXISTS Flags ( INSERT INTO Flags (FlagName, FlagValue) VALUES ('CurrentVersion', 0); ` version1 = ` -CREATE TABLE IF NOT EXISTS Edges ( - Id TEXT NOT NULL PRIMARY KEY, - ChallengeLevel INTEGER NOT NULL, - OriginId TEXT NOT NULL, - StartHistoryRoot TEXT NOT NULL, - StartHeight INTEGER NOT NULL, - EndHistoryRoot TEXT NOT NULL, - EndHeight INTEGER NOT NULL, - CreatedAtBlock INTEGER NOT NULL, - MutualId TEXT NOT NULL, - ClaimId TEXT NOT NULL, - MiniStaker TEXT NOT NULL, - AssertionHash TEXT NOT NULL, - HasChildren BOOLEAN NOT NULL, - LowerChildId TEXT NOT NULL, - UpperChildId TEXT NOT NULL, - HasRival BOOLEAN NOT NULL, - Status TEXT NOT NULL, - HasLengthOneRival BOOLEAN NOT NULL, - IsRoyal BOOLEAN NOT NULL, - RawAncestors TEXT NOT NULL, - LastUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(LowerChildID) REFERENCES Edges(Id), - FOREIGN KEY(ClaimId) REFERENCES EdgeClaims(ClaimId), - FOREIGN KEY(UpperChildID) REFERENCES Edges(Id), - FOREIGN KEY(AssertionHash) REFERENCES Challenges(Hash) +CREATE TABLE IF NOT EXISTS Bids ( + Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + ChainId TEXT NOT NULL, + ExpressLaneController TEXT NOT NULL, + AuctionContractAddress TEXT NOT NULL, + Round INTEGER NOT NULL, + Amount TEXT NOT NULL, + Signature TEXT NOT NULL ); ` + schemaList = []string{version1} ) diff --git a/timeboost/types.go b/timeboost/types.go index 22ca660ccc..360cf4357d 100644 --- a/timeboost/types.go +++ b/timeboost/types.go @@ -17,12 +17,13 @@ import ( ) type Bid struct { - ChainId *big.Int - ExpressLaneController common.Address - AuctionContractAddress common.Address - Round uint64 - Amount *big.Int - Signature []byte + Id uint64 `db:"Id"` + ChainId *big.Int `db:"ChainId"` + ExpressLaneController common.Address `db:"ExpressLaneController"` + AuctionContractAddress common.Address `db:"AuctionContractAddress"` + Round uint64 `db:"Round"` + Amount *big.Int `db:"Amount"` + Signature []byte `db:"Signature"` } func (b *Bid) ToJson() *JsonBid { From f997721c9d68fe26ce2e40b8cb53b3683ed0099a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 16:57:32 -0500 Subject: [PATCH 053/109] all tests passing once more --- timeboost/auctioneer.go | 19 ++++ timeboost/auctioneer_test.go | 7 ++ timeboost/bid_cache_test.go | 193 +++++++++++--------------------- timeboost/bid_validator.go | 2 - timeboost/bid_validator_test.go | 2 + timeboost/{db => }/db.go | 32 ++---- timeboost/{db => }/db_test.go | 45 ++++---- timeboost/{db => }/schema.go | 3 +- util/redisutil/test_redis.go | 2 +- util/testhelpers/testhelpers.go | 2 +- 10 files changed, 136 insertions(+), 171 deletions(-) rename timeboost/{db => }/db.go (81%) rename timeboost/{db => }/db_test.go (81%) rename timeboost/{db => }/schema.go (92%) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index e5254a1b12..9757a40994 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -50,6 +50,7 @@ type AuctioneerServerConfig struct { Wallet genericconf.WalletConfig `koanf:"wallet"` SequencerEndpoint string `koanf:"sequencer-endpoint"` AuctionContractAddress string `koanf:"auction-contract-address"` + DbDirectory string `koanf:"db-directory"` } var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ @@ -73,6 +74,7 @@ func AuctioneerConfigAddOptions(prefix string, f *pflag.FlagSet) { pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) f.String(prefix+".redis-url", DefaultAuctioneerServerConfig.RedisURL, "url of redis server") f.String(prefix+".stream-prefix", DefaultAuctioneerServerConfig.StreamPrefix, "prefix for stream name") + f.String(prefix+".db-directory", DefaultAuctioneerServerConfig.DbDirectory, "path to database directory for persisting validated bids in a sqlite file") f.Duration(prefix+".stream-timeout", DefaultAuctioneerServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") @@ -96,6 +98,7 @@ type AuctioneerServer struct { auctionClosingDuration time.Duration roundDuration time.Duration streamTimeout time.Duration + database *SqliteDatabase } // NewAuctioneerServer creates a new autonomous auctioneer struct. @@ -107,6 +110,13 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf if cfg.AuctionContractAddress == "" { return nil, fmt.Errorf("auction contract address cannot be empty") } + if cfg.DbDirectory == "" { + return nil, errors.New("database directory is empty") + } + database, err := NewDatabase(cfg.DbDirectory) + if err != nil { + return nil, err + } auctionContractAddr := common.HexToAddress(cfg.AuctionContractAddress) redisClient, err := redisutil.RedisClientFromURL(cfg.RedisURL) if err != nil { @@ -145,6 +155,7 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf sequencerRpc: client, chainId: chainId, client: sequencerClient, + database: database, consumer: c, auctionContract: auctionContract, auctionContractAddr: auctionContractAddr, @@ -235,6 +246,8 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { case bid := <-a.bidsReceiver: log.Info("Consumed validated bid", "bidder", bid.Bidder, "amount", bid.Amount, "round", bid.Round) a.bidCache.add(JsonValidatedBidToGo(bid)) + // Persist the validated bid to the database as a non-blocking operation. + go a.persistValidatedBid(bid) case <-ctx.Done(): log.Info("Context done while waiting redis streams to be ready, failed to start") return @@ -337,6 +350,12 @@ func (a *AuctioneerServer) sendAuctionResolutionTransactionRPC(ctx context.Conte return a.sequencerRpc.CallContext(ctx, nil, "auctioneer_submitAuctionResolutionTransaction", tx) } +func (a *AuctioneerServer) persistValidatedBid(bid *JsonValidatedBid) { + if err := a.database.InsertBid(JsonValidatedBidToGo(bid)); err != nil { + log.Error("Could not persist validated bid to database", "err", err, "bidder", bid.Bidder, "amount", bid.Amount.String()) + } +} + func copyTxOpts(opts *bind.TransactOpts) *bind.TransactOpts { if opts == nil { fmt.Println("nil opts") diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 02344601fe..2416f1460f 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "os" "testing" "time" @@ -23,6 +24,11 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { defer cancel() testSetup := setupAuctionTest(t, ctx) redisURL := redisutil.CreateTestRedis(ctx, t) + tmpDir, err := os.MkdirTemp("", "*") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }) // Set up multiple bid validators that will receive bids via RPC using a bidder client. // They inject their validated bids into a Redis stream that a single auctioneer instance @@ -79,6 +85,7 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { AuctionContractAddress: testSetup.expressLaneAuctionAddr.Hex(), RedisURL: redisURL, ConsumerConfig: pubsub.TestConsumerConfig, + DbDirectory: tmpDir, Wallet: genericconf.WalletConfig{ PrivateKey: fmt.Sprintf("%x", testSetup.accounts[0].privKey.D.Bytes()), }, diff --git a/timeboost/bid_cache_test.go b/timeboost/bid_cache_test.go index 9cb75d8c9e..79c89d8bcd 100644 --- a/timeboost/bid_cache_test.go +++ b/timeboost/bid_cache_test.go @@ -1,90 +1,23 @@ package timeboost import ( + "context" + "fmt" "math/big" "net" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/pubsub" + "github.com/offchainlabs/nitro/util/redisutil" "github.com/stretchr/testify/require" ) -// func TestResolveAuction(t *testing.T) { -// ctx, cancel := context.WithCancel(context.Background()) -// defer cancel() - -// testSetup := setupAuctionTest(t, ctx) -// am, endpoint := setupAuctioneer(t, ctx, testSetup) - -// // Set up two different bidders. -// alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) -// bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[1], testSetup, endpoint) -// require.NoError(t, alice.Deposit(ctx, big.NewInt(5))) -// require.NoError(t, bob.Deposit(ctx, big.NewInt(5))) - -// // Wait until the initial round. -// info, err := alice.auctionContract.RoundTimingInfo(&bind.CallOpts{}) -// require.NoError(t, err) -// timeToWait := time.Until(time.Unix(int64(info.OffsetTimestamp), 0)) -// <-time.After(timeToWait) -// time.Sleep(time.Second) // Add a second of wait so that we are within a round. - -// // Form two new bids for the round, with Alice being the bigger one. -// _, err = alice.Bid(ctx, big.NewInt(2), alice.txOpts.From) -// require.NoError(t, err) -// _, err = bob.Bid(ctx, big.NewInt(1), bob.txOpts.From) -// require.NoError(t, err) - -// // Attempt to resolve the auction before it is closed and receive an error. -// require.ErrorContains(t, am.resolveAuction(ctx), "AuctionNotClosed") - -// // Await resolution. -// t.Log(time.Now()) -// ticker := newAuctionCloseTicker(am.roundDuration, am.auctionClosingDuration) -// go ticker.start() -// <-ticker.c -// require.NoError(t, am.resolveAuction(ctx)) - -// filterOpts := &bind.FilterOpts{ -// Context: ctx, -// Start: 0, -// End: nil, -// } -// it, err := am.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) -// require.NoError(t, err) -// aliceWon := false -// for it.Next() { -// // Expect Alice to have become the next express lane controller. -// if it.Event.FirstPriceBidder == alice.txOpts.From { -// aliceWon = true -// } -// } -// require.True(t, aliceWon) -// } - -// func TestReceiveBid_OK(t *testing.T) { -// ctx, cancel := context.WithCancel(context.Background()) -// defer cancel() - -// testSetup := setupAuctionTest(t, ctx) -// am, endpoint := setupAuctioneer(t, ctx, testSetup) -// bc := setupBidderClient(t, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) -// require.NoError(t, bc.Deposit(ctx, big.NewInt(5))) - -// // Form a new bid with an amount. -// newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) -// require.NoError(t, err) - -// // Check the bid passes validation. -// _, err = am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) -// require.NoError(t, err) - -// topTwoBids := am.bidCache.topTwoBids() -// require.True(t, topTwoBids.secondPlace == nil) -// require.True(t, topTwoBids.firstPlace.expressLaneController == newBid.ExpressLaneController) -// } - func TestTopTwoBids(t *testing.T) { + t.Parallel() tests := []struct { name string bids map[common.Address]*ValidatedBid @@ -208,57 +141,67 @@ func TestTopTwoBids(t *testing.T) { } } -// func BenchmarkBidValidation(b *testing.B) { -// b.StopTimer() -// ctx, cancel := context.WithCancel(context.Background()) -// defer cancel() - -// testSetup := setupAuctionTest(b, ctx) -// am, endpoint := setupAuctioneer(b, ctx, testSetup) -// bc := setupBidderClient(b, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) -// require.NoError(b, bc.Deposit(ctx, big.NewInt(5))) - -// // Form a valid bid. -// newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) -// require.NoError(b, err) - -// b.StartTimer() -// for i := 0; i < b.N; i++ { -// am.validateBid(newBid, am.auctionContract.BalanceOf, am.fetchReservePrice) -// } -// } +func BenchmarkBidValidation(b *testing.B) { + b.StopTimer() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + redisURL := redisutil.CreateTestRedis(ctx, b) + testSetup := setupAuctionTest(b, ctx) + bv, endpoint := setupBidValidator(b, ctx, redisURL, testSetup) + bc := setupBidderClient(b, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) + require.NoError(b, bc.Deposit(ctx, big.NewInt(5))) + + // Form a valid bid. + newBid, err := bc.Bid(ctx, big.NewInt(5), testSetup.accounts[0].txOpts.From) + require.NoError(b, err) + + b.StartTimer() + for i := 0; i < b.N; i++ { + bv.validateBid(newBid, bv.auctionContract.BalanceOf, bv.fetchReservePrice) + } +} -// func setupAuctioneer(t testing.TB, ctx context.Context, testSetup *auctionSetup) (*AuctioneerServer, string) { -// // Set up a new auctioneer instance that can validate bids. -// // Set up the auctioneer RPC service. -// randHttp := getRandomPort(t) -// stackConf := node.Config{ -// DataDir: "", // ephemeral. -// HTTPPort: randHttp, -// HTTPModules: []string{AuctioneerNamespace}, -// HTTPHost: "localhost", -// HTTPVirtualHosts: []string{"localhost"}, -// HTTPTimeouts: rpc.DefaultHTTPTimeouts, -// WSPort: getRandomPort(t), -// WSModules: []string{AuctioneerNamespace}, -// WSHost: "localhost", -// GraphQLVirtualHosts: []string{"localhost"}, -// P2P: p2p.Config{ -// ListenAddr: "", -// NoDial: true, -// NoDiscovery: true, -// }, -// } -// stack, err := node.New(&stackConf) -// require.NoError(t, err) -// am, err := NewAuctioneerServer( -// testSetup.accounts[0].txOpts, []*big.Int{testSetup.chainId}, testSetup.backend.Client(), testSetup.expressLaneAuctionAddr, "", nil, -// ) -// require.NoError(t, err) -// go am.Start(ctx) -// require.NoError(t, stack.Start()) -// return am, fmt.Sprintf("http://localhost:%d", randHttp) -// } +func setupBidValidator(t testing.TB, ctx context.Context, redisURL string, testSetup *auctionSetup) (*BidValidator, string) { + randHttp := getRandomPort(t) + stackConf := node.Config{ + DataDir: "", // ephemeral. + HTTPPort: randHttp, + HTTPModules: []string{AuctioneerNamespace}, + HTTPHost: "localhost", + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: getRandomPort(t), + WSModules: []string{AuctioneerNamespace}, + WSHost: "localhost", + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDial: true, + NoDiscovery: true, + }, + } + stack, err := node.New(&stackConf) + require.NoError(t, err) + cfg := &BidValidatorConfig{ + SequencerEndpoint: testSetup.endpoint, + AuctionContractAddress: testSetup.expressLaneAuctionAddr.Hex(), + RedisURL: redisURL, + ProducerConfig: pubsub.TestProducerConfig, + } + fetcher := func() *BidValidatorConfig { + return cfg + } + bidValidator, err := NewBidValidator( + ctx, + stack, + fetcher, + ) + require.NoError(t, err) + require.NoError(t, bidValidator.Initialize(ctx)) + require.NoError(t, stack.Start()) + bidValidator.Start(ctx) + return bidValidator, fmt.Sprintf("http://localhost:%d", randHttp) +} func getRandomPort(t testing.TB) int { listener, err := net.Listen("tcp", "localhost:0") diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index d36d9a000f..a08a464c46 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -307,10 +307,8 @@ func (bv *BidValidator) validateBid( if !ok { bv.bidsPerSenderInRound[bidder] = 1 } - fmt.Println(numBids) if numBids > bv.maxBidsPerSenderInRound { bv.Unlock() - fmt.Println("Reached limit") return nil, errors.Wrapf(ErrTooManyBids, "bidder %s has already sent the maximum allowed bids = %d in this round", bidder.Hex(), numBids) } bv.bidsPerSenderInRound[bidder]++ diff --git a/timeboost/bid_validator_test.go b/timeboost/bid_validator_test.go index 004272f974..25481ac32c 100644 --- a/timeboost/bid_validator_test.go +++ b/timeboost/bid_validator_test.go @@ -15,6 +15,7 @@ import ( ) func TestBidValidator_validateBid(t *testing.T) { + t.Parallel() setup := setupAuctionTest(t, context.Background()) tests := []struct { name string @@ -123,6 +124,7 @@ func TestBidValidator_validateBid(t *testing.T) { } func TestBidValidator_validateBid_perRoundBidLimitReached(t *testing.T) { + t.Parallel() balanceCheckerFn := func(_ *bind.CallOpts, _ common.Address) (*big.Int, error) { return big.NewInt(10), nil } diff --git a/timeboost/db/db.go b/timeboost/db.go similarity index 81% rename from timeboost/db/db.go rename to timeboost/db.go index e2867192b8..d5825166d6 100644 --- a/timeboost/db/db.go +++ b/timeboost/db.go @@ -1,20 +1,18 @@ -package db +package timeboost import ( "fmt" + "io/fs" "os" + "path/filepath" "strings" "sync" "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" - "github.com/offchainlabs/nitro/timeboost" ) -type Database interface { - SaveBids(bids []*timeboost.Bid) error - DeleteBids(round uint64) -} +const sqliteFileName = "validated_bids.db?_journal_mode=WAL" type SqliteDatabase struct { sqlDB *sqlx.DB @@ -25,12 +23,12 @@ type SqliteDatabase struct { func NewDatabase(path string) (*SqliteDatabase, error) { //#nosec G304 if _, err := os.Stat(path); err != nil { - _, err = os.Create(path) - if err != nil { + if err = os.MkdirAll(path, fs.ModeDir); err != nil { return nil, err } } - db, err := sqlx.Open("sqlite3", path) + filePath := filepath.Join(path, sqliteFileName) + db, err := sqlx.Open("sqlite3", filePath) if err != nil { return nil, err } @@ -107,25 +105,17 @@ func executeSchema(db *sqlx.DB, schema string, version int) error { return tx.Commit() } -func (d *SqliteDatabase) InsertBids(bids []*timeboost.Bid) error { - for _, b := range bids { - if err := d.InsertBid(b); err != nil { - return err - } - } - return nil -} - -func (d *SqliteDatabase) InsertBid(b *timeboost.Bid) error { +func (d *SqliteDatabase) InsertBid(b *ValidatedBid) error { d.lock.Lock() defer d.lock.Unlock() query := `INSERT INTO Bids ( - ChainID, ExpressLaneController, AuctionContractAddress, Round, Amount, Signature + ChainID, Bidder, ExpressLaneController, AuctionContractAddress, Round, Amount, Signature ) VALUES ( - :ChainID, :ExpressLaneController, :AuctionContractAddress, :Round, :Amount, :Signature + :ChainID, :Bidder, :ExpressLaneController, :AuctionContractAddress, :Round, :Amount, :Signature )` params := map[string]interface{}{ "ChainID": b.ChainId.String(), + "Bidder": b.Bidder.Hex(), "ExpressLaneController": b.ExpressLaneController.Hex(), "AuctionContractAddress": b.AuctionContractAddress.Hex(), "Round": b.Round, diff --git a/timeboost/db/db_test.go b/timeboost/db_test.go similarity index 81% rename from timeboost/db/db_test.go rename to timeboost/db_test.go index e8c23ba901..a2c056f52a 100644 --- a/timeboost/db/db_test.go +++ b/timeboost/db_test.go @@ -1,40 +1,39 @@ -package db +package timeboost import ( "math/big" "os" - "path/filepath" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/ethereum/go-ethereum/common" "github.com/jmoiron/sqlx" - "github.com/offchainlabs/nitro/timeboost" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -type DatabaseBid struct { - Id uint64 `db:"Id"` - ChainId string `db:"ChainId"` - ExpressLaneController string `db:"ExpressLaneController"` - AuctionContractAddress string `db:"AuctionContractAddress"` - Round uint64 `db:"Round"` - Amount string `db:"Amount"` - Signature string `db:"Signature"` -} - func TestInsertAndFetchBids(t *testing.T) { + t.Parallel() + type DatabaseBid struct { + Id uint64 `db:"Id"` + ChainId string `db:"ChainId"` + Bidder string `db:"Bidder"` + ExpressLaneController string `db:"ExpressLaneController"` + AuctionContractAddress string `db:"AuctionContractAddress"` + Round uint64 `db:"Round"` + Amount string `db:"Amount"` + Signature string `db:"Signature"` + } + tmpDir, err := os.MkdirTemp("", "*") require.NoError(t, err) t.Cleanup(func() { require.NoError(t, os.RemoveAll(tmpDir)) }) - tmpFile := filepath.Join(tmpDir, "database.sql?_journal_mode=WAL") - db, err := NewDatabase(tmpFile) + db, err := NewDatabase(tmpDir) require.NoError(t, err) - bids := []*timeboost.Bid{ + bids := []*ValidatedBid{ { ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000001"), @@ -52,7 +51,9 @@ func TestInsertAndFetchBids(t *testing.T) { Signature: []byte("signature2"), }, } - require.NoError(t, db.InsertBids(bids)) + for _, bid := range bids { + require.NoError(t, db.InsertBid(bid)) + } gotBids := make([]*DatabaseBid, 2) err = db.sqlDB.Select(&gotBids, "SELECT * FROM Bids ORDER BY Id") require.NoError(t, err) @@ -61,6 +62,7 @@ func TestInsertAndFetchBids(t *testing.T) { } func TestInsertBids(t *testing.T) { + t.Parallel() db, mock, err := sqlmock.New() assert.NoError(t, err) defer db.Close() @@ -69,7 +71,7 @@ func TestInsertBids(t *testing.T) { d := &SqliteDatabase{sqlDB: sqlxDB, currentTableVersion: -1} - bids := []*timeboost.Bid{ + bids := []*ValidatedBid{ { ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000001"), @@ -99,14 +101,17 @@ func TestInsertBids(t *testing.T) { ).WillReturnResult(sqlmock.NewResult(1, 1)) } - err = d.InsertBids(bids) - assert.NoError(t, err) + for _, bid := range bids { + err = d.InsertBid(bid) + assert.NoError(t, err) + } err = mock.ExpectationsWereMet() assert.NoError(t, err) } func TestDeleteBidsLowerThanRound(t *testing.T) { + t.Parallel() db, mock, err := sqlmock.New() assert.NoError(t, err) defer db.Close() diff --git a/timeboost/db/schema.go b/timeboost/schema.go similarity index 92% rename from timeboost/db/schema.go rename to timeboost/schema.go index 2c18c84ae8..94fc04d1f1 100644 --- a/timeboost/db/schema.go +++ b/timeboost/schema.go @@ -1,4 +1,4 @@ -package db +package timeboost var ( flagSetup = ` @@ -12,6 +12,7 @@ INSERT INTO Flags (FlagName, FlagValue) VALUES ('CurrentVersion', 0); CREATE TABLE IF NOT EXISTS Bids ( Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ChainId TEXT NOT NULL, + Bidder TEXT NOT NULL, ExpressLaneController TEXT NOT NULL, AuctionContractAddress TEXT NOT NULL, Round INTEGER NOT NULL, diff --git a/util/redisutil/test_redis.go b/util/redisutil/test_redis.go index 6d493b1546..b6d2dc8fa8 100644 --- a/util/redisutil/test_redis.go +++ b/util/redisutil/test_redis.go @@ -15,7 +15,7 @@ import ( // CreateTestRedis Provides external redis url, this is only done in TEST_REDIS env, // else creates a new miniredis and returns its url. -func CreateTestRedis(ctx context.Context, t *testing.T) string { +func CreateTestRedis(ctx context.Context, t testing.TB) string { redisUrl := os.Getenv("TEST_REDIS") if redisUrl != "" { return redisUrl diff --git a/util/testhelpers/testhelpers.go b/util/testhelpers/testhelpers.go index b1b08708e7..9fe2e299d0 100644 --- a/util/testhelpers/testhelpers.go +++ b/util/testhelpers/testhelpers.go @@ -22,7 +22,7 @@ import ( ) // Fail a test should an error occur -func RequireImpl(t *testing.T, err error, printables ...interface{}) { +func RequireImpl(t testing.TB, err error, printables ...interface{}) { t.Helper() if err != nil { t.Log(string(debug.Stack())) From 6a0afafc2cb124b79b54eb19ac4ff0f1d3d40789 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 17:01:27 -0500 Subject: [PATCH 054/109] rem ctx --- timeboost/express_lane_client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go index da6a366dfa..e70ff8bd9e 100644 --- a/timeboost/express_lane_client.go +++ b/timeboost/express_lane_client.go @@ -82,7 +82,7 @@ func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction * return err } msg.Signature = signature - promise := elc.sendExpressLaneRPC(ctx, msg) + promise := elc.sendExpressLaneRPC(msg) if _, err := promise.Await(ctx); err != nil { return err } @@ -90,8 +90,8 @@ func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction * return nil } -func (elc *ExpressLaneClient) sendExpressLaneRPC(ctx context.Context, msg *JsonExpressLaneSubmission) containers.PromiseInterface[struct{}] { - return stopwaiter.LaunchPromiseThread[struct{}](elc, func(ctx context.Context) (struct{}, error) { +func (elc *ExpressLaneClient) sendExpressLaneRPC(msg *JsonExpressLaneSubmission) containers.PromiseInterface[struct{}] { + return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { err := elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) return struct{}{}, err }) From 9c4af2a9850b1c5863155c7138c841da734b90a5 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 17:25:43 -0500 Subject: [PATCH 055/109] add jwt auth for auctioneer to sequencer --- execution/gethexec/node.go | 10 +++++----- go.mod | 13 ++++++------- go.sum | 4 ++++ system_tests/timeboost_test.go | 31 +++++++++++++++++++++++++++---- timeboost/auctioneer.go | 33 ++++++++++++++++++++++++++++++++- 5 files changed, 74 insertions(+), 17 deletions(-) diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index f78dde5646..c0680724c9 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -234,11 +234,11 @@ func CreateExecutionNode( Public: false, }} apis = append(apis, rpc.API{ - Namespace: "auctioneer", - Version: "1.0", - Service: NewArbTimeboostAuctioneerAPI(txPublisher), - Public: false, - // Authenticated: true, /* Only exposed via JWT Auth */ + Namespace: "auctioneer", + Version: "1.0", + Service: NewArbTimeboostAuctioneerAPI(txPublisher), + Public: false, + Authenticated: true, // Only exposed via JWT Auth to the auctioneer. }) apis = append(apis, rpc.API{ Namespace: "timeboost", diff --git a/go.mod b/go.mod index a7ea6373ea..d0431ce3c4 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ replace github.com/VictoriaMetrics/fastcache => ./fastcache replace github.com/ethereum/go-ethereum => ./go-ethereum require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible github.com/Shopify/toxiproxy v2.1.4+incompatible github.com/alicebob/miniredis/v2 v2.32.1 @@ -28,12 +29,15 @@ require ( github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.2.1 github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.3.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/holiman/uint256 v1.2.4 + github.com/jmoiron/sqlx v1.4.0 github.com/knadh/koanf v1.4.0 github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f + github.com/mattn/go-sqlite3 v1.14.22 github.com/mitchellh/mapstructure v1.4.1 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v3 v3.0.1 @@ -52,12 +56,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) -require ( - github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/jmoiron/sqlx v1.4.0 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect -) +require github.com/google/go-querystring v1.1.0 // indirect require ( github.com/DataDog/zstd v1.4.5 // indirect @@ -113,7 +112,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.3 // indirect diff --git a/go.sum b/go.sum index 7fa19235fb..cf57e516b5 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -280,6 +281,7 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -303,6 +305,7 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -510,6 +513,7 @@ github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awS github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index cd9a75eccb..9e953ae8e8 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "math/big" + "os" + "path/filepath" "sync" "testing" "time" @@ -25,6 +27,7 @@ import ( "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/redisutil" + "github.com/stretchr/testify/require" ) func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing.T) { @@ -32,7 +35,14 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) + tmpDir, err := os.MkdirTemp("", "*") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }) + jwtSecretPath := filepath.Join(tmpDir, "sequencer.jwt") + + _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, tmpDir, ctx, jwtSecretPath) defer cleanupSeq() chainId, err := seqClient.ChainID(ctx) Require(t, err) @@ -116,7 +126,13 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, ctx) + tmpDir, err := os.MkdirTemp("", "*") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }) + jwtSecretPath := filepath.Join(tmpDir, "sequencer.jwt") + _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, tmpDir, ctx, jwtSecretPath) defer cleanupSeq() chainId, err := seqClient.ChainID(ctx) Require(t, err) @@ -225,14 +241,19 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test func setupExpressLaneAuction( t *testing.T, + dbDirPath string, ctx context.Context, + jwtSecretPath string, ) (*arbnode.Node, *ethclient.Client, *BlockchainTestInfo, common.Address, func()) { builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, true) builderSeq.l2StackConfig.HTTPHost = "localhost" builderSeq.l2StackConfig.HTTPPort = 9567 - builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost", "auctioneer"} + builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost"} + builderSeq.l2StackConfig.AuthPort = 9568 + builderSeq.l2StackConfig.AuthModules = []string{"eth", "arb", "debug", "timeboost", "auctioneer"} + builderSeq.l2StackConfig.JWTSecret = jwtSecretPath builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() builderSeq.execConfig.Sequencer.Enable = true builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ @@ -415,10 +436,12 @@ func setupExpressLaneAuction( bidValidator.Start(ctx) auctioneerCfg := &timeboost.AuctioneerServerConfig{ - SequencerEndpoint: "http://localhost:9567", + SequencerEndpoint: "http://localhost:9568", AuctionContractAddress: proxyAddr.Hex(), RedisURL: redisURL, ConsumerConfig: pubsub.TestConsumerConfig, + SequencerJWTPath: jwtSecretPath, + DbDirectory: dbDirPath, Wallet: genericconf.WalletConfig{ PrivateKey: fmt.Sprintf("00%x", seqInfo.Accounts["AuctionContract"].PrivateKey.D.Bytes()), }, diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 9757a40994..fe9f6901b4 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -4,14 +4,18 @@ import ( "context" "fmt" "math/big" + "net/http" + "os" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" + "github.com/golang-jwt/jwt/v4" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/cmd/util" "github.com/offchainlabs/nitro/pubsub" @@ -51,6 +55,7 @@ type AuctioneerServerConfig struct { SequencerEndpoint string `koanf:"sequencer-endpoint"` AuctionContractAddress string `koanf:"auction-contract-address"` DbDirectory string `koanf:"db-directory"` + SequencerJWTPath string `koanf:"sequencer-jwt-path"` } var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ @@ -78,6 +83,7 @@ func AuctioneerConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Duration(prefix+".stream-timeout", DefaultAuctioneerServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") + f.String(prefix+".sequencer-jwt-path", DefaultAuctioneerServerConfig.SequencerJWTPath, "sequencer jwt file path") f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") } @@ -113,6 +119,9 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf if cfg.DbDirectory == "" { return nil, errors.New("database directory is empty") } + if cfg.SequencerJWTPath == "" { + return nil, errors.New("no sequencer jwt path specified") + } database, err := NewDatabase(cfg.DbDirectory) if err != nil { return nil, err @@ -126,7 +135,29 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf if err != nil { return nil, fmt.Errorf("creating consumer for validation: %w", err) } - client, err := rpc.DialContext(ctx, cfg.SequencerEndpoint) + sequencerJwtStr, err := os.ReadFile(cfg.SequencerJWTPath) + if err != nil { + return nil, err + } + sequencerJwt, err := hexutil.Decode(string(sequencerJwtStr)) + if err != nil { + return nil, err + } + client, err := rpc.DialOptions(ctx, cfg.SequencerEndpoint, rpc.WithHTTPAuth(func(h http.Header) error { + claims := jwt.MapClaims{ + // Required claim for engine API auth. "iat" stands for issued at + // and it must be a unix timestamp that is +/- 5 seconds from the current + // timestamp at the moment the server verifies this value. + "iat": time.Now().Unix(), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString(sequencerJwt) + if err != nil { + return errors.Wrap(err, "could not produce signed JWT token") + } + h.Set("Authorization", fmt.Sprintf("Bearer %s", tokenString)) + return nil + })) if err != nil { return nil, err } From 40f4f24184b9b4b43c73e2658066f1f6fa7a0b9d Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Wed, 7 Aug 2024 17:29:21 -0500 Subject: [PATCH 056/109] authenticated, use call iteratively --- execution/gethexec/express_lane_service.go | 80 ++++++++++------------ 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 3145995c97..2e4f61d65b 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -109,52 +109,42 @@ func (es *expressLaneService) Start(ctxIn context.Context) { log.Crit("Could not get latest header", "err", err) } fromBlock := latestBlock.Number.Uint64() - ticker := time.NewTicker(time.Millisecond * 250) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - latestBlock, err := es.seqClient.HeaderByNumber(ctx, nil) - if err != nil { - log.Error("Could not get latest header", "err", err) - continue - } - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: fromBlock, - End: &toBlock, - } - it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) - if err != nil { - log.Error("Could not filter auction resolutions", "error", err) - continue - } - for it.Next() { - log.Info( - "New express lane controller assigned", - "round", it.Event.Round, - "controller", it.Event.FirstPriceExpressLaneController, - ) - es.Lock() - es.roundControl.Add(it.Event.Round, &expressLaneControl{ - controller: it.Event.FirstPriceExpressLaneController, - sequence: 0, - }) - es.Unlock() - } - fromBlock = toBlock + duration := time.Millisecond * 250 + es.CallIteratively(func(ctx context.Context) time.Duration { + latestBlock, err := es.seqClient.HeaderByNumber(ctx, nil) + if err != nil { + log.Error("Could not get latest header", "err", err) } - } - }) - es.LaunchThread(func(ctx context.Context) { - // Monitor for auction cancelations. - // TODO: Implement. + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + return duration + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) + if err != nil { + log.Error("Could not filter auction resolutions", "error", err) + return duration + } + for it.Next() { + log.Info( + "New express lane controller assigned", + "round", it.Event.Round, + "controller", it.Event.FirstPriceExpressLaneController, + ) + es.Lock() + es.roundControl.Add(it.Event.Round, &expressLaneControl{ + controller: it.Event.FirstPriceExpressLaneController, + sequence: 0, + }) + es.Unlock() + } + fromBlock = toBlock + return duration + }) }) } From 60fefb5290540e3b5686fcef8f74b6278b5a94d2 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 09:37:21 -0500 Subject: [PATCH 057/109] update contracts --- contracts | 2 +- timeboost/auctioneer_test.go | 7 +++++++ timeboost/db_test.go | 5 +++++ timeboost/setup_test.go | 33 +++++++++++++++++---------------- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/contracts b/contracts index dca59ed7ba..a815490fce 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit dca59ed7ba4aef52211b771c11d6bfd9fd144d8f +Subproject commit a815490fce7c7869f026df88efb437856caa46d8 diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 2416f1460f..681a201fcd 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -5,10 +5,13 @@ import ( "fmt" "math/big" "os" + "path/filepath" "testing" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" @@ -29,6 +32,9 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { t.Cleanup(func() { require.NoError(t, os.RemoveAll(tmpDir)) }) + jwtFilePath := filepath.Join(tmpDir, "jwt.key") + jwtSecret := common.BytesToHash([]byte("jwt")) + require.NoError(t, os.WriteFile(jwtFilePath, []byte(hexutil.Encode(jwtSecret[:])), 0644)) // Set up multiple bid validators that will receive bids via RPC using a bidder client. // They inject their validated bids into a Redis stream that a single auctioneer instance @@ -82,6 +88,7 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { // by the bid validators from a redis stream. cfg := &AuctioneerServerConfig{ SequencerEndpoint: testSetup.endpoint, + SequencerJWTPath: jwtFilePath, AuctionContractAddress: testSetup.expressLaneAuctionAddr.Hex(), RedisURL: redisURL, ConsumerConfig: pubsub.TestConsumerConfig, diff --git a/timeboost/db_test.go b/timeboost/db_test.go index a2c056f52a..a193cdaf8f 100644 --- a/timeboost/db_test.go +++ b/timeboost/db_test.go @@ -38,6 +38,7 @@ func TestInsertAndFetchBids(t *testing.T) { ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000001"), AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"), + Bidder: common.HexToAddress("0x0000000000000000000000000000000000000002"), Round: 1, Amount: big.NewInt(100), Signature: []byte("signature1"), @@ -46,6 +47,7 @@ func TestInsertAndFetchBids(t *testing.T) { ChainId: big.NewInt(2), ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000003"), AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000004"), + Bidder: common.HexToAddress("0x0000000000000000000000000000000000000002"), Round: 2, Amount: big.NewInt(200), Signature: []byte("signature2"), @@ -76,6 +78,7 @@ func TestInsertBids(t *testing.T) { ChainId: big.NewInt(1), ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000001"), AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"), + Bidder: common.HexToAddress("0x0000000000000000000000000000000000000002"), Round: 1, Amount: big.NewInt(100), Signature: []byte("signature1"), @@ -84,6 +87,7 @@ func TestInsertBids(t *testing.T) { ChainId: big.NewInt(2), ExpressLaneController: common.HexToAddress("0x0000000000000000000000000000000000000003"), AuctionContractAddress: common.HexToAddress("0x0000000000000000000000000000000000000004"), + Bidder: common.HexToAddress("0x0000000000000000000000000000000000000002"), Round: 2, Amount: big.NewInt(200), Signature: []byte("signature2"), @@ -93,6 +97,7 @@ func TestInsertBids(t *testing.T) { for _, bid := range bids { mock.ExpectExec("INSERT INTO Bids").WithArgs( bid.ChainId.String(), + bid.Bidder.Hex(), bid.ExpressLaneController.Hex(), bid.AuctionContractAddress.Hex(), bid.Round, diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 7ee83b986a..b47aab5b0f 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -114,25 +114,26 @@ func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { reserveSubmissionSeconds := uint64(15) minReservePrice := big.NewInt(1) // 1 wei. roleAdmin := opts.From - minReservePriceSetter := opts.From - reservePriceSetter := opts.From - beneficiarySetter := opts.From tx, err = auctionContract.Initialize( opts, - auctioneer, - beneficiary, - biddingToken, - express_lane_auctiongen.RoundTimingInfo{ - OffsetTimestamp: initialTimestamp.Uint64(), - RoundDurationSeconds: bidRoundSeconds, - AuctionClosingSeconds: auctionClosingSeconds, - ReserveSubmissionSeconds: reserveSubmissionSeconds, + express_lane_auctiongen.InitArgs{ + Auctioneer: auctioneer, + BiddingToken: biddingToken, + Beneficiary: beneficiary, + RoundTimingInfo: express_lane_auctiongen.RoundTimingInfo{ + OffsetTimestamp: initialTimestamp.Uint64(), + RoundDurationSeconds: bidRoundSeconds, + AuctionClosingSeconds: auctionClosingSeconds, + ReserveSubmissionSeconds: reserveSubmissionSeconds, + }, + MinReservePrice: minReservePrice, + AuctioneerAdmin: roleAdmin, + MinReservePriceSetter: roleAdmin, + ReservePriceSetter: roleAdmin, + BeneficiarySetter: roleAdmin, + RoundTimingSetter: roleAdmin, + MasterAdmin: roleAdmin, }, - minReservePrice, - roleAdmin, - minReservePriceSetter, - reservePriceSetter, - beneficiarySetter, ) require.NoError(t, err) if _, err = bind.WaitMined(ctx, backend.Client(), tx); err != nil { From 27b9a0c17d53973aec56472683a83ec5a58ebc93 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 09:42:13 -0500 Subject: [PATCH 058/109] test pass --- system_tests/timeboost_test.go | 33 +++++++++++++++++---------------- timeboost/auctioneer.go | 1 - timeboost/bid_validator.go | 3 --- timeboost/bidder_client.go | 1 - 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 9e953ae8e8..00072ba003 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -332,25 +332,26 @@ func setupExpressLaneAuction( reserveSubmissionSeconds := uint64(15) minReservePrice := big.NewInt(1) // 1 wei. roleAdmin := auctioneerAddr - minReservePriceSetter := auctioneerAddr - reservePriceSetter := auctioneerAddr - beneficiarySetter := auctioneerAddr tx, err = auctionContract.Initialize( &ownerOpts, - auctioneerAddr, - beneficiary, - biddingToken, - express_lane_auctiongen.RoundTimingInfo{ - OffsetTimestamp: initialTimestamp.Uint64(), - RoundDurationSeconds: bidRoundSeconds, - AuctionClosingSeconds: auctionClosingSeconds, - ReserveSubmissionSeconds: reserveSubmissionSeconds, + express_lane_auctiongen.InitArgs{ + Auctioneer: auctioneerAddr, + BiddingToken: biddingToken, + Beneficiary: beneficiary, + RoundTimingInfo: express_lane_auctiongen.RoundTimingInfo{ + OffsetTimestamp: initialTimestamp.Uint64(), + RoundDurationSeconds: bidRoundSeconds, + AuctionClosingSeconds: auctionClosingSeconds, + ReserveSubmissionSeconds: reserveSubmissionSeconds, + }, + MinReservePrice: minReservePrice, + AuctioneerAdmin: roleAdmin, + MinReservePriceSetter: roleAdmin, + ReservePriceSetter: roleAdmin, + BeneficiarySetter: roleAdmin, + RoundTimingSetter: roleAdmin, + MasterAdmin: roleAdmin, }, - minReservePrice, - roleAdmin, - minReservePriceSetter, - reservePriceSetter, - beneficiarySetter, ) Require(t, err) if _, err = bind.WaitMined(ctx, seqClient, tx); err != nil { diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index fe9f6901b4..4d1ac9b038 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -389,7 +389,6 @@ func (a *AuctioneerServer) persistValidatedBid(bid *JsonValidatedBid) { func copyTxOpts(opts *bind.TransactOpts) *bind.TransactOpts { if opts == nil { - fmt.Println("nil opts") return nil } copied := &bind.TransactOpts{ diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index a08a464c46..fe677003c8 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -196,8 +196,6 @@ type BidValidatorAPI struct { } func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { - // return stopwaiter.LaunchPromiseThread[struct{}](bv, func(ctx context.Context) (struct{}, error) { - // Validate the received bid. start := time.Now() validatedBid, err := bv.validateBid( &Bid{ @@ -220,7 +218,6 @@ func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return err } return nil - // }) } // TODO(Terence): Set reserve price from the contract. diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index d07bde5710..02f603b545 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -140,7 +140,6 @@ func (bd *BidderClient) Bid( func (bd *BidderClient) submitBid(bid *Bid) containers.PromiseInterface[struct{}] { return stopwaiter.LaunchPromiseThread[struct{}](bd, func(ctx context.Context) (struct{}, error) { err := bd.auctioneerClient.CallContext(ctx, nil, "auctioneer_submitBid", bid.ToJson()) - fmt.Println(err) return struct{}{}, err }) } From bd47e8062431f5f22f8b1f5038571265ca1a19b3 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 09:58:47 -0500 Subject: [PATCH 059/109] sequencer checks within auction closing --- execution/gethexec/express_lane_service.go | 14 +++++ .../gethexec/express_lane_service_test.go | 53 +++++++++++++++++++ execution/gethexec/sequencer.go | 7 ++- 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 2e4f61d65b..62a4dd8f15 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -34,6 +34,7 @@ type expressLaneService struct { auctionContractAddr common.Address initialTimestamp time.Time roundDuration time.Duration + auctionClosingSeconds time.Duration chainConfig *params.ChainConfig logs chan []*types.Log seqClient *ethclient.Client @@ -58,10 +59,12 @@ func newExpressLaneService( } initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + auctionClosingSeconds := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second return &expressLaneService{ auctionContract: auctionContract, chainConfig: chainConfig, initialTimestamp: initialTimestamp, + auctionClosingSeconds: auctionClosingSeconds, roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), // Keep 8 rounds cached. auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, @@ -159,6 +162,17 @@ func (es *expressLaneService) currentRoundHasController() bool { return control.controller != (common.Address{}) } +func (es *expressLaneService) isWithinAuctionCloseWindow(arrivalTime time.Time) bool { + // Calculate the next round start time + elapsedTime := arrivalTime.Sub(es.initialTimestamp) + elapsedRounds := elapsedTime / es.roundDuration + nextRoundStart := es.initialTimestamp.Add((elapsedRounds + 1) * es.roundDuration) + // Calculate the time to the next round + timeToNextRound := nextRoundStart.Sub(arrivalTime) + // Check if the arrival timestamp is within AUCTION_CLOSING_DURATION of TIME_TO_NEXT_ROUND + return timeToNextRound <= es.auctionClosingSeconds +} + func (es *expressLaneService) sequenceExpressLaneSubmission( ctx context.Context, msg *timeboost.ExpressLaneSubmission, diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index eba15dc635..9c39e0743e 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -372,6 +372,59 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_erroredTx(t *testing. require.Equal(t, []uint64{1, 2, 3}, publishedTxOrder) } +func TestIsWithinAuctionCloseWindow(t *testing.T) { + initialTimestamp := time.Date(2024, 8, 8, 15, 0, 0, 0, time.UTC) + roundDuration := 1 * time.Minute + auctionClosing := 15 * time.Second + + es := &expressLaneService{ + initialTimestamp: initialTimestamp, + roundDuration: roundDuration, + auctionClosingSeconds: auctionClosing, + } + + tests := []struct { + name string + arrivalTime time.Time + expectedBool bool + }{ + { + name: "Right before auction close window", + arrivalTime: initialTimestamp.Add(44 * time.Second), // 16 seconds left to the next round + expectedBool: false, + }, + { + name: "On the edge of auction close window", + arrivalTime: initialTimestamp.Add(45 * time.Second), // Exactly 15 seconds left to the next round + expectedBool: true, + }, + { + name: "Outside auction close window", + arrivalTime: initialTimestamp.Add(30 * time.Second), // 30 seconds left to the next round + expectedBool: false, + }, + { + name: "Exactly at the next round", + arrivalTime: initialTimestamp.Add(time.Minute), // At the start of the next round + expectedBool: false, + }, + { + name: "Just before the start of the next round", + arrivalTime: initialTimestamp.Add(time.Minute - 1*time.Second), // 1 second left to the next round + expectedBool: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := es.isWithinAuctionCloseWindow(tt.arrivalTime) + if actual != tt.expectedBool { + t.Errorf("isWithinAuctionCloseWindow(%v) = %v; want %v", tt.arrivalTime, actual, tt.expectedBool) + } + }) + } +} + func Benchmark_expressLaneService_validateExpressLaneTx(b *testing.B) { b.StopTimer() addr := crypto.PubkeyToAddress(testPriv.PublicKey) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index f80334f770..9b58acf549 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -543,6 +543,7 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx if !s.config().Timeboost.Enable { return errors.New("timeboost not enabled") } + arrivalTime := time.Now() auctioneerAddr := s.auctioneerAddr if auctioneerAddr == (common.Address{}) { return errors.New("invalid auctioneer address") @@ -555,11 +556,13 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx if sender != auctioneerAddr { return fmt.Errorf("sender %#x is not the auctioneer address %#x", sender, auctioneerAddr) } + if !s.expressLaneService.isWithinAuctionCloseWindow(arrivalTime) { + return fmt.Errorf("transaction arrival time not within auction closure window: %v", arrivalTime) + } txBytes, err := tx.MarshalBinary() if err != nil { return err } - // TODO: Check it is within the resolution window. s.timeboostLock.Lock() // Set it as a value that will be consumed first in `createBlock` if s.timeboostAuctionResolutionTx != nil { @@ -568,7 +571,7 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx } s.timeboostAuctionResolutionTx = tx s.timeboostLock.Unlock() - log.Info("Creating auction resolution tx") + log.Info("Prioritizing auction resolution transaction from auctioneer", "txHash", tx.Hash().Hex()) s.txQueue <- txQueueItem{ tx: tx, txSize: len(txBytes), From c5fc04853d02b45ddfebd1e7ec5126e028a26ab2 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 10:03:03 -0500 Subject: [PATCH 060/109] sequencer endpoint in cfg --- execution/gethexec/sequencer.go | 15 +++++++++------ system_tests/timeboost_test.go | 5 +++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 9b58acf549..fe987a19f6 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -86,13 +86,15 @@ type SequencerConfig struct { } type TimeboostConfig struct { - Enable bool `koanf:"enable"` - ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` + Enable bool `koanf:"enable"` + ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` + SequencerHTTPEndpoint string `koanf:"sequencer-http-endpoint"` } var DefaultTimeboostConfig = TimeboostConfig{ - Enable: false, - ExpressLaneAdvantage: time.Millisecond * 250, + Enable: false, + ExpressLaneAdvantage: time.Millisecond * 250, + SequencerHTTPEndpoint: "http://localhost:9567", } func (c *SequencerConfig) Validate() error { @@ -189,6 +191,7 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { func TimeboostAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultTimeboostConfig.Enable, "enable timeboost based on express lane auctions") 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") } type txQueueItem struct { @@ -1264,9 +1267,9 @@ func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr co if !s.config().Timeboost.Enable { log.Crit("Timeboost is not enabled, but StartExpressLane was called") } - rpcClient, err := rpc.DialContext(ctx, "http://localhost:9567") + rpcClient, err := rpc.DialContext(ctx, s.config().Timeboost.SequencerHTTPEndpoint) if err != nil { - log.Crit("Failed to connect to RPC client", "err", err) + log.Crit("Failed to connect to sequencer RPC client", "err", err) } seqClient := ethclient.NewClient(rpcClient) els, err := newExpressLaneService( diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 00072ba003..fc4cd13672 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -257,8 +257,9 @@ func setupExpressLaneAuction( builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() builderSeq.execConfig.Sequencer.Enable = true builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ - Enable: true, - ExpressLaneAdvantage: time.Second * 5, + Enable: true, + ExpressLaneAdvantage: time.Second * 5, + SequencerHTTPEndpoint: "http://localhost:9567", } cleanupSeq := builderSeq.Build(t) seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client From 91373de38783f82f2b4ff5899247050bf706239b Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 10:20:16 -0500 Subject: [PATCH 061/109] add method to bid --- cmd/autonomous-auctioneer/config.go | 4 +- cmd/autonomous-auctioneer/main.go | 2 +- timeboost/auctioneer.go | 19 +++------- timeboost/bid_validator.go | 14 +------ timeboost/bid_validator_test.go | 16 +++----- timeboost/bidder_client.go | 13 +------ timeboost/types.go | 59 ++++++++++------------------- 7 files changed, 38 insertions(+), 89 deletions(-) diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go index afbb513bf1..dba4684c97 100644 --- a/cmd/autonomous-auctioneer/config.go +++ b/cmd/autonomous-auctioneer/config.go @@ -71,11 +71,13 @@ var AutonomousAuctioneerConfigDefault = AutonomousAuctioneerConfig{ } func AuctioneerConfigAddOptions(f *flag.FlagSet) { + timeboost.AuctioneerServerConfigAddOptions("auctioneer-server", f) + timeboost.BidValidatorConfigAddOptions("bid-validator", f) + conf.PersistentConfigAddOptions("persistent", f) genericconf.ConfConfigAddOptions("conf", f) f.String("log-level", AutonomousAuctioneerConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") f.String("log-type", AutonomousAuctioneerConfigDefault.LogType, "log type (plaintext or json)") genericconf.FileLoggingConfigAddOptions("file-logging", f) - conf.PersistentConfigAddOptions("persistent", f) genericconf.HTTPConfigAddOptions("http", f) genericconf.WSConfigAddOptions("ws", f) genericconf.IPCConfigAddOptions("ipc", f) diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go index d4b0a71986..ab5caa3908 100644 --- a/cmd/autonomous-auctioneer/main.go +++ b/cmd/autonomous-auctioneer/main.go @@ -182,7 +182,7 @@ func mainImpl() int { func parseAuctioneerArgs(ctx context.Context, args []string) (*AutonomousAuctioneerConfig, error) { f := flag.NewFlagSet("", flag.ContinueOnError) - // ValidationNodeConfigAddOptions(f) + AuctioneerConfigAddOptions(f) k, err := confighelpers.BeginCommonParse(f, args) if err != nil { diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 4d1ac9b038..eb077d5ee5 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -50,18 +50,16 @@ type AuctioneerServerConfig struct { ConsumerConfig pubsub.ConsumerConfig `koanf:"consumer-config"` // Timeout on polling for existence of each redis stream. StreamTimeout time.Duration `koanf:"stream-timeout"` - StreamPrefix string `koanf:"stream-prefix"` Wallet genericconf.WalletConfig `koanf:"wallet"` SequencerEndpoint string `koanf:"sequencer-endpoint"` + SequencerJWTPath string `koanf:"sequencer-jwt-path"` AuctionContractAddress string `koanf:"auction-contract-address"` DbDirectory string `koanf:"db-directory"` - SequencerJWTPath string `koanf:"sequencer-jwt-path"` } var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ Enable: true, RedisURL: "", - StreamPrefix: "", ConsumerConfig: pubsub.DefaultConsumerConfig, StreamTimeout: 10 * time.Minute, } @@ -69,22 +67,20 @@ var DefaultAuctioneerServerConfig = AuctioneerServerConfig{ var TestAuctioneerServerConfig = AuctioneerServerConfig{ Enable: true, RedisURL: "", - StreamPrefix: "test-", ConsumerConfig: pubsub.TestConsumerConfig, StreamTimeout: time.Minute, } -func AuctioneerConfigAddOptions(prefix string, f *pflag.FlagSet) { +func AuctioneerServerConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultAuctioneerServerConfig.Enable, "enable auctioneer server") - pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) f.String(prefix+".redis-url", DefaultAuctioneerServerConfig.RedisURL, "url of redis server") - f.String(prefix+".stream-prefix", DefaultAuctioneerServerConfig.StreamPrefix, "prefix for stream name") - f.String(prefix+".db-directory", DefaultAuctioneerServerConfig.DbDirectory, "path to database directory for persisting validated bids in a sqlite file") + pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) f.Duration(prefix+".stream-timeout", DefaultAuctioneerServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") f.String(prefix+".sequencer-jwt-path", DefaultAuctioneerServerConfig.SequencerJWTPath, "sequencer jwt file path") f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") + f.String(prefix+".db-directory", DefaultAuctioneerServerConfig.DbDirectory, "path to database directory for persisting validated bids in a sqlite file") } // AuctioneerServer is a struct that represents an autonomous auctioneer. @@ -145,7 +141,7 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf } client, err := rpc.DialOptions(ctx, cfg.SequencerEndpoint, rpc.WithHTTPAuth(func(h http.Header) error { claims := jwt.MapClaims{ - // Required claim for engine API auth. "iat" stands for issued at + // Required claim for Ethereum RPC API auth. "iat" stands for issued at // and it must be a unix timestamp that is +/- 5 seconds from the current // timestamp at the moment the server verifies this value. "iat": time.Now().Unix(), @@ -237,7 +233,7 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { } if req == nil { // There's nothing in the queue. - return time.Second // TODO: Make this faster? + return time.Millisecond * 250 // TODO: Make this faster? } // Forward the message over a channel for processing elsewhere in // another thread, so as to not block this consumption thread. @@ -266,9 +262,6 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { } } }) - // TODO: Check sequencer health. - // a.StopWaiter.LaunchThread(func(ctx context.Context) { - // }) // Bid receiver thread. a.StopWaiter.LaunchThread(func(ctx context.Context) { diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index fe677003c8..33f7a7973c 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -48,8 +48,8 @@ var TestBidValidatorConfig = BidValidatorConfig{ func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultBidValidatorConfig.Enable, "enable bid validator") - pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.String(prefix+".redis-url", DefaultBidValidatorConfig.RedisURL, "url of redis server") + pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") } @@ -269,17 +269,7 @@ func (bv *BidValidator) validateBid( } // Validate the signature. - packedBidBytes, err := encodeBidValues( - domainValue, - bid.ChainId, - bid.AuctionContractAddress, - bid.Round, - bid.Amount, - bid.ExpressLaneController, - ) - if err != nil { - return nil, ErrMalformedData - } + packedBidBytes := bid.ToMessageBytes() if len(bid.Signature) != 65 { return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") } diff --git a/timeboost/bid_validator_test.go b/timeboost/bid_validator_test.go index 25481ac32c..24552fc150 100644 --- a/timeboost/bid_validator_test.go +++ b/timeboost/bid_validator_test.go @@ -152,10 +152,7 @@ func TestBidValidator_validateBid_perRoundBidLimitReached(t *testing.T) { Amount: big.NewInt(3), Signature: []byte{'a'}, } - bidValues, err := encodeBidValues(domainValue, bid.ChainId, bid.AuctionContractAddress, bid.Round, bid.Amount, bid.ExpressLaneController) - require.NoError(t, err) - - signature, err := buildSignature(privateKey, bidValues) + signature, err := buildSignature(privateKey, bid.ToMessageBytes()) require.NoError(t, err) bid.Signature = signature @@ -180,7 +177,7 @@ func buildSignature(privateKey *ecdsa.PrivateKey, data []byte) ([]byte, error) { func buildValidBid(t *testing.T, auctionContractAddr common.Address) *Bid { privateKey, err := crypto.GenerateKey() require.NoError(t, err) - b := &Bid{ + bid := &Bid{ ExpressLaneController: common.Address{'b'}, AuctionContractAddress: auctionContractAddr, ChainId: big.NewInt(1), @@ -189,13 +186,10 @@ func buildValidBid(t *testing.T, auctionContractAddr common.Address) *Bid { Signature: []byte{'a'}, } - bidValues, err := encodeBidValues(domainValue, b.ChainId, b.AuctionContractAddress, b.Round, b.Amount, b.ExpressLaneController) + signature, err := buildSignature(privateKey, bid.ToMessageBytes()) require.NoError(t, err) - signature, err := buildSignature(privateKey, bidValues) - require.NoError(t, err) - - b.Signature = signature + bid.Signature = signature - return b + return bid } diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 02f603b545..81f212fd6f 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -114,18 +114,7 @@ func (bd *BidderClient) Bid( Amount: amount, Signature: nil, } - packedBidBytes, err := encodeBidValues( - bd.domainValue, - newBid.ChainId, - bd.auctionContractAddress, - newBid.Round, - amount, - expressLaneController, - ) - if err != nil { - return nil, err - } - sig, err := sign(packedBidBytes, bd.privKey) + sig, err := sign(newBid.ToMessageBytes(), bd.privKey) if err != nil { return nil, err } diff --git a/timeboost/types.go b/timeboost/types.go index 360cf4357d..b151442402 100644 --- a/timeboost/types.go +++ b/timeboost/types.go @@ -37,6 +37,21 @@ func (b *Bid) ToJson() *JsonBid { } } +func (b *Bid) ToMessageBytes() []byte { + buf := new(bytes.Buffer) + // Encode uint256 values - each occupies 32 bytes + buf.Write(domainValue) + buf.Write(padBigInt(b.ChainId)) + buf.Write(b.AuctionContractAddress[:]) + roundBuf := make([]byte, 8) + binary.BigEndian.PutUint64(roundBuf, b.Round) + buf.Write(roundBuf) + buf.Write(padBigInt(b.Amount)) + buf.Write(b.ExpressLaneController[:]) + + return buf.Bytes() +} + type JsonBid struct { ChainId *hexutil.Big `json:"chainId"` ExpressLaneController common.Address `json:"expressLaneController"` @@ -159,35 +174,17 @@ func (els *ExpressLaneSubmission) ToJson() (*JsonExpressLaneSubmission, error) { } func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { - return encodeExpressLaneSubmission( - domainValue, - els.ChainId, - els.Sequence, - els.AuctionContractAddress, - els.Round, - els.Transaction, - ) -} - -func encodeExpressLaneSubmission( - domainValue []byte, - chainId *big.Int, - sequence uint64, - auctionContractAddress common.Address, - round uint64, - tx *types.Transaction, -) ([]byte, error) { buf := new(bytes.Buffer) buf.Write(domainValue) - buf.Write(padBigInt(chainId)) + buf.Write(padBigInt(els.ChainId)) seqBuf := make([]byte, 8) - binary.BigEndian.PutUint64(seqBuf, sequence) + binary.BigEndian.PutUint64(seqBuf, els.Sequence) buf.Write(seqBuf) - buf.Write(auctionContractAddress[:]) + buf.Write(els.AuctionContractAddress[:]) roundBuf := make([]byte, 8) - binary.BigEndian.PutUint64(roundBuf, round) + binary.BigEndian.PutUint64(roundBuf, els.Round) buf.Write(roundBuf) - rlpTx, err := tx.MarshalBinary() + rlpTx, err := els.Transaction.MarshalBinary() if err != nil { return nil, err } @@ -207,19 +204,3 @@ func padBigInt(bi *big.Int) []byte { padded = append(padded, bb...) return padded } - -func encodeBidValues(domainValue []byte, chainId *big.Int, auctionContractAddress common.Address, round uint64, amount *big.Int, expressLaneController common.Address) ([]byte, error) { - buf := new(bytes.Buffer) - - // Encode uint256 values - each occupies 32 bytes - buf.Write(domainValue) - buf.Write(padBigInt(chainId)) - buf.Write(auctionContractAddress[:]) - roundBuf := make([]byte, 8) - binary.BigEndian.PutUint64(roundBuf, round) - buf.Write(roundBuf) - buf.Write(padBigInt(amount)) - buf.Write(expressLaneController[:]) - - return buf.Bytes(), nil -} From a9b2bdd4f4a33c5ac7cc889bd8391385da0b3fe3 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 10:47:28 -0500 Subject: [PATCH 062/109] productionize bidder client --- timeboost/auctioneer_test.go | 6 +-- timeboost/bid_cache_test.go | 2 +- timeboost/bid_validator.go | 3 +- timeboost/bidder_client.go | 96 +++++++++++++++++++++++------------- timeboost/setup_test.go | 24 ++++----- 5 files changed, 79 insertions(+), 52 deletions(-) diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 681a201fcd..ba6315bf8c 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -112,9 +112,9 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { aliceAddr := testSetup.accounts[1].txOpts.From bobAddr := testSetup.accounts[2].txOpts.From charlieAddr := testSetup.accounts[3].txOpts.From - alice := setupBidderClient(t, ctx, "alice", testSetup.accounts[1], testSetup, bidValidators[0].stack.HTTPEndpoint()) - bob := setupBidderClient(t, ctx, "bob", testSetup.accounts[2], testSetup, bidValidators[1].stack.HTTPEndpoint()) - charlie := setupBidderClient(t, ctx, "charlie", testSetup.accounts[3], testSetup, bidValidators[2].stack.HTTPEndpoint()) + alice := setupBidderClient(t, ctx, testSetup.accounts[1], testSetup, bidValidators[0].stack.HTTPEndpoint()) + bob := setupBidderClient(t, ctx, testSetup.accounts[2], testSetup, bidValidators[1].stack.HTTPEndpoint()) + charlie := setupBidderClient(t, ctx, testSetup.accounts[3], testSetup, bidValidators[2].stack.HTTPEndpoint()) require.NoError(t, alice.Deposit(ctx, big.NewInt(20))) require.NoError(t, bob.Deposit(ctx, big.NewInt(20))) require.NoError(t, charlie.Deposit(ctx, big.NewInt(20))) diff --git a/timeboost/bid_cache_test.go b/timeboost/bid_cache_test.go index 79c89d8bcd..62c249c539 100644 --- a/timeboost/bid_cache_test.go +++ b/timeboost/bid_cache_test.go @@ -148,7 +148,7 @@ func BenchmarkBidValidation(b *testing.B) { redisURL := redisutil.CreateTestRedis(ctx, b) testSetup := setupAuctionTest(b, ctx) bv, endpoint := setupBidValidator(b, ctx, redisURL, testSetup) - bc := setupBidderClient(b, ctx, "alice", testSetup.accounts[0], testSetup, endpoint) + bc := setupBidderClient(b, ctx, testSetup.accounts[0], testSetup, endpoint) require.NoError(b, bc.Deposit(ctx, big.NewInt(5))) // Form a valid bid. diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 33f7a7973c..58718e7b5c 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -274,13 +274,12 @@ func (bv *BidValidator) validateBid( return nil, errors.Wrap(ErrMalformedData, "signature length is not 65") } // Recover the public key. - prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(packedBidBytes))), packedBidBytes...)) sigItem := make([]byte, len(bid.Signature)) copy(sigItem, bid.Signature) if sigItem[len(sigItem)-1] >= 27 { sigItem[len(sigItem)-1] -= 27 } - pubkey, err := crypto.SigToPub(prefixed, sigItem) + pubkey, err := crypto.SigToPub(buildEthereumSignedMessage(packedBidBytes), sigItem) if err != nil { return nil, ErrMalformedData } diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 81f212fd6f..8ca126d961 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -2,33 +2,59 @@ package timeboost import ( "context" - "crypto/ecdsa" "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/cmd/util" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/util/containers" + "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" + "github.com/spf13/pflag" ) +type BidderClientConfigFetcher func() *BidderClientConfig + +type BidderClientConfig struct { + Wallet genericconf.WalletConfig `koanf:"wallet"` + ArbitrumNodeEndpoint string `koanf:"arbitrum-node-endpoint"` + BidValidatorEndpoint string `koanf:"bid-validator-endpoint"` + AuctionContractAddress string `koanf:"auction-contract-address"` +} + +var DefaultBidderClientConfig = BidderClientConfig{ + ArbitrumNodeEndpoint: "http://localhost:9567", + BidValidatorEndpoint: "http://localhost:9372", +} + +var TestBidderClientConfig = BidderClientConfig{ + ArbitrumNodeEndpoint: "http://localhost:9567", + BidValidatorEndpoint: "http://localhost:9372", +} + +func BidderClientConfigAddOptions(prefix string, f *pflag.FlagSet) { + genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") + f.String(prefix+".arbitrum-node-endpoint", DefaultBidderClientConfig.ArbitrumNodeEndpoint, "arbitrum node RPC http endpoint") + f.String(prefix+".bid-validator-endpoint", DefaultBidderClientConfig.BidValidatorEndpoint, "bid validator http endpoint") + f.String(prefix+".auction-contract-address", DefaultBidderClientConfig.AuctionContractAddress, "express lane auction contract address") +} + type BidderClient struct { stopwaiter.StopWaiter chainId *big.Int - name string auctionContractAddress common.Address txOpts *bind.TransactOpts client *ethclient.Client - privKey *ecdsa.PrivateKey + signer signature.DataSignerFunc auctionContract *express_lane_auctiongen.ExpressLaneAuction auctioneerClient *rpc.Client initialRoundTimestamp time.Time @@ -36,48 +62,53 @@ type BidderClient struct { domainValue []byte } -// TODO: Provide a safer option. -type Wallet struct { - TxOpts *bind.TransactOpts - PrivKey *ecdsa.PrivateKey -} - func NewBidderClient( ctx context.Context, - name string, - wallet *Wallet, - client *ethclient.Client, - auctionContractAddress common.Address, - auctioneerEndpoint string, + configFetcher BidderClientConfigFetcher, ) (*BidderClient, error) { - chainId, err := client.ChainID(ctx) + cfg := configFetcher() + if cfg.AuctionContractAddress == "" { + return nil, fmt.Errorf("auction contract address cannot be empty") + } + auctionContractAddr := common.HexToAddress(cfg.AuctionContractAddress) + client, err := rpc.DialContext(ctx, cfg.ArbitrumNodeEndpoint) if err != nil { return nil, err } - auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddress, client) + arbClient := ethclient.NewClient(client) + chainId, err := arbClient.ChainID(ctx) if err != nil { return nil, err } - roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, arbClient) + if err != nil { + return nil, err + } + roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{ + Context: ctx, + }) if err != nil { return nil, err } initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + txOpts, signer, err := util.OpenWallet("bidder-client", &cfg.Wallet, chainId) + if err != nil { + return nil, errors.Wrap(err, "opening wallet") + } - auctioneerClient, err := rpc.DialContext(ctx, auctioneerEndpoint) + bidValidatorClient, err := rpc.DialContext(ctx, cfg.BidValidatorEndpoint) if err != nil { return nil, err } return &BidderClient{ chainId: chainId, - name: name, - auctionContractAddress: auctionContractAddress, - client: client, - txOpts: wallet.TxOpts, - privKey: wallet.PrivKey, + auctionContractAddress: auctionContractAddr, + client: arbClient, + txOpts: txOpts, + signer: signer, auctionContract: auctionContract, - auctioneerClient: auctioneerClient, + auctioneerClient: bidValidatorClient, initialRoundTimestamp: initialTimestamp, roundDuration: roundDuration, domainValue: domainValue, @@ -114,10 +145,11 @@ func (bd *BidderClient) Bid( Amount: amount, Signature: nil, } - sig, err := sign(newBid.ToMessageBytes(), bd.privKey) + sig, err := bd.signer(buildEthereumSignedMessage(newBid.ToMessageBytes())) if err != nil { return nil, err } + sig[64] += 27 newBid.Signature = sig promise := bd.submitBid(newBid) if _, err := promise.Await(ctx); err != nil { @@ -133,12 +165,6 @@ func (bd *BidderClient) submitBid(bid *Bid) containers.PromiseInterface[struct{} }) } -func sign(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { - prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) - sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) - if err != nil { - return nil, err - } - sig[64] += 27 - return sig, nil +func buildEthereumSignedMessage(msg []byte) []byte { + return crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(msg))), msg...)) } diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index b47aab5b0f..71c901d51d 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -13,10 +13,9 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient/simulated" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/timeboost/bindings" @@ -156,18 +155,21 @@ func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { } func setupBidderClient( - t testing.TB, ctx context.Context, name string, account *testAccount, testSetup *auctionSetup, auctioneerEndpoint string, + t testing.TB, ctx context.Context, account *testAccount, testSetup *auctionSetup, bidValidatorEndpoint string, ) *BidderClient { - rpcClient, err := rpc.Dial(testSetup.endpoint) - require.NoError(t, err) - client := ethclient.NewClient(rpcClient) + cfgFetcher := func() *BidderClientConfig { + return &BidderClientConfig{ + AuctionContractAddress: testSetup.expressLaneAuctionAddr.Hex(), + BidValidatorEndpoint: bidValidatorEndpoint, + ArbitrumNodeEndpoint: testSetup.endpoint, + Wallet: genericconf.WalletConfig{ + PrivateKey: fmt.Sprintf("00%x", account.privKey.D.Bytes()), + }, + } + } bc, err := NewBidderClient( ctx, - name, - &Wallet{TxOpts: account.txOpts, PrivKey: account.privKey}, - client, - testSetup.expressLaneAuctionAddr, - auctioneerEndpoint, + cfgFetcher, ) require.NoError(t, err) bc.Start(ctx) From 4ed60df4bf7fbbf3b59f0ba9275362e15939fcc1 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 10:49:55 -0500 Subject: [PATCH 063/109] tests passing --- timeboost/auctioneer.go | 2 +- timeboost/setup_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index eb077d5ee5..35a75ebc69 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -233,7 +233,7 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { } if req == nil { // There's nothing in the queue. - return time.Millisecond * 250 // TODO: Make this faster? + return time.Millisecond * 250 } // Forward the message over a channel for processing elsewhere in // another thread, so as to not block this consumption thread. diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 71c901d51d..9a603d4d7b 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -163,7 +163,7 @@ func setupBidderClient( BidValidatorEndpoint: bidValidatorEndpoint, ArbitrumNodeEndpoint: testSetup.endpoint, Wallet: genericconf.WalletConfig{ - PrivateKey: fmt.Sprintf("00%x", account.privKey.D.Bytes()), + PrivateKey: fmt.Sprintf("%x", account.privKey.D.Bytes()), }, } } From b8b448305dcd49961de7ac4bdd3042a252804614 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 10:53:02 -0500 Subject: [PATCH 064/109] productionize bidder client --- system_tests/timeboost_test.go | 38 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index fc4cd13672..6835b191c9 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -460,30 +460,36 @@ func setupExpressLaneAuction( // Set up a bidder client for Alice and Bob. alicePriv := seqInfo.Accounts["Alice"].PrivateKey + cfgFetcherAlice := func() *timeboost.BidderClientConfig { + return &timeboost.BidderClientConfig{ + AuctionContractAddress: proxyAddr.Hex(), + BidValidatorEndpoint: "http://localhost:9372", + ArbitrumNodeEndpoint: "http://localhost:9567", + Wallet: genericconf.WalletConfig{ + PrivateKey: fmt.Sprintf("00%x", alicePriv.D.Bytes()), + }, + } + } alice, err := timeboost.NewBidderClient( ctx, - "alice", - &timeboost.Wallet{ - TxOpts: &aliceOpts, - PrivKey: alicePriv, - }, - seqClient, - proxyAddr, - "http://localhost:9372", + cfgFetcherAlice, ) Require(t, err) bobPriv := seqInfo.Accounts["Bob"].PrivateKey + cfgFetcherBob := func() *timeboost.BidderClientConfig { + return &timeboost.BidderClientConfig{ + AuctionContractAddress: proxyAddr.Hex(), + BidValidatorEndpoint: "http://localhost:9372", + ArbitrumNodeEndpoint: "http://localhost:9567", + Wallet: genericconf.WalletConfig{ + PrivateKey: fmt.Sprintf("00%x", bobPriv.D.Bytes()), + }, + } + } bob, err := timeboost.NewBidderClient( ctx, - "bob", - &timeboost.Wallet{ - TxOpts: &bobOpts, - PrivKey: bobPriv, - }, - seqClient, - proxyAddr, - "http://localhost:9372", + cfgFetcherBob, ) Require(t, err) From 119e1bb946925f4a8c3fca52981098c2e59bcd98 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 11:15:12 -0500 Subject: [PATCH 065/109] system test pass --- system_tests/timeboost_test.go | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 6835b191c9..e9562ab76a 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -71,14 +71,31 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing // In the end, Bob's txs should be ordered before Alice's during the round. var wg sync.WaitGroup wg.Add(2) - aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + ownerAddr := seqInfo.GetAddress("Owner") + aliceData := &types.DynamicFeeTx{ + To: &ownerAddr, + Gas: seqInfo.TransferGas, + GasFeeCap: new(big.Int).Set(seqInfo.GasPrice), + Value: big.NewInt(1e12), + Nonce: 3, + Data: nil, + } + aliceTx := seqInfo.SignTxAs("Alice", aliceData) go func(w *sync.WaitGroup) { defer w.Done() err = seqClient.SendTransaction(ctx, aliceTx) Require(t, err) }(&wg) - bobBoostableTx := seqInfo.PrepareTx("Bob", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + bobData := &types.DynamicFeeTx{ + To: &ownerAddr, + Gas: seqInfo.TransferGas, + GasFeeCap: new(big.Int).Set(seqInfo.GasPrice), + Value: big.NewInt(1e12), + Nonce: 3, + Data: nil, + } + bobBoostableTx := seqInfo.SignTxAs("Bob", bobData) go func(w *sync.WaitGroup) { defer w.Done() time.Sleep(time.Millisecond * 10) @@ -165,14 +182,22 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test // These tx payloads are sent with nonces out of order, and those with nonces too high should fail. var wg sync.WaitGroup wg.Add(2) - aliceTx := seqInfo.PrepareTx("Alice", "Owner", seqInfo.TransferGas, big.NewInt(1e12), nil) + ownerAddr := seqInfo.GetAddress("Owner") + aliceData := &types.DynamicFeeTx{ + To: &ownerAddr, + Gas: seqInfo.TransferGas, + GasFeeCap: new(big.Int).Set(seqInfo.GasPrice), + Value: big.NewInt(1e12), + Nonce: 3, + Data: nil, + } + aliceTx := seqInfo.SignTxAs("Alice", aliceData) go func(w *sync.WaitGroup) { defer w.Done() err = seqClient.SendTransaction(ctx, aliceTx) Require(t, err) }(&wg) - ownerAddr := seqInfo.GetAddress("Owner") txData := &types.DynamicFeeTx{ To: &ownerAddr, Gas: seqInfo.TransferGas, From 203a8ad08d0cf01331f3ea82c162afbaeb48497c Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 8 Aug 2024 12:44:54 -0500 Subject: [PATCH 066/109] edit --- staker/staker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staker/staker.go b/staker/staker.go index 3eb941c6dd..79ed7e89a4 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -404,7 +404,7 @@ func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, if err != nil { return err } - _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot, nodeHash) + _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot) return err } From e7b0fa3ce95e08a388155c06244b2f14b4eccef8 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Fri, 9 Aug 2024 08:32:46 -0500 Subject: [PATCH 067/109] move express lane client to system test --- system_tests/timeboost_test.go | 99 +++++++++++++++++++++++++++- timeboost/express_lane_client.go | 108 ------------------------------- 2 files changed, 97 insertions(+), 110 deletions(-) delete mode 100644 timeboost/express_lane_client.go diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index e9562ab76a..6a5297745a 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -2,6 +2,7 @@ package arbtest import ( "context" + "crypto/ecdsa" "fmt" "math/big" "os" @@ -12,7 +13,11 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -26,7 +31,9 @@ import ( "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/stretchr/testify/require" ) @@ -56,7 +63,7 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing // Prepare a client that can submit txs to the sequencer via the express lane. seqDial, err := rpc.Dial("http://localhost:9567") Require(t, err) - expressLaneClient := timeboost.NewExpressLaneClient( + expressLaneClient := newExpressLaneClient( bobPriv, chainId, time.Unix(int64(info.OffsetTimestamp), 0), @@ -163,7 +170,7 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test // Prepare a client that can submit txs to the sequencer via the express lane. seqDial, err := rpc.Dial("http://localhost:9567") Require(t, err) - expressLaneClient := timeboost.NewExpressLaneClient( + expressLaneClient := newExpressLaneClient( bobPriv, chainId, time.Unix(int64(info.OffsetTimestamp), 0), @@ -631,3 +638,91 @@ func awaitAuctionResolved( } } } + +type expressLaneClient struct { + stopwaiter.StopWaiter + sync.Mutex + privKey *ecdsa.PrivateKey + chainId *big.Int + initialRoundTimestamp time.Time + roundDuration time.Duration + auctionContractAddr common.Address + client *rpc.Client + sequence uint64 +} + +func newExpressLaneClient( + privKey *ecdsa.PrivateKey, + chainId *big.Int, + initialRoundTimestamp time.Time, + roundDuration time.Duration, + auctionContractAddr common.Address, + client *rpc.Client, +) *expressLaneClient { + return &expressLaneClient{ + privKey: privKey, + chainId: chainId, + initialRoundTimestamp: initialRoundTimestamp, + roundDuration: roundDuration, + auctionContractAddr: auctionContractAddr, + client: client, + sequence: 0, + } +} + +func (elc *expressLaneClient) Start(ctxIn context.Context) { + elc.StopWaiter.Start(ctxIn, elc) +} + +func (elc *expressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) error { + elc.Lock() + defer elc.Unlock() + encodedTx, err := transaction.MarshalBinary() + if err != nil { + return err + } + msg := &timeboost.JsonExpressLaneSubmission{ + ChainId: (*hexutil.Big)(elc.chainId), + Round: hexutil.Uint64(timeboost.CurrentRound(elc.initialRoundTimestamp, elc.roundDuration)), + AuctionContractAddress: elc.auctionContractAddr, + Transaction: encodedTx, + Sequence: hexutil.Uint64(elc.sequence), + Signature: hexutil.Bytes{}, + } + msgGo, err := timeboost.JsonSubmissionToGo(msg) + if err != nil { + return err + } + signingMsg, err := msgGo.ToMessageBytes() + if err != nil { + return err + } + signature, err := signSubmission(signingMsg, elc.privKey) + if err != nil { + return err + } + msg.Signature = signature + promise := elc.sendExpressLaneRPC(msg) + if _, err := promise.Await(ctx); err != nil { + return err + } + elc.sequence += 1 + return nil +} + +func (elc *expressLaneClient) sendExpressLaneRPC(msg *timeboost.JsonExpressLaneSubmission) containers.PromiseInterface[struct{}] { + return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { + err := elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) + return struct{}{}, err + }) +} + +func signSubmission(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { + prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) + sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) + if err != nil { + return nil, err + } + sig[64] += 27 + return sig, nil +} diff --git a/timeboost/express_lane_client.go b/timeboost/express_lane_client.go deleted file mode 100644 index e70ff8bd9e..0000000000 --- a/timeboost/express_lane_client.go +++ /dev/null @@ -1,108 +0,0 @@ -package timeboost - -import ( - "context" - "crypto/ecdsa" - "fmt" - "math/big" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/secp256k1" - "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/nitro/util/containers" - "github.com/offchainlabs/nitro/util/stopwaiter" -) - -type ExpressLaneClient struct { - stopwaiter.StopWaiter - sync.Mutex - privKey *ecdsa.PrivateKey - chainId *big.Int - initialRoundTimestamp time.Time - roundDuration time.Duration - auctionContractAddr common.Address - client *rpc.Client - sequence uint64 -} - -func NewExpressLaneClient( - privKey *ecdsa.PrivateKey, - chainId *big.Int, - initialRoundTimestamp time.Time, - roundDuration time.Duration, - auctionContractAddr common.Address, - client *rpc.Client, -) *ExpressLaneClient { - return &ExpressLaneClient{ - privKey: privKey, - chainId: chainId, - initialRoundTimestamp: initialRoundTimestamp, - roundDuration: roundDuration, - auctionContractAddr: auctionContractAddr, - client: client, - sequence: 0, - } -} - -func (elc *ExpressLaneClient) Start(ctxIn context.Context) { - elc.StopWaiter.Start(ctxIn, elc) -} - -func (elc *ExpressLaneClient) SendTransaction(ctx context.Context, transaction *types.Transaction) error { - elc.Lock() - defer elc.Unlock() - encodedTx, err := transaction.MarshalBinary() - if err != nil { - return err - } - msg := &JsonExpressLaneSubmission{ - ChainId: (*hexutil.Big)(elc.chainId), - Round: hexutil.Uint64(CurrentRound(elc.initialRoundTimestamp, elc.roundDuration)), - AuctionContractAddress: elc.auctionContractAddr, - Transaction: encodedTx, - Sequence: hexutil.Uint64(elc.sequence), - Signature: hexutil.Bytes{}, - } - msgGo, err := JsonSubmissionToGo(msg) - if err != nil { - return err - } - signingMsg, err := msgGo.ToMessageBytes() - if err != nil { - return err - } - signature, err := signSubmission(signingMsg, elc.privKey) - if err != nil { - return err - } - msg.Signature = signature - promise := elc.sendExpressLaneRPC(msg) - if _, err := promise.Await(ctx); err != nil { - return err - } - elc.sequence += 1 - return nil -} - -func (elc *ExpressLaneClient) sendExpressLaneRPC(msg *JsonExpressLaneSubmission) containers.PromiseInterface[struct{}] { - return stopwaiter.LaunchPromiseThread(elc, func(ctx context.Context) (struct{}, error) { - err := elc.client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", msg) - return struct{}{}, err - }) -} - -func signSubmission(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { - prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) - sig, err := secp256k1.Sign(prefixed, math.PaddedBigBytes(key.D, 32)) - if err != nil { - return nil, err - } - sig[64] += 27 - return sig, nil -} From 7396c626e2dc700c9a201e01fc6acc6227926606 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Mon, 12 Aug 2024 20:50:52 +0000 Subject: [PATCH 068/109] Auctioneer metrics: bids and value (part 1) --- timeboost/auctioneer.go | 11 +++++++++++ timeboost/bid_validator.go | 2 ++ 2 files changed, 13 insertions(+) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 35a75ebc69..a4eb272ca9 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rpc" "github.com/golang-jwt/jwt/v4" "github.com/offchainlabs/nitro/cmd/genericconf" @@ -36,6 +37,13 @@ const ( validatedBidsRedisStream = "validated_bids" ) +var ( + receivedBidsCounter = metrics.NewRegisteredCounter("arb/auctioneer/bids/received", nil) + validatedBidsCounter = metrics.NewRegisteredCounter("arb/auctioneer/bids/validated", nil) + FirstBidValueGauge = metrics.NewRegisteredGauge("arb/auctioneer/bids/firstbidvalue", nil) + SecondBidValueGauge = metrics.NewRegisteredGauge("arb/auctioneer/bids/secondbidvalue", nil) +) + func init() { hash := sha3.NewLegacyKeccak256() hash.Write([]byte("TIMEBOOST_BID")) @@ -325,6 +333,8 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { Signature: second.Signature, }, ) + FirstBidValueGauge.Update(int64(first.Amount.Int64())) + SecondBidValueGauge.Update(int64(second.Amount.Int64())) log.Info("Resolving auction with two bids", "round", upcomingRound) case first != nil: // Single bid is present @@ -336,6 +346,7 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { Signature: first.Signature, }, ) + FirstBidValueGauge.Update(int64(first.Amount.Int64())) log.Info("Resolving auction with single bid", "round", upcomingRound) case second == nil: // No bids received diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 58718e7b5c..abce190861 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -197,6 +197,7 @@ type BidValidatorAPI struct { func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { start := time.Now() + receivedBidsCounter.Inc(1) validatedBid, err := bv.validateBid( &Bid{ ChainId: bid.ChainId.ToInt(), @@ -212,6 +213,7 @@ func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { if err != nil { return err } + validatedBidsCounter.Inc(1) log.Info("Validated bid", "bidder", validatedBid.Bidder.Hex(), "amount", validatedBid.Amount.String(), "round", validatedBid.Round, "elapsed", time.Since(start)) _, err = bv.producer.Produce(ctx, validatedBid) if err != nil { From c315dd875c9bb55b8e1c7bcbf13e86231ffb6387 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 10:34:10 -0500 Subject: [PATCH 069/109] tidy --- go.mod | 2 -- go.sum | 1 - 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index d2aca61aea..51bdaf06ae 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.2.1 github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 - github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/btree v1.1.2 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.3.0 @@ -170,7 +169,6 @@ require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.22.0 - golang.org/x/sync v0.5.0 golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index 090c308751..49f31efc0f 100644 --- a/go.sum +++ b/go.sum @@ -305,7 +305,6 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= From d40cee9ac9f8fac65c212203699c1e3e6d4d3642 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 10:35:06 -0500 Subject: [PATCH 070/109] Update timeboost/ticker.go Co-authored-by: Chris Buckland --- timeboost/ticker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timeboost/ticker.go b/timeboost/ticker.go index f04ff82a46..edd1a20c5c 100644 --- a/timeboost/ticker.go +++ b/timeboost/ticker.go @@ -53,7 +53,7 @@ func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) return uint64(time.Since(initialRoundTimestamp) / roundDuration) } -// auctionClosed returns the time since auction was closed and whether the auction is closed. +// auctionClosed returns the time into the current round and whether the auction for this round is closed. func auctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { if roundDuration == 0 { return 0, true From 7b63dc9f82182bcb02231e31ff751202aa2a1b4e Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 11:07:22 -0500 Subject: [PATCH 071/109] condition --- timeboost/ticker.go | 2 +- timeboost/ticker_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 timeboost/ticker_test.go diff --git a/timeboost/ticker.go b/timeboost/ticker.go index edd1a20c5c..c47265d58f 100644 --- a/timeboost/ticker.go +++ b/timeboost/ticker.go @@ -59,5 +59,5 @@ func auctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, return 0, true } d := time.Since(initialRoundTimestamp) % roundDuration - return d, d > auctionClosingDuration + return d, d >= roundDuration-auctionClosingDuration } diff --git a/timeboost/ticker_test.go b/timeboost/ticker_test.go new file mode 100644 index 0000000000..db853b7a88 --- /dev/null +++ b/timeboost/ticker_test.go @@ -0,0 +1,40 @@ +package timeboost + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func Test_auctionClosed(t *testing.T) { + t.Parallel() + roundDuration := time.Minute + auctionClosingDuration := time.Second * 15 + now := time.Now() + waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + nextMinute := now.Add(waitTime) + <-time.After(waitTime) + + timeIntoRound, isClosed := auctionClosed(nextMinute, roundDuration, auctionClosingDuration) + + // We should not have closed the round yet, and the time into the round should be less than a second. + require.False(t, isClosed) + require.True(t, timeIntoRound < time.Second) + + // Wait right before auction closure (before the 45 second mark). + now = time.Now() + waitTime = (roundDuration - auctionClosingDuration) - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) + secondBeforeClosing := waitTime - time.Second + <-time.After(secondBeforeClosing) + + timeIntoRound, isClosed = auctionClosed(nextMinute, roundDuration, auctionClosingDuration) + require.False(t, isClosed) + require.True(t, timeIntoRound < (roundDuration-auctionClosingDuration)) + + // Wait a second more and the auction should be closed. + <-time.After(time.Second) + timeIntoRound, isClosed = auctionClosed(nextMinute, roundDuration, auctionClosingDuration) + require.True(t, isClosed) + require.True(t, timeIntoRound >= (roundDuration-auctionClosingDuration)) +} From 1aaf4da536d567b9014c474d05aa08fb701e10ef Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 11:39:25 -0500 Subject: [PATCH 072/109] limit bids --- execution/gethexec/sequencer.go | 6 ++++++ timeboost/auctioneer_test.go | 4 ++-- timeboost/bid_validator.go | 2 +- timeboost/ticker.go | 10 +++++----- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 553aaedf5b..c43b6f1866 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -530,6 +530,12 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx if auctioneerAddr == (common.Address{}) { return errors.New("invalid auctioneer address") } + if tx.To() == nil { + return errors.New("transaction has no recipient") + } + if *tx.To() != s.expressLaneService.auctionContractAddr { + return errors.New("transaction recipient is not the auction contract") + } signer := types.LatestSigner(s.execEngine.bc.Config()) sender, err := types.Sender(signer, tx) if err != nil { diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index ba6315bf8c..c1333bfe6b 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -149,8 +149,8 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { // We also verify the top two bids are those we expect. require.Equal(t, 3, len(am.bidCache.bidsByExpressLaneControllerAddr)) result := am.bidCache.topTwoBids() - require.Equal(t, result.firstPlace.Amount, big.NewInt(7)) + require.Equal(t, result.firstPlace.Amount, big.NewInt(6)) require.Equal(t, result.firstPlace.Bidder, charlieAddr) - require.Equal(t, result.secondPlace.Amount, big.NewInt(6)) + require.Equal(t, result.secondPlace.Amount, big.NewInt(5)) require.Equal(t, result.secondPlace.Bidder, bobAddr) } diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index abce190861..5c73385f75 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -295,7 +295,7 @@ func (bv *BidValidator) validateBid( if !ok { bv.bidsPerSenderInRound[bidder] = 1 } - if numBids > bv.maxBidsPerSenderInRound { + if numBids >= bv.maxBidsPerSenderInRound { bv.Unlock() return nil, errors.Wrapf(ErrTooManyBids, "bidder %s has already sent the maximum allowed bids = %d in this round", bidder.Hex(), numBids) } diff --git a/timeboost/ticker.go b/timeboost/ticker.go index c47265d58f..ca898a4088 100644 --- a/timeboost/ticker.go +++ b/timeboost/ticker.go @@ -23,14 +23,14 @@ func newAuctionCloseTicker(roundDuration, auctionClosingDuration time.Duration) func (t *auctionCloseTicker) start() { for { now := time.Now() - // Calculate the start of the next minute - startOfNextMinute := now.Truncate(time.Minute).Add(time.Minute) - // Subtract 15 seconds to get the tick time - nextTickTime := startOfNextMinute.Add(-15 * time.Second) + // Calculate the start of the next round + startOfNextMinute := now.Truncate(t.roundDuration).Add(t.roundDuration) + // Subtract AUCTION_CLOSING_SECONDS seconds to get the tick time + nextTickTime := startOfNextMinute.Add(-t.auctionClosingDuration) // Ensure we are not setting a past tick time if nextTickTime.Before(now) { // If the calculated tick time is in the past, move to the next interval - nextTickTime = nextTickTime.Add(time.Minute) + nextTickTime = nextTickTime.Add(t.roundDuration) } // Calculate how long to wait until the next tick waitTime := nextTickTime.Sub(now) From 6705b06c23d09ff4d2667f0930bfd0b5b15ee6db Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 11:46:21 -0500 Subject: [PATCH 073/109] last second leeway for bid processing --- timeboost/auctioneer.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index a4eb272ca9..58161f9b45 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -298,6 +298,10 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { return case auctionClosingTime := <-ticker.c: log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", a.bidCache.size()) + // Wait for a second, just to give some leeway for latency of bids received last minute. + // Process any remaining bids that may exist in the bids receiver channel before we close + // within this remaining second. + a.processRemainingBidsBeforeResolution(ctx, time.Second) if err := a.resolveAuction(ctx); err != nil { log.Error("Could not resolve auction for round", "error", err) } @@ -308,6 +312,22 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { }) } +func (a *AuctioneerServer) processRemainingBidsBeforeResolution(ctx context.Context, timeoutDuration time.Duration) { + timeout, cancel := context.WithTimeout(ctx, timeoutDuration) + defer cancel() + for { + select { + case <-timeout.Done(): + return + case bid := <-a.bidsReceiver: + log.Info("Consumed validated bid", "bidder", bid.Bidder, "amount", bid.Amount, "round", bid.Round) + a.bidCache.add(JsonValidatedBidToGo(bid)) + // Persist the validated bid to the database as a non-blocking operation. + go a.persistValidatedBid(bid) + } + } +} + // Resolves the auction by calling the smart contract with the top two bids. func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 From ab552925188e9663fb89ebed6f65cff161849546 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 11:48:46 -0500 Subject: [PATCH 074/109] resolve wait a second --- timeboost/auctioneer.go | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 58161f9b45..f92eeb04ab 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -299,9 +299,7 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { case auctionClosingTime := <-ticker.c: log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", a.bidCache.size()) // Wait for a second, just to give some leeway for latency of bids received last minute. - // Process any remaining bids that may exist in the bids receiver channel before we close - // within this remaining second. - a.processRemainingBidsBeforeResolution(ctx, time.Second) + time.Sleep(time.Second) if err := a.resolveAuction(ctx); err != nil { log.Error("Could not resolve auction for round", "error", err) } @@ -312,22 +310,6 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { }) } -func (a *AuctioneerServer) processRemainingBidsBeforeResolution(ctx context.Context, timeoutDuration time.Duration) { - timeout, cancel := context.WithTimeout(ctx, timeoutDuration) - defer cancel() - for { - select { - case <-timeout.Done(): - return - case bid := <-a.bidsReceiver: - log.Info("Consumed validated bid", "bidder", bid.Bidder, "amount", bid.Amount, "round", bid.Round) - a.bidCache.add(JsonValidatedBidToGo(bid)) - // Persist the validated bid to the database as a non-blocking operation. - go a.persistValidatedBid(bid) - } - } -} - // Resolves the auction by calling the smart contract with the top two bids. func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { upcomingRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + 1 From 7a70988a7eb77766881bf25ae6b79ec085295c8c Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 11:55:32 -0500 Subject: [PATCH 075/109] dont hardcode ports --- system_tests/timeboost_test.go | 42 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 6a5297745a..e7cbce7e02 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "fmt" "math/big" + "net" "os" "path/filepath" "sync" @@ -49,7 +50,7 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing }) jwtSecretPath := filepath.Join(tmpDir, "sequencer.jwt") - _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, tmpDir, ctx, jwtSecretPath) + seq, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, tmpDir, ctx, jwtSecretPath) defer cleanupSeq() chainId, err := seqClient.ChainID(ctx) Require(t, err) @@ -61,7 +62,7 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing bobPriv := seqInfo.Accounts["Bob"].PrivateKey // Prepare a client that can submit txs to the sequencer via the express lane. - seqDial, err := rpc.Dial("http://localhost:9567") + seqDial, err := rpc.Dial(seq.Stack.HTTPEndpoint()) Require(t, err) expressLaneClient := newExpressLaneClient( bobPriv, @@ -156,7 +157,7 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test require.NoError(t, os.RemoveAll(tmpDir)) }) jwtSecretPath := filepath.Join(tmpDir, "sequencer.jwt") - _, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, tmpDir, ctx, jwtSecretPath) + seq, seqClient, seqInfo, auctionContractAddr, cleanupSeq := setupExpressLaneAuction(t, tmpDir, ctx, jwtSecretPath) defer cleanupSeq() chainId, err := seqClient.ChainID(ctx) Require(t, err) @@ -168,7 +169,7 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test bobPriv := seqInfo.Accounts["Bob"].PrivateKey // Prepare a client that can submit txs to the sequencer via the express lane. - seqDial, err := rpc.Dial("http://localhost:9567") + seqDial, err := rpc.Dial(seq.Stack.HTTPEndpoint()) Require(t, err) expressLaneClient := newExpressLaneClient( bobPriv, @@ -280,10 +281,12 @@ func setupExpressLaneAuction( builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, true) + seqPort := getRandomPort(t) + seqAuthPort := getRandomPort(t) builderSeq.l2StackConfig.HTTPHost = "localhost" - builderSeq.l2StackConfig.HTTPPort = 9567 + builderSeq.l2StackConfig.HTTPPort = seqPort builderSeq.l2StackConfig.HTTPModules = []string{"eth", "arb", "debug", "timeboost"} - builderSeq.l2StackConfig.AuthPort = 9568 + builderSeq.l2StackConfig.AuthPort = seqAuthPort builderSeq.l2StackConfig.AuthModules = []string{"eth", "arb", "debug", "timeboost", "auctioneer"} builderSeq.l2StackConfig.JWTSecret = jwtSecretPath builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() @@ -291,7 +294,7 @@ func setupExpressLaneAuction( builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ Enable: true, ExpressLaneAdvantage: time.Second * 5, - SequencerHTTPEndpoint: "http://localhost:9567", + SequencerHTTPEndpoint: fmt.Sprintf("http://localhost:%d", seqPort), } cleanupSeq := builderSeq.Build(t) seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client @@ -433,15 +436,17 @@ func setupExpressLaneAuction( redisURL := redisutil.CreateTestRedis(ctx, t) // Set up the auctioneer RPC service. + bidValidatorPort := getRandomPort(t) + bidValidatorWsPort := getRandomPort(t) stackConf := node.Config{ DataDir: "", // ephemeral. - HTTPPort: 9372, + HTTPPort: bidValidatorPort, HTTPHost: "localhost", HTTPModules: []string{timeboost.AuctioneerNamespace}, HTTPVirtualHosts: []string{"localhost"}, HTTPTimeouts: rpc.DefaultHTTPTimeouts, WSHost: "localhost", - WSPort: 9373, + WSPort: bidValidatorWsPort, WSModules: []string{timeboost.AuctioneerNamespace}, GraphQLVirtualHosts: []string{"localhost"}, P2P: p2p.Config{ @@ -453,7 +458,7 @@ func setupExpressLaneAuction( stack, err := node.New(&stackConf) Require(t, err) cfg := &timeboost.BidValidatorConfig{ - SequencerEndpoint: "http://localhost:9567", + SequencerEndpoint: fmt.Sprintf("http://localhost:%d", seqPort), AuctionContractAddress: proxyAddr.Hex(), RedisURL: redisURL, ProducerConfig: pubsub.TestProducerConfig, @@ -470,7 +475,7 @@ func setupExpressLaneAuction( bidValidator.Start(ctx) auctioneerCfg := &timeboost.AuctioneerServerConfig{ - SequencerEndpoint: "http://localhost:9568", + SequencerEndpoint: fmt.Sprintf("http://localhost:%d", seqAuthPort), AuctionContractAddress: proxyAddr.Hex(), RedisURL: redisURL, ConsumerConfig: pubsub.TestConsumerConfig, @@ -495,8 +500,8 @@ func setupExpressLaneAuction( cfgFetcherAlice := func() *timeboost.BidderClientConfig { return &timeboost.BidderClientConfig{ AuctionContractAddress: proxyAddr.Hex(), - BidValidatorEndpoint: "http://localhost:9372", - ArbitrumNodeEndpoint: "http://localhost:9567", + BidValidatorEndpoint: fmt.Sprintf("http://localhost:%d", bidValidatorPort), + ArbitrumNodeEndpoint: fmt.Sprintf("http://localhost:%d", seqPort), Wallet: genericconf.WalletConfig{ PrivateKey: fmt.Sprintf("00%x", alicePriv.D.Bytes()), }, @@ -512,8 +517,8 @@ func setupExpressLaneAuction( cfgFetcherBob := func() *timeboost.BidderClientConfig { return &timeboost.BidderClientConfig{ AuctionContractAddress: proxyAddr.Hex(), - BidValidatorEndpoint: "http://localhost:9372", - ArbitrumNodeEndpoint: "http://localhost:9567", + BidValidatorEndpoint: fmt.Sprintf("http://localhost:%d", bidValidatorPort), + ArbitrumNodeEndpoint: fmt.Sprintf("http://localhost:%d", seqPort), Wallet: genericconf.WalletConfig{ PrivateKey: fmt.Sprintf("00%x", bobPriv.D.Bytes()), }, @@ -726,3 +731,10 @@ func signSubmission(message []byte, key *ecdsa.PrivateKey) ([]byte, error) { sig[64] += 27 return sig, nil } + +func getRandomPort(t testing.TB) int { + listener, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + defer listener.Close() + return listener.Addr().(*net.TCPAddr).Port +} From e4892b295a66836bfb328aba7349fc60fef7bf29 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 12:14:21 -0500 Subject: [PATCH 076/109] geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 48de2030c7..575062fad7 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 48de2030c7a6fa8689bc0a0212ebca2a0c73e3ad +Subproject commit 575062fad7ff4db9d7c235f49472f658be29e2fe From 4dc427a5e5323208a6f790eb99c1ab10459740fc Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 12:32:27 -0500 Subject: [PATCH 077/109] add in new queue --- execution/gethexec/sequencer.go | 52 +++++++-------------------------- system_tests/timeboost_test.go | 42 +++----------------------- 2 files changed, 15 insertions(+), 79 deletions(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index c43b6f1866..7d8427d5bf 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -338,12 +338,12 @@ type Sequencer struct { pauseChan chan struct{} forwarder *TxForwarder - expectedSurplusMutex sync.RWMutex - expectedSurplus int64 - expectedSurplusUpdated bool - auctioneerAddr common.Address - timeboostLock sync.Mutex - timeboostAuctionResolutionTx *types.Transaction + expectedSurplusMutex sync.RWMutex + expectedSurplus int64 + expectedSurplusUpdated bool + auctioneerAddr common.Address + timeboostLock sync.Mutex + timeboostAuctionResolutionTxQueue containers.Queue[txQueueItem] } func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderReader, configFetcher SequencerConfigFetcher) (*Sequencer, error) { @@ -551,16 +551,8 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx if err != nil { return err } - s.timeboostLock.Lock() - // Set it as a value that will be consumed first in `createBlock` - if s.timeboostAuctionResolutionTx != nil { - s.timeboostLock.Unlock() - return errors.New("auction resolution tx for round already received") - } - s.timeboostAuctionResolutionTx = tx - s.timeboostLock.Unlock() log.Info("Prioritizing auction resolution transaction from auctioneer", "txHash", tx.Hash().Hex()) - s.txQueue <- txQueueItem{ + s.timeboostAuctionResolutionTxQueue.Push(txQueueItem{ tx: tx, txSize: len(txBytes), options: nil, @@ -568,8 +560,7 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx returnedResult: &atomic.Bool{}, ctx: ctx, firstAppearance: time.Now(), - } - s.createBlock(ctx) + }) return nil } @@ -897,7 +888,9 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { for { var queueItem txQueueItem - if s.txRetryQueue.Len() > 0 { + if s.timeboostAuctionResolutionTxQueue.Len() > 0 { + queueItem = s.txRetryQueue.Pop() + } else if s.txRetryQueue.Len() > 0 { queueItem = s.txRetryQueue.Pop() } else if len(queueItems) == 0 { var nextNonceExpiryChan <-chan time.Time @@ -953,29 +946,6 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { s.nonceCache.Resize(config.NonceCacheSize) // Would probably be better in a config hook but this is basically free s.nonceCache.BeginNewBlock() - if s.config().Timeboost.Enable { - s.timeboostLock.Lock() - if s.timeboostAuctionResolutionTx != nil { - txBytes, err := s.timeboostAuctionResolutionTx.MarshalBinary() - if err != nil { - s.timeboostLock.Unlock() - log.Error("Failed to marshal timeboost auction resolution tx", "err", err) - return false - } - queueItems = append([]txQueueItem{ - { - tx: s.timeboostAuctionResolutionTx, - txSize: len(txBytes), - options: nil, - resultChan: make(chan error, 1), - returnedResult: &atomic.Bool{}, - ctx: ctx, - firstAppearance: time.Now(), - }, - }, queueItems...) - } - s.timeboostLock.Unlock() - } queueItems = s.precheckNonces(queueItems, totalBlockSize) txes := make([]*types.Transaction, len(queueItems)) hooks := s.makeSequencingHooks() diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index e7cbce7e02..126588da80 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -121,26 +121,9 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing bobBlock := bobReceipt.BlockNumber.Uint64() if aliceBlock < bobBlock { - t.Fatal("Bob should have been sequenced before Alice with express lane") + t.Fatal("Alice's tx should not have been sequenced before Bob's in different blocks") } else if aliceBlock == bobBlock { - t.Log("Sequenced in same output block") - block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) - Require(t, err) - findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { - for index, tx := range transactions { - if tx.Hash() == txHash { - return index - } - } - return -1 - } - txes := block.Transactions() - indexA := findTransactionIndex(txes, aliceTx.Hash()) - indexB := findTransactionIndex(txes, bobBoostableTx.Hash()) - if indexA == -1 || indexB == -1 { - t.Fatal("Did not find txs in block") - } - if indexA < indexB { + if aliceReceipt.TransactionIndex < bobReceipt.TransactionIndex { t.Fatal("Bob should have been sequenced before Alice with express lane") } } @@ -247,26 +230,9 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test charlieBlock := charlieReceipt.BlockNumber.Uint64() if aliceBlock < charlieBlock { - t.Fatal("Charlie should have been sequenced before Alice with express lane") + t.Fatal("Alice's tx should not have been sequenced before Charlie's in different blocks") } else if aliceBlock == charlieBlock { - t.Log("Sequenced in same output block") - block, err := seqClient.BlockByNumber(ctx, new(big.Int).SetUint64(aliceBlock)) - Require(t, err) - findTransactionIndex := func(transactions types.Transactions, txHash common.Hash) int { - for index, tx := range transactions { - if tx.Hash() == txHash { - return index - } - } - return -1 - } - txes := block.Transactions() - indexA := findTransactionIndex(txes, aliceTx.Hash()) - indexB := findTransactionIndex(txes, charlie0.Hash()) - if indexA == -1 || indexB == -1 { - t.Fatal("Did not find txs in block") - } - if indexA < indexB { + if aliceReceipt.TransactionIndex < charlieReceipt.TransactionIndex { t.Fatal("Charlie should have been sequenced before Alice with express lane") } } From e2110abdfcc5ebd0673377506b60463750a36ea4 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 12:42:32 -0500 Subject: [PATCH 078/109] edits --- execution/gethexec/express_lane_service.go | 41 +++++++++++++++++++--- execution/gethexec/sequencer.go | 1 - 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 62a4dd8f15..fd752e5553 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -34,7 +34,7 @@ type expressLaneService struct { auctionContractAddr common.Address initialTimestamp time.Time roundDuration time.Duration - auctionClosingSeconds time.Duration + auctionClosing time.Duration chainConfig *params.ChainConfig logs chan []*types.Log seqClient *ethclient.Client @@ -59,12 +59,12 @@ func newExpressLaneService( } initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second - auctionClosingSeconds := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second + auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second return &expressLaneService{ auctionContract: auctionContract, chainConfig: chainConfig, initialTimestamp: initialTimestamp, - auctionClosingSeconds: auctionClosingSeconds, + auctionClosing: auctionClosingDuration, roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), // Keep 8 rounds cached. auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, @@ -104,6 +104,39 @@ func (es *expressLaneService) Start(ctxIn context.Context) { } }) es.LaunchThread(func(ctx context.Context) { + // rollupAbi, err := express_lane_auctiongen.ExpressLaneAuctionMetaData.GetAbi() + // if err != nil { + // panic(err) + // } + // rawEv := rollupAbi.Events["AuctionResolved"] + // express_lane_auctiongen.ExpressLaneAuctionAuctionResolved + // rollupAbi, err := rollupgen.RollupCoreMetaData.GetAbi() + // event := new(ExpressLaneAuctionAuctionResolved) + // if err := _ExpressLaneAuction.contract.UnpackLog(event, "AuctionResolved", log); err != nil { + // return nil, err + // } + // event.Raw = log + // UnpackLog(out interface{}, event string, log types.Log) error { + // // Anonymous events are not supported. + // if len(log.Topics) == 0 { + // return errNoEventSignature + // } + // if log.Topics[0] != c.abi.Events[event].ID { + // return errEventSignatureMismatch + // } + // if len(log.Data) > 0 { + // if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + // return err + // } + // } + // var indexed abi.Arguments + // for _, arg := range c.abi.Events[event].Inputs { + // if arg.Indexed { + // indexed = append(indexed, arg) + // } + // } + // return abi.ParseTopics(out, indexed, log.Topics[1:]) + // } log.Info("Monitoring express lane auction contract") // Monitor for auction resolutions from the auction manager smart contract // and set the express lane controller for the upcoming round accordingly. @@ -170,7 +203,7 @@ func (es *expressLaneService) isWithinAuctionCloseWindow(arrivalTime time.Time) // Calculate the time to the next round timeToNextRound := nextRoundStart.Sub(arrivalTime) // Check if the arrival timestamp is within AUCTION_CLOSING_DURATION of TIME_TO_NEXT_ROUND - return timeToNextRound <= es.auctionClosingSeconds + return timeToNextRound <= es.auctionClosing } func (es *expressLaneService) sequenceExpressLaneSubmission( diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 7d8427d5bf..b507d4c90b 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -342,7 +342,6 @@ type Sequencer struct { expectedSurplus int64 expectedSurplusUpdated bool auctioneerAddr common.Address - timeboostLock sync.Mutex timeboostAuctionResolutionTxQueue containers.Queue[txQueueItem] } From 15e60876bf3fb3ff3c5378e80387c812bd0cef13 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Mon, 19 Aug 2024 12:45:40 -0700 Subject: [PATCH 079/109] Fix tie breaker and retry resolve bid tx Fix tie breaker --- timeboost/auctioneer.go | 56 ++++++++++++++++++++++++++++------------- timeboost/types.go | 34 +++++++++++++++++-------- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index f92eeb04ab..ebabb11112 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -360,31 +360,53 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { return err } - if err = a.sendAuctionResolutionTransactionRPC(ctx, tx); err != nil { + if err = a.retrySendAuctionResolutionTx(ctx, tx); err != nil { log.Error("Error submitting auction resolution to privileged sequencer endpoint", "error", err) return err } - receipt, err := bind.WaitMined(ctx, a.client, tx) - if err != nil { - log.Error("Error waiting for transaction to be mined", "error", err) - return err - } - - if tx == nil || receipt == nil || receipt.Status != types.ReceiptStatusSuccessful { - if tx != nil { - log.Error("Transaction failed or did not finalize successfully", "txHash", tx.Hash().Hex()) - } - return errors.New("transaction failed or did not finalize successfully") - } - log.Info("Auction resolved successfully", "txHash", tx.Hash().Hex()) return nil } -func (a *AuctioneerServer) sendAuctionResolutionTransactionRPC(ctx context.Context, tx *types.Transaction) error { - // TODO: Retry a few times if fails. - return a.sequencerRpc.CallContext(ctx, nil, "auctioneer_submitAuctionResolutionTransaction", tx) +// retrySendAuctionResolutionTx attempts to send the auction resolution transaction to the +// sequencer endpoint. If the transaction submission fails, it retries the +// submission at regular intervals until the end of the current round or until the context +// is canceled or times out. The function returns an error if all attempts fail. +func (a *AuctioneerServer) retrySendAuctionResolutionTx(ctx context.Context, tx *types.Transaction) error { + var err error + + currentRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + roundEndTime := a.initialRoundTimestamp.Add(time.Duration(currentRound) * a.roundDuration) + retryInterval := 1 * time.Second + + for { + // Attempt to send the transaction + if err = a.sequencerRpc.CallContext(ctx, nil, "auctioneer_submitAuctionResolutionTransaction", tx); err == nil { + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, a.client, tx) + if err != nil || tx == nil || receipt == nil || receipt.Status != types.ReceiptStatusSuccessful { + log.Error("Transaction failed or did not finalize successfully", "txHash", tx.Hash().Hex(), "error", err) + err = errors.New("transaction failed or did not finalize successfully") + } else { + return nil // Transaction was successful + } + } else { + log.Error("Error submitting auction resolution to privileged sequencer endpoint", "error", err) + } + + if ctx.Err() != nil { + return ctx.Err() + } + + if time.Now().After(roundEndTime) { + break + } + + time.Sleep(retryInterval) + } + + return errors.New("failed to submit auction resolution after multiple attempts") } func (a *AuctioneerServer) persistValidatedBid(bid *JsonValidatedBid) { diff --git a/timeboost/types.go b/timeboost/types.go index b151442402..1b395e39ad 100644 --- a/timeboost/types.go +++ b/timeboost/types.go @@ -3,7 +3,6 @@ package timeboost import ( "bytes" "crypto/ecdsa" - "crypto/sha256" "encoding/binary" "fmt" "math/big" @@ -72,19 +71,34 @@ type ValidatedBid struct { Bidder common.Address } +// Hash returns the following solidity implementation: +// +// uint256(keccak256(abi.encodePacked(bidder, bidBytes))) func (v *ValidatedBid) Hash() string { - // Concatenate the bidder address and the byte representation of the bid - data := append(v.Bidder.Bytes(), padBigInt(v.ChainId)...) - data = append(data, v.AuctionContractAddress.Bytes()...) + bidBytes := v.BidBytes() + bidder := v.Bidder.Bytes() + + return crypto.Keccak256Hash(bidder, bidBytes).String() +} + +// BidBytes returns the byte representation equivalent to the Solidity implementation of +// +// abi.encodePacked(BID_DOMAIN, block.chainid, address(this), _round, _amount, _expressLaneController) +func (v *ValidatedBid) BidBytes() []byte { + var buffer bytes.Buffer + + buffer.Write(domainValue) + buffer.Write(v.ChainId.Bytes()) + buffer.Write(v.AuctionContractAddress.Bytes()) + roundBytes := make([]byte, 8) binary.BigEndian.PutUint64(roundBytes, v.Round) - data = append(data, roundBytes...) - data = append(data, v.Amount.Bytes()...) - data = append(data, v.ExpressLaneController.Bytes()...) + buffer.Write(roundBytes) + + buffer.Write(v.Amount.Bytes()) + buffer.Write(v.ExpressLaneController.Bytes()) - hash := sha256.Sum256(data) - // Return the hash as a hexadecimal string - return fmt.Sprintf("%x", hash) + return buffer.Bytes() } func (v *ValidatedBid) ToJson() *JsonValidatedBid { From f75f2425f4a186995e059abe682614f9f347ad09 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 19 Aug 2024 21:55:55 -0500 Subject: [PATCH 080/109] edits --- execution/gethexec/express_lane_service.go | 74 ++++++++++++---------- execution/gethexec/sequencer.go | 5 +- system_tests/timeboost_test.go | 16 +++++ timeboost/auctioneer.go | 6 +- timeboost/bid_validator.go | 9 ++- timeboost/ticker.go | 26 ++++++-- timeboost/ticker_test.go | 30 ++++----- 7 files changed, 102 insertions(+), 64 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index fd752e5553..7d874f616c 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -142,45 +142,49 @@ func (es *expressLaneService) Start(ctxIn context.Context) { // and set the express lane controller for the upcoming round accordingly. latestBlock, err := es.seqClient.HeaderByNumber(ctx, nil) if err != nil { + // TODO: Should not be a crit. log.Crit("Could not get latest header", "err", err) } fromBlock := latestBlock.Number.Uint64() - duration := time.Millisecond * 250 - es.CallIteratively(func(ctx context.Context) time.Duration { - latestBlock, err := es.seqClient.HeaderByNumber(ctx, nil) - if err != nil { - log.Error("Could not get latest header", "err", err) - } - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - return duration - } - filterOpts := &bind.FilterOpts{ - Context: ctx, - Start: fromBlock, - End: &toBlock, - } - it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) - if err != nil { - log.Error("Could not filter auction resolutions", "error", err) - return duration - } - for it.Next() { - log.Info( - "New express lane controller assigned", - "round", it.Event.Round, - "controller", it.Event.FirstPriceExpressLaneController, - ) - es.Lock() - es.roundControl.Add(it.Event.Round, &expressLaneControl{ - controller: it.Event.FirstPriceExpressLaneController, - sequence: 0, - }) - es.Unlock() + for { + select { + case <-ctx.Done(): + return + case <-time.After(time.Millisecond * 250): + latestBlock, err := es.seqClient.HeaderByNumber(ctx, nil) + if err != nil { + log.Crit("Could not get latest header", "err", err) + } + toBlock := latestBlock.Number.Uint64() + if fromBlock == toBlock { + continue + } + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: fromBlock, + End: &toBlock, + } + it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) + if err != nil { + log.Error("Could not filter auction resolutions", "error", err) + continue + } + for it.Next() { + log.Info( + "New express lane controller assigned", + "round", it.Event.Round, + "controller", it.Event.FirstPriceExpressLaneController, + ) + es.Lock() + es.roundControl.Add(it.Event.Round, &expressLaneControl{ + controller: it.Event.FirstPriceExpressLaneController, + sequence: 0, + }) + es.Unlock() + } + fromBlock = toBlock } - fromBlock = toBlock - return duration - }) + } }) } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index b507d4c90b..e8c8ccc94e 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -557,7 +557,7 @@ func (s *Sequencer) PublishAuctionResolutionTransaction(ctx context.Context, tx options: nil, resultChan: make(chan error, 1), returnedResult: &atomic.Bool{}, - ctx: ctx, + ctx: context.TODO(), firstAppearance: time.Now(), }) return nil @@ -888,7 +888,8 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { for { var queueItem txQueueItem if s.timeboostAuctionResolutionTxQueue.Len() > 0 { - queueItem = s.txRetryQueue.Pop() + queueItem = s.timeboostAuctionResolutionTxQueue.Pop() + fmt.Println("Popped the auction resolution tx") } else if s.txRetryQueue.Len() > 0 { queueItem = s.txRetryQueue.Pop() } else if len(queueItems) == 0 { diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 126588da80..12a3c35b92 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -265,6 +265,22 @@ func setupExpressLaneAuction( cleanupSeq := builderSeq.Build(t) seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client + // Send an L2 tx in the background every two seconds to keep the chain moving. + go func() { + tick := time.NewTicker(time.Second * 2) + defer tick.Stop() + for { + select { + case <-ctx.Done(): + return + case <-tick.C: + tx := seqInfo.PrepareTx("Owner", "Owner", seqInfo.TransferGas, big.NewInt(1), nil) + err := seqClient.SendTransaction(ctx, tx) + t.Log("Failed to send test tx", err) + } + } + }() + // Set up the auction contracts on L2. // Deploy the express lane auction contract and erc20 to the parent chain. ownerOpts := seqInfo.GetDefaultTransactOpts("Owner", ctx) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index f92eeb04ab..2a4a904fca 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -298,8 +298,8 @@ func (a *AuctioneerServer) Start(ctx_in context.Context) { return case auctionClosingTime := <-ticker.c: log.Info("New auction closing time reached", "closingTime", auctionClosingTime, "totalBids", a.bidCache.size()) - // Wait for a second, just to give some leeway for latency of bids received last minute. - time.Sleep(time.Second) + // Wait for two seconds, just to give some leeway for latency of bids received last minute. + time.Sleep(2 * time.Second) if err := a.resolveAuction(ctx); err != nil { log.Error("Could not resolve auction for round", "error", err) } @@ -364,13 +364,11 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { log.Error("Error submitting auction resolution to privileged sequencer endpoint", "error", err) return err } - receipt, err := bind.WaitMined(ctx, a.client, tx) if err != nil { log.Error("Error waiting for transaction to be mined", "error", err) return err } - if tx == nil || receipt == nil || receipt.Status != types.ReceiptStatusSuccessful { if tx != nil { log.Error("Transaction failed or did not finalize successfully", "txHash", tx.Hash().Hex()) diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 5c73385f75..6cb1bcc350 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -260,8 +260,13 @@ func (bv *BidValidator) validateBid( } // Check if the auction is closed. - if d, closed := auctionClosed(bv.initialRoundTimestamp, bv.roundDuration, bv.auctionClosingDuration); closed { - return nil, errors.Wrapf(ErrBadRoundNumber, "auction is closed, %s since closing", d) + if isAuctionRoundClosed( + time.Now(), + bv.initialRoundTimestamp, + bv.roundDuration, + bv.auctionClosingDuration, + ) { + return nil, errors.Wrap(ErrBadRoundNumber, "auction is closed") } // Check bid is higher than reserve price. diff --git a/timeboost/ticker.go b/timeboost/ticker.go index ca898a4088..45e6ecef11 100644 --- a/timeboost/ticker.go +++ b/timeboost/ticker.go @@ -53,11 +53,25 @@ func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) return uint64(time.Since(initialRoundTimestamp) / roundDuration) } -// auctionClosed returns the time into the current round and whether the auction for this round is closed. -func auctionClosed(initialRoundTimestamp time.Time, roundDuration time.Duration, auctionClosingDuration time.Duration) (time.Duration, bool) { - if roundDuration == 0 { - return 0, true +func isAuctionRoundClosed( + timestamp time.Time, + initialTimestamp time.Time, + roundDuration time.Duration, + auctionClosingDuration time.Duration, +) bool { + if timestamp.Before(initialTimestamp) { + return false } - d := time.Since(initialRoundTimestamp) % roundDuration - return d, d >= roundDuration-auctionClosingDuration + timeInRound := timeIntoRound(timestamp, initialTimestamp, roundDuration) + return time.Duration(timeInRound)*time.Second >= roundDuration-auctionClosingDuration +} + +func timeIntoRound( + timestamp time.Time, + initialTimestamp time.Time, + roundDuration time.Duration, +) uint64 { + secondsSinceOffset := uint64(timestamp.Sub(initialTimestamp).Seconds()) + roundDurationSeconds := uint64(roundDuration.Seconds()) + return secondsSinceOffset % roundDurationSeconds } diff --git a/timeboost/ticker_test.go b/timeboost/ticker_test.go index db853b7a88..b1ee996bc0 100644 --- a/timeboost/ticker_test.go +++ b/timeboost/ticker_test.go @@ -13,28 +13,28 @@ func Test_auctionClosed(t *testing.T) { auctionClosingDuration := time.Second * 15 now := time.Now() waitTime := roundDuration - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) - nextMinute := now.Add(waitTime) - <-time.After(waitTime) - - timeIntoRound, isClosed := auctionClosed(nextMinute, roundDuration, auctionClosingDuration) + initialTimestamp := now.Add(waitTime) // We should not have closed the round yet, and the time into the round should be less than a second. + isClosed := isAuctionRoundClosed(initialTimestamp, initialTimestamp, roundDuration, auctionClosingDuration) require.False(t, isClosed) - require.True(t, timeIntoRound < time.Second) // Wait right before auction closure (before the 45 second mark). - now = time.Now() - waitTime = (roundDuration - auctionClosingDuration) - time.Duration(now.Second())*time.Second - time.Duration(now.Nanosecond()) - secondBeforeClosing := waitTime - time.Second - <-time.After(secondBeforeClosing) - - timeIntoRound, isClosed = auctionClosed(nextMinute, roundDuration, auctionClosingDuration) + timestamp := initialTimestamp.Add((roundDuration - auctionClosingDuration) - time.Second) + isClosed = isAuctionRoundClosed(timestamp, initialTimestamp, roundDuration, auctionClosingDuration) require.False(t, isClosed) - require.True(t, timeIntoRound < (roundDuration-auctionClosingDuration)) // Wait a second more and the auction should be closed. - <-time.After(time.Second) - timeIntoRound, isClosed = auctionClosed(nextMinute, roundDuration, auctionClosingDuration) + timestamp = initialTimestamp.Add(roundDuration - auctionClosingDuration) + isClosed = isAuctionRoundClosed(timestamp, initialTimestamp, roundDuration, auctionClosingDuration) require.True(t, isClosed) - require.True(t, timeIntoRound >= (roundDuration-auctionClosingDuration)) + + // Future timestamp should also be closed, until we reach the new round + for i := float64(0); i < auctionClosingDuration.Seconds(); i++ { + timestamp = initialTimestamp.Add((roundDuration - auctionClosingDuration) + time.Second*time.Duration(i)) + isClosed = isAuctionRoundClosed(timestamp, initialTimestamp, roundDuration, auctionClosingDuration) + require.True(t, isClosed) + } + isClosed = isAuctionRoundClosed(initialTimestamp.Add(roundDuration), initialTimestamp, roundDuration, auctionClosingDuration) + require.False(t, isClosed) } From a5c8777a89362322be267bd75a4428566a7c5d0c Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 20 Aug 2024 07:54:54 -0500 Subject: [PATCH 081/109] rem redundant sig verify --- execution/gethexec/express_lane_service.go | 4 ---- execution/gethexec/express_lane_service_test.go | 6 +++--- timeboost/bid_validator.go | 3 --- timeboost/types.go | 8 -------- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 7d874f616c..1a099eb5e0 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -300,9 +299,6 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu if err != nil { return timeboost.ErrMalformedData } - if !secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sigItem[:len(sigItem)-1]) { - return timeboost.ErrWrongSignature - } sender := crypto.PubkeyToAddress(*pubkey) es.RLock() defer es.RUnlock() diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 9c39e0743e..6034f81c80 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -378,9 +378,9 @@ func TestIsWithinAuctionCloseWindow(t *testing.T) { auctionClosing := 15 * time.Second es := &expressLaneService{ - initialTimestamp: initialTimestamp, - roundDuration: roundDuration, - auctionClosingSeconds: auctionClosing, + initialTimestamp: initialTimestamp, + roundDuration: roundDuration, + auctionClosing: auctionClosing, } tests := []struct { diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 6cb1bcc350..4a12e6f3b6 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -290,9 +290,6 @@ func (bv *BidValidator) validateBid( if err != nil { return nil, ErrMalformedData } - if !verifySignature(pubkey, packedBidBytes, sigItem) { - return nil, ErrWrongSignature - } // Check how many bids the bidder has sent in this round and cap according to a limit. bidder := crypto.PubkeyToAddress(*pubkey) bv.Lock() diff --git a/timeboost/types.go b/timeboost/types.go index 1b395e39ad..861caa0260 100644 --- a/timeboost/types.go +++ b/timeboost/types.go @@ -2,9 +2,7 @@ package timeboost import ( "bytes" - "crypto/ecdsa" "encoding/binary" - "fmt" "math/big" "github.com/ethereum/go-ethereum/arbitrum_types" @@ -12,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/secp256k1" ) type Bid struct { @@ -206,11 +203,6 @@ func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { return buf.Bytes(), nil } -func verifySignature(pubkey *ecdsa.PublicKey, message []byte, sig []byte) bool { - prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(message))), message...)) - return secp256k1.VerifySignature(crypto.FromECDSAPub(pubkey), prefixed, sig[:len(sig)-1]) -} - // Helper function to pad a big integer to 32 bytes func padBigInt(bi *big.Int) []byte { bb := bi.Bytes() From 196bdc74364b26c578a136eaa9e752b49bb867e3 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 20 Aug 2024 08:51:20 -0700 Subject: [PATCH 082/109] Fix tie breaker and better retry --- timeboost/auctioneer.go | 64 +++++++++++++++++++---------------- timeboost/auctioneer_test.go | 65 ++++++++++++++++++++++++++++++++++++ timeboost/bid_cache.go | 6 ++-- timeboost/types.go | 7 ++-- 4 files changed, 108 insertions(+), 34 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 43e819edeb..397dec0b0e 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -360,8 +360,33 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { return err } - if err = a.retrySendAuctionResolutionTx(ctx, tx); err != nil { - log.Error("Error submitting auction resolution to privileged sequencer endpoint", "error", err) + currentRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) + roundEndTime := a.initialRoundTimestamp.Add(time.Duration(currentRound) * a.roundDuration) + retryInterval := 1 * time.Second + + if err := retryUntil(ctx, func() error { + if err := a.sequencerRpc.CallContext(ctx, nil, "auctioneer_submitAuctionResolutionTransaction", tx); err != nil { + log.Error("Error submitting auction resolution to privileged sequencer endpoint", "error", err) + return err + } + + // Wait for the transaction to be mined + receipt, err := bind.WaitMined(ctx, a.client, tx) + if err != nil { + log.Error("Error waiting for transaction to be mined", "error", err) + return err + } + + // Check if the transaction was successful + if tx == nil || receipt == nil || receipt.Status != types.ReceiptStatusSuccessful { + if tx != nil { + log.Error("Transaction failed or did not finalize successfully", "txHash", tx.Hash().Hex()) + } + return errors.New("transaction failed or did not finalize successfully") + } + + return nil + }, retryInterval, roundEndTime); err != nil { return err } @@ -369,44 +394,27 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { return nil } -// retrySendAuctionResolutionTx attempts to send the auction resolution transaction to the -// sequencer endpoint. If the transaction submission fails, it retries the -// submission at regular intervals until the end of the current round or until the context -// is canceled or times out. The function returns an error if all attempts fail. -func (a *AuctioneerServer) retrySendAuctionResolutionTx(ctx context.Context, tx *types.Transaction) error { - var err error - - currentRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) - roundEndTime := a.initialRoundTimestamp.Add(time.Duration(currentRound) * a.roundDuration) - retryInterval := 1 * time.Second - +// retryUntil retries a given operation defined by the closure until the specified duration +// has passed or the operation succeeds. It waits for the specified retry interval between +// attempts. The function returns an error if all attempts fail. +func retryUntil(ctx context.Context, operation func() error, retryInterval time.Duration, endTime time.Time) error { for { - // Attempt to send the transaction - if err = a.sequencerRpc.CallContext(ctx, nil, "auctioneer_submitAuctionResolutionTransaction", tx); err == nil { - // Wait for the transaction to be mined - receipt, err := bind.WaitMined(ctx, a.client, tx) - if err != nil || tx == nil || receipt == nil || receipt.Status != types.ReceiptStatusSuccessful { - log.Error("Transaction failed or did not finalize successfully", "txHash", tx.Hash().Hex(), "error", err) - err = errors.New("transaction failed or did not finalize successfully") - } else { - return nil // Transaction was successful - } - } else { - log.Error("Error submitting auction resolution to privileged sequencer endpoint", "error", err) + // Execute the operation + if err := operation(); err == nil { + return nil } if ctx.Err() != nil { return ctx.Err() } - if time.Now().After(roundEndTime) { + if time.Now().After(endTime) { break } time.Sleep(retryInterval) } - - return errors.New("failed to submit auction resolution after multiple attempts") + return errors.New("operation failed after multiple attempts") } func (a *AuctioneerServer) persistValidatedBid(bid *JsonValidatedBid) { diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index c1333bfe6b..71b1cef17a 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -2,6 +2,7 @@ package timeboost import ( "context" + "errors" "fmt" "math/big" "os" @@ -154,3 +155,67 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { require.Equal(t, result.secondPlace.Amount, big.NewInt(5)) require.Equal(t, result.secondPlace.Bidder, bobAddr) } + +func TestRetryUntil(t *testing.T) { + t.Run("Success", func(t *testing.T) { + var currentAttempt int + successAfter := 3 + retryInterval := 100 * time.Millisecond + endTime := time.Now().Add(500 * time.Millisecond) + + err := retryUntil(context.Background(), mockOperation(successAfter, ¤tAttempt), retryInterval, endTime) + if err != nil { + t.Errorf("expected success, got error: %v", err) + } + if currentAttempt != successAfter { + t.Errorf("expected %d attempts, got %d", successAfter, currentAttempt) + } + }) + + t.Run("Timeout", func(t *testing.T) { + var currentAttempt int + successAfter := 5 + retryInterval := 100 * time.Millisecond + endTime := time.Now().Add(300 * time.Millisecond) + + err := retryUntil(context.Background(), mockOperation(successAfter, ¤tAttempt), retryInterval, endTime) + if err == nil { + t.Errorf("expected timeout error, got success") + } + if currentAttempt == successAfter { + t.Errorf("expected failure, but operation succeeded") + } + }) + + t.Run("ContextCancel", func(t *testing.T) { + var currentAttempt int + successAfter := 5 + retryInterval := 100 * time.Millisecond + endTime := time.Now().Add(500 * time.Millisecond) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(200 * time.Millisecond) + cancel() + }() + + err := retryUntil(ctx, mockOperation(successAfter, ¤tAttempt), retryInterval, endTime) + if err == nil { + t.Errorf("expected context cancellation error, got success") + } + if currentAttempt >= successAfter { + t.Errorf("expected failure due to context cancellation, but operation succeeded") + } + }) +} + +// Mock operation function to simulate different scenarios +func mockOperation(successAfter int, currentAttempt *int) func() error { + return func() error { + *currentAttempt++ + if *currentAttempt >= successAfter { + return nil + } + return errors.New("operation failed") + } +} diff --git a/timeboost/bid_cache.go b/timeboost/bid_cache.go index f48011e80c..4031ab9a0b 100644 --- a/timeboost/bid_cache.go +++ b/timeboost/bid_cache.go @@ -50,16 +50,16 @@ func (bc *bidCache) topTwoBids() *auctionResult { result.secondPlace = result.firstPlace result.firstPlace = bid } else if bid.Amount.Cmp(result.firstPlace.Amount) == 0 { - if bid.Hash() > result.firstPlace.Hash() { + if bid.BigIntHash().Cmp(result.firstPlace.BigIntHash()) > 0 { result.secondPlace = result.firstPlace result.firstPlace = bid - } else if result.secondPlace == nil || bid.Hash() > result.secondPlace.Hash() { + } else if result.secondPlace == nil || bid.BigIntHash().Cmp(result.secondPlace.BigIntHash()) > 0 { result.secondPlace = bid } } else if result.secondPlace == nil || bid.Amount.Cmp(result.secondPlace.Amount) > 0 { result.secondPlace = bid } else if bid.Amount.Cmp(result.secondPlace.Amount) == 0 { - if bid.Hash() > result.secondPlace.Hash() { + if bid.BigIntHash().Cmp(result.secondPlace.BigIntHash()) > 0 { result.secondPlace = bid } } diff --git a/timeboost/types.go b/timeboost/types.go index 861caa0260..428027730a 100644 --- a/timeboost/types.go +++ b/timeboost/types.go @@ -68,14 +68,15 @@ type ValidatedBid struct { Bidder common.Address } -// Hash returns the following solidity implementation: +// BigIntHash returns the hash of the bidder and bidBytes in the form of a big.Int. +// The hash is equivalent to the following Solidity implementation: // // uint256(keccak256(abi.encodePacked(bidder, bidBytes))) -func (v *ValidatedBid) Hash() string { +func (v *ValidatedBid) BigIntHash() *big.Int { bidBytes := v.BidBytes() bidder := v.Bidder.Bytes() - return crypto.Keccak256Hash(bidder, bidBytes).String() + return new(big.Int).SetBytes(crypto.Keccak256Hash(bidder, bidBytes).Bytes()) } // BidBytes returns the byte representation equivalent to the Solidity implementation of From e61c3b27df6bb8d2b42fece7b9e13b50c4e899a7 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 20 Aug 2024 15:56:21 -0500 Subject: [PATCH 083/109] edit --- execution/gethexec/express_lane_service.go | 33 ---------------------- 1 file changed, 33 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 1a099eb5e0..399591b655 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -103,39 +103,6 @@ func (es *expressLaneService) Start(ctxIn context.Context) { } }) es.LaunchThread(func(ctx context.Context) { - // rollupAbi, err := express_lane_auctiongen.ExpressLaneAuctionMetaData.GetAbi() - // if err != nil { - // panic(err) - // } - // rawEv := rollupAbi.Events["AuctionResolved"] - // express_lane_auctiongen.ExpressLaneAuctionAuctionResolved - // rollupAbi, err := rollupgen.RollupCoreMetaData.GetAbi() - // event := new(ExpressLaneAuctionAuctionResolved) - // if err := _ExpressLaneAuction.contract.UnpackLog(event, "AuctionResolved", log); err != nil { - // return nil, err - // } - // event.Raw = log - // UnpackLog(out interface{}, event string, log types.Log) error { - // // Anonymous events are not supported. - // if len(log.Topics) == 0 { - // return errNoEventSignature - // } - // if log.Topics[0] != c.abi.Events[event].ID { - // return errEventSignatureMismatch - // } - // if len(log.Data) > 0 { - // if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { - // return err - // } - // } - // var indexed abi.Arguments - // for _, arg := range c.abi.Events[event].Inputs { - // if arg.Indexed { - // indexed = append(indexed, arg) - // } - // } - // return abi.ParseTopics(out, indexed, log.Topics[1:]) - // } log.Info("Monitoring express lane auction contract") // Monitor for auction resolutions from the auction manager smart contract // and set the express lane controller for the upcoming round accordingly. From 981bc6a2df442bca92e3e5137be1569033f5ff4d Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 20 Aug 2024 14:57:08 -0700 Subject: [PATCH 084/109] Fix duplicated word --- execution/gethexec/express_lane_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 399591b655..f16e5b2969 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -204,7 +204,7 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( if msg.Sequence > control.sequence { log.Warn("Received express lane submission with future sequence number", "sequence", msg.Sequence) } - // Put into the the sequence number map. + // Put into the sequence number map. es.messagesBySequenceNumber[msg.Sequence] = msg for { From 8db0bd06023e4fe15337f40862beea145479301d Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 21 Aug 2024 06:16:49 -0700 Subject: [PATCH 085/109] Add copyright to express lane service --- execution/gethexec/express_lane_service.go | 3 +++ execution/gethexec/express_lane_service_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index f16e5b2969..528034ef06 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -1,3 +1,6 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + package gethexec import ( diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 6034f81c80..859015f51b 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -1,3 +1,6 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + package gethexec import ( From ec185f5c776ebbe2b49d7adf001e5076b8f5d81b Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 21 Aug 2024 06:23:31 -0700 Subject: [PATCH 086/109] Better popped the auction resolution tx log --- execution/gethexec/sequencer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index e8c8ccc94e..e7c254b8b7 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -889,7 +889,7 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { var queueItem txQueueItem if s.timeboostAuctionResolutionTxQueue.Len() > 0 { queueItem = s.timeboostAuctionResolutionTxQueue.Pop() - fmt.Println("Popped the auction resolution tx") + log.Info("Popped the auction resolution tx", queueItem.tx.Hash()) } else if s.txRetryQueue.Len() > 0 { queueItem = s.txRetryQueue.Pop() } else if len(queueItems) == 0 { From d15e0c56363cb805bdea9da41bb8b1aef7b5f79c Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 21 Aug 2024 07:00:47 -0700 Subject: [PATCH 087/109] Fix auction-contract-address doesnt match the field SequencerEndpoint --- execution/gethexec/express_lane_service.go | 2 +- timeboost/auctioneer.go | 5 ++++- timeboost/bid_validator.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 528034ef06..be4b96dbf4 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -1,4 +1,4 @@ -// Copyright 2023-2024, Offchain Labs, Inc. +// Copyright 2024-2025, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package gethexec diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 397dec0b0e..586ef6dddc 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -1,3 +1,6 @@ +// Copyright 2024-2025, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + package timeboost import ( @@ -87,7 +90,7 @@ func AuctioneerServerConfigAddOptions(prefix string, f *pflag.FlagSet) { genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") f.String(prefix+".sequencer-jwt-path", DefaultAuctioneerServerConfig.SequencerJWTPath, "sequencer jwt file path") - f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") + f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.AuctionContractAddress, "express lane auction contract address") f.String(prefix+".db-directory", DefaultAuctioneerServerConfig.DbDirectory, "path to database directory for persisting validated bids in a sqlite file") } diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 4a12e6f3b6..1a65e954af 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -51,7 +51,7 @@ func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".redis-url", DefaultBidValidatorConfig.RedisURL, "url of redis server") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") - f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.SequencerEndpoint, "express lane auction contract address") + f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.AuctionContractAddress, "express lane auction contract address") } type BidValidator struct { From f28896de6fe53f293e831c5ce331795a7f182043 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 21 Aug 2024 07:57:43 -0700 Subject: [PATCH 088/109] Remove unused conversions --- timeboost/auctioneer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 586ef6dddc..86225ff2f5 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -338,8 +338,8 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { Signature: second.Signature, }, ) - FirstBidValueGauge.Update(int64(first.Amount.Int64())) - SecondBidValueGauge.Update(int64(second.Amount.Int64())) + FirstBidValueGauge.Update(first.Amount.Int64()) + SecondBidValueGauge.Update(second.Amount.Int64()) log.Info("Resolving auction with two bids", "round", upcomingRound) case first != nil: // Single bid is present @@ -351,7 +351,7 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { Signature: first.Signature, }, ) - FirstBidValueGauge.Update(int64(first.Amount.Int64())) + FirstBidValueGauge.Update(first.Amount.Int64()) log.Info("Resolving auction with single bid", "round", upcomingRound) case second == nil: // No bids received From 3eec6863de6350df954e5e3755144d3df27ee4aa Mon Sep 17 00:00:00 2001 From: terence tsao Date: Fri, 23 Aug 2024 08:36:51 -0700 Subject: [PATCH 089/109] Fix express lane advantage to 200ms --- execution/gethexec/sequencer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index e7c254b8b7..60413f19c2 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -92,7 +92,7 @@ type TimeboostConfig struct { var DefaultTimeboostConfig = TimeboostConfig{ Enable: false, - ExpressLaneAdvantage: time.Millisecond * 250, + ExpressLaneAdvantage: time.Millisecond * 200, SequencerHTTPEndpoint: "http://localhost:9567", } From 98326dc626424c0e126d8912fa532ab1320f456a Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 21 Aug 2024 12:20:53 -0700 Subject: [PATCH 090/109] Update reserve price --- timeboost/bid_validator.go | 48 ++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 1a65e954af..b7c8e43466 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -57,7 +57,6 @@ func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { type BidValidator struct { stopwaiter.StopWaiter sync.RWMutex - reservePriceLock sync.RWMutex chainId *big.Int stack *node.Node producerCfg *pubsub.ProducerConfig @@ -73,6 +72,7 @@ type BidValidator struct { roundDuration time.Duration auctionClosingDuration time.Duration reserveSubmissionDuration time.Duration + reservePriceLock sync.RWMutex reservePrice *big.Int bidsPerSenderInRound map[common.Address]uint8 maxBidsPerSenderInRound uint8 @@ -189,6 +189,33 @@ func (bv *BidValidator) Start(ctx_in context.Context) { log.Crit("Bid validator not yet initialized by calling Initialize(ctx)") } bv.producer.Start(ctx_in) + + // Set reserve price thread. + bv.StopWaiter.LaunchThread(func(ctx context.Context) { + ticker := newAuctionCloseTicker(bv.roundDuration, bv.auctionClosingDuration+bv.reserveSubmissionDuration) + go ticker.start() + for { + select { + case <-ctx.Done(): + log.Error("Context closed, autonomous auctioneer shutting down") + return + case _ = <-ticker.c: + rp, err := bv.auctionContract.ReservePrice(&bind.CallOpts{}) + if err != nil { + log.Error("Could not get reserve price", "error", err) + continue + } + + currentReservePrice := bv.fetchReservePrice() + if currentReservePrice.Cmp(rp) == 0 { + continue + } + + log.Info("Reserve price updated", "old", currentReservePrice.String(), "new", rp.String()) + bv.setReservePrice(rp) + } + } + }) } type BidValidatorAPI struct { @@ -208,7 +235,6 @@ func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { Signature: bid.Signature, }, bv.auctionContract.BalanceOf, - bv.fetchReservePrice, ) if err != nil { return err @@ -222,18 +248,21 @@ func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return nil } -// TODO(Terence): Set reserve price from the contract. +func (bv *BidValidator) setReservePrice(p *big.Int) { + bv.reservePriceLock.Lock() + defer bv.reservePriceLock.Unlock() + bv.reservePrice = p +} + func (bv *BidValidator) fetchReservePrice() *big.Int { bv.reservePriceLock.RLock() defer bv.reservePriceLock.RUnlock() - return new(big.Int).Set(bv.reservePrice) + return bv.reservePrice } func (bv *BidValidator) validateBid( bid *Bid, - balanceCheckerFn func(opts *bind.CallOpts, addr common.Address) (*big.Int, error), - fetchReservePriceFn func() *big.Int, -) (*JsonValidatedBid, error) { + balanceCheckerFn func(opts *bind.CallOpts, account common.Address) (*big.Int, error)) (*JsonValidatedBid, error) { // Check basic integrity. if bid == nil { return nil, errors.Wrap(ErrMalformedData, "nil bid") @@ -270,9 +299,8 @@ func (bv *BidValidator) validateBid( } // Check bid is higher than reserve price. - reservePrice := fetchReservePriceFn() - if bid.Amount.Cmp(reservePrice) == -1 { - return nil, errors.Wrapf(ErrReservePriceNotMet, "reserve price %s, bid %s", reservePrice.String(), bid.Amount.String()) + if bid.Amount.Cmp(bv.reservePrice) == -1 { + return nil, errors.Wrapf(ErrReservePriceNotMet, "reserve price %s, bid %s", bv.reservePrice.String(), bid.Amount.String()) } // Validate the signature. From 3632c66b5d77d28f1d365661fce91c908809f4c1 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Fri, 23 Aug 2024 14:45:49 -0700 Subject: [PATCH 091/109] Filter transfer log Tristan's feedback --- execution/gethexec/express_lane_service.go | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index be4b96dbf4..6a517d64b6 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -135,7 +135,7 @@ func (es *expressLaneService) Start(ctxIn context.Context) { } it, err := es.auctionContract.FilterAuctionResolved(filterOpts, nil, nil, nil) if err != nil { - log.Error("Could not filter auction resolutions", "error", err) + log.Error("Could not filter auction resolutions event", "error", err) continue } for it.Next() { @@ -151,6 +151,36 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) es.Unlock() } + setExpressLaneIterator, err := es.auctionContract.FilterSetExpressLaneController(filterOpts, nil, nil, nil) + if err != nil { + log.Error("Could not filter express lane controller transfer event", "error", err) + continue + } + for setExpressLaneIterator.Next() { + round := setExpressLaneIterator.Event.Round + es.RLock() + roundInfo, ok := es.roundControl.Get(round) + es.RUnlock() + if !ok { + log.Warn("Could not find round info for express lane controller transfer event", "round", round) + continue + } + prevController := setExpressLaneIterator.Event.PreviousExpressLaneController + if roundInfo.controller != prevController { + log.Warn("New express lane controller did not match previous controller", + "round", round, + "previous", setExpressLaneIterator.Event.PreviousExpressLaneController, + "new", setExpressLaneIterator.Event.NewExpressLaneController) + continue + } + es.Lock() + newController := setExpressLaneIterator.Event.NewExpressLaneController + es.roundControl.Add(it.Event.Round, &expressLaneControl{ + controller: newController, + sequence: 0, + }) + es.Unlock() + } fromBlock = toBlock } } From 8122a49235c3e64442a946433db57eb9da58cc90 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 29 Aug 2024 09:27:27 -0700 Subject: [PATCH 092/109] Tristan's feedback --- timeboost/bid_validator.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index b7c8e43466..273570be18 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -67,7 +67,6 @@ type BidValidator struct { auctionContract *express_lane_auctiongen.ExpressLaneAuction auctionContractAddr common.Address bidsReceiver chan *Bid - bidCache *bidCache initialRoundTimestamp time.Time roundDuration time.Duration auctionClosingDuration time.Duration @@ -130,7 +129,6 @@ func NewBidValidator( auctionContract: auctionContract, auctionContractAddr: auctionContractAddr, bidsReceiver: make(chan *Bid, 10_000), - bidCache: newBidCache(), initialRoundTimestamp: initialTimestamp, roundDuration: roundDuration, auctionClosingDuration: auctionClosingDuration, @@ -298,7 +296,7 @@ func (bv *BidValidator) validateBid( return nil, errors.Wrap(ErrBadRoundNumber, "auction is closed") } - // Check bid is higher than reserve price. + // Check bid is higher than or equal to reserve price. if bid.Amount.Cmp(bv.reservePrice) == -1 { return nil, errors.Wrapf(ErrReservePriceNotMet, "reserve price %s, bid %s", bv.reservePrice.String(), bid.Amount.String()) } From 5c03cf04cc5b80deaea45abe56a0793b1574f28e Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 4 Sep 2024 18:55:57 -0700 Subject: [PATCH 093/109] Tristan's feedback --- execution/gethexec/express_lane_service.go | 6 ++++++ execution/gethexec/express_lane_service_test.go | 6 +++++- system_tests/timeboost_test.go | 2 +- timeboost/bid_validator.go | 4 ++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 6a517d64b6..d62c4176f4 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -209,6 +209,8 @@ func (es *expressLaneService) isWithinAuctionCloseWindow(arrivalTime time.Time) return timeToNextRound <= es.auctionClosing } +// Sequence express lane submission skips validation of the express lane message itself, +// as the core validator logic is handled in `validateExpressLaneTx“ func (es *expressLaneService) sequenceExpressLaneSubmission( ctx context.Context, msg *timeboost.ExpressLaneSubmission, @@ -292,6 +294,10 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu prefixed := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(signingMessage))), signingMessage...)) sigItem := make([]byte, len(msg.Signature)) copy(sigItem, msg.Signature) + + // Signature verification expects the last byte of the signature to have 27 subtracted, + // as it represents the recovery ID. If the last byte is greater than or equal to 27, it indicates a recovery ID that hasn't been adjusted yet, + // it's needed for internal signature verification logic. if sigItem[len(sigItem)-1] >= 27 { sigItem[len(sigItem)-1] -= 27 } diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 859015f51b..3561f3abca 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -137,7 +137,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { Signature: []byte{'b'}, Round: 100, }, - expectedErr: timeboost.ErrNoOnchainController, + expectedErr: timeboost.ErrBadRoundNumber, }, { name: "malformed signature", @@ -320,6 +320,10 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_outOfOrder(t *testing // We should have only published 2, as we are missing sequence number 3. require.Equal(t, 2, numPublished) require.Equal(t, len(messages), len(els.messagesBySequenceNumber)) + + err := els.sequenceExpressLaneSubmission(ctx, &timeboost.ExpressLaneSubmission{Sequence: 3}, publishFn) + require.NoError(t, err) + require.Equal(t, 5, numPublished) } func Test_expressLaneService_sequenceExpressLaneSubmission_erroredTx(t *testing.T) { diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 12a3c35b92..9f839ccdfa 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -218,7 +218,7 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test }(&wg) wg.Wait() if err2 == nil { - t.Fatal("Charlie should not be able to send tx with nonce 2") + t.Fatal("Charlie should not be able to send tx with nonce 1") } // After round is done, verify that Charlie beats Alice in the final sequence, and that the emitted txs // for Charlie are correct. diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 273570be18..6a4999531b 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -309,6 +309,10 @@ func (bv *BidValidator) validateBid( // Recover the public key. sigItem := make([]byte, len(bid.Signature)) copy(sigItem, bid.Signature) + + // Signature verification expects the last byte of the signature to have 27 subtracted, + // as it represents the recovery ID. If the last byte is greater than or equal to 27, it indicates a recovery ID that hasn't been adjusted yet, + // it's needed for internal signature verification logic. if sigItem[len(sigItem)-1] >= 27 { sigItem[len(sigItem)-1] -= 27 } From fa457b1702786bcb2ccf5158ac81bff0a12d77b9 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Tue, 1 Oct 2024 17:51:40 +0200 Subject: [PATCH 094/109] Fix autonomous-auctioner cli startup autonomous-auctioneer on the cli was failing to start becuase we were adding the "auth" config options without having the corresponding field on the AuctioneerConfig. We can add it back in later if needed. --- cmd/autonomous-auctioneer/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/autonomous-auctioneer/config.go b/cmd/autonomous-auctioneer/config.go index dba4684c97..74ca4340ed 100644 --- a/cmd/autonomous-auctioneer/config.go +++ b/cmd/autonomous-auctioneer/config.go @@ -81,7 +81,6 @@ func AuctioneerConfigAddOptions(f *flag.FlagSet) { genericconf.HTTPConfigAddOptions("http", f) genericconf.WSConfigAddOptions("ws", f) genericconf.IPCConfigAddOptions("ipc", f) - genericconf.AuthRPCConfigAddOptions("auth", f) f.Bool("metrics", AutonomousAuctioneerConfigDefault.Metrics, "enable metrics") genericconf.MetricsServerAddOptions("metrics-server", f) f.Bool("pprof", AutonomousAuctioneerConfigDefault.PProf, "enable pprof") From 0ff4f7db0706b924ba8ec0f5c61fd38043f4bddc Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Fri, 11 Oct 2024 11:54:24 +0200 Subject: [PATCH 095/109] Start rpc stack after creating bid validator RPC methods can't be registered after the stack is started. --- cmd/autonomous-auctioneer/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go index ab5caa3908..9007a74816 100644 --- a/cmd/autonomous-auctioneer/main.go +++ b/cmd/autonomous-auctioneer/main.go @@ -138,11 +138,6 @@ func mainImpl() int { flag.Usage() log.Crit("failed to initialize geth stack", "err", err) } - err = stack.Start() - if err != nil { - fatalErrChan <- fmt.Errorf("error starting stack: %w", err) - } - defer stack.Close() bidValidator, err := timeboost.NewBidValidator( ctx, stack, @@ -156,6 +151,11 @@ func mainImpl() int { log.Error("error initializing bid validator", "err", err) return 1 } + err = stack.Start() + if err != nil { + fatalErrChan <- fmt.Errorf("error starting stack: %w", err) + } + defer stack.Close() bidValidator.Start(ctx) } From 7a2eb14b49686cd72434b6c9a083011306f2d034 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Fri, 11 Oct 2024 17:48:17 +0200 Subject: [PATCH 096/109] Plumbing to be able to start timeboost in nitro --- execution/gethexec/express_lane_service.go | 4 +++ execution/gethexec/sequencer.go | 41 ++++++++++++++++++---- timeboost/bidder_client.go | 4 +-- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index d62c4176f4..5354b71bdb 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -187,6 +187,10 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) } +func (es *expressLaneService) StopAndWait() { + es.StopWaiter.StopAndWait() +} + func (es *expressLaneService) currentRoundHasController() bool { es.Lock() defer es.Unlock() diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 60413f19c2..1a5325a1a0 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -85,15 +85,19 @@ type SequencerConfig struct { } type TimeboostConfig struct { - Enable bool `koanf:"enable"` - ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` - SequencerHTTPEndpoint string `koanf:"sequencer-http-endpoint"` + Enable bool `koanf:"enable"` + AuctionContractAddress string `koanf:"auction-contract-address"` + AuctioneerAddress string `koanf:"auctioneer-address"` + ExpressLaneAdvantage time.Duration `koanf:"express-lane-advantage"` + SequencerHTTPEndpoint string `koanf:"sequencer-http-endpoint"` } var DefaultTimeboostConfig = TimeboostConfig{ - Enable: false, - ExpressLaneAdvantage: time.Millisecond * 200, - SequencerHTTPEndpoint: "http://localhost:9567", + Enable: false, + AuctionContractAddress: "", + AuctioneerAddress: "", + ExpressLaneAdvantage: time.Millisecond * 200, + SequencerHTTPEndpoint: "http://localhost:8547", } func (c *SequencerConfig) Validate() error { @@ -122,6 +126,19 @@ func (c *SequencerConfig) Validate() error { if c.MaxTxDataSize > arbostypes.MaxL2MessageSize-50000 { return errors.New("max-tx-data-size too large for MaxL2MessageSize") } + return c.Timeboost.Validate() +} + +func (c *TimeboostConfig) Validate() error { + if !c.Enable { + return nil + } + if len(c.AuctionContractAddress) > 0 && !common.IsHexAddress(c.AuctionContractAddress) { + return fmt.Errorf("invalid timeboost.auction-contract-address \"%v\"", c.AuctionContractAddress) + } + if len(c.AuctioneerAddress) > 0 && !common.IsHexAddress(c.AuctioneerAddress) { + return fmt.Errorf("invalid timeboost.auctioneer-address \"%v\"", c.AuctioneerAddress) + } return nil } @@ -170,6 +187,8 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { func TimeboostAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultTimeboostConfig.Enable, "enable timeboost based on express lane auctions") + 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") } @@ -1215,6 +1234,13 @@ func (s *Sequencer) Start(ctxIn context.Context) error { return 0 }) + if config.Timeboost.Enable { + s.StartExpressLane( + ctxIn, + common.HexToAddress(config.Timeboost.AuctionContractAddress), + common.HexToAddress(config.Timeboost.AuctioneerAddress)) + } + return nil } @@ -1242,6 +1268,9 @@ func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr co func (s *Sequencer) StopAndWait() { s.StopWaiter.StopAndWait() + if s.config().Timeboost.Enable { + s.expressLaneService.StopWaiter.StopAndWait() + } if s.txRetryQueue.Len() == 0 && len(s.txQueue) == 0 && s.nonceFailures.Len() == 0 { return } diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 8ca126d961..c51e9fa52d 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -32,12 +32,12 @@ type BidderClientConfig struct { } var DefaultBidderClientConfig = BidderClientConfig{ - ArbitrumNodeEndpoint: "http://localhost:9567", + ArbitrumNodeEndpoint: "http://localhost:8547", BidValidatorEndpoint: "http://localhost:9372", } var TestBidderClientConfig = BidderClientConfig{ - ArbitrumNodeEndpoint: "http://localhost:9567", + ArbitrumNodeEndpoint: "http://localhost:8547", BidValidatorEndpoint: "http://localhost:9372", } From d72fd38cd92bc6bacb95b08bd5c5717f8f1d8ef8 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Mon, 14 Oct 2024 15:09:39 +0200 Subject: [PATCH 097/109] Fix various linter and compilation issues --- execution/gethexec/express_lane_service_test.go | 3 ++- timeboost/auctioneer_test.go | 11 +++++++---- timeboost/bid_cache_test.go | 3 ++- timeboost/bid_validator.go | 2 +- timeboost/bid_validator_test.go | 9 +++------ 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 3561f3abca..39b7751b4f 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -215,7 +215,8 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { }, } - for _, tt := range tests { + for _, _tt := range tests { + tt := _tt t.Run(tt.name, func(t *testing.T) { if tt.sub != nil { tt.es.roundControl.Add(tt.sub.Round, &tt.control) diff --git a/timeboost/auctioneer_test.go b/timeboost/auctioneer_test.go index 71b1cef17a..951dee8845 100644 --- a/timeboost/auctioneer_test.go +++ b/timeboost/auctioneer_test.go @@ -35,7 +35,7 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { }) jwtFilePath := filepath.Join(tmpDir, "jwt.key") jwtSecret := common.BytesToHash([]byte("jwt")) - require.NoError(t, os.WriteFile(jwtFilePath, []byte(hexutil.Encode(jwtSecret[:])), 0644)) + require.NoError(t, os.WriteFile(jwtFilePath, []byte(hexutil.Encode(jwtSecret[:])), 0600)) // Set up multiple bid validators that will receive bids via RPC using a bidder client. // They inject their validated bids into a Redis stream that a single auctioneer instance @@ -130,9 +130,12 @@ func TestBidValidatorAuctioneerRedisStream(t *testing.T) { // Alice, Bob, and Charlie will submit bids to the three different bid validators instances. start := time.Now() for i := 1; i <= 5; i++ { - alice.Bid(ctx, big.NewInt(int64(i)), aliceAddr) - bob.Bid(ctx, big.NewInt(int64(i)+1), bobAddr) // Bob bids 1 wei higher than Alice. - charlie.Bid(ctx, big.NewInt(int64(i)+2), charlieAddr) // Charlie bids 2 wei higher than the Bob. + _, err = alice.Bid(ctx, big.NewInt(int64(i)), aliceAddr) + require.NoError(t, err) + _, err = bob.Bid(ctx, big.NewInt(int64(i)+1), bobAddr) // Bob bids 1 wei higher than Alice. + require.NoError(t, err) + _, err = charlie.Bid(ctx, big.NewInt(int64(i)+2), charlieAddr) // Charlie bids 2 wei higher than the Bob. + require.NoError(t, err) } // We expect that a final submission from each fails, as the bid limit is exceeded. diff --git a/timeboost/bid_cache_test.go b/timeboost/bid_cache_test.go index 62c249c539..c0aa7eafd5 100644 --- a/timeboost/bid_cache_test.go +++ b/timeboost/bid_cache_test.go @@ -157,7 +157,8 @@ func BenchmarkBidValidation(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - bv.validateBid(newBid, bv.auctionContract.BalanceOf, bv.fetchReservePrice) + _, err = bv.validateBid(newBid, bv.auctionContract.BalanceOf) + require.NoError(b, err) } } diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 6a4999531b..10512343ad 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -197,7 +197,7 @@ func (bv *BidValidator) Start(ctx_in context.Context) { case <-ctx.Done(): log.Error("Context closed, autonomous auctioneer shutting down") return - case _ = <-ticker.c: + case <-ticker.c: rp, err := bv.auctionContract.ReservePrice(&bind.CallOpts{}) if err != nil { log.Error("Could not get reserve price", "error", err) diff --git a/timeboost/bid_validator_test.go b/timeboost/bid_validator_test.go index 24552fc150..6532596ab3 100644 --- a/timeboost/bid_validator_test.go +++ b/timeboost/bid_validator_test.go @@ -116,7 +116,7 @@ func TestBidValidator_validateBid(t *testing.T) { bv.roundDuration = 0 } t.Run(tt.name, func(t *testing.T) { - _, err := bv.validateBid(tt.bid, setup.expressLaneAuction.BalanceOf, bv.fetchReservePrice) + _, err := bv.validateBid(tt.bid, setup.expressLaneAuction.BalanceOf) require.ErrorIs(t, err, tt.expectedErr) require.Contains(t, err.Error(), tt.errMsg) }) @@ -128,9 +128,6 @@ func TestBidValidator_validateBid_perRoundBidLimitReached(t *testing.T) { balanceCheckerFn := func(_ *bind.CallOpts, _ common.Address) (*big.Int, error) { return big.NewInt(10), nil } - fetchReservePriceFn := func() *big.Int { - return big.NewInt(0) - } auctionContractAddr := common.Address{'a'} bv := BidValidator{ chainId: big.NewInt(1), @@ -157,10 +154,10 @@ func TestBidValidator_validateBid_perRoundBidLimitReached(t *testing.T) { bid.Signature = signature for i := 0; i < int(bv.maxBidsPerSenderInRound); i++ { - _, err := bv.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) + _, err := bv.validateBid(bid, balanceCheckerFn) require.NoError(t, err) } - _, err = bv.validateBid(bid, balanceCheckerFn, fetchReservePriceFn) + _, err = bv.validateBid(bid, balanceCheckerFn) require.ErrorIs(t, err, ErrTooManyBids) } From 2cd1ed06230cb03569aec5060708d82a326590a9 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Mon, 14 Oct 2024 15:40:04 +0200 Subject: [PATCH 098/109] Fix cyclic dependency in test --- execution/gethexec/sequencer.go | 2 +- system_tests/timeboost_test.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 1a5325a1a0..f8efa1b517 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -1259,7 +1259,7 @@ func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr co s.execEngine.bc, ) if err != nil { - log.Crit("Failed to create express lane service", "err", err) + log.Crit("Failed to create express lane service", "err", err, "auctionContractAddr", auctionContractAddr) } s.auctioneerAddr = auctioneerAddr s.expressLaneService = els diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 9f839ccdfa..af02888e43 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -258,7 +258,7 @@ func setupExpressLaneAuction( builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() builderSeq.execConfig.Sequencer.Enable = true builderSeq.execConfig.Sequencer.Timeboost = gethexec.TimeboostConfig{ - Enable: true, + Enable: false, // We need to start without timeboost initially to create the auction contract ExpressLaneAdvantage: time.Second * 5, SequencerHTTPEndpoint: fmt.Sprintf("http://localhost:%d", seqPort), } @@ -411,6 +411,9 @@ func setupExpressLaneAuction( t.Fatal(err) } + // 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, proxyAddr, seqInfo.GetAddress("AuctionContract")) t.Log("Started express lane service in sequencer") From f4efcd907bdae40aa4ea0d7130bc36f66ba71a65 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Tue, 15 Oct 2024 14:09:29 +0200 Subject: [PATCH 099/109] Move where timeboost is started to avoid circ dep --- cmd/nitro/nitro.go | 8 ++++++++ execution/gethexec/sequencer.go | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index a052c146d1..c6096ab54e 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -676,6 +676,14 @@ func mainImpl() int { deferFuncs = append(deferFuncs, func() { blocksReExecutor.StopAndWait() }) } + execNodeConfig := execNode.ConfigFetcher() + if execNodeConfig.Sequencer.Enable && execNodeConfig.Sequencer.Timeboost.Enable { + execNode.Sequencer.StartExpressLane( + ctx, + common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctionContractAddress), + common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctioneerAddress)) + } + sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index f8efa1b517..683c596e10 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -1234,13 +1234,6 @@ func (s *Sequencer) Start(ctxIn context.Context) error { return 0 }) - if config.Timeboost.Enable { - s.StartExpressLane( - ctxIn, - common.HexToAddress(config.Timeboost.AuctionContractAddress), - common.HexToAddress(config.Timeboost.AuctioneerAddress)) - } - return nil } From 651277d07327527addd4cb01fd3041ec0bdd8594 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Tue, 15 Oct 2024 17:25:08 +0200 Subject: [PATCH 100/109] Temp fix for timeboost startup race cond, fix NPE --- cmd/nitro/nitro.go | 2 ++ execution/gethexec/express_lane_service.go | 4 ---- execution/gethexec/sequencer.go | 5 +++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index c6096ab54e..d91773d75c 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -678,6 +678,8 @@ func mainImpl() int { execNodeConfig := execNode.ConfigFetcher() if execNodeConfig.Sequencer.Enable && execNodeConfig.Sequencer.Timeboost.Enable { + log.Warn("TODO FIX RACE CONDITION sleeping for 10 seconds before starting express lane...") + time.Sleep(10 * time.Second) execNode.Sequencer.StartExpressLane( ctx, common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctionContractAddress), diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 5354b71bdb..d62c4176f4 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -187,10 +187,6 @@ func (es *expressLaneService) Start(ctxIn context.Context) { }) } -func (es *expressLaneService) StopAndWait() { - es.StopWaiter.StopAndWait() -} - func (es *expressLaneService) currentRoundHasController() bool { es.Lock() defer es.Unlock() diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 683c596e10..a10a39854b 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -1246,6 +1246,7 @@ func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr co log.Crit("Failed to connect to sequencer RPC client", "err", err) } seqClient := ethclient.NewClient(rpcClient) + els, err := newExpressLaneService( auctionContractAddr, seqClient, @@ -1261,8 +1262,8 @@ func (s *Sequencer) StartExpressLane(ctx context.Context, auctionContractAddr co func (s *Sequencer) StopAndWait() { s.StopWaiter.StopAndWait() - if s.config().Timeboost.Enable { - s.expressLaneService.StopWaiter.StopAndWait() + if s.config().Timeboost.Enable && s.expressLaneService != nil { + s.expressLaneService.StopAndWait() } if s.txRetryQueue.Len() == 0 && len(s.txQueue) == 0 && s.nonceFailures.Len() == 0 { return From 31ea7be2de4d83b78e7446f9aac7ae0aab98d809 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Wed, 16 Oct 2024 13:34:32 +0200 Subject: [PATCH 101/109] Retry initial call on express lane contract If the sequencer restarts just after the ExpressLaneAuction contract is deployed, it may not be fully synced up to that point when it starts up again. This commit adds in retries with exponential backoff up to 4 seconds. --- cmd/nitro/nitro.go | 18 ++++++++---------- execution/gethexec/express_lane_service.go | 11 +++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index d91773d75c..bea754d5ce 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -676,16 +676,6 @@ func mainImpl() int { deferFuncs = append(deferFuncs, func() { blocksReExecutor.StopAndWait() }) } - execNodeConfig := execNode.ConfigFetcher() - if execNodeConfig.Sequencer.Enable && execNodeConfig.Sequencer.Timeboost.Enable { - log.Warn("TODO FIX RACE CONDITION sleeping for 10 seconds before starting express lane...") - time.Sleep(10 * time.Second) - execNode.Sequencer.StartExpressLane( - ctx, - common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctionContractAddress), - common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctioneerAddress)) - } - sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) @@ -698,6 +688,14 @@ func mainImpl() int { } } + execNodeConfig := execNode.ConfigFetcher() + if execNodeConfig.Sequencer.Enable && execNodeConfig.Sequencer.Timeboost.Enable { + execNode.Sequencer.StartExpressLane( + ctx, + common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctionContractAddress), + common.HexToAddress(execNodeConfig.Sequencer.Timeboost.AuctioneerAddress)) + } + err = nil select { case err = <-fatalErrChan: diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index d62c4176f4..0412edfed7 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -55,8 +55,19 @@ func newExpressLaneService( if err != nil { return nil, err } + + retries := 0 +pending: roundTimingInfo, err := auctionContract.RoundTimingInfo(&bind.CallOpts{}) if err != nil { + const maxRetries = 5 + if errors.Is(err, bind.ErrNoCode) && retries < maxRetries { + wait := time.Millisecond * 250 * (1 << retries) + log.Info("ExpressLaneAuction contract not ready, will retry afer wait", "err", err, "auctionContractAddr", auctionContractAddr, "wait", wait, "maxRetries", maxRetries) + retries++ + time.Sleep(wait) + goto pending + } return nil, err } initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) From d1890daee44d553d069e634c19c9e94860bef9d5 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Fri, 18 Oct 2024 15:49:16 +0200 Subject: [PATCH 102/109] Add bidder-client exe for timeboost deposits/bids --- Dockerfile | 1 + Makefile | 5 +- cmd/autonomous-auctioneer/main.go | 2 +- cmd/bidder-client/main.go | 95 +++++++++++++++++++++++++++++++ timeboost/bidder_client.go | 59 +++++++++++++++++-- 5 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 cmd/bidder-client/main.go diff --git a/Dockerfile b/Dockerfile index 9f43dc79c4..459412ca05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -297,6 +297,7 @@ USER root COPY --from=prover-export /bin/jit /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/daserver /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/autonomous-auctioneer /usr/local/bin/ +COPY --from=node-builder /workspace/target/bin/bidder-client /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/datool /usr/local/bin/ COPY --from=nitro-legacy /home/user/target/machines /home/user/nitro-legacy/machines RUN rm -rf /workspace/target/legacy-machines/latest diff --git a/Makefile b/Makefile index 6d31ae2678..b49a7bafe1 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ all: build build-replay-env test-gen-proofs @touch .make/all .PHONY: build -build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daserver autonomous-auctioneer datool seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv) +build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daserver autonomous-auctioneer bidder-client datool seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv) @printf $(done) .PHONY: build-node-deps @@ -301,6 +301,9 @@ $(output_root)/bin/daserver: $(DEP_PREDICATE) build-node-deps $(output_root)/bin/autonomous-auctioneer: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/autonomous-auctioneer" +$(output_root)/bin/bidder-client: $(DEP_PREDICATE) build-node-deps + go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/bidder-client" + $(output_root)/bin/datool: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/datool" diff --git a/cmd/autonomous-auctioneer/main.go b/cmd/autonomous-auctioneer/main.go index 9007a74816..e1e540c4a1 100644 --- a/cmd/autonomous-auctioneer/main.go +++ b/cmd/autonomous-auctioneer/main.go @@ -144,7 +144,7 @@ func mainImpl() int { func() *timeboost.BidValidatorConfig { return &liveNodeConfig.Get().BidValidator }, ) if err != nil { - log.Error("Error creating new auctioneer", "error", err) + log.Error("Error creating new bid validator", "error", err) return 1 } if err = bidValidator.Initialize(ctx); err != nil { diff --git a/cmd/bidder-client/main.go b/cmd/bidder-client/main.go new file mode 100644 index 0000000000..133b27f498 --- /dev/null +++ b/cmd/bidder-client/main.go @@ -0,0 +1,95 @@ +package main + +import ( + "context" + "errors" + "fmt" + "math/big" + "os" + + flag "github.com/spf13/pflag" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/cmd/util/confighelpers" + "github.com/offchainlabs/nitro/timeboost" +) + +func printSampleUsage(name string) { + fmt.Printf("Sample usage: %s --help \n", name) +} + +func main() { + if err := mainImpl(); err != nil { + log.Error("Error running bidder-client", "err", err) + os.Exit(1) + } + os.Exit(0) +} + +func mainImpl() error { + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + args := os.Args[1:] + bidderClientConfig, err := parseBidderClientArgs(ctx, args) + if err != nil { + confighelpers.PrintErrorAndExit(err, printSampleUsage) + return err + } + + configFetcher := func() *timeboost.BidderClientConfig { + return bidderClientConfig + } + + bidderClient, err := timeboost.NewBidderClient(ctx, configFetcher) + if err != nil { + return err + } + + if bidderClientConfig.DepositGwei > 0 && bidderClientConfig.BidGwei > 0 { + return errors.New("--deposit-gwei and --bid-gwei can't both be set, either make a deposit or a bid") + } + + if bidderClientConfig.DepositGwei > 0 { + err = bidderClient.Deposit(ctx, big.NewInt(int64(bidderClientConfig.DepositGwei)*1_000_000_000)) + if err == nil { + log.Info("Depsoit successful") + } + return err + } + + if bidderClientConfig.BidGwei > 0 { + bidderClient.Start(ctx) + bid, err := bidderClient.Bid(ctx, big.NewInt(int64(bidderClientConfig.BidGwei)*1_000_000_000), common.Address{}) + if err == nil { + log.Info("Bid submitted successfully", "bid", bid) + } + return err + } + + return errors.New("select one of --deposit-gwei or --bid-gwei") +} + +func parseBidderClientArgs(ctx context.Context, args []string) (*timeboost.BidderClientConfig, error) { + f := flag.NewFlagSet("", flag.ContinueOnError) + + timeboost.BidderClientConfigAddOptions(f) + + k, err := confighelpers.BeginCommonParse(f, args) + if err != nil { + return nil, err + } + + err = confighelpers.ApplyOverrides(f, k) + if err != nil { + return nil, err + } + + var cfg timeboost.BidderClientConfig + if err := confighelpers.EndCommonParse(k, &cfg); err != nil { + return nil, err + } + + return &cfg, nil +} diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index c51e9fa52d..884cfe8acc 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -11,10 +11,12 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/cmd/util" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/timeboost/bindings" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -29,6 +31,8 @@ type BidderClientConfig struct { ArbitrumNodeEndpoint string `koanf:"arbitrum-node-endpoint"` BidValidatorEndpoint string `koanf:"bid-validator-endpoint"` AuctionContractAddress string `koanf:"auction-contract-address"` + DepositGwei int `koanf:"deposit-gwei"` + BidGwei int `koanf:"bid-gwei"` } var DefaultBidderClientConfig = BidderClientConfig{ @@ -41,21 +45,25 @@ var TestBidderClientConfig = BidderClientConfig{ BidValidatorEndpoint: "http://localhost:9372", } -func BidderClientConfigAddOptions(prefix string, f *pflag.FlagSet) { - genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") - f.String(prefix+".arbitrum-node-endpoint", DefaultBidderClientConfig.ArbitrumNodeEndpoint, "arbitrum node RPC http endpoint") - f.String(prefix+".bid-validator-endpoint", DefaultBidderClientConfig.BidValidatorEndpoint, "bid validator http endpoint") - f.String(prefix+".auction-contract-address", DefaultBidderClientConfig.AuctionContractAddress, "express lane auction contract address") +func BidderClientConfigAddOptions(f *pflag.FlagSet) { + genericconf.WalletConfigAddOptions("wallet", f, "wallet for bidder") + f.String("arbitrum-node-endpoint", DefaultBidderClientConfig.ArbitrumNodeEndpoint, "arbitrum node RPC http endpoint") + f.String("bid-validator-endpoint", DefaultBidderClientConfig.BidValidatorEndpoint, "bid validator http endpoint") + f.String("auction-contract-address", DefaultBidderClientConfig.AuctionContractAddress, "express lane auction contract address") + f.Int("deposit-gwei", DefaultBidderClientConfig.DepositGwei, "deposit amount in gwei to take from bidder's account and send to auction contract") + f.Int("bid-gwei", DefaultBidderClientConfig.BidGwei, "bid amount in gwei, bidder must have already deposited enough into the auction contract") } type BidderClient struct { stopwaiter.StopWaiter chainId *big.Int auctionContractAddress common.Address + biddingTokenAddress common.Address txOpts *bind.TransactOpts client *ethclient.Client signer signature.DataSignerFunc auctionContract *express_lane_auctiongen.ExpressLaneAuction + biddingTokenContract *bindings.MockERC20 auctioneerClient *rpc.Client initialRoundTimestamp time.Time roundDuration time.Duration @@ -97,6 +105,17 @@ func NewBidderClient( return nil, errors.Wrap(err, "opening wallet") } + biddingTokenAddr, err := auctionContract.BiddingToken(&bind.CallOpts{ + Context: ctx, + }) + if err != nil { + return nil, errors.Wrap(err, "fetching bidding token") + } + biddingTokenContract, err := bindings.NewMockERC20(biddingTokenAddr, arbClient) + if err != nil { + return nil, errors.Wrap(err, "creating bindings to bidding token contract") + } + bidValidatorClient, err := rpc.DialContext(ctx, cfg.BidValidatorEndpoint) if err != nil { return nil, err @@ -104,10 +123,12 @@ func NewBidderClient( return &BidderClient{ chainId: chainId, auctionContractAddress: auctionContractAddr, + biddingTokenAddress: biddingTokenAddr, client: arbClient, txOpts: txOpts, signer: signer, auctionContract: auctionContract, + biddingTokenContract: biddingTokenContract, auctioneerClient: bidValidatorClient, initialRoundTimestamp: initialTimestamp, roundDuration: roundDuration, @@ -119,7 +140,32 @@ func (bd *BidderClient) Start(ctx_in context.Context) { bd.StopWaiter.Start(ctx_in, bd) } +// Deposit into the auction contract for the account configured by the BidderClient wallet. +// Handles approving the auction contract to spend the erc20 on behalf of the account. func (bd *BidderClient) Deposit(ctx context.Context, amount *big.Int) error { + allowance, err := bd.biddingTokenContract.Allowance(&bind.CallOpts{ + Context: ctx, + }, bd.txOpts.From, bd.auctionContractAddress) + if err != nil { + return err + } + + if amount.Cmp(allowance) > 0 { + log.Info("Spend allowance of bidding token from auction contract is insufficient, increasing allowance", "from", bd.txOpts.From, "auctionContract", bd.auctionContractAddress, "biddingToken", bd.biddingTokenAddress, "amount", amount.Int64()) + // defecit := arbmath.BigSub(allowance, amount) + tx, err := bd.biddingTokenContract.Approve(bd.txOpts, bd.auctionContractAddress, amount) + if err != nil { + return err + } + receipt, err := bind.WaitMined(ctx, bd.client, tx) + if err != nil { + return err + } + if receipt.Status != types.ReceiptStatusSuccessful { + return errors.New("approval failed") + } + } + tx, err := bd.auctionContract.Deposit(bd.txOpts, amount) if err != nil { return err @@ -137,6 +183,9 @@ func (bd *BidderClient) Deposit(ctx context.Context, amount *big.Int) error { func (bd *BidderClient) Bid( ctx context.Context, amount *big.Int, expressLaneController common.Address, ) (*Bid, error) { + if (expressLaneController == common.Address{}) { + expressLaneController = bd.txOpts.From + } newBid := &Bid{ ChainId: bd.chainId, ExpressLaneController: expressLaneController, From 103b39564781c36f5556781ee339e74444b2056a Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Wed, 23 Oct 2024 12:28:11 +0200 Subject: [PATCH 103/109] Clear bidsPerSenderInRound when auction closes --- timeboost/bid_validator.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 10512343ad..61360d0d8d 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -211,6 +211,10 @@ func (bv *BidValidator) Start(ctx_in context.Context) { log.Info("Reserve price updated", "old", currentReservePrice.String(), "new", rp.String()) bv.setReservePrice(rp) + + bv.Lock() + bv.bidsPerSenderInRound = make(map[common.Address]uint8) + bv.Unlock() } } }) From cad5b126a3afd90be4f08c9a0883dbd85e8cb8dc Mon Sep 17 00:00:00 2001 From: TucksonDev Date: Fri, 25 Oct 2024 12:00:47 +0100 Subject: [PATCH 104/109] Fix SequenceNumber attribute and decode order --- execution/gethexec/express_lane_service.go | 12 +++++----- .../gethexec/express_lane_service_test.go | 24 +++++++++---------- system_tests/timeboost_test.go | 2 +- timeboost/types.go | 14 +++++------ 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 0412edfed7..c9e9d7209a 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -239,19 +239,19 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( return timeboost.ErrNoOnchainController } // Check if the submission nonce is too low. - if msg.Sequence < control.sequence { + if msg.SequenceNumber < control.sequence { return timeboost.ErrSequenceNumberTooLow } // Check if a duplicate submission exists already, and reject if so. - if _, exists := es.messagesBySequenceNumber[msg.Sequence]; exists { + if _, exists := es.messagesBySequenceNumber[msg.SequenceNumber]; exists { return timeboost.ErrDuplicateSequenceNumber } // Log an informational warning if the message's sequence number is in the future. - if msg.Sequence > control.sequence { - log.Warn("Received express lane submission with future sequence number", "sequence", msg.Sequence) + if msg.SequenceNumber > control.sequence { + log.Warn("Received express lane submission with future sequence number", "sequence", msg.SequenceNumber) } // Put into the sequence number map. - es.messagesBySequenceNumber[msg.Sequence] = msg + es.messagesBySequenceNumber[msg.SequenceNumber] = msg for { // Get the next message in the sequence. @@ -266,7 +266,7 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( false, /* no delay, as it should go through express lane */ ); err != nil { // If the tx failed, clear it from the sequence map. - delete(es.messagesBySequenceNumber, msg.Sequence) + delete(es.messagesBySequenceNumber, msg.SequenceNumber) return err } // Increase the global round sequence number. diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 39b7751b4f..9f19cde0e3 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -242,7 +242,7 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_nonceTooLow(t *testin sequence: 1, }) msg := &timeboost.ExpressLaneSubmission{ - Sequence: 0, + SequenceNumber: 0, } publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { return nil @@ -262,7 +262,7 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_duplicateNonce(t *tes sequence: 1, }) msg := &timeboost.ExpressLaneSubmission{ - Sequence: 2, + SequenceNumber: 2, } numPublished := 0 publishFn := func(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions, delay bool) error { @@ -299,19 +299,19 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_outOfOrder(t *testing } messages := []*timeboost.ExpressLaneSubmission{ { - Sequence: 10, + SequenceNumber: 10, }, { - Sequence: 5, + SequenceNumber: 5, }, { - Sequence: 1, + SequenceNumber: 1, }, { - Sequence: 4, + SequenceNumber: 4, }, { - Sequence: 2, + SequenceNumber: 2, }, } for _, msg := range messages { @@ -322,7 +322,7 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_outOfOrder(t *testing require.Equal(t, 2, numPublished) require.Equal(t, len(messages), len(els.messagesBySequenceNumber)) - err := els.sequenceExpressLaneSubmission(ctx, &timeboost.ExpressLaneSubmission{Sequence: 3}, publishFn) + err := els.sequenceExpressLaneSubmission(ctx, &timeboost.ExpressLaneSubmission{SequenceNumber: 3}, publishFn) require.NoError(t, err) require.Equal(t, 5, numPublished) } @@ -350,19 +350,19 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_erroredTx(t *testing. } messages := []*timeboost.ExpressLaneSubmission{ { - Sequence: 1, + SequenceNumber: 1, Transaction: &types.Transaction{}, }, { - Sequence: 3, + SequenceNumber: 3, Transaction: &types.Transaction{}, }, { - Sequence: 2, + SequenceNumber: 2, Transaction: nil, }, { - Sequence: 2, + SequenceNumber: 2, Transaction: &types.Transaction{}, }, } diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index af02888e43..6eaea7e1bb 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -676,7 +676,7 @@ func (elc *expressLaneClient) SendTransaction(ctx context.Context, transaction * Round: hexutil.Uint64(timeboost.CurrentRound(elc.initialRoundTimestamp, elc.roundDuration)), AuctionContractAddress: elc.auctionContractAddr, Transaction: encodedTx, - Sequence: hexutil.Uint64(elc.sequence), + SequenceNumber: hexutil.Uint64(elc.sequence), Signature: hexutil.Bytes{}, } msgGo, err := timeboost.JsonSubmissionToGo(msg) diff --git a/timeboost/types.go b/timeboost/types.go index 428027730a..146bbc3740 100644 --- a/timeboost/types.go +++ b/timeboost/types.go @@ -139,7 +139,7 @@ type JsonExpressLaneSubmission struct { AuctionContractAddress common.Address `json:"auctionContractAddress"` Transaction hexutil.Bytes `json:"transaction"` Options *arbitrum_types.ConditionalOptions `json:"options"` - Sequence hexutil.Uint64 + SequenceNumber hexutil.Uint64 Signature hexutil.Bytes `json:"signature"` } @@ -149,7 +149,7 @@ type ExpressLaneSubmission struct { AuctionContractAddress common.Address Transaction *types.Transaction Options *arbitrum_types.ConditionalOptions `json:"options"` - Sequence uint64 + SequenceNumber uint64 Signature []byte } @@ -164,7 +164,7 @@ func JsonSubmissionToGo(submission *JsonExpressLaneSubmission) (*ExpressLaneSubm AuctionContractAddress: submission.AuctionContractAddress, Transaction: tx, Options: submission.Options, - Sequence: uint64(submission.Sequence), + SequenceNumber: uint64(submission.SequenceNumber), Signature: submission.Signature, }, nil } @@ -180,7 +180,7 @@ func (els *ExpressLaneSubmission) ToJson() (*JsonExpressLaneSubmission, error) { AuctionContractAddress: els.AuctionContractAddress, Transaction: encoded, Options: els.Options, - Sequence: hexutil.Uint64(els.Sequence), + SequenceNumber: hexutil.Uint64(els.SequenceNumber), Signature: els.Signature, }, nil } @@ -189,13 +189,13 @@ func (els *ExpressLaneSubmission) ToMessageBytes() ([]byte, error) { buf := new(bytes.Buffer) buf.Write(domainValue) buf.Write(padBigInt(els.ChainId)) - seqBuf := make([]byte, 8) - binary.BigEndian.PutUint64(seqBuf, els.Sequence) - buf.Write(seqBuf) buf.Write(els.AuctionContractAddress[:]) roundBuf := make([]byte, 8) binary.BigEndian.PutUint64(roundBuf, els.Round) buf.Write(roundBuf) + seqBuf := make([]byte, 8) + binary.BigEndian.PutUint64(seqBuf, els.SequenceNumber) + buf.Write(seqBuf) rlpTx, err := els.Transaction.MarshalBinary() if err != nil { return nil, err From b6ea4f589707b570b4095fde4acba2b431ebeebf Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 28 Oct 2024 19:22:13 +0530 Subject: [PATCH 105/109] Fix panic when transferring express lane controller due to race in lru cache --- execution/gethexec/express_lane_service.go | 12 ++----- .../gethexec/express_lane_service_test.go | 32 +++++++++---------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 0412edfed7..55885f4b47 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -41,7 +41,7 @@ type expressLaneService struct { logs chan []*types.Log seqClient *ethclient.Client auctionContract *express_lane_auctiongen.ExpressLaneAuction - roundControl lru.BasicLRU[uint64, *expressLaneControl] + roundControl *lru.Cache[uint64, *expressLaneControl] messagesBySequenceNumber map[uint64]*timeboost.ExpressLaneSubmission } @@ -78,7 +78,7 @@ pending: chainConfig: chainConfig, initialTimestamp: initialTimestamp, auctionClosing: auctionClosingDuration, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), // Keep 8 rounds cached. + roundControl: lru.NewCache[uint64, *expressLaneControl](8), // Keep 8 rounds cached. auctionContractAddr: auctionContractAddr, roundDuration: roundDuration, seqClient: sequencerClient, @@ -155,12 +155,10 @@ func (es *expressLaneService) Start(ctxIn context.Context) { "round", it.Event.Round, "controller", it.Event.FirstPriceExpressLaneController, ) - es.Lock() es.roundControl.Add(it.Event.Round, &expressLaneControl{ controller: it.Event.FirstPriceExpressLaneController, sequence: 0, }) - es.Unlock() } setExpressLaneIterator, err := es.auctionContract.FilterSetExpressLaneController(filterOpts, nil, nil, nil) if err != nil { @@ -169,9 +167,7 @@ func (es *expressLaneService) Start(ctxIn context.Context) { } for setExpressLaneIterator.Next() { round := setExpressLaneIterator.Event.Round - es.RLock() roundInfo, ok := es.roundControl.Get(round) - es.RUnlock() if !ok { log.Warn("Could not find round info for express lane controller transfer event", "round", round) continue @@ -184,13 +180,11 @@ func (es *expressLaneService) Start(ctxIn context.Context) { "new", setExpressLaneIterator.Event.NewExpressLaneController) continue } - es.Lock() newController := setExpressLaneIterator.Event.NewExpressLaneController es.roundControl.Add(it.Event.Round, &expressLaneControl{ controller: newController, sequence: 0, }) - es.Unlock() } fromBlock = toBlock } @@ -317,8 +311,6 @@ func (es *expressLaneService) validateExpressLaneTx(msg *timeboost.ExpressLaneSu return timeboost.ErrMalformedData } sender := crypto.PubkeyToAddress(*pubkey) - es.RLock() - defer es.RUnlock() control, ok := es.roundControl.Get(msg.Round) if !ok { return timeboost.ErrNoOnchainController diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 39b7751b4f..a237c86a42 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -45,7 +45,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { name: "nil msg", sub: nil, es: &expressLaneService{ - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, expectedErr: timeboost.ErrMalformedData, }, @@ -53,7 +53,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { name: "nil tx", sub: &timeboost.ExpressLaneSubmission{}, es: &expressLaneService{ - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, expectedErr: timeboost.ErrMalformedData, }, @@ -63,7 +63,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { Transaction: &types.Transaction{}, }, es: &expressLaneService{ - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, expectedErr: timeboost.ErrMalformedData, }, @@ -73,7 +73,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(2), @@ -89,7 +89,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(1), @@ -106,7 +106,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, sub: &timeboost.ExpressLaneSubmission{ ChainId: big.NewInt(1), @@ -125,7 +125,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, control: expressLaneControl{ controller: common.Address{'b'}, @@ -148,7 +148,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, control: expressLaneControl{ controller: common.Address{'b'}, @@ -171,7 +171,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, control: expressLaneControl{ controller: common.Address{'b'}, @@ -188,7 +188,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, control: expressLaneControl{ controller: common.Address{'b'}, @@ -205,7 +205,7 @@ func Test_expressLaneService_validateExpressLaneTx(t *testing.T) { chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), }, control: expressLaneControl{ controller: crypto.PubkeyToAddress(testPriv.PublicKey), @@ -236,7 +236,7 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_nonceTooLow(t *testin defer cancel() els := &expressLaneService{ messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), } els.roundControl.Add(0, &expressLaneControl{ sequence: 1, @@ -255,7 +255,7 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_duplicateNonce(t *tes ctx, cancel := context.WithCancel(context.Background()) defer cancel() els := &expressLaneService{ - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), } els.roundControl.Add(0, &expressLaneControl{ @@ -283,7 +283,7 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_outOfOrder(t *testing ctx, cancel := context.WithCancel(context.Background()) defer cancel() els := &expressLaneService{ - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), } els.roundControl.Add(0, &expressLaneControl{ @@ -331,7 +331,7 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_erroredTx(t *testing. ctx, cancel := context.WithCancel(context.Background()) defer cancel() els := &expressLaneService{ - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), messagesBySequenceNumber: make(map[uint64]*timeboost.ExpressLaneSubmission), } els.roundControl.Add(0, &expressLaneControl{ @@ -440,7 +440,7 @@ func Benchmark_expressLaneService_validateExpressLaneTx(b *testing.B) { auctionContractAddr: common.HexToAddress("0x2Aef36410182881a4b13664a1E079762D7F716e6"), initialTimestamp: time.Now(), roundDuration: time.Minute, - roundControl: lru.NewBasicLRU[uint64, *expressLaneControl](8), + roundControl: lru.NewCache[uint64, *expressLaneControl](8), chainConfig: ¶ms.ChainConfig{ ChainID: big.NewInt(1), }, From 1cb97313f38e0ff3e8a968f2566d5ba8059fb615 Mon Sep 17 00:00:00 2001 From: Tristan-Wilson <87238672+Tristan-Wilson@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:07:25 +0100 Subject: [PATCH 106/109] Update execution/gethexec/express_lane_service.go --- execution/gethexec/express_lane_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index c9e9d7209a..d23f402946 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -248,7 +248,7 @@ func (es *expressLaneService) sequenceExpressLaneSubmission( } // Log an informational warning if the message's sequence number is in the future. if msg.SequenceNumber > control.sequence { - log.Warn("Received express lane submission with future sequence number", "sequence", msg.SequenceNumber) + log.Warn("Received express lane submission with future sequence number", "SequenceNumber", msg.SequenceNumber) } // Put into the sequence number map. es.messagesBySequenceNumber[msg.SequenceNumber] = msg From 4567b0a4efcdc8bd4a070fd20db03fb1d61b3371 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Wed, 30 Oct 2024 11:42:50 +0530 Subject: [PATCH 107/109] Fix typo causing panic --- execution/gethexec/express_lane_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 89ba5d00af..cb8162d6e6 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -181,7 +181,7 @@ func (es *expressLaneService) Start(ctxIn context.Context) { continue } newController := setExpressLaneIterator.Event.NewExpressLaneController - es.roundControl.Add(it.Event.Round, &expressLaneControl{ + es.roundControl.Add(round, &expressLaneControl{ controller: newController, sequence: 0, }) From aaf051ff40ac77d75290ed9d48e0414b164017da Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Mon, 4 Nov 2024 11:38:53 +0100 Subject: [PATCH 108/109] Post merge fixes, mostly stricter linter from 1.23 --- execution/gethexec/express_lane_service.go | 7 ++++--- .../gethexec/express_lane_service_test.go | 16 ++++++++-------- go.mod | 16 +++++++++++++--- go.sum | 19 +++++++++++++++---- system_tests/timeboost_test.go | 8 ++++---- timeboost/auctioneer.go | 9 +++++---- timeboost/bid_validator.go | 9 +++++---- timeboost/bidder_client.go | 4 +++- timeboost/setup_test.go | 2 +- timeboost/ticker.go | 6 ++++-- 10 files changed, 62 insertions(+), 34 deletions(-) diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index cb8162d6e6..2505afaebe 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" ) @@ -70,9 +71,9 @@ pending: } return nil, err } - initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) - roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second - auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second + initialTimestamp := time.Unix(roundTimingInfo.OffsetTimestamp, 0) + roundDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.RoundDurationSeconds) * time.Second + auctionClosingDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.AuctionClosingSeconds) * time.Second return &expressLaneService{ auctionContract: auctionContract, chainConfig: chainConfig, diff --git a/execution/gethexec/express_lane_service_test.go b/execution/gethexec/express_lane_service_test.go index 678dc86414..7b01dc757e 100644 --- a/execution/gethexec/express_lane_service_test.go +++ b/execution/gethexec/express_lane_service_test.go @@ -350,20 +350,20 @@ func Test_expressLaneService_sequenceExpressLaneSubmission_erroredTx(t *testing. } messages := []*timeboost.ExpressLaneSubmission{ { - SequenceNumber: 1, - Transaction: &types.Transaction{}, + SequenceNumber: 1, + Transaction: &types.Transaction{}, }, { - SequenceNumber: 3, - Transaction: &types.Transaction{}, + SequenceNumber: 3, + Transaction: &types.Transaction{}, }, { - SequenceNumber: 2, - Transaction: nil, + SequenceNumber: 2, + Transaction: nil, }, { - SequenceNumber: 2, - Transaction: &types.Transaction{}, + SequenceNumber: 2, + Transaction: &types.Transaction{}, }, } for _, msg := range messages { diff --git a/go.mod b/go.mod index 21ba319ea1..40ba981524 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ replace github.com/VictoriaMetrics/fastcache => ./fastcache replace github.com/ethereum/go-ethereum => ./go-ethereum require ( - github.com/DATA-DOG/go-sqlmock v1.5.2 cloud.google.com/go/storage v1.43.0 + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible github.com/Shopify/toxiproxy v2.1.4+incompatible github.com/alicebob/miniredis/v2 v2.32.1 @@ -29,6 +29,7 @@ require ( github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.2.1 github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/btree v1.1.2 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 @@ -44,12 +45,12 @@ require ( github.com/redis/go-redis/v9 v9.6.1 github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/wealdtech/go-merkletree v1.0.0 golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/sync v0.5.0 + golang.org/x/sync v0.7.0 golang.org/x/sys v0.21.0 golang.org/x/term v0.21.0 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d @@ -66,19 +67,28 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.18.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( diff --git a/go.sum b/go.sum index 1ecfd83517..2aacdc857b 100644 --- a/go.sum +++ b/go.sum @@ -294,6 +294,7 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -396,6 +397,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= @@ -458,6 +460,7 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -597,19 +600,25 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -868,6 +877,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -949,6 +959,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1047,6 +1058,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -1056,7 +1068,6 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 6eaea7e1bb..2b8db0a9c9 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -67,8 +67,8 @@ func TestSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing expressLaneClient := newExpressLaneClient( bobPriv, chainId, - time.Unix(int64(info.OffsetTimestamp), 0), - time.Duration(info.RoundDurationSeconds)*time.Second, + time.Unix(info.OffsetTimestamp, 0), + arbmath.SaturatingCast[time.Duration](info.RoundDurationSeconds)*time.Second, auctionContractAddr, seqDial, ) @@ -158,7 +158,7 @@ func TestSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected(t *test bobPriv, chainId, time.Unix(int64(info.OffsetTimestamp), 0), - time.Duration(info.RoundDurationSeconds)*time.Second, + arbmath.SaturatingCast[time.Duration](info.RoundDurationSeconds)*time.Second, auctionContractAddr, seqDial, ) @@ -357,7 +357,7 @@ func setupExpressLaneAuction( BiddingToken: biddingToken, Beneficiary: beneficiary, RoundTimingInfo: express_lane_auctiongen.RoundTimingInfo{ - OffsetTimestamp: initialTimestamp.Uint64(), + OffsetTimestamp: initialTimestamp.Int64(), RoundDurationSeconds: bidRoundSeconds, AuctionClosingSeconds: auctionClosingSeconds, ReserveSubmissionSeconds: reserveSubmissionSeconds, diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 86225ff2f5..946bbf4d50 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -24,6 +24,7 @@ import ( "github.com/offchainlabs/nitro/cmd/util" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" @@ -185,9 +186,9 @@ func NewAuctioneerServer(ctx context.Context, configFetcher AuctioneerServerConf if err != nil { return nil, err } - auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second - initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) - roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + auctionClosingDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.AuctionClosingSeconds) * time.Second + initialTimestamp := time.Unix(roundTimingInfo.OffsetTimestamp, 0) + roundDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.RoundDurationSeconds) * time.Second return &AuctioneerServer{ txOpts: txOpts, sequencerRpc: client, @@ -364,7 +365,7 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { } currentRound := CurrentRound(a.initialRoundTimestamp, a.roundDuration) - roundEndTime := a.initialRoundTimestamp.Add(time.Duration(currentRound) * a.roundDuration) + roundEndTime := a.initialRoundTimestamp.Add(arbmath.SaturatingCast[time.Duration](currentRound) * a.roundDuration) retryInterval := 1 * time.Second if err := retryUntil(ctx, func() error { diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 61360d0d8d..b4b966d38b 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -14,12 +14,13 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" - "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/pkg/errors" + "github.com/redis/go-redis/v9" "github.com/spf13/pflag" ) @@ -113,9 +114,9 @@ func NewBidValidator( return nil, err } initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) - roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second - auctionClosingDuration := time.Duration(roundTimingInfo.AuctionClosingSeconds) * time.Second - reserveSubmissionDuration := time.Duration(roundTimingInfo.ReserveSubmissionSeconds) * time.Second + roundDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.RoundDurationSeconds) * time.Second + auctionClosingDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.AuctionClosingSeconds) * time.Second + reserveSubmissionDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.ReserveSubmissionSeconds) * time.Second reservePrice, err := auctionContract.ReservePrice(&bind.CallOpts{}) if err != nil { diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 884cfe8acc..b48d8d4513 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -17,6 +17,7 @@ import ( "github.com/offchainlabs/nitro/cmd/util" "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" "github.com/offchainlabs/nitro/timeboost/bindings" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -75,6 +76,7 @@ func NewBidderClient( configFetcher BidderClientConfigFetcher, ) (*BidderClient, error) { cfg := configFetcher() + _, _ = cfg.BidGwei, cfg.DepositGwei if cfg.AuctionContractAddress == "" { return nil, fmt.Errorf("auction contract address cannot be empty") } @@ -99,7 +101,7 @@ func NewBidderClient( return nil, err } initialTimestamp := time.Unix(int64(roundTimingInfo.OffsetTimestamp), 0) - roundDuration := time.Duration(roundTimingInfo.RoundDurationSeconds) * time.Second + roundDuration := arbmath.SaturatingCast[time.Duration](roundTimingInfo.RoundDurationSeconds) * time.Second txOpts, signer, err := util.OpenWallet("bidder-client", &cfg.Wallet, chainId) if err != nil { return nil, errors.Wrap(err, "opening wallet") diff --git a/timeboost/setup_test.go b/timeboost/setup_test.go index 9a603d4d7b..db9918b583 100644 --- a/timeboost/setup_test.go +++ b/timeboost/setup_test.go @@ -120,7 +120,7 @@ func setupAuctionTest(t testing.TB, ctx context.Context) *auctionSetup { BiddingToken: biddingToken, Beneficiary: beneficiary, RoundTimingInfo: express_lane_auctiongen.RoundTimingInfo{ - OffsetTimestamp: initialTimestamp.Uint64(), + OffsetTimestamp: initialTimestamp.Int64(), RoundDurationSeconds: bidRoundSeconds, AuctionClosingSeconds: auctionClosingSeconds, ReserveSubmissionSeconds: reserveSubmissionSeconds, diff --git a/timeboost/ticker.go b/timeboost/ticker.go index 45e6ecef11..c3a1777f0a 100644 --- a/timeboost/ticker.go +++ b/timeboost/ticker.go @@ -2,6 +2,8 @@ package timeboost import ( "time" + + "github.com/offchainlabs/nitro/util/arbmath" ) type auctionCloseTicker struct { @@ -50,7 +52,7 @@ func CurrentRound(initialRoundTimestamp time.Time, roundDuration time.Duration) if roundDuration == 0 { return 0 } - return uint64(time.Since(initialRoundTimestamp) / roundDuration) + return arbmath.SaturatingUCast[uint64](time.Since(initialRoundTimestamp) / roundDuration) } func isAuctionRoundClosed( @@ -63,7 +65,7 @@ func isAuctionRoundClosed( return false } timeInRound := timeIntoRound(timestamp, initialTimestamp, roundDuration) - return time.Duration(timeInRound)*time.Second >= roundDuration-auctionClosingDuration + return arbmath.SaturatingCast[time.Duration](timeInRound)*time.Second >= roundDuration-auctionClosingDuration } func timeIntoRound( From 7219c33151938a000aea710701fe597f18ed28f6 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Fri, 8 Nov 2024 12:32:02 +0100 Subject: [PATCH 109/109] Set dep of arbitrator/langs/bf to match master --- arbitrator/langs/bf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbitrator/langs/bf b/arbitrator/langs/bf index cb5750580f..92420f8f34 160000 --- a/arbitrator/langs/bf +++ b/arbitrator/langs/bf @@ -1 +1 @@ -Subproject commit cb5750580f6990b5166ffce83de11b766a25ca31 +Subproject commit 92420f8f34b53f3c1d47047f9f894820d506c565