Skip to content

Commit

Permalink
Merge branch 'fix/new-rfq' of https://github.com/synapsecns/sanguine
Browse files Browse the repository at this point in the history
…into fix/new-rfq
  • Loading branch information
trajan0x committed Dec 13, 2023
2 parents e937576 + f5eab64 commit c4448f7
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 0 deletions.
36 changes: 36 additions & 0 deletions services/rfq/api/db/api_db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package db

import (
"time"

"github.com/shopspring/decimal"
)

type QuoteModel struct {
// ID is the unique identifier saved of each quote provided
ID uint64 `gorm:"column:id;primaryKey;"`
// DestChainID is the chain which the relayer is willing to provide liquidity for
DestChainID uint64 `gorm:"column:dest_chain_id;index"`
// DestToken is the token address for which the relayer is providing liquidity
DestTokenAddr string `gorm:"column:token;index"`
// DestAmount is the max amount of liquidity which exists for a given destination token, provided in the destination token decimals
DestAmount decimal.Decimal `gorm:"column:dest_amount"`
// Price is the price per origin token provided for which a relayer is indicating willingness to relay
Price decimal.Decimal `gorm:"column:price"`
// UpdatedAt is the time that the quote was last upserted
UpdatedAt time.Time `gorm:"column:updated_at"`
}

// ApiDBReader is the interface for reading from the database.
type ApiDBReader interface {
}

// ApiDBWriter is the interface for writing to the database.
type ApiDBWriter interface {
}

// ApiDB is the interface for the database service.
type ApiDB interface {
ApiDBReader
ApiDBWriter
}
7 changes: 7 additions & 0 deletions services/rfq/api/db/api_db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package db_test

import "fmt"

func (d *DBSuite) TestModelCreation() {
fmt.Println("suite started successfully")
}
32 changes: 32 additions & 0 deletions services/rfq/api/db/sql/base/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package base

import (
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/services/rfq/api/db"
"gorm.io/gorm"
)

// Store is a store that implements an underlying gorm db.
type Store struct {
db *gorm.DB
metrics metrics.Handler
}

// NewStore creates a new store.
func NewStore(db *gorm.DB, metrics metrics.Handler) *Store {
return &Store{db: db, metrics: metrics}
}

// DB gets the database object for mutation outside of the lib.
func (s Store) DB() *gorm.DB {
return s.db
}

// GetAllModels gets all models to migrate.
// see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time
func GetAllModels() (allModels []interface{}) {
allModels = append(allModels, &db.QuoteModel{})
return allModels
}

var _ db.ApiDB = &Store{}
2 changes: 2 additions & 0 deletions services/rfq/api/db/sql/base/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package base contains the base sql implementation
package base
2 changes: 2 additions & 0 deletions services/rfq/api/db/sql/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package sql provides a common interface for starting sql-lite databases
package sql
2 changes: 2 additions & 0 deletions services/rfq/api/db/sql/mysql/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package mysql contains a mysql db
package mysql
66 changes: 66 additions & 0 deletions services/rfq/api/db/sql/mysql/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package mysql

import (
"context"
"fmt"
"time"

"github.com/ipfs/go-log"
common_base "github.com/synapsecns/sanguine/core/dbcommon"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/services/rfq/api/db"
"github.com/synapsecns/sanguine/services/rfq/api/db/sql/base"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)

// Logger is the mysql logger.
var logger = log.Logger("api-mysql")

// NewMysqlStore creates a new mysql store for a given data store.
func NewMysqlStore(ctx context.Context, dbURL string, handler metrics.Handler) (*Store, error) {
logger.Debug("create mysql store")

gdb, err := gorm.Open(mysql.Open(dbURL), &gorm.Config{
Logger: common_base.GetGormLogger(logger),
FullSaveAssociations: true,
NamingStrategy: NamingStrategy,
NowFunc: time.Now,
})

if err != nil {
return nil, fmt.Errorf("could not create mysql connection: %w", err)
}

sqlDB, err := gdb.DB()
if err != nil {
return nil, fmt.Errorf("could not get sql db: %w", err)
}

// fixes a timeout issue https://stackoverflow.com/a/42146536
sqlDB.SetMaxIdleConns(MaxIdleConns)
sqlDB.SetConnMaxLifetime(time.Hour)

handler.AddGormCallbacks(gdb)

err = gdb.WithContext(ctx).AutoMigrate(base.GetAllModels()...)
if err != nil {
return nil, fmt.Errorf("could not migrate on mysql: %w", err)
}

return &Store{base.NewStore(gdb, handler)}, nil
}

// Store is the mysql store. It extends the bsae store for mysql queries.
type Store struct {
*base.Store
}

// MaxIdleConns is exported here for testing. Tests execute too slowly with a reconnect each time.
var MaxIdleConns = 10

// NamingStrategy is for table prefixes.
var NamingStrategy = schema.NamingStrategy{}

var _ db.ApiDB = &Store{}
2 changes: 2 additions & 0 deletions services/rfq/api/db/sql/sqlite/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package sqlite implements the sqlite package
package sqlite
63 changes: 63 additions & 0 deletions services/rfq/api/db/sql/sqlite/sqlite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package sqlite

import (
"context"
"fmt"
"os"

"github.com/synapsecns/sanguine/services/rfq/api/db/sql/base"

"github.com/ipfs/go-log"
common_base "github.com/synapsecns/sanguine/core/dbcommon"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/services/rfq/api/db"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

// Store is the sqlite store. It extends the base store for sqlite specific queries.
type Store struct {
*base.Store
}

var logger = log.Logger("api-sqlite")

// NewSqliteStore creates a new sqlite data store.
func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Handler, skipMigrations bool) (_ *Store, err error) {
logger.Debugf("creating sqlite store at %s", dbPath)

ctx, span := handler.Tracer().Start(parentCtx, "start-sqlite")
defer func() {
metrics.EndSpanWithErr(span, err)
}()

// create the directory to the store if it doesn't exist
err = os.MkdirAll(dbPath, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("could not create sqlite store")
}

logger.Warnf("api database is at %s/api.db", dbPath)

gdb, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s/%s", dbPath, "api.db")), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
Logger: common_base.GetGormLogger(logger),
FullSaveAssociations: true,
SkipDefaultTransaction: true,
})
if err != nil {
return nil, fmt.Errorf("could not connect to db %s: %w", dbPath, err)
}

handler.AddGormCallbacks(gdb)

if !skipMigrations {
err = gdb.WithContext(ctx).AutoMigrate(base.GetAllModels()...)
if err != nil {
return nil, fmt.Errorf("could not migrate models: %w", err)
}
}
return &Store{base.NewStore(gdb, handler)}, nil
}

var _ db.ApiDB = &Store{}
37 changes: 37 additions & 0 deletions services/rfq/api/db/sql/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package sql

import (
"context"
"errors"
"fmt"

"github.com/synapsecns/sanguine/core/dbcommon"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/services/rfq/api/db"
"github.com/synapsecns/sanguine/services/rfq/api/db/sql/mysql"
"github.com/synapsecns/sanguine/services/rfq/api/db/sql/sqlite"
)

// Connect connects to the database.
func Connect(ctx context.Context, dbType dbcommon.DBType, path string, metrics metrics.Handler) (db.ApiDB, error) {
switch dbType {
case dbcommon.Mysql:
store, err := mysql.NewMysqlStore(ctx, path, metrics)
if err != nil {
return nil, fmt.Errorf("could not create mysql store: %w", err)
}

return store, nil
case dbcommon.Sqlite:
store, err := sqlite.NewSqliteStore(ctx, path, metrics, false)
if err != nil {
return nil, fmt.Errorf("could not create sqlite store: %w", err)
}

return store, nil
case dbcommon.Clickhouse:
return nil, errors.New("driver not supported")
default:
return nil, fmt.Errorf("unsupported driver: %s", dbType)
}
}
109 changes: 109 additions & 0 deletions services/rfq/api/db/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package db_test

import (
dbSQL "database/sql"
"fmt"
"os"
"sync"
"testing"

"github.com/Flaque/filet"
. "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/synapsecns/sanguine/core"
"github.com/synapsecns/sanguine/core/dbcommon"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/core/metrics/localmetrics"
"github.com/synapsecns/sanguine/core/testsuite"
"github.com/synapsecns/sanguine/services/rfq/api/db"
"github.com/synapsecns/sanguine/services/rfq/api/db/sql"
"github.com/synapsecns/sanguine/services/rfq/api/db/sql/mysql"
"github.com/synapsecns/sanguine/services/rfq/api/metadata"
"gorm.io/gorm/schema"
)

type DBSuite struct {
*testsuite.TestSuite
dbs []db.ApiDB
metrics metrics.Handler
}

// NewDBSuite creates a new DBSuite.
func NewDBSuite(tb testing.TB) *DBSuite {
tb.Helper()
return &DBSuite{
TestSuite: testsuite.NewTestSuite(tb),
dbs: []db.ApiDB{},
}
}
func (d *DBSuite) SetupSuite() {
d.TestSuite.SetupSuite()

// don't use metrics on ci for integration tests
isCI := core.GetEnvBool("CI", false)
useMetrics := !isCI
metricsHandler := metrics.Null

if useMetrics {
localmetrics.SetupTestJaeger(d.GetSuiteContext(), d.T())
metricsHandler = metrics.Jaeger
}

var err error
d.metrics, err = metrics.NewByType(d.GetSuiteContext(), metadata.BuildInfo(), metricsHandler)
Nil(d.T(), err)
}

func (d *DBSuite) SetupTest() {
d.TestSuite.SetupTest()

sqliteStore, err := sql.Connect(d.GetTestContext(), dbcommon.Sqlite, filet.TmpDir(d.T(), ""), d.metrics)
Nil(d.T(), err)

d.dbs = []db.ApiDB{sqliteStore}
d.setupMysqlDB()
}

func (d *DBSuite) setupMysqlDB() {
if os.Getenv(dbcommon.EnableMysqlTestVar) != "true" {
return
}

mysql.NamingStrategy = schema.NamingStrategy{
TablePrefix: fmt.Sprintf("api_%d", d.GetTestID()),
}

// sets up the conn string to the default database
connString := dbcommon.GetTestConnString()
// sets up the myqsl db
testDB, err := dbSQL.Open("mysql", connString)
d.Require().NoError(err)
// close the db once the connection is don
defer func() {
d.Require().NoError(testDB.Close())
}()

mysqlStore, err := mysql.NewMysqlStore(d.GetTestContext(), connString, d.metrics)
d.Require().NoError(err)

d.dbs = append(d.dbs, mysqlStore)
}

func (d *DBSuite) RunOnAllDBs(testFunc func(testDB db.ApiDB)) {
d.T().Helper()

wg := sync.WaitGroup{}
for _, testDB := range d.dbs {
wg.Add(1)
// capture the value
go func(testDB db.ApiDB) {
defer wg.Done()
testFunc(testDB)
}(testDB)
}
wg.Wait()
}

func TestDBSuite(t *testing.T) {
suite.Run(t, NewDBSuite(t))
}
15 changes: 15 additions & 0 deletions services/rfq/api/metadata/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Package metadata provides a metadata service for the RFQ API.
package metadata

import "github.com/synapsecns/sanguine/core/config"

var (
version = config.DefaultVersion
commit = config.DefaultCommit
date = config.DefaultDate
)

// BuildInfo returns the build info for the service.
func BuildInfo() config.BuildInfo {
return config.NewBuildInfo(version, commit, "rfq-api", date)
}

0 comments on commit c4448f7

Please sign in to comment.