package reldb import ( "context" "database/sql" "database/sql/driver" "errors" "fmt" "math/big" "github.com/synapsecns/sanguine/ethergo/listener/db" "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/core/dbcommon" submitterDB "github.com/synapsecns/sanguine/ethergo/submitter/db" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" ) // Writer is the interface for writing to the database. type Writer interface { // StoreQuoteRequest stores a quote request. If one already exists, only the status will be updated // TODO: find a better way to describe this in the name StoreQuoteRequest(ctx context.Context, request QuoteRequest) error // StoreRebalance stores a rebalance. StoreRebalance(ctx context.Context, rebalance Rebalance) error // UpdateQuoteRequestStatus updates the status of a quote request UpdateQuoteRequestStatus(ctx context.Context, id [32]byte, status QuoteRequestStatus, prevStatus *QuoteRequestStatus) error // UpdateRebalance updates the status of a rebalance action. // If the origin is supplied, it will be used to update the ID for the corresponding rebalance model. UpdateRebalance(ctx context.Context, rebalance Rebalance, updateID bool) error // UpdateLatestRebalance updates a rebalance action. // This is meant to be used in cases where a consistent rebalance ID cannot be determined across chains. UpdateLatestRebalance(ctx context.Context, rebalance Rebalance) error // UpdateDestTxHash updates the dest tx hash of a quote request UpdateDestTxHash(ctx context.Context, id [32]byte, destTxHash common.Hash) error // UpdateRelayNonce updates the relay nonce of a quote request UpdateRelayNonce(ctx context.Context, id [32]byte, nonce uint64) error } // Reader is the interface for reading from the database. type Reader interface { // GetQuoteRequestByID gets a quote request by id. Should return ErrNoQuoteForID if not found GetQuoteRequestByID(ctx context.Context, id [32]byte) (*QuoteRequest, error) // GetQuoteRequestByOriginTxHash gets a quote request by origin tx hash. Should return ErrNoQuoteForTxHash if not found GetQuoteRequestByOriginTxHash(ctx context.Context, txHash common.Hash) (*QuoteRequest, error) // GetQuoteResultsByStatus gets quote results by status GetQuoteResultsByStatus(ctx context.Context, matchStatuses ...QuoteRequestStatus) (res []QuoteRequest, _ error) // GetPendingRebalances checks fetches all pending rebalances that involve the given chainIDs. GetPendingRebalances(ctx context.Context, chainIDs ...uint64) ([]*Rebalance, error) // GetRebalance gets a rebalance by ID. Should return ErrNoRebalanceForID if not found. GetRebalanceByID(ctx context.Context, rebalanceID string) (*Rebalance, error) // GetDBStats gets the database stats. GetDBStats(ctx context.Context) (*sql.DBStats, error) // GetStatusCounts gets the counts of quote requests by status. GetStatusCounts(ctx context.Context, matchStatuses ...QuoteRequestStatus) (map[QuoteRequestStatus]int, error) } // Service is the interface for the database service. type Service interface { Reader // SubmitterDB returns the submitter database service. SubmitterDB() submitterDB.Service Writer db.ChainListenerDB } var ( // ErrNoQuoteForID means the quote was not found. ErrNoQuoteForID = errors.New("no quote found for tx id") // ErrNoQuoteForTxHash means the quote was not found. ErrNoQuoteForTxHash = errors.New("no quote found for tx hash") // ErrNoRebalanceForID means the rebalance was not found. ErrNoRebalanceForID = errors.New("no rebalance found for id") ) // QuoteRequest is the quote request object. type QuoteRequest struct { BlockNumber uint64 OriginTokenDecimals uint8 RawRequest []byte DestTokenDecimals uint8 TransactionID [32]byte Sender common.Address Transaction fastbridge.IFastBridgeBridgeTransaction // Status is the quote request status Status QuoteRequestStatus OriginTxHash common.Hash DestTxHash common.Hash // RelayNonce is the nonce for the relay transaction. RelayNonce uint64 } // GetOriginIDPair gets the origin chain id and token address pair. // for some reason, this is specified as [chainid]-[tokenaddr] in the config. // this represents the origin pair. func (q QuoteRequest) GetOriginIDPair() string { return fmt.Sprintf("%d-%s", q.Transaction.OriginChainId, q.Transaction.OriginToken.Hex()) } // GetDestIDPair gets the destination chain id and token address pair. // for some reason, this is specified as [chainid]-[tokenaddr] in the config. // this represents the destination pair. func (q QuoteRequest) GetDestIDPair() string { return fmt.Sprintf("%d-%s", q.Transaction.DestChainId, q.Transaction.DestToken.Hex()) } // QuoteRequestStatus is the status of a quote request in the db. // This is the primary mechanism for moving data through the app. // // TODO: consider making this an interface and exporting that. // // EXTREMELY IMPORTANT: DO NOT ADD NEW VALUES TO THIS ENUM UNLESS THEY ARE AT THE END. // //go:generate go run golang.org/x/tools/cmd/stringer -type=QuoteRequestStatus type QuoteRequestStatus uint8 const ( // Seen means the quote request has been seen by the relayer, but not processed or committed to. Seen QuoteRequestStatus = iota + 1 // NotEnoughInventory means the relayer does not have enough inventory to process the request. // This can be retried at a later time. NotEnoughInventory // DeadlineExceeded means the quote request has exceeded the deadline. // This is a terminal state. DeadlineExceeded // WillNotProcess means the relayer will not process the request for some reason. // This is a terminal state. WillNotProcess // CommittedPending means the relayer has committed liquidity to the request to the chain, but it is not yet confirmed on chain. CommittedPending // CommittedConfirmed means the relayer has committed liquidity to the request to the chain, and original bridge tx has been confirmed on chain. CommittedConfirmed // RelayStarted means the relayer has called Relay() on the destination chain. RelayStarted // RelayCompleted means the relayer has called Relay() on the destination chain, and the tx has been confirmed on chain. RelayCompleted // ProvePosting means the relayer has called Prove() on the origin chain. ProvePosting // ProvePosted means the relayer has called Prove() on the origin chain, and the tx has been confirmed on chain. ProvePosted // ClaimPending means the relayer has called Claim() on the origin chain. ClaimPending // ClaimCompleted means the relayer has called Claim() on the origin chain, and the tx has been confirmed on chain. ClaimCompleted // RelayRaceLost means another relayer has relayed the tx. RelayRaceLost ) // Int returns the int value of the quote request status. func (q QuoteRequestStatus) Int() uint8 { return uint8(q) } // GormDataType implements the gorm common interface for enums. func (q QuoteRequestStatus) GormDataType() string { return dbcommon.EnumDataType } // Scan implements the gorm common interface for enums. func (q *QuoteRequestStatus) Scan(src any) error { res, err := dbcommon.EnumScan(src) if err != nil { return fmt.Errorf("could not scan %w", err) } newStatus := QuoteRequestStatus(res) *q = newStatus return nil } // Value implements the gorm common interface for enums. func (q QuoteRequestStatus) Value() (driver.Value, error) { // nolint: wrapcheck return dbcommon.EnumValue(q) } var _ dbcommon.Enum = (*QuoteRequestStatus)(nil) // Rebalance represents a rebalance action. type Rebalance struct { RebalanceID *string Origin uint64 Destination uint64 OriginAmount *big.Int OriginTokenAddr common.Address Status RebalanceStatus OriginTxHash common.Hash DestTxHash common.Hash TokenName string } // RebalanceStatus is the status of a rebalance action in the db. // //go:generate go run golang.org/x/tools/cmd/stringer -type=RebalanceStatus type RebalanceStatus uint8 const ( // RebalanceInitiated means the rebalance transaction has been initiated. RebalanceInitiated RebalanceStatus = iota + 1 // RebalancePending means the rebalance transaction has been confirmed on the origin. RebalancePending // RebalanceCompleted means the rebalance transaction has been confirmed on the destination. RebalanceCompleted ) // Int returns the int value of the quote request status. func (r RebalanceStatus) Int() uint8 { return uint8(r) } // GormDataType implements the gorm common interface for enums. func (r RebalanceStatus) GormDataType() string { return dbcommon.EnumDataType } // Scan implements the gorm common interface for enums. func (r *RebalanceStatus) Scan(src any) error { res, err := dbcommon.EnumScan(src) if err != nil { return fmt.Errorf("could not scan %w", err) } newStatus := RebalanceStatus(res) *r = newStatus return nil } // Value implements the gorm common interface for enums. func (r RebalanceStatus) Value() (driver.Value, error) { // nolint: wrapcheck return dbcommon.EnumValue(r) } var _ dbcommon.Enum = (*RebalanceStatus)(nil)