From 73008bf56d9d5d4cee86bb4d79ddec98c4060a58 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 15 May 2025 18:46:55 +0530 Subject: [PATCH 01/69] feat: preconf RPC service --- tools/preconf-rpc/rpcserver/rpcserver.go | 172 +++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 tools/preconf-rpc/rpcserver/rpcserver.go diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go new file mode 100644 index 000000000..13aaa0050 --- /dev/null +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -0,0 +1,172 @@ +package rpcserver + +import ( + "context" + "encoding/json" + "io" + "net/http" + "sync" + "time" +) + +const ( + defaultTimeout = 5 * time.Second + defaultMaxBodySize = 30 * 1024 * 1024 // 30 MB + + CodeParseError = -32700 + CodeInvalidRequest = -32600 + CodeCustomError = -32000 +) + +type jsonRPCRequest struct { + JSONRPC string `json:"jsonrpc"` + ID any `json:"id"` + Method string `json:"method"` + Params []json.RawMessage `json:"params"` +} + +type jsonRPCResponse struct { + JSONRPC string `json:"jsonrpc"` + ID any `json:"id"` + Result *json.RawMessage `json:"result,omitempty"` + Error *jsonRPCError `json:"error,omitempty"` +} + +type methodHandler func(ctx context.Context, params ...json.RawMessage) (json.RawMessage, error) + +type jsonRPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data *any `json:"data,omitempty"` +} + +type JSONRPCServer struct { + rwLock sync.RWMutex + methods map[string]methodHandler + proxyURL string +} + +func NewJSONRPCServer(proxyURL string) *JSONRPCServer { + return &JSONRPCServer{ + proxyURL: proxyURL, + methods: make(map[string]methodHandler), + } +} + +func (s *JSONRPCServer) RegisterHandler(method string, handler methodHandler) { + s.rwLock.Lock() + s.methods[method] = handler + s.rwLock.Unlock() +} + +func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + if r.Header.Get("Content-Type") != "application/json" { + http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) + return + } + + r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBodySize) + defer r.Body.Close() + + body, err := io.ReadAll(r.Body) + if err != nil { + s.writeError(w, nil, CodeInvalidRequest, "Failed to read request body") + return + } + + var req jsonRPCRequest + err = json.Unmarshal(body, &req) + if err != nil { + s.writeError(w, nil, CodeParseError, "Failed to parse request") + return + } + + if req.JSONRPC != "2.0" { + s.writeError(w, nil, CodeInvalidRequest, "Invalid JSON-RPC version") + return + } + + s.rwLock.RLock() + handler, ok := s.methods[req.Method] + s.rwLock.RUnlock() + if !ok { + s.proxyRequest(w, r) + return + } + + resp, err := handler(r.Context(), req.Params...) + if err != nil { + s.writeError(w, req.ID, CodeCustomError, err.Error()) + return + } + + s.writeResponse(w, req.ID, &resp) +} + +func (s *JSONRPCServer) writeResponse(w http.ResponseWriter, id any, result *json.RawMessage) { + response := jsonRPCResponse{ + JSONRPC: "2.0", + ID: id, + Result: result, + Error: nil, + } + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Failed to write response", http.StatusInternalServerError) + return + } +} + +func (s *JSONRPCServer) writeError(w http.ResponseWriter, id any, code int, message string) { + response := jsonRPCResponse{ + JSONRPC: "2.0", + ID: id, + Result: nil, + Error: &jsonRPCError{ + Code: code, + Message: message, + Data: nil, + }, + } + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Failed to write error response", http.StatusInternalServerError) + return + } +} + +func (s *JSONRPCServer) proxyRequest(w http.ResponseWriter, r *http.Request) { + client := &http.Client{ + Timeout: defaultTimeout, + } + req, err := http.NewRequest(r.Method, s.proxyURL, r.Body) + if err != nil { + http.Error(w, "Failed to create proxy request", http.StatusInternalServerError) + return + } + + resp, err := client.Do(req) + if err != nil { + http.Error(w, "Failed to execute proxy request", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(resp.StatusCode) + rdr := io.LimitReader(resp.Body, defaultMaxBodySize) + respBuf, err := io.ReadAll(rdr) + if err != nil { + http.Error(w, "Failed to read proxy response", http.StatusInternalServerError) + return + } + if _, err := w.Write(respBuf); err != nil { + http.Error(w, "Failed to write proxy response", http.StatusInternalServerError) + return + } +} From 4d30b6acddc05dcf0ef4b381d713ee69e77f16ed Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 15 May 2025 20:19:28 +0530 Subject: [PATCH 02/69] feat: preconf RPC service --- x/opt-in-bidder/bidder/bidder.go | 311 ++++++++++++++++++++++++++ x/opt-in-bidder/bidder/bidder_test.go | 254 +++++++++++++++++++++ x/opt-in-bidder/bidder/export_test.go | 7 + 3 files changed, 572 insertions(+) create mode 100644 x/opt-in-bidder/bidder/bidder.go create mode 100644 x/opt-in-bidder/bidder/bidder_test.go create mode 100644 x/opt-in-bidder/bidder/export_test.go diff --git a/x/opt-in-bidder/bidder/bidder.go b/x/opt-in-bidder/bidder/bidder.go new file mode 100644 index 000000000..faeade1e3 --- /dev/null +++ b/x/opt-in-bidder/bidder/bidder.go @@ -0,0 +1,311 @@ +package optinbidder + +import ( + "context" + "errors" + "io" + "log/slog" + "math/big" + "sync" + "sync/atomic" + "time" + + bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" + debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" + notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" +) + +const ( + epochNotificationTopic = "epoch_validators_opted_in" + slotDuration = 12 * time.Second +) + +var ( + ErrNoEpochInfo = errors.New("no epoch info available") + ErrNoSlotInCurrentEpoch = errors.New("no slot available in current epoch") +) + +var nowFunc = time.Now + +type slotInfo struct { + slot uint64 + startTime time.Time + blsKey string +} + +type epochInfo struct { + epoch uint64 + startTime time.Time + slots []slotInfo +} + +type BlockNumberGetter interface { + BlockNumber(ctx context.Context) (uint64, error) +} + +type BidderClient struct { + logger *slog.Logger + bigWg sync.WaitGroup + bidderClient bidderapiv1.BidderClient + topologyClient debugapiv1.DebugServiceClient + notificationsClient notificationsapiv1.NotificationsClient + currentEpoch atomic.Pointer[epochInfo] + blkNumberGetter BlockNumberGetter +} + +func NewBidderClient( + logger *slog.Logger, + bidderClient bidderapiv1.BidderClient, + topologyClient debugapiv1.DebugServiceClient, + notificationsClient notificationsapiv1.NotificationsClient, + blkNumberGetter BlockNumberGetter, +) *BidderClient { + return &BidderClient{ + logger: logger, + bidderClient: bidderClient, + topologyClient: topologyClient, + notificationsClient: notificationsClient, + blkNumberGetter: blkNumberGetter, + } +} + +func (b *BidderClient) Start(ctx context.Context) <-chan struct{} { + done := make(chan struct{}) + go func() { + defer close(done) + + lastMsg := nowFunc() + RESTART: + sub, err := b.notificationsClient.Subscribe(ctx, ¬ificationsapiv1.SubscribeRequest{ + Topics: []string{epochNotificationTopic}, + }) + if err != nil { + b.logger.Error("failed to subscribe to notifications", "error", err) + return + } + + if time.Since(lastMsg) > 15*time.Minute { + b.logger.Error("no messages received for 15 minutes, closing subscription") + return + } + + for { + select { + case <-ctx.Done(): + b.logger.Info("context done") + return + default: + } + + msg, err := sub.Recv() + if err != nil { + b.logger.Error("failed to receive message", "error", err) + goto RESTART + } + + lastMsg = nowFunc() + + b.logger.Debug("received message", "msg", msg) + + if msg.Topic != epochNotificationTopic { + b.logger.Error("unexpected topic", "topic", msg.Topic) + continue + } + + epoch, err := parseEpochInfo(msg) + if err != nil { + b.logger.Error("failed to parse epoch info", "error", err, "msg", msg) + continue + } + + b.currentEpoch.Store(epoch) + b.logger.Info("current epoch info updated", "epoch", epoch.epoch) + } + }() + return done +} + +func parseEpochInfo(msg *notificationsapiv1.Notification) (*epochInfo, error) { + epochIdx := msg.Value.Fields["epoch"].GetNumberValue() + if epochIdx == 0 { + return nil, errors.New("failed to parse epoch index") + } + startTime := msg.Value.Fields["epoch_start_time"].GetNumberValue() + if startTime == 0 { + return nil, errors.New("failed to parse start time") + } + slots := msg.Value.Fields["slots"].GetListValue() + if slots == nil { + return nil, errors.New("failed to parse slots") + } + epoch := &epochInfo{ + epoch: uint64(epochIdx), + startTime: time.Unix(int64(startTime), 0), + } + baseSlot := epochIdx * 32 + for _, slot := range slots.Values { + slotIdx := slot.GetStructValue().Fields["slot"].GetNumberValue() + if slotIdx == 0 { + return nil, errors.New("failed to parse slot index") + } + if slotIdx < baseSlot || slotIdx >= baseSlot+32 { + return nil, errors.New("slot index out of range") + } + blsKey := slot.GetStructValue().Fields["bls_key"].GetStringValue() + if blsKey == "" { + return nil, errors.New("failed to parse BLS key") + } + idx := slotIdx - baseSlot + epoch.slots = append(epoch.slots, slotInfo{ + slot: uint64(slotIdx), + startTime: epoch.startTime.Add(time.Duration(idx) * slotDuration), + blsKey: blsKey, + }) + } + + return epoch, nil +} + +type BidStatusType int + +const ( + BidStatusNoOfProviders BidStatusType = iota + BidStatusWaitSecs + BidStatusAttempted + BidStatusSucceeded + BidStatusFailed + BidStatusCancelled + BidStatusCommitment +) + +type BidStatus struct { + Type BidStatusType + Arg any +} + +func (b *BidderClient) Bid( + ctx context.Context, + bidAmount *big.Int, + bridgeAmount *big.Int, + slashAmount *big.Int, + rawTx string, +) (chan BidStatus, error) { + topo, err := b.topologyClient.GetTopology(ctx, &debugapiv1.EmptyMessage{}) + if err != nil { + b.logger.Error("failed to get topology", "error", err) + return nil, err + } + + providers := topo.Topology.Fields["connected_providers"].GetListValue() + if providers == nil || len(providers.Values) == 0 { + return nil, errors.New("no connected providers") + } + + // Channel length chosen is 3 so that sending the bid is not blocked by the first + // status message. + res := make(chan BidStatus, 3) + b.bigWg.Add(1) + go func() { + defer close(res) + defer b.bigWg.Done() + + res <- BidStatus{Type: BidStatusNoOfProviders, Arg: len(providers.Values)} + + nextSlot, err := b.getNextSlot() + if err != nil { + b.logger.Error("failed to get next slot", "error", err) + res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} + return + } + + bidTime := nextSlot.startTime.Add(-1 * time.Second) + wait := bidTime.Sub(nowFunc()) + res <- BidStatus{Type: BidStatusWaitSecs, Arg: int(wait.Seconds())} + + if wait > 0 { + b.logger.Info("waiting for next slot", "wait", wait) + select { + case <-time.After(wait): + case <-ctx.Done(): + res <- BidStatus{Type: BidStatusCancelled, Arg: ctx.Err().Error()} + return + } + } + + blkNumber, err := b.blkNumberGetter.BlockNumber(ctx) + if err != nil { + b.logger.Error("failed to get block number", "error", err) + res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} + return + } + + res <- BidStatus{Type: BidStatusAttempted, Arg: int(blkNumber + 1)} + + pc, err := b.bidderClient.SendBid(ctx, &bidderapiv1.Bid{ + Amount: bidAmount.String(), + BlockNumber: int64(blkNumber + 1), + RawTransactions: []string{rawTx}, + DecayStartTimestamp: nowFunc().UnixMilli(), + DecayEndTimestamp: nowFunc().Add(12 * time.Second).UnixMilli(), + SlashAmount: slashAmount.String(), + }) + if err != nil { + b.logger.Error("failed to send bid", "error", err) + res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} + return + } + + for { + select { + case <-ctx.Done(): + res <- BidStatus{Type: BidStatusCancelled, Arg: ctx.Err().Error()} + return + default: + } + + msg, err := pc.Recv() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + if errors.Is(err, context.Canceled) { + res <- BidStatus{Type: BidStatusCancelled, Arg: err.Error()} + return + } + b.logger.Error("failed to receive commitment", "error", err) + res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} + return + } + + res <- BidStatus{Type: BidStatusCommitment, Arg: msg} + } + + }() + + return res, nil +} + +func (b *BidderClient) Estimate() (int64, error) { + nextSlot, err := b.getNextSlot() + if err != nil { + return 0, err + } + + return int64(nextSlot.startTime.Sub(nowFunc()).Seconds()), nil +} + +func (b *BidderClient) getNextSlot() (slotInfo, error) { + epochInfo := b.currentEpoch.Load() + if epochInfo == nil { + return slotInfo{}, ErrNoEpochInfo + } + + now := nowFunc() + for _, slot := range epochInfo.slots { + if now.Before(slot.startTime) { + return slot, nil + } + } + + return slotInfo{}, ErrNoSlotInCurrentEpoch +} diff --git a/x/opt-in-bidder/bidder/bidder_test.go b/x/opt-in-bidder/bidder/bidder_test.go new file mode 100644 index 000000000..3cab4826d --- /dev/null +++ b/x/opt-in-bidder/bidder/bidder_test.go @@ -0,0 +1,254 @@ +package optinbidder_test + +import ( + "context" + "crypto/rand" + "encoding/hex" + "io" + "math/big" + "os" + "testing" + "time" + + bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" + debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" + notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" + optinbidder "github.com/primev/mev-commit/x/opt-in-bidder/bidder" + "github.com/primev/mev-commit/x/util" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/structpb" +) + +type testRPCServices struct { + bidderapiv1.BidderClient + debugapiv1.DebugServiceClient + notificationsapiv1.NotificationsClient + + notificationChan chan *notificationsapiv1.Notification + bidChan chan *bidderapiv1.Bid + commitmentChan chan *bidderapiv1.Commitment + topo *debugapiv1.TopologyResponse +} + +type testNotificationStream struct { + grpc.ClientStream + + ctx context.Context + notificationChan chan *notificationsapiv1.Notification +} + +func (t *testNotificationStream) Recv() (*notificationsapiv1.Notification, error) { + select { + case <-t.ctx.Done(): + return nil, io.EOF + case n := <-t.notificationChan: + return n, nil + } +} + +type testCommitmentStream struct { + grpc.ClientStream + + ctx context.Context + commitmentChan chan *bidderapiv1.Commitment +} + +func (t *testCommitmentStream) Recv() (*bidderapiv1.Commitment, error) { + select { + case <-t.ctx.Done(): + return nil, io.EOF + case c, more := <-t.commitmentChan: + if !more { + return nil, io.EOF + } + return c, nil + } +} + +func (t *testRPCServices) SendBid( + ctx context.Context, + in *bidderapiv1.Bid, + _ ...grpc.CallOption, +) (grpc.ServerStreamingClient[bidderapiv1.Commitment], error) { + select { + case t.bidChan <- in: + case <-ctx.Done(): + return nil, ctx.Err() + } + return &testCommitmentStream{ctx: ctx, commitmentChan: t.commitmentChan}, nil +} + +func (t *testRPCServices) GetTopology( + _ context.Context, + _ *debugapiv1.EmptyMessage, + _ ...grpc.CallOption, +) (*debugapiv1.TopologyResponse, error) { + return t.topo, nil +} + +func (t *testRPCServices) Subscribe( + ctx context.Context, + in *notificationsapiv1.SubscribeRequest, + _ ...grpc.CallOption, +) (grpc.ServerStreamingClient[notificationsapiv1.Notification], error) { + return &testNotificationStream{ctx: ctx, notificationChan: t.notificationChan}, nil +} + +type testBlockNumberGetter struct { + blockNumber uint64 +} + +func (t *testBlockNumberGetter) BlockNumber(ctx context.Context) (uint64, error) { + return t.blockNumber, nil +} + +type testTimeSetter struct { + now time.Time +} + +func (t *testTimeSetter) Now() time.Time { + return t.now +} + +func TestBidderClient(t *testing.T) { + t.Parallel() + + clock := time.Now() + timeSetter := &testTimeSetter{ + now: clock, + } + + optinbidder.SetNowFunc(timeSetter.Now) + + topoVal, err := structpb.NewStruct(map[string]interface{}{ + "connected_providers": []any{"provider1", "provider2"}, + }) + if err != nil { + t.Fatal(err) + } + // Create a new test RPC services. + rpcServices := &testRPCServices{ + notificationChan: make(chan *notificationsapiv1.Notification), + bidChan: make(chan *bidderapiv1.Bid), + commitmentChan: make(chan *bidderapiv1.Commitment), + topo: &debugapiv1.TopologyResponse{ + Topology: &structpb.Struct{}, + }, + } + + blockNumberGetter := &testBlockNumberGetter{blockNumber: 10} + bidderClient := optinbidder.NewBidderClient( + util.NewTestLogger(os.Stdout), + rpcServices, + rpcServices, + rpcServices, + blockNumberGetter, + ) + + ctx, cancel := context.WithCancel(context.Background()) + done := bidderClient.Start(ctx) + + _, err = bidderClient.Estimate() + if err != optinbidder.ErrNoEpochInfo { + t.Fatalf("expected error %v, got %v", optinbidder.ErrNoEpochInfo, err) + } + + // Send a notification. + nVal, err := structpb.NewStruct(map[string]interface{}{ + "epoch": 1, + "epoch_start_time": clock.Add(2 * time.Second).Unix(), + "slots": []any{ + map[string]interface{}{ + "slot": 33, + "start_time": clock.Add(14 * time.Second).Unix(), + "bls_key": "key2", + "opted_in": true, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + + rpcServices.notificationChan <- ¬ificationsapiv1.Notification{ + Topic: "epoch_validators_opted_in", + Value: nVal, + } + + for { + if _, err := bidderClient.Estimate(); err == nil { + break + } + } + + estimate, err := bidderClient.Estimate() + if err != nil { + t.Fatal(err) + } + if estimate != 13 { + t.Fatalf("expected estimate 13, got %d", estimate) + } + + timeSetter.now = clock.Add(10 * time.Second) + + buf := make([]byte, 32) + _, _ = rand.Read(buf) + txString := hex.EncodeToString(buf) + + _, err = bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), big.NewInt(1), txString) + if err == nil { + t.Fatal("expected error, got nil") + } + + rpcServices.topo = &debugapiv1.TopologyResponse{ + Topology: topoVal, + } + + statusC, err := bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), big.NewInt(1), txString) + if err != nil { + t.Fatal(err) + } + +WaitLoop: + for { + select { + case status := <-statusC: + switch { + case status.Type == optinbidder.BidStatusNoOfProviders: + if status.Arg.(int) != 2 { + t.Fatalf("expected 2 providers, got %d", status.Arg) + } + case status.Type == optinbidder.BidStatusWaitSecs: + if status.Arg.(int) != 2 { + t.Fatalf("expected 2 seconds, got %d", status.Arg) + } + case status.Type == optinbidder.BidStatusAttempted: + if status.Arg.(int) != 11 { + t.Fatalf("expected 11, got %d", status.Arg) + } + case status.Type == optinbidder.BidStatusSucceeded: + break WaitLoop + } + case bid := <-rpcServices.bidChan: + if bid.Amount != big.NewInt(1).String() { + t.Fatalf("expected amount 1, got %s", bid.Amount) + } + if bid.BlockNumber != 11 { + t.Fatalf("expected block number 11, got %d", bid.BlockNumber) + } + if bid.RawTransactions[0] != txString { + t.Fatalf("expected raw transaction %x, got %s", buf, bid.RawTransactions[0]) + } + rpcServices.commitmentChan <- &bidderapiv1.Commitment{ + BlockNumber: 11, + } + rpcServices.commitmentChan <- &bidderapiv1.Commitment{ + BlockNumber: 11, + } + close(rpcServices.commitmentChan) + } + } + + cancel() + <-done +} diff --git a/x/opt-in-bidder/bidder/export_test.go b/x/opt-in-bidder/bidder/export_test.go new file mode 100644 index 000000000..31674fc5f --- /dev/null +++ b/x/opt-in-bidder/bidder/export_test.go @@ -0,0 +1,7 @@ +package optinbidder + +import "time" + +func SetNowFunc(f func() time.Time) { + nowFunc = f +} From 4af7eabe1d155a703c351e9c1874f4a88d836dd3 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 17 May 2025 13:36:45 +0530 Subject: [PATCH 03/69] chore: move opt-in bidder to new pkg --- x/opt-in-bidder/bidder/bidder.go | 4 +++- x/opt-in-bidder/bidder/bidder_test.go | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/x/opt-in-bidder/bidder/bidder.go b/x/opt-in-bidder/bidder/bidder.go index faeade1e3..4419eaad1 100644 --- a/x/opt-in-bidder/bidder/bidder.go +++ b/x/opt-in-bidder/bidder/bidder.go @@ -3,6 +3,7 @@ package optinbidder import ( "context" "errors" + "fmt" "io" "log/slog" "math/big" @@ -172,7 +173,6 @@ const ( BidStatusNoOfProviders BidStatusType = iota BidStatusWaitSecs BidStatusAttempted - BidStatusSucceeded BidStatusFailed BidStatusCancelled BidStatusCommitment @@ -206,9 +206,11 @@ func (b *BidderClient) Bid( res := make(chan BidStatus, 3) b.bigWg.Add(1) go func() { + defer fmt.Println("BidderClient goroutine exiting") defer close(res) defer b.bigWg.Done() + fmt.Println("BidderClient sending no of providers") res <- BidStatus{Type: BidStatusNoOfProviders, Arg: len(providers.Values)} nextSlot, err := b.getNextSlot() diff --git a/x/opt-in-bidder/bidder/bidder_test.go b/x/opt-in-bidder/bidder/bidder_test.go index 3cab4826d..b900fae4f 100644 --- a/x/opt-in-bidder/bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder/bidder_test.go @@ -209,7 +209,8 @@ func TestBidderClient(t *testing.T) { t.Fatal(err) } -WaitLoop: + commitments := 0 +waitLoop: for { select { case status := <-statusC: @@ -226,8 +227,14 @@ WaitLoop: if status.Arg.(int) != 11 { t.Fatalf("expected 11, got %d", status.Arg) } - case status.Type == optinbidder.BidStatusSucceeded: - break WaitLoop + case status.Type == optinbidder.BidStatusCommitment: + if status.Arg.(*bidderapiv1.Commitment).BlockNumber != 11 { + t.Fatalf("expected block number 11, got %d", status.Arg.(*bidderapiv1.Commitment).BlockNumber) + } + commitments++ + if commitments == 2 { + break waitLoop + } } case bid := <-rpcServices.bidChan: if bid.Amount != big.NewInt(1).String() { From b5c7c628f1faf120c3fc9a97fb4d0460ec5c332f Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 17 May 2025 13:39:05 +0530 Subject: [PATCH 04/69] fix: option to proxy from handler --- tools/preconf-rpc/rpcserver/rpcserver.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 13aaa0050..b4fe5b24a 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -32,7 +32,7 @@ type jsonRPCResponse struct { Error *jsonRPCError `json:"error,omitempty"` } -type methodHandler func(ctx context.Context, params ...json.RawMessage) (json.RawMessage, error) +type methodHandler func(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) type jsonRPCError struct { Code int `json:"code"` @@ -99,10 +99,17 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - resp, err := handler(r.Context(), req.Params...) - if err != nil { + resp, proxy, err := handler(r.Context(), req.Params...) + switch { + case err != nil: s.writeError(w, req.ID, CodeCustomError, err.Error()) return + case proxy: + s.proxyRequest(w, r) + return + case resp == nil: + s.writeError(w, req.ID, CodeCustomError, "No response") + return } s.writeResponse(w, req.ID, &resp) From 249d6c39d4f93a958625014a72a36db64571c07c Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 17 May 2025 14:17:17 +0530 Subject: [PATCH 05/69] fix: restructuring --- tools/preconf-rpc/service/service.go | 194 ++++++++++++++++++++ x/accountsync/accountsync.go | 56 ++++++ x/accountsync/accountsync_test.go | 61 ++++++ x/opt-in-bidder/{bidder => }/bidder.go | 2 + x/opt-in-bidder/{bidder => }/bidder_test.go | 9 +- x/opt-in-bidder/{bidder => }/export_test.go | 0 6 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 tools/preconf-rpc/service/service.go create mode 100644 x/accountsync/accountsync.go create mode 100644 x/accountsync/accountsync_test.go rename x/opt-in-bidder/{bidder => }/bidder.go (99%) rename x/opt-in-bidder/{bidder => }/bidder_test.go (97%) rename x/opt-in-bidder/{bidder => }/export_test.go (100%) diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go new file mode 100644 index 000000000..324ef7bda --- /dev/null +++ b/tools/preconf-rpc/service/service.go @@ -0,0 +1,194 @@ +package service + +import ( + "context" + "crypto/tls" + "errors" + "io" + "log/slog" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" + debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" + notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" + "github.com/primev/mev-commit/tools/instant-bridge/accountsync" + "github.com/primev/mev-commit/tools/instant-bridge/api" + "github.com/primev/mev-commit/tools/instant-bridge/bidder" + "github.com/primev/mev-commit/tools/instant-bridge/transfer" + "github.com/primev/mev-commit/x/contracts/ethwrapper" + "github.com/primev/mev-commit/x/health" + "github.com/primev/mev-commit/x/keysigner" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +type Config struct { + Logger *slog.Logger + Signer keysigner.KeySigner + BidderRPC string + AutoDepositAmount *big.Int + L1RPCUrls []string + SettlementRPCUrl string + L1ContractAddr common.Address + SettlementContractAddr common.Address + SettlementThreshold *big.Int + SettlementTopup *big.Int + HTTPPort int + MinServiceFee *big.Int + GasTipCap *big.Int + GasFeeCap *big.Int +} + +type Service struct { + cancel context.CancelFunc + closers []io.Closer +} + +func New(config *Config) (*Service, error) { + s := &Service{} + + conn, err := grpc.NewClient( + config.BidderRPC, + grpc.WithTransportCredentials(credentials.NewTLS( + &tls.Config{InsecureSkipVerify: true}, + )), + ) + if err != nil { + return nil, err + } + + s.closers = append(s.closers, conn) + + l1RPCClient, err := ethwrapper.NewClient( + config.Logger.With("module", "ethwrapper"), + config.L1RPCUrls, + ethwrapper.EthClientWithMaxRetries(5), + ) + if err != nil { + return nil, err + } + l1ChainID, err := l1RPCClient.RawClient().ChainID(context.Background()) + if err != nil { + return nil, err + } + + settlementClient, err := ethclient.Dial(config.SettlementRPCUrl) + if err != nil { + return nil, err + } + settlementChainID, err := settlementClient.ChainID(context.Background()) + if err != nil { + return nil, err + } + + bidderCli := bidderapiv1.NewBidderClient(conn) + topologyCli := debugapiv1.NewDebugServiceClient(conn) + notificationsCli := notificationsapiv1.NewNotificationsClient(conn) + + status, err := bidderCli.AutoDepositStatus(context.Background(), &bidderapiv1.EmptyMessage{}) + if err != nil { + return nil, err + } + + if !status.IsAutodepositEnabled { + _, err := bidderCli.AutoDeposit( + context.Background(), + &bidderapiv1.DepositRequest{ + Amount: config.AutoDepositAmount.String(), + }, + ) + if err != nil { + return nil, err + } + } + + bridgeConfig := transfer.BridgeConfig{ + Signer: config.Signer, + L1ContractAddr: config.L1ContractAddr, + SettlementContractAddr: config.SettlementContractAddr, + L1RPCUrl: config.L1RPCUrls[0], + SettlementRPCUrl: config.SettlementRPCUrl, + } + + syncer := accountsync.NewAccountSync(config.Signer.GetAddress(), settlementClient) + bridger := transfer.NewBridger( + config.Logger.With("module", "bridger"), + syncer, + bridgeConfig, + config.SettlementThreshold, + config.SettlementTopup, + ) + + bidderClient := bidder.NewBidderClient( + config.Logger.With("module", "bidder"), + bidderCli, + topologyCli, + notificationsCli, + l1RPCClient, + ) + + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = cancel + + healthChecker := health.New() + + bridgerDone := bridger.Start(ctx) + healthChecker.Register(health.CloseChannelHealthCheck("Bridger", bridgerDone)) + s.closers = append(s.closers, channelCloser(bridgerDone)) + + bidderDone := bidderClient.Start(ctx) + healthChecker.Register(health.CloseChannelHealthCheck("BidderService", bidderDone)) + s.closers = append(s.closers, channelCloser(bidderDone)) + + transferer := transfer.NewTransferer( + config.Logger.With("module", "transferer"), + settlementClient, + l1ChainID, + settlementChainID, + config.Signer, + config.GasTipCap, + config.GasFeeCap, + ) + + apiService := api.NewAPI( + config.Logger.With("module", "api"), + config.HTTPPort, + healthChecker, + bidderClient, + transferer, + config.MinServiceFee, + config.Signer.GetAddress(), + l1RPCClient.RawClient(), + settlementClient, + ) + + apiService.Start() + s.closers = append(s.closers, apiService) + + return s, nil +} + +func (s *Service) Close() error { + s.cancel() + + for _, c := range s.closers { + if err := c.Close(); err != nil { + return err + } + } + return nil +} + +type channelCloser <-chan struct{} + +func (c channelCloser) Close() error { + select { + case <-c: + case <-time.After(5 * time.Second): + return errors.New("timed out waiting for channel to close") + } + return nil +} diff --git a/x/accountsync/accountsync.go b/x/accountsync/accountsync.go new file mode 100644 index 000000000..33a7ed638 --- /dev/null +++ b/x/accountsync/accountsync.go @@ -0,0 +1,56 @@ +package accountsync + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +type BalanceGetter interface { + BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) +} + +// AccountSync service is responsible for keeping track of the accounts and their balances. +type AccountSync struct { + owner common.Address + client BalanceGetter +} + +// NewAccountSync creates a new AccountSync service. +func NewAccountSync(address common.Address, client BalanceGetter) *AccountSync { + return &AccountSync{ + owner: address, + client: client, + } +} + +// Subscribe call will start a goroutine that will periodically check the account balance and +// will notify the caller when the balance is below the threshold. The channel returned will be closed +// when the context is done or the account balance is below the threshold. +func (a *AccountSync) Subscribe(ctx context.Context, threshold *big.Int) <-chan struct{} { + ticker := time.NewTicker(5 * time.Second) + done := make(chan struct{}) + go func() { + defer ticker.Stop() + defer close(done) + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + balance, err := a.client.BalanceAt(ctx, a.owner, nil) + if err != nil { + continue + } + if balance.Cmp(threshold) < 0 { + return + } + } + } + }() + + return done +} diff --git a/x/accountsync/accountsync_test.go b/x/accountsync/accountsync_test.go new file mode 100644 index 000000000..e52813e78 --- /dev/null +++ b/x/accountsync/accountsync_test.go @@ -0,0 +1,61 @@ +package accountsync_test + +import ( + "context" + "math/big" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/primev/mev-commit/tools/instant-bridge/accountsync" +) + +type mockBalanceGetter struct { + balance atomic.Pointer[big.Int] +} + +func (m *mockBalanceGetter) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + return m.balance.Load(), nil +} + +func TestAccountSync(t *testing.T) { + t.Parallel() + + // Create a new mock balance getter. + mockBalanceGetter := &mockBalanceGetter{} + mockBalanceGetter.balance.Store(big.NewInt(10)) + + // Create a new account sync service. + accountSync := accountsync.NewAccountSync(common.HexToAddress("0x123"), mockBalanceGetter) + + // Create a new channel to receive the notification. + done := accountSync.Subscribe(context.Background(), big.NewInt(100)) + + // Wait for the notification. + <-done + + mockBalanceGetter.balance.Store(big.NewInt(150)) + + // Create a new channel to receive the notification. + done = accountSync.Subscribe(context.Background(), big.NewInt(100)) + + select { + case <-done: + t.Fatal("expected the channel to be open") + case <-time.After(2 * time.Second): + break + } + + mockBalanceGetter.balance.Store(big.NewInt(50)) + <-done + + // Create a new channel to receive the notification. + mockBalanceGetter.balance.Store(big.NewInt(150)) + ctx, cancel := context.WithCancel(context.Background()) + done = accountSync.Subscribe(ctx, big.NewInt(100)) + + // Cancel the context. + cancel() + <-done +} diff --git a/x/opt-in-bidder/bidder/bidder.go b/x/opt-in-bidder/bidder.go similarity index 99% rename from x/opt-in-bidder/bidder/bidder.go rename to x/opt-in-bidder/bidder.go index 4419eaad1..d1d838469 100644 --- a/x/opt-in-bidder/bidder/bidder.go +++ b/x/opt-in-bidder/bidder.go @@ -176,6 +176,7 @@ const ( BidStatusFailed BidStatusCancelled BidStatusCommitment + BidStatusDone ) type BidStatus struct { @@ -268,6 +269,7 @@ func (b *BidderClient) Bid( msg, err := pc.Recv() if err != nil { if errors.Is(err, io.EOF) { + res <- BidStatus{Type: BidStatusDone, Arg: nil} break } if errors.Is(err, context.Canceled) { diff --git a/x/opt-in-bidder/bidder/bidder_test.go b/x/opt-in-bidder/bidder_test.go similarity index 97% rename from x/opt-in-bidder/bidder/bidder_test.go rename to x/opt-in-bidder/bidder_test.go index b900fae4f..5018ff842 100644 --- a/x/opt-in-bidder/bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder_test.go @@ -232,9 +232,8 @@ waitLoop: t.Fatalf("expected block number 11, got %d", status.Arg.(*bidderapiv1.Commitment).BlockNumber) } commitments++ - if commitments == 2 { - break waitLoop - } + case status.Type == optinbidder.BidStatusDone: + break waitLoop } case bid := <-rpcServices.bidChan: if bid.Amount != big.NewInt(1).String() { @@ -256,6 +255,10 @@ waitLoop: } } + if commitments != 2 { + t.Fatalf("expected 2 commitments, got %d", commitments) + } + cancel() <-done } diff --git a/x/opt-in-bidder/bidder/export_test.go b/x/opt-in-bidder/export_test.go similarity index 100% rename from x/opt-in-bidder/bidder/export_test.go rename to x/opt-in-bidder/export_test.go From eac7c6353682d2a081f336ce029a3e88dd44516d Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 18:45:44 +0530 Subject: [PATCH 06/69] feat: preconf RPC service --- bridge/standard/pkg/transfer/transfer.go | 16 +- .../instant-bridge/accountsync/accountsync.go | 56 ---- .../accountsync/accountsync_test.go | 61 ---- tools/instant-bridge/api/api.go | 75 +++-- tools/instant-bridge/bidder/bidder.go | 316 ------------------ tools/instant-bridge/bidder/bidder_test.go | 254 -------------- tools/instant-bridge/bidder/export_test.go | 7 - tools/instant-bridge/service/service.go | 10 +- tools/preconf-rpc/.goreleaser.yml | 64 ++++ tools/preconf-rpc/main.go | 252 ++++++++++++++ tools/preconf-rpc/service/service.go | 57 ++-- .../instant-bridge => x}/transfer/bridger.go | 6 +- x/transfer/bridger_test.go | 131 ++++++++ x/transfer/export_test.go | 25 ++ .../instant-bridge => x}/transfer/transfer.go | 85 +++-- x/transfer/transfer_test.go | 97 ++++++ 16 files changed, 711 insertions(+), 801 deletions(-) delete mode 100644 tools/instant-bridge/accountsync/accountsync.go delete mode 100644 tools/instant-bridge/accountsync/accountsync_test.go delete mode 100644 tools/instant-bridge/bidder/bidder.go delete mode 100644 tools/instant-bridge/bidder/bidder_test.go delete mode 100644 tools/instant-bridge/bidder/export_test.go create mode 100644 tools/preconf-rpc/.goreleaser.yml create mode 100644 tools/preconf-rpc/main.go rename {tools/instant-bridge => x}/transfer/bridger.go (92%) create mode 100644 x/transfer/bridger_test.go create mode 100644 x/transfer/export_test.go rename {tools/instant-bridge => x}/transfer/transfer.go (57%) create mode 100644 x/transfer/transfer_test.go diff --git a/bridge/standard/pkg/transfer/transfer.go b/bridge/standard/pkg/transfer/transfer.go index e60e64493..a138d7644 100644 --- a/bridge/standard/pkg/transfer/transfer.go +++ b/bridge/standard/pkg/transfer/transfer.go @@ -34,7 +34,11 @@ type TransferStatus struct { Error error } -type Transfer struct { +type Transfer interface { + Do(ctx context.Context) <-chan TransferStatus +} + +type transfer struct { signer keysigner.KeySigner amount *big.Int destAddress common.Address @@ -54,7 +58,7 @@ func NewTransferToSettlement( l1RPCUrl string, l1ContractAddr common.Address, settlementContractAddr common.Address, -) (*Transfer, error) { +) (Transfer, error) { l1Client, err := ethclient.Dial(l1RPCUrl) if err != nil { return nil, fmt.Errorf("failed to dial l1 rpc: %s", err) @@ -88,7 +92,7 @@ func NewTransferToSettlement( return nil, fmt.Errorf("failed to create settlement filterer: %s", err) } - return &Transfer{ + return &transfer{ signer: signer, amount: amount, destAddress: destAddress, @@ -109,7 +113,7 @@ func NewTransferToL1( l1RPCUrl string, l1ContractAddr common.Address, settlementContractAddr common.Address, -) (*Transfer, error) { +) (Transfer, error) { l1Client, err := ethclient.Dial(l1RPCUrl) if err != nil { return nil, fmt.Errorf("failed to dial l1 rpc: %s", err) @@ -146,7 +150,7 @@ func NewTransferToL1( return nil, fmt.Errorf("failed to create settlement filterer: %s", err) } - return &Transfer{ + return &transfer{ amount: amount, destAddress: destAddress, signer: signer, @@ -159,7 +163,7 @@ func NewTransferToL1( }, nil } -func (t *Transfer) Do(ctx context.Context) <-chan TransferStatus { +func (t *transfer) Do(ctx context.Context) <-chan TransferStatus { statusChan := make(chan TransferStatus) go func() { defer close(statusChan) diff --git a/tools/instant-bridge/accountsync/accountsync.go b/tools/instant-bridge/accountsync/accountsync.go deleted file mode 100644 index 33a7ed638..000000000 --- a/tools/instant-bridge/accountsync/accountsync.go +++ /dev/null @@ -1,56 +0,0 @@ -package accountsync - -import ( - "context" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" -) - -type BalanceGetter interface { - BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) -} - -// AccountSync service is responsible for keeping track of the accounts and their balances. -type AccountSync struct { - owner common.Address - client BalanceGetter -} - -// NewAccountSync creates a new AccountSync service. -func NewAccountSync(address common.Address, client BalanceGetter) *AccountSync { - return &AccountSync{ - owner: address, - client: client, - } -} - -// Subscribe call will start a goroutine that will periodically check the account balance and -// will notify the caller when the balance is below the threshold. The channel returned will be closed -// when the context is done or the account balance is below the threshold. -func (a *AccountSync) Subscribe(ctx context.Context, threshold *big.Int) <-chan struct{} { - ticker := time.NewTicker(5 * time.Second) - done := make(chan struct{}) - go func() { - defer ticker.Stop() - defer close(done) - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - balance, err := a.client.BalanceAt(ctx, a.owner, nil) - if err != nil { - continue - } - if balance.Cmp(threshold) < 0 { - return - } - } - } - }() - - return done -} diff --git a/tools/instant-bridge/accountsync/accountsync_test.go b/tools/instant-bridge/accountsync/accountsync_test.go deleted file mode 100644 index e52813e78..000000000 --- a/tools/instant-bridge/accountsync/accountsync_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package accountsync_test - -import ( - "context" - "math/big" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/primev/mev-commit/tools/instant-bridge/accountsync" -) - -type mockBalanceGetter struct { - balance atomic.Pointer[big.Int] -} - -func (m *mockBalanceGetter) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { - return m.balance.Load(), nil -} - -func TestAccountSync(t *testing.T) { - t.Parallel() - - // Create a new mock balance getter. - mockBalanceGetter := &mockBalanceGetter{} - mockBalanceGetter.balance.Store(big.NewInt(10)) - - // Create a new account sync service. - accountSync := accountsync.NewAccountSync(common.HexToAddress("0x123"), mockBalanceGetter) - - // Create a new channel to receive the notification. - done := accountSync.Subscribe(context.Background(), big.NewInt(100)) - - // Wait for the notification. - <-done - - mockBalanceGetter.balance.Store(big.NewInt(150)) - - // Create a new channel to receive the notification. - done = accountSync.Subscribe(context.Background(), big.NewInt(100)) - - select { - case <-done: - t.Fatal("expected the channel to be open") - case <-time.After(2 * time.Second): - break - } - - mockBalanceGetter.balance.Store(big.NewInt(50)) - <-done - - // Create a new channel to receive the notification. - mockBalanceGetter.balance.Store(big.NewInt(150)) - ctx, cancel := context.WithCancel(context.Background()) - done = accountSync.Subscribe(ctx, big.NewInt(100)) - - // Cancel the context. - cancel() - <-done -} diff --git a/tools/instant-bridge/api/api.go b/tools/instant-bridge/api/api.go index c14d76a92..808059b26 100644 --- a/tools/instant-bridge/api/api.go +++ b/tools/instant-bridge/api/api.go @@ -16,24 +16,26 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/primev/mev-commit/p2p/pkg/apiserver" - "github.com/primev/mev-commit/tools/instant-bridge/bidder" - "github.com/primev/mev-commit/tools/instant-bridge/transfer" "github.com/primev/mev-commit/x/health" + bidder "github.com/primev/mev-commit/x/opt-in-bidder" + "github.com/primev/mev-commit/x/transfer" ) type API struct { - logger *slog.Logger - mux *http.ServeMux - port int - srv *http.Server - health health.Health - bidder *bidder.BidderClient - transferer *transfer.Transferer - minServiceFee *big.Int - status *status - owner common.Address - l1Client *ethclient.Client - settlementClient *ethclient.Client + logger *slog.Logger + mux *http.ServeMux + port int + srv *http.Server + health health.Health + bidder *bidder.BidderClient + transferer *transfer.Transferer + minServiceFee *big.Int + status *status + owner common.Address + l1Client *ethclient.Client + settlementClient *ethclient.Client + l1ChainID *big.Int + settlementChainID *big.Int } type bid struct { @@ -62,19 +64,23 @@ func NewAPI( owner common.Address, l1Client *ethclient.Client, settlementClient *ethclient.Client, + l1ChainID *big.Int, + settlementChainID *big.Int, ) *API { a := &API{ - logger: logger, - mux: http.NewServeMux(), - port: port, - status: &status{}, - health: health, - bidder: bdr, - transferer: transferer, - minServiceFee: minServiceFee, - owner: owner, - l1Client: l1Client, - settlementClient: settlementClient, + logger: logger, + mux: http.NewServeMux(), + port: port, + status: &status{}, + health: health, + bidder: bdr, + transferer: transferer, + minServiceFee: minServiceFee, + owner: owner, + l1Client: l1Client, + settlementClient: settlementClient, + l1ChainID: l1ChainID, + settlementChainID: settlementChainID, } a.status.bridgedAmount.Store(big.NewInt(0)) @@ -170,7 +176,7 @@ func NewAPI( return } - tx, err := a.transferer.ValidateL1Tx(b.RawTx) + tx, err := a.transferer.ValidateTx(b.RawTx, a.l1ChainID) if err != nil { apiserver.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid raw tx: %w", err)) return @@ -216,6 +222,7 @@ func NewAPI( req.Context(), halfFee, bridgeAmt, + bridgeAmt, b.RawTx, ) if err != nil { @@ -226,29 +233,31 @@ func NewAPI( for status := range statusC { switch status.Type { case bidder.BidStatusNoOfProviders: - a.logger.Info("no of providers", "count", status.Arg1) + a.logger.Info("no of providers", "count", status.Arg.(int)) case bidder.BidStatusWaitSecs: - a.logger.Info("waiting for next slot", "seconds", status.Arg1) + a.logger.Info("waiting for next slot", "seconds", status.Arg.(int)) case bidder.BidStatusAttempted: - a.logger.Info("bid attempted", "block", status.Arg1) + a.logger.Info("bid attempted", "block", status.Arg) case bidder.BidStatusFailed: apiserver.WriteError( w, http.StatusInternalServerError, - fmt.Errorf("bid failed: %s", status.Arg2), + fmt.Errorf("bid failed: %s", status.Arg.(string)), ) return - case bidder.BidStatusSucceeded: - a.logger.Info("bid succeeded", "block", status.Arg1) + case bidder.BidStatusDone: + a.logger.Info("bid succeeded", "block", status.Arg.(int)) + break } } a.status.bidsSucceeded.Add(1) a.status.transfersAttempted.Add(1) - err = a.transferer.TransferOnSettlement( + err = a.transferer.Transfer( req.Context(), destAddr, + a.settlementChainID, bridgeAmt, ) if err != nil { diff --git a/tools/instant-bridge/bidder/bidder.go b/tools/instant-bridge/bidder/bidder.go deleted file mode 100644 index 02eea2e1d..000000000 --- a/tools/instant-bridge/bidder/bidder.go +++ /dev/null @@ -1,316 +0,0 @@ -package bidder - -import ( - "context" - "errors" - "io" - "log/slog" - "math/big" - "sync" - "sync/atomic" - "time" - - bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" - debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" - notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" -) - -const ( - epochNotificationTopic = "epoch_validators_opted_in" - slotDuration = 12 * time.Second -) - -var ( - ErrNoEpochInfo = errors.New("no epoch info available") - ErrNoSlotInCurrentEpoch = errors.New("no slot available in current epoch") -) - -var nowFunc = time.Now - -type slotInfo struct { - slot uint64 - startTime time.Time - blsKey string -} - -type epochInfo struct { - epoch uint64 - startTime time.Time - slots []slotInfo -} - -type BlockNumberGetter interface { - BlockNumber(ctx context.Context) (uint64, error) -} - -type BidderClient struct { - logger *slog.Logger - bigWg sync.WaitGroup - bidderClient bidderapiv1.BidderClient - topologyClient debugapiv1.DebugServiceClient - notificationsClient notificationsapiv1.NotificationsClient - currentEpoch atomic.Pointer[epochInfo] - blkNumberGetter BlockNumberGetter -} - -func NewBidderClient( - logger *slog.Logger, - bidderClient bidderapiv1.BidderClient, - topologyClient debugapiv1.DebugServiceClient, - notificationsClient notificationsapiv1.NotificationsClient, - blkNumberGetter BlockNumberGetter, -) *BidderClient { - return &BidderClient{ - logger: logger, - bidderClient: bidderClient, - topologyClient: topologyClient, - notificationsClient: notificationsClient, - blkNumberGetter: blkNumberGetter, - } -} - -func (b *BidderClient) Start(ctx context.Context) <-chan struct{} { - done := make(chan struct{}) - go func() { - defer close(done) - - lastMsg := nowFunc() - RESTART: - sub, err := b.notificationsClient.Subscribe(ctx, ¬ificationsapiv1.SubscribeRequest{ - Topics: []string{epochNotificationTopic}, - }) - if err != nil { - b.logger.Error("failed to subscribe to notifications", "error", err) - return - } - - if time.Since(lastMsg) > 15*time.Minute { - b.logger.Error("no messages received for 15 minutes, closing subscription") - return - } - - for { - select { - case <-ctx.Done(): - b.logger.Info("context done") - return - default: - } - - msg, err := sub.Recv() - if err != nil { - b.logger.Error("failed to receive message", "error", err) - goto RESTART - } - - lastMsg = nowFunc() - - b.logger.Debug("received message", "msg", msg) - - if msg.Topic != epochNotificationTopic { - b.logger.Error("unexpected topic", "topic", msg.Topic) - continue - } - - epoch, err := parseEpochInfo(msg) - if err != nil { - b.logger.Error("failed to parse epoch info", "error", err, "msg", msg) - continue - } - - b.currentEpoch.Store(epoch) - b.logger.Info("current epoch info updated", "epoch", epoch.epoch) - } - }() - return done -} - -func parseEpochInfo(msg *notificationsapiv1.Notification) (*epochInfo, error) { - epochIdx := msg.Value.Fields["epoch"].GetNumberValue() - if epochIdx == 0 { - return nil, errors.New("failed to parse epoch index") - } - startTime := msg.Value.Fields["epoch_start_time"].GetNumberValue() - if startTime == 0 { - return nil, errors.New("failed to parse start time") - } - slots := msg.Value.Fields["slots"].GetListValue() - if slots == nil { - return nil, errors.New("failed to parse slots") - } - epoch := &epochInfo{ - epoch: uint64(epochIdx), - startTime: time.Unix(int64(startTime), 0), - } - baseSlot := epochIdx * 32 - for _, slot := range slots.Values { - slotIdx := slot.GetStructValue().Fields["slot"].GetNumberValue() - if slotIdx == 0 { - return nil, errors.New("failed to parse slot index") - } - if slotIdx < baseSlot || slotIdx >= baseSlot+32 { - return nil, errors.New("slot index out of range") - } - blsKey := slot.GetStructValue().Fields["bls_key"].GetStringValue() - if blsKey == "" { - return nil, errors.New("failed to parse BLS key") - } - idx := slotIdx - baseSlot - epoch.slots = append(epoch.slots, slotInfo{ - slot: uint64(slotIdx), - startTime: epoch.startTime.Add(time.Duration(idx) * slotDuration), - blsKey: blsKey, - }) - } - - return epoch, nil -} - -type BidStatusType int - -const ( - BidStatusNoOfProviders BidStatusType = iota - BidStatusWaitSecs - BidStatusAttempted - BidStatusSucceeded - BidStatusFailed -) - -type BidStatus struct { - Type BidStatusType - Arg1 int - Arg2 string -} - -func (b *BidderClient) Bid( - ctx context.Context, - bidAmount *big.Int, - bridgeAmount *big.Int, - rawTx string, -) (chan BidStatus, error) { - topo, err := b.topologyClient.GetTopology(ctx, &debugapiv1.EmptyMessage{}) - if err != nil { - b.logger.Error("failed to get topology", "error", err) - return nil, err - } - - providers := topo.Topology.Fields["connected_providers"].GetListValue() - if providers == nil || len(providers.Values) == 0 { - return nil, errors.New("no connected providers") - } - - // Channel length chosen is 3 so that sending the bid is not blocked by the first - // status message. - res := make(chan BidStatus, 3) - b.bigWg.Add(1) - go func() { - defer close(res) - defer b.bigWg.Done() - - res <- BidStatus{Type: BidStatusNoOfProviders, Arg1: len(providers.Values)} - - nextSlot, err := b.getNextSlot() - if err != nil { - b.logger.Error("failed to get next slot", "error", err) - res <- BidStatus{Type: BidStatusFailed, Arg2: err.Error()} - return - } - - bidTime := nextSlot.startTime.Add(-1 * time.Second) - wait := bidTime.Sub(nowFunc()) - res <- BidStatus{Type: BidStatusWaitSecs, Arg1: int(wait.Seconds())} - - if wait > 0 { - b.logger.Info("waiting for next slot", "wait", wait) - select { - case <-time.After(wait): - case <-ctx.Done(): - res <- BidStatus{Type: BidStatusFailed, Arg2: ctx.Err().Error()} - return - } - } - - blkNumber, err := b.blkNumberGetter.BlockNumber(ctx) - if err != nil { - b.logger.Error("failed to get block number", "error", err) - res <- BidStatus{Type: BidStatusFailed, Arg2: err.Error()} - return - } - - res <- BidStatus{Type: BidStatusAttempted, Arg1: int(blkNumber + 1)} - - pc, err := b.bidderClient.SendBid(ctx, &bidderapiv1.Bid{ - Amount: bidAmount.String(), - BlockNumber: int64(blkNumber + 1), - RawTransactions: []string{rawTx}, - DecayStartTimestamp: nowFunc().UnixMilli(), - DecayEndTimestamp: nowFunc().Add(12 * time.Second).UnixMilli(), - SlashAmount: bridgeAmount.String(), - }) - if err != nil { - b.logger.Error("failed to send bid", "error", err) - res <- BidStatus{Type: BidStatusFailed, Arg2: err.Error()} - return - } - - commitments := make([]*bidderapiv1.Commitment, 0) - for { - select { - case <-ctx.Done(): - res <- BidStatus{Type: BidStatusFailed, Arg2: ctx.Err().Error()} - return - default: - } - - msg, err := pc.Recv() - if err != nil { - if errors.Is(err, io.EOF) { - break - } - b.logger.Error("failed to receive commitment", "error", err) - res <- BidStatus{Type: BidStatusFailed, Arg2: err.Error()} - return - } - - commitments = append(commitments, msg) - } - - if len(commitments) == len(providers.Values) { - b.logger.Info("all commitments received") - } else { - b.logger.Warn( - "not all commitments received", - "received", len(commitments), - "expected", len(providers.Values), - ) - } - res <- BidStatus{Type: BidStatusSucceeded, Arg1: len(commitments)} - }() - - return res, nil -} - -func (b *BidderClient) Estimate() (int64, error) { - nextSlot, err := b.getNextSlot() - if err != nil { - return 0, err - } - - return int64(nextSlot.startTime.Sub(nowFunc()).Seconds()), nil -} - -func (b *BidderClient) getNextSlot() (slotInfo, error) { - epochInfo := b.currentEpoch.Load() - if epochInfo == nil { - return slotInfo{}, ErrNoEpochInfo - } - - now := nowFunc() - for _, slot := range epochInfo.slots { - if now.Before(slot.startTime) { - return slot, nil - } - } - - return slotInfo{}, ErrNoSlotInCurrentEpoch -} diff --git a/tools/instant-bridge/bidder/bidder_test.go b/tools/instant-bridge/bidder/bidder_test.go deleted file mode 100644 index d4bca666a..000000000 --- a/tools/instant-bridge/bidder/bidder_test.go +++ /dev/null @@ -1,254 +0,0 @@ -package bidder_test - -import ( - "context" - "crypto/rand" - "encoding/hex" - "io" - "math/big" - "os" - "testing" - "time" - - bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" - debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" - notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" - "github.com/primev/mev-commit/tools/instant-bridge/bidder" - "github.com/primev/mev-commit/x/util" - "google.golang.org/grpc" - "google.golang.org/protobuf/types/known/structpb" -) - -type testRPCServices struct { - bidderapiv1.BidderClient - debugapiv1.DebugServiceClient - notificationsapiv1.NotificationsClient - - notificationChan chan *notificationsapiv1.Notification - bidChan chan *bidderapiv1.Bid - commitmentChan chan *bidderapiv1.Commitment - topo *debugapiv1.TopologyResponse -} - -type testNotificationStream struct { - grpc.ClientStream - - ctx context.Context - notificationChan chan *notificationsapiv1.Notification -} - -func (t *testNotificationStream) Recv() (*notificationsapiv1.Notification, error) { - select { - case <-t.ctx.Done(): - return nil, io.EOF - case n := <-t.notificationChan: - return n, nil - } -} - -type testCommitmentStream struct { - grpc.ClientStream - - ctx context.Context - commitmentChan chan *bidderapiv1.Commitment -} - -func (t *testCommitmentStream) Recv() (*bidderapiv1.Commitment, error) { - select { - case <-t.ctx.Done(): - return nil, io.EOF - case c, more := <-t.commitmentChan: - if !more { - return nil, io.EOF - } - return c, nil - } -} - -func (t *testRPCServices) SendBid( - ctx context.Context, - in *bidderapiv1.Bid, - _ ...grpc.CallOption, -) (grpc.ServerStreamingClient[bidderapiv1.Commitment], error) { - select { - case t.bidChan <- in: - case <-ctx.Done(): - return nil, ctx.Err() - } - return &testCommitmentStream{ctx: ctx, commitmentChan: t.commitmentChan}, nil -} - -func (t *testRPCServices) GetTopology( - _ context.Context, - _ *debugapiv1.EmptyMessage, - _ ...grpc.CallOption, -) (*debugapiv1.TopologyResponse, error) { - return t.topo, nil -} - -func (t *testRPCServices) Subscribe( - ctx context.Context, - in *notificationsapiv1.SubscribeRequest, - _ ...grpc.CallOption, -) (grpc.ServerStreamingClient[notificationsapiv1.Notification], error) { - return &testNotificationStream{ctx: ctx, notificationChan: t.notificationChan}, nil -} - -type testBlockNumberGetter struct { - blockNumber uint64 -} - -func (t *testBlockNumberGetter) BlockNumber(ctx context.Context) (uint64, error) { - return t.blockNumber, nil -} - -type testTimeSetter struct { - now time.Time -} - -func (t *testTimeSetter) Now() time.Time { - return t.now -} - -func TestBidderClient(t *testing.T) { - t.Parallel() - - clock := time.Now() - timeSetter := &testTimeSetter{ - now: clock, - } - - bidder.SetNowFunc(timeSetter.Now) - - topoVal, err := structpb.NewStruct(map[string]interface{}{ - "connected_providers": []any{"provider1", "provider2"}, - }) - if err != nil { - t.Fatal(err) - } - // Create a new test RPC services. - rpcServices := &testRPCServices{ - notificationChan: make(chan *notificationsapiv1.Notification), - bidChan: make(chan *bidderapiv1.Bid), - commitmentChan: make(chan *bidderapiv1.Commitment), - topo: &debugapiv1.TopologyResponse{ - Topology: &structpb.Struct{}, - }, - } - - blockNumberGetter := &testBlockNumberGetter{blockNumber: 10} - bidderClient := bidder.NewBidderClient( - util.NewTestLogger(os.Stdout), - rpcServices, - rpcServices, - rpcServices, - blockNumberGetter, - ) - - ctx, cancel := context.WithCancel(context.Background()) - done := bidderClient.Start(ctx) - - _, err = bidderClient.Estimate() - if err != bidder.ErrNoEpochInfo { - t.Fatalf("expected error %v, got %v", bidder.ErrNoEpochInfo, err) - } - - // Send a notification. - nVal, err := structpb.NewStruct(map[string]interface{}{ - "epoch": 1, - "epoch_start_time": clock.Add(2 * time.Second).Unix(), - "slots": []any{ - map[string]interface{}{ - "slot": 33, - "start_time": clock.Add(14 * time.Second).Unix(), - "bls_key": "key2", - "opted_in": true, - }, - }, - }) - if err != nil { - t.Fatal(err) - } - - rpcServices.notificationChan <- ¬ificationsapiv1.Notification{ - Topic: "epoch_validators_opted_in", - Value: nVal, - } - - for { - if _, err := bidderClient.Estimate(); err == nil { - break - } - } - - estimate, err := bidderClient.Estimate() - if err != nil { - t.Fatal(err) - } - if estimate != 13 { - t.Fatalf("expected estimate 13, got %d", estimate) - } - - timeSetter.now = clock.Add(10 * time.Second) - - buf := make([]byte, 32) - _, _ = rand.Read(buf) - txString := hex.EncodeToString(buf) - - _, err = bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString) - if err == nil { - t.Fatal("expected error, got nil") - } - - rpcServices.topo = &debugapiv1.TopologyResponse{ - Topology: topoVal, - } - - statusC, err := bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString) - if err != nil { - t.Fatal(err) - } - -WaitLoop: - for { - select { - case status := <-statusC: - switch status.Type { - case bidder.BidStatusNoOfProviders: - if status.Arg1 != 2 { - t.Fatalf("expected 2 providers, got %d", status.Arg1) - } - case bidder.BidStatusWaitSecs: - if status.Arg1 != 2 { - t.Fatalf("expected 2 seconds, got %d", status.Arg1) - } - case bidder.BidStatusAttempted: - if status.Arg1 != 11 { - t.Fatalf("expected 11, got %d", status.Arg1) - } - case bidder.BidStatusSucceeded: - break WaitLoop - } - case bid := <-rpcServices.bidChan: - if bid.Amount != big.NewInt(1).String() { - t.Fatalf("expected amount 1, got %s", bid.Amount) - } - if bid.BlockNumber != 11 { - t.Fatalf("expected block number 11, got %d", bid.BlockNumber) - } - if bid.RawTransactions[0] != txString { - t.Fatalf("expected raw transaction %x, got %s", buf, bid.RawTransactions[0]) - } - rpcServices.commitmentChan <- &bidderapiv1.Commitment{ - BlockNumber: 11, - } - rpcServices.commitmentChan <- &bidderapiv1.Commitment{ - BlockNumber: 11, - } - close(rpcServices.commitmentChan) - } - } - - cancel() - <-done -} diff --git a/tools/instant-bridge/bidder/export_test.go b/tools/instant-bridge/bidder/export_test.go deleted file mode 100644 index 35e7047a0..000000000 --- a/tools/instant-bridge/bidder/export_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package bidder - -import "time" - -func SetNowFunc(f func() time.Time) { - nowFunc = f -} diff --git a/tools/instant-bridge/service/service.go b/tools/instant-bridge/service/service.go index 324ef7bda..6b96a952f 100644 --- a/tools/instant-bridge/service/service.go +++ b/tools/instant-bridge/service/service.go @@ -14,13 +14,13 @@ import ( bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" - "github.com/primev/mev-commit/tools/instant-bridge/accountsync" "github.com/primev/mev-commit/tools/instant-bridge/api" - "github.com/primev/mev-commit/tools/instant-bridge/bidder" - "github.com/primev/mev-commit/tools/instant-bridge/transfer" + "github.com/primev/mev-commit/x/accountsync" "github.com/primev/mev-commit/x/contracts/ethwrapper" "github.com/primev/mev-commit/x/health" "github.com/primev/mev-commit/x/keysigner" + bidder "github.com/primev/mev-commit/x/opt-in-bidder" + "github.com/primev/mev-commit/x/transfer" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -146,8 +146,6 @@ func New(config *Config) (*Service, error) { transferer := transfer.NewTransferer( config.Logger.With("module", "transferer"), settlementClient, - l1ChainID, - settlementChainID, config.Signer, config.GasTipCap, config.GasFeeCap, @@ -163,6 +161,8 @@ func New(config *Config) (*Service, error) { config.Signer.GetAddress(), l1RPCClient.RawClient(), settlementClient, + l1ChainID, + settlementChainID, ) apiService.Start() diff --git a/tools/preconf-rpc/.goreleaser.yml b/tools/preconf-rpc/.goreleaser.yml new file mode 100644 index 000000000..e9655f183 --- /dev/null +++ b/tools/preconf-rpc/.goreleaser.yml @@ -0,0 +1,64 @@ +version: 1 + +project_name: preconf-rpc +dist: /tmp/dist/preconf-rpc + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + - arm64 + dir: ./tools/preconf-rpc + binary: "{{ .ProjectName }}" + flags: + - -v + - -trimpath + +archives: + - format: tar.gz + name_template: >- + {{- .Binary }}_ + {{- with index .Env "RELEASE_VERSION" -}} + {{ . }} + {{- else -}} + {{- if .IsSnapshot }}{{ .ShortCommit }} + {{- else }}{{ .Version }} + {{- end }} + {{- end -}} + {{- with index .Env "DIRTY_SUFFIX" -}} + {{ . }} + {{- end -}}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }} + {{- end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + format_overrides: + - goos: windows + format: zip + +checksum: + name_template: >- + {{ .ProjectName }}_ + {{- with index .Env "RELEASE_VERSION" -}} + {{ . }} + {{- else -}} + {{- if .IsSnapshot }}{{ .ShortCommit }} + {{- else }}{{ .Version }} + {{- end }} + {{- end -}} + {{- with index .Env "DIRTY_SUFFIX" -}} + {{ . }} + {{- end -}} + _checksums.txt + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/tools/preconf-rpc/main.go b/tools/preconf-rpc/main.go new file mode 100644 index 000000000..ad940cdb8 --- /dev/null +++ b/tools/preconf-rpc/main.go @@ -0,0 +1,252 @@ +package main + +import ( + "fmt" + "math/big" + "os" + "os/signal" + "slices" + "strings" + "syscall" + + "github.com/ethereum/go-ethereum/common" + "github.com/primev/mev-commit/tools/preconf-rpc/service" + "github.com/primev/mev-commit/x/keysigner" + "github.com/primev/mev-commit/x/util" + "github.com/urfave/cli/v2" +) + +var ( + optionHTTPPort = &cli.IntFlag{ + Name: "http-port", + Usage: "port for the HTTP server", + EnvVars: []string{"INSTANT_BRIDGE_HTTP_PORT"}, + Value: 8080, + } + + optionKeystorePath = &cli.StringFlag{ + Name: "keystore-dir", + Usage: "directory where keystore file is stored", + EnvVars: []string{"INSTANT_BRIDGE_KEYSTORE_DIR"}, + Required: true, + } + + optionKeystorePassword = &cli.StringFlag{ + Name: "keystore-password", + Usage: "use to access keystore", + EnvVars: []string{"INSTANT_BRIDGE_KEYSTORE_PASSWORD"}, + Required: true, + } + + optionL1RPCUrls = &cli.StringSliceFlag{ + Name: "l1-rpc-urls", + Usage: "URLs for L1 RPC", + EnvVars: []string{"INSTANT_BRIDGE_L1_RPC_URLS"}, + Required: true, + } + + optionSettlementRPCUrl = &cli.StringFlag{ + Name: "settlement-rpc-url", + Usage: "URL for settlement RPC", + EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_RPC_URL"}, + Required: true, + } + + optionBidderRPCUrl = &cli.StringFlag{ + Name: "bidder-rpc-url", + Usage: "URL for mev-commit bidder RPC", + EnvVars: []string{"INSTANT_BRIDGE_BIDDER_RPC_URL"}, + Required: true, + } + + optionL1ContractAddr = &cli.StringFlag{ + Name: "l1-contract-addr", + Usage: "address of the L1 gateway contract", + EnvVars: []string{"INSTANT_BRIDGE_L1_CONTRACT_ADDR"}, + Required: true, + } + + optionSettlementThreshold = &cli.StringFlag{ + Name: "settlement-threshold", + Usage: "Minimum threshold for settlement chain balance", + EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_THRESHOLD"}, + Value: "5000000000000000000", // 5 ETH + } + + optionSettlementTopup = &cli.StringFlag{ + Name: "settlement-topup", + Usage: "topup for settlement", + EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_TOPUP"}, + Value: "10000000000000000000", // 10 ETH + } + + optionAutoDepositAmount = &cli.StringFlag{ + Name: "auto-deposit-amount", + Usage: "auto deposit amount", + EnvVars: []string{"INSTANT_BRIDGE_AUTO_DEPOSIT_AMOUNT"}, + Value: "1000000000000000000", // 1 ETH + } + + optionGasTipCap = &cli.StringFlag{ + Name: "gas-tip-cap", + Usage: "gas tip cap", + EnvVars: []string{"INSTANT_BRIDGE_GAS_TIP_CAP"}, + Value: "50000000", // 0.05 gWEI + } + + optionGasFeeCap = &cli.StringFlag{ + Name: "gas-fee-cap", + Usage: "gas fee cap", + EnvVars: []string{"INSTANT_BRIDGE_GAS_FEE_CAP"}, + Value: "60000000", // 0.06 gWEI + } + + optionSettlementContractAddr = &cli.StringFlag{ + Name: "settlement-contract-addr", + Usage: "address of the settlement gateway contract", + EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_CONTRACT_ADDR"}, + Required: true, + } + + optionLogFmt = &cli.StringFlag{ + Name: "log-fmt", + Usage: "log format to use, options are 'text' or 'json'", + EnvVars: []string{"INSTANT_BRIDGE_LOG_FMT"}, + Value: "text", + Action: func(ctx *cli.Context, s string) error { + if !slices.Contains([]string{"text", "json"}, s) { + return fmt.Errorf("invalid log-fmt, expecting 'text' or 'json'") + } + return nil + }, + } + + optionLogLevel = &cli.StringFlag{ + Name: "log-level", + Usage: "log level to use, options are 'debug', 'info', 'warn', 'error'", + EnvVars: []string{"INSTANT_BRIDGE_LOG_LEVEL"}, + Value: "info", + Action: func(ctx *cli.Context, s string) error { + if !slices.Contains([]string{"debug", "info", "warn", "error"}, s) { + return fmt.Errorf("invalid log-level, expecting 'debug', 'info', 'warn', 'error'") + } + return nil + }, + } + + optionLogTags = &cli.StringFlag{ + Name: "log-tags", + Usage: "log tags is a comma-separated list of pairs that will be inserted into each log line", + EnvVars: []string{"INSTANT_BRIDGE_LOG_TAGS"}, + Action: func(ctx *cli.Context, s string) error { + for i, p := range strings.Split(s, ",") { + if len(strings.Split(p, ":")) != 2 { + return fmt.Errorf("invalid log-tags at index %d, expecting ", i) + } + } + return nil + }, + } +) + +func main() { + app := &cli.App{ + Name: "preconf-rpc", + Usage: "Preconf RPC service", + Flags: []cli.Flag{ + optionHTTPPort, + optionLogFmt, + optionLogLevel, + optionLogTags, + optionKeystorePath, + optionKeystorePassword, + optionL1RPCUrls, + optionSettlementRPCUrl, + optionBidderRPCUrl, + optionL1ContractAddr, + optionSettlementThreshold, + optionSettlementTopup, + optionGasTipCap, + optionGasFeeCap, + optionSettlementContractAddr, + optionAutoDepositAmount, + }, + Action: func(c *cli.Context) error { + logger, err := util.NewLogger( + c.String(optionLogLevel.Name), + c.String(optionLogFmt.Name), + c.String(optionLogTags.Name), + c.App.Writer, + ) + if err != nil { + return fmt.Errorf("failed to create logger: %w", err) + } + + gasTipCap, ok := new(big.Int).SetString(c.String(optionGasTipCap.Name), 10) + if !ok { + return fmt.Errorf("failed to parse gas-tip-cap") + } + + gasFeeCap, ok := new(big.Int).SetString(c.String(optionGasFeeCap.Name), 10) + if !ok { + return fmt.Errorf("failed to parse gas-fee-cap") + } + + autoDepositAmount, ok := new(big.Int).SetString(c.String(optionAutoDepositAmount.Name), 10) + if !ok { + return fmt.Errorf("failed to parse auto-deposit-amount") + } + + settlementThreshold, ok := new(big.Int).SetString(c.String(optionSettlementThreshold.Name), 10) + if !ok { + return fmt.Errorf("failed to parse settlement-threshold") + } + + settlementTopup, ok := new(big.Int).SetString(c.String(optionSettlementTopup.Name), 10) + if !ok { + return fmt.Errorf("failed to parse settlement-topup") + } + + signer, err := keysigner.NewKeystoreSigner( + c.String(optionKeystorePath.Name), + c.String(optionKeystorePassword.Name), + ) + if err != nil { + return fmt.Errorf("failed to create signer: %w", err) + } + + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) + + config := service.Config{ + HTTPPort: c.Int(optionHTTPPort.Name), + Logger: logger, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + AutoDepositAmount: autoDepositAmount, + SettlementThreshold: settlementThreshold, + SettlementTopup: settlementTopup, + SettlementRPCUrl: c.String(optionSettlementRPCUrl.Name), + BidderRPC: c.String(optionBidderRPCUrl.Name), + L1RPCUrls: c.StringSlice(optionL1RPCUrls.Name), + L1ContractAddr: common.HexToAddress(c.String(optionL1ContractAddr.Name)), + SettlementContractAddr: common.HexToAddress(c.String(optionSettlementContractAddr.Name)), + Signer: signer, + } + + s, err := service.New(&config) + if err != nil { + return fmt.Errorf("failed to create service: %w", err) + } + + <-sigc + logger.Info("shutting down...") + + return s.Close() + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + } +} diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index 324ef7bda..398611b69 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -4,9 +4,11 @@ import ( "context" "crypto/tls" "errors" + "fmt" "io" "log/slog" "math/big" + "net/http" "time" "github.com/ethereum/go-ethereum/common" @@ -14,13 +16,13 @@ import ( bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" - "github.com/primev/mev-commit/tools/instant-bridge/accountsync" - "github.com/primev/mev-commit/tools/instant-bridge/api" - "github.com/primev/mev-commit/tools/instant-bridge/bidder" - "github.com/primev/mev-commit/tools/instant-bridge/transfer" + "github.com/primev/mev-commit/tools/preconf-rpc/rpcserver" + "github.com/primev/mev-commit/x/accountsync" "github.com/primev/mev-commit/x/contracts/ethwrapper" "github.com/primev/mev-commit/x/health" "github.com/primev/mev-commit/x/keysigner" + bidder "github.com/primev/mev-commit/x/opt-in-bidder" + "github.com/primev/mev-commit/x/transfer" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -37,7 +39,6 @@ type Config struct { SettlementThreshold *big.Int SettlementTopup *big.Int HTTPPort int - MinServiceFee *big.Int GasTipCap *big.Int GasFeeCap *big.Int } @@ -70,19 +71,10 @@ func New(config *Config) (*Service, error) { if err != nil { return nil, err } - l1ChainID, err := l1RPCClient.RawClient().ChainID(context.Background()) - if err != nil { - return nil, err - } - settlementClient, err := ethclient.Dial(config.SettlementRPCUrl) if err != nil { return nil, err } - settlementChainID, err := settlementClient.ChainID(context.Background()) - if err != nil { - return nil, err - } bidderCli := bidderapiv1.NewBidderClient(conn) topologyCli := debugapiv1.NewDebugServiceClient(conn) @@ -143,30 +135,25 @@ func New(config *Config) (*Service, error) { healthChecker.Register(health.CloseChannelHealthCheck("BidderService", bidderDone)) s.closers = append(s.closers, channelCloser(bidderDone)) - transferer := transfer.NewTransferer( - config.Logger.With("module", "transferer"), - settlementClient, - l1ChainID, - settlementChainID, - config.Signer, - config.GasTipCap, - config.GasFeeCap, + rpcServer := rpcserver.NewJSONRPCServer( + config.L1RPCUrls[0], ) - apiService := api.NewAPI( - config.Logger.With("module", "api"), - config.HTTPPort, - healthChecker, - bidderClient, - transferer, - config.MinServiceFee, - config.Signer.GetAddress(), - l1RPCClient.RawClient(), - settlementClient, - ) + // TODO: Implement the handler for the RPC methods. Also use some sort of + // database to store the state of the service like balances of users etc. + + srv := http.Server{ + Addr: fmt.Sprintf(":%d", config.HTTPPort), + Handler: rpcServer, + } + + go func() { + if err := srv.ListenAndServe(); err != nil { + config.Logger.Error("failed to start HTTP server", "error", err) + } + }() - apiService.Start() - s.closers = append(s.closers, apiService) + s.closers = append(s.closers, &srv) return s, nil } diff --git a/tools/instant-bridge/transfer/bridger.go b/x/transfer/bridger.go similarity index 92% rename from tools/instant-bridge/transfer/bridger.go rename to x/transfer/bridger.go index 6380daa8b..e007407f6 100644 --- a/tools/instant-bridge/transfer/bridger.go +++ b/x/transfer/bridger.go @@ -6,10 +6,12 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/primev/mev-commit/bridge/standard/pkg/transfer" + bridgetransfer "github.com/primev/mev-commit/bridge/standard/pkg/transfer" "github.com/primev/mev-commit/x/keysigner" ) +var transferFunc = bridgetransfer.NewTransferToSettlement + type AccountSyncer interface { Subscribe(ctx context.Context, threshold *big.Int) <-chan struct{} } @@ -64,7 +66,7 @@ func (b *Bridger) Start(ctx context.Context) <-chan struct{} { "topup", b.topup, "address", b.config.Signer.GetAddress().Hex(), ) - tx, err := transfer.NewTransferToSettlement( + tx, err := transferFunc( b.topup, b.config.Signer.GetAddress(), b.config.Signer, diff --git a/x/transfer/bridger_test.go b/x/transfer/bridger_test.go new file mode 100644 index 000000000..4b895b8fa --- /dev/null +++ b/x/transfer/bridger_test.go @@ -0,0 +1,131 @@ +package transfer_test + +import ( + "context" + "log/slog" + "math/big" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + bridgetransfer "github.com/primev/mev-commit/bridge/standard/pkg/transfer" + "github.com/primev/mev-commit/x/keysigner" + "github.com/primev/mev-commit/x/transfer" +) + +type MockAccountSyncer struct { + trigger chan struct{} +} + +func (m *MockAccountSyncer) Subscribe(ctx context.Context, threshold *big.Int) <-chan struct{} { + ch := make(chan struct{}) + go func() { + <-m.trigger + close(ch) + }() + return ch +} + +type MockTransfer struct { + called int + amount *big.Int +} + +func (m *MockTransfer) Do(ctx context.Context) <-chan bridgetransfer.TransferStatus { + ch := make(chan bridgetransfer.TransferStatus, 1) + ch <- bridgetransfer.TransferStatus{ + Message: "Transfer Done", + Error: nil, + } + close(ch) + m.called++ + return ch +} + +type keySigner struct { + keysigner.KeySigner +} + +func (k *keySigner) GetAddress() common.Address { + return common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") +} + +func TestBridger(t *testing.T) { + t.Parallel() + + syncer := &MockAccountSyncer{ + trigger: make(chan struct{}), + } + + txfer := &MockTransfer{} + + done := transfer.SetTransferFunc(func( + amount *big.Int, + destAddress common.Address, + signer keysigner.KeySigner, + settlementRPCUrl string, + l1RPCUrl string, + l1ContractAddr common.Address, + settlementContractAddr common.Address, + ) (bridgetransfer.Transfer, error) { + txfer.amount = amount + return txfer, nil + }, + ) + t.Cleanup(done) + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) + + bridger := transfer.NewBridger( + logger, + syncer, + transfer.BridgeConfig{ + Signer: &keySigner{}, + L1ContractAddr: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + SettlementContractAddr: common.HexToAddress("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"), + L1RPCUrl: "http://localhost:8545", + SettlementRPCUrl: "http://localhost:8546", + }, + big.NewInt(1000000000000000000), + big.NewInt(1000000000000000000), + ) + ctx, cancel := context.WithCancel(context.Background()) + closed := bridger.Start(ctx) + + // Simulate the syncer triggering the event + syncer.trigger <- struct{}{} + + start := time.Now() + for { + if time.Since(start) > 5*time.Second { + t.Fatal("Timeout waiting for transfer to be called") + } + if txfer.called == 1 { + break + } + } + + if txfer.amount.Cmp(big.NewInt(1000000000000000000)) != 0 { + t.Fatalf("Expected amount to be 1 ETH, got %s", txfer.amount.String()) + } + + syncer.trigger <- struct{}{} + + start = time.Now() + for { + if time.Since(start) > 5*time.Second { + t.Fatal("Timeout waiting for transfer to be called again") + } + if txfer.called == 2 { + break + } + } + + if txfer.amount.Cmp(big.NewInt(1000000000000000000)) != 0 { + t.Fatalf("Expected amount to be 1 ETH, got %s", txfer.amount.String()) + } + + cancel() + <-closed + +} diff --git a/x/transfer/export_test.go b/x/transfer/export_test.go new file mode 100644 index 000000000..b6422acc9 --- /dev/null +++ b/x/transfer/export_test.go @@ -0,0 +1,25 @@ +package transfer + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + bridgetransfer "github.com/primev/mev-commit/bridge/standard/pkg/transfer" + "github.com/primev/mev-commit/x/keysigner" +) + +func SetTransferFunc(f func( + amount *big.Int, + destAddress common.Address, + signer keysigner.KeySigner, + settlementRPCUrl string, + l1RPCUrl string, + l1ContractAddr common.Address, + settlementContractAddr common.Address, +) (bridgetransfer.Transfer, error)) func() { + prev := transferFunc + transferFunc = f + return func() { + transferFunc = prev + } +} diff --git a/tools/instant-bridge/transfer/transfer.go b/x/transfer/transfer.go similarity index 57% rename from tools/instant-bridge/transfer/transfer.go rename to x/transfer/transfer.go index 4ad8c9094..4e0f995fe 100644 --- a/tools/instant-bridge/transfer/transfer.go +++ b/x/transfer/transfer.go @@ -11,50 +11,82 @@ 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/ethclient" - "github.com/primev/mev-commit/x/keysigner" ) +type EthClient interface { + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + SendTransaction(ctx context.Context, tx *types.Transaction) error + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) +} + +type Signer interface { + GetAddress() common.Address + SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) +} + type Transferer struct { - mtx sync.Mutex - logger *slog.Logger - client *ethclient.Client - l1ChainID *big.Int - settlementChainID *big.Int - signer keysigner.KeySigner - gasTip *big.Int - gasFeeCap *big.Int + mtx sync.Mutex + logger *slog.Logger + client EthClient + signer Signer + gasTip *big.Int + gasFeeCap *big.Int } func NewTransferer( logger *slog.Logger, - client *ethclient.Client, - l1ChainID *big.Int, - settlementChainID *big.Int, - signer keysigner.KeySigner, + client EthClient, + signer Signer, gasTip *big.Int, gasFeeCap *big.Int, ) *Transferer { return &Transferer{ - logger: logger, - client: client, - l1ChainID: l1ChainID, - settlementChainID: settlementChainID, - signer: signer, - gasTip: gasTip, - gasFeeCap: gasFeeCap, + logger: logger, + client: client, + signer: signer, + gasTip: gasTip, + gasFeeCap: gasFeeCap, } } -func (t *Transferer) TransferOnSettlement( +func (t *Transferer) Transfer( ctx context.Context, to common.Address, + chainID *big.Int, amount *big.Int, ) error { // Only one transfer at a time t.mtx.Lock() defer t.mtx.Unlock() + if to == (common.Address{}) { + t.logger.Error("invalid address") + return errors.New("invalid address") + } + + if amount.Sign() <= 0 { + t.logger.Error("invalid amount") + return errors.New("invalid amount") + } + + if chainID.Cmp(big.NewInt(0)) <= 0 { + t.logger.Error("invalid chain ID") + return errors.New("invalid chain ID") + } + + // Check if the account is a contract + code, err := t.client.CodeAt(ctx, to, nil) + if err != nil { + t.logger.Error("failed to get code", "error", err) + return err + } + + if len(code) > 0 { + t.logger.Error("address is a contract") + return errors.New("address is a contract") + } + nonce, err := t.client.PendingNonceAt(ctx, t.signer.GetAddress()) if err != nil { t.logger.Error("failed to get nonce", "error", err) @@ -62,6 +94,7 @@ func (t *Transferer) TransferOnSettlement( } txData := &types.DynamicFeeTx{ To: &to, + ChainID: chainID, Nonce: nonce, GasFeeCap: t.gasFeeCap, GasTipCap: t.gasTip, @@ -71,7 +104,7 @@ func (t *Transferer) TransferOnSettlement( tx := types.NewTx(txData) - signedTx, err := t.signer.SignTx(tx, t.settlementChainID) + signedTx, err := t.signer.SignTx(tx, chainID) if err != nil { t.logger.Error("failed to sign tx", "error", err) return err @@ -97,7 +130,7 @@ func (t *Transferer) TransferOnSettlement( return nil } -func (t *Transferer) ValidateL1Tx(rawTx string) (*types.Transaction, error) { +func (t *Transferer) ValidateTx(rawTx string, chainID *big.Int) (*types.Transaction, error) { txBytes, err := hex.DecodeString(rawTx) if err != nil { t.logger.Error("failed to decode tx", "error", err) @@ -110,8 +143,8 @@ func (t *Transferer) ValidateL1Tx(rawTx string) (*types.Transaction, error) { return nil, err } - if tx.ChainId().Cmp(t.l1ChainID) != 0 { - t.logger.Error("tx has wrong chain ID", "chainID", tx.ChainId(), "expected", t.l1ChainID) + if tx.ChainId().Cmp(chainID) != 0 { + t.logger.Error("tx has wrong chain ID", "chainID", tx.ChainId(), "expected", chainID) return nil, errors.New("tx has wrong chain ID") } diff --git a/x/transfer/transfer_test.go b/x/transfer/transfer_test.go new file mode 100644 index 000000000..7b2a8d6a1 --- /dev/null +++ b/x/transfer/transfer_test.go @@ -0,0 +1,97 @@ +package transfer_test + +import ( + "context" + "log/slog" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/primev/mev-commit/x/transfer" +) + +type MockEthClient struct{} + +func (m *MockEthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + return 0, nil +} + +func (m *MockEthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + return nil +} + +func (m *MockEthClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + return &types.Receipt{ + Status: 1, + }, nil +} + +func (m *MockEthClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { + return []byte{}, nil +} + +type MockKeySigner struct { + transaction *types.Transaction +} + +func (m *MockKeySigner) GetAddress() common.Address { + return common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") +} + +func (m *MockKeySigner) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + m.transaction = tx + return tx, nil +} + +func TestTransferrer(t *testing.T) { + t.Parallel() + + // Mock the EthClient and KeySigner interfaces + client := new(MockEthClient) + signer := new(MockKeySigner) + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) + gasTip := big.NewInt(1000000000) // 1 Gwei + gasFeeCap := big.NewInt(2000000000) // 2 Gwei + transferer := transfer.NewTransferer(logger, client, signer, gasTip, gasFeeCap) + + // Mock the context + ctx := context.Background() + // Mock the address and amount + to := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + chainID := big.NewInt(1) // Mainnet + amount := big.NewInt(1000000000000000000) // 1 ETH + + // Call the Transfer method + err := transferer.Transfer(ctx, to, chainID, amount) + if err != nil { + t.Fatalf("Transfer failed: %v", err) + } + + // Check if the transaction was signed correctly + if signer.transaction == nil { + t.Fatal("Transaction was not signed") + } + // Check if the transaction was sent correctly + if signer.transaction.To() == nil { + t.Fatal("Transaction was not sent") + } + if signer.transaction.Value().Cmp(amount) != 0 { + t.Fatalf("Transaction amount mismatch: expected %s, got %s", amount.String(), signer.transaction.Value().String()) + } + if signer.transaction.GasTipCap().Cmp(gasTip) != 0 { + t.Fatalf("Transaction gas tip cap mismatch: expected %s, got %s", gasTip.String(), signer.transaction.GasTipCap().String()) + } + if signer.transaction.GasFeeCap().Cmp(gasFeeCap) != 0 { + t.Fatalf("Transaction gas fee cap mismatch: expected %s, got %s", gasFeeCap.String(), signer.transaction.GasFeeCap().String()) + } + // Check if the transaction was sent to the correct address + if signer.transaction.To().Hex() != to.Hex() { + t.Fatalf("Transaction to address mismatch: expected %s, got %s", to.Hex(), signer.transaction.To().Hex()) + } + // Check if the transaction was sent with the correct chain ID + if signer.transaction.ChainId().Cmp(chainID) != 0 { + t.Fatalf("Transaction chain ID mismatch: expected %s, got %s", chainID.String(), signer.transaction.ChainId().String()) + } +} From ec230b696c45f024c7231bec0a973353ed2d311a Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 18:53:00 +0530 Subject: [PATCH 07/69] feat: preconf RPC service --- x/opt-in-bidder/bidder_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x/opt-in-bidder/bidder_test.go b/x/opt-in-bidder/bidder_test.go index 5018ff842..ef8d17670 100644 --- a/x/opt-in-bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder_test.go @@ -214,25 +214,25 @@ waitLoop: for { select { case status := <-statusC: - switch { - case status.Type == optinbidder.BidStatusNoOfProviders: + switch status.Type { + case optinbidder.BidStatusNoOfProviders: if status.Arg.(int) != 2 { t.Fatalf("expected 2 providers, got %d", status.Arg) } - case status.Type == optinbidder.BidStatusWaitSecs: + case optinbidder.BidStatusWaitSecs: if status.Arg.(int) != 2 { t.Fatalf("expected 2 seconds, got %d", status.Arg) } - case status.Type == optinbidder.BidStatusAttempted: + case optinbidder.BidStatusAttempted: if status.Arg.(int) != 11 { t.Fatalf("expected 11, got %d", status.Arg) } - case status.Type == optinbidder.BidStatusCommitment: + case optinbidder.BidStatusCommitment: if status.Arg.(*bidderapiv1.Commitment).BlockNumber != 11 { t.Fatalf("expected block number 11, got %d", status.Arg.(*bidderapiv1.Commitment).BlockNumber) } commitments++ - case status.Type == optinbidder.BidStatusDone: + case optinbidder.BidStatusDone: break waitLoop } case bid := <-rpcServices.bidChan: From 7a431f1d8deabf748de0cbd22ac50537ece2eeaa Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 19:02:04 +0530 Subject: [PATCH 08/69] feat: preconf RPC service --- x/accountsync/accountsync_test.go | 2 +- x/opt-in-bidder/bidder_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x/accountsync/accountsync_test.go b/x/accountsync/accountsync_test.go index e52813e78..53f2b3bcb 100644 --- a/x/accountsync/accountsync_test.go +++ b/x/accountsync/accountsync_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/primev/mev-commit/tools/instant-bridge/accountsync" + "github.com/primev/mev-commit/x/accountsync" ) type mockBalanceGetter struct { diff --git a/x/opt-in-bidder/bidder_test.go b/x/opt-in-bidder/bidder_test.go index ef8d17670..a64c8bdda 100644 --- a/x/opt-in-bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder_test.go @@ -13,7 +13,7 @@ import ( bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" - optinbidder "github.com/primev/mev-commit/x/opt-in-bidder/bidder" + optinbidder "github.com/primev/mev-commit/x/opt-in-bidder" "github.com/primev/mev-commit/x/util" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/structpb" From dbc1f043618ab9a78630d2967206997fcb790518 Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 19:45:02 +0530 Subject: [PATCH 09/69] feat: preconf RPC service --- tools/go.mod | 9 ++++++--- tools/go.sum | 2 ++ x/go.mod | 5 ++++- x/go.sum | 12 ++++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index f0dc09db0..06262d1cf 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -11,7 +11,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/mattn/go-sqlite3 v1.14.24 - github.com/primev/mev-commit/bridge/standard v0.0.1 + github.com/primev/mev-commit/bridge/standard v0.0.1 // indirect github.com/primev/mev-commit/contracts-abi v0.0.1 github.com/primev/mev-commit/p2p v0.0.1 github.com/primev/mev-commit/x v0.0.1 @@ -21,7 +21,10 @@ require ( google.golang.org/protobuf v1.34.2 ) -require github.com/DATA-DOG/go-sqlmock v1.5.2 +require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 + github.com/primev/mev-commit/oracle v0.0.0-20250519054853-c4dccc38837f +) require ( github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect @@ -75,7 +78,7 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/crypto v0.35.0 // indirect golang.org/x/net v0.36.0 // indirect - golang.org/x/sync v0.11.0 // indirect + golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/tools/go.sum b/tools/go.sum index 5b8846f59..7d5604702 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -179,6 +179,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/primev/mev-commit/oracle v0.0.0-20250519054853-c4dccc38837f h1:QsAS/1UoUnxzaea6uw4HH7xfyoUOaByhyq9Hpl+QiAc= +github.com/primev/mev-commit/oracle v0.0.0-20250519054853-c4dccc38837f/go.mod h1:DkdY3unugcUfB6DDf7+yGY9uSzIxGGP1vNITsNkp/t8= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= diff --git a/x/go.mod b/x/go.mod index 6f840f43d..981d75e63 100644 --- a/x/go.mod +++ b/x/go.mod @@ -7,7 +7,9 @@ toolchain go1.24.0 require ( github.com/ethereum/go-ethereum v1.15.11 github.com/google/go-cmp v0.6.0 + github.com/primev/mev-commit/bridge/standard v0.0.0-20250519054853-c4dccc38837f github.com/primev/mev-commit/contracts-abi v0.0.1 + github.com/primev/mev-commit/p2p v0.0.0-20250519054853-c4dccc38837f github.com/prometheus/client_golang v1.19.1 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.28.0 @@ -16,9 +18,11 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 go.opentelemetry.io/otel/sdk v1.28.0 google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.34.2 ) require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20240221180331-f05a6f4403ce.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -67,7 +71,6 @@ require ( golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect - google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/x/go.sum b/x/go.sum index 6bf72b15c..3adc78183 100644 --- a/x/go.sum +++ b/x/go.sum @@ -1,3 +1,5 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20240221180331-f05a6f4403ce.1 h1:AmmAwHbvaeOIxDKG2+aTn5C36HjmFIMkrdTp49rp80Q= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20240221180331-f05a6f4403ce.1/go.mod h1:tiTMKD8j6Pd/D2WzREoweufjzaJKHZg35f/VGcZ2v3I= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -78,8 +80,10 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -151,6 +155,7 @@ github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks= github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc= @@ -161,6 +166,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/primev/mev-commit/bridge/standard v0.0.0-20250519054853-c4dccc38837f h1:oQwQsW50+ctLPsucph6Kato9hkyEO4ouFI/UhBxqKWg= +github.com/primev/mev-commit/bridge/standard v0.0.0-20250519054853-c4dccc38837f/go.mod h1:2ggyr94ORSRQt1u1RV1s2jem24aPLjyzvRim66P2gRg= +github.com/primev/mev-commit/p2p v0.0.0-20250519054853-c4dccc38837f h1:L3XnHB2M4XT+PJ2sFnin4LytIVdtOxg0NwCt5vz0PxU= +github.com/primev/mev-commit/p2p v0.0.0-20250519054853-c4dccc38837f/go.mod h1:OlBj0Kig5m44jbrZNLu6xQD4OIhrbX8StrkcRi7pO1o= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -228,12 +237,15 @@ golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From d36eaf80c1b358679823e7ed6dafe920a6ba979f Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 20:13:48 +0530 Subject: [PATCH 10/69] feat: preconf RPC service --- go.work.sum | 1 + x/go.mod | 8 ++++++-- x/go.sum | 4 ---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.work.sum b/go.work.sum index afe9f6f34..e7b42451b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1796,6 +1796,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= diff --git a/x/go.mod b/x/go.mod index 981d75e63..b3c59f6ba 100644 --- a/x/go.mod +++ b/x/go.mod @@ -7,9 +7,9 @@ toolchain go1.24.0 require ( github.com/ethereum/go-ethereum v1.15.11 github.com/google/go-cmp v0.6.0 - github.com/primev/mev-commit/bridge/standard v0.0.0-20250519054853-c4dccc38837f + github.com/primev/mev-commit/bridge/standard v0.0.1 github.com/primev/mev-commit/contracts-abi v0.0.1 - github.com/primev/mev-commit/p2p v0.0.0-20250519054853-c4dccc38837f + github.com/primev/mev-commit/p2p v0.0.1 github.com/prometheus/client_golang v1.19.1 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.28.0 @@ -76,3 +76,7 @@ require ( ) replace github.com/primev/mev-commit/contracts-abi => ../contracts-abi + +replace github.com/primev/mev-commit/bridge/standard => ../bridge/standard + +replace github.com/primev/mev-commit/p2p => ../p2p diff --git a/x/go.sum b/x/go.sum index 3adc78183..9c7fc382a 100644 --- a/x/go.sum +++ b/x/go.sum @@ -166,10 +166,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/primev/mev-commit/bridge/standard v0.0.0-20250519054853-c4dccc38837f h1:oQwQsW50+ctLPsucph6Kato9hkyEO4ouFI/UhBxqKWg= -github.com/primev/mev-commit/bridge/standard v0.0.0-20250519054853-c4dccc38837f/go.mod h1:2ggyr94ORSRQt1u1RV1s2jem24aPLjyzvRim66P2gRg= -github.com/primev/mev-commit/p2p v0.0.0-20250519054853-c4dccc38837f h1:L3XnHB2M4XT+PJ2sFnin4LytIVdtOxg0NwCt5vz0PxU= -github.com/primev/mev-commit/p2p v0.0.0-20250519054853-c4dccc38837f/go.mod h1:OlBj0Kig5m44jbrZNLu6xQD4OIhrbX8StrkcRi7pO1o= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= From 2f8895efef6dec9d5ec55886470b90e96ad81927 Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 20:19:02 +0530 Subject: [PATCH 11/69] feat: preconf RPC service --- tools/go.mod | 4 +++- tools/go.sum | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 06262d1cf..cc1a97740 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -23,7 +23,7 @@ require ( require ( github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/primev/mev-commit/oracle v0.0.0-20250519054853-c4dccc38837f + github.com/primev/mev-commit/oracle v0.0.1 ) require ( @@ -40,6 +40,8 @@ replace github.com/primev/mev-commit/contracts-abi => ../contracts-abi replace github.com/primev/mev-commit/bridge/standard => ../bridge/standard +replace github.com/primev/mev-commit/oracle => ../oracle + require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20240221180331-f05a6f4403ce.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect diff --git a/tools/go.sum b/tools/go.sum index 7d5604702..5b8846f59 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -179,8 +179,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/primev/mev-commit/oracle v0.0.0-20250519054853-c4dccc38837f h1:QsAS/1UoUnxzaea6uw4HH7xfyoUOaByhyq9Hpl+QiAc= -github.com/primev/mev-commit/oracle v0.0.0-20250519054853-c4dccc38837f/go.mod h1:DkdY3unugcUfB6DDf7+yGY9uSzIxGGP1vNITsNkp/t8= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= From 575a09e9ba82ee985366137b98b9223b6711ecae Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 20:34:02 +0530 Subject: [PATCH 12/69] feat: preconf RPC service --- tools/go.mod | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index cc1a97740..7cf15f8e0 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -21,10 +21,7 @@ require ( google.golang.org/protobuf v1.34.2 ) -require ( - github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/primev/mev-commit/oracle v0.0.1 -) +require github.com/DATA-DOG/go-sqlmock v1.5.2 require ( github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect @@ -40,8 +37,6 @@ replace github.com/primev/mev-commit/contracts-abi => ../contracts-abi replace github.com/primev/mev-commit/bridge/standard => ../bridge/standard -replace github.com/primev/mev-commit/oracle => ../oracle - require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.32.0-20240221180331-f05a6f4403ce.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect @@ -80,7 +75,7 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/crypto v0.35.0 // indirect golang.org/x/net v0.36.0 // indirect - golang.org/x/sync v0.11.0 + golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect From 8c4674d1ef8a917ced066abb91784cba0de29772 Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 20:53:12 +0530 Subject: [PATCH 13/69] feat: preconf RPC service --- tools/instant-bridge/api/api.go | 3 --- tools/preconf-rpc/rpcserver/rpcserver.go | 8 ++++++-- x/opt-in-bidder/bidder.go | 4 +--- x/opt-in-bidder/bidder_test.go | 8 ++++---- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tools/instant-bridge/api/api.go b/tools/instant-bridge/api/api.go index 808059b26..72162c00e 100644 --- a/tools/instant-bridge/api/api.go +++ b/tools/instant-bridge/api/api.go @@ -245,9 +245,6 @@ func NewAPI( fmt.Errorf("bid failed: %s", status.Arg.(string)), ) return - case bidder.BidStatusDone: - a.logger.Info("bid succeeded", "block", status.Arg.(int)) - break } } diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index b4fe5b24a..98503f00b 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -71,7 +71,9 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBodySize) - defer r.Body.Close() + defer func() { + _ = r.Body.Close() + }() body, err := io.ReadAll(r.Body) if err != nil { @@ -162,7 +164,9 @@ func (s *JSONRPCServer) proxyRequest(w http.ResponseWriter, r *http.Request) { http.Error(w, "Failed to execute proxy request", http.StatusInternalServerError) return } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) diff --git a/x/opt-in-bidder/bidder.go b/x/opt-in-bidder/bidder.go index d1d838469..d66e81712 100644 --- a/x/opt-in-bidder/bidder.go +++ b/x/opt-in-bidder/bidder.go @@ -176,7 +176,6 @@ const ( BidStatusFailed BidStatusCancelled BidStatusCommitment - BidStatusDone ) type BidStatus struct { @@ -269,8 +268,7 @@ func (b *BidderClient) Bid( msg, err := pc.Recv() if err != nil { if errors.Is(err, io.EOF) { - res <- BidStatus{Type: BidStatusDone, Arg: nil} - break + return } if errors.Is(err, context.Canceled) { res <- BidStatus{Type: BidStatusCancelled, Arg: err.Error()} diff --git a/x/opt-in-bidder/bidder_test.go b/x/opt-in-bidder/bidder_test.go index a64c8bdda..01e743867 100644 --- a/x/opt-in-bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder_test.go @@ -213,7 +213,10 @@ func TestBidderClient(t *testing.T) { waitLoop: for { select { - case status := <-statusC: + case status, more := <-statusC: + if !more { + break waitLoop + } switch status.Type { case optinbidder.BidStatusNoOfProviders: if status.Arg.(int) != 2 { @@ -232,9 +235,6 @@ waitLoop: t.Fatalf("expected block number 11, got %d", status.Arg.(*bidderapiv1.Commitment).BlockNumber) } commitments++ - case optinbidder.BidStatusDone: - break waitLoop - } case bid := <-rpcServices.bidChan: if bid.Amount != big.NewInt(1).String() { t.Fatalf("expected amount 1, got %s", bid.Amount) From 2a149245cb9257d736b45bd29b9669bb3cb5d66e Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 21:00:55 +0530 Subject: [PATCH 14/69] feat: preconf RPC service --- x/opt-in-bidder/bidder_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/opt-in-bidder/bidder_test.go b/x/opt-in-bidder/bidder_test.go index 01e743867..11af8e1bc 100644 --- a/x/opt-in-bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder_test.go @@ -235,6 +235,7 @@ waitLoop: t.Fatalf("expected block number 11, got %d", status.Arg.(*bidderapiv1.Commitment).BlockNumber) } commitments++ + } case bid := <-rpcServices.bidChan: if bid.Amount != big.NewInt(1).String() { t.Fatalf("expected amount 1, got %s", bid.Amount) From 19597328e403befef219df023ac2a5e0d1be9218 Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 21:21:10 +0530 Subject: [PATCH 15/69] feat: preconf RPC service --- x/transfer/bridger_test.go | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/x/transfer/bridger_test.go b/x/transfer/bridger_test.go index 4b895b8fa..f353d92a1 100644 --- a/x/transfer/bridger_test.go +++ b/x/transfer/bridger_test.go @@ -5,6 +5,7 @@ import ( "log/slog" "math/big" "os" + "sync" "testing" "time" @@ -28,6 +29,7 @@ func (m *MockAccountSyncer) Subscribe(ctx context.Context, threshold *big.Int) < } type MockTransfer struct { + mtx sync.Mutex called int amount *big.Int } @@ -39,10 +41,30 @@ func (m *MockTransfer) Do(ctx context.Context) <-chan bridgetransfer.TransferSta Error: nil, } close(ch) + m.mtx.Lock() m.called++ + m.mtx.Unlock() return ch } +func (m *MockTransfer) getAmount() *big.Int { + m.mtx.Lock() + defer m.mtx.Unlock() + return m.amount +} + +func (m *MockTransfer) setAmount(amount *big.Int) { + m.mtx.Lock() + defer m.mtx.Unlock() + m.amount = amount +} + +func (m *MockTransfer) called() int { + m.mtx.Lock() + defer m.mtx.Unlock() + return m.called +} + type keySigner struct { keysigner.KeySigner } @@ -69,7 +91,7 @@ func TestBridger(t *testing.T) { l1ContractAddr common.Address, settlementContractAddr common.Address, ) (bridgetransfer.Transfer, error) { - txfer.amount = amount + txfer.setAmount(amount) return txfer, nil }, ) @@ -100,12 +122,12 @@ func TestBridger(t *testing.T) { if time.Since(start) > 5*time.Second { t.Fatal("Timeout waiting for transfer to be called") } - if txfer.called == 1 { + if txfer.called() == 1 { break } } - if txfer.amount.Cmp(big.NewInt(1000000000000000000)) != 0 { + if txfer.getAmount().Cmp(big.NewInt(1000000000000000000)) != 0 { t.Fatalf("Expected amount to be 1 ETH, got %s", txfer.amount.String()) } @@ -116,12 +138,12 @@ func TestBridger(t *testing.T) { if time.Since(start) > 5*time.Second { t.Fatal("Timeout waiting for transfer to be called again") } - if txfer.called == 2 { + if txfer.called() == 2 { break } } - if txfer.amount.Cmp(big.NewInt(1000000000000000000)) != 0 { + if txfer.getAmount().Cmp(big.NewInt(1000000000000000000)) != 0 { t.Fatalf("Expected amount to be 1 ETH, got %s", txfer.amount.String()) } From 670dd7d800663f393e8dd3e918d36da4488bcee2 Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 20 May 2025 21:22:16 +0530 Subject: [PATCH 16/69] feat: preconf RPC service --- x/transfer/bridger_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/transfer/bridger_test.go b/x/transfer/bridger_test.go index f353d92a1..61e38ca35 100644 --- a/x/transfer/bridger_test.go +++ b/x/transfer/bridger_test.go @@ -59,7 +59,7 @@ func (m *MockTransfer) setAmount(amount *big.Int) { m.amount = amount } -func (m *MockTransfer) called() int { +func (m *MockTransfer) calls() int { m.mtx.Lock() defer m.mtx.Unlock() return m.called @@ -122,7 +122,7 @@ func TestBridger(t *testing.T) { if time.Since(start) > 5*time.Second { t.Fatal("Timeout waiting for transfer to be called") } - if txfer.called() == 1 { + if txfer.calls() == 1 { break } } @@ -138,7 +138,7 @@ func TestBridger(t *testing.T) { if time.Since(start) > 5*time.Second { t.Fatal("Timeout waiting for transfer to be called again") } - if txfer.called() == 2 { + if txfer.calls() == 2 { break } } From 80931b465f359259c1b6b512f8750f32a485cf00 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 31 May 2025 01:33:44 +0530 Subject: [PATCH 17/69] fix: proxy request --- tools/preconf-rpc/rpcserver/rpcserver.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 98503f00b..652f09966 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -1,6 +1,7 @@ package rpcserver import ( + "bytes" "context" "encoding/json" "io" @@ -97,7 +98,7 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { handler, ok := s.methods[req.Method] s.rwLock.RUnlock() if !ok { - s.proxyRequest(w, r) + s.proxyRequest(w, body) return } @@ -107,7 +108,7 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.writeError(w, req.ID, CodeCustomError, err.Error()) return case proxy: - s.proxyRequest(w, r) + s.proxyRequest(w, body) return case resp == nil: s.writeError(w, req.ID, CodeCustomError, "No response") @@ -149,11 +150,11 @@ func (s *JSONRPCServer) writeError(w http.ResponseWriter, id any, code int, mess } } -func (s *JSONRPCServer) proxyRequest(w http.ResponseWriter, r *http.Request) { +func (s *JSONRPCServer) proxyRequest(w http.ResponseWriter, body []byte) { client := &http.Client{ Timeout: defaultTimeout, } - req, err := http.NewRequest(r.Method, s.proxyURL, r.Body) + req, err := http.NewRequest(http.MethodPost, s.proxyURL, bytes.NewReader(body)) if err != nil { http.Error(w, "Failed to create proxy request", http.StatusInternalServerError) return From 2a2cba2b11b2134e1f85ce8f261780ee89611325 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Mon, 2 Jun 2025 16:33:59 +0530 Subject: [PATCH 18/69] fix: temp --- tools/preconf-rpc/handlers/handlers.go | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tools/preconf-rpc/handlers/handlers.go diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go new file mode 100644 index 000000000..5c1b7608c --- /dev/null +++ b/tools/preconf-rpc/handlers/handlers.go @@ -0,0 +1,48 @@ +package handlers + +import ( + "context" + "encoding/json" + "log/slog" +) + +type rpcMethodHandler struct { + logger *slog.Logger +} + +func (h *rpcMethodHandler) handleSendRawTx(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { + if len(params) != 1 { + return nil, false, &jsonRPCError{ + Code: CodeInvalidRequest, + Message: "sendRawTx requires exactly one parameter", + } + } + if params[0] == nil { + return nil, false, &jsonRPCError{ + Code: CodeParseError, + Message: "sendRawTx parameter cannot be null", + } + } + + rawTxHex := params[0].(string) + if len(rawTxHex) < 2 || rawTxHex[:2] != "0x" { + return nil, false, &jsonRPCError{ + Code: CodeParseError, + Message: "sendRawTx parameter must be a hex string starting with '0x'", + } + } + + return nil, false, nil +} + +func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { + return nil, false, nil +} + +func (h *rpcMethodHandler) handleGetBalance(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { + return nil, false, nil +} + +func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { + return nil, false, nil +} From 10f483f452f486be9f9c33ebaca20c34088632c6 Mon Sep 17 00:00:00 2001 From: Alok Date: Mon, 2 Jun 2025 19:12:16 +0530 Subject: [PATCH 19/69] feat: preconf rpc --- tools/preconf-rpc/handlers/handlers.go | 67 +++++++++++++++++++----- tools/preconf-rpc/rpcserver/rpcserver.go | 33 ++++++++++-- x/opt-in-bidder/bidder.go | 1 - x/opt-in-bidder/bidder_test.go | 4 +- 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 5c1b7608c..a6e0a8cb9 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -2,33 +2,76 @@ package handlers import ( "context" + "encoding/hex" "encoding/json" "log/slog" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/primev/mev-commit/tools/preconf-rpc/rpcserver" + optinbidder "github.com/primev/mev-commit/x/opt-in-bidder" +) + +const ( + blockTime = 12 // seconds, typical Ethereum block time ) +type Bidder interface { + Estimate() (int64, error) + Bid(ctx context.Context, bidAmount *big.Int, slashAmount *big.Int, rawTx string) (<-chan optinbidder.BidStatus, error) +} + type rpcMethodHandler struct { logger *slog.Logger + bidder Bidder } -func (h *rpcMethodHandler) handleSendRawTx(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { +func (h *rpcMethodHandler) handleSendRawTx(ctx context.Context, params ...any) (json.RawMessage, bool, error) { if len(params) != 1 { - return nil, false, &jsonRPCError{ - Code: CodeInvalidRequest, - Message: "sendRawTx requires exactly one parameter", - } + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeInvalidRequest, "sendRawTx requires exactly one parameter") } if params[0] == nil { - return nil, false, &jsonRPCError{ - Code: CodeParseError, - Message: "sendRawTx parameter cannot be null", - } + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter cannot be null") } rawTxHex := params[0].(string) if len(rawTxHex) < 2 || rawTxHex[:2] != "0x" { - return nil, false, &jsonRPCError{ - Code: CodeParseError, - Message: "sendRawTx parameter must be a hex string starting with '0x'", + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter must be a hex string starting with '0x'") + } + + decodedTxn, err := hex.DecodeString(rawTxHex[2:]) + if err != nil { + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter must be a valid hex string") + } + + txn := new(types.Transaction) + if err := txn.UnmarshalBinary(decodedTxn); err != nil { + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter must be a valid transaction") + } + + timeToOptIn, err := h.bidder.Estimate() + if err != nil { + h.logger.Error("Failed to estimate time to opt-in", "error", err) + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to estimate time to opt-in") + } + + if timeToOptIn <= blockTime { + bidC, err := h.bidder.Bid(ctx, big.NewInt(0), big.NewInt(0), rawTxHex) + if err != nil { + h.logger.Error("Failed to place bid", "error", err) + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to place bid") + } + for { + select { + case <-ctx.Done(): + h.logger.Info("Context cancelled while waiting for bid status") + return nil, false, ctx.Err() + case bidStatus := <-bidC: + switch bidStatus.Type { + case optinbidder.BidStatusNoOfProviders: + case optinbidder.BidStatusAttempted: + } + } } } diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 652f09966..4d70a45ef 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "io" "net/http" "sync" @@ -19,11 +20,27 @@ const ( CodeCustomError = -32000 ) +type JSONErr struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (e *JSONErr) Error() string { + return e.Message +} + +func NewJSONErr(code int, message string) *JSONErr { + return &JSONErr{ + Code: code, + Message: message, + } +} + type jsonRPCRequest struct { - JSONRPC string `json:"jsonrpc"` - ID any `json:"id"` - Method string `json:"method"` - Params []json.RawMessage `json:"params"` + JSONRPC string `json:"jsonrpc"` + ID any `json:"id"` + Method string `json:"method"` + Params []any `json:"params"` } type jsonRPCResponse struct { @@ -33,7 +50,7 @@ type jsonRPCResponse struct { Error *jsonRPCError `json:"error,omitempty"` } -type methodHandler func(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) +type methodHandler func(ctx context.Context, params ...interface{}) (json.RawMessage, bool, error) type jsonRPCError struct { Code int `json:"code"` @@ -105,6 +122,12 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { resp, proxy, err := handler(r.Context(), req.Params...) switch { case err != nil: + var jsonErr *JSONErr + if ok := errors.As(err, jsonErr); ok { + // If the error is a JSONErr, we can use it directly. + s.writeError(w, req.ID, jsonErr.Code, jsonErr.Message) + return + } s.writeError(w, req.ID, CodeCustomError, err.Error()) return case proxy: diff --git a/x/opt-in-bidder/bidder.go b/x/opt-in-bidder/bidder.go index d66e81712..82f97c435 100644 --- a/x/opt-in-bidder/bidder.go +++ b/x/opt-in-bidder/bidder.go @@ -186,7 +186,6 @@ type BidStatus struct { func (b *BidderClient) Bid( ctx context.Context, bidAmount *big.Int, - bridgeAmount *big.Int, slashAmount *big.Int, rawTx string, ) (chan BidStatus, error) { diff --git a/x/opt-in-bidder/bidder_test.go b/x/opt-in-bidder/bidder_test.go index 11af8e1bc..1ed1b01de 100644 --- a/x/opt-in-bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder_test.go @@ -195,7 +195,7 @@ func TestBidderClient(t *testing.T) { _, _ = rand.Read(buf) txString := hex.EncodeToString(buf) - _, err = bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), big.NewInt(1), txString) + _, err = bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString) if err == nil { t.Fatal("expected error, got nil") } @@ -204,7 +204,7 @@ func TestBidderClient(t *testing.T) { Topology: topoVal, } - statusC, err := bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), big.NewInt(1), txString) + statusC, err := bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString) if err != nil { t.Fatal(err) } From 606fc8498d95dd6cfb2c78063af0b7211975962e Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 3 Jun 2025 19:09:52 +0530 Subject: [PATCH 20/69] feat: preconf rpc --- tools/preconf-rpc/handlers/handlers.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index a6e0a8cb9..bfbbbb547 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -61,6 +61,8 @@ func (h *rpcMethodHandler) handleSendRawTx(ctx context.Context, params ...any) ( h.logger.Error("Failed to place bid", "error", err) return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to place bid") } + noOfProviders, noOfCommitments, blockNumber := 0, 0, 0 + commitments := make([]*bidderapiv1.Commitment, 0) for { select { case <-ctx.Done(): @@ -69,7 +71,16 @@ func (h *rpcMethodHandler) handleSendRawTx(ctx context.Context, params ...any) ( case bidStatus := <-bidC: switch bidStatus.Type { case optinbidder.BidStatusNoOfProviders: + noOfProviders = bidStatus.Arg.(int) case optinbidder.BidStatusAttempted: + blockNumber = bidStatus.Arg.(int) + case optinbidder.BidStatusCommitment: + noOfCommitments++ + commitments = append(commitments, bidStatus.Arg.(*bidderapiv1.Commitment)) + case optinbidder.BidStatusCancelled: + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "bid cancelled") + case optinbidder.BidStatusFailed: + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "bid failed") } } } From 4be6fdb6f143961d85b6dae471f074f0be05a8f4 Mon Sep 17 00:00:00 2001 From: Alok Date: Fri, 6 Jun 2025 21:04:33 +0530 Subject: [PATCH 21/69] feat: preconf RPC --- tools/go.mod | 1 + tools/go.sum | 2 + tools/preconf-rpc/handlers/handlers.go | 226 ++++++++++++++++++++----- 3 files changed, 191 insertions(+), 38 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 7cf15f8e0..a1dde590d 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -27,6 +27,7 @@ require ( github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + resenje.org/multex v0.2.0 // indirect ) replace github.com/primev/mev-commit/p2p => ../p2p diff --git a/tools/go.sum b/tools/go.sum index 5b8846f59..af1c3659c 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -249,5 +249,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +resenje.org/multex v0.2.0 h1:y1S8+bItGZo0lberxtQi9IhbWTpvRezhCWIFvt12VmU= +resenje.org/multex v0.2.0/go.mod h1:z+E+cUHGTgpqYn+P3yFOnC92i3X7rStzSur4rjOZM9s= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index bfbbbb547..4e44176c6 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -8,8 +8,10 @@ import ( "math/big" "github.com/ethereum/go-ethereum/core/types" + bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" "github.com/primev/mev-commit/tools/preconf-rpc/rpcserver" optinbidder "github.com/primev/mev-commit/x/opt-in-bidder" + "resenje.org/multex" ) const ( @@ -21,72 +23,191 @@ type Bidder interface { Bid(ctx context.Context, bidAmount *big.Int, slashAmount *big.Int, rawTx string) (<-chan optinbidder.BidStatus, error) } +type Store interface { + StorePreconfirmedTransaction( + ctx context.Context, + blockNumber int64, + txn *types.Transaction, + commitments []*bidderapiv1.Commitment, + ) error + UpdateAccountNonce( + ctx context.Context, + account string, + nonce uint64, + ) error + GetAccountNonce( + ctx context.Context, + account string, + ) (uint64, error) +} + type rpcMethodHandler struct { - logger *slog.Logger - bidder Bidder + logger *slog.Logger + bidder Bidder + store Store + nonceLock *multex.Multex[string] } -func (h *rpcMethodHandler) handleSendRawTx(ctx context.Context, params ...any) (json.RawMessage, bool, error) { +func (h *rpcMethodHandler) handleSendRawTx( + ctx context.Context, + params ...any, +) (json.RawMessage, bool, error) { if len(params) != 1 { - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeInvalidRequest, "sendRawTx requires exactly one parameter") + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "sendRawTx requires exactly one parameter", + ) } if params[0] == nil { - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter cannot be null") + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "sendRawTx parameter cannot be null", + ) } rawTxHex := params[0].(string) if len(rawTxHex) < 2 || rawTxHex[:2] != "0x" { - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter must be a hex string starting with '0x'") + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "sendRawTx parameter must be a hex string starting with '0x'", + ) } decodedTxn, err := hex.DecodeString(rawTxHex[2:]) if err != nil { - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter must be a valid hex string") + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "sendRawTx parameter must be a valid hex string", + ) } txn := new(types.Transaction) if err := txn.UnmarshalBinary(decodedTxn); err != nil { - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeParseError, "sendRawTx parameter must be a valid transaction") + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "sendRawTx parameter must be a valid transaction", + ) + } + + sender, err := types.Sender(types.LatestSignerForChainID(txn.ChainId()), txn) + if err != nil { + h.logger.Error("Failed to get transaction sender", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to get transaction sender", + ) } timeToOptIn, err := h.bidder.Estimate() if err != nil { h.logger.Error("Failed to estimate time to opt-in", "error", err) - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to estimate time to opt-in") + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to estimate time to opt-in", + ) } + optedInSlot := false if timeToOptIn <= blockTime { - bidC, err := h.bidder.Bid(ctx, big.NewInt(0), big.NewInt(0), rawTxHex) - if err != nil { - h.logger.Error("Failed to place bid", "error", err) - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to place bid") - } - noOfProviders, noOfCommitments, blockNumber := 0, 0, 0 - commitments := make([]*bidderapiv1.Commitment, 0) - for { - select { - case <-ctx.Done(): - h.logger.Info("Context cancelled while waiting for bid status") - return nil, false, ctx.Err() - case bidStatus := <-bidC: - switch bidStatus.Type { - case optinbidder.BidStatusNoOfProviders: - noOfProviders = bidStatus.Arg.(int) - case optinbidder.BidStatusAttempted: - blockNumber = bidStatus.Arg.(int) - case optinbidder.BidStatusCommitment: - noOfCommitments++ - commitments = append(commitments, bidStatus.Arg.(*bidderapiv1.Commitment)) - case optinbidder.BidStatusCancelled: - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "bid cancelled") - case optinbidder.BidStatusFailed: - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "bid failed") - } + optedInSlot = true + } + + bidC, err := h.bidder.Bid(ctx, big.NewInt(0), big.NewInt(0), rawTxHex) + if err != nil { + h.logger.Error("Failed to place bid", "error", err) + return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to place bid") + } + noOfProviders, noOfCommitments, blockNumber := 0, 0, 0 + cancelled, failed := false, false + commitments := make([]*bidderapiv1.Commitment, 0) +BID_LOOP: + for { + select { + case <-ctx.Done(): + h.logger.Info("Context cancelled while waiting for bid status") + return nil, false, ctx.Err() + case bidStatus := <-bidC: + switch bidStatus.Type { + case optinbidder.BidStatusNoOfProviders: + noOfProviders = bidStatus.Arg.(int) + case optinbidder.BidStatusAttempted: + blockNumber = bidStatus.Arg.(int) + case optinbidder.BidStatusCommitment: + noOfCommitments++ + commitments = append(commitments, bidStatus.Arg.(*bidderapiv1.Commitment)) + case optinbidder.BidStatusCancelled: + cancelled = true + break BID_LOOP + case optinbidder.BidStatusFailed: + failed = true + break BID_LOOP } } } + switch { + case noOfProviders == 0: + h.logger.Info("No providers available for the bid", "noOfProviders", noOfProviders) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "no providers available for the bid", + ) + case cancelled || failed: + h.logger.Info( + "Bid cancelled or failed", + "cancelled", cancelled, + "failed", failed, + "noOfCommitments", noOfCommitments, + ) + if noOfCommitments == 0 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "bid cancelled with no commitments", + ) + } + case noOfCommitments == 0: + h.logger.Info("Bid completed with no commitments", "noOfCommitments", noOfCommitments) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "bid completed with no commitments", + ) + } + h.logger.Info( + "Bid successful with commitments", + "noOfProviders", noOfProviders, + "noOfCommitments", noOfCommitments, + "blockNumber", blockNumber, + ) + // If we reach here, we have a successful bid with commitments + txHashJSON, err := json.Marshal(txn.Hash().Hex()) + if err != nil { + h.logger.Error("Failed to marshal transaction hash to JSON", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to marshal transaction hash", + ) + } - return nil, false, nil + if err := h.store.StorePreconfirmedTransaction(ctx, int64(blockNumber), txn, commitments); err != nil { + h.logger.Error("Failed to store preconfirmed transaction", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to store preconfirmed transaction", + ) + } + + if noOfProviders == noOfCommitments && optedInSlot { + if err := h.store.UpdateAccountNonce(ctx, sender.Hex(), txn.Nonce()+1); err != nil { + h.logger.Error("Failed to update account nonce", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to update account nonce", + ) + } + } else { + h.nonceLock.Lock(sender.Hex()) + } + + return txHashJSON, false, nil } func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { @@ -97,6 +218,35 @@ func (h *rpcMethodHandler) handleGetBalance(ctx context.Context, params ...json. return nil, false, nil } -func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { - return nil, false, nil +func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) (json.RawMessage, bool, error) { + if len(params) != 1 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "getTxCount requires exactly one parameter", + ) + } + + if params[0] == nil { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getTxCount parameter cannot be null", + ) + } + + account := params[0].(string) + nonce, err := h.store.GetAccountNonce(ctx, account) + if err != nil { + h.logger.Error("Failed to get account nonce", "error", err, "account", account) + return nil, true, nil + } + nonceJSON, err := json.Marshal(nonce) + if err != nil { + h.logger.Error("Failed to marshal nonce to JSON", "error", err, "account", account) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to marshal nonce", + ) + } + h.logger.Info("Retrieved account nonce", "account", account, "nonce", nonce) + return nonceJSON, false, nil } From d3fd9f72cb6e1649fc8e90da6fbe3462f736220c Mon Sep 17 00:00:00 2001 From: Alok Date: Mon, 9 Jun 2025 18:11:56 +0530 Subject: [PATCH 22/69] feat: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 132 +++++++++++++++++++------ 1 file changed, 103 insertions(+), 29 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 4e44176c6..86dcfd6dd 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -6,6 +6,8 @@ import ( "encoding/json" "log/slog" "math/big" + "sync" + "sync/atomic" "github.com/ethereum/go-ethereum/core/types" bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" @@ -30,22 +32,26 @@ type Store interface { txn *types.Transaction, commitments []*bidderapiv1.Commitment, ) error - UpdateAccountNonce( + GetPreconfirmedTransaction( ctx context.Context, - account string, - nonce uint64, - ) error - GetAccountNonce( - ctx context.Context, - account string, - ) (uint64, error) + txnHash string, + ) (*types.Transaction, []*bidderapiv1.Commitment, error) +} + +type accountNonce struct { + Account string `json:"account"` + Nonce uint64 `json:"nonce"` + Block int64 `json:"block"` } type rpcMethodHandler struct { - logger *slog.Logger - bidder Bidder - store Store - nonceLock *multex.Multex[string] + logger *slog.Logger + bidder Bidder + store Store + nonceLock *multex.Multex[string] + latestBlock atomic.Pointer[types.Block] + nonceMap map[string]accountNonce + nonceMapLock sync.RWMutex } func (h *rpcMethodHandler) handleSendRawTx( @@ -98,6 +104,14 @@ func (h *rpcMethodHandler) handleSendRawTx( ) } + h.nonceLock.Lock(sender.Hex()) + unlock := true + defer func() { + if unlock { + h.nonceLock.Unlock(sender.Hex()) + } + }() + timeToOptIn, err := h.bidder.Estimate() if err != nil { h.logger.Error("Failed to estimate time to opt-in", "error", err) @@ -196,26 +210,65 @@ BID_LOOP: } if noOfProviders == noOfCommitments && optedInSlot { - if err := h.store.UpdateAccountNonce(ctx, sender.Hex(), txn.Nonce()+1); err != nil { - h.logger.Error("Failed to update account nonce", "error", err) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "failed to update account nonce", - ) + h.logger.Info("All providers committed, updating nonce", "account", sender.Hex(), "nonce", txn.Nonce()+1) + h.nonceMapLock.Lock() + h.nonceMap[sender.Hex()] = accountNonce{ + Account: sender.Hex(), + Nonce: txn.Nonce() + 1, + Block: int64(blockNumber), } + h.nonceMapLock.Unlock() } else { - h.nonceLock.Lock(sender.Hex()) + unlock = false } return txHashJSON, false, nil } -func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { - return nil, false, nil -} +func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any) (json.RawMessage, bool, error) { + if len(params) != 1 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "getTxReceipt requires exactly one parameter", + ) + } + if params[0] == nil { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getTxReceipt parameter cannot be null", + ) + } + + txHash := params[0].(string) + if len(txHash) < 2 || txHash[:2] != "0x" { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getTxReceipt parameter must be a hex string starting with '0x'", + ) + } -func (h *rpcMethodHandler) handleGetBalance(ctx context.Context, params ...json.RawMessage) (json.RawMessage, bool, error) { - return nil, false, nil + h.logger.Info("Retrieving transaction receipt", "txHash", txHash) + txn, commitments, err := h.store.GetPreconfirmedTransaction(ctx, txHash[2:]) + if err != nil { + return nil, true, nil + } + + receipt := &types.Receipt{ + TxHash: txn.Hash(), + Status: 1, // Assuming success, as this is a preconfirmed transaction + BlockNumber: big.NewInt(commitments[0].BlockNumber), + } + + receiptJSON, err := json.Marshal(receipt) + if err != nil { + h.logger.Error("Failed to marshal receipt to JSON", "error", err, "txHash", txHash) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to marshal receipt", + ) + } + + return receiptJSON, false, nil } func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) (json.RawMessage, bool, error) { @@ -234,12 +287,32 @@ func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) } account := params[0].(string) - nonce, err := h.store.GetAccountNonce(ctx, account) - if err != nil { - h.logger.Error("Failed to get account nonce", "error", err, "account", account) + if len(account) < 2 || account[:2] != "0x" { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getTxCount parameter must be a hex string starting with '0x'", + ) + } + + h.nonceLock.Lock(account) + defer h.nonceLock.Unlock(account) + + h.nonceMapLock.RLock() + accNonce, found := h.nonceMap[account] + h.nonceMapLock.RUnlock() + + if !found { + return nil, true, nil + } + + if h.latestBlock.Load().Number().Uint64() > uint64(accNonce.Block) { + h.nonceMapLock.Lock() + delete(h.nonceMap, account) + h.nonceMapLock.Unlock() return nil, true, nil } - nonceJSON, err := json.Marshal(nonce) + + nonceJSON, err := json.Marshal(accNonce.Nonce) if err != nil { h.logger.Error("Failed to marshal nonce to JSON", "error", err, "account", account) return nil, false, rpcserver.NewJSONErr( @@ -247,6 +320,7 @@ func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) "failed to marshal nonce", ) } - h.logger.Info("Retrieved account nonce", "account", account, "nonce", nonce) + + h.logger.Info("Retrieved account nonce from cache", "account", account, "nonce", accNonce.Nonce) return nonceJSON, false, nil } From bb137626636743a6f7ba70538e693a1002c91b12 Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 10 Jun 2025 21:21:40 +0530 Subject: [PATCH 23/69] feat: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 54 +++++++++++++++++++- x/opt-in-bidder/bidder.go | 71 +++++++++++++++++--------- x/opt-in-bidder/bidder_test.go | 6 +-- 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 86dcfd6dd..7d636a8f1 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -22,7 +22,20 @@ const ( type Bidder interface { Estimate() (int64, error) - Bid(ctx context.Context, bidAmount *big.Int, slashAmount *big.Int, rawTx string) (<-chan optinbidder.BidStatus, error) + Bid( + ctx context.Context, + bidAmount *big.Int, + slashAmount *big.Int, + rawTx string, + opts *optinbidder.BidOpts, + ) (<-chan optinbidder.BidStatus, error) +} + +type Pricer interface { + EstimatePrice( + ctx context.Context, + txn *types.Transaction, + ) (*big.Int, error) } type Store interface { @@ -36,6 +49,16 @@ type Store interface { ctx context.Context, txnHash string, ) (*types.Transaction, []*bidderapiv1.Commitment, error) + DeductBalance( + ctx context.Context, + account string, + amount *big.Int, + ) error + RefundBalance( + ctx context.Context, + account string, + amount *big.Int, + ) error } type accountNonce struct { @@ -48,6 +71,7 @@ type rpcMethodHandler struct { logger *slog.Logger bidder Bidder store Store + pricer Pricer nonceLock *multex.Multex[string] latestBlock atomic.Pointer[types.Block] nonceMap map[string]accountNonce @@ -126,7 +150,24 @@ func (h *rpcMethodHandler) handleSendRawTx( optedInSlot = true } - bidC, err := h.bidder.Bid(ctx, big.NewInt(0), big.NewInt(0), rawTxHex) + price, err := h.pricer.EstimatePrice(ctx, txn) + if err != nil { + h.logger.Error("Failed to estimate transaction price", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to estimate transaction price", + ) + } + + bidC, err := h.bidder.Bid( + ctx, + price, + big.NewInt(0), + rawTxHex, + &optinbidder.BidOpts{ + WaitForOptIn: optedInSlot, + }, + ) if err != nil { h.logger.Error("Failed to place bid", "error", err) return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to place bid") @@ -201,6 +242,14 @@ BID_LOOP: ) } + if err := h.store.DeductBalance(ctx, sender.Hex(), price); err != nil { + h.logger.Error("Failed to deduct balance for sender", "sender", sender.Hex(), "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to deduct balance for sender", + ) + } + if err := h.store.StorePreconfirmedTransaction(ctx, int64(blockNumber), txn, commitments); err != nil { h.logger.Error("Failed to store preconfirmed transaction", "error", err) return nil, false, rpcserver.NewJSONErr( @@ -255,6 +304,7 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any receipt := &types.Receipt{ TxHash: txn.Hash(), + Type: txn.Type(), Status: 1, // Assuming success, as this is a preconfirmed transaction BlockNumber: big.NewInt(commitments[0].BlockNumber), } diff --git a/x/opt-in-bidder/bidder.go b/x/opt-in-bidder/bidder.go index 82f97c435..d206ff74f 100644 --- a/x/opt-in-bidder/bidder.go +++ b/x/opt-in-bidder/bidder.go @@ -24,6 +24,7 @@ const ( var ( ErrNoEpochInfo = errors.New("no epoch info available") ErrNoSlotInCurrentEpoch = errors.New("no slot available in current epoch") + ErrNoProviders = errors.New("no connected providers found") ) var nowFunc = time.Now @@ -183,12 +184,26 @@ type BidStatus struct { Arg any } +type BidOpts struct { + WaitForOptIn bool + BlockNumber uint64 +} + +var defaultBidOpts = &BidOpts{ + WaitForOptIn: true, +} + func (b *BidderClient) Bid( ctx context.Context, bidAmount *big.Int, slashAmount *big.Int, rawTx string, + opts *BidOpts, ) (chan BidStatus, error) { + if opts == nil { + opts = defaultBidOpts + } + topo, err := b.topologyClient.GetTopology(ctx, &debugapiv1.EmptyMessage{}) if err != nil { b.logger.Error("failed to get topology", "error", err) @@ -197,7 +212,7 @@ func (b *BidderClient) Bid( providers := topo.Topology.Fields["connected_providers"].GetListValue() if providers == nil || len(providers.Values) == 0 { - return nil, errors.New("no connected providers") + return nil, ErrNoProviders } // Channel length chosen is 3 so that sending the bid is not blocked by the first @@ -212,41 +227,47 @@ func (b *BidderClient) Bid( fmt.Println("BidderClient sending no of providers") res <- BidStatus{Type: BidStatusNoOfProviders, Arg: len(providers.Values)} - nextSlot, err := b.getNextSlot() - if err != nil { - b.logger.Error("failed to get next slot", "error", err) - res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} - return - } + if opts.WaitForOptIn { + nextSlot, err := b.getNextSlot() + if err != nil { + b.logger.Error("failed to get next slot", "error", err) + res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} + return + } - bidTime := nextSlot.startTime.Add(-1 * time.Second) - wait := bidTime.Sub(nowFunc()) - res <- BidStatus{Type: BidStatusWaitSecs, Arg: int(wait.Seconds())} + bidTime := nextSlot.startTime.Add(-1 * time.Second) + wait := bidTime.Sub(nowFunc()) + res <- BidStatus{Type: BidStatusWaitSecs, Arg: int(wait.Seconds())} - if wait > 0 { - b.logger.Info("waiting for next slot", "wait", wait) - select { - case <-time.After(wait): - case <-ctx.Done(): - res <- BidStatus{Type: BidStatusCancelled, Arg: ctx.Err().Error()} - return + if wait > 0 { + b.logger.Info("waiting for next slot", "wait", wait) + select { + case <-time.After(wait): + case <-ctx.Done(): + res <- BidStatus{Type: BidStatusCancelled, Arg: ctx.Err().Error()} + return + } } } - blkNumber, err := b.blkNumberGetter.BlockNumber(ctx) - if err != nil { - b.logger.Error("failed to get block number", "error", err) - res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} - return + blkNumber := opts.BlockNumber + if blkNumber == 0 { + bNo, err := b.blkNumberGetter.BlockNumber(ctx) + if err != nil { + b.logger.Error("failed to get block number", "error", err) + res <- BidStatus{Type: BidStatusFailed, Arg: err.Error()} + return + } + blkNumber = bNo + 1 } - res <- BidStatus{Type: BidStatusAttempted, Arg: int(blkNumber + 1)} + res <- BidStatus{Type: BidStatusAttempted, Arg: blkNumber} pc, err := b.bidderClient.SendBid(ctx, &bidderapiv1.Bid{ Amount: bidAmount.String(), - BlockNumber: int64(blkNumber + 1), + BlockNumber: int64(blkNumber), RawTransactions: []string{rawTx}, - DecayStartTimestamp: nowFunc().UnixMilli(), + DecayStartTimestamp: nowFunc().Add(100 * time.Millisecond).UnixMilli(), DecayEndTimestamp: nowFunc().Add(12 * time.Second).UnixMilli(), SlashAmount: slashAmount.String(), }) diff --git a/x/opt-in-bidder/bidder_test.go b/x/opt-in-bidder/bidder_test.go index 1ed1b01de..9a8bdb81b 100644 --- a/x/opt-in-bidder/bidder_test.go +++ b/x/opt-in-bidder/bidder_test.go @@ -195,7 +195,7 @@ func TestBidderClient(t *testing.T) { _, _ = rand.Read(buf) txString := hex.EncodeToString(buf) - _, err = bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString) + _, err = bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString, nil) if err == nil { t.Fatal("expected error, got nil") } @@ -204,7 +204,7 @@ func TestBidderClient(t *testing.T) { Topology: topoVal, } - statusC, err := bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString) + statusC, err := bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString, nil) if err != nil { t.Fatal(err) } @@ -227,7 +227,7 @@ waitLoop: t.Fatalf("expected 2 seconds, got %d", status.Arg) } case optinbidder.BidStatusAttempted: - if status.Arg.(int) != 11 { + if status.Arg.(uint64) != 11 { t.Fatalf("expected 11, got %d", status.Arg) } case optinbidder.BidStatusCommitment: From 6ff52217664a6995caca30233cc922b3c686ab9f Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 10 Jun 2025 21:22:19 +0530 Subject: [PATCH 24/69] feat: preconf RPC --- tools/preconf-rpc/pricer/pricer.go | 17 +++++++++++++++++ tools/preconf-rpc/store/store.go | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tools/preconf-rpc/pricer/pricer.go create mode 100644 tools/preconf-rpc/store/store.go diff --git a/tools/preconf-rpc/pricer/pricer.go b/tools/preconf-rpc/pricer/pricer.go new file mode 100644 index 000000000..afa4035bd --- /dev/null +++ b/tools/preconf-rpc/pricer/pricer.go @@ -0,0 +1,17 @@ +package pricer + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" +) + +type bidPricer struct { +} + +func (b *bidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (*big.Int, error) { + // Implement the logic to estimate the price for a transaction. + // This is a placeholder implementation. + return big.NewInt(1000000000), nil // Return a dummy value for now. +} diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go new file mode 100644 index 000000000..9a74d6d98 --- /dev/null +++ b/tools/preconf-rpc/store/store.go @@ -0,0 +1,27 @@ +package store + +import ( + "context" + + "github.com/ethereum/go-ethereum/core/types" + bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" +) + +type rpcstore struct { +} + +func (s *rpcstore) StorePreconfirmedTransaction( + ctx context.Context, + blockNumber int64, + txn *types.Transaction, + commitments []*bidderapiv1.Commitment, +) error { + return nil +} + +func (s *rpcstore) GetPreconfirmedTransaction( + ctx context.Context, + txnHash string, +) (*types.Transaction, []*bidderapiv1.Commitment, error) { + return nil, nil, nil +} From 51e1e97e4ae1f0da24b5985400d9f64263d319ef Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 10 Jun 2025 21:30:09 +0530 Subject: [PATCH 25/69] feat: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 2 ++ tools/preconf-rpc/pricer/pricer.go | 34 ++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 7d636a8f1..381ab324a 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -128,6 +128,8 @@ func (h *rpcMethodHandler) handleSendRawTx( ) } + // Once we are ready to send the bid, we need to ensure that the nonce for the + // sender is not locked by another transaction. h.nonceLock.Lock(sender.Hex()) unlock := true defer func() { diff --git a/tools/preconf-rpc/pricer/pricer.go b/tools/preconf-rpc/pricer/pricer.go index afa4035bd..85fa25523 100644 --- a/tools/preconf-rpc/pricer/pricer.go +++ b/tools/preconf-rpc/pricer/pricer.go @@ -2,16 +2,46 @@ package pricer import ( "context" + "errors" + "io" "math/big" + "net/http" + "time" "github.com/ethereum/go-ethereum/core/types" ) +var apiURL = "https://api.blocknative.com/gasprices/blockprices?chainid=1" + type bidPricer struct { + apiURL string } func (b *bidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (*big.Int, error) { - // Implement the logic to estimate the price for a transaction. - // This is a placeholder implementation. + client := &http.Client{ + Timeout: 10 * time.Second, + } + + req, err := http.NewRequestWithContext(ctx, "GET", b.apiURL, nil) + if err != nil { + return nil, err + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, errors.New("failed to fetch price estimate: " + resp.Status) + } + + respBuf, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024)) + if err != nil { + return nil, err + } + return big.NewInt(1000000000), nil // Return a dummy value for now. } From 43152e65959a1de3685efb99ed3382273123ce8c Mon Sep 17 00:00:00 2001 From: Alok Date: Wed, 11 Jun 2025 17:50:45 +0530 Subject: [PATCH 26/69] feat: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 26 +++- tools/preconf-rpc/pricer/pricer.go | 50 ++++++- tools/preconf-rpc/store/store.go | 192 ++++++++++++++++++++++++- 3 files changed, 259 insertions(+), 9 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 381ab324a..e99153a0a 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" + "github.com/primev/mev-commit/tools/preconf-rpc/pricer" "github.com/primev/mev-commit/tools/preconf-rpc/rpcserver" optinbidder "github.com/primev/mev-commit/x/opt-in-bidder" "resenje.org/multex" @@ -35,7 +36,7 @@ type Pricer interface { EstimatePrice( ctx context.Context, txn *types.Transaction, - ) (*big.Int, error) + ) (*pricer.BlockPrice, error) } type Store interface { @@ -78,6 +79,24 @@ type rpcMethodHandler struct { nonceMapLock sync.RWMutex } +func NewRPCMethodHandler( + logger *slog.Logger, + bidder Bidder, + store Store, + pricer Pricer, + nonceLock *multex.Multex[string], +) *rpcMethodHandler { + return &rpcMethodHandler{ + logger: logger, + bidder: bidder, + store: store, + pricer: pricer, + nonceLock: nonceLock, + nonceMap: make(map[string]accountNonce), + latestBlock: atomic.Pointer[types.Block]{}, + } +} + func (h *rpcMethodHandler) handleSendRawTx( ctx context.Context, params ...any, @@ -163,11 +182,12 @@ func (h *rpcMethodHandler) handleSendRawTx( bidC, err := h.bidder.Bid( ctx, - price, + price.BidAmount, big.NewInt(0), rawTxHex, &optinbidder.BidOpts{ WaitForOptIn: optedInSlot, + BlockNumber: uint64(price.BlockNumber), }, ) if err != nil { @@ -244,7 +264,7 @@ BID_LOOP: ) } - if err := h.store.DeductBalance(ctx, sender.Hex(), price); err != nil { + if err := h.store.DeductBalance(ctx, sender.Hex(), price.BidAmount); err != nil { h.logger.Error("Failed to deduct balance for sender", "sender", sender.Hex(), "error", err) return nil, false, rpcserver.NewJSONErr( rpcserver.CodeCustomError, diff --git a/tools/preconf-rpc/pricer/pricer.go b/tools/preconf-rpc/pricer/pricer.go index 85fa25523..ef058f735 100644 --- a/tools/preconf-rpc/pricer/pricer.go +++ b/tools/preconf-rpc/pricer/pricer.go @@ -2,6 +2,7 @@ package pricer import ( "context" + "encoding/json" "errors" "io" "math/big" @@ -13,16 +14,30 @@ import ( var apiURL = "https://api.blocknative.com/gasprices/blockprices?chainid=1" -type bidPricer struct { - apiURL string +type blockPrice struct { + CurrentBlockNumber int64 `json:"currentBlockNumber"` + BlockPrices []struct { + BlockNumber int64 `json:"blockNumber"` + EstimatedPrices []struct { + Confidence int `json:"confidence"` + PriorityFeePerGas float64 `json:"maxPriorityFeePerGas"` + } + } +} + +type BlockPrice struct { + BlockNumber int64 + BidAmount *big.Int } -func (b *bidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (*big.Int, error) { +type bidPricer struct{} + +func (b *bidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (*BlockPrice, error) { client := &http.Client{ Timeout: 10 * time.Second, } - req, err := http.NewRequestWithContext(ctx, "GET", b.apiURL, nil) + req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) if err != nil { return nil, err } @@ -43,5 +58,30 @@ func (b *bidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) ( return nil, err } - return big.NewInt(1000000000), nil // Return a dummy value for now. + var bp blockPrice + if err := json.Unmarshal(respBuf, &bp); err != nil { + return nil, err + } + + if len(bp.BlockPrices) == 0 { + return nil, errors.New("no block prices available") + } + + for _, price := range bp.BlockPrices { + if price.BlockNumber == bp.CurrentBlockNumber+1 { + for _, p := range price.EstimatedPrices { + if p.Confidence == 99 { // Assuming we want the 99% confidence price + // Convert the priority fee from Gwei to Wei + // 1 Gwei = 1e9 Wei + priorityFee := p.PriorityFeePerGas * 1e9 + bidAmount := big.NewInt(0).Mul(big.NewInt(int64(priorityFee)), big.NewInt(int64(txn.Gas()))) + return &BlockPrice{BlockNumber: price.BlockNumber, BidAmount: bidAmount}, nil + } + } + } + } + + // If we reach here, it means we didn't find a suitable price. + // This could happen if the API response format changes or if no 99% confidence price is available. + return nil, errors.New("no suitable price found for the next block") } diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index 9a74d6d98..63136c4d8 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -2,12 +2,37 @@ package store import ( "context" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "math/big" + "github.com/cockroachdb/pebble" "github.com/ethereum/go-ethereum/core/types" bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" ) +var ( + ErrInsufficientBalance = errors.New("insufficient balance") +) + type rpcstore struct { + db *pebble.DB +} + +func New(path string) (*rpcstore, error) { + db, err := pebble.Open(path, &pebble.Options{}) + if err != nil { + return nil, err + } + return &rpcstore{ + db: db, + }, nil +} + +func (s *rpcstore) Close() error { + return errors.Join(s.db.Flush(), s.db.Close()) } func (s *rpcstore) StorePreconfirmedTransaction( @@ -16,6 +41,36 @@ func (s *rpcstore) StorePreconfirmedTransaction( txn *types.Transaction, commitments []*bidderapiv1.Commitment, ) error { + if blockNumber <= 0 || txn == nil || commitments == nil { + return errors.New("invalid input parameters") + } + + // Serialize the transaction and commitments + txnData, err := txn.MarshalBinary() + if err != nil { + return err + } + + commitmentsData, err := json.Marshal(commitments) + if err != nil { + return err + } + + // Create a composite key for the block number and transaction hash + key := []byte(fmt.Sprintf("%d:%s", blockNumber, txn.Hash().Hex())) + // Store the transaction and commitments in the database + if err := s.db.Set(key, append(txnData, commitmentsData...), nil); err != nil { + return err + } + + blockNumBuf := make([]byte, 8) + binary.BigEndian.PutUint64(blockNumBuf, uint64(blockNumber)) + + txnKey := []byte(fmt.Sprintf("txn:%s", txn.Hash().Hex())) + if err := s.db.Set(txnKey, blockNumBuf, nil); err != nil { + return err + } + return nil } @@ -23,5 +78,140 @@ func (s *rpcstore) GetPreconfirmedTransaction( ctx context.Context, txnHash string, ) (*types.Transaction, []*bidderapiv1.Commitment, error) { - return nil, nil, nil + if txnHash == "" { + return nil, nil, errors.New("transaction hash cannot be empty") + } + + txnKey := []byte(fmt.Sprintf("txn:%s", txnHash)) + blkNumBuf, closer, err := s.db.Get(txnKey) + if err != nil { + return nil, nil, err + } + defer func() { + _ = closer.Close() + }() + + blockNumber := binary.BigEndian.Uint64(blkNumBuf) + if blockNumber == 0 { + return nil, nil, fmt.Errorf("transaction %s not found", txnHash) + } + + key := []byte(fmt.Sprintf("%d:%s", blockNumber, txnHash)) + txnData, closer, err := s.db.Get(key) + if err != nil { + return nil, nil, err + } + defer func() { + _ = closer.Close() + }() + + var commitments []*bidderapiv1.Commitment + txnLen := len(txnData) + if txnLen < 32 { + return nil, nil, fmt.Errorf("invalid transaction data length: %d", txnLen) + } + + txn := new(types.Transaction) + if err := txn.UnmarshalBinary(txnData[:txnLen-32]); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal transaction: %w", err) + } + + if err := json.Unmarshal(txnData[txnLen-32:], &commitments); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal commitments: %w", err) + } + + return txn, commitments, nil +} + +func (s *rpcstore) DeductBalance( + ctx context.Context, + account string, + amount *big.Int, +) error { + if account == "" || amount == nil || amount.Sign() <= 0 { + return errors.New("invalid account or amount") + } + + balanceKey := []byte(fmt.Sprintf("balance:%s", account)) + currentBalance, closer, err := s.db.Get(balanceKey) + if err != nil { + return err + } + defer func() { + _ = closer.Close() + }() + + currentBalanceBig := new(big.Int) + if err := currentBalanceBig.UnmarshalText(currentBalance); err != nil { + return fmt.Errorf("failed to unmarshal current balance: %w", err) + } + if currentBalanceBig.Cmp(amount) < 0 { + return fmt.Errorf("insufficient balance for account %s: %w", account, ErrInsufficientBalance) + } + newBalance := new(big.Int).Sub(currentBalanceBig, amount) + if err := s.db.Set(balanceKey, newBalance.Bytes(), nil); err != nil { + return fmt.Errorf("failed to update balance for account %s: %w", account, err) + } + + return nil +} + +func (s *rpcstore) RefundBalance( + ctx context.Context, + account string, + amount *big.Int, +) error { + if account == "" || amount == nil || amount.Sign() <= 0 { + return errors.New("invalid account or amount") + } + + balanceKey := []byte(fmt.Sprintf("balance:%s", account)) + currentBalance, closer, err := s.db.Get(balanceKey) + if err != nil { + return err + } + defer func() { + _ = closer.Close() + }() + + currentBalanceBig := new(big.Int) + if err := currentBalanceBig.UnmarshalText(currentBalance); err != nil { + return fmt.Errorf("failed to unmarshal current balance: %w", err) + } + + newBalance := new(big.Int).Add(currentBalanceBig, amount) + if err := s.db.Set(balanceKey, newBalance.Bytes(), nil); err != nil { + return fmt.Errorf("failed to update balance for account %s: %w", account, err) + } + + return nil +} + +func (s *rpcstore) HasBalance( + ctx context.Context, + account string, + amount *big.Int, +) error { + if account == "" || amount == nil || amount.Sign() <= 0 { + return errors.New("invalid account or amount") + } + + balanceKey := []byte(fmt.Sprintf("balance:%s", account)) + currentBalance, closer, err := s.db.Get(balanceKey) + if err != nil { + return err + } + defer func() { + _ = closer.Close() + }() + + currentBalanceBig := new(big.Int) + if err := currentBalanceBig.UnmarshalText(currentBalance); err != nil { + return fmt.Errorf("failed to unmarshal current balance: %w", err) + } + + if currentBalanceBig.Cmp(amount) < 0 { + return fmt.Errorf("insufficient balance for account %s: %w", account, ErrInsufficientBalance) + } + return nil } From e5e4f3f656d39faba5d1f8c065b4d43f54b426b9 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Wed, 11 Jun 2025 19:50:23 +0530 Subject: [PATCH 27/69] fix: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 21 +++++++++++++---- tools/preconf-rpc/store/store.go | 32 +++++++++++++++----------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index e99153a0a..350b15eca 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -55,11 +55,11 @@ type Store interface { account string, amount *big.Int, ) error - RefundBalance( + HasBalance( ctx context.Context, account string, amount *big.Int, - ) error + ) bool } type accountNonce struct { @@ -84,19 +84,24 @@ func NewRPCMethodHandler( bidder Bidder, store Store, pricer Pricer, - nonceLock *multex.Multex[string], ) *rpcMethodHandler { return &rpcMethodHandler{ logger: logger, bidder: bidder, store: store, pricer: pricer, - nonceLock: nonceLock, + nonceLock: multex.New[string](), nonceMap: make(map[string]accountNonce), latestBlock: atomic.Pointer[types.Block]{}, } } +func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) { + server.RegisterHandler("eth_sendRawTransaction", h.handleSendRawTx) + server.RegisterHandler("eth_getTransactionReceipt", h.handleGetTxReceipt) + server.RegisterHandler("eth_getTransactionCount", h.handleGetTxCount) +} + func (h *rpcMethodHandler) handleSendRawTx( ctx context.Context, params ...any, @@ -157,6 +162,14 @@ func (h *rpcMethodHandler) handleSendRawTx( } }() + if !h.store.HasBalance(ctx, sender.Hex(), txn.Value()) { + h.logger.Error("Insufficient balance for sender", "sender", sender.Hex()) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "insufficient balance for sender", + ) + } + timeToOptIn, err := h.bidder.Estimate() if err != nil { h.logger.Error("Failed to estimate time to opt-in", "error", err) diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index 63136c4d8..c43aeec60 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -51,6 +51,10 @@ func (s *rpcstore) StorePreconfirmedTransaction( return err } + txnDataLenBuf := make([]byte, 8) + binary.BigEndian.PutUint64(txnDataLenBuf, uint64(len(txnData))) + txnDataWithLen := append(txnDataLenBuf, txnData...) + commitmentsData, err := json.Marshal(commitments) if err != nil { return err @@ -59,7 +63,7 @@ func (s *rpcstore) StorePreconfirmedTransaction( // Create a composite key for the block number and transaction hash key := []byte(fmt.Sprintf("%d:%s", blockNumber, txn.Hash().Hex())) // Store the transaction and commitments in the database - if err := s.db.Set(key, append(txnData, commitmentsData...), nil); err != nil { + if err := s.db.Set(key, append(txnDataWithLen, commitmentsData...), nil); err != nil { return err } @@ -105,18 +109,16 @@ func (s *rpcstore) GetPreconfirmedTransaction( _ = closer.Close() }() - var commitments []*bidderapiv1.Commitment - txnLen := len(txnData) - if txnLen < 32 { - return nil, nil, fmt.Errorf("invalid transaction data length: %d", txnLen) - } + // The first 8 bytes are the length of the transaction data + txnDataLen := binary.BigEndian.Uint64(txnData[:8]) txn := new(types.Transaction) - if err := txn.UnmarshalBinary(txnData[:txnLen-32]); err != nil { + if err := txn.UnmarshalBinary(txnData[8 : 8+txnDataLen]); err != nil { return nil, nil, fmt.Errorf("failed to unmarshal transaction: %w", err) } - if err := json.Unmarshal(txnData[txnLen-32:], &commitments); err != nil { + var commitments []*bidderapiv1.Commitment + if err := json.Unmarshal(txnData[8+txnDataLen:], &commitments); err != nil { return nil, nil, fmt.Errorf("failed to unmarshal commitments: %w", err) } @@ -191,15 +193,15 @@ func (s *rpcstore) HasBalance( ctx context.Context, account string, amount *big.Int, -) error { +) bool { if account == "" || amount == nil || amount.Sign() <= 0 { - return errors.New("invalid account or amount") + return false } balanceKey := []byte(fmt.Sprintf("balance:%s", account)) currentBalance, closer, err := s.db.Get(balanceKey) if err != nil { - return err + return false } defer func() { _ = closer.Close() @@ -207,11 +209,13 @@ func (s *rpcstore) HasBalance( currentBalanceBig := new(big.Int) if err := currentBalanceBig.UnmarshalText(currentBalance); err != nil { - return fmt.Errorf("failed to unmarshal current balance: %w", err) + // If we can't unmarshal the balance, we assume the account has no balance + return false } if currentBalanceBig.Cmp(amount) < 0 { - return fmt.Errorf("insufficient balance for account %s: %w", account, ErrInsufficientBalance) + return false } - return nil + + return true } From e44265d332b33ef53155f409d5eb440f21fb8350 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 12 Jun 2025 01:29:03 +0530 Subject: [PATCH 28/69] fix: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 16 ++++++++-------- tools/preconf-rpc/store/store.go | 9 +++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 350b15eca..d6b5e0efc 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -162,14 +162,6 @@ func (h *rpcMethodHandler) handleSendRawTx( } }() - if !h.store.HasBalance(ctx, sender.Hex(), txn.Value()) { - h.logger.Error("Insufficient balance for sender", "sender", sender.Hex()) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "insufficient balance for sender", - ) - } - timeToOptIn, err := h.bidder.Estimate() if err != nil { h.logger.Error("Failed to estimate time to opt-in", "error", err) @@ -193,6 +185,14 @@ func (h *rpcMethodHandler) handleSendRawTx( ) } + if !h.store.HasBalance(ctx, sender.Hex(), price.BidAmount) { + h.logger.Error("Insufficient balance for sender", "sender", sender.Hex()) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "insufficient balance for sender", + ) + } + bidC, err := h.bidder.Bid( ctx, price.BidAmount, diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index c43aeec60..facb5b146 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -158,7 +158,7 @@ func (s *rpcstore) DeductBalance( return nil } -func (s *rpcstore) RefundBalance( +func (s *rpcstore) AddBalance( ctx context.Context, account string, amount *big.Int, @@ -170,7 +170,12 @@ func (s *rpcstore) RefundBalance( balanceKey := []byte(fmt.Sprintf("balance:%s", account)) currentBalance, closer, err := s.db.Get(balanceKey) if err != nil { - return err + if errors.Is(err, pebble.ErrNotFound) { + // If the account does not exist, we create a new one with the initial balance + currentBalance = []byte("0") // Default balance for a new account + } else { + return fmt.Errorf("failed to get balance for account %s: %w", account, err) + } } defer func() { _ = closer.Close() From a9d8db09c14c3282cab7c392f8ee7379357faa8d Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 13:26:43 +0530 Subject: [PATCH 29/69] feat: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 2 +- tools/preconf-rpc/pricer/pricer.go | 4 +- tools/preconf-rpc/pricer/pricer_test.go | 42 +++++++++ tools/preconf-rpc/service/service.go | 21 ++++- tools/preconf-rpc/store/store.go | 25 ++---- tools/preconf-rpc/store/store_test.go | 111 ++++++++++++++++++++++++ 6 files changed, 183 insertions(+), 22 deletions(-) create mode 100644 tools/preconf-rpc/pricer/pricer_test.go create mode 100644 tools/preconf-rpc/store/store_test.go diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index d6b5e0efc..08ff7809f 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -29,7 +29,7 @@ type Bidder interface { slashAmount *big.Int, rawTx string, opts *optinbidder.BidOpts, - ) (<-chan optinbidder.BidStatus, error) + ) (chan optinbidder.BidStatus, error) } type Pricer interface { diff --git a/tools/preconf-rpc/pricer/pricer.go b/tools/preconf-rpc/pricer/pricer.go index ef058f735..11acd82d1 100644 --- a/tools/preconf-rpc/pricer/pricer.go +++ b/tools/preconf-rpc/pricer/pricer.go @@ -30,9 +30,9 @@ type BlockPrice struct { BidAmount *big.Int } -type bidPricer struct{} +type BidPricer struct{} -func (b *bidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (*BlockPrice, error) { +func (b *BidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) (*BlockPrice, error) { client := &http.Client{ Timeout: 10 * time.Second, } diff --git a/tools/preconf-rpc/pricer/pricer_test.go b/tools/preconf-rpc/pricer/pricer_test.go new file mode 100644 index 000000000..b84d54ad0 --- /dev/null +++ b/tools/preconf-rpc/pricer/pricer_test.go @@ -0,0 +1,42 @@ +package pricer_test + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/primev/mev-commit/tools/preconf-rpc/pricer" +) + +func TestEstimatePrice(t *testing.T) { + t.Parallel() + + bp := pricer.BidPricer{} + + ctx := context.Background() + txn := types.NewTransaction( + 0, + common.HexToAddress("0x1234567890123456789012345678901234567890"), + big.NewInt(1000000000), // 1 Gwei + 21000, // gas limit + big.NewInt(1000000000), // gas price + nil, // no data + ) + + price, err := bp.EstimatePrice(ctx, txn) + if err != nil { + t.Fatalf("failed to estimate price: %v", err) + } + + if price.BlockNumber == 0 { + t.Error("expected non-zero block number in estimated price") + } + + if price.BidAmount.Cmp(big.NewInt(0)) <= 0 { + t.Error("expected estimated price to be greater than zero") + } + + t.Logf("Estimated price: %s at block %d", price.BidAmount.String(), price.BlockNumber) +} diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index 398611b69..3f705b742 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -16,7 +16,10 @@ import ( bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" + "github.com/primev/mev-commit/tools/preconf-rpc/handlers" + "github.com/primev/mev-commit/tools/preconf-rpc/pricer" "github.com/primev/mev-commit/tools/preconf-rpc/rpcserver" + "github.com/primev/mev-commit/tools/preconf-rpc/store" "github.com/primev/mev-commit/x/accountsync" "github.com/primev/mev-commit/x/contracts/ethwrapper" "github.com/primev/mev-commit/x/health" @@ -29,6 +32,7 @@ import ( type Config struct { Logger *slog.Logger + DataDir string Signer keysigner.KeySigner BidderRPC string AutoDepositAmount *big.Int @@ -139,8 +143,21 @@ func New(config *Config) (*Service, error) { config.L1RPCUrls[0], ) - // TODO: Implement the handler for the RPC methods. Also use some sort of - // database to store the state of the service like balances of users etc. + bidpricer := &pricer.BidPricer{} + + rpcstore, err := store.New(config.DataDir) + if err != nil { + return nil, fmt.Errorf("failed to create store: %w", err) + } + + handlers := handlers.NewRPCMethodHandler( + config.Logger.With("module", "handlers"), + bidderClient, + rpcstore, + bidpricer, + ) + + handlers.RegisterMethods(rpcServer) srv := http.Server{ Addr: fmt.Sprintf(":%d", config.HTTPPort), diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index facb5b146..683ac13bc 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -91,15 +91,14 @@ func (s *rpcstore) GetPreconfirmedTransaction( if err != nil { return nil, nil, err } - defer func() { - _ = closer.Close() - }() blockNumber := binary.BigEndian.Uint64(blkNumBuf) if blockNumber == 0 { return nil, nil, fmt.Errorf("transaction %s not found", txnHash) } + _ = closer.Close() // Close the closer from Get + key := []byte(fmt.Sprintf("%d:%s", blockNumber, txnHash)) txnData, closer, err := s.db.Get(key) if err != nil { @@ -143,10 +142,7 @@ func (s *rpcstore) DeductBalance( _ = closer.Close() }() - currentBalanceBig := new(big.Int) - if err := currentBalanceBig.UnmarshalText(currentBalance); err != nil { - return fmt.Errorf("failed to unmarshal current balance: %w", err) - } + currentBalanceBig := new(big.Int).SetBytes(currentBalance) if currentBalanceBig.Cmp(amount) < 0 { return fmt.Errorf("insufficient balance for account %s: %w", account, ErrInsufficientBalance) } @@ -178,13 +174,12 @@ func (s *rpcstore) AddBalance( } } defer func() { - _ = closer.Close() + if closer != nil { + _ = closer.Close() + } }() - currentBalanceBig := new(big.Int) - if err := currentBalanceBig.UnmarshalText(currentBalance); err != nil { - return fmt.Errorf("failed to unmarshal current balance: %w", err) - } + currentBalanceBig := new(big.Int).SetBytes(currentBalance) newBalance := new(big.Int).Add(currentBalanceBig, amount) if err := s.db.Set(balanceKey, newBalance.Bytes(), nil); err != nil { @@ -212,11 +207,7 @@ func (s *rpcstore) HasBalance( _ = closer.Close() }() - currentBalanceBig := new(big.Int) - if err := currentBalanceBig.UnmarshalText(currentBalance); err != nil { - // If we can't unmarshal the balance, we assume the account has no balance - return false - } + currentBalanceBig := new(big.Int).SetBytes(currentBalance) if currentBalanceBig.Cmp(amount) < 0 { return false diff --git a/tools/preconf-rpc/store/store_test.go b/tools/preconf-rpc/store/store_test.go new file mode 100644 index 000000000..8722e13e1 --- /dev/null +++ b/tools/preconf-rpc/store/store_test.go @@ -0,0 +1,111 @@ +package store_test + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" + "github.com/primev/mev-commit/tools/preconf-rpc/store" +) + +func TestStore(t *testing.T) { + t.Parallel() + + st, err := store.New(t.TempDir()) + if err != nil { + t.Fatalf("failed to create store: %v", err) + } + + t.Cleanup(func() { + if err := st.Close(); err != nil { + t.Errorf("failed to close store: %v", err) + } + }) + + t.Run("StorePreconfirmedTransaction", func(t *testing.T) { + txn := types.NewTransaction( + 0, + common.HexToAddress("0x1234567890123456789012345678901234567890"), + big.NewInt(1000000000), // 1 Gwei + 21000, // gas limit + big.NewInt(1000000000), // gas price + nil, // no data + ) + commitments := []*bidderapiv1.Commitment{ + { + TxHashes: []string{txn.Hash().Hex()}, + BidAmount: big.NewInt(1000000000).String(), + BlockNumber: 1, + ReceivedBidDigest: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + ReceivedBidSignature: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + CommitmentDigest: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + CommitmentSignature: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + DecayStartTimestamp: time.Now().UnixMilli(), + DecayEndTimestamp: time.Now().Add(24 * time.Hour).UnixMilli(), + }, + { + TxHashes: []string{txn.Hash().Hex()}, + BidAmount: big.NewInt(1000000000).String(), + BlockNumber: 1, + ReceivedBidDigest: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + ReceivedBidSignature: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + CommitmentDigest: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + CommitmentSignature: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + DecayStartTimestamp: time.Now().UnixMilli(), + DecayEndTimestamp: time.Now().Add(24 * time.Hour).UnixMilli(), + }, + } + + err := st.StorePreconfirmedTransaction(context.Background(), 1, txn, commitments) + if err != nil { + t.Errorf("failed to store preconfirmed transaction: %v", err) + } + + storedTxn, storedCommitments, err := st.GetPreconfirmedTransaction(context.Background(), txn.Hash().Hex()) + if err != nil { + t.Errorf("failed to get preconfirmed transaction: %v", err) + } + + if txn.Hash() != txn.Hash() { + t.Errorf("expected transaction hash %s, got %s", txn.Hash().Hex(), storedTxn.Hash().Hex()) + } + if len(storedCommitments) != len(commitments) { + t.Errorf("expected %d commitments, got %d", len(commitments), len(storedCommitments)) + } + + for i, commitment := range commitments { + if diff := cmp.Diff(commitment, storedCommitments[i], cmpopts.IgnoreUnexported(bidderapiv1.Commitment{})); diff != "" { + t.Errorf("commitment mismatch (-want +got):\n%s", diff) + } + } + }) + + t.Run("Account Balance", func(t *testing.T) { + address := common.HexToAddress("0x1234567890123456789012345678901234567890") + initialBalance := big.NewInt(1000000000) // 1 Gwei + + err := st.AddBalance(context.Background(), address.Hex(), initialBalance) + if err != nil { + t.Errorf("failed to add balance: %v", err) + } + + if !st.HasBalance(context.Background(), address.Hex(), initialBalance) { + t.Errorf("expected balance %s, but has no balance", initialBalance.String()) + } + + err = st.DeductBalance(context.Background(), address.Hex(), initialBalance) + if err != nil { + t.Errorf("failed to deduct balance: %v", err) + } + + if st.HasBalance(context.Background(), address.Hex(), initialBalance) { + t.Errorf("expected no balance after deduction, but still has %s", initialBalance.String()) + } + }) +} From 0a595bd22e75f3ee2876f751df34756f2201231b Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 12 Jun 2025 15:42:13 +0530 Subject: [PATCH 30/69] feat: preconf RPC --- tools/preconf-rpc/main.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/preconf-rpc/main.go b/tools/preconf-rpc/main.go index ad940cdb8..fbf65dd9f 100644 --- a/tools/preconf-rpc/main.go +++ b/tools/preconf-rpc/main.go @@ -38,6 +38,19 @@ var ( Required: true, } + optionDataDir = &cli.StringFlag{ + Name: "data-dir", + Usage: "directory where data is stored", + EnvVars: []string{"INSTANT_BRIDGE_DATA_DIR"}, + Required: true, + Action: func(ctx *cli.Context, s string) error { + if _, err := os.Stat(s); os.IsNotExist(err) { + return fmt.Errorf("data-dir %s does not exist", s) + } + return nil + }, + } + optionL1RPCUrls = &cli.StringSliceFlag{ Name: "l1-rpc-urls", Usage: "URLs for L1 RPC", @@ -155,6 +168,7 @@ func main() { Usage: "Preconf RPC service", Flags: []cli.Flag{ optionHTTPPort, + optionDataDir, optionLogFmt, optionLogLevel, optionLogTags, @@ -220,6 +234,7 @@ func main() { config := service.Config{ HTTPPort: c.Int(optionHTTPPort.Name), + DataDir: c.String(optionDataDir.Name), Logger: logger, GasTipCap: gasTipCap, GasFeeCap: gasFeeCap, From 83855ea681fbd034f5cacdc8ffb05a905cc7ea20 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 18:34:14 +0530 Subject: [PATCH 31/69] feat: preconf RPC --- .github/workflows/infrastructure.yml | 1 + .../templates/jobs/preconf-rpc.nomad.j2 | 204 ++++++++++++++++++ .../nomad/playbooks/variables/profiles.yml | 56 +++++ tools/preconf-rpc/main.go | 34 +-- 4 files changed, 278 insertions(+), 17 deletions(-) create mode 100644 infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 diff --git a/.github/workflows/infrastructure.yml b/.github/workflows/infrastructure.yml index 1eab7f6f3..085ca1d83 100644 --- a/.github/workflows/infrastructure.yml +++ b/.github/workflows/infrastructure.yml @@ -14,6 +14,7 @@ on: - stressnet-wl1 - manual-test - instant-bridge-test + - preconf-rpc-test default: 'devnet' all_targets: description: 'All Arch & Os Targets' diff --git a/infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 new file mode 100644 index 000000000..263e6df61 --- /dev/null +++ b/infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 @@ -0,0 +1,204 @@ +#jinja2: trim_blocks:True, lstrip_blocks:True +job "{{ job.name }}" { + datacenters = ["{{ datacenter }}"] + + group "{{ job.name }}-group" { + count = {{ job.count }} + + {% if env == 'devenv' %} + restart { + attempts = 0 + mode = "fail" + } + + reschedule { + attempts = 0 + unlimited = false + } + {% endif %} + + network { + mode = "bridge" + + dns { + servers = {{ (ansible_facts['dns']['nameservers'] + ['1.1.1.1']) | tojson }} + } + + {% for port_name, port_details in job.ports[0].items() %} + port "{{ port_name }}" { + {% if port_details.get('static') %} + static = {{ port_details['static'] }} + {% endif %} + {% if port_details.get('to') %} + to = {{ port_details['to'] }} + {% endif %} + } + {% endfor %} + } + + {% for port_name in job.ports[0] %} + service { + name = "{{ job.name }}" + port = "{{ port_name }}" + tags = ["{{ port_name }}"] + provider = "nomad" + {% if port_name == "http" %} + check { + type = "http" + path = "/health" + interval = "10s" + timeout = "2s" + } + {% endif %} + } + {% endfor %} + + task "preconfrpc" { + driver = "exec" + + resources { + cpu = 4000 + memory = 4096 + } + + artifact { + source = "https://foundry.paradigm.xyz" + destination = "local/foundry.sh" + } + + {% if env != 'devenv' %} + artifact { + source = "https://primev-infrastructure-artifacts.s3.us-west-2.amazonaws.com/preconf-rpc_{{ version }}_Linux_{{ target_system_architecture }}.tar.gz" + } + {% else %} + artifact { + source = "http://{{ ansible_facts['default_ipv4']['address'] }}:1111/preconf-rpc_{{ version }}_Linux_{{ target_system_architecture }}.tar.gz" + } + {% endif %} + + template { + data = <<-EOH + XDG_CONFIG_HOME="local/.config" + PRECONF_RPC_LOG_LEVEL="{{ job.env.get('log-level', 'info') }}" + PRECONF_RPC_LOG_FMT="{{ job.env.get('log-format', 'json') }}" + PRECONF_RPC_LOG_TAGS="{{ 'service.name:' + job.name + '-{{ env "NOMAD_ALLOC_INDEX" }}' + ',service.version:' + version }}" + CONTRACTS_JSON_URL="{{ job.env.get('contracts_json_url', '') }}" + PRECONF_RPC_SETTLEMENT_RPC_URL="{{ job.env.get('settlement_rpc_url', '') }}" + {%- raw %} + PRECONF_RPC_KEYSTORE_DIR="/local/data-{{ env "NOMAD_ALLOC_INDEX" }}/keystore" + PRECONF_RPC_KEYSTORE_FILENAME="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.instant_bridge_keystore_filename }}{{ end }}" + PRECONF_RPC_KEYSTORE_PASSWORD="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.instant_bridge_keystore_password }}{{ end }}" + {{- range nomadService "mev-commit-geth-bootnode1" }} + {{- if contains "http" .Tags }} + PRECONF_RPC_SETTLEMENT_RPC_URL="http://{{ .Address }}:{{ .Port }}" + {{- end }} + {{- end }} + {{- range nomadService "{% endraw %}{{ job.target.name }}{% raw %}" }} + {{- if contains "rpc" .Tags }} + PRECONF_RPC_BIDDER_RPC_URL="{{ .Address }}:{{ .Port }}" + {{- end }} + {{- end }} + {% endraw %} + XDG_CONFIG_HOME="local/.config" + {% if profile == 'preconf-rpc-test' %} + {%- raw %} + {{- $secret := secret "secret/data/mev-commit" }} + CONTRACT_DEPLOYER_KEYSTORE_PATH="/local/data-{{ env "NOMAD_ALLOC_INDEX" }}/deployer_keystore" + CONTRACT_DEPLOYER_KEYSTORE_FILENAME="{{ $secret.Data.data.contract_deployer_keystore_filename }}" + CONTRACT_DEPLOYER_KEYSTORE_PASSWORD="{{ $secret.Data.data.contract_deployer_keystore_password }}" + {% endraw %} + {% endif %} + PRECONF_RPC_L1_RPC_URLS="{{ job.env['l1_rpc_urls'] }}" + CONTRACTS_PATH="local/contracts" + ARTIFACT_OUT_PATH="local" + EOH + destination = "secrets/.env" + env = true + } + + template { + data = <<-EOH + #!/usr/bin/env bash + + {% raw %} + {{- range nomadService "datadog-agent-logs-collector" }} + {{ if contains "tcp" .Tags }} + exec > >(nc {{ .Address }} {{ .Port }}) 2>&1 + {{ end }} + {{- end }} + mkdir -p "${PRECONF_RPC_KEYSTORE_DIR}" > /dev/null 2>&1 + {{- with secret "secret/data/mev-commit" }} + PRECONF_RPC_KEYSTORE_FILE="${PRECONF_RPC_KEYSTORE_DIR}/${PRECONF_RPC_KEYSTORE_FILENAME}" + echo '{{ .Data.data.instant_bridge_keystore }}' > "${PRECONF_RPC_KEYSTORE_FILE}" + {{ end }} + {% endraw %} + + {% if profile == 'preconf-rpc-test' %} + mkdir -p "${CONTRACT_DEPLOYER_KEYSTORE_PATH}" > /dev/null 2>&1 + CONTRACT_DEPLOYER_KEYSTORE_FILE="${CONTRACT_DEPLOYER_KEYSTORE_PATH}/${CONTRACT_DEPLOYER_KEYSTORE_FILENAME}" + {%- raw %} + {{- $secret := secret "secret/data/mev-commit" }} + echo '{{ $secret.Data.data.contract_deployer_keystore }}' > "${CONTRACT_DEPLOYER_KEYSTORE_FILE}" + {%- endraw %} + {% endif %} + + {% raw %} + {{- range nomadService "contracts-deployer" }} + {{ if contains "http" .Tags }} + CONTRACTS_JSON_URL="http://{{ .Address }}:{{ .Port }}/contracts.json" + {{ end }} + {{- end }} + {% endraw %} + CONTRACTS_FILE="/local/contracts.json" + curl -s -o "${CONTRACTS_FILE}" "${CONTRACTS_JSON_URL}" + export PRECONF_RPC_SETTLEMENT_CONTRACT_ADDR="$(jq -r '.SettlementGateway' ${CONTRACTS_FILE})" + export PRECONF_RPC_L1_CONTRACT_ADDR="$(jq -r '.L1Gateway' ${CONTRACTS_FILE})" + + chmod +x local/foundry.sh && local/foundry.sh + chmod +x ${XDG_CONFIG_HOME}/.foundry/bin/foundryup + ${XDG_CONFIG_HOME}/.foundry/bin/foundryup 2>&1 + if [ $? -ne 0 ]; then + echo "Failed to install foundry tools" + exit 1 + fi + export PATH="${XDG_CONFIG_HOME}/.foundry/bin:$PATH" + {%- raw %} + {{- range nomadService "mock-l1" }} + {{- if contains "ws" .Tags }} + L1_RPC_URL="ws://{{ .Address}}:{{ .Port }}" + {{- end }} + {{- with secret "secret/data/mev-commit" }} + ADDRESS="$(cat "${PRECONF_RPC_KEYSTORE_FILE}" | jq -r '.address')" + {{ end }} + cast send \ + --keystore "${CONTRACT_DEPLOYER_KEYSTORE_FILE}" \ + --password "${CONTRACT_DEPLOYER_KEYSTORE_PASSWORD}" \ + --priority-gas-price 2000000000 \ + --gas-price 5000000000 \ + --value 100ether \ + --rpc-url "${L1_RPC_URL}" \ + "${ADDRESS}" + + if [ $? -eq 0 ]; then + echo "Funds successfully sent to: ${ADDRESS}" + else + echo "Failed to send funds to: ${ADDRESS}" + fi + {{- end }} + {% endraw %} + + chmod +x local/preconf-rpc + exec ./local/preconf-rpc + EOH + destination = "local/run.sh" + change_mode = "noop" + perms = "0755" + } + + config { + command = "bash" + args = ["-c", "exec local/run.sh"] + } + } + } +} diff --git a/infrastructure/nomad/playbooks/variables/profiles.yml b/infrastructure/nomad/playbooks/variables/profiles.yml index 295c3380c..69abdc3d6 100644 --- a/infrastructure/nomad/playbooks/variables/profiles.yml +++ b/infrastructure/nomad/playbooks/variables/profiles.yml @@ -42,6 +42,9 @@ artifacts: instant-bridge: &instant_bridge_artifact type: binary path: tools/instant-bridge + preconf-rpc: &preconf_rpc_artifact + type: binary + path: tools/preconf-rpc jobs: artifacts: &artifacts_job @@ -738,6 +741,24 @@ jobs: settlement_rpc_url: "{{ settlement_rpc_url if settlement_rpc_url is defined else '' }}" contracts_json_url: "{{ contracts_json_url if contracts_json_url is defined else '' }}" + preconf_rpc: &preconf_rpc_job + name: preconf-rpc + template: preconf-rpc.nomad.j2 + artifacts: + - *preconf_rpc_artifact + - keystores: + preconf_rpc_keystore: + count: 1 + target: *mev_commit_bidder_node1_job + ports: + - http: + to: 8080 + env: + l1_chain_id: "{{ environments[env].chain_id }}" + l1_rpc_urls: "{{ resolved_l1_rpc_urls }}" + settlement_rpc_url: "{{ settlement_rpc_url if settlement_rpc_url is defined else '' }}" + contracts_json_url: "{{ contracts_json_url if contracts_json_url is defined else '' }}" + profiles: ci: jobs: @@ -943,6 +964,41 @@ profiles: - *instant_bridge_job - *datadog_agent_metrics_collector_job + preconf-rpc-test: + jobs: + - *artifacts_job + - *datadog_agent_logs_collector_job + - *otel_collector_job + - *beacon_emulator_job + - *mock_l1_job + - *mev_commit_geth_bootnode1_job + - *mev_commit_geth_signer_node1_job + - *mev_commit_geth_member_node_job + - *relay_emulator_job + - *contracts_deployer_job + - *mev_commit_bridge_job + - *mev_commit_dashboard_job + - *mev_commit_bootnode1_job + - *mev_commit_provider_node1_job + - *mev_commit_provider_node1_funder_job + - *mev_commit_provider_node2_job + - *mev_commit_provider_node2_funder_job + - *mev_commit_provider_emulator_nodes_job + - *mev_commit_oracle_job + - *mev_commit_bidder_node1_job + - *mev_commit_bidder_node1_funder_job + - *preconf_rpc_job + - *datadog_agent_metrics_collector_job + + instant-bridge: + jobs: + - *artifacts_job + - *datadog_agent_logs_collector_job + - *mev_commit_bidder_node1_job + - *instant_bridge_job + + archive: + jobs: instant-bridge: jobs: - *artifacts_job diff --git a/tools/preconf-rpc/main.go b/tools/preconf-rpc/main.go index fbf65dd9f..5e4858ebd 100644 --- a/tools/preconf-rpc/main.go +++ b/tools/preconf-rpc/main.go @@ -20,28 +20,28 @@ var ( optionHTTPPort = &cli.IntFlag{ Name: "http-port", Usage: "port for the HTTP server", - EnvVars: []string{"INSTANT_BRIDGE_HTTP_PORT"}, + EnvVars: []string{"PRECONF_RPC_HTTP_PORT"}, Value: 8080, } optionKeystorePath = &cli.StringFlag{ Name: "keystore-dir", Usage: "directory where keystore file is stored", - EnvVars: []string{"INSTANT_BRIDGE_KEYSTORE_DIR"}, + EnvVars: []string{"PRECONF_RPC_KEYSTORE_DIR"}, Required: true, } optionKeystorePassword = &cli.StringFlag{ Name: "keystore-password", Usage: "use to access keystore", - EnvVars: []string{"INSTANT_BRIDGE_KEYSTORE_PASSWORD"}, + EnvVars: []string{"PRECONF_RPC_KEYSTORE_PASSWORD"}, Required: true, } optionDataDir = &cli.StringFlag{ Name: "data-dir", Usage: "directory where data is stored", - EnvVars: []string{"INSTANT_BRIDGE_DATA_DIR"}, + EnvVars: []string{"PRECONF_RPC_DATA_DIR"}, Required: true, Action: func(ctx *cli.Context, s string) error { if _, err := os.Stat(s); os.IsNotExist(err) { @@ -54,77 +54,77 @@ var ( optionL1RPCUrls = &cli.StringSliceFlag{ Name: "l1-rpc-urls", Usage: "URLs for L1 RPC", - EnvVars: []string{"INSTANT_BRIDGE_L1_RPC_URLS"}, + EnvVars: []string{"PRECONF_RPC_L1_RPC_URLS"}, Required: true, } optionSettlementRPCUrl = &cli.StringFlag{ Name: "settlement-rpc-url", Usage: "URL for settlement RPC", - EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_RPC_URL"}, + EnvVars: []string{"PRECONF_RPC_SETTLEMENT_RPC_URL"}, Required: true, } optionBidderRPCUrl = &cli.StringFlag{ Name: "bidder-rpc-url", Usage: "URL for mev-commit bidder RPC", - EnvVars: []string{"INSTANT_BRIDGE_BIDDER_RPC_URL"}, + EnvVars: []string{"PRECONF_RPC_BIDDER_RPC_URL"}, Required: true, } optionL1ContractAddr = &cli.StringFlag{ Name: "l1-contract-addr", Usage: "address of the L1 gateway contract", - EnvVars: []string{"INSTANT_BRIDGE_L1_CONTRACT_ADDR"}, + EnvVars: []string{"PRECONF_RPC_L1_CONTRACT_ADDR"}, Required: true, } optionSettlementThreshold = &cli.StringFlag{ Name: "settlement-threshold", Usage: "Minimum threshold for settlement chain balance", - EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_THRESHOLD"}, + EnvVars: []string{"PRECONF_RPC_SETTLEMENT_THRESHOLD"}, Value: "5000000000000000000", // 5 ETH } optionSettlementTopup = &cli.StringFlag{ Name: "settlement-topup", Usage: "topup for settlement", - EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_TOPUP"}, + EnvVars: []string{"PRECONF_RPC_SETTLEMENT_TOPUP"}, Value: "10000000000000000000", // 10 ETH } optionAutoDepositAmount = &cli.StringFlag{ Name: "auto-deposit-amount", Usage: "auto deposit amount", - EnvVars: []string{"INSTANT_BRIDGE_AUTO_DEPOSIT_AMOUNT"}, + EnvVars: []string{"PRECONF_RPC_AUTO_DEPOSIT_AMOUNT"}, Value: "1000000000000000000", // 1 ETH } optionGasTipCap = &cli.StringFlag{ Name: "gas-tip-cap", Usage: "gas tip cap", - EnvVars: []string{"INSTANT_BRIDGE_GAS_TIP_CAP"}, + EnvVars: []string{"PRECONF_RPC_GAS_TIP_CAP"}, Value: "50000000", // 0.05 gWEI } optionGasFeeCap = &cli.StringFlag{ Name: "gas-fee-cap", Usage: "gas fee cap", - EnvVars: []string{"INSTANT_BRIDGE_GAS_FEE_CAP"}, + EnvVars: []string{"PRECONF_RPC_GAS_FEE_CAP"}, Value: "60000000", // 0.06 gWEI } optionSettlementContractAddr = &cli.StringFlag{ Name: "settlement-contract-addr", Usage: "address of the settlement gateway contract", - EnvVars: []string{"INSTANT_BRIDGE_SETTLEMENT_CONTRACT_ADDR"}, + EnvVars: []string{"PRECONF_RPC_SETTLEMENT_CONTRACT_ADDR"}, Required: true, } optionLogFmt = &cli.StringFlag{ Name: "log-fmt", Usage: "log format to use, options are 'text' or 'json'", - EnvVars: []string{"INSTANT_BRIDGE_LOG_FMT"}, + EnvVars: []string{"PRECONF_RPC_LOG_FMT"}, Value: "text", Action: func(ctx *cli.Context, s string) error { if !slices.Contains([]string{"text", "json"}, s) { @@ -137,7 +137,7 @@ var ( optionLogLevel = &cli.StringFlag{ Name: "log-level", Usage: "log level to use, options are 'debug', 'info', 'warn', 'error'", - EnvVars: []string{"INSTANT_BRIDGE_LOG_LEVEL"}, + EnvVars: []string{"PRECONF_RPC_LOG_LEVEL"}, Value: "info", Action: func(ctx *cli.Context, s string) error { if !slices.Contains([]string{"debug", "info", "warn", "error"}, s) { @@ -150,7 +150,7 @@ var ( optionLogTags = &cli.StringFlag{ Name: "log-tags", Usage: "log tags is a comma-separated list of pairs that will be inserted into each log line", - EnvVars: []string{"INSTANT_BRIDGE_LOG_TAGS"}, + EnvVars: []string{"PRECONF_RPC_LOG_TAGS"}, Action: func(ctx *cli.Context, s string) error { for i, p := range strings.Split(s, ",") { if len(strings.Split(p, ":")) != 2 { From 0aadda4ffc07ae0164dc83f67dd5f155c4824e14 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 18:39:29 +0530 Subject: [PATCH 32/69] feat: preconf RPC --- tools/go.mod | 23 +++++++++++++++++++++-- tools/go.sum | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index a1dde590d..12f27477a 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -21,13 +21,32 @@ require ( google.golang.org/protobuf v1.34.2 ) -require github.com/DATA-DOG/go-sqlmock v1.5.2 +require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 + github.com/cockroachdb/pebble v1.1.2 + github.com/google/go-cmp v0.6.0 + resenje.org/multex v0.2.0 +) require ( + github.com/DataDog/zstd v1.5.5 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect + github.com/getsentry/sentry-go v0.28.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - resenje.org/multex v0.2.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect ) replace github.com/primev/mev-commit/p2p => ../p2p diff --git a/tools/go.sum b/tools/go.sum index af1c3659c..a689056f3 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -20,6 +20,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a h1:f52TdbU4D5nozMAhO9TvTJ2ZMCXtN4VIAmfrrZ0JXQ4= @@ -44,6 +46,7 @@ github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOV github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= @@ -70,6 +73,8 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqG github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -124,6 +129,8 @@ github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7 github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= @@ -164,6 +171,8 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks= github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -175,6 +184,7 @@ github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3 github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -189,6 +199,7 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= @@ -213,23 +224,49 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= From 4f0f72f4c79f5ae2001df8338277d847800c9c20 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 18:51:55 +0530 Subject: [PATCH 33/69] fix: lint --- tools/instant-bridge/api/api.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/instant-bridge/api/api.go b/tools/instant-bridge/api/api.go index 72162c00e..eb079b5e8 100644 --- a/tools/instant-bridge/api/api.go +++ b/tools/instant-bridge/api/api.go @@ -222,8 +222,9 @@ func NewAPI( req.Context(), halfFee, bridgeAmt, - bridgeAmt, + // bridgeAmt, b.RawTx, + nil, ) if err != nil { apiserver.WriteError(w, http.StatusInternalServerError, err) From 2e3deedcae97e8e6b0a401956c75876170ad7f92 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 19:14:40 +0530 Subject: [PATCH 34/69] fix: lint --- tools/preconf-rpc/handlers/handlers.go | 5 +---- tools/preconf-rpc/pricer/pricer.go | 4 +++- tools/preconf-rpc/rpcserver/rpcserver.go | 2 +- tools/preconf-rpc/store/store.go | 6 +----- tools/preconf-rpc/store/store_test.go | 2 +- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 08ff7809f..925a2b607 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -171,10 +171,7 @@ func (h *rpcMethodHandler) handleSendRawTx( ) } - optedInSlot := false - if timeToOptIn <= blockTime { - optedInSlot = true - } + optedInSlot := timeToOptIn <= blockTime price, err := h.pricer.EstimatePrice(ctx, txn) if err != nil { diff --git a/tools/preconf-rpc/pricer/pricer.go b/tools/preconf-rpc/pricer/pricer.go index 11acd82d1..8c05cd619 100644 --- a/tools/preconf-rpc/pricer/pricer.go +++ b/tools/preconf-rpc/pricer/pricer.go @@ -47,7 +47,9 @@ func (b *BidPricer) EstimatePrice(ctx context.Context, txn *types.Transaction) ( return nil, err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode != http.StatusOK { return nil, errors.New("failed to fetch price estimate: " + resp.Status) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 4d70a45ef..b233c0f0f 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -122,7 +122,7 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { resp, proxy, err := handler(r.Context(), req.Params...) switch { case err != nil: - var jsonErr *JSONErr + jsonErr := new(JSONErr) if ok := errors.As(err, jsonErr); ok { // If the error is a JSONErr, we can use it directly. s.writeError(w, req.ID, jsonErr.Code, jsonErr.Message) diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index 683ac13bc..0e923bc9a 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -209,9 +209,5 @@ func (s *rpcstore) HasBalance( currentBalanceBig := new(big.Int).SetBytes(currentBalance) - if currentBalanceBig.Cmp(amount) < 0 { - return false - } - - return true + return currentBalanceBig.Cmp(amount) >= 0 } diff --git a/tools/preconf-rpc/store/store_test.go b/tools/preconf-rpc/store/store_test.go index 8722e13e1..4568eefab 100644 --- a/tools/preconf-rpc/store/store_test.go +++ b/tools/preconf-rpc/store/store_test.go @@ -72,7 +72,7 @@ func TestStore(t *testing.T) { t.Errorf("failed to get preconfirmed transaction: %v", err) } - if txn.Hash() != txn.Hash() { + if txn.Hash().Hex() != txn.Hash().Hex() { t.Errorf("expected transaction hash %s, got %s", txn.Hash().Hex(), storedTxn.Hash().Hex()) } if len(storedCommitments) != len(commitments) { From 6742447d3739062963423a3a9165987e87e9e43c Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 19:24:50 +0530 Subject: [PATCH 35/69] fix: lint --- tools/preconf-rpc/rpcserver/rpcserver.go | 2 +- tools/preconf-rpc/store/store_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index b233c0f0f..d1865cbd9 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -122,7 +122,7 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { resp, proxy, err := handler(r.Context(), req.Params...) switch { case err != nil: - jsonErr := new(JSONErr) + jsonErr := &JSONErr{} if ok := errors.As(err, jsonErr); ok { // If the error is a JSONErr, we can use it directly. s.writeError(w, req.ID, jsonErr.Code, jsonErr.Message) diff --git a/tools/preconf-rpc/store/store_test.go b/tools/preconf-rpc/store/store_test.go index 4568eefab..3ea1ab124 100644 --- a/tools/preconf-rpc/store/store_test.go +++ b/tools/preconf-rpc/store/store_test.go @@ -72,7 +72,7 @@ func TestStore(t *testing.T) { t.Errorf("failed to get preconfirmed transaction: %v", err) } - if txn.Hash().Hex() != txn.Hash().Hex() { + if txn.Hash().Hex() != storedTxn.Hash().Hex() { t.Errorf("expected transaction hash %s, got %s", txn.Hash().Hex(), storedTxn.Hash().Hex()) } if len(storedCommitments) != len(commitments) { From 25210ff44347433b7a74b62e864cb432fd9b1b02 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 19:35:46 +0530 Subject: [PATCH 36/69] fix: lint --- tools/preconf-rpc/rpcserver/rpcserver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index d1865cbd9..200519566 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -122,8 +122,8 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { resp, proxy, err := handler(r.Context(), req.Params...) switch { case err != nil: - jsonErr := &JSONErr{} - if ok := errors.As(err, jsonErr); ok { + var jsonErr *JSONErr + if ok := errors.As(err, &jsonErr); ok { // If the error is a JSONErr, we can use it directly. s.writeError(w, req.ID, jsonErr.Code, jsonErr.Message) return From 59a9700368f98392e912af82802c6e2032e2d823 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 20:59:39 +0530 Subject: [PATCH 37/69] fix: data dir --- tools/preconf-rpc/handlers/handlers.go | 52 +++++++++++++++++++++++++- tools/preconf-rpc/main.go | 20 +++++----- tools/preconf-rpc/service/service.go | 30 ++++++++++++++- 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 925a2b607..f761d2cf6 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -100,6 +100,7 @@ func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) { server.RegisterHandler("eth_sendRawTransaction", h.handleSendRawTx) server.RegisterHandler("eth_getTransactionReceipt", h.handleGetTxReceipt) server.RegisterHandler("eth_getTransactionCount", h.handleGetTxCount) + server.RegisterHandler("mevcommit_getTransactionCommitments", h.handleGetTxCommitments) } func (h *rpcMethodHandler) handleSendRawTx( @@ -329,7 +330,7 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any } h.logger.Info("Retrieving transaction receipt", "txHash", txHash) - txn, commitments, err := h.store.GetPreconfirmedTransaction(ctx, txHash[2:]) + txn, commitments, err := h.store.GetPreconfirmedTransaction(ctx, txHash) if err != nil { return nil, true, nil } @@ -406,3 +407,52 @@ func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) h.logger.Info("Retrieved account nonce from cache", "account", account, "nonce", accNonce.Nonce) return nonceJSON, false, nil } + +func (h *rpcMethodHandler) handleGetTxCommitments( + ctx context.Context, + params ...any, +) (json.RawMessage, bool, error) { + if len(params) != 1 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "getTxCommitments requires exactly one parameter", + ) + } + + if params[0] == nil { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getTxCommitments parameter cannot be null", + ) + } + + txHash := params[0].(string) + if len(txHash) < 2 || txHash[:2] != "0x" { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getTxCommitments parameter must be a hex string starting with '0x'", + ) + } + + h.logger.Info("Retrieving transaction commitments", "txHash", txHash) + _, commitments, err := h.store.GetPreconfirmedTransaction(ctx, txHash) + if err != nil { + return nil, true, nil + } + + if len(commitments) == 0 { + h.logger.Info("No commitments found for transaction", "txHash", txHash) + return json.RawMessage("[]"), false, nil + } + + commitmentsJSON, err := json.Marshal(commitments) + if err != nil { + h.logger.Error("Failed to marshal commitments to JSON", "error", err, "txHash", txHash) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to marshal commitments", + ) + } + + return commitmentsJSON, false, nil +} diff --git a/tools/preconf-rpc/main.go b/tools/preconf-rpc/main.go index 5e4858ebd..ede6b1d3c 100644 --- a/tools/preconf-rpc/main.go +++ b/tools/preconf-rpc/main.go @@ -39,16 +39,10 @@ var ( } optionDataDir = &cli.StringFlag{ - Name: "data-dir", - Usage: "directory where data is stored", - EnvVars: []string{"PRECONF_RPC_DATA_DIR"}, - Required: true, - Action: func(ctx *cli.Context, s string) error { - if _, err := os.Stat(s); os.IsNotExist(err) { - return fmt.Errorf("data-dir %s does not exist", s) - } - return nil - }, + Name: "data-dir", + Usage: "directory where data is stored", + EnvVars: []string{"PRECONF_RPC_DATA_DIR"}, + Value: "~/data", } optionL1RPCUrls = &cli.StringSliceFlag{ @@ -229,6 +223,12 @@ func main() { return fmt.Errorf("failed to create signer: %w", err) } + if _, err := os.Stat(c.String(optionDataDir.Name)); os.IsNotExist(err) { + if err := os.MkdirAll(c.String(optionDataDir.Name), 0755); err != nil { + return fmt.Errorf("failed to create data directory: %w", err) + } + } + sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index 3f705b742..772e158db 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -159,9 +159,37 @@ func New(config *Config) (*Service, error) { handlers.RegisterMethods(rpcServer) + mux := http.NewServeMux() + mux.Handle("/", rpcServer) + mux.HandleFunc("/add_balance", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + // get address and amount from params + address := r.URL.Query().Get("address") + amountStr := r.URL.Query().Get("amount") + if address == "" || amountStr == "" { + http.Error(w, "Missing address or amount", http.StatusBadRequest) + return + } + amount, ok := new(big.Int).SetString(amountStr, 10) + if !ok || amount.Sign() <= 0 { + http.Error(w, "Invalid amount", http.StatusBadRequest) + return + } + err := rpcstore.AddBalance(r.Context(), address, amount) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to add balance: %v", err), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Balance added successfully for address %s", address) + }) + srv := http.Server{ Addr: fmt.Sprintf(":%d", config.HTTPPort), - Handler: rpcServer, + Handler: mux, } go func() { From 65f7c483f346e4a224d2cbdace61ff96c2ef42f1 Mon Sep 17 00:00:00 2001 From: Alok Date: Thu, 12 Jun 2025 21:20:56 +0530 Subject: [PATCH 38/69] fix: profiles --- infrastructure/nomad/playbooks/variables/profiles.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/infrastructure/nomad/playbooks/variables/profiles.yml b/infrastructure/nomad/playbooks/variables/profiles.yml index 69abdc3d6..8547890c7 100644 --- a/infrastructure/nomad/playbooks/variables/profiles.yml +++ b/infrastructure/nomad/playbooks/variables/profiles.yml @@ -997,15 +997,6 @@ profiles: - *mev_commit_bidder_node1_job - *instant_bridge_job - archive: - jobs: - instant-bridge: - jobs: - - *artifacts_job - - *datadog_agent_logs_collector_job - - *mev_commit_bidder_node1_job - - *instant_bridge_job - archive: jobs: - *artifacts_job From 892caf658a33f9de44d0bfc1dbab8633404895ef Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 12 Jun 2025 23:30:01 +0530 Subject: [PATCH 39/69] feat: preconf RPC --- .../playbooks/templates/jobs/preconf-rpc.nomad.j2 | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 index 263e6df61..c184c8e28 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/preconf-rpc.nomad.j2 @@ -42,14 +42,6 @@ job "{{ job.name }}" { port = "{{ port_name }}" tags = ["{{ port_name }}"] provider = "nomad" - {% if port_name == "http" %} - check { - type = "http" - path = "/health" - interval = "10s" - timeout = "2s" - } - {% endif %} } {% endfor %} @@ -86,8 +78,8 @@ job "{{ job.name }}" { PRECONF_RPC_SETTLEMENT_RPC_URL="{{ job.env.get('settlement_rpc_url', '') }}" {%- raw %} PRECONF_RPC_KEYSTORE_DIR="/local/data-{{ env "NOMAD_ALLOC_INDEX" }}/keystore" - PRECONF_RPC_KEYSTORE_FILENAME="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.instant_bridge_keystore_filename }}{{ end }}" - PRECONF_RPC_KEYSTORE_PASSWORD="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.instant_bridge_keystore_password }}{{ end }}" + PRECONF_RPC_KEYSTORE_FILENAME="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.preconf_rpc_keystore_filename }}{{ end }}" + PRECONF_RPC_KEYSTORE_PASSWORD="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.preconf_rpc_keystore_password }}{{ end }}" {{- range nomadService "mev-commit-geth-bootnode1" }} {{- if contains "http" .Tags }} PRECONF_RPC_SETTLEMENT_RPC_URL="http://{{ .Address }}:{{ .Port }}" @@ -129,7 +121,7 @@ job "{{ job.name }}" { mkdir -p "${PRECONF_RPC_KEYSTORE_DIR}" > /dev/null 2>&1 {{- with secret "secret/data/mev-commit" }} PRECONF_RPC_KEYSTORE_FILE="${PRECONF_RPC_KEYSTORE_DIR}/${PRECONF_RPC_KEYSTORE_FILENAME}" - echo '{{ .Data.data.instant_bridge_keystore }}' > "${PRECONF_RPC_KEYSTORE_FILE}" + echo '{{ .Data.data.preconf_rpc_keystore }}' > "${PRECONF_RPC_KEYSTORE_FILE}" {{ end }} {% endraw %} From f433759d6189052260ee90ce724d523e59041c36 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 13 Jun 2025 02:59:45 +0530 Subject: [PATCH 40/69] feat: preconf RPC --- .../playbooks/templates/jobs/contracts-deployer.nomad.j2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 index 941f663d1..944a6150c 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 @@ -14,8 +14,8 @@ job "{{ job.name }}" { } {% else %} update { - healthy_deadline = "10m" - progress_deadline = "15m" + healthy_deadline = "20m" + progress_deadline = "30m" } {% endif %} From 6407b2952e58613b853edaca72a4ce29ad966b06 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 13 Jun 2025 03:18:50 +0530 Subject: [PATCH 41/69] feat: preconf RPC --- .../templates/jobs/contracts-deployer.nomad.j2 | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 index 944a6150c..c940a969b 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 @@ -5,19 +5,10 @@ job "{{ job.name }}" { group "{{ job.name }}-group" { count = {{ job.count }} - # This is a special case for CI because the runner machine is not very - # powerful and compiling and deploying contracts can take a long time. - {% if env == 'devenv' and profile == 'ci' %} update { - healthy_deadline = "20m" - progress_deadline = "30m" + healthy_deadline = "25m" + progress_deadline = "35m" } - {% else %} - update { - healthy_deadline = "20m" - progress_deadline = "30m" - } - {% endif %} {% if env == 'devenv' %} restart { From 8b77b5b7127b168775ed68f95a046f3a2af7c8f3 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 13 Jun 2025 03:44:01 +0530 Subject: [PATCH 42/69] feat: preconf RPC --- infrastructure/nomad/playbooks/variables/profiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/nomad/playbooks/variables/profiles.yml b/infrastructure/nomad/playbooks/variables/profiles.yml index 8547890c7..197aaf4f0 100644 --- a/infrastructure/nomad/playbooks/variables/profiles.yml +++ b/infrastructure/nomad/playbooks/variables/profiles.yml @@ -969,7 +969,7 @@ profiles: - *artifacts_job - *datadog_agent_logs_collector_job - *otel_collector_job - - *beacon_emulator_job + # - *beacon_emulator_job - *mock_l1_job - *mev_commit_geth_bootnode1_job - *mev_commit_geth_signer_node1_job From 8c6849bbe8d73b33adca2233c86e7be8baff394b Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 13 Jun 2025 15:55:39 +0530 Subject: [PATCH 43/69] feat: preconf RPC --- tools/preconf-rpc/rpcserver/rpcserver.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 200519566..e3808a735 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -83,10 +83,10 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if r.Header.Get("Content-Type") != "application/json" { - http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) - return - } + // if r.Header.Get("Content-Type") != "application/json" { + // http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) + // return + // } r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBodySize) defer func() { From f1b8bec31f5852dbe3f16b7234a79084365acee8 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 13 Jun 2025 16:26:09 +0530 Subject: [PATCH 44/69] feat: preconf RPC --- tools/preconf-rpc/rpcserver/rpcserver.go | 17 ++++++++++++----- tools/preconf-rpc/service/service.go | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index e3808a735..469a0c02a 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "io" + "log/slog" "net/http" "sync" "time" @@ -62,9 +63,10 @@ type JSONRPCServer struct { rwLock sync.RWMutex methods map[string]methodHandler proxyURL string + logger *slog.Logger } -func NewJSONRPCServer(proxyURL string) *JSONRPCServer { +func NewJSONRPCServer(proxyURL string, logger *slog.Logger) *JSONRPCServer { return &JSONRPCServer{ proxyURL: proxyURL, methods: make(map[string]methodHandler), @@ -83,10 +85,13 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // if r.Header.Get("Content-Type") != "application/json" { - // http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) - // return - // } + s.logger.Info("Received JSON-RPC request", "method", r.Method, "url", r.URL.Path) + + if r.Header.Get("Content-Type") != "application/json" { + s.logger.Error("Invalid content type", "content-type", r.Header.Get("Content-Type")) + http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) + return + } r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBodySize) defer func() { @@ -111,6 +116,8 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + s.logger.Info("Processing JSON-RPC request", "method", req.Method, "id", req.ID) + s.rwLock.RLock() handler, ok := s.methods[req.Method] s.rwLock.RUnlock() diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index 772e158db..e3af2285e 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -141,6 +141,7 @@ func New(config *Config) (*Service, error) { rpcServer := rpcserver.NewJSONRPCServer( config.L1RPCUrls[0], + config.Logger.With("module", "rpcserver"), ) bidpricer := &pricer.BidPricer{} From 95972f7f528a4990528407475671e8d1bf51b1d7 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 13 Jun 2025 16:56:26 +0530 Subject: [PATCH 45/69] feat: preconf RPC --- tools/preconf-rpc/rpcserver/rpcserver.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 469a0c02a..c763e7e5b 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -85,12 +85,12 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - s.logger.Info("Received JSON-RPC request", "method", r.Method, "url", r.URL.Path) + s.logger.Info("Received JSON-RPC request", "method", r.Method) if r.Header.Get("Content-Type") != "application/json" { s.logger.Error("Invalid content type", "content-type", r.Header.Get("Content-Type")) - http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) - return + // http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) + // return } r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBodySize) From a038901ce3d5730c955d0f2d00ed12fd884ff7cf Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 13 Jun 2025 17:32:37 +0530 Subject: [PATCH 46/69] feat: preconf RPC --- tools/preconf-rpc/rpcserver/rpcserver.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index c763e7e5b..9135615d5 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -70,6 +70,7 @@ func NewJSONRPCServer(proxyURL string, logger *slog.Logger) *JSONRPCServer { return &JSONRPCServer{ proxyURL: proxyURL, methods: make(map[string]methodHandler), + logger: logger, } } From 4838333e722733bfc39a6871b715f289c4909377 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 14 Jun 2025 14:31:53 +0530 Subject: [PATCH 47/69] feat: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 273 +++++++++++++++-------- tools/preconf-rpc/rpcserver/rpcserver.go | 15 +- x/opt-in-bidder/bidder.go | 7 +- 3 files changed, 188 insertions(+), 107 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index f761d2cf6..bd9ff9e0b 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -4,11 +4,14 @@ import ( "context" "encoding/hex" "encoding/json" + "errors" + "fmt" "log/slog" "math/big" "sync" "sync/atomic" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" "github.com/primev/mev-commit/tools/preconf-rpc/pricer" @@ -62,17 +65,34 @@ type Store interface { ) bool } +type BlockTracker interface { + CheckTxnInclusion( + ctx context.Context, + txnHash common.Hash, + blockNumber uint64, + ) (bool, error) +} + type accountNonce struct { Account string `json:"account"` Nonce uint64 `json:"nonce"` Block int64 `json:"block"` } +type bidResult struct { + noOfProviders int + blockNumber uint64 + optedInSlot bool + bidAmount *big.Int + commitments []*bidderapiv1.Commitment +} + type rpcMethodHandler struct { logger *slog.Logger bidder Bidder store Store pricer Pricer + blockTracker BlockTracker nonceLock *multex.Multex[string] latestBlock atomic.Pointer[types.Block] nonceMap map[string]accountNonce @@ -156,155 +176,211 @@ func (h *rpcMethodHandler) handleSendRawTx( // Once we are ready to send the bid, we need to ensure that the nonce for the // sender is not locked by another transaction. h.nonceLock.Lock(sender.Hex()) - unlock := true - defer func() { - if unlock { - h.nonceLock.Unlock(sender.Hex()) + defer h.nonceLock.Unlock(sender.Hex()) + +BID_LOOP: + for { + select { + case <-ctx.Done(): + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "context cancelled while processing transaction", + ) + default: } - }() - timeToOptIn, err := h.bidder.Estimate() + result, err := h.sendBid(ctx, txn, sender, rawTxHex) + switch { + case err != nil: + h.logger.Error("Failed to send bid", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to send bid", + ) + case result.optedInSlot: + if result.noOfProviders == len(result.commitments) { + // This means that all builders have committed to the bid and it + // is a primev opted in slot. We can safely proceed to inform the + // user that the txn was successfully sent and will be processed + if err := h.storePreconfAndDeductBalance( + ctx, + txn, + result.commitments, + sender, + int64(result.blockNumber), + result.bidAmount, + ); err != nil { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to update preconfirmed transaction and deduct balance", + ) + } + // Update the nonce locally if user wants to send more transactions + h.nonceMapLock.Lock() + h.nonceMap[sender.Hex()] = accountNonce{ + Account: sender.Hex(), + Nonce: txn.Nonce() + 1, + Block: int64(result.blockNumber), + } + h.nonceMapLock.Unlock() + break BID_LOOP + } + default: + } + + // Wait for block number to be updated to confirm transaction. If failed + // we will retry the bid process till user cancels the operation + included, err := h.blockTracker.CheckTxnInclusion(ctx, txn.Hash(), result.blockNumber) + if err != nil { + h.logger.Error("Failed to check transaction inclusion", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to check transaction inclusion", + ) + } + if included { + if err := h.storePreconfAndDeductBalance( + ctx, + txn, + result.commitments, + sender, + int64(result.blockNumber), + result.bidAmount, + ); err != nil { + h.logger.Error("Failed to update preconfirmed transaction and deduct balance", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to update preconfirmed transaction and deduct balance", + ) + } + break BID_LOOP + } + } + + // If we reach here, we have a successful bid with commitments + txHashJSON, err := json.Marshal(txn.Hash().Hex()) if err != nil { - h.logger.Error("Failed to estimate time to opt-in", "error", err) + h.logger.Error("Failed to marshal transaction hash to JSON", "error", err) return nil, false, rpcserver.NewJSONErr( rpcserver.CodeCustomError, - "failed to estimate time to opt-in", + "failed to marshal transaction hash", ) } + return txHashJSON, false, nil +} + +func (h *rpcMethodHandler) sendBid( + ctx context.Context, + txn *types.Transaction, + sender common.Address, + rawTxHex string, +) (bidResult, error) { + timeToOptIn, err := h.bidder.Estimate() + if err != nil { + h.logger.Error("Failed to estimate time to opt-in", "error", err) + if !errors.Is(err, optinbidder.ErrNoSlotInCurrentEpoch) { + return bidResult{}, err + } + // If we cannot estimate the time to opt-in, we assume a default value and + // proceed with the bid process. The default value should be higher than + // the typical block time to ensure we consider the next slot as a non-opt-in slot. + timeToOptIn = blockTime * 32 + } + optedInSlot := timeToOptIn <= blockTime price, err := h.pricer.EstimatePrice(ctx, txn) if err != nil { h.logger.Error("Failed to estimate transaction price", "error", err) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "failed to estimate transaction price", - ) + return bidResult{}, fmt.Errorf("failed to estimate transaction price: %w", err) } if !h.store.HasBalance(ctx, sender.Hex(), price.BidAmount) { h.logger.Error("Insufficient balance for sender", "sender", sender.Hex()) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "insufficient balance for sender", - ) + return bidResult{}, fmt.Errorf("insufficient balance for sender: %s", sender.Hex()) } bidC, err := h.bidder.Bid( ctx, price.BidAmount, big.NewInt(0), - rawTxHex, + rawTxHex[2:], &optinbidder.BidOpts{ WaitForOptIn: optedInSlot, - BlockNumber: uint64(price.BlockNumber), + // BlockNumber: uint64(price.BlockNumber), }, ) if err != nil { h.logger.Error("Failed to place bid", "error", err) - return nil, false, rpcserver.NewJSONErr(rpcserver.CodeCustomError, "failed to place bid") + return bidResult{}, fmt.Errorf("failed to place bid: %w", err) + } + + result := bidResult{ + commitments: make([]*bidderapiv1.Commitment, 0), } - noOfProviders, noOfCommitments, blockNumber := 0, 0, 0 - cancelled, failed := false, false - commitments := make([]*bidderapiv1.Commitment, 0) BID_LOOP: for { select { case <-ctx.Done(): h.logger.Info("Context cancelled while waiting for bid status") - return nil, false, ctx.Err() - case bidStatus := <-bidC: + return bidResult{}, ctx.Err() + case bidStatus, more := <-bidC: + if !more { + h.logger.Info("Bid channel closed, no more bid statuses") + break BID_LOOP + } switch bidStatus.Type { case optinbidder.BidStatusNoOfProviders: - noOfProviders = bidStatus.Arg.(int) + result.noOfProviders = bidStatus.Arg.(int) case optinbidder.BidStatusAttempted: - blockNumber = bidStatus.Arg.(int) + result.blockNumber = bidStatus.Arg.(uint64) case optinbidder.BidStatusCommitment: - noOfCommitments++ - commitments = append(commitments, bidStatus.Arg.(*bidderapiv1.Commitment)) + result.commitments = append(result.commitments, bidStatus.Arg.(*bidderapiv1.Commitment)) case optinbidder.BidStatusCancelled: - cancelled = true + h.logger.Warn("Bid context cancelled by the bidder") break BID_LOOP case optinbidder.BidStatusFailed: - failed = true + h.logger.Error("Bid failed", "error", bidStatus.Arg) break BID_LOOP } } } - switch { - case noOfProviders == 0: - h.logger.Info("No providers available for the bid", "noOfProviders", noOfProviders) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "no providers available for the bid", - ) - case cancelled || failed: - h.logger.Info( - "Bid cancelled or failed", - "cancelled", cancelled, - "failed", failed, - "noOfCommitments", noOfCommitments, - ) - if noOfCommitments == 0 { - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "bid cancelled with no commitments", - ) - } - case noOfCommitments == 0: - h.logger.Info("Bid completed with no commitments", "noOfCommitments", noOfCommitments) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "bid completed with no commitments", - ) + if len(result.commitments) == 0 { + h.logger.Error("Bid completed with no commitments") + return bidResult{}, fmt.Errorf("bid completed with no commitments") } h.logger.Info( "Bid successful with commitments", - "noOfProviders", noOfProviders, - "noOfCommitments", noOfCommitments, - "blockNumber", blockNumber, + "noOfProviders", result.noOfProviders, + "noOfCommitments", len(result.commitments), + "blockNumber", result.blockNumber, + "optedInSlot", optedInSlot, ) - // If we reach here, we have a successful bid with commitments - txHashJSON, err := json.Marshal(txn.Hash().Hex()) - if err != nil { - h.logger.Error("Failed to marshal transaction hash to JSON", "error", err) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "failed to marshal transaction hash", - ) - } - if err := h.store.DeductBalance(ctx, sender.Hex(), price.BidAmount); err != nil { - h.logger.Error("Failed to deduct balance for sender", "sender", sender.Hex(), "error", err) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "failed to deduct balance for sender", - ) - } + result.optedInSlot = optedInSlot + return result, nil +} - if err := h.store.StorePreconfirmedTransaction(ctx, int64(blockNumber), txn, commitments); err != nil { +func (h *rpcMethodHandler) storePreconfAndDeductBalance( + ctx context.Context, + txn *types.Transaction, + commitments []*bidderapiv1.Commitment, + sender common.Address, + blockNumber int64, + amount *big.Int, +) error { + if err := h.store.StorePreconfirmedTransaction(ctx, blockNumber, txn, commitments); err != nil { h.logger.Error("Failed to store preconfirmed transaction", "error", err) - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeCustomError, - "failed to store preconfirmed transaction", - ) + return fmt.Errorf("failed to store preconfirmed transaction: %w", err) } - if noOfProviders == noOfCommitments && optedInSlot { - h.logger.Info("All providers committed, updating nonce", "account", sender.Hex(), "nonce", txn.Nonce()+1) - h.nonceMapLock.Lock() - h.nonceMap[sender.Hex()] = accountNonce{ - Account: sender.Hex(), - Nonce: txn.Nonce() + 1, - Block: int64(blockNumber), - } - h.nonceMapLock.Unlock() - } else { - unlock = false + if err := h.store.DeductBalance(ctx, sender.Hex(), amount); err != nil { + h.logger.Error("Failed to deduct balance for sender", "sender", sender.Hex(), "error", err) + return fmt.Errorf("failed to deduct balance for sender: %w", err) } - return txHashJSON, false, nil + return nil } func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any) (json.RawMessage, bool, error) { @@ -338,7 +414,7 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any receipt := &types.Receipt{ TxHash: txn.Hash(), Type: txn.Type(), - Status: 1, // Assuming success, as this is a preconfirmed transaction + Status: types.ReceiptStatusSuccessful, // Assuming success, as this is a preconfirmed transaction BlockNumber: big.NewInt(commitments[0].BlockNumber), } @@ -355,11 +431,11 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any } func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) (json.RawMessage, bool, error) { - if len(params) != 1 { - return nil, false, rpcserver.NewJSONErr( - rpcserver.CodeInvalidRequest, - "getTxCount requires exactly one parameter", - ) + if len(params) == 2 { + state := params[1].(string) + if state != "latest" && state != "pending" { + return nil, true, nil + } } if params[0] == nil { @@ -434,7 +510,6 @@ func (h *rpcMethodHandler) handleGetTxCommitments( ) } - h.logger.Info("Retrieving transaction commitments", "txHash", txHash) _, commitments, err := h.store.GetPreconfirmedTransaction(ctx, txHash) if err != nil { return nil, true, nil diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 9135615d5..12c2c8aea 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -89,9 +89,8 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.logger.Info("Received JSON-RPC request", "method", r.Method) if r.Header.Get("Content-Type") != "application/json" { - s.logger.Error("Invalid content type", "content-type", r.Header.Get("Content-Type")) - // http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) - // return + http.Error(w, "Invalid content type", http.StatusUnsupportedMediaType) + return } r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBodySize) @@ -190,7 +189,9 @@ func (s *JSONRPCServer) proxyRequest(w http.ResponseWriter, body []byte) { http.Error(w, "Failed to create proxy request", http.StatusInternalServerError) return } + req.Header.Set("Content-Type", "application/json") + s.logger.Info("Proxying request", "url", s.proxyURL, "body", string(body)) resp, err := client.Do(req) if err != nil { http.Error(w, "Failed to execute proxy request", http.StatusInternalServerError) @@ -203,13 +204,13 @@ func (s *JSONRPCServer) proxyRequest(w http.ResponseWriter, body []byte) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(resp.StatusCode) rdr := io.LimitReader(resp.Body, defaultMaxBodySize) - respBuf, err := io.ReadAll(rdr) + n, err := io.Copy(w, rdr) if err != nil { - http.Error(w, "Failed to read proxy response", http.StatusInternalServerError) + http.Error(w, "Failed to copy proxy response", http.StatusInternalServerError) return } - if _, err := w.Write(respBuf); err != nil { - http.Error(w, "Failed to write proxy response", http.StatusInternalServerError) + if n == 0 { + http.Error(w, "Empty response from proxy", http.StatusInternalServerError) return } } diff --git a/x/opt-in-bidder/bidder.go b/x/opt-in-bidder/bidder.go index d206ff74f..69a7cc281 100644 --- a/x/opt-in-bidder/bidder.go +++ b/x/opt-in-bidder/bidder.go @@ -224,7 +224,6 @@ func (b *BidderClient) Bid( defer close(res) defer b.bigWg.Done() - fmt.Println("BidderClient sending no of providers") res <- BidStatus{Type: BidStatusNoOfProviders, Arg: len(providers.Values)} if opts.WaitForOptIn { @@ -262,6 +261,12 @@ func (b *BidderClient) Bid( } res <- BidStatus{Type: BidStatusAttempted, Arg: blkNumber} + b.logger.Info( + "attempting to send bid", + "blockNumber", blkNumber, + "bidAmount", bidAmount, + "slashAmount", slashAmount, + ) pc, err := b.bidderClient.SendBid(ctx, &bidderapiv1.Bid{ Amount: bidAmount.String(), From aa63e1d46d0842162bd82c95716ee65766ca6f76 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 14 Jun 2025 15:15:30 +0530 Subject: [PATCH 48/69] feat: preconf RPC --- tools/preconf-rpc/handlers/handlers.go | 22 ++++++++++++--------- tools/preconf-rpc/store/store.go | 27 +++++++++++++------------- tools/preconf-rpc/store/store_test.go | 10 +++++----- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index bd9ff9e0b..ec88d9b1a 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -51,16 +51,16 @@ type Store interface { ) error GetPreconfirmedTransaction( ctx context.Context, - txnHash string, + txnHash common.Hash, ) (*types.Transaction, []*bidderapiv1.Commitment, error) DeductBalance( ctx context.Context, - account string, + account common.Address, amount *big.Int, ) error HasBalance( ctx context.Context, - account string, + account common.Address, amount *big.Int, ) bool } @@ -296,7 +296,7 @@ func (h *rpcMethodHandler) sendBid( return bidResult{}, fmt.Errorf("failed to estimate transaction price: %w", err) } - if !h.store.HasBalance(ctx, sender.Hex(), price.BidAmount) { + if !h.store.HasBalance(ctx, sender, price.BidAmount) { h.logger.Error("Insufficient balance for sender", "sender", sender.Hex()) return bidResult{}, fmt.Errorf("insufficient balance for sender: %s", sender.Hex()) } @@ -375,7 +375,7 @@ func (h *rpcMethodHandler) storePreconfAndDeductBalance( return fmt.Errorf("failed to store preconfirmed transaction: %w", err) } - if err := h.store.DeductBalance(ctx, sender.Hex(), amount); err != nil { + if err := h.store.DeductBalance(ctx, sender, amount); err != nil { h.logger.Error("Failed to deduct balance for sender", "sender", sender.Hex(), "error", err) return fmt.Errorf("failed to deduct balance for sender: %w", err) } @@ -397,14 +397,16 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any ) } - txHash := params[0].(string) - if len(txHash) < 2 || txHash[:2] != "0x" { + txHashStr := params[0].(string) + if len(txHashStr) < 2 || txHashStr[:2] != "0x" { return nil, false, rpcserver.NewJSONErr( rpcserver.CodeParseError, "getTxReceipt parameter must be a hex string starting with '0x'", ) } + txHash := common.HexToHash(txHashStr) + h.logger.Info("Retrieving transaction receipt", "txHash", txHash) txn, commitments, err := h.store.GetPreconfirmedTransaction(ctx, txHash) if err != nil { @@ -502,14 +504,16 @@ func (h *rpcMethodHandler) handleGetTxCommitments( ) } - txHash := params[0].(string) - if len(txHash) < 2 || txHash[:2] != "0x" { + txHashStr := params[0].(string) + if len(txHashStr) < 2 || txHashStr[:2] != "0x" { return nil, false, rpcserver.NewJSONErr( rpcserver.CodeParseError, "getTxCommitments parameter must be a hex string starting with '0x'", ) } + txHash := common.HexToHash(txHashStr) + _, commitments, err := h.store.GetPreconfirmedTransaction(ctx, txHash) if err != nil { return nil, true, nil diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index 0e923bc9a..508cc2b9c 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -9,6 +9,7 @@ import ( "math/big" "github.com/cockroachdb/pebble" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" ) @@ -80,13 +81,13 @@ func (s *rpcstore) StorePreconfirmedTransaction( func (s *rpcstore) GetPreconfirmedTransaction( ctx context.Context, - txnHash string, + txnHash common.Hash, ) (*types.Transaction, []*bidderapiv1.Commitment, error) { - if txnHash == "" { + if txnHash == (common.Hash{}) { return nil, nil, errors.New("transaction hash cannot be empty") } - txnKey := []byte(fmt.Sprintf("txn:%s", txnHash)) + txnKey := []byte(fmt.Sprintf("txn:%s", txnHash.Hex())) blkNumBuf, closer, err := s.db.Get(txnKey) if err != nil { return nil, nil, err @@ -99,7 +100,7 @@ func (s *rpcstore) GetPreconfirmedTransaction( _ = closer.Close() // Close the closer from Get - key := []byte(fmt.Sprintf("%d:%s", blockNumber, txnHash)) + key := []byte(fmt.Sprintf("%d:%s", blockNumber, txnHash.Hex())) txnData, closer, err := s.db.Get(key) if err != nil { return nil, nil, err @@ -126,14 +127,14 @@ func (s *rpcstore) GetPreconfirmedTransaction( func (s *rpcstore) DeductBalance( ctx context.Context, - account string, + account common.Address, amount *big.Int, ) error { - if account == "" || amount == nil || amount.Sign() <= 0 { + if account == (common.Address{}) || amount == nil || amount.Sign() <= 0 { return errors.New("invalid account or amount") } - balanceKey := []byte(fmt.Sprintf("balance:%s", account)) + balanceKey := []byte(fmt.Sprintf("balance:%s", account.Hex())) currentBalance, closer, err := s.db.Get(balanceKey) if err != nil { return err @@ -156,14 +157,14 @@ func (s *rpcstore) DeductBalance( func (s *rpcstore) AddBalance( ctx context.Context, - account string, + account common.Address, amount *big.Int, ) error { - if account == "" || amount == nil || amount.Sign() <= 0 { + if account == (common.Address{}) || amount == nil || amount.Sign() <= 0 { return errors.New("invalid account or amount") } - balanceKey := []byte(fmt.Sprintf("balance:%s", account)) + balanceKey := []byte(fmt.Sprintf("balance:%s", account.Hex())) currentBalance, closer, err := s.db.Get(balanceKey) if err != nil { if errors.Is(err, pebble.ErrNotFound) { @@ -191,14 +192,14 @@ func (s *rpcstore) AddBalance( func (s *rpcstore) HasBalance( ctx context.Context, - account string, + account common.Address, amount *big.Int, ) bool { - if account == "" || amount == nil || amount.Sign() <= 0 { + if account == (common.Address{}) || amount == nil || amount.Sign() <= 0 { return false } - balanceKey := []byte(fmt.Sprintf("balance:%s", account)) + balanceKey := []byte(fmt.Sprintf("balance:%s", account.Hex())) currentBalance, closer, err := s.db.Get(balanceKey) if err != nil { return false diff --git a/tools/preconf-rpc/store/store_test.go b/tools/preconf-rpc/store/store_test.go index 3ea1ab124..4c16af0f4 100644 --- a/tools/preconf-rpc/store/store_test.go +++ b/tools/preconf-rpc/store/store_test.go @@ -67,7 +67,7 @@ func TestStore(t *testing.T) { t.Errorf("failed to store preconfirmed transaction: %v", err) } - storedTxn, storedCommitments, err := st.GetPreconfirmedTransaction(context.Background(), txn.Hash().Hex()) + storedTxn, storedCommitments, err := st.GetPreconfirmedTransaction(context.Background(), txn.Hash()) if err != nil { t.Errorf("failed to get preconfirmed transaction: %v", err) } @@ -90,21 +90,21 @@ func TestStore(t *testing.T) { address := common.HexToAddress("0x1234567890123456789012345678901234567890") initialBalance := big.NewInt(1000000000) // 1 Gwei - err := st.AddBalance(context.Background(), address.Hex(), initialBalance) + err := st.AddBalance(context.Background(), address, initialBalance) if err != nil { t.Errorf("failed to add balance: %v", err) } - if !st.HasBalance(context.Background(), address.Hex(), initialBalance) { + if !st.HasBalance(context.Background(), address, initialBalance) { t.Errorf("expected balance %s, but has no balance", initialBalance.String()) } - err = st.DeductBalance(context.Background(), address.Hex(), initialBalance) + err = st.DeductBalance(context.Background(), address, initialBalance) if err != nil { t.Errorf("failed to deduct balance: %v", err) } - if st.HasBalance(context.Background(), address.Hex(), initialBalance) { + if st.HasBalance(context.Background(), address, initialBalance) { t.Errorf("expected no balance after deduction, but still has %s", initialBalance.String()) } }) From c18bf99dcfc36cd5df30699e213d59cec3985efd Mon Sep 17 00:00:00 2001 From: Alok Date: Sat, 14 Jun 2025 18:43:21 +0530 Subject: [PATCH 49/69] fix: misc --- tools/preconf-rpc/handlers/handlers.go | 23 +++++++++++++++-------- tools/preconf-rpc/service/service.go | 17 +++++++++++++++-- tools/preconf-rpc/store/store.go | 1 + 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index ec88d9b1a..6f058dbe0 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -104,15 +104,17 @@ func NewRPCMethodHandler( bidder Bidder, store Store, pricer Pricer, + blockTracker BlockTracker, ) *rpcMethodHandler { return &rpcMethodHandler{ - logger: logger, - bidder: bidder, - store: store, - pricer: pricer, - nonceLock: multex.New[string](), - nonceMap: make(map[string]accountNonce), - latestBlock: atomic.Pointer[types.Block]{}, + logger: logger, + bidder: bidder, + store: store, + pricer: pricer, + blockTracker: blockTracker, + nonceLock: multex.New[string](), + nonceMap: make(map[string]accountNonce), + latestBlock: atomic.Pointer[types.Block]{}, } } @@ -279,7 +281,7 @@ func (h *rpcMethodHandler) sendBid( timeToOptIn, err := h.bidder.Estimate() if err != nil { h.logger.Error("Failed to estimate time to opt-in", "error", err) - if !errors.Is(err, optinbidder.ErrNoSlotInCurrentEpoch) { + if !errors.Is(err, optinbidder.ErrNoSlotInCurrentEpoch) && !errors.Is(err, optinbidder.ErrNoEpochInfo) { return bidResult{}, err } // If we cannot estimate the time to opt-in, we assume a default value and @@ -318,6 +320,7 @@ func (h *rpcMethodHandler) sendBid( result := bidResult{ commitments: make([]*bidderapiv1.Commitment, 0), + bidAmount: price.BidAmount, } BID_LOOP: for { @@ -413,6 +416,10 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any return nil, true, nil } + if h.latestBlock.Load().Number().Uint64() > uint64(commitments[0].BlockNumber) { + return nil, true, nil + } + receipt := &types.Receipt{ TxHash: txn.Hash(), Type: txn.Type(), diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index e3af2285e..faafdb440 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -156,6 +156,7 @@ func New(config *Config) (*Service, error) { bidderClient, rpcstore, bidpricer, + &dummyBlockTracker{}, ) handlers.RegisterMethods(rpcServer) @@ -168,9 +169,9 @@ func New(config *Config) (*Service, error) { return } // get address and amount from params - address := r.URL.Query().Get("address") + addressStr := r.URL.Query().Get("address") amountStr := r.URL.Query().Get("amount") - if address == "" || amountStr == "" { + if addressStr == "" || amountStr == "" { http.Error(w, "Missing address or amount", http.StatusBadRequest) return } @@ -179,6 +180,7 @@ func New(config *Config) (*Service, error) { http.Error(w, "Invalid amount", http.StatusBadRequest) return } + address := common.HexToAddress(addressStr) err := rpcstore.AddBalance(r.Context(), address, amount) if err != nil { http.Error(w, fmt.Sprintf("Failed to add balance: %v", err), http.StatusInternalServerError) @@ -225,3 +227,14 @@ func (c channelCloser) Close() error { } return nil } + +type dummyBlockTracker struct{} + +func (d *dummyBlockTracker) CheckTxnInclusion( + ctx context.Context, + txHash common.Hash, + blockNumber uint64, +) (bool, error) { + // Dummy implementation, always returns true + return true, nil +} diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index 508cc2b9c..5daaa9977 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -131,6 +131,7 @@ func (s *rpcstore) DeductBalance( amount *big.Int, ) error { if account == (common.Address{}) || amount == nil || amount.Sign() <= 0 { + fmt.Println("invalid account or amount: %s, %s", account.Hex(), amount.String()) return errors.New("invalid account or amount") } From 7c6c11fd24e6b18c3104deaa7ad9e6a3dbc3ce21 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Mon, 16 Jun 2025 11:05:24 +0530 Subject: [PATCH 50/69] feat: preconf RPC --- .../preconf-rpc/blocktracker/blocktracker.go | 107 ++++++++++++++++++ .../blocktracker/blocktracker_test.go | 27 +++++ tools/preconf-rpc/handlers/handlers.go | 8 +- 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 tools/preconf-rpc/blocktracker/blocktracker.go create mode 100644 tools/preconf-rpc/blocktracker/blocktracker_test.go diff --git a/tools/preconf-rpc/blocktracker/blocktracker.go b/tools/preconf-rpc/blocktracker/blocktracker.go new file mode 100644 index 000000000..ed280e35d --- /dev/null +++ b/tools/preconf-rpc/blocktracker/blocktracker.go @@ -0,0 +1,107 @@ +package blocktracker + +import ( + "context" + "log/slog" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type EthClient interface { + BlockNumber(ctx context.Context) (uint64, error) + BlockByNumber(ctx context.Context, blockNumber *big.Int) (*types.Block, error) +} + +type blockTracker struct { + latestBlockNo atomic.Uint64 + blocks map[uint64]*types.Block + client EthClient + log *slog.Logger + checkTrigger chan struct{} +} + +func NewBlockTracker(client EthClient, log *slog.Logger) *blockTracker { + return &blockTracker{ + latestBlockNo: atomic.Uint64{}, + blocks: make(map[uint64]*types.Block), + client: client, + log: log, + checkTrigger: make(chan struct{}, 1), + } +} + +func (b *blockTracker) Start(ctx context.Context) <-chan struct{} { + done := make(chan struct{}) + ticker := time.NewTicker(500 * time.Millisecond) + go func() { + defer close(done) + // Simulate block tracking logic + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + blockNo, err := b.client.BlockNumber(ctx) + if err != nil { + b.log.Error("Failed to get block number", "error", err) + continue + } + if blockNo > b.latestBlockNo.Load() { + block, err := b.client.BlockByNumber(ctx, big.NewInt(int64(blockNo))) + if err != nil { + b.log.Error("Failed to get block by number", "error", err) + continue + } + b.blocks[blockNo] = block + b.latestBlockNo.Store(block.NumberU64()) + b.log.Info("New block detected", "number", block.NumberU64(), "hash", block.Hash().Hex()) + b.checkTrigger <- struct{}{} + } + } + } + }() + return done +} + +func (b *blockTracker) LatestBlockNumber() uint64 { + return b.latestBlockNo.Load() +} + +func (b *blockTracker) CheckTxnInclusion( + ctx context.Context, + txHash common.Hash, + blockNumber uint64, +) (bool, error) { +WaitForBlock: + for { + select { + case <-ctx.Done(): + return false, ctx.Err() + case <-b.checkTrigger: + if blockNumber <= b.latestBlockNo.Load() { + break WaitForBlock + } + } + } + + block, ok := b.blocks[blockNumber] + if !ok { + block, err := b.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber))) + if err != nil { + b.log.Error("Failed to get block by number", "error", err, "blockNumber", blockNumber) + return false, err + } + b.blocks[blockNumber] = block + } + + for _, tx := range block.Transactions() { + if tx.Hash().Cmp(txHash) == 0 { + return true, nil + } + } + return false, nil +} diff --git a/tools/preconf-rpc/blocktracker/blocktracker_test.go b/tools/preconf-rpc/blocktracker/blocktracker_test.go new file mode 100644 index 000000000..0e1f5fe9a --- /dev/null +++ b/tools/preconf-rpc/blocktracker/blocktracker_test.go @@ -0,0 +1,27 @@ +package blocktracker_test + + +func TestBlockTracker(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := &mockEthClient{ + blockNumber: 100, + blocks: map[uint64]*types.Block{ + 100: { + Number: big.NewInt(100), + Hash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + Transactions: []*types.Transaction{}, + }, + 101: { + Number: big.NewInt(101), + Hash: common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"), + Transactions: []*types.Transaction{}, + }, + }, + } + + tracker := &blockTracker{ + diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 6f058dbe0..1cc45029b 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -9,7 +9,6 @@ import ( "log/slog" "math/big" "sync" - "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -71,6 +70,7 @@ type BlockTracker interface { txnHash common.Hash, blockNumber uint64, ) (bool, error) + LatestBlockNumber() uint64 } type accountNonce struct { @@ -94,7 +94,6 @@ type rpcMethodHandler struct { pricer Pricer blockTracker BlockTracker nonceLock *multex.Multex[string] - latestBlock atomic.Pointer[types.Block] nonceMap map[string]accountNonce nonceMapLock sync.RWMutex } @@ -114,7 +113,6 @@ func NewRPCMethodHandler( blockTracker: blockTracker, nonceLock: multex.New[string](), nonceMap: make(map[string]accountNonce), - latestBlock: atomic.Pointer[types.Block]{}, } } @@ -416,7 +414,7 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any return nil, true, nil } - if h.latestBlock.Load().Number().Uint64() > uint64(commitments[0].BlockNumber) { + if h.blockTracker.LatestBlockNumber() > uint64(commitments[0].BlockNumber) { return nil, true, nil } @@ -473,7 +471,7 @@ func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) return nil, true, nil } - if h.latestBlock.Load().Number().Uint64() > uint64(accNonce.Block) { + if h.blockTracker.LatestBlockNumber() > uint64(accNonce.Block) { h.nonceMapLock.Lock() delete(h.nonceMap, account) h.nonceMapLock.Unlock() From af38590199265c14294eca26341c401d81b54ef4 Mon Sep 17 00:00:00 2001 From: Alok Date: Mon, 16 Jun 2025 18:46:52 +0530 Subject: [PATCH 51/69] fix: misc --- .../preconf-rpc/blocktracker/blocktracker.go | 11 +- .../blocktracker/blocktracker_test.go | 138 ++++++++++++++++-- tools/preconf-rpc/handlers/handlers.go | 134 +++++++++++++---- tools/preconf-rpc/service/service.go | 37 +---- tools/preconf-rpc/store/store.go | 24 ++- tools/preconf-rpc/store/store_test.go | 9 ++ 6 files changed, 280 insertions(+), 73 deletions(-) diff --git a/tools/preconf-rpc/blocktracker/blocktracker.go b/tools/preconf-rpc/blocktracker/blocktracker.go index ed280e35d..449f76694 100644 --- a/tools/preconf-rpc/blocktracker/blocktracker.go +++ b/tools/preconf-rpc/blocktracker/blocktracker.go @@ -39,7 +39,6 @@ func (b *blockTracker) Start(ctx context.Context) <-chan struct{} { ticker := time.NewTicker(500 * time.Millisecond) go func() { defer close(done) - // Simulate block tracking logic for { select { case <-ctx.Done(): @@ -59,7 +58,7 @@ func (b *blockTracker) Start(ctx context.Context) <-chan struct{} { b.blocks[blockNo] = block b.latestBlockNo.Store(block.NumberU64()) b.log.Info("New block detected", "number", block.NumberU64(), "hash", block.Hash().Hex()) - b.checkTrigger <- struct{}{} + b.triggerCheck() } } } @@ -67,6 +66,14 @@ func (b *blockTracker) Start(ctx context.Context) <-chan struct{} { return done } +func (b *blockTracker) triggerCheck() { + select { + case b.checkTrigger <- struct{}{}: + default: + // Non-blocking send, if channel is full, we skip + } +} + func (b *blockTracker) LatestBlockNumber() uint64 { return b.latestBlockNo.Load() } diff --git a/tools/preconf-rpc/blocktracker/blocktracker_test.go b/tools/preconf-rpc/blocktracker/blocktracker_test.go index 0e1f5fe9a..9601f4e8b 100644 --- a/tools/preconf-rpc/blocktracker/blocktracker_test.go +++ b/tools/preconf-rpc/blocktracker/blocktracker_test.go @@ -1,27 +1,139 @@ package blocktracker_test +import ( + "context" + "hash" + "log/slog" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/primev/mev-commit/tools/preconf-rpc/blocktracker" + "golang.org/x/crypto/sha3" +) + +type mockEthClient struct { + blockNumber chan uint64 + blocks map[uint64]*types.Block +} + +func (m *mockEthClient) BlockNumber(ctx context.Context) (uint64, error) { + select { + case blockNo := <-m.blockNumber: + return blockNo, nil + case <-ctx.Done(): + return 0, ctx.Err() + } +} + +func (m *mockEthClient) BlockByNumber(ctx context.Context, blockNumber *big.Int) (*types.Block, error) { + block, exists := m.blocks[blockNumber.Uint64()] + if !exists { + return nil, nil // Simulate block not found + } + return block, nil +} + +type testHasher struct { + hasher hash.Hash +} + +// NewHasher returns a new testHasher instance. +func NewHasher() *testHasher { + return &testHasher{hasher: sha3.NewLegacyKeccak256()} +} + +// Reset resets the hash state. +func (h *testHasher) Reset() { + h.hasher.Reset() +} + +// Update updates the hash state with the given key and value. +func (h *testHasher) Update(key, val []byte) error { + h.hasher.Write(key) + h.hasher.Write(val) + return nil +} + +// Hash returns the hash value. +func (h *testHasher) Hash() common.Hash { + return common.BytesToHash(h.hasher.Sum(nil)) +} func TestBlockTracker(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + + tx1 := types.NewTransaction(1, common.HexToAddress("0xabc"), big.NewInt(100), 21000, big.NewInt(1), nil) + tx2 := types.NewTransaction(2, common.HexToAddress("0xdef"), big.NewInt(200), 21000, big.NewInt(1), nil) + tx3 := types.NewTransaction(3, common.HexToAddress("0x123"), big.NewInt(300), 21000, big.NewInt(1), nil) + tx4 := types.NewTransaction(4, common.HexToAddress("0x456"), big.NewInt(400), 21000, big.NewInt(1), nil) + + blk1 := types.NewBlock( + &types.Header{ + Number: big.NewInt(100), + Time: 1622547800, + }, + &types.Body{Transactions: []*types.Transaction{tx1, tx2}}, + nil, // No receipts + NewHasher(), + ) + + blk2 := types.NewBlock( + &types.Header{ + Number: big.NewInt(101), + Time: 1622547900, + }, + &types.Body{Transactions: []*types.Transaction{tx3}}, + nil, // No receipts + NewHasher(), + ) client := &mockEthClient{ - blockNumber: 100, + blockNumber: make(chan uint64, 1), blocks: map[uint64]*types.Block{ - 100: { - Number: big.NewInt(100), - Hash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), - Transactions: []*types.Transaction{}, - }, - 101: { - Number: big.NewInt(101), - Hash: common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"), - Transactions: []*types.Transaction{}, - }, + 100: blk1, + 101: blk2, }, } - tracker := &blockTracker{ + tracker := blocktracker.NewBlockTracker(client, slog.Default()) + done := tracker.Start(ctx) + + blkNo := tracker.LatestBlockNumber() + if blkNo != 0 { + t.Fatalf("Expected latest block number to be 0, got %d", blkNo) + } + + client.blockNumber <- 100 + + included, err := tracker.CheckTxnInclusion(ctx, tx1.Hash(), 100) + if err != nil { + t.Fatalf("Error checking transaction inclusion: %v", err) + } + + if !included { + t.Fatalf("Expected transaction %s to be included in block 100", tx1.Hash().Hex()) + } + + blkNo = tracker.LatestBlockNumber() + if blkNo != 100 { + t.Fatalf("Expected latest block number to be 100, got %d", blkNo) + } + + client.blockNumber <- 101 + + included, err = tracker.CheckTxnInclusion(ctx, tx4.Hash(), 101) + if err != nil { + t.Fatalf("Error checking transaction inclusion: %v", err) + } + + if included { + t.Fatalf("Expected transaction %s not to be included in block 101", tx4.Hash().Hex()) + } + cancel() + <-done // Wait for the tracker to finish +} diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 1cc45029b..ff00441d2 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -11,6 +11,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" "github.com/primev/mev-commit/tools/preconf-rpc/pricer" @@ -52,24 +53,14 @@ type Store interface { ctx context.Context, txnHash common.Hash, ) (*types.Transaction, []*bidderapiv1.Commitment, error) - DeductBalance( - ctx context.Context, - account common.Address, - amount *big.Int, - ) error - HasBalance( - ctx context.Context, - account common.Address, - amount *big.Int, - ) bool + DeductBalance(ctx context.Context, account common.Address, amount *big.Int) error + HasBalance(ctx context.Context, account common.Address, amount *big.Int) bool + GetBalance(ctx context.Context, account common.Address) (*big.Int, error) + AddBalance(ctx context.Context, account common.Address, amount *big.Int) error } type BlockTracker interface { - CheckTxnInclusion( - ctx context.Context, - txnHash common.Hash, - blockNumber uint64, - ) (bool, error) + CheckTxnInclusion(ctx context.Context, txnHash common.Hash, blockNumber uint64) (bool, error) LatestBlockNumber() uint64 } @@ -93,6 +84,7 @@ type rpcMethodHandler struct { store Store pricer Pricer blockTracker BlockTracker + owner common.Address nonceLock *multex.Multex[string] nonceMap map[string]accountNonce nonceMapLock sync.RWMutex @@ -104,6 +96,7 @@ func NewRPCMethodHandler( store Store, pricer Pricer, blockTracker BlockTracker, + owner common.Address, ) *rpcMethodHandler { return &rpcMethodHandler{ logger: logger, @@ -111,6 +104,7 @@ func NewRPCMethodHandler( store: store, pricer: pricer, blockTracker: blockTracker, + owner: owner, nonceLock: multex.New[string](), nonceMap: make(map[string]accountNonce), } @@ -121,6 +115,8 @@ func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) { server.RegisterHandler("eth_getTransactionReceipt", h.handleGetTxReceipt) server.RegisterHandler("eth_getTransactionCount", h.handleGetTxCount) server.RegisterHandler("mevcommit_getTransactionCommitments", h.handleGetTxCommitments) + server.RegisterHandler("mevcommit_getBalance", h.handleMevCommitGetBalance) + server.RegisterHandler("mevcommit_estimateFastBid", h.handleMevCommitEstimateFastBid) } func (h *rpcMethodHandler) handleSendRawTx( @@ -178,6 +174,11 @@ func (h *rpcMethodHandler) handleSendRawTx( h.nonceLock.Lock(sender.Hex()) defer h.nonceLock.Unlock(sender.Hex()) + // This is a txn to add balance to the bidder's account, so we will pay this + // out of the owner's account. We will add the balance to the bidder's + // account and then proceed with the bid process. + depositTxn := txn.To().Cmp(h.owner) == 0 && txn.Value().Cmp(big.NewInt(0)) > 0 + BID_LOOP: for { select { @@ -189,7 +190,7 @@ BID_LOOP: default: } - result, err := h.sendBid(ctx, txn, sender, rawTxHex) + result, err := h.sendBid(ctx, txn, sender, rawTxHex, depositTxn) switch { case err != nil: h.logger.Error("Failed to send bid", "error", err) @@ -209,6 +210,7 @@ BID_LOOP: sender, int64(result.blockNumber), result.bidAmount, + depositTxn, ); err != nil { return nil, false, rpcserver.NewJSONErr( rpcserver.CodeCustomError, @@ -246,6 +248,7 @@ BID_LOOP: sender, int64(result.blockNumber), result.bidAmount, + depositTxn, ); err != nil { h.logger.Error("Failed to update preconfirmed transaction and deduct balance", "error", err) return nil, false, rpcserver.NewJSONErr( @@ -275,6 +278,7 @@ func (h *rpcMethodHandler) sendBid( txn *types.Transaction, sender common.Address, rawTxHex string, + depositTxn bool, ) (bidResult, error) { timeToOptIn, err := h.bidder.Estimate() if err != nil { @@ -296,7 +300,7 @@ func (h *rpcMethodHandler) sendBid( return bidResult{}, fmt.Errorf("failed to estimate transaction price: %w", err) } - if !h.store.HasBalance(ctx, sender, price.BidAmount) { + if !depositTxn && !h.store.HasBalance(ctx, sender, price.BidAmount) { h.logger.Error("Insufficient balance for sender", "sender", sender.Hex()) return bidResult{}, fmt.Errorf("insufficient balance for sender: %s", sender.Hex()) } @@ -370,15 +374,23 @@ func (h *rpcMethodHandler) storePreconfAndDeductBalance( sender common.Address, blockNumber int64, amount *big.Int, + depositTxn bool, ) error { if err := h.store.StorePreconfirmedTransaction(ctx, blockNumber, txn, commitments); err != nil { h.logger.Error("Failed to store preconfirmed transaction", "error", err) return fmt.Errorf("failed to store preconfirmed transaction: %w", err) } - if err := h.store.DeductBalance(ctx, sender, amount); err != nil { - h.logger.Error("Failed to deduct balance for sender", "sender", sender.Hex(), "error", err) - return fmt.Errorf("failed to deduct balance for sender: %w", err) + if !depositTxn { + if err := h.store.DeductBalance(ctx, sender, amount); err != nil { + h.logger.Error("Failed to deduct balance for sender", "sender", sender.Hex(), "error", err) + return fmt.Errorf("failed to deduct balance for sender: %w", err) + } + } else { + if err := h.store.AddBalance(ctx, sender, txn.Value()); err != nil { + h.logger.Error("Failed to add balance for sender", "sender", sender.Hex(), "error", err) + return fmt.Errorf("failed to add balance for sender: %w", err) + } } return nil @@ -418,14 +430,33 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any return nil, true, nil } - receipt := &types.Receipt{ - TxHash: txn.Hash(), - Type: txn.Type(), - Status: types.ReceiptStatusSuccessful, // Assuming success, as this is a preconfirmed transaction - BlockNumber: big.NewInt(commitments[0].BlockNumber), + sender, err := types.Sender(types.LatestSignerForChainID(txn.ChainId()), txn) + if err != nil { + h.logger.Error("Failed to get transaction sender", "error", err, "txHash", txHash) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to get transaction sender", + ) } - receiptJSON, err := json.Marshal(receipt) + result := map[string]interface{}{ + "type": hexutil.Uint(txn.Type()), + "transactionHash": txn.Hash().Hex(), + "transactionIndex": hexutil.Uint(0), + "blockHash": (common.Hash{}).Hex(), + "blockNumber": hexutil.EncodeBig(big.NewInt(commitments[0].BlockNumber)), + "from": sender.Hex(), + "to": nil, + "contractAddress": (common.Address{}).Hex(), + "gasUsed": hexutil.Uint64(0), + "cumulativeGasUsed": hexutil.Uint64(1), + "logs": []*types.Log{}, // should be [] not null + "logsBloom": hexutil.Bytes(types.Bloom{}.Bytes()), + "status": hexutil.Uint64(types.ReceiptStatusSuccessful), + "effectiveGasPrice": hexutil.EncodeBig(big.NewInt(0)), + } + + receiptJSON, err := json.Marshal(result) if err != nil { h.logger.Error("Failed to marshal receipt to JSON", "error", err, "txHash", txHash) return nil, false, rpcserver.NewJSONErr( @@ -540,3 +571,54 @@ func (h *rpcMethodHandler) handleGetTxCommitments( return commitmentsJSON, false, nil } + +func (h *rpcMethodHandler) handleMevCommitGetBalance(ctx context.Context, params ...any) (json.RawMessage, bool, error) { + if len(params) != 1 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "mevcommit_getBalance requires exactly one parameter", + ) + } + + if params[0] == nil { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "mevcommit_getBalance parameters cannot be null", + ) + } + + account := params[0].(string) + if len(account) < 2 || account[:2] != "0x" { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "mevcommit_getBalance account must be a hex string starting with '0x'", + ) + } + + balance, err := h.store.GetBalance(ctx, common.HexToAddress(account)) + if err != nil { + h.logger.Error("Failed to get balance for account", "error", err, "account", account) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to get balance for account", + ) + } + + return json.RawMessage(fmt.Sprintf(`{"balance": "%s"}`, balance)), false, nil +} + +func (h *rpcMethodHandler) handleMevCommitEstimateFastBid( + ctx context.Context, + _ ...any, +) (json.RawMessage, bool, error) { + timeToOptIn, err := h.bidder.Estimate() + if err != nil { + h.logger.Error("Failed to estimate fast bid", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to estimate fast bid", + ) + } + + return json.RawMessage(fmt.Sprintf(`{"timeInSecs": "%d"}`, timeToOptIn)), false, nil +} diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index faafdb440..5d60b74de 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -157,42 +157,14 @@ func New(config *Config) (*Service, error) { rpcstore, bidpricer, &dummyBlockTracker{}, + config.Signer.GetAddress(), ) handlers.RegisterMethods(rpcServer) - mux := http.NewServeMux() - mux.Handle("/", rpcServer) - mux.HandleFunc("/add_balance", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - // get address and amount from params - addressStr := r.URL.Query().Get("address") - amountStr := r.URL.Query().Get("amount") - if addressStr == "" || amountStr == "" { - http.Error(w, "Missing address or amount", http.StatusBadRequest) - return - } - amount, ok := new(big.Int).SetString(amountStr, 10) - if !ok || amount.Sign() <= 0 { - http.Error(w, "Invalid amount", http.StatusBadRequest) - return - } - address := common.HexToAddress(addressStr) - err := rpcstore.AddBalance(r.Context(), address, amount) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to add balance: %v", err), http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "Balance added successfully for address %s", address) - }) - srv := http.Server{ Addr: fmt.Sprintf(":%d", config.HTTPPort), - Handler: mux, + Handler: rpcServer, } go func() { @@ -238,3 +210,8 @@ func (d *dummyBlockTracker) CheckTxnInclusion( // Dummy implementation, always returns true return true, nil } + +func (d *dummyBlockTracker) LatestBlockNumber() uint64 { + // Dummy implementation, always returns 0 + return 0 +} diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index 5daaa9977..bf8749b9c 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -131,7 +131,6 @@ func (s *rpcstore) DeductBalance( amount *big.Int, ) error { if account == (common.Address{}) || amount == nil || amount.Sign() <= 0 { - fmt.Println("invalid account or amount: %s, %s", account.Hex(), amount.String()) return errors.New("invalid account or amount") } @@ -170,7 +169,8 @@ func (s *rpcstore) AddBalance( if err != nil { if errors.Is(err, pebble.ErrNotFound) { // If the account does not exist, we create a new one with the initial balance - currentBalance = []byte("0") // Default balance for a new account + bal := new(big.Int) + currentBalance = bal.Bytes() // Default balance for a new account } else { return fmt.Errorf("failed to get balance for account %s: %w", account, err) } @@ -213,3 +213,23 @@ func (s *rpcstore) HasBalance( return currentBalanceBig.Cmp(amount) >= 0 } + +func (s *rpcstore) GetBalance( + ctx context.Context, + account common.Address, +) (*big.Int, error) { + if account == (common.Address{}) { + return nil, errors.New("account cannot be empty") + } + + balanceKey := []byte(fmt.Sprintf("balance:%s", account.Hex())) + currentBalance, closer, err := s.db.Get(balanceKey) + if err != nil { + return nil, err + } + defer func() { + _ = closer.Close() + }() + + return new(big.Int).SetBytes(currentBalance), nil +} diff --git a/tools/preconf-rpc/store/store_test.go b/tools/preconf-rpc/store/store_test.go index 4c16af0f4..105d30234 100644 --- a/tools/preconf-rpc/store/store_test.go +++ b/tools/preconf-rpc/store/store_test.go @@ -99,6 +99,15 @@ func TestStore(t *testing.T) { t.Errorf("expected balance %s, but has no balance", initialBalance.String()) } + // Check if the balance is correctly stored + balance, err := st.GetBalance(context.Background(), address) + if err != nil { + t.Errorf("failed to get balance: %v", err) + } + if balance.Cmp(initialBalance) != 0 { + t.Errorf("expected balance %s, got %s", initialBalance.String(), balance.String()) + } + err = st.DeductBalance(context.Background(), address, initialBalance) if err != nil { t.Errorf("failed to deduct balance: %v", err) From fc714aea9b9a858a1e05d54585cdf97356657ff5 Mon Sep 17 00:00:00 2001 From: Alok Date: Mon, 16 Jun 2025 18:54:14 +0530 Subject: [PATCH 52/69] fix: tidy --- tools/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/go.mod b/tools/go.mod index 12f27477a..ebe3e9044 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -93,7 +93,7 @@ require ( github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/crypto v0.35.0 // indirect + golang.org/x/crypto v0.35.0 golang.org/x/net v0.36.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect From 8ab4e389f88ecf7bc269a2d6e93edf0b8d564634 Mon Sep 17 00:00:00 2001 From: Alok Date: Mon, 16 Jun 2025 19:11:36 +0530 Subject: [PATCH 53/69] fix: debug contract deployer --- .../nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 index c940a969b..599a3625a 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 @@ -127,6 +127,8 @@ job "{{ job.name }}" { data = <<-EOH #!/usr/bin/env bash + set -x + {%- raw %} {{- range nomadService "datadog-agent-logs-collector" }} {{ if contains "tcp" .Tags }} From 423583d7b04d9756328607d4a671f78e49bf85b1 Mon Sep 17 00:00:00 2001 From: Alok Date: Mon, 16 Jun 2025 19:12:01 +0530 Subject: [PATCH 54/69] fix: debug contract deployer --- infrastructure/nomad/playbooks/variables/profiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/nomad/playbooks/variables/profiles.yml b/infrastructure/nomad/playbooks/variables/profiles.yml index 197aaf4f0..8547890c7 100644 --- a/infrastructure/nomad/playbooks/variables/profiles.yml +++ b/infrastructure/nomad/playbooks/variables/profiles.yml @@ -969,7 +969,7 @@ profiles: - *artifacts_job - *datadog_agent_logs_collector_job - *otel_collector_job - # - *beacon_emulator_job + - *beacon_emulator_job - *mock_l1_job - *mev_commit_geth_bootnode1_job - *mev_commit_geth_signer_node1_job From 9c04c5c06fce9b99ce639b83f61bdd3728cf812e Mon Sep 17 00:00:00 2001 From: Alok Date: Mon, 16 Jun 2025 19:37:14 +0530 Subject: [PATCH 55/69] fix: debug contract deployer --- .../validator-registry/DeployForMockL1.s.sol | 17 ++++------------- .../templates/jobs/mev-commit.nomad.j2 | 2 +- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/contracts/scripts/validator-registry/DeployForMockL1.s.sol b/contracts/scripts/validator-registry/DeployForMockL1.s.sol index d997031ec..706238ea3 100644 --- a/contracts/scripts/validator-registry/DeployForMockL1.s.sol +++ b/contracts/scripts/validator-registry/DeployForMockL1.s.sol @@ -34,7 +34,6 @@ contract DeployForMockL1 is Script { uint256 payoutPeriodBlocks = 200; address owner = msg.sender; - console.log("Deploying VanillaRegistry..."); address vanillaRegistryProxy = Upgrades.deployUUPSProxy( "VanillaRegistry.sol", abi.encodeCall( @@ -42,20 +41,14 @@ contract DeployForMockL1 is Script { (minStake, slashOracle, slashReceiver, unstakePeriodBlocks, payoutPeriodBlocks, owner) ) ); - console.log("VanillaRegistry deployed at:", vanillaRegistryProxy); + console.log("_VanillaRegistry:", vanillaRegistryProxy); VanillaRegistry vanillaRegistry = VanillaRegistry(payable(vanillaRegistryProxy)); - address[] memory stakers = new address[](1); - stakers[0] = owner; - vanillaRegistry.whitelistStakers(stakers); - - console.log("Deploying mock AVS and Middleware contracts..."); AlwaysFalseAVS mockAVS = new AlwaysFalseAVS(); AlwaysFalseMiddleware mockMiddleware = new AlwaysFalseMiddleware(); - console.log("Mock AVS deployed at:", address(mockAVS)); - console.log("Mock Middleware deployed at:", address(mockMiddleware)); + console.log("_MockAVS:", address(mockAVS)); + console.log("_MockMiddleware:", address(mockMiddleware)); - console.log("Deploying ValidatorOptInRouter..."); address routerProxy = Upgrades.deployUUPSProxy( "ValidatorOptInRouter.sol", abi.encodeCall( @@ -63,7 +56,7 @@ contract DeployForMockL1 is Script { (vanillaRegistryProxy, address(mockAVS), address(mockMiddleware), msg.sender) ) ); - console.log("ValidatorOptInRouter deployed at:", routerProxy); + console.log("ValidatorOptInRouter:", routerProxy); uint256 batchSize = 5; uint256 numKeys = 32; @@ -104,7 +97,6 @@ contract DeployForMockL1 is Script { pubkeysToRegister[31] = hex"ababbfe729893e69384ef1f32c7fa15902be6ace12aeaa21c56be726bc8c71e4e9b884735b82dbc619315752cffdb73e"; uint256 totalKeys = pubkeysToRegister.length; - console.log("Registering", totalKeys, "validators in batches of", batchSize); for (uint256 i = 0; i < totalKeys; i += batchSize) { uint256 currentBatchSize = batchSize; @@ -118,7 +110,6 @@ contract DeployForMockL1 is Script { uint256 batchStake = minStake * currentBatchSize; vanillaRegistry.stake{value: batchStake}(batchKeys); } - console.log("Successfully registered all validators"); vm.stopBroadcast(); } } diff --git a/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 index 3f2014235..17fdb32c5 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 @@ -174,7 +174,7 @@ job "{{ job.name }}" { export MEV_COMMIT_ORACLE_ADDR="$(jq -r '.Oracle' ${CONTRACTS_FILE})" {{- range nomadService "beacon-emulator" }} {{ if contains "http" .Tags }} - export MEV_COMMIT_VALIDATOR_ROUTER_ADDR="0x251Fbc993f58cBfDA8Ad7b0278084F915aCE7fc3" + export MEV_COMMIT_VALIDATOR_ROUTER_ADDR="$(jq -r '.ValidatorOptInRouter' ${CONTRACTS_FILE})" {{ end }} {{- end }} {% endraw %} From 036277e3431045849e5c631a362553dd03d06fc0 Mon Sep 17 00:00:00 2001 From: Alok Date: Tue, 17 Jun 2025 21:52:55 +0530 Subject: [PATCH 56/69] fix: restructure docker files --- .../docker/Dockerfile.bidderemulator | 22 ++----- infrastructure/docker/Dockerfile.bridge | 20 +------ infrastructure/docker/Dockerfile.builder | 58 +++++++++++++++++++ infrastructure/docker/Dockerfile.dashboard | 20 +------ infrastructure/docker/Dockerfile.l1transactor | 24 +------- infrastructure/docker/Dockerfile.oracle | 20 +------ infrastructure/docker/Dockerfile.p2p | 20 +------ .../docker/Dockerfile.provideremulator | 22 ++----- .../docker/Dockerfile.relayemulator | 24 +------- infrastructure/docker/Dockerfile.rpc | 7 +++ tools/preconf-rpc/service/service.go | 24 +++----- 11 files changed, 98 insertions(+), 163 deletions(-) create mode 100644 infrastructure/docker/Dockerfile.builder create mode 100644 infrastructure/docker/Dockerfile.rpc diff --git a/infrastructure/docker/Dockerfile.bidderemulator b/infrastructure/docker/Dockerfile.bidderemulator index e01396237..c676f1b22 100644 --- a/infrastructure/docker/Dockerfile.bidderemulator +++ b/infrastructure/docker/Dockerfile.bidderemulator @@ -1,22 +1,8 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY p2p/go.mod p2p/go.sum /app/p2p/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ - -RUN cd /app/p2p && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download - -COPY . . - -RUN go build -o /app/bidder-emulator ./p2p/integrationtest/real-bidder - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/bidder-emulator /usr/local/bin/bidder-emulator -COPY --from=builder /app/p2p/integrationtest/real-bidder/entrypoint.sh entrypoint.sh +COPY --from=builder /go/bin/real-bidder /usr/local/bin/bidder-emulator +COPY --from=builder /scripts/bidder-emulator-entrypoint.sh entrypoint.sh ENTRYPOINT ["./entrypoint.sh"] diff --git a/infrastructure/docker/Dockerfile.bridge b/infrastructure/docker/Dockerfile.bridge index 1b35a52ac..38e9ae29d 100644 --- a/infrastructure/docker/Dockerfile.bridge +++ b/infrastructure/docker/Dockerfile.bridge @@ -1,21 +1,7 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY bridge/standard/go.mod bridge/standard/go.sum /app/bridge/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ - -RUN cd /app/bridge && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download - -COPY . . - -RUN go build -o /app/mev-commit-bridge ./bridge/cmd - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/mev-commit-bridge /usr/local/bin/mev-commit-bridge +COPY --from=builder /go/bin/relayer /usr/local/bin/mev-commit-bridge ENTRYPOINT ["mev-commit-bridge"] diff --git a/infrastructure/docker/Dockerfile.builder b/infrastructure/docker/Dockerfile.builder new file mode 100644 index 000000000..8dc0ff263 --- /dev/null +++ b/infrastructure/docker/Dockerfile.builder @@ -0,0 +1,58 @@ +FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS build + +WORKDIR /ws + +COPY go.work go.work.sum ./ + +COPY contracts-abi/go.mod contracts-abi/go.sum ./contracts-abi/ +COPY p2p/go.mod p2p/go.sum ./p2p/ +COPY oracle/go.mod oracle/go.sum ./oracle/ +COPY testing/go.mod testing/go.sum ./testing/ +COPY tools/go.mod tools/go.sum ./tools/ +COPY x/go.mod x/go.sum ./x/ +COPY bridge/standard/go.mod bridge/standard/go.sum ./bridge/standard/ +COPY cl/go.mod cl/go.sum ./cl/ +COPY infrastructure/tools/keystore-generator/go.mod infrastructure/tools/keystore-generator/go.sum ./infrastructure/tools/keystore-generator/ + +COPY p2p/integrationtest/real-bidder/entrypoint.sh ./scripts/bidder-emulator-entrypoint.sh +COPY p2p/integrationtest/provider/entrypoint.sh ./scripts/provider-emulator-entrypoint.sh + +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/go/pkg/mod \ + go work sync && \ + go mod download all + +COPY . . + +ARG TARGETS="./oracle/cmd \ + ./p2p/cmd \ + ./bridge/standard/cmd/relayer \ + ./bridge/standard/cmd/emulator \ + ./infrastructure/tools/keystore-generator \ + ./testing/cmd \ + ./tools/preconf-rpc \ + ./tools/beacon-emulator \ + ./tools/dashboard \ + ./tools/bidder-cli \ + ./tools/bls-signer \ + ./tools/l1-transaction-emulator \ + ./tools/relay-emulator \ + ./tools/validators-monitor \ + ./tools/points-service \ + ./p2p/integrationtest/real-bidder \ + ./p2p/integrationtest/provider" + +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/go/pkg/mod \ + set -e; \ + for path in ${TARGETS}; do \ + # If the last element is literally "cmd", use the directory above it + bn=$(basename "$path"); \ + if [ "$bn" = "cmd" ]; then \ + name=$(basename "$(dirname "$path")"); \ + else \ + name=$bn; \ + fi; \ + echo "→ building $path as /go/bin/$name"; \ + CGO_ENABLED=0 go build -o "/go/bin/$name" "$path"; \ + done diff --git a/infrastructure/docker/Dockerfile.dashboard b/infrastructure/docker/Dockerfile.dashboard index 7000cc4ce..e21026d1e 100644 --- a/infrastructure/docker/Dockerfile.dashboard +++ b/infrastructure/docker/Dockerfile.dashboard @@ -1,21 +1,7 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY tools/go.mod tools/go.sum /app/dashboard/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ - -RUN cd /app/tools && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download - -COPY . . - -RUN go build -o /app/mev-commit-dashboard ./tools/dashboard - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/mev-commit-dashboard /usr/local/bin/mev-commit-dashboard +COPY --from=builder /go/bin/dashboard /usr/local/bin/mev-commit-dashboard ENTRYPOINT ["mev-commit-dashboard"] diff --git a/infrastructure/docker/Dockerfile.l1transactor b/infrastructure/docker/Dockerfile.l1transactor index 6097474af..4eec2fdcc 100644 --- a/infrastructure/docker/Dockerfile.l1transactor +++ b/infrastructure/docker/Dockerfile.l1transactor @@ -1,25 +1,7 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY tools/go.mod tools/go.sum /app/tools/ -COPY p2p/go.mod p2p/go.sum /app/p2p/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ -COPY bridge/standard/go.mod bridge/standard/go.sum /app/bridge/standard/ - -RUN cd /app/tools && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download -RUN cd /app/p2p && go mod download -RUN cd /app/bridge/standard && go mod download - -COPY . . - -RUN go build -o /app/l1-transactor ./tools/l1-transaction-emulator - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/l1-transactor /usr/local/bin/l1-transactor +COPY --from=builder /go/bin/l1-transaction-emulator /usr/local/bin/l1-transactor ENTRYPOINT ["l1-transactor"] diff --git a/infrastructure/docker/Dockerfile.oracle b/infrastructure/docker/Dockerfile.oracle index 981818269..edddb7afd 100644 --- a/infrastructure/docker/Dockerfile.oracle +++ b/infrastructure/docker/Dockerfile.oracle @@ -1,21 +1,7 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY oracle/go.mod oracle/go.sum /app/oracle/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ - -RUN cd /app/oracle && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download - -COPY . . - -RUN go build -o /app/mev-commit-oracle ./oracle/cmd - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/mev-commit-oracle /usr/local/bin/mev-commit-oracle +COPY --from=builder /go/bin/oracle /usr/local/bin/mev-commit-oracle ENTRYPOINT ["mev-commit-oracle", "start"] diff --git a/infrastructure/docker/Dockerfile.p2p b/infrastructure/docker/Dockerfile.p2p index c57d9eea0..549bf2743 100644 --- a/infrastructure/docker/Dockerfile.p2p +++ b/infrastructure/docker/Dockerfile.p2p @@ -1,21 +1,7 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY p2p/go.mod p2p/go.sum /app/p2p/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ - -RUN cd /app/p2p && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download - -COPY . . - -RUN go build -o /app/mev-commit ./p2p/cmd - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/mev-commit /usr/local/bin/mev-commit +COPY --from=builder /go/bin/p2p /usr/local/bin/mev-commit ENTRYPOINT ["mev-commit"] diff --git a/infrastructure/docker/Dockerfile.provideremulator b/infrastructure/docker/Dockerfile.provideremulator index 5ff3e86f7..59d0e5d25 100644 --- a/infrastructure/docker/Dockerfile.provideremulator +++ b/infrastructure/docker/Dockerfile.provideremulator @@ -1,22 +1,8 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY p2p/go.mod p2p/go.sum /app/p2p/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ - -RUN cd /app/p2p && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download - -COPY . . - -RUN go build -o /app/provider-emulator ./p2p/integrationtest/provider - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/provider-emulator /usr/local/bin/provider-emulator -COPY --from=builder /app/p2p/integrationtest/provider/entrypoint.sh entrypoint.sh +COPY --from=builder /go/bin/provider /usr/local/bin/provider-emulator +COPY --from=builder /scripts/provider-emulator-entrypoint.sh entrypoint.sh ENTRYPOINT ["./entrypoint.sh"] diff --git a/infrastructure/docker/Dockerfile.relayemulator b/infrastructure/docker/Dockerfile.relayemulator index 4f096e6bd..5c86c656a 100644 --- a/infrastructure/docker/Dockerfile.relayemulator +++ b/infrastructure/docker/Dockerfile.relayemulator @@ -1,25 +1,7 @@ -FROM golang:1.23.0-alpine AS builder - -WORKDIR /app - -COPY tools/go.mod tools/go.sum /app/tools/ -COPY p2p/go.mod p2p/go.sum /app/p2p/ -COPY x/go.mod x/go.sum /app/x/ -COPY contracts-abi/go.mod contracts-abi/go.sum /app/contracts-abi/ -COPY bridge/standard/go.mod bridge/standard/go.sum /app/bridge/standard/ - -RUN cd /app/tools && go mod download -RUN cd /app/x && go mod download -RUN cd /app/contracts-abi && go mod download -RUN cd /app/bridge/standard && go mod download -RUN cd /app/p2p && go mod download - -COPY . . - -RUN go build -o /app/relay-emulator ./tools/relay-emulator - +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder FROM alpine:3.10 -COPY --from=builder /app/relay-emulator /usr/local/bin/relay-emulator +COPY --from=builder /go/bin/relay-emulator /usr/local/bin/relay-emulator ENTRYPOINT ["relay-emulator"] diff --git a/infrastructure/docker/Dockerfile.rpc b/infrastructure/docker/Dockerfile.rpc new file mode 100644 index 000000000..8fae967c0 --- /dev/null +++ b/infrastructure/docker/Dockerfile.rpc @@ -0,0 +1,7 @@ +ARG MEV_COMMIT_BUILDER_VERSION=latest +FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +FROM alpine:3.10 + +COPY --from=builder /go/bin/preconf-rpc /usr/local/bin/preconf-rpc + +ENTRYPOINT ["preconf-rpc"] diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index 5d60b74de..792ee489e 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -16,6 +16,7 @@ import ( bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" debugapiv1 "github.com/primev/mev-commit/p2p/gen/go/debugapi/v1" notificationsapiv1 "github.com/primev/mev-commit/p2p/gen/go/notificationsapi/v1" + "github.com/primev/mev-commit/tools/preconf-rpc/blocktracker" "github.com/primev/mev-commit/tools/preconf-rpc/handlers" "github.com/primev/mev-commit/tools/preconf-rpc/pricer" "github.com/primev/mev-commit/tools/preconf-rpc/rpcserver" @@ -151,12 +152,17 @@ func New(config *Config) (*Service, error) { return nil, fmt.Errorf("failed to create store: %w", err) } + blockTracker := blocktracker.NewBlockTracker( + l1RPCClient, + config.Logger.With("module", "blocktracker"), + ) + handlers := handlers.NewRPCMethodHandler( config.Logger.With("module", "handlers"), bidderClient, rpcstore, bidpricer, - &dummyBlockTracker{}, + blockTracker, config.Signer.GetAddress(), ) @@ -199,19 +205,3 @@ func (c channelCloser) Close() error { } return nil } - -type dummyBlockTracker struct{} - -func (d *dummyBlockTracker) CheckTxnInclusion( - ctx context.Context, - txHash common.Hash, - blockNumber uint64, -) (bool, error) { - // Dummy implementation, always returns true - return true, nil -} - -func (d *dummyBlockTracker) LatestBlockNumber() uint64 { - // Dummy implementation, always returns 0 - return 0 -} From d9945d18ad104ca3eaa6ea91494f6f4e327a742e Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Wed, 18 Jun 2025 02:25:08 +0530 Subject: [PATCH 57/69] feat: preconf RPC --- .../docker/Dockerfile.bidderemulator | 9 +- infrastructure/docker/Dockerfile.bridge | 7 +- infrastructure/docker/Dockerfile.builder | 4 +- infrastructure/docker/Dockerfile.dashboard | 7 +- infrastructure/docker/Dockerfile.l1transactor | 5 +- infrastructure/docker/Dockerfile.oracle | 7 +- infrastructure/docker/Dockerfile.p2p | 7 +- .../docker/Dockerfile.provideremulator | 9 +- .../docker/Dockerfile.relayemulator | 7 +- infrastructure/docker/Dockerfile.rpc | 7 +- infrastructure/docker/docker-bake.hcl | 92 +++++++++++++++++++ 11 files changed, 130 insertions(+), 31 deletions(-) create mode 100644 infrastructure/docker/docker-bake.hcl diff --git a/infrastructure/docker/Dockerfile.bidderemulator b/infrastructure/docker/Dockerfile.bidderemulator index c676f1b22..1a8d698f9 100644 --- a/infrastructure/docker/Dockerfile.bidderemulator +++ b/infrastructure/docker/Dockerfile.bidderemulator @@ -1,8 +1,9 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/real-bidder /usr/local/bin/bidder-emulator -COPY --from=builder /scripts/bidder-emulator-entrypoint.sh entrypoint.sh +COPY --from=builder_ctx /go/bin/real-bidder /usr/local/bin/bidder-emulator +COPY --from=builder_ctx /scripts/bidder-emulator-entrypoint.sh entrypoint.sh + +EXPOSE 8080 ENTRYPOINT ["./entrypoint.sh"] diff --git a/infrastructure/docker/Dockerfile.bridge b/infrastructure/docker/Dockerfile.bridge index 38e9ae29d..77dfabfc4 100644 --- a/infrastructure/docker/Dockerfile.bridge +++ b/infrastructure/docker/Dockerfile.bridge @@ -1,7 +1,8 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/relayer /usr/local/bin/mev-commit-bridge +COPY --from=builder_ctx /go/bin/relayer /usr/local/bin/mev-commit-bridge + +EXPOSE 8080 ENTRYPOINT ["mev-commit-bridge"] diff --git a/infrastructure/docker/Dockerfile.builder b/infrastructure/docker/Dockerfile.builder index 8dc0ff263..d76f6c8a8 100644 --- a/infrastructure/docker/Dockerfile.builder +++ b/infrastructure/docker/Dockerfile.builder @@ -14,8 +14,8 @@ COPY bridge/standard/go.mod bridge/standard/go.sum ./bridge/standard/ COPY cl/go.mod cl/go.sum ./cl/ COPY infrastructure/tools/keystore-generator/go.mod infrastructure/tools/keystore-generator/go.sum ./infrastructure/tools/keystore-generator/ -COPY p2p/integrationtest/real-bidder/entrypoint.sh ./scripts/bidder-emulator-entrypoint.sh -COPY p2p/integrationtest/provider/entrypoint.sh ./scripts/provider-emulator-entrypoint.sh +COPY p2p/integrationtest/real-bidder/entrypoint.sh /scripts/bidder-emulator-entrypoint.sh +COPY p2p/integrationtest/provider/entrypoint.sh /scripts/provider-emulator-entrypoint.sh RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ diff --git a/infrastructure/docker/Dockerfile.dashboard b/infrastructure/docker/Dockerfile.dashboard index e21026d1e..57d3948ab 100644 --- a/infrastructure/docker/Dockerfile.dashboard +++ b/infrastructure/docker/Dockerfile.dashboard @@ -1,7 +1,8 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/dashboard /usr/local/bin/mev-commit-dashboard +COPY --from=builder_ctx /go/bin/dashboard /usr/local/bin/mev-commit-dashboard + +EXPOSE 8080 ENTRYPOINT ["mev-commit-dashboard"] diff --git a/infrastructure/docker/Dockerfile.l1transactor b/infrastructure/docker/Dockerfile.l1transactor index 4eec2fdcc..664e6c930 100644 --- a/infrastructure/docker/Dockerfile.l1transactor +++ b/infrastructure/docker/Dockerfile.l1transactor @@ -1,7 +1,6 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/l1-transaction-emulator /usr/local/bin/l1-transactor +COPY --from=builder_ctx /go/bin/l1-transaction-emulator /usr/local/bin/l1-transactor ENTRYPOINT ["l1-transactor"] diff --git a/infrastructure/docker/Dockerfile.oracle b/infrastructure/docker/Dockerfile.oracle index edddb7afd..599049b64 100644 --- a/infrastructure/docker/Dockerfile.oracle +++ b/infrastructure/docker/Dockerfile.oracle @@ -1,7 +1,8 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/oracle /usr/local/bin/mev-commit-oracle +COPY --from=builder_ctx /go/bin/oracle /usr/local/bin/mev-commit-oracle + +EXPOSE 8080 ENTRYPOINT ["mev-commit-oracle", "start"] diff --git a/infrastructure/docker/Dockerfile.p2p b/infrastructure/docker/Dockerfile.p2p index 549bf2743..dcabb97d8 100644 --- a/infrastructure/docker/Dockerfile.p2p +++ b/infrastructure/docker/Dockerfile.p2p @@ -1,7 +1,8 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/p2p /usr/local/bin/mev-commit +COPY --from=builder_ctx /go/bin/p2p /usr/local/bin/mev-commit + +EXPOSE 13522 13523 13524 ENTRYPOINT ["mev-commit"] diff --git a/infrastructure/docker/Dockerfile.provideremulator b/infrastructure/docker/Dockerfile.provideremulator index 59d0e5d25..ffc43fa90 100644 --- a/infrastructure/docker/Dockerfile.provideremulator +++ b/infrastructure/docker/Dockerfile.provideremulator @@ -1,8 +1,9 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/provider /usr/local/bin/provider-emulator -COPY --from=builder /scripts/provider-emulator-entrypoint.sh entrypoint.sh +COPY --from=builder_ctx /go/bin/provider /usr/local/bin/provider-emulator +COPY --from=builder_ctx /scripts/provider-emulator-entrypoint.sh entrypoint.sh + +EXPOSE 8080 ENTRYPOINT ["./entrypoint.sh"] diff --git a/infrastructure/docker/Dockerfile.relayemulator b/infrastructure/docker/Dockerfile.relayemulator index 5c86c656a..2d10f69fa 100644 --- a/infrastructure/docker/Dockerfile.relayemulator +++ b/infrastructure/docker/Dockerfile.relayemulator @@ -1,7 +1,8 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/relay-emulator /usr/local/bin/relay-emulator +COPY --from=builder_ctx /go/bin/relay-emulator /usr/local/bin/relay-emulator + +EXPOSE 8080 ENTRYPOINT ["relay-emulator"] diff --git a/infrastructure/docker/Dockerfile.rpc b/infrastructure/docker/Dockerfile.rpc index 8fae967c0..6e4dcb8a6 100644 --- a/infrastructure/docker/Dockerfile.rpc +++ b/infrastructure/docker/Dockerfile.rpc @@ -1,7 +1,8 @@ -ARG MEV_COMMIT_BUILDER_VERSION=latest -FROM mev-commit-builder:{$MEV_COMMIT_BUILDER_VERSION} AS builder +# syntax=docker/dockerfile:1.4 FROM alpine:3.10 -COPY --from=builder /go/bin/preconf-rpc /usr/local/bin/preconf-rpc +COPY --from=builder_ctx /go/bin/preconf-rpc /usr/local/bin/preconf-rpc + +EXPOSE 8080 ENTRYPOINT ["preconf-rpc"] diff --git a/infrastructure/docker/docker-bake.hcl b/infrastructure/docker/docker-bake.hcl new file mode 100644 index 000000000..e8e87d93a --- /dev/null +++ b/infrastructure/docker/docker-bake.hcl @@ -0,0 +1,92 @@ +variable "TAG" { default = "dev" } + +target "mev-commit-builder" { + context = "../../" + dockerfile = "infrastructure/docker/Dockerfile.builder" +} + +target "mev-commit-oracle" { + context = "./" + dockerfile = "Dockerfile.oracle" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/mev-commit-oracle:${TAG}"] +} + +target "mev-commit" { + context = "./" + dockerfile = "Dockerfile.p2p" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/mev-commit:${TAG}"] +} + +target "mev-commit-bridge" { + context = "./" + dockerfile = "Dockerfile.bridge" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/mev-commit-bridge:${TAG}"] +} + +target "mev-commit-dashboard" { + context = "./" + dockerfile = "Dockerfile.dashboard" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/mev-commit-dashboard:${TAG}"] +} + +target "preconf-rpc" { + context = "./" + dockerfile = "Dockerfile.rpc" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/preconf-rpc:${TAG}"] +} + +target "bidder-emulator" { + context = "./" + dockerfile = "Dockerfile.bidderemulator" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/bidder-emulator:${TAG}"] +} + +target "provider-emulator" { + context = "./" + dockerfile = "Dockerfile.provideremulator" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/provider-emulator:${TAG}"] +} + +target "relay-emulator" { + context = "./" + dockerfile = "Dockerfile.relayemulator" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/relay-emulator:${TAG}"] +} + +target "l1-transactor" { + context = "./" + dockerfile = "Dockerfile.l1transactor" + contexts = { + builder_ctx = "target:mev-commit-builder" + } + tags = ["ghcr.io/primev/l1-transactor:${TAG}"] +} + +group "default" { + targets = ["mev-commit-builder", "mev-commit-oracle", "mev-commit", "mev-commit-bridge", "mev-commit-dashboard", "preconf-rpc", "bidder-emulator", "provider-emulator", "relay-emulator", "l1-transactor"] +} + From 36edd8a8cfa90c9ba37b6f385033a755d6e3c90c Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Wed, 18 Jun 2025 02:37:34 +0530 Subject: [PATCH 58/69] feat: add Makefile for docker build --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..3988e40b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +TAG ?= $(shell git describe --tags || git rev-parse --short HEAD) + +.PHONY: docker +docker: + cd infrastructure/docker && TAG=$(TAG) docker buildx bake From 6506d89ea39d4a32e042f98b230ae869609e76e8 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Wed, 18 Jun 2025 17:08:48 +0530 Subject: [PATCH 59/69] feat: test validator registry deployment --- .../validator-registry/DeployForMockL1.s.sol | 4 + .../jobs/contracts-deployer.nomad.j2 | 82 +++++++++---------- .../nomad/playbooks/variables/profiles.yml | 2 + p2p/pkg/node/node.go | 7 ++ 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/contracts/scripts/validator-registry/DeployForMockL1.s.sol b/contracts/scripts/validator-registry/DeployForMockL1.s.sol index 706238ea3..5e0f7b257 100644 --- a/contracts/scripts/validator-registry/DeployForMockL1.s.sol +++ b/contracts/scripts/validator-registry/DeployForMockL1.s.sol @@ -44,6 +44,10 @@ contract DeployForMockL1 is Script { console.log("_VanillaRegistry:", vanillaRegistryProxy); VanillaRegistry vanillaRegistry = VanillaRegistry(payable(vanillaRegistryProxy)); + address[] memory stakers = new address[](1); + stakers[0] = owner; + vanillaRegistry.whitelistStakers(stakers); + AlwaysFalseAVS mockAVS = new AlwaysFalseAVS(); AlwaysFalseMiddleware mockMiddleware = new AlwaysFalseMiddleware(); console.log("_MockAVS:", address(mockAVS)); diff --git a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 index 599a3625a..a84fc5259 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/contracts-deployer.nomad.j2 @@ -127,8 +127,6 @@ job "{{ job.name }}" { data = <<-EOH #!/usr/bin/env bash - set -x - {%- raw %} {{- range nomadService "datadog-agent-logs-collector" }} {{ if contains "tcp" .Tags }} @@ -224,8 +222,6 @@ job "{{ job.name }}" { | jq -c 'with_entries(select(.key | startswith("_") | not))' \ > local/${DEPLOY_TYPE}_addresses.json - forge clean --root "${CONTRACT_REPO_ROOT_PATH}" - {% if profile == 'testnet' %} export RPC_URL="{{ job.env['l1_rpc_url'] }}" export CHAIN_ID="17000" @@ -244,6 +240,43 @@ job "{{ job.name }}" { {{- end }} {% endraw %} + # Only deploy validator-registry when beacon-emulator is running + {%- raw %} + BEACON_EMULATOR_RUNNING=false + {{- range nomadService "beacon-emulator" }} + BEACON_EMULATOR_RUNNING=true + {{- end }} + {% endraw %} + + if [ "$BEACON_EMULATOR_RUNNING" = true ]; then + + forge clean --root "${CONTRACT_REPO_ROOT_PATH}" + + start_time=$(date +%s) + export DEPLOY_TYPE="validator-registry" + echo "Deploying ${DEPLOY_TYPE} contracts..." + LOGS="$(${CONTRACT_REPO_ROOT_PATH}/entrypoint.sh)" + + if [ $? -ne 0 ]; then + echo "Failed to deploy ${DEPLOY_TYPE} contracts!" + echo "${LOGS}" + exit 1 + fi + end_time=$(date +%s) + echo "${DEPLOY_TYPE} contracts deployed successfully in: $(date -ud @$((end_time - start_time)) +'%H:%M:%S')." + echo "${LOGS}" \ + | sed -n '/{.*}/p' \ + | jq -c 'reduce .logs[] as $item ({}; . + {($item | split(": ")[0]): ($item | split(": ")[1])})' \ + | jq -c 'with_entries(select(.key | startswith("_") | not))' \ + > local/${DEPLOY_TYPE}_addresses.json + else + echo "Skipping validator-registry deployment as beacon-emulator is not running" + fi + + {%- endif %} + + forge clean --root "${CONTRACT_REPO_ROOT_PATH}" + start_time=$(date +%s) export DEPLOY_TYPE="l1-gateway" echo "Deploying ${DEPLOY_TYPE} contracts..." @@ -279,51 +312,10 @@ job "{{ job.name }}" { | jq -c 'with_entries(select(.key | startswith("_") | not))' \ > local/${DEPLOY_TYPE}_addresses.json - # Only deploy validator-registry when beacon-emulator is running - {%- raw %} - BEACON_EMULATOR_RUNNING=false - {{- range nomadService "beacon-emulator" }} - BEACON_EMULATOR_RUNNING=true - {{- end }} - {% endraw %} - - if [ "$BEACON_EMULATOR_RUNNING" = true ]; then - - cd "${TMP_CONTRACTS_DIR}" - "${FORGE_BIN_PATH}" clean --root "${CONTRACT_REPO_ROOT_PATH}" - - start_time=$(date +%s) - export DEPLOY_TYPE="validator-registry" - echo "Deploying ${DEPLOY_TYPE} contracts..." - LOGS="$(./entrypoint.sh)" - - # Now cd back to previous directory again. - cd ../../ - - if [ $? -ne 0 ]; then - echo "Failed to deploy ${DEPLOY_TYPE} contracts!" - echo "${LOGS}" - exit 1 - fi - end_time=$(date +%s) - echo "${DEPLOY_TYPE} contracts deployed successfully in: $(date -ud @$((end_time - start_time)) +'%H:%M:%S')." - echo "${LOGS}" \ - | sed -n '/{.*}/p' \ - | jq -c 'reduce .logs[] as $item ({}; . + {($item | split(": ")[0]): ($item | split(": ")[1])})' \ - | jq -c 'with_entries(select(.key | startswith("_") | not))' \ - > local/${DEPLOY_TYPE}_addresses.json - - else - echo "Skipping validator-registry deployment as beacon-emulator is not running" - fi - - {%- endif %} - mkdir -p /local/data/www > /dev/null 2>&1 jq -s 'add' local/*_addresses.json > local/data/www/contracts.json exec python3 -m http.server {{ job.ports[0]['http']['static'] }} --directory /local/data/www - # endtodo EOH destination = "local/run.sh" change_mode = "noop" diff --git a/infrastructure/nomad/playbooks/variables/profiles.yml b/infrastructure/nomad/playbooks/variables/profiles.yml index 8547890c7..fc2f0256c 100644 --- a/infrastructure/nomad/playbooks/variables/profiles.yml +++ b/infrastructure/nomad/playbooks/variables/profiles.yml @@ -983,6 +983,8 @@ profiles: - *mev_commit_provider_node1_funder_job - *mev_commit_provider_node2_job - *mev_commit_provider_node2_funder_job + - *mev_commit_provider_node3_job + - *mev_commit_provider_node3_funder_job - *mev_commit_provider_emulator_nodes_job - *mev_commit_oracle_job - *mev_commit_bidder_node1_job diff --git a/p2p/pkg/node/node.go b/p2p/pkg/node/node.go index 7189c2ae3..18b2475e5 100644 --- a/p2p/pkg/node/node.go +++ b/p2p/pkg/node/node.go @@ -191,6 +191,13 @@ func NewNode(opts *Options) (*Node, error) { setDefault(&opts.BeaconAPIURL, defaults.BeaconAPIURL) } + opts.Logger.Info( + "using L1 contracts", + "ValidatorRouter", opts.ValidatorRouterContract, + "BeaconAPIURL", opts.BeaconAPIURL, + "L1RPCURL", opts.L1RPCURL, + ) + notificationsSvc := notifications.New(opts.NotificationsBufferCap) nd.closers = append( nd.closers, From 6022cee2f370f2350dc0566a9f4f34e99554f0db Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 19 Jun 2025 13:23:11 +0530 Subject: [PATCH 60/69] feat: l1 rpc endpoint for test --- contracts/scripts/validator-registry/DeployForMockL1.s.sol | 4 ---- .../nomad/playbooks/templates/jobs/mev-commit.nomad.j2 | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/scripts/validator-registry/DeployForMockL1.s.sol b/contracts/scripts/validator-registry/DeployForMockL1.s.sol index 5e0f7b257..706238ea3 100644 --- a/contracts/scripts/validator-registry/DeployForMockL1.s.sol +++ b/contracts/scripts/validator-registry/DeployForMockL1.s.sol @@ -44,10 +44,6 @@ contract DeployForMockL1 is Script { console.log("_VanillaRegistry:", vanillaRegistryProxy); VanillaRegistry vanillaRegistry = VanillaRegistry(payable(vanillaRegistryProxy)); - address[] memory stakers = new address[](1); - stakers[0] = owner; - vanillaRegistry.whitelistStakers(stakers); - AlwaysFalseAVS mockAVS = new AlwaysFalseAVS(); AlwaysFalseMiddleware mockMiddleware = new AlwaysFalseMiddleware(); console.log("_MockAVS:", address(mockAVS)); diff --git a/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 index 17fdb32c5..aa7514d24 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 @@ -99,6 +99,11 @@ job "{{ job.name }}" { MEV_COMMIT_SETTLEMENT_WS_RPC_ENDPOINT="ws://{{ .Address}}:{{ .Port }}" {{- end }} {{- end }} + {{- range nomadService "mock-l1" }} + {{- if contains "http" .Tags }} + MEV_COMMIT_L1_RPC_ENDPOINT="http://{{ .Address }}:{{ .Port }}" + {{- end }} + {{- end }} {{- range nomadService "beacon-emulator" }} {{- if contains "http" .Tags }} MEV_COMMIT_BEACON_API_URL="http://{{ .Address }}:{{ .Port }}" From f620194d97887096776365ff40e8aab6a48d7b6b Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 19 Jun 2025 15:02:16 +0530 Subject: [PATCH 61/69] fix: rebase issue --- contracts/scripts/validator-registry/DeployForMockL1.s.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/scripts/validator-registry/DeployForMockL1.s.sol b/contracts/scripts/validator-registry/DeployForMockL1.s.sol index 706238ea3..5e0f7b257 100644 --- a/contracts/scripts/validator-registry/DeployForMockL1.s.sol +++ b/contracts/scripts/validator-registry/DeployForMockL1.s.sol @@ -44,6 +44,10 @@ contract DeployForMockL1 is Script { console.log("_VanillaRegistry:", vanillaRegistryProxy); VanillaRegistry vanillaRegistry = VanillaRegistry(payable(vanillaRegistryProxy)); + address[] memory stakers = new address[](1); + stakers[0] = owner; + vanillaRegistry.whitelistStakers(stakers); + AlwaysFalseAVS mockAVS = new AlwaysFalseAVS(); AlwaysFalseMiddleware mockMiddleware = new AlwaysFalseMiddleware(); console.log("_MockAVS:", address(mockAVS)); From 3e0d06d4140afc72a3b63e6aa5a5ed3c3f6addd3 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 19 Jun 2025 15:46:12 +0530 Subject: [PATCH 62/69] fix: rebase issue --- .../nomad/playbooks/templates/jobs/mev-commit.nomad.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 index aa7514d24..7f6edf9b2 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/mev-commit.nomad.j2 @@ -101,7 +101,7 @@ job "{{ job.name }}" { {{- end }} {{- range nomadService "mock-l1" }} {{- if contains "http" .Tags }} - MEV_COMMIT_L1_RPC_ENDPOINT="http://{{ .Address }}:{{ .Port }}" + MEV_COMMIT_L1_RPC_URL="http://{{ .Address }}:{{ .Port }}" {{- end }} {{- end }} {{- range nomadService "beacon-emulator" }} From d997f9def3c978e291dd28c4ccb837dd9c15a2ca Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 19 Jun 2025 20:42:40 +0530 Subject: [PATCH 63/69] fix: test params --- .../nomad/playbooks/templates/jobs/mev-commit-emulator.nomad.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/nomad/playbooks/templates/jobs/mev-commit-emulator.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/mev-commit-emulator.nomad.j2 index 0f2379d82..2f46511d6 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/mev-commit-emulator.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/mev-commit-emulator.nomad.j2 @@ -117,7 +117,7 @@ job "{{ job.name }}" { {% if job.target_type == 'bidder' %} -rpc-addr "${EMULATOR_L1_RPC_URL}" \ {% endif %} - {% if job.target_type == 'provider' and profile == 'instant-bridge' %} + {% if job.target_type == 'provider' and profile == 'preconf-rpc-test' %} -error-probability 0 \ {% endif %} -log-tags "${EMULATOR_LOG_TAGS}" \ From 2edf0ab6416555ac50688a119c46142ac6c204bf Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 19 Jun 2025 21:14:08 +0530 Subject: [PATCH 64/69] fix: add some logs --- tools/preconf-rpc/rpcserver/rpcserver.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/preconf-rpc/rpcserver/rpcserver.go b/tools/preconf-rpc/rpcserver/rpcserver.go index 12c2c8aea..e1307a6e0 100644 --- a/tools/preconf-rpc/rpcserver/rpcserver.go +++ b/tools/preconf-rpc/rpcserver/rpcserver.go @@ -117,6 +117,7 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } s.logger.Info("Processing JSON-RPC request", "method", req.Method, "id", req.ID) + defer s.logger.Info("Finished processing JSON-RPC request", "method", req.Method, "id", req.ID) s.rwLock.RLock() handler, ok := s.methods[req.Method] @@ -149,6 +150,7 @@ func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (s *JSONRPCServer) writeResponse(w http.ResponseWriter, id any, result *json.RawMessage) { + s.logger.Info("Writing JSON-RPC response", "id", id, "result", result) response := jsonRPCResponse{ JSONRPC: "2.0", ID: id, @@ -163,6 +165,7 @@ func (s *JSONRPCServer) writeResponse(w http.ResponseWriter, id any, result *jso } func (s *JSONRPCServer) writeError(w http.ResponseWriter, id any, code int, message string) { + s.logger.Error("JSON-RPC error", "id", id, "code", code, "message", message) response := jsonRPCResponse{ JSONRPC: "2.0", ID: id, From 36e98e6e7bf36febae1c4deb535fc100eb509160 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Thu, 19 Jun 2025 23:52:33 +0530 Subject: [PATCH 65/69] fix: start blocktracker --- tools/preconf-rpc/service/service.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index 792ee489e..a7605f9e1 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -156,6 +156,8 @@ func New(config *Config) (*Service, error) { l1RPCClient, config.Logger.With("module", "blocktracker"), ) + blockTrackerDone := blockTracker.Start(ctx) + healthChecker.Register(health.CloseChannelHealthCheck("BlockTracker", blockTrackerDone)) handlers := handlers.NewRPCMethodHandler( config.Logger.With("module", "handlers"), From 7e1125128cafad2d734b361b875a82b058723328 Mon Sep 17 00:00:00 2001 From: Alok Date: Fri, 20 Jun 2025 18:35:23 +0530 Subject: [PATCH 66/69] fix: getBlockByHash --- tools/preconf-rpc/handlers/handlers.go | 151 ++++++++++++++++++++++++- tools/preconf-rpc/store/store.go | 42 +++++++ 2 files changed, 192 insertions(+), 1 deletion(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index ff00441d2..e75612186 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -8,6 +8,8 @@ import ( "fmt" "log/slog" "math/big" + "strconv" + "strings" "sync" "github.com/ethereum/go-ethereum/common" @@ -24,6 +26,11 @@ const ( blockTime = 12 // seconds, typical Ethereum block time ) +var ( + preconfBlockHashPrefix = hex.EncodeToString([]byte("mev-commit")) + preconfBlockHashPrefixLen = len(preconfBlockHashPrefix) +) + type Bidder interface { Estimate() (int64, error) Bid( @@ -53,6 +60,10 @@ type Store interface { ctx context.Context, txnHash common.Hash, ) (*types.Transaction, []*bidderapiv1.Commitment, error) + GetPreconfirmedTransactionsForBlock( + ctx context.Context, + blockNumber int64, + ) ([]*types.Transaction, error) DeductBalance(ctx context.Context, account common.Address, amount *big.Int) error HasBalance(ctx context.Context, account common.Address, amount *big.Int) bool GetBalance(ctx context.Context, account common.Address) (*big.Int, error) @@ -85,6 +96,7 @@ type rpcMethodHandler struct { pricer Pricer blockTracker BlockTracker owner common.Address + chainID *big.Int nonceLock *multex.Multex[string] nonceMap map[string]accountNonce nonceMapLock sync.RWMutex @@ -111,14 +123,147 @@ func NewRPCMethodHandler( } func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) { + // Ethereum JSON-RPC methods overridden + server.RegisterHandler("eth_getBlockNumber", h.handleGetBlockNumber) + server.RegisterHandler("eth_chainId", h.handleChainID) server.RegisterHandler("eth_sendRawTransaction", h.handleSendRawTx) server.RegisterHandler("eth_getTransactionReceipt", h.handleGetTxReceipt) server.RegisterHandler("eth_getTransactionCount", h.handleGetTxCount) + server.RegisterHandler("eth_getBlockByHash", h.handleGetBlockByHash) + // Custom methods for MEV Commit server.RegisterHandler("mevcommit_getTransactionCommitments", h.handleGetTxCommitments) server.RegisterHandler("mevcommit_getBalance", h.handleMevCommitGetBalance) server.RegisterHandler("mevcommit_estimateFastBid", h.handleMevCommitEstimateFastBid) } +func (h *rpcMethodHandler) handleGetBlockNumber( + ctx context.Context, + params ...any, +) (json.RawMessage, bool, error) { + if len(params) != 0 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "getBlockNumber does not require any parameters", + ) + } + + blockNumber := h.blockTracker.LatestBlockNumber() + h.logger.Info("Retrieved latest block number", "blockNumber", blockNumber) + + blockNumberJSON, err := json.Marshal(hexutil.Uint64(blockNumber)) + if err != nil { + h.logger.Error("Failed to marshal block number to JSON", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to marshal block number", + ) + } + + return blockNumberJSON, false, nil +} + +func (h *rpcMethodHandler) handleChainID( + ctx context.Context, + params ...any, +) (json.RawMessage, bool, error) { + if len(params) != 0 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "chainID does not require any parameters", + ) + } + + chainIDJSON, err := json.Marshal(hexutil.Uint64(h.chainID.Uint64())) + if err != nil { + h.logger.Error("Failed to marshal chain ID to JSON", "error", err) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to marshal chain ID", + ) + } + + return chainIDJSON, false, nil +} + +func (h *rpcMethodHandler) handleGetBlockByHash( + ctx context.Context, + params ...any, +) (json.RawMessage, bool, error) { + if len(params) != 1 { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeInvalidRequest, + "getBlock requires exactly one parameter", + ) + } + + if params[0] == nil { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getBlock parameter cannot be null", + ) + } + + blockHashStr := params[0].(string) + if !strings.HasPrefix(blockHashStr, preconfBlockHashPrefix) { + return nil, true, nil // Not a preconf block hash, proxy + } + + blockNumberWithPadding := strings.TrimPrefix(blockHashStr, preconfBlockHashPrefix) + blockNumber, err := strconv.ParseUint(blockNumberWithPadding[:8], 10, 64) + if err != nil { + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeParseError, + "getBlock parameter must be a valid preconf block hash", + ) + } + + txns, err := h.store.GetPreconfirmedTransactionsForBlock(ctx, int64(blockNumber)) + if err != nil { + h.logger.Error("Failed to get preconfirmed transactions for block", "error", err, "blockNumber", blockNumber) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to get preconfirmed transactions for block", + ) + } + + block := map[string]interface{}{ + "number": hexutil.Uint64(blockNumber), + "hash": blockHashStr, + "parentHash": (common.Hash{}).Hex(), + "nonce": "0x0000000000000000", + "sha3Uncles": (common.Hash{}).Hex(), + "logsBloom": hexutil.Bytes(types.Bloom{}.Bytes()), + "transactions": make([]string, len(txns)), + "transactionsRoot": (common.Hash{}).Hex(), + "stateRoot": (common.Hash{}).Hex(), + "miner": h.owner.Hex(), + "difficulty": hexutil.Uint64(0), + "totalDifficulty": hexutil.Uint64(0), + "size": hexutil.Uint64(0), + "extraData": "0x", + "gasLimit": hexutil.Uint64(0), + "gasUsed": hexutil.Uint64(0), + "timestamp": hexutil.Uint64(0), + "baseFeePerGas": hexutil.EncodeBig(big.NewInt(0)), + "withdrawals": nil, + } + + for i, txn := range txns { + block["transactions"].([]string)[i] = txn.Hash().Hex() + } + blockJSON, err := json.Marshal(block) + if err != nil { + h.logger.Error("Failed to marshal block to JSON", "error", err, "blockNumber", blockNumber) + return nil, false, rpcserver.NewJSONErr( + rpcserver.CodeCustomError, + "failed to marshal block", + ) + } + + h.logger.Info("Retrieved preconf block", "blockNumber", blockNumber, "txCount", len(txns)) + return blockJSON, false, nil +} + func (h *rpcMethodHandler) handleSendRawTx( ctx context.Context, params ...any, @@ -439,11 +584,15 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any ) } + blockHash := fmt.Sprintf("%s%08d", preconfBlockHashPrefix, commitments[0].BlockNumber) + padding := strings.Repeat("0", 66-len(blockHash)) + blockHash = blockHash + padding + result := map[string]interface{}{ "type": hexutil.Uint(txn.Type()), "transactionHash": txn.Hash().Hex(), "transactionIndex": hexutil.Uint(0), - "blockHash": (common.Hash{}).Hex(), + "blockHash": blockHash, "blockNumber": hexutil.EncodeBig(big.NewInt(commitments[0].BlockNumber)), "from": sender.Hex(), "to": nil, diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index bf8749b9c..821ab0ad1 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -1,6 +1,7 @@ package store import ( + "bytes" "context" "encoding/binary" "encoding/json" @@ -125,6 +126,47 @@ func (s *rpcstore) GetPreconfirmedTransaction( return txn, commitments, nil } +func (s *rpcstore) GetPreconfirmedTransactionsForBlock( + ctx context.Context, + blockNumber int64, +) ([]*types.Transaction, error) { + if blockNumber <= 0 { + return nil, errors.New("invalid block number") + } + + keyPrefix := []byte(fmt.Sprintf("%d:", blockNumber)) + iter, err := s.db.NewIter(&pebble.IterOptions{ + LowerBound: keyPrefix, + UpperBound: append(keyPrefix, 0xFF), + }) + if err != nil { + return nil, fmt.Errorf("failed to create iterator for block %d: %w", blockNumber, err) + } + defer iter.Close() + + var transactions []*types.Transaction + for iter.First(); iter.Valid(); iter.Next() { + if !bytes.Equal(iter.Key()[:len(keyPrefix)], keyPrefix) { + continue + } + txnData := iter.Value() + if len(txnData) < 8 { + return nil, fmt.Errorf("invalid transaction data length for block %d", blockNumber) + } + txnDataLen := binary.BigEndian.Uint64(txnData[:8]) + if len(txnData) < int(8+txnDataLen) { + return nil, fmt.Errorf("invalid transaction data length for block %d", blockNumber) + } + + txn := new(types.Transaction) + if err := txn.UnmarshalBinary(txnData[8 : 8+txnDataLen]); err != nil { + return nil, fmt.Errorf("failed to unmarshal transaction: %w", err) + } + transactions = append(transactions, txn) + } + return transactions, nil +} + func (s *rpcstore) DeductBalance( ctx context.Context, account common.Address, From f14323113c518a5906c8b0e0a07671d0988197ea Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Fri, 20 Jun 2025 19:40:56 +0530 Subject: [PATCH 67/69] fix: add new APIs --- tools/preconf-rpc/handlers/handlers.go | 2 ++ tools/preconf-rpc/service/service.go | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index e75612186..78091d72e 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -109,6 +109,7 @@ func NewRPCMethodHandler( pricer Pricer, blockTracker BlockTracker, owner common.Address, + chainId *big.Int, ) *rpcMethodHandler { return &rpcMethodHandler{ logger: logger, @@ -117,6 +118,7 @@ func NewRPCMethodHandler( pricer: pricer, blockTracker: blockTracker, owner: owner, + chainID: chainId, nonceLock: multex.New[string](), nonceMap: make(map[string]accountNonce), } diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index a7605f9e1..04a912f09 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -81,6 +81,11 @@ func New(config *Config) (*Service, error) { return nil, err } + l1ChainID, err := l1RPCClient.ChainID(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get L1 chain ID: %w", err) + } + bidderCli := bidderapiv1.NewBidderClient(conn) topologyCli := debugapiv1.NewDebugServiceClient(conn) notificationsCli := notificationsapiv1.NewNotificationsClient(conn) @@ -166,6 +171,7 @@ func New(config *Config) (*Service, error) { bidpricer, blockTracker, config.Signer.GetAddress(), + l1ChainID, ) handlers.RegisterMethods(rpcServer) From 8d212341d4e19483cd96834ae2fe3e0f8a140ef0 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 21 Jun 2025 01:09:00 +0530 Subject: [PATCH 68/69] fix: add new APIs --- tools/preconf-rpc/handlers/handlers.go | 57 +++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 78091d72e..dcc61a961 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -191,10 +191,10 @@ func (h *rpcMethodHandler) handleGetBlockByHash( ctx context.Context, params ...any, ) (json.RawMessage, bool, error) { - if len(params) != 1 { + if len(params) == 0 { return nil, false, rpcserver.NewJSONErr( rpcserver.CodeInvalidRequest, - "getBlock requires exactly one parameter", + "getBlockByHash requires one or two parameter", ) } @@ -210,6 +210,11 @@ func (h *rpcMethodHandler) handleGetBlockByHash( return nil, true, nil // Not a preconf block hash, proxy } + details := false + if len(params) > 1 && params[1] != nil { + details, _ = params[1].(bool) + } + blockNumberWithPadding := strings.TrimPrefix(blockHashStr, preconfBlockHashPrefix) blockNumber, err := strconv.ParseUint(blockNumberWithPadding[:8], 10, 64) if err != nil { @@ -235,7 +240,6 @@ func (h *rpcMethodHandler) handleGetBlockByHash( "nonce": "0x0000000000000000", "sha3Uncles": (common.Hash{}).Hex(), "logsBloom": hexutil.Bytes(types.Bloom{}.Bytes()), - "transactions": make([]string, len(txns)), "transactionsRoot": (common.Hash{}).Hex(), "stateRoot": (common.Hash{}).Hex(), "miner": h.owner.Hex(), @@ -250,9 +254,52 @@ func (h *rpcMethodHandler) handleGetBlockByHash( "withdrawals": nil, } + var txnsToReturn any for i, txn := range txns { - block["transactions"].([]string)[i] = txn.Hash().Hex() - } + if !details { + if txnsToReturn == nil { + txnsToReturn = make([]string, 0, len(txns)) + } + txnsToReturn = append( + txnsToReturn.([]string), + txn.Hash().Hex(), + ) + continue + } + if txnsToReturn == nil { + txnsToReturn = make([]map[string]interface{}, len(txns)) + } + r, s, v := txn.RawSignatureValues() + sender, err := types.Sender(types.LatestSignerForChainID(txn.ChainId()), txn) + if err != nil { + h.logger.Error("Failed to get transaction sender", "error", err, "txnHash", txn.Hash().Hex()) + continue + } + txnsToReturn = append( + txnsToReturn.([]map[string]interface{}), + map[string]interface{}{ + "hash": txn.Hash().Hex(), + "blockHash": blockHashStr, + "blockNumber": hexutil.Uint64(blockNumber), + "transactionIndex": hexutil.Uint64(i), + "type": hexutil.Uint(txn.Type()), + "accessList": nil, // Access lists are not used in preconf blocks + "maxFeePerGas": hexutil.EncodeBig(txn.GasFeeCap()), + "maxPriorityFeePerGas": hexutil.EncodeBig(txn.GasTipCap()), + "to": txn.To().Hex(), + "value": hexutil.EncodeBig(txn.Value()), + "input": hexutil.Encode(txn.Data()), + "from": sender.Hex(), + "nonce": hexutil.Uint64(txn.Nonce()), + "gas": hexutil.Uint64(txn.Gas()), + "gasPrice": hexutil.EncodeBig(txn.GasPrice()), + "r": hexutil.EncodeBig(r), + "s": hexutil.EncodeBig(s), + "v": hexutil.EncodeBig(v), + }, + ) + } + block["transactions"] = txnsToReturn blockJSON, err := json.Marshal(block) if err != nil { h.logger.Error("Failed to marshal block to JSON", "error", err, "blockNumber", blockNumber) From 43e2304095b124a39aeace6583132eb28e261f3a Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Sat, 21 Jun 2025 02:18:01 +0530 Subject: [PATCH 69/69] fix: add new APIs --- tools/preconf-rpc/handlers/handlers.go | 3 +-- tools/preconf-rpc/store/store.go | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index dcc61a961..c45d2248c 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -27,8 +27,7 @@ const ( ) var ( - preconfBlockHashPrefix = hex.EncodeToString([]byte("mev-commit")) - preconfBlockHashPrefixLen = len(preconfBlockHashPrefix) + preconfBlockHashPrefix = hex.EncodeToString([]byte("mev-commit")) ) type Bidder interface { diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index 821ab0ad1..73432c314 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -142,7 +142,9 @@ func (s *rpcstore) GetPreconfirmedTransactionsForBlock( if err != nil { return nil, fmt.Errorf("failed to create iterator for block %d: %w", blockNumber, err) } - defer iter.Close() + defer func() { + _ = iter.Close() + }() var transactions []*types.Transaction for iter.First(); iter.Valid(); iter.Next() {