Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cobra command to batch settle pending settlements #77

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,21 @@ Now you can run the solver commands from anywhere. Available commands:

```shell
solver submit-transfer \
--config <configFilePath> \
--token <usdc address> \
--recipient <destination address> \
--amount <usdc amount> \
--source-chain-id <source chain id> \
--destination-chain-id <destination chain id> \
--gateway <gateway contract> \
--private-key <private key> \
--deadline-hours <timeout in hours>
--token <usdc_address> \
--recipient <destination_address> \
--amount-in <amount_in> \
--amount-out <amount_out> \
--source-chain-id <source_chain_id> \
--destination-chain-id <destination_chain_id> \
--gateway <fast_transfer_gateway_contract> \
--private-key <private_key> \
--deadline-hours <timeout_in_hours>
```

**relay**: Manually relay a hyperlane transaction

```shell
solver relay \
--config <configFilePath> \
--keys <keysFilePath> \
--key-store-type <store type> \
--aes-key-hex <hex key> \
--origin-chain-id <chain id> \
--originTxHash <tx hash> \
--checkpoint-storage-location-override <storage path>
solver relay --origin-chain-id <chain_id> --origin-tx-hash <settlement_tx_hash>
```

**balances**: Get current on-chain balances (USDC, gas token, and custom assets requested)
Expand All @@ -147,6 +140,16 @@ solver rebalances
solver settlements
```

**settle-orders**: settle all pending order batches immediately without any threshold checks (ignoring configured BatchUUSDCSettleUpThreshold)

```shell
solver settle-orders

# The above only initiates batch settlements. To relay the settlement transactions, you need to relay the settlements
# individually using the relay CLI command
solver relay --origin-chain-id <chain_id> --origin-tx-hash <settlement_tx_hash>
```

**profit**: Calculate solver total profit

```shell
Expand Down
4 changes: 2 additions & 2 deletions cmd/solvercli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ func init() {
rootCmd.PersistentFlags().String("keys", "./config/local/keys.json", "path to solver key file. must be specified if key-store-type is plaintext-file or encrpyted-file")
rootCmd.PersistentFlags().String("key-store-type", "plaintext-file", "where to load the solver keys from. (plaintext-file, encrypted-file, env)")
rootCmd.PersistentFlags().String("aes-key-hex", "", "hex-encoded AES key used to decrypt keys file. must be specified if key-store-type is encrypted-file")
rootCmd.Flags().String("sqlite-db-path", "./solver.db", "path to sqlite db file")
rootCmd.Flags().String("migrations-path", "./db/migrations", "path to db migrations directory")
rootCmd.PersistentFlags().String("sqlite-db-path", "./solver.db", "path to sqlite db file")
rootCmd.PersistentFlags().String("migrations-path", "./db/migrations", "path to db migrations directory")
}
209 changes: 209 additions & 0 deletions cmd/solvercli/cmd/settle_orders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package cmd

import (
"fmt"
"github.com/skip-mev/go-fast-solver/shared/oracle"
"os/signal"
"strconv"
"syscall"
"time"

"github.com/skip-mev/go-fast-solver/hyperlane"
"github.com/skip-mev/go-fast-solver/ordersettler/types"
"github.com/skip-mev/go-fast-solver/shared/clients/coingecko"
"github.com/skip-mev/go-fast-solver/shared/clients/utils"
"github.com/skip-mev/go-fast-solver/shared/contracts/fast_transfer_gateway"
"github.com/skip-mev/go-fast-solver/shared/evmrpc"
"github.com/skip-mev/go-fast-solver/shared/txexecutor/evm"

"github.com/skip-mev/go-fast-solver/db/gen/db"
"github.com/skip-mev/go-fast-solver/ordersettler"
"github.com/skip-mev/go-fast-solver/shared/clientmanager"
"github.com/skip-mev/go-fast-solver/shared/config"
"github.com/skip-mev/go-fast-solver/shared/keys"
"github.com/skip-mev/go-fast-solver/shared/lmt"
"github.com/skip-mev/go-fast-solver/shared/txexecutor/cosmos"
"github.com/spf13/cobra"
"go.uber.org/zap"
"golang.org/x/net/context"
)

var settleCmd = &cobra.Command{
Use: "settle-orders",
Short: "Settle pending order batches",
Long: `Settle all pending order batches immediately without any threshold checks (ignoring configured BatchUUSDCSettleUpThreshold).`,
Example: `solver settle-orders`,
Run: settleOrders,
}

func init() {
rootCmd.AddCommand(settleCmd)
}

func settleOrders(cmd *cobra.Command, args []string) {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()

lmt.ConfigureLogger()
ctx = lmt.LoggerContext(ctx)

keyStoreType, err := cmd.Flags().GetString("key-store-type")
if err != nil {
lmt.Logger(ctx).Error("Failed to get key-store-type", zap.Error(err))
return
}

keysPath, err := cmd.Flags().GetString("keys")
if err != nil {
lmt.Logger(ctx).Error("Failed to get keys path", zap.Error(err))
return
}

configPath, err := cmd.Flags().GetString("config")
if err != nil {
lmt.Logger(ctx).Error("Failed to get config path", zap.Error(err))
return
}

cfg, err := config.LoadConfig(configPath)
if err != nil {
lmt.Logger(ctx).Error("Unable to load config", zap.Error(err))
return
}

ctx = config.ConfigReaderContext(ctx, config.NewConfigReader(cfg))

keyStore, err := keys.GetKeyStore(keyStoreType, keys.GetKeyStoreOpts{KeyFilePath: keysPath})
if err != nil {
lmt.Logger(ctx).Fatal("Unable to load keystore", zap.Error(err))
}

cosmosTxExecutor := cosmos.DefaultSerializedCosmosTxExecutor()
evmTxExecutor := evm.DefaultEVMTxExecutor()

clientManager := clientmanager.NewClientManager(keyStore, cosmosTxExecutor)

database, err := setupDatabase(ctx, cmd)
if err != nil {
lmt.Logger(ctx).Fatal("Failed to setup database", zap.Error(err))
}

evmManager := evmrpc.NewEVMRPCClientManager()
rateLimitedClient := utils.DefaultRateLimitedHTTPClient(3)
coingeckoClient := coingecko.NewCoingeckoClient(rateLimitedClient, "https://api.coingecko.com/api/v3/", "")
cachedCoinGeckoClient := coingecko.NewCachedPriceClient(coingeckoClient, 15*time.Minute)
txPriceOracle := oracle.NewOracle(cachedCoinGeckoClient)

hype, err := hyperlane.NewMultiClientFromConfig(ctx, evmManager, keyStore, txPriceOracle, evmTxExecutor)
if err != nil {
lmt.Logger(ctx).Fatal("creating hyperlane multi client from config", zap.Error(err))
}

relayer := hyperlane.NewRelayer(hype, make(map[string]string))
relayerRunner := hyperlane.NewRelayerRunner(database, hype, relayer)

settler, err := ordersettler.NewOrderSettler(ctx, database, clientManager, relayerRunner)
if err != nil {
lmt.Logger(ctx).Error("creating order settler", zap.Error(err))
return
}

chains, err := config.GetConfigReader(ctx).GetAllChainConfigsOfType(config.ChainType_COSMOS)
if err != nil {
lmt.Logger(ctx).Error("error getting Cosmos chains", zap.Error(err))
return
}

var pendingSettlements []db.OrderSettlement
for _, chain := range chains {
if chain.FastTransferContractAddress == "" {
continue
}

bridgeClient, err := clientManager.GetClient(ctx, chain.ChainID)
if err != nil {
lmt.Logger(ctx).Error("failed to get client",
zap.String("chainID", chain.ChainID),
zap.Error(err))
continue
}

fills, err := bridgeClient.OrderFillsByFiller(ctx, chain.FastTransferContractAddress, chain.SolverAddress)
if err != nil {
lmt.Logger(ctx).Error("getting order fills",
zap.String("chainID", chain.ChainID),
zap.Error(err))
continue
}

// For each fill, check if it needs settlement
for _, fill := range fills {
sourceChainID, err := config.GetConfigReader(ctx).GetChainIDByHyperlaneDomain(strconv.Itoa(int(fill.SourceDomain)))
if err != nil {
lmt.Logger(ctx).Error("failed to get source chain ID",
zap.Uint32("domain", fill.SourceDomain),
zap.Error(err))
continue
}

sourceGatewayAddress, err := config.GetConfigReader(ctx).GetGatewayContractAddress(sourceChainID)
if err != nil {
lmt.Logger(ctx).Error("getting source gateway address",
zap.String("chainID", sourceChainID),
zap.Error(err))
continue
}

sourceBridgeClient, err := clientManager.GetClient(ctx, sourceChainID)
if err != nil {
lmt.Logger(ctx).Error("getting source chain client",
zap.String("chainID", sourceChainID),
zap.Error(err))
continue
}

status, err := sourceBridgeClient.OrderStatus(ctx, sourceGatewayAddress, fill.OrderID)
if err != nil {
lmt.Logger(ctx).Error("getting order status",
zap.String("orderID", fill.OrderID),
zap.Error(err))
continue
}

if status != fast_transfer_gateway.OrderStatusUnfilled {
continue
}

pendingSettlements = append(pendingSettlements, db.OrderSettlement{
SourceChainID: sourceChainID,
DestinationChainID: chain.ChainID,
SourceChainGatewayContractAddress: sourceGatewayAddress,
OrderID: fill.OrderID,
})
}
}

if len(pendingSettlements) == 0 {
fmt.Println("No pending settlement batches found")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use the logging library

return
}

batches := types.IntoSettlementBatchesByChains(pendingSettlements)
fmt.Printf("Found %d pending settlement batches\n", len(batches))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too


hashes, err := settler.SettleBatches(ctx, batches)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this will initiate settlements but not relay the hyperlane message to the destination. How were you intending for the relay to complete?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the batch relays

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hyperlane validator set usually takes a bit to generate the checkpoints signatures. What happens if those signatures aren't generated here? Were you able to test this?

if err != nil {
lmt.Logger(ctx).Error("settling pending batches", zap.Error(err))
return
}

for i, batch := range batches {
hash := hashes[i]

fmt.Printf("Initiated settlement batch %d:\n", i+1)
fmt.Printf("Source Chain: %s\n", batch.SourceChainID())
fmt.Printf("Destination Chain: %s\n", batch.DestinationChainID())
fmt.Printf("Number of Orders: %d\n", len(batch.OrderIDs()))
fmt.Printf("Transaction Hash: %s\n", hash)
}
}
Loading