diff --git a/infrastructure/nomad/playbooks/templates/jobs/instant-bridge.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/instant-bridge.nomad.j2 deleted file mode 100644 index b20d0af61..000000000 --- a/infrastructure/nomad/playbooks/templates/jobs/instant-bridge.nomad.j2 +++ /dev/null @@ -1,204 +0,0 @@ -#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 "instantbridge" { - 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/instant-bridge_{{ version }}_Linux_{{ target_system_architecture }}.tar.gz" - } - {% else %} - artifact { - source = "http://{{ ansible_facts['default_ipv4']['address'] }}:1111/instant-bridge_{{ version }}_Linux_{{ target_system_architecture }}.tar.gz" - } - {% endif %} - - template { - data = <<-EOH - XDG_CONFIG_HOME="local/.config" - INSTANT_BRIDGE_LOG_LEVEL="{{ job.env.get('log-level', 'info') }}" - INSTANT_BRIDGE_LOG_FMT="{{ job.env.get('log-format', 'json') }}" - INSTANT_BRIDGE_LOG_TAGS="{{ 'service.name:' + job.name + '-{{ env "NOMAD_ALLOC_INDEX" }}' + ',service.version:' + version }}" - CONTRACTS_JSON_URL="{{ job.env.get('contracts_json_url', '') }}" - INSTANT_BRIDGE_SETTLEMENT_RPC_URL="{{ job.env.get('settlement_rpc_url', '') }}" - {%- raw %} - INSTANT_BRIDGE_KEYSTORE_DIR="/local/data-{{ env "NOMAD_ALLOC_INDEX" }}/keystore" - INSTANT_BRIDGE_KEYSTORE_FILENAME="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.instant_bridge_keystore_filename }}{{ end }}" - INSTANT_BRIDGE_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 }} - INSTANT_BRIDGE_SETTLEMENT_RPC_URL="http://{{ .Address }}:{{ .Port }}" - {{- end }} - {{- end }} - {{- range nomadService "{% endraw %}{{ job.target.name }}{% raw %}" }} - {{- if contains "rpc" .Tags }} - INSTANT_BRIDGE_BIDDER_RPC_URL="{{ .Address }}:{{ .Port }}" - {{- end }} - {{- end }} - {% endraw %} - XDG_CONFIG_HOME="local/.config" - {% if profile == 'instant-bridge-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 %} - INSTANT_BRIDGE_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 "${INSTANT_BRIDGE_KEYSTORE_DIR}" > /dev/null 2>&1 - {{- with secret "secret/data/mev-commit" }} - INSTANT_BRIDGE_KEYSTORE_FILE="${INSTANT_BRIDGE_KEYSTORE_DIR}/${INSTANT_BRIDGE_KEYSTORE_FILENAME}" - echo '{{ .Data.data.instant_bridge_keystore }}' > "${INSTANT_BRIDGE_KEYSTORE_FILE}" - {{ end }} - {% endraw %} - - {% if profile == 'instant-bridge-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 INSTANT_BRIDGE_SETTLEMENT_CONTRACT_ADDR="$(jq -r '.SettlementGateway' ${CONTRACTS_FILE})" - export INSTANT_BRIDGE_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 "${INSTANT_BRIDGE_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/instant-bridge - exec ./local/instant-bridge - 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 2f651a713..b928cb447 100644 --- a/infrastructure/nomad/playbooks/variables/profiles.yml +++ b/infrastructure/nomad/playbooks/variables/profiles.yml @@ -39,9 +39,6 @@ artifacts: beacon-emulator: &beacon_emulator_artifact type: binary path: tools/beacon-emulator - instant-bridge: &instant_bridge_artifact - type: binary - path: tools/instant-bridge preconf-rpc: &preconf_rpc_artifact type: binary path: tools/preconf-rpc @@ -723,24 +720,6 @@ jobs: env: l1_rpc_url: "{{ resolved_l1_rpc_urls.split(',')[0] }}" - instant_bridge: &instant_bridge_job - name: instant-bridge - template: instant-bridge.nomad.j2 - artifacts: - - *instant_bridge_artifact - - keystores: - instant_bridge_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 '' }}" - preconf_rpc: &preconf_rpc_job name: preconf-rpc template: preconf-rpc.nomad.j2 @@ -941,35 +920,6 @@ profiles: - *mev_commit_faucet_job - *datadog_agent_metrics_collector_job - instant-bridge-test: - jobs: - - *artifacts_job - - *datadog_agent_logs_collector_job - - *otel_collector_job - - *beacon_emulator_job - - *mock_l1_job - - *l1_transactor_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_node3_job - - *mev_commit_provider_node3_funder_job - - *mev_commit_provider_emulator_nodes_job - - *mev_commit_oracle_job - - *mev_commit_bidder_node1_job - - *mev_commit_bidder_node1_funder_job - - *instant_bridge_job - - *datadog_agent_metrics_collector_job - preconf-rpc-test: jobs: - *artifacts_job @@ -998,13 +948,6 @@ profiles: - *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: - *artifacts_job diff --git a/p2p/pkg/preconfirmation/preconfirmation.go b/p2p/pkg/preconfirmation/preconfirmation.go index 306e42182..af1c30195 100644 --- a/p2p/pkg/preconfirmation/preconfirmation.go +++ b/p2p/pkg/preconfirmation/preconfirmation.go @@ -5,6 +5,7 @@ import ( "errors" "log/slog" "math/big" + "slices" "sync" "time" @@ -20,6 +21,7 @@ import ( providerapi "github.com/primev/mev-commit/p2p/pkg/rpc/provider" "github.com/primev/mev-commit/p2p/pkg/topology" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) @@ -147,6 +149,22 @@ func (p *Preconfirmation) SendBid( return nil, errors.New("no providers available") } + md, ok := metadata.FromIncomingContext(ctx) + if ok { + ignoredProviders := md.Get("ignore-provider") + p.logger.Info("ignoring providers for this bid", "providers", ignoredProviders, "bid", bid) + for _, ip := range ignoredProviders { + ignoredAddr := common.HexToAddress(ip) + idx := slices.IndexFunc(providers, func(p p2p.Peer) bool { + return p.EthAddress == ignoredAddr + }) + if idx != -1 { + p.logger.Info("ignoring provider for this bid", "provider", ignoredAddr.Hex(), "bid", bid) + providers = append(providers[:idx], providers[idx+1:]...) + } + } + } + // Create a new channel to receive preConfirmations preConfirmations := make(chan *preconfpb.PreConfirmation, len(providers)) diff --git a/tools/instant-bridge/.goreleaser.yml b/tools/instant-bridge/.goreleaser.yml deleted file mode 100644 index f4c2001de..000000000 --- a/tools/instant-bridge/.goreleaser.yml +++ /dev/null @@ -1,64 +0,0 @@ -version: 1 - -project_name: instant-bridge -dist: /tmp/dist/instant-bridge - -builds: - - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - dir: ./tools/instant-bridge - 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/instant-bridge/api/api.go b/tools/instant-bridge/api/api.go deleted file mode 100644 index eb079b5e8..000000000 --- a/tools/instant-bridge/api/api.go +++ /dev/null @@ -1,337 +0,0 @@ -package api - -import ( - "bufio" - "context" - "errors" - "fmt" - "log/slog" - "math/big" - "net" - "net/http" - "os" - "sync/atomic" - "time" - - "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/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 - l1ChainID *big.Int - settlementChainID *big.Int -} - -type bid struct { - BridgeAmount string `json:"bridgeAmount"` - RawTx string `json:"rawTx"` - DestAddress string `json:"destAddress"` -} - -type status struct { - bidsAttempted atomic.Int64 - bidsSucceeded atomic.Int64 - transfersAttempted atomic.Int64 - transfersSucceeded atomic.Int64 - bridgedAmount atomic.Pointer[big.Int] - bidAmountSpent atomic.Pointer[big.Int] - feesAccumulated atomic.Pointer[big.Int] -} - -func NewAPI( - logger *slog.Logger, - port int, - health health.Health, - bdr *bidder.BidderClient, - transferer *transfer.Transferer, - minServiceFee *big.Int, - 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, - l1ChainID: l1ChainID, - settlementChainID: settlementChainID, - } - - a.status.bridgedAmount.Store(big.NewInt(0)) - a.status.bidAmountSpent.Store(big.NewInt(0)) - a.status.feesAccumulated.Store(big.NewInt(0)) - - a.mux.HandleFunc("GET /health", func(w http.ResponseWriter, req *http.Request) { - err := a.health.Health() - if err != nil { - apiserver.WriteError(w, http.StatusServiceUnavailable, err) - return - } - w.WriteHeader(http.StatusOK) - _, err = fmt.Fprintf(w, "ok\n") - if err != nil { - a.logger.Error( - "failed to write response", - "error", err, - ) - } - }) - - a.mux.HandleFunc("GET /estimate", func(w http.ResponseWriter, req *http.Request) { - estimation, err := a.bidder.Estimate() - if err != nil { - apiserver.WriteError(w, http.StatusInternalServerError, err) - return - } - if err := apiserver.WriteResponse(w, http.StatusOK, struct { - Seconds int64 `json:"seconds"` - Cost string `json:"cost"` - Destination string `json:"destination"` - }{ - Seconds: estimation, - Cost: a.minServiceFee.String(), - Destination: a.owner.Hex(), - }); err != nil { - a.logger.Error("failed to write response", "error", err) - } - }) - - a.mux.HandleFunc("GET /status", func(w http.ResponseWriter, req *http.Request) { - bridgedAmount := a.status.bridgedAmount.Load() - bidAmountSpent := a.status.bidAmountSpent.Load() - feesAccumulated := a.status.feesAccumulated.Load() - - l1Balance, err := a.l1Client.BalanceAt(req.Context(), a.owner, nil) - if err != nil { - apiserver.WriteError(w, http.StatusInternalServerError, err) - return - } - - settlementBalance, err := a.settlementClient.BalanceAt(req.Context(), a.owner, nil) - if err != nil { - apiserver.WriteError(w, http.StatusInternalServerError, err) - return - } - - if err := apiserver.WriteResponse(w, http.StatusOK, struct { - BidsAttempted int64 `json:"bidsAttempted"` - BidsSucceeded int64 `json:"bidsSucceeded"` - TransfersAttempted int64 `json:"transfersAttempted"` - TransfersSucceeded int64 `json:"transfersSucceeded"` - BridgedAmount string `json:"bridgedAmount"` - BidAmountSpent string `json:"bidAmountSpent"` - FeesAccumulated string `json:"feesAccumulated"` - L1Balance string `json:"l1Balance"` - SettlementBalance string `json:"settlementBalance"` - }{ - BidsAttempted: a.status.bidsAttempted.Load(), - BidsSucceeded: a.status.bidsSucceeded.Load(), - TransfersAttempted: a.status.transfersAttempted.Load(), - TransfersSucceeded: a.status.transfersSucceeded.Load(), - BridgedAmount: bridgedAmount.String(), - BidAmountSpent: bidAmountSpent.String(), - FeesAccumulated: feesAccumulated.String(), - L1Balance: l1Balance.String(), - SettlementBalance: settlementBalance.String(), - }); err != nil { - a.logger.Error("failed to write response", "error", err) - } - }) - - a.mux.HandleFunc("POST /bid", func(w http.ResponseWriter, req *http.Request) { - b, err := apiserver.BindJSON[bid](w, req) - if err != nil { - apiserver.WriteError(w, http.StatusBadRequest, err) - return - } - - if b.RawTx == "" || b.BridgeAmount == "" { - apiserver.WriteError(w, http.StatusBadRequest, errors.New("missing fields")) - return - } - - 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 - } - - bridgeAmt, ok := new(big.Int).SetString(b.BridgeAmount, 10) - if !ok { - apiserver.WriteError(w, http.StatusBadRequest, errors.New("invalid bridge amount")) - return - } - - minCost := new(big.Int).Add(bridgeAmt, a.minServiceFee) - if tx.Value().Cmp(minCost) < 0 { - diff := new(big.Int).Sub(minCost, tx.Value()) - apiserver.WriteError( - w, - http.StatusBadRequest, - fmt.Errorf("insufficient funds; short by %s", diff.String()), - ) - return - } - - fees := new(big.Int).Sub(tx.Value(), bridgeAmt) - halfFee := big.NewInt(0).Div(fees, big.NewInt(2)) - - var destAddr common.Address - if b.DestAddress == "" { - destAddr, err = a.transferer.Sender(tx) - if err != nil { - apiserver.WriteError( - w, - http.StatusBadRequest, - fmt.Errorf("failed to identify sender: %w", err), - ) - return - } - } else { - destAddr = common.HexToAddress(b.DestAddress) - } - - a.status.bidsAttempted.Add(1) - statusC, err := a.bidder.Bid( - req.Context(), - halfFee, - bridgeAmt, - // bridgeAmt, - b.RawTx, - nil, - ) - if err != nil { - apiserver.WriteError(w, http.StatusInternalServerError, err) - return - } - - for status := range statusC { - switch status.Type { - case bidder.BidStatusNoOfProviders: - a.logger.Info("no of providers", "count", status.Arg.(int)) - case bidder.BidStatusWaitSecs: - a.logger.Info("waiting for next slot", "seconds", status.Arg.(int)) - case bidder.BidStatusAttempted: - a.logger.Info("bid attempted", "block", status.Arg) - case bidder.BidStatusFailed: - apiserver.WriteError( - w, - http.StatusInternalServerError, - fmt.Errorf("bid failed: %s", status.Arg.(string)), - ) - return - } - } - - a.status.bidsSucceeded.Add(1) - - a.status.transfersAttempted.Add(1) - err = a.transferer.Transfer( - req.Context(), - destAddr, - a.settlementChainID, - bridgeAmt, - ) - if err != nil { - apiserver.WriteError(w, http.StatusInternalServerError, err) - return - } - a.status.transfersSucceeded.Add(1) - a.status.bridgedAmount.Store(new(big.Int).Add(a.status.bridgedAmount.Load(), bridgeAmt)) - a.status.bidAmountSpent.Store(new(big.Int).Add(a.status.bidAmountSpent.Load(), halfFee)) - a.status.feesAccumulated.Store(new(big.Int).Add(a.status.feesAccumulated.Load(), halfFee)) - - if err := apiserver.WriteResponse(w, http.StatusOK, struct { - Message string `json:"message"` - }{ - Message: "success", - }); err != nil { - a.logger.Error("failed to write response", "error", err) - } - }) - - return a -} - -func (a *API) Start() { - a.srv = &http.Server{ - Addr: fmt.Sprintf(":%d", a.port), - Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - recorder := &responseStatusRecorder{ResponseWriter: w} - - start := time.Now() - a.mux.ServeHTTP(recorder, req) - a.logger.Info( - "api access", - slog.Int("http_status", recorder.status), - slog.String("http_method", req.Method), - slog.String("path", req.URL.Path), - slog.Duration("duration", time.Since(start)), - ) - }), - } - - go func() { - if err := a.srv.ListenAndServe(); err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) - } - }() -} - -func (a *API) Close() error { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - return a.srv.Shutdown(ctx) -} - -type responseStatusRecorder struct { - http.ResponseWriter - status int -} - -func (r *responseStatusRecorder) WriteHeader(status int) { - r.status = status - r.ResponseWriter.WriteHeader(status) -} - -// Hijack implements http.Hijacker. -func (r *responseStatusRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return r.ResponseWriter.(http.Hijacker).Hijack() -} - -// Flush implements http.Flusher. -func (r *responseStatusRecorder) Flush() { - r.ResponseWriter.(http.Flusher).Flush() -} - -// Push implements http.Pusher. -func (r *responseStatusRecorder) Push(target string, opts *http.PushOptions) error { - return r.ResponseWriter.(http.Pusher).Push(target, opts) -} diff --git a/tools/instant-bridge/main.go b/tools/instant-bridge/main.go deleted file mode 100644 index edd3415a9..000000000 --- a/tools/instant-bridge/main.go +++ /dev/null @@ -1,266 +0,0 @@ -package main - -import ( - "fmt" - "math/big" - "os" - "os/signal" - "slices" - "strings" - "syscall" - - "github.com/ethereum/go-ethereum/common" - "github.com/primev/mev-commit/tools/instant-bridge/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 - } - - optionMinServiceFee = &cli.StringFlag{ - Name: "min-service-fee", - Usage: "minimum service fee", - EnvVars: []string{"INSTANT_BRIDGE_MIN_SERVICE_FEE"}, - Value: "50000000000000000", // 0.05 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: "instant-bridge", - Usage: "Instant Bridge service", - Flags: []cli.Flag{ - optionHTTPPort, - optionLogFmt, - optionLogLevel, - optionLogTags, - optionKeystorePath, - optionKeystorePassword, - optionL1RPCUrls, - optionSettlementRPCUrl, - optionBidderRPCUrl, - optionL1ContractAddr, - optionSettlementThreshold, - optionSettlementTopup, - optionMinServiceFee, - 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) - } - - minServiceFee, ok := new(big.Int).SetString(c.String(optionMinServiceFee.Name), 10) - if !ok { - return fmt.Errorf("failed to parse min-service-fee") - } - - 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, - MinServiceFee: minServiceFee, - 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/instant-bridge/service/service.go b/tools/instant-bridge/service/service.go deleted file mode 100644 index db33b9380..000000000 --- a/tools/instant-bridge/service/service.go +++ /dev/null @@ -1,218 +0,0 @@ -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/api" - "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" -) - -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.DepositManagerStatus(context.Background(), &bidderapiv1.DepositManagerStatusRequest{}) - if err != nil { - return nil, err - } - if !status.Enabled { - resp, err := bidderCli.EnableDepositManager(context.Background(), &bidderapiv1.EnableDepositManagerRequest{}) - if err != nil { - return nil, err - } - if !resp.Success { - return nil, errors.New("failed to enable deposit manager") - } - } - config.Logger.Info("deposit manager enabled") - - validProviders, err := bidderCli.GetValidProviders(context.Background(), &bidderapiv1.GetValidProvidersRequest{}) - if err != nil { - return nil, err - } - if len(validProviders.ValidProviders) == 0 { - return nil, errors.New("no connected and valid providers found") - } - - targetDeposits := make([]*bidderapiv1.TargetDeposit, len(validProviders.ValidProviders)) - for i, provider := range validProviders.ValidProviders { - targetDeposits[i] = &bidderapiv1.TargetDeposit{ - Provider: provider, - TargetDeposit: config.AutoDepositAmount.String(), - } - } - - resp, err := bidderCli.SetTargetDeposits(context.Background(), &bidderapiv1.SetTargetDepositsRequest{ - TargetDeposits: targetDeposits, - }) - if err != nil { - return nil, err - } - if len(resp.SuccessfullySetDeposits) != len(targetDeposits) { - return nil, errors.New("failed to set target deposits") - } - - 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, - 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, - l1ChainID, - settlementChainID, - ) - - 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/opt-in-bidder/bidder.go b/tools/preconf-rpc/bidder/bidder.go similarity index 92% rename from x/opt-in-bidder/bidder.go rename to tools/preconf-rpc/bidder/bidder.go index cac41c4b4..607d8ee2c 100644 --- a/x/opt-in-bidder/bidder.go +++ b/tools/preconf-rpc/bidder/bidder.go @@ -1,4 +1,4 @@ -package optinbidder +package bidder import ( "context" @@ -14,6 +14,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" + "google.golang.org/grpc/metadata" ) const ( @@ -171,8 +172,7 @@ func parseEpochInfo(msg *notificationsapiv1.Notification) (*epochInfo, error) { type BidStatusType int const ( - BidStatusNoOfProviders BidStatusType = iota - BidStatusWaitSecs + BidStatusWaitSecs BidStatusType = iota BidStatusAttempted BidStatusFailed BidStatusCancelled @@ -190,6 +190,7 @@ type BidOpts struct { RevertingTxHashes []string DecayDuration time.Duration Constraint *bidderapiv1.PositionConstraint + IgnoreProviders []string } var defaultBidOpts = &BidOpts{ @@ -207,17 +208,6 @@ func (b *BidderClient) Bid( opts = defaultBidOpts } - 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, ErrNoProviders - } - // Channel length chosen is 3 so that sending the bid is not blocked by the first // status message. res := make(chan BidStatus, 3) @@ -227,8 +217,6 @@ func (b *BidderClient) Bid( defer close(res) defer b.bigWg.Done() - res <- BidStatus{Type: BidStatusNoOfProviders, Arg: len(providers.Values)} - if opts.WaitForOptIn { nextSlot, err := b.getNextSlot() if err != nil { @@ -261,9 +249,9 @@ func (b *BidderClient) Bid( return } blkNumber = bNo + 1 + res <- BidStatus{Type: BidStatusAttempted, Arg: blkNumber} } - res <- BidStatus{Type: BidStatusAttempted, Arg: blkNumber} b.logger.Info( "attempting to send bid", "blockNumber", blkNumber, @@ -298,6 +286,15 @@ func (b *BidderClient) Bid( } } + if len(opts.IgnoreProviders) > 0 { + var pairs []string + for _, ip := range opts.IgnoreProviders { + pairs = append(pairs, "ignore-provider", ip) + } + md := metadata.Pairs(pairs...) + ctx = metadata.NewOutgoingContext(ctx, md) + } + pc, err := b.bidderClient.SendBid(ctx, bidReq) if err != nil { b.logger.Error("failed to send bid", "error", err) @@ -333,6 +330,25 @@ func (b *BidderClient) Bid( return res, nil } +func (b *BidderClient) ConnectedProviders(ctx context.Context) ([]string, error) { + topo, err := b.topologyClient.GetTopology(ctx, &debugapiv1.EmptyMessage{}) + if err != nil { + b.logger.Error("failed to get topology", "error", err) + return []string{}, err + } + + providers := topo.Topology.Fields["connected_providers"].GetListValue() + if providers == nil { + return []string{}, nil + } + + prvs := make([]string, 0, len(providers.Values)) + for _, p := range providers.Values { + prvs = append(prvs, p.GetStringValue()) + } + return prvs, nil +} + func (b *BidderClient) Estimate() (int64, error) { nextSlot, err := b.getNextSlot() if err != nil { diff --git a/x/opt-in-bidder/bidder_test.go b/tools/preconf-rpc/bidder/bidder_test.go similarity index 89% rename from x/opt-in-bidder/bidder_test.go rename to tools/preconf-rpc/bidder/bidder_test.go index 9a8bdb81b..16a909576 100644 --- a/x/opt-in-bidder/bidder_test.go +++ b/tools/preconf-rpc/bidder/bidder_test.go @@ -1,4 +1,4 @@ -package optinbidder_test +package bidder_test import ( "context" @@ -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" + "github.com/primev/mev-commit/tools/preconf-rpc/bidder" "github.com/primev/mev-commit/x/util" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/structpb" @@ -118,7 +118,7 @@ func TestBidderClient(t *testing.T) { now: clock, } - optinbidder.SetNowFunc(timeSetter.Now) + bidder.SetNowFunc(timeSetter.Now) topoVal, err := structpb.NewStruct(map[string]interface{}{ "connected_providers": []any{"provider1", "provider2"}, @@ -137,7 +137,7 @@ func TestBidderClient(t *testing.T) { } blockNumberGetter := &testBlockNumberGetter{blockNumber: 10} - bidderClient := optinbidder.NewBidderClient( + bidderClient := bidder.NewBidderClient( util.NewTestLogger(os.Stdout), rpcServices, rpcServices, @@ -149,8 +149,8 @@ func TestBidderClient(t *testing.T) { done := bidderClient.Start(ctx) _, err = bidderClient.Estimate() - if err != optinbidder.ErrNoEpochInfo { - t.Fatalf("expected error %v, got %v", optinbidder.ErrNoEpochInfo, err) + if err != bidder.ErrNoEpochInfo { + t.Fatalf("expected error %v, got %v", bidder.ErrNoEpochInfo, err) } // Send a notification. @@ -195,15 +195,18 @@ func TestBidderClient(t *testing.T) { _, _ = rand.Read(buf) txString := hex.EncodeToString(buf) - _, err = bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString, nil) - if err == nil { - t.Fatal("expected error, got nil") - } - rpcServices.topo = &debugapiv1.TopologyResponse{ Topology: topoVal, } + providers, err := bidderClient.ConnectedProviders(ctx) + if err != nil { + t.Fatal(err) + } + if len(providers) != 2 { + t.Fatalf("expected 2 providers, got %d", len(providers)) + } + statusC, err := bidderClient.Bid(ctx, big.NewInt(1), big.NewInt(1), txString, nil) if err != nil { t.Fatal(err) @@ -218,19 +221,15 @@ waitLoop: break waitLoop } switch status.Type { - case optinbidder.BidStatusNoOfProviders: - if status.Arg.(int) != 2 { - t.Fatalf("expected 2 providers, got %d", status.Arg) - } - case optinbidder.BidStatusWaitSecs: + case bidder.BidStatusWaitSecs: if status.Arg.(int) != 2 { t.Fatalf("expected 2 seconds, got %d", status.Arg) } - case optinbidder.BidStatusAttempted: + case bidder.BidStatusAttempted: if status.Arg.(uint64) != 11 { t.Fatalf("expected 11, got %d", status.Arg) } - case optinbidder.BidStatusCommitment: + case bidder.BidStatusCommitment: if status.Arg.(*bidderapiv1.Commitment).BlockNumber != 11 { t.Fatalf("expected block number 11, got %d", status.Arg.(*bidderapiv1.Commitment).BlockNumber) } diff --git a/x/opt-in-bidder/export_test.go b/tools/preconf-rpc/bidder/export_test.go similarity index 77% rename from x/opt-in-bidder/export_test.go rename to tools/preconf-rpc/bidder/export_test.go index 31674fc5f..35e7047a0 100644 --- a/x/opt-in-bidder/export_test.go +++ b/tools/preconf-rpc/bidder/export_test.go @@ -1,4 +1,4 @@ -package optinbidder +package bidder import "time" diff --git a/tools/preconf-rpc/blocktracker/blocktracker.go b/tools/preconf-rpc/blocktracker/blocktracker.go index 3c6780efa..c9287f414 100644 --- a/tools/preconf-rpc/blocktracker/blocktracker.go +++ b/tools/preconf-rpc/blocktracker/blocktracker.go @@ -12,11 +12,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" lru "github.com/hashicorp/golang-lru/v2" + "golang.org/x/sync/errgroup" ) type EthClient interface { BlockNumber(ctx context.Context) (uint64, error) BlockByNumber(ctx context.Context, blockNumber *big.Int) (*types.Block, error) + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) } type blockTracker struct { @@ -24,7 +26,9 @@ type blockTracker struct { blocks *lru.Cache[uint64, *types.Block] client EthClient log *slog.Logger - checkCond *sync.Cond + txnToCheckMu sync.Mutex + txnsToCheck map[common.Hash]chan uint64 + newBlockChan chan uint64 } func NewBlockTracker(client EthClient, log *slog.Logger) (*blockTracker, error) { @@ -38,52 +42,89 @@ func NewBlockTracker(client EthClient, log *slog.Logger) (*blockTracker, error) blocks: cache, client: client, log: log, - checkCond: sync.NewCond(&sync.Mutex{}), + txnsToCheck: make(map[common.Hash]chan uint64), + newBlockChan: make(chan uint64, 1), }, nil } func (b *blockTracker) Start(ctx context.Context) <-chan struct{} { done := make(chan struct{}) - ticker := time.NewTicker(500 * time.Millisecond) - go func() { - defer close(done) + eg, egCtx := errgroup.WithContext(ctx) + eg.Go(func() error { + ticker := time.NewTicker(500 * time.Millisecond) for { select { - case <-ctx.Done(): - return + case <-egCtx.Done(): + return egCtx.Err() case <-ticker.C: - blockNo, err := b.client.BlockNumber(ctx) + blockNo, err := b.client.BlockNumber(egCtx) 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))) + block, err := b.client.BlockByNumber(egCtx, big.NewInt(int64(blockNo))) if err != nil { b.log.Error("Failed to get block by number", "error", err) continue } _ = b.blocks.Add(blockNo, block) b.latestBlockNo.Store(block.NumberU64()) - b.triggerCheck() + select { + case b.newBlockChan <- blockNo: + case <-egCtx.Done(): + return egCtx.Err() + } b.log.Debug("New block detected", "number", block.NumberU64(), "hash", block.Hash().Hex()) } } } + }) + eg.Go(func() error { + for { + select { + case <-egCtx.Done(): + return egCtx.Err() + case bNo := <-b.newBlockChan: + block, ok := b.blocks.Get(bNo) + if !ok { + b.log.Error("Block not found in cache", "blockNumber", bNo) + continue + } + for txHash, resultCh := range b.txnsToCheck { + if txn := block.Transaction(txHash); txn != nil { + resultCh <- bNo + close(resultCh) + b.txnToCheckMu.Lock() + delete(b.txnsToCheck, txHash) + b.txnToCheckMu.Unlock() + } + } + } + } + }) + + go func() { + defer close(done) + if err := eg.Wait(); err != nil { + b.log.Error("Block tracker exited with error", "error", err) + } }() - return done -} -func (b *blockTracker) triggerCheck() { - b.checkCond.L.Lock() - b.checkCond.Broadcast() - b.checkCond.L.Unlock() + return done } func (b *blockTracker) LatestBlockNumber() uint64 { return b.latestBlockNo.Load() } +func (b *blockTracker) AccountNonce( + ctx context.Context, + account common.Address, +) (uint64, error) { + return b.client.PendingNonceAt(ctx, account) +} + func (b *blockTracker) NextBlockNumber() (uint64, time.Duration, error) { latestBlockNo := b.latestBlockNo.Load() block, found := b.blocks.Get(latestBlockNo) @@ -91,54 +132,18 @@ func (b *blockTracker) NextBlockNumber() (uint64, time.Duration, error) { return 0, 0, errors.New("latest block not found in cache") } blockTime := time.Unix(int64(block.Time()), 0) - if time.Since(blockTime) >= 11*time.Second { + if time.Since(blockTime) >= 12*time.Second { return latestBlockNo + 2, time.Until(blockTime.Add(24 * time.Second)), nil } return latestBlockNo + 1, time.Until(blockTime.Add(12 * time.Second)), nil } -func (b *blockTracker) CheckTxnInclusion( - ctx context.Context, +func (b *blockTracker) WaitForTxnInclusion( txHash common.Hash, - blockNumber uint64, -) (bool, error) { - if blockNumber <= b.latestBlockNo.Load() { - return b.checkTxnInclusion(ctx, txHash, blockNumber) - } - - waitCh := make(chan struct{}) - go func() { - b.checkCond.L.Lock() - defer b.checkCond.L.Unlock() - for blockNumber > b.latestBlockNo.Load() { - b.checkCond.Wait() - } - close(waitCh) - }() - - select { - case <-ctx.Done(): - return false, ctx.Err() - case <-waitCh: - return b.checkTxnInclusion(ctx, txHash, blockNumber) - } -} - -func (b *blockTracker) checkTxnInclusion(ctx context.Context, txHash common.Hash, blockNumber uint64) (bool, error) { - var err error - block, ok := b.blocks.Get(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.Add(blockNumber, block) - } - - if txn := block.Transaction(txHash); txn != nil { - return true, nil - } - - return false, nil +) chan uint64 { + resultCh := make(chan uint64, 1) + b.txnToCheckMu.Lock() + b.txnsToCheck[txHash] = resultCh + b.txnToCheckMu.Unlock() + return resultCh } diff --git a/tools/preconf-rpc/blocktracker/blocktracker_test.go b/tools/preconf-rpc/blocktracker/blocktracker_test.go index 54f395de9..858ce6459 100644 --- a/tools/preconf-rpc/blocktracker/blocktracker_test.go +++ b/tools/preconf-rpc/blocktracker/blocktracker_test.go @@ -37,6 +37,10 @@ func (m *mockEthClient) BlockByNumber(ctx context.Context, blockNumber *big.Int) return block, nil } +func (m *mockEthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + return 0, nil +} + type testHasher struct { hasher hash.Hash } @@ -112,6 +116,11 @@ func TestBlockTracker(t *testing.T) { t.Fatalf("Expected latest block number to be 0, got %d", blkNo) } + included1 := tracker.WaitForTxnInclusion(tx1.Hash()) + included2 := tracker.WaitForTxnInclusion(tx2.Hash()) + included3 := tracker.WaitForTxnInclusion(tx3.Hash()) + included4 := tracker.WaitForTxnInclusion(tx4.Hash()) + client.blockNumber <- 100 start := time.Now() @@ -134,13 +143,13 @@ func TestBlockTracker(t *testing.T) { time.Sleep(100 * time.Millisecond) } - included, err := tracker.CheckTxnInclusion(ctx, tx1.Hash(), 100) - if err != nil { - t.Fatalf("Error checking transaction inclusion: %v", err) + bNo1 := <-included1 + if bNo1 != 100 { + t.Fatalf("Expected transaction %s to be included in block 100, got %d", tx1.Hash().Hex(), bNo1) } - - if !included { - t.Fatalf("Expected transaction %s to be included in block 100", tx1.Hash().Hex()) + bNo2 := <-included2 + if bNo2 != 100 { + t.Fatalf("Expected transaction %s to be included in block 100, got %d", tx2.Hash().Hex(), bNo2) } blkNo = tracker.LatestBlockNumber() @@ -166,13 +175,16 @@ func TestBlockTracker(t *testing.T) { time.Sleep(100 * time.Millisecond) } - included, err = tracker.CheckTxnInclusion(ctx, tx4.Hash(), 101) - if err != nil { - t.Fatalf("Error checking transaction inclusion: %v", err) + bNo3 := <-included3 + if bNo3 != 101 { + t.Fatalf("Expected transaction %s to be included in block 101, got %d", tx3.Hash().Hex(), bNo3) } - if included { - t.Fatalf("Expected transaction %s not to be included in block 101", tx4.Hash().Hex()) + select { + case bNo4 := <-included4: + t.Fatalf("Did not expect transaction %s to be included, but got block number %d", tx4.Hash().Hex(), bNo4) + case <-time.After(1 * time.Second): + // Expected timeout } cancel() diff --git a/tools/preconf-rpc/handlers/handlers.go b/tools/preconf-rpc/handlers/handlers.go index 1ce0b637a..7c0acf4f6 100644 --- a/tools/preconf-rpc/handlers/handlers.go +++ b/tools/preconf-rpc/handlers/handlers.go @@ -41,6 +41,7 @@ type Store interface { type BlockTracker interface { LatestBlockNumber() uint64 + AccountNonce(ctx context.Context, account common.Address) (uint64, error) } type Sender interface { @@ -506,6 +507,13 @@ func (h *rpcMethodHandler) handleGetTxCount(ctx context.Context, params ...any) accNonce += 1 + backendNonce, err := h.blockTracker.AccountNonce(ctx, common.HexToAddress(account)) + if err == nil { + if backendNonce > accNonce { + accNonce = backendNonce + } + } + nonceJSON, err := json.Marshal(accNonce) if err != nil { h.logger.Error("Failed to marshal nonce to JSON", "error", err, "account", account) diff --git a/tools/preconf-rpc/notifier/notifier.go b/tools/preconf-rpc/notifier/notifier.go index 4060d5d02..d96c4fda8 100644 --- a/tools/preconf-rpc/notifier/notifier.go +++ b/tools/preconf-rpc/notifier/notifier.go @@ -49,6 +49,7 @@ type Field struct { type txnInfo struct { txn *sender.Transaction noOfAttempts int + noOfBlocks int timeTaken time.Duration } @@ -295,6 +296,8 @@ func (n *Notifier) StartTransactionNotifier( Field{Title: "Type", Value: buildType(t), Short: true}, Field{Title: "Attempts", Value: fmt.Sprintf("%d", t.noOfAttempts), Short: true}, Field{Title: "Duration", Value: t.timeTaken.String(), Short: true}, + Field{Title: "Included Block", Value: fmt.Sprintf("%d", t.txn.BlockNumber), Short: true}, + Field{Title: "No. of Blocks to confirm", Value: fmt.Sprintf("%d", t.noOfBlocks), Short: true}, ) if t.txn.Constraint != nil { fields = append(fields, @@ -325,6 +328,7 @@ func (n *Notifier) StartTransactionNotifier( func (n *Notifier) NotifyTransactionStatus( txn *sender.Transaction, noOfAttempts int, + noOfBlocks int, timeTaken time.Duration, ) { n.queuedMu.Lock() diff --git a/tools/preconf-rpc/sender/sender.go b/tools/preconf-rpc/sender/sender.go index 8f781e23d..35b0174d1 100644 --- a/tools/preconf-rpc/sender/sender.go +++ b/tools/preconf-rpc/sender/sender.go @@ -15,7 +15,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" lru "github.com/hashicorp/golang-lru/v2" bidderapiv1 "github.com/primev/mev-commit/p2p/gen/go/bidderapi/v1" - optinbidder "github.com/primev/mev-commit/x/opt-in-bidder" + "github.com/primev/mev-commit/tools/preconf-rpc/bidder" "golang.org/x/sync/errgroup" ) @@ -44,6 +44,7 @@ const ( confidenceSubsequentAttempts = 99 // confidence level for subsequent attempts transactionTimeout = 10 * time.Minute // timeout for transaction processing maxAttemptsPerBlock = 10 // maximum attempts per block + defaultRetryDelay = 500 * time.Millisecond ) var ( @@ -67,6 +68,10 @@ type Transaction struct { Details string BlockNumber int64 Constraint *bidderapiv1.PositionConstraint + // local fields not stored in DB + noOfProviders int + commitments []*bidderapiv1.Commitment + logs []*types.Log } type Store interface { @@ -87,8 +92,9 @@ type Bidder interface { bidAmount *big.Int, slashAmount *big.Int, rawTx string, - opts *optinbidder.BidOpts, - ) (chan optinbidder.BidStatus, error) + opts *bidder.BidOpts, + ) (chan bidder.BidStatus, error) + ConnectedProviders(ctx context.Context) ([]string, error) } type Pricer interface { @@ -96,8 +102,9 @@ type Pricer interface { } type BlockTracker interface { - CheckTxnInclusion(ctx context.Context, txnHash common.Hash, blockNumber uint64) (bool, error) + WaitForTxnInclusion(txnHash common.Hash) chan uint64 NextBlockNumber() (uint64, time.Duration, error) + LatestBlockNumber() uint64 } type Transferer interface { @@ -120,7 +127,7 @@ type txnAttempt struct { } type Notifier interface { - NotifyTransactionStatus(txn *Transaction, noOfAttempts int, timeTaken time.Duration) + NotifyTransactionStatus(txn *Transaction, noOfAttempts, noOfBlocks int, timeTaken time.Duration) } type TxSender struct { @@ -443,19 +450,13 @@ func (t *TxSender) processTransaction(ctx context.Context, txn *Transaction, can "sender", txn.Sender.Hex(), "type", txn.Type, ) -BID_LOOP: - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-cancel: - return ErrTransactionCancelled - default: - } - preConfirmed := false - maxAttemptsPerBlockExceeded := false + retryTicker := time.NewTicker(defaultRetryDelay) + defer retryTicker.Stop() + inclusion := t.blockTracker.WaitForTxnInclusion(txn.Hash()) +BID_LOOP: + for { result, err = t.sendBid(ctx, txn) switch { case err != nil: @@ -465,39 +466,14 @@ BID_LOOP: "error", retryErr.err, "retryAfter", retryErr.retryAfter, ) - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(retryErr.retryAfter): - // Wait for the specified retry duration before retrying - case <-cancel: - return ErrTransactionCancelled - } - continue - } - // If we exceeded max attempts per block, we retry for the next block but - // also check for inclusion in case the transaction got included - if !errors.Is(err, ErrMaxAttemptsPerBlockExceeded) { - return err + retryTicker.Reset(retryErr.retryAfter) + } else if errors.Is(err, ErrMaxAttemptsPerBlockExceeded) { + retryTicker.Reset(result.timeUntillNextBlock + 500*time.Millisecond) } else { - maxAttemptsPerBlockExceeded = true - } - case t.fastTrack(result.commitments, result.optedInSlot): - // If the commitments indicate that the transaction can be fast-tracked, - // we consider it pre-confirmed and skip further checks - txn.Status = TxStatusPreConfirmed - txn.BlockNumber = int64(result.blockNumber) - logger.Info( - "Transaction fast-tracked based on commitments", - "blockNumber", result.blockNumber, - "bidAmount", result.bidAmount.String(), - ) - if err := t.store.StoreTransaction(ctx, txn, result.commitments, result.logs); err != nil { - return fmt.Errorf("failed to store fast-tracked transaction: %w", err) + return err } - preConfirmed = true - case result.optedInSlot: - if result.noOfProviders == len(result.commitments) { + case txn.noOfProviders == len(txn.commitments): + if result.optedInSlot { // 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 @@ -508,52 +484,47 @@ BID_LOOP: "blockNumber", result.blockNumber, "bidAmount", result.bidAmount.String(), ) - if err := t.store.StoreTransaction(ctx, txn, result.commitments, result.logs); err != nil { + if err := t.store.StoreTransaction(ctx, txn, txn.commitments, txn.logs); err != nil { return fmt.Errorf("failed to store preconfirmed transaction: %w", err) } - preConfirmed = true } + retryTicker.Reset(result.timeUntillNextBlock + 1*time.Second) default: - } - - if !preConfirmed && result.noOfProviders > len(result.commitments) && !maxAttemptsPerBlockExceeded { logger.Warn( "Not all builders committed to the bid", - "noOfProviders", result.noOfProviders, - "noOfCommitments", len(result.commitments), + "noOfProviders", txn.noOfProviders, + "noOfCommitments", len(txn.commitments), "blockNumber", result.blockNumber, "bidAmount", result.bidAmount.String(), ) - if (result.timeUntillNextBlock - 2*time.Second) > time.Since(result.startTime) { - // If not all builders committed, we will retry the bid process - // immediately if we have atleast 2 seconds left before the next block - continue - } - } - - // 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 := t.blockTracker.CheckTxnInclusion(ctx, txn.Hash(), result.blockNumber) - if err != nil { - logger.Error("Failed to check transaction inclusion", "error", err) - return fmt.Errorf("failed to check transaction inclusion: %w", err) + retryTicker.Reset(defaultRetryDelay) } - if included { - if !preConfirmed { + select { + case <-ctx.Done(): + return ctx.Err() + case <-cancel: + return ErrTransactionCancelled + case <-retryTicker.C: + // Continue to the next iteration after the retry delay + case bNo := <-inclusion: + if txn.Status != TxStatusPreConfirmed { + // It could happen that the transaction got included but we got the signal + // late and made a failed attempt. So we should update the commitments and + // logs from the last successful bid attempt. txn.Status = TxStatusConfirmed - txn.BlockNumber = int64(result.blockNumber) + txn.BlockNumber = int64(bNo) logger.Info( "Transaction confirmed", - "blockNumber", result.blockNumber, + "blockNumber", bNo, "bidAmount", result.bidAmount.String(), ) - if err := t.store.StoreTransaction(ctx, txn, result.commitments, result.logs); err != nil { + if err := t.store.StoreTransaction(ctx, txn, txn.commitments, txn.logs); err != nil { return fmt.Errorf("failed to store preconfirmed transaction: %w", err) } } endTime := time.Now() - if len(result.commitments) > 0 { - endTime = time.UnixMilli(result.commitments[len(result.commitments)-1].DispatchTimestamp) + if len(txn.commitments) > 0 { + endTime = time.UnixMilli(txn.commitments[0].DispatchTimestamp) } t.clearBlockAttemptHistory(txn, endTime) break BID_LOOP @@ -595,12 +566,9 @@ func (e *errRetry) Error() string { type bidResult struct { startTime time.Time timeUntillNextBlock time.Duration - noOfProviders int blockNumber uint64 optedInSlot bool bidAmount *big.Int - commitments []*bidderapiv1.Commitment - logs []*types.Log } func (t *TxSender) sendBid( @@ -623,12 +591,6 @@ func (t *TxSender) sendBid( timeToOptIn = blockTime * 32 } - logs, err := t.simulator.Simulate(ctx, txn.Raw) - if err != nil { - logger.Error("Failed to simulate transaction", "error", err) - return bidResult{}, fmt.Errorf("failed to simulate transaction: %w", err) - } - bidBlockNo, timeUntilNextBlock, err := t.blockTracker.NextBlockNumber() if err != nil { logger.Error("Failed to get next block number", "error", err) @@ -638,11 +600,11 @@ func (t *TxSender) sendBid( } } - if timeUntilNextBlock <= time.Second { + if timeUntilNextBlock <= 500*time.Millisecond { logger.Warn("Next block time is too short, skipping bid", "timeUntilNextBlock", timeUntilNextBlock) return bidResult{}, &errRetry{ err: fmt.Errorf("next block time is too short: %s", timeUntilNextBlock), - retryAfter: time.Second, + retryAfter: defaultRetryDelay, } } @@ -654,7 +616,7 @@ func (t *TxSender) sendBid( cctx, cancel := context.WithTimeout(ctx, t.getBidTimeout()) defer cancel() - cost, err := t.calculatePriceForNextBlock(txn, bidBlockNo, prices, optedInSlot) + cost, isRetry, err := t.calculatePriceForNextBlock(txn, bidBlockNo, prices, optedInSlot) if err != nil { logger.Error("Failed to calculate price for next block", "error", err) if errors.Is(err, ErrTimeoutExceeded) || errors.Is(err, ErrMaxAttemptsPerBlockExceeded) { @@ -667,6 +629,17 @@ func (t *TxSender) sendBid( } } + var ignoreProviders []string + if isRetry && len(txn.commitments) > 0 { + for _, cmt := range txn.commitments { + ignoreProviders = append(ignoreProviders, cmt.ProviderAddress) + } + logger.Info( + "Retrying bid, ignoring previously committed providers", + "ignoreProviders", ignoreProviders, + ) + } + slashAmount := big.NewInt(0) switch txn.Type { case TxTypeRegular: @@ -706,17 +679,48 @@ func (t *TxSender) sendBid( slashAmount = new(big.Int).Set(txn.Value()) } + if !isRetry { + logs, err := t.simulator.Simulate(ctx, txn.Raw) + if err != nil { + if t.blockTracker.LatestBlockNumber() < bidBlockNo { + logger.Warn( + "Simulation failed, but block may not be mined yet, will retry", + "error", err, + "blockNumber", bidBlockNo, + ) + return bidResult{}, &errRetry{ + err: fmt.Errorf("simulation may have failed due to unmined block: %w", err), + retryAfter: time.Second, + } + } + logger.Error("Failed to simulate transaction", "error", err, "blockNumber", bidBlockNo) + return bidResult{}, fmt.Errorf("failed to simulate transaction: %w", err) + } + providers, err := t.bidder.ConnectedProviders(ctx) + if err != nil { + logger.Error("Failed to get connected providers", "error", err) + return bidResult{}, fmt.Errorf("failed to get connected providers: %w", err) + } + txn.logs = logs + txn.noOfProviders = len(providers) + // We could have already made a attempt on the previous block but the block + // update hasn't happened yet. This means that the bid might fail, but + // we should retain the previous commitments. Only clear if we get new + // commitments for the new block. + } + bidC, err := t.bidder.Bid( cctx, cost, slashAmount, strings.TrimPrefix(txn.Raw, "0x"), - &optinbidder.BidOpts{ + &bidder.BidOpts{ WaitForOptIn: false, BlockNumber: uint64(bidBlockNo), RevertingTxHashes: []string{txn.Hash().Hex()}, DecayDuration: t.getBidTimeout() * 2, Constraint: txn.Constraint, + IgnoreProviders: ignoreProviders, }, ) if err != nil { @@ -725,11 +729,10 @@ func (t *TxSender) sendBid( } result := bidResult{ - commitments: make([]*bidderapiv1.Commitment, 0), bidAmount: cost, + blockNumber: bidBlockNo, startTime: start, timeUntillNextBlock: timeUntilNextBlock, - logs: logs, } BID_LOOP: for { @@ -743,16 +746,29 @@ BID_LOOP: break BID_LOOP } switch bidStatus.Type { - case optinbidder.BidStatusNoOfProviders: - result.noOfProviders = bidStatus.Arg.(int) - case optinbidder.BidStatusAttempted: - result.blockNumber = bidStatus.Arg.(uint64) - case optinbidder.BidStatusCommitment: - result.commitments = append(result.commitments, bidStatus.Arg.(*bidderapiv1.Commitment)) - case optinbidder.BidStatusCancelled: + case bidder.BidStatusCommitment: + if len(txn.commitments) > 0 { + if txn.commitments[0].BlockNumber != int64(bidBlockNo) { + txn.commitments = nil // clear previous commitments for new block + } + } + txn.commitments = append(txn.commitments, bidStatus.Arg.(*bidderapiv1.Commitment)) + if t.fastTrack(txn.commitments, optedInSlot) && txn.Status != TxStatusPreConfirmed { + txn.Status = TxStatusPreConfirmed + txn.BlockNumber = int64(bidBlockNo) + logger.Info( + "Transaction fast-tracked based on commitments", + "blockNumber", result.blockNumber, + "bidAmount", result.bidAmount.String(), + ) + if err := t.store.StoreTransaction(ctx, txn, txn.commitments, txn.logs); err != nil { + logger.Error("Failed to store fast-tracked transaction", "error", err) + } + } + case bidder.BidStatusCancelled: logger.Warn("Bid context cancelled by the bidder") break BID_LOOP - case optinbidder.BidStatusFailed: + case bidder.BidStatusFailed: logger.Error("Bid failed", "error", bidStatus.Arg) break BID_LOOP } @@ -760,8 +776,8 @@ BID_LOOP: } logger.Info( "Bid operation complete", - "noOfProviders", result.noOfProviders, - "noOfCommitments", len(result.commitments), + "noOfProviders", txn.noOfProviders, + "noOfCommitments", len(txn.commitments), "blockNumber", result.blockNumber, "optedInSlot", optedInSlot, ) @@ -775,7 +791,7 @@ func (t *TxSender) calculatePriceForNextBlock( bidBlockNo uint64, prices map[int64]float64, optedInSlot bool, -) (*big.Int, error) { +) (*big.Int, bool, error) { attempts, found := t.txnAttemptHistory.Get(txn.Hash()) if !found { attempts = &txnAttempt{ @@ -785,7 +801,7 @@ func (t *TxSender) calculatePriceForNextBlock( } if time.Since(attempts.startTime) > transactionTimeout { - return nil, ErrTimeoutExceeded + return nil, false, ErrTimeoutExceeded } // default confidence level for the next block @@ -805,7 +821,7 @@ func (t *TxSender) calculatePriceForNextBlock( case attempts.attempts[i].attempts > 2: confidence = confidenceSubsequentAttempts case attempts.attempts[i].attempts > maxAttemptsPerBlock: - return nil, fmt.Errorf("%w: block %d", ErrMaxAttemptsPerBlockExceeded, bidBlockNo) + return nil, false, fmt.Errorf("%w: block %d", ErrMaxAttemptsPerBlockExceeded, bidBlockNo) } break // No need to check further attempts for the same block } @@ -829,11 +845,11 @@ func (t *TxSender) calculatePriceForNextBlock( if conf == int64(confidence) { // the gwei value is in float, so we need to convert it to wei before multiplying with gas limit priceInWei := price * 1e9 // Convert Gwei to Wei - return new(big.Int).Mul(big.NewInt(int64(priceInWei)), big.NewInt(int64(txn.Gas()))), nil + return new(big.Int).Mul(big.NewInt(int64(priceInWei)), big.NewInt(int64(txn.Gas()))), isRetry, nil } } - return nil, fmt.Errorf( + return nil, false, fmt.Errorf( "no estimated price found for block %d with confidence %d", bidBlockNo, confidence, ) } @@ -857,9 +873,11 @@ func (t *TxSender) clearBlockAttemptHistory(txn *Transaction, endTime time.Time) "startTime", attempts.startTime.Format(time.RFC3339), "startBlockNumber", attempts.attempts[0].blockNumber, "totalAttempts", totalAttempts, + "totalBlockAttempts", len(attempts.attempts), ) _ = t.txnAttemptHistory.Remove(txn.Hash()) - t.notifier.NotifyTransactionStatus(txn, totalAttempts, endTime.Sub(attempts.startTime).Round(time.Millisecond)) + timeTaken := endTime.Sub(attempts.startTime).Round(time.Millisecond) + t.notifier.NotifyTransactionStatus(txn, totalAttempts, len(attempts.attempts), timeTaken) } diff --git a/tools/preconf-rpc/sender/sender_test.go b/tools/preconf-rpc/sender/sender_test.go index 806e91b6b..c9929a5c2 100644 --- a/tools/preconf-rpc/sender/sender_test.go +++ b/tools/preconf-rpc/sender/sender_test.go @@ -3,6 +3,7 @@ package sender_test import ( "context" "errors" + "io" "math/big" "os" "sync" @@ -12,8 +13,8 @@ import ( "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/bidder" "github.com/primev/mev-commit/tools/preconf-rpc/sender" - optinbidder "github.com/primev/mev-commit/x/opt-in-bidder" "github.com/primev/mev-commit/x/util" ) @@ -164,13 +165,13 @@ type bidOp struct { bidAmount *big.Int slashAmount *big.Int rawTx string - opts *optinbidder.BidOpts + opts *bidder.BidOpts } type mockBidder struct { optinEstimate chan int64 in chan bidOp - out chan chan optinbidder.BidStatus + out chan chan bidder.BidStatus } func (m *mockBidder) Estimate() (int64, error) { @@ -183,8 +184,8 @@ func (m *mockBidder) Bid( bidAmount *big.Int, slashAmount *big.Int, rawTx string, - opts *optinbidder.BidOpts, -) (chan optinbidder.BidStatus, error) { + opts *bidder.BidOpts, +) (chan bidder.BidStatus, error) { m.in <- bidOp{ bidAmount: bidAmount, slashAmount: slashAmount, @@ -196,6 +197,10 @@ func (m *mockBidder) Bid( return res, nil } +func (m *mockBidder) ConnectedProviders(ctx context.Context) ([]string, error) { + return []string{"provider1", "provider2"}, nil +} + type mockPricer struct { out chan map[int64]float64 } @@ -212,35 +217,25 @@ func (m *mockPricer) EstimatePrice(ctx context.Context) map[int64]float64 { } } -type op struct { - hash common.Hash - block uint64 -} - type blockNoOp struct { block uint64 timeTillNextBlock time.Duration } type mockBlockTracker struct { - in chan op - out chan bool + out chan uint64 bnIn chan struct{} bnOut chan blockNoOp bnErr chan error } -func (m *mockBlockTracker) CheckTxnInclusion(ctx context.Context, txnHash common.Hash, blockNumber uint64) (bool, error) { - m.in <- op{ - hash: txnHash, - block: blockNumber, - } - select { - case included := <-m.out: - return included, nil - case <-ctx.Done(): - return false, ctx.Err() - } +func (m *mockBlockTracker) WaitForTxnInclusion(txnHash common.Hash) chan uint64 { + includedCh := make(chan uint64, 1) + go func() { + included := <-m.out + includedCh <- included + }() + return includedCh } func (m *mockBlockTracker) NextBlockNumber() (uint64, time.Duration, error) { @@ -254,6 +249,10 @@ func (m *mockBlockTracker) NextBlockNumber() (uint64, time.Duration, error) { } } +func (m *mockBlockTracker) LatestBlockNumber() uint64 { + return 0 +} + type mockTransferer struct{} func (m *mockTransferer) Transfer(ctx context.Context, to common.Address, chainID *big.Int, amount *big.Int) error { @@ -264,7 +263,7 @@ type mockNotifier struct { notifications []string } -func (m *mockNotifier) NotifyTransactionStatus(txn *sender.Transaction, attempts int, start time.Duration) { +func (m *mockNotifier) NotifyTransactionStatus(txn *sender.Transaction, attempts, blocks int, start time.Duration) { m.notifications = append(m.notifications, txn.Hash().Hex()) } @@ -281,14 +280,13 @@ func TestSender(t *testing.T) { testPricer := &mockPricer{ out: make(chan map[int64]float64, 10), } - bidder := &mockBidder{ + bidderImpl := &mockBidder{ optinEstimate: make(chan int64, 10), in: make(chan bidOp, 10), - out: make(chan chan optinbidder.BidStatus, 10), + out: make(chan chan bidder.BidStatus, 10), } blockTracker := &mockBlockTracker{ - in: make(chan op, 10), - out: make(chan bool, 10), + out: make(chan uint64, 10), bnIn: make(chan struct{}, 10), bnOut: make(chan blockNoOp, 10), bnErr: make(chan error, 1), @@ -297,7 +295,7 @@ func TestSender(t *testing.T) { sndr, err := sender.NewTxSender( st, - bidder, + bidderImpl, testPricer, blockTracker, &mockTransferer{}, @@ -337,12 +335,12 @@ func TestSender(t *testing.T) { } // Simulate opted in block - bidder.optinEstimate <- 2 + bidderImpl.optinEstimate <- 2 <-blockTracker.bnIn blockTracker.bnErr <- errors.New("simulated error for testing") - bidder.optinEstimate <- 7 + bidderImpl.optinEstimate <- 7 <-blockTracker.bnIn @@ -358,22 +356,17 @@ func TestSender(t *testing.T) { 99: 2.0, } + // Simulate transaction inclusion + blockTracker.out <- 1 + // Simulate a bid response - bidOp := <-bidder.in + bidOp := <-bidderImpl.in if bidOp.rawTx != tx1.Raw[2:] { t.Fatalf("expected raw transaction %s, got %s", tx1.Raw, bidOp.rawTx) } - resC := make(chan optinbidder.BidStatus, 3) - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusNoOfProviders, - Arg: 1, - } - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusAttempted, - Arg: uint64(1), - } - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusCommitment, + resC := make(chan bidder.BidStatus, 3) + resC <- bidder.BidStatus{ + Type: bidder.BidStatusCommitment, Arg: &bidderapiv1.Commitment{ TxHashes: []string{tx1.Hash().Hex()}, BidAmount: big.NewInt(100).String(), @@ -381,8 +374,17 @@ func TestSender(t *testing.T) { ProviderAddress: "provider1", }, } + resC <- bidder.BidStatus{ + Type: bidder.BidStatusCommitment, + Arg: &bidderapiv1.Commitment{ + TxHashes: []string{tx1.Hash().Hex()}, + BidAmount: big.NewInt(100).String(), + BlockNumber: 1, + ProviderAddress: "provider2", + }, + } close(resC) - bidder.out <- resC + bidderImpl.out <- resC res := <-st.preconfirmedTxns if res.txn == nil { @@ -404,19 +406,9 @@ func TestSender(t *testing.T) { t.Fatalf("expected transaction hash %s, got %s", tx1.Hash().Hex(), res.txn.Hash().Hex()) } // Check that the commitments are as expected - if len(res.commitments) != 1 { - t.Fatalf("expected 1 commitment, got %d", len(res.commitments)) - } - - checkOp := <-blockTracker.in - if checkOp.hash != tx1.Hash() { - t.Fatalf("expected transaction hash %s, got %s", tx1.Hash().Hex(), checkOp.hash.Hex()) - } - if checkOp.block != 1 { - t.Fatalf("expected block number 1, got %d", checkOp.block) + if len(res.commitments) != 2 { + t.Fatalf("expected 2 commitments, got %d", len(res.commitments)) } - // Simulate transaction inclusion - blockTracker.out <- true tx2 := &sender.Transaction{ Transaction: types.NewTransaction( @@ -437,7 +429,7 @@ func TestSender(t *testing.T) { } // Simulate non opted in block - bidder.optinEstimate <- 20 + bidderImpl.optinEstimate <- 20 <-blockTracker.bnIn blockTracker.bnOut <- blockNoOp{ @@ -453,25 +445,17 @@ func TestSender(t *testing.T) { } // Simulate a bid response - bidOp = <-bidder.in + bidOp = <-bidderImpl.in if bidOp.rawTx != tx2.Raw[2:] { t.Fatalf("expected raw transaction %s, got %s", tx1.Raw, bidOp.rawTx) } - resC = make(chan optinbidder.BidStatus, 3) - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusNoOfProviders, - Arg: 1, - } - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusAttempted, - Arg: uint64(2), - } + resC = make(chan bidder.BidStatus, 3) // Simulate retry due to incomplete commitments close(resC) - bidder.out <- resC + bidderImpl.out <- resC // Simulate non opted in block - bidder.optinEstimate <- 18 + bidderImpl.optinEstimate <- 18 <-blockTracker.bnIn blockTracker.bnOut <- blockNoOp{ @@ -486,22 +470,17 @@ func TestSender(t *testing.T) { 99: 2.0, } + // Simulate transaction inclusion + blockTracker.out <- 2 + // Simulate a bid response - bidOp = <-bidder.in + bidOp = <-bidderImpl.in if bidOp.rawTx != tx2.Raw[2:] { t.Fatalf("expected raw transaction %s, got %s", tx1.Raw, bidOp.rawTx) } - resC = make(chan optinbidder.BidStatus, 3) - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusNoOfProviders, - Arg: 1, - } - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusAttempted, - Arg: uint64(2), - } - resC <- optinbidder.BidStatus{ - Type: optinbidder.BidStatusCommitment, + resC = make(chan bidder.BidStatus, 3) + resC <- bidder.BidStatus{ + Type: bidder.BidStatusCommitment, Arg: &bidderapiv1.Commitment{ TxHashes: []string{tx1.Hash().Hex()}, BidAmount: big.NewInt(100).String(), @@ -510,17 +489,7 @@ func TestSender(t *testing.T) { }, } close(resC) - bidder.out <- resC - - checkOp = <-blockTracker.in - if checkOp.hash != tx2.Hash() { - t.Fatalf("expected transaction hash %s, got %s", tx2.Hash().Hex(), checkOp.hash.Hex()) - } - if checkOp.block != 2 { - t.Fatalf("expected block number 2, got %d", checkOp.block) - } - // Simulate transaction inclusion - blockTracker.out <- true + bidderImpl.out <- resC res = <-st.preconfirmedTxns if res.txn == nil { @@ -561,14 +530,13 @@ func TestCancelTransaction(t *testing.T) { testPricer := &mockPricer{ out: make(chan map[int64]float64, 10), } - bidder := &mockBidder{ + bidderImpl := &mockBidder{ optinEstimate: make(chan int64), in: make(chan bidOp, 10), - out: make(chan chan optinbidder.BidStatus, 10), + out: make(chan chan bidder.BidStatus, 10), } blockTracker := &mockBlockTracker{ - in: make(chan op, 10), - out: make(chan bool, 10), + out: make(chan uint64, 10), bnIn: make(chan struct{}, 10), bnOut: make(chan blockNoOp, 10), bnErr: make(chan error, 3), @@ -576,7 +544,7 @@ func TestCancelTransaction(t *testing.T) { sndr, err := sender.NewTxSender( st, - bidder, + bidderImpl, testPricer, blockTracker, &mockTransferer{}, @@ -627,7 +595,7 @@ func TestCancelTransaction(t *testing.T) { blockTracker.bnErr <- errors.New("simulated error for testing") blockTracker.bnErr <- errors.New("simulated error for testing") - bidder.optinEstimate <- 2 + bidderImpl.optinEstimate <- 2 cancelled, err := sndr.CancelTransaction(ctx, tx1.Hash()) if err != nil { @@ -640,3 +608,141 @@ func TestCancelTransaction(t *testing.T) { cancel() <-done } + +func TestIgnoreProvidersOnRetry(t *testing.T) { + t.Parallel() + + st := newMockStore() + testPricer := &mockPricer{ + out: make(chan map[int64]float64, 10), + } + bidderImpl := &mockBidder{ + optinEstimate: make(chan int64, 10), + in: make(chan bidOp, 10), + out: make(chan chan bidder.BidStatus, 10), + } + blockTracker := &mockBlockTracker{ + out: make(chan uint64, 10), + bnIn: make(chan struct{}, 10), + bnOut: make(chan blockNoOp, 10), + bnErr: make(chan error, 1), + } + notifier := &mockNotifier{} + + sndr, err := sender.NewTxSender( + st, + bidderImpl, + testPricer, + blockTracker, + &mockTransferer{}, + notifier, + &mockSimulator{}, + big.NewInt(1), // Settlement chain ID + util.NewTestLogger(io.Discard), + ) + if err != nil { + t.Fatalf("failed to create sender: %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + done := sndr.Start(ctx) + + tx1 := &sender.Transaction{ + Transaction: types.NewTransaction( + 1, + common.HexToAddress("0x1234567890123456789012345678901234567890"), + big.NewInt(100), + 21000, + big.NewInt(1), + nil, + ), + Sender: common.HexToAddress("0x1234567890123456789012345678901234567890"), + Type: sender.TxTypeRegular, + Raw: "0x1234567890123456789012345678901234567890", + } + + if err := st.AddBalance(ctx, tx1.Sender, big.NewInt(5e18)); err != nil { + t.Fatalf("failed to add balance: %v", err) + } + + if err := sndr.Enqueue(ctx, tx1); err != nil { + t.Fatalf("failed to enqueue transaction: %v", err) + } + + // Simulate opted in block + bidderImpl.optinEstimate <- 2 + + <-blockTracker.bnIn + + blockTracker.bnOut <- blockNoOp{ + block: 1, + timeTillNextBlock: 5 * time.Second, + } + + // Simulate a price estimate + testPricer.out <- map[int64]float64{ + 90: 1.0, + 95: 1.5, + 99: 2.0, + } + + // Simulate a bid response + bidOp := <-bidderImpl.in + if bidOp.rawTx != tx1.Raw[2:] { + t.Fatalf("expected raw transaction %s, got %s", tx1.Raw, bidOp.rawTx) + } + resC := make(chan bidder.BidStatus, 3) + resC <- bidder.BidStatus{ + Type: bidder.BidStatusCommitment, + Arg: &bidderapiv1.Commitment{ + TxHashes: []string{tx1.Hash().Hex()}, + BidAmount: big.NewInt(100).String(), + BlockNumber: 1, + ProviderAddress: "provider1", + }, + } + close(resC) + bidderImpl.out <- resC + + bidderImpl.optinEstimate <- 2 + + <-blockTracker.bnIn + + blockTracker.bnOut <- blockNoOp{ + block: 1, + timeTillNextBlock: 2 * time.Second, + } + + // Simulate a price estimate + testPricer.out <- map[int64]float64{ + 90: 1.0, + 95: 1.5, + 99: 2.0, + } + + bidOp = <-bidderImpl.in + if len(bidOp.opts.IgnoreProviders) != 1 { + t.Fatalf("expected 1 ignored provider, got %d", len(bidOp.opts.IgnoreProviders)) + } + + resC = make(chan bidder.BidStatus, 3) + resC <- bidder.BidStatus{ + Type: bidder.BidStatusCommitment, + Arg: &bidderapiv1.Commitment{ + TxHashes: []string{tx1.Hash().Hex()}, + BidAmount: big.NewInt(100).String(), + BlockNumber: 1, + ProviderAddress: "provider2", + }, + } + close(resC) + bidderImpl.out <- resC + res := <-st.preconfirmedTxns + if res.txn == nil { + t.Fatal("expected a preconfirmed transaction, got nil") + } + + cancel() + <-done +} diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index b9b6dd714..47d3c9d87 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -23,6 +23,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" + bidder "github.com/primev/mev-commit/tools/preconf-rpc/bidder" "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/notifier" @@ -35,7 +36,6 @@ import ( "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" diff --git a/tools/preconf-rpc/store/store.go b/tools/preconf-rpc/store/store.go index cd7198666..3c694f53f 100644 --- a/tools/preconf-rpc/store/store.go +++ b/tools/preconf-rpc/store/store.go @@ -442,7 +442,7 @@ func (s *rpcstore) GetCurrentNonce(ctx context.Context, sender common.Address) u query := ` SELECT COALESCE(MAX(nonce), 0) FROM mcTransactions - WHERE sender = $1 AND (status = 'pending' OR status = 'pre-confirmed'); + WHERE sender = $1 AND status != 'failed'; ` row := s.db.QueryRowContext(ctx, query, sender.Hex()) var nextNonce uint64 diff --git a/x/contracts/ethwrapper/client.go b/x/contracts/ethwrapper/client.go index 814cfbf1c..0562f49f0 100644 --- a/x/contracts/ethwrapper/client.go +++ b/x/contracts/ethwrapper/client.go @@ -264,3 +264,11 @@ func (c *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*t } return rawClient.TransactionReceipt(ctx, txHash) } + +func (c *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + rawClient := c.RawClient() + if rawClient == nil { + return 0, fmt.Errorf("no raw client") + } + return rawClient.PendingNonceAt(ctx, account) +} diff --git a/x/go.mod b/x/go.mod index fa3b0abc3..e5fd859e5 100644 --- a/x/go.mod +++ b/x/go.mod @@ -9,7 +9,6 @@ require ( github.com/google/go-cmp v0.6.0 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.1 github.com/prometheus/client_golang v1.19.1 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.28.0 @@ -18,11 +17,9 @@ 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 @@ -71,6 +68,7 @@ require ( golang.org/x/text v0.24.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 5d0110d3b..c2ab1c0db 100644 --- a/x/go.sum +++ b/x/go.sum @@ -1,5 +1,3 @@ -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= @@ -80,10 +78,8 @@ 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= @@ -155,7 +151,6 @@ 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= @@ -233,15 +228,12 @@ golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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=