Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions tools/preconf-rpc/blocktracker/blocktracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,19 @@ func (b *blockTracker) Start(ctx context.Context) <-chan struct{} {
b.log.Error("Block not found in cache", "blockNumber", bNo)
continue
}
txnsToClear := make([]common.Hash, 0)
b.txnToCheckMu.Lock()
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()
txnsToClear = append(txnsToClear, txHash)
}
}
for _, txHash := range txnsToClear {
delete(b.txnsToCheck, txHash)
}
b.txnToCheckMu.Unlock()
}
}
})
Expand Down
194 changes: 193 additions & 1 deletion tools/preconf-rpc/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

const (
bridgeLimitWei = 1000000000000000000 // 1 ETH
defaultSubsidy = 10000000000000000 // 0.01 ETH
)

type positionConstraintKey struct{}
Expand All @@ -37,6 +38,9 @@ type Store interface {
GetTransactionLogs(ctx context.Context, txnHash common.Hash) ([]*types.Log, error)
GetBalance(ctx context.Context, account common.Address) (*big.Int, error)
GetCurrentNonce(ctx context.Context, account common.Address) uint64
HasBalance(ctx context.Context, account common.Address, amount *big.Int) bool
AlreadySubsidized(ctx context.Context, account common.Address) bool
AddSubsidy(ctx context.Context, account common.Address, amount *big.Int) error
}

type BlockTracker interface {
Expand All @@ -47,6 +51,7 @@ type BlockTracker interface {
type Sender interface {
Enqueue(ctx context.Context, txn *sender.Transaction) error
CancelTransaction(ctx context.Context, txHash common.Hash) (bool, error)
WaitForReceiptAvailable(ctx context.Context, txHash common.Hash) <-chan struct{}
}

func SetPositionConstraint(ctx context.Context, constraint *bidderapiv1.PositionConstraint) context.Context {
Expand Down Expand Up @@ -135,9 +140,17 @@ func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) {
return maxPriorityFeeJSON, false, nil
})
server.RegisterHandler("eth_sendRawTransaction", h.handleSendRawTx)
server.RegisterHandler("eth_sendRawTransactionSync", h.handleSendRawTxSync)
server.RegisterHandler("eth_getTransactionReceipt", h.handleGetTxReceipt)
server.RegisterHandler("eth_getTransactionCount", h.handleGetTxCount)
server.RegisterHandler("eth_getBlockByHash", h.handleGetBlockByHash)
// Prevent spam
server.RegisterHandler("eth_sendBundle", func(ctx context.Context, params ...any) (json.RawMessage, bool, error) {
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeInvalidRequest,
"eth_sendBundle is not supported",
)
})
// Custom methods for MEV Commit
server.RegisterHandler("mevcommit_optInBlock", func(ctx context.Context, params ...any) (json.RawMessage, bool, error) {
timeToOptIn, err := h.bidder.Estimate()
Expand Down Expand Up @@ -189,6 +202,25 @@ func (h *rpcMethodHandler) RegisterMethods(server *rpcserver.JSONRPCServer) {
h.logger.Debug("Estimated bridge price", "bidAmount", bridgeCost, "bridgeAddress", h.bridgeAddress.Hex())
return resultJSON, false, nil
})
server.RegisterHandler("mevcommit_estimateBidPricePerGas", func(ctx context.Context, params ...any) (json.RawMessage, bool, error) {
blockPrices := h.pricer.EstimatePrice(ctx)

minPrice, maxPrice := getMinMaxPrice(blockPrices)
result := map[string]interface{}{
"minGasPrice": minPrice.String(),
"maxGasPrice": maxPrice.String(),
}
resultJSON, err := json.Marshal(result)
if err != nil {
h.logger.Error("Failed to marshal gas price estimate to JSON", "error", err)
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
"failed to marshal gas price estimate",
)
}
return resultJSON, false, nil
})

server.RegisterHandler("mevcommit_cancelTransaction", h.handleCancelTransaction)
server.RegisterHandler("mevcommit_getTransactionCommitments", h.handleGetTxCommitments)
server.RegisterHandler("mevcommit_getBalance", h.handleMevCommitGetBalance)
Expand All @@ -205,6 +237,24 @@ func getNextBlockPrice(blockPrices map[int64]float64) *big.Int {
return big.NewInt(0) // Return zero if no suitable estimate is found
}

func getMinMaxPrice(blockPrices map[int64]float64) (*big.Int, *big.Int) {
minPrice := big.NewInt(0)
maxPrice := big.NewInt(0)

for confidence, price := range blockPrices {
if confidence == 90 {
minPriceInWei := price * 1e9 // Convert Gwei to Wei
minPrice = new(big.Int).SetUint64(uint64(minPriceInWei))
}
if confidence == 99 {
maxPriceInWei := price * 1e9 // Convert Gwei to Wei
maxPrice = new(big.Int).SetUint64(uint64(maxPriceInWei))
}
}

return minPrice, maxPrice
}

func (h *rpcMethodHandler) handleGetBlockByHash(
ctx context.Context,
params ...any,
Expand Down Expand Up @@ -369,6 +419,10 @@ func (h *rpcMethodHandler) handleSendRawTx(
}
}

if err := h.subsidizeOnce(ctx, txSender); err != nil {
h.logger.Warn("Failed to subsidize user", "error", err, "sender", txSender.Hex())
}

txnToEnqueue := &sender.Transaction{
Transaction: txn,
Raw: rawTxHex,
Expand All @@ -385,7 +439,7 @@ func (h *rpcMethodHandler) handleSendRawTx(
h.logger.Error("Failed to enqueue transaction for sending", "error", err, "sender", txSender.Hex())
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
"failed to enqueue transaction for sending",
err.Error(),
)
}

Expand All @@ -401,6 +455,110 @@ func (h *rpcMethodHandler) handleSendRawTx(
return txHashJSON, false, nil
}

func (h *rpcMethodHandler) handleSendRawTxSync(
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",
)
}
if params[0] == nil {
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'",
)
}

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",
)
}

txSender, 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",
)
}

txType := sender.TxTypeRegular
switch {
case txn.To().Cmp(h.depositAddress) == 0:
txType = sender.TxTypeDeposit
case txn.To().Cmp(h.bridgeAddress) == 0:
txType = sender.TxTypeInstantBridge
if txn.Value().Cmp(big.NewInt(bridgeLimitWei)) > 0 {
h.logger.Error("Bridge transaction with value greater than limit", "value", txn.Value().String())
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
fmt.Sprintf("bridge transaction value exceeds limit %d wei", bridgeLimitWei),
)
}
}

if err := h.subsidizeOnce(ctx, txSender); err != nil {
h.logger.Warn("Failed to subsidize user", "error", err, "sender", txSender.Hex())
}

txnToEnqueue := &sender.Transaction{
Transaction: txn,
Raw: rawTxHex,
Sender: txSender,
Type: txType,
}
constraint, ok := getPositionConstraint(ctx)
if ok {
txnToEnqueue.Constraint = constraint
}

err = h.sndr.Enqueue(ctx, txnToEnqueue)
if err != nil {
h.logger.Error("Failed to enqueue transaction for sending", "error", err, "sender", txSender.Hex())
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
err.Error(),
)
}

waitCh := h.sndr.WaitForReceiptAvailable(ctx, txn.Hash())
select {
case <-waitCh:
// Receipt is now available
case <-ctx.Done():
h.logger.Error("Context cancelled while waiting for transaction receipt", "txHash", txn.Hash().Hex())
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
"context cancelled while waiting for transaction receipt",
)
}

return h.handleGetTxReceipt(ctx, txn.Hash().Hex())
}

func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any) (json.RawMessage, bool, error) {
if len(params) != 1 {
return nil, false, rpcserver.NewJSONErr(
Expand Down Expand Up @@ -443,6 +601,15 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any
)
}

commitments, err := h.store.GetTransactionCommitments(ctx, txHash)
if err != nil && txn.Status != sender.TxStatusFailed {
h.logger.Error("Failed to get transaction commitments", "error", err, "txHash", txHash)
return nil, false, rpcserver.NewJSONErr(
rpcserver.CodeCustomError,
"failed to get transaction commitments",
)
}

result := map[string]interface{}{
"type": hexutil.Uint(txn.Transaction.Type()),
"transactionHash": txn.Hash().Hex(),
Expand All @@ -463,6 +630,7 @@ func (h *rpcMethodHandler) handleGetTxReceipt(ctx context.Context, params ...any
result["status"] = hexutil.Uint64(types.ReceiptStatusSuccessful)
result["blockHash"] = txn.Hash().Hex()
result["blockNumber"] = hexutil.EncodeBig(big.NewInt(txn.BlockNumber))
result["maxBidAmount"] = getFinalBidAmount(commitments).String()
}

receiptJSON, err := json.Marshal(result)
Expand Down Expand Up @@ -654,3 +822,27 @@ func (r *rpcMethodHandler) handleCancelTransaction(ctx context.Context, params .
r.logger.Info("Transaction cancelled successfully", "txHash", txHash)
return json.RawMessage(fmt.Sprintf(`{"cancelled": true, "txHash": "%s"}`, txHash.Hex())), false, nil
}

func (r *rpcMethodHandler) subsidizeOnce(ctx context.Context, account common.Address) error {
if r.store.HasBalance(ctx, account, big.NewInt(1)) {
return nil
}
if r.store.AlreadySubsidized(ctx, account) {
return nil
}
r.logger.Info("Subsidizing account for first transaction", "account", account.Hex())
return r.store.AddSubsidy(ctx, account, big.NewInt(defaultSubsidy))
}

func getFinalBidAmount(cmts []*bidderapiv1.Commitment) *big.Int {
finalBid := big.NewInt(0)
for _, cmt := range cmts {
bidAmount, ok := new(big.Int).SetString(cmt.BidAmount, 10)
if ok {
if bidAmount.Cmp(finalBid) > 0 {
finalBid = bidAmount
}
}
}
return finalBid
}
Loading
Loading