-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Feature ORM for CCIP in-db prices #12813
Merged
Merged
Changes from 14 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
26871d2
define CCIP migration sql
matYang 35d720e
POC orm
matYang 47194bb
Merge branch 'develop' into feature/ccip-in-db-prices
matYang 5674c8b
use context as opposed to pg opts
matYang 976ddbb
add CCIP ORM test
matYang adae96e
pass ORM tests
matYang 58edd9b
Merge branch 'develop' into feature/ccip-in-db-prices
matYang 7f282e4
add change set
matYang 83ef8d8
update changeset
matYang 27ee64c
fix lint and goimports
matYang 3f92f4b
address comments
matYang 861d425
replce big.int with assets.wei for scanner/valuer type
matYang 868bbd9
inline table names
matYang f42ccb8
use named exec to insert multiple rows
matYang 9b90ba5
Merge branch 'develop' into feature/ccip-in-db-prices
matYang 9640989
bump migration index
matYang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"chainlink": patch | ||
--- | ||
|
||
#added ORM and corresponding tables for CCIP gas prices and token prices |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package ccip | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/sqlutil" | ||
|
||
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" | ||
) | ||
|
||
type GasPrice struct { | ||
SourceChainSelector uint64 | ||
GasPrice *assets.Wei | ||
CreatedAt time.Time | ||
} | ||
|
||
type GasPriceUpdate struct { | ||
SourceChainSelector uint64 | ||
GasPrice *assets.Wei | ||
} | ||
|
||
type TokenPrice struct { | ||
TokenAddr string | ||
TokenPrice *assets.Wei | ||
CreatedAt time.Time | ||
} | ||
|
||
type TokenPriceUpdate struct { | ||
TokenAddr string | ||
TokenPrice *assets.Wei | ||
} | ||
|
||
//go:generate mockery --quiet --name ORM --output ./mocks/ --case=underscore | ||
type ORM interface { | ||
GetGasPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]GasPrice, error) | ||
GetTokenPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]TokenPrice, error) | ||
|
||
InsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, gasPrices []GasPriceUpdate) error | ||
InsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, tokenPrices []TokenPriceUpdate) error | ||
|
||
ClearGasPricesByDestChain(ctx context.Context, destChainSelector uint64, to time.Time) error | ||
ClearTokenPricesByDestChain(ctx context.Context, destChainSelector uint64, to time.Time) error | ||
} | ||
|
||
type orm struct { | ||
ds sqlutil.DataSource | ||
} | ||
|
||
var _ ORM = (*orm)(nil) | ||
|
||
func NewORM(ds sqlutil.DataSource) (ORM, error) { | ||
if ds == nil { | ||
return nil, fmt.Errorf("datasource to CCIP NewORM cannot be nil") | ||
} | ||
|
||
return &orm{ | ||
ds: ds, | ||
}, nil | ||
} | ||
|
||
func (o *orm) GetGasPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]GasPrice, error) { | ||
var gasPrices []GasPrice | ||
stmt := ` | ||
SELECT DISTINCT ON (source_chain_selector) | ||
source_chain_selector, gas_price, created_at | ||
FROM ccip.observed_gas_prices | ||
WHERE chain_selector = $1 | ||
ORDER BY source_chain_selector, created_at DESC; | ||
` | ||
err := o.ds.SelectContext(ctx, &gasPrices, stmt, destChainSelector) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return gasPrices, nil | ||
} | ||
|
||
func (o *orm) GetTokenPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]TokenPrice, error) { | ||
var tokenPrices []TokenPrice | ||
stmt := ` | ||
SELECT DISTINCT ON (token_addr) | ||
token_addr, token_price, created_at | ||
FROM ccip.observed_token_prices | ||
WHERE chain_selector = $1 | ||
ORDER BY token_addr, created_at DESC; | ||
` | ||
err := o.ds.SelectContext(ctx, &tokenPrices, stmt, destChainSelector) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return tokenPrices, nil | ||
} | ||
|
||
func (o *orm) InsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, gasPrices []GasPriceUpdate) error { | ||
if len(gasPrices) == 0 { | ||
return nil | ||
} | ||
|
||
now := time.Now() | ||
insertData := make([]map[string]interface{}, 0, len(gasPrices)) | ||
for _, price := range gasPrices { | ||
insertData = append(insertData, map[string]interface{}{ | ||
"chain_selector": destChainSelector, | ||
"job_id": jobId, | ||
"source_chain_selector": price.SourceChainSelector, | ||
"gas_price": price.GasPrice, | ||
"created_at": now, | ||
}) | ||
} | ||
|
||
stmt := `INSERT INTO ccip.observed_gas_prices (chain_selector, job_id, source_chain_selector, gas_price, created_at) | ||
VALUES (:chain_selector, :job_id, :source_chain_selector, :gas_price, :created_at);` | ||
_, err := o.ds.NamedExecContext(ctx, stmt, insertData) | ||
if err != nil { | ||
err = fmt.Errorf("error inserting gas prices for job %d: %w", jobId, err) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (o *orm) InsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, tokenPrices []TokenPriceUpdate) error { | ||
if len(tokenPrices) == 0 { | ||
return nil | ||
} | ||
|
||
now := time.Now() | ||
insertData := make([]map[string]interface{}, 0, len(tokenPrices)) | ||
for _, price := range tokenPrices { | ||
insertData = append(insertData, map[string]interface{}{ | ||
"chain_selector": destChainSelector, | ||
"job_id": jobId, | ||
"token_addr": price.TokenAddr, | ||
"token_price": price.TokenPrice, | ||
"created_at": now, | ||
}) | ||
} | ||
|
||
stmt := `INSERT INTO ccip.observed_token_prices (chain_selector, job_id, token_addr, token_price, created_at) | ||
VALUES (:chain_selector, :job_id, :token_addr, :token_price, :created_at);` | ||
_, err := o.ds.NamedExecContext(ctx, stmt, insertData) | ||
if err != nil { | ||
err = fmt.Errorf("error inserting token prices for job %d: %w", jobId, err) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (o *orm) ClearGasPricesByDestChain(ctx context.Context, destChainSelector uint64, to time.Time) error { | ||
stmt := `DELETE FROM ccip.observed_gas_prices WHERE chain_selector = $1 AND created_at < $2` | ||
|
||
_, err := o.ds.ExecContext(ctx, stmt, destChainSelector, to) | ||
return err | ||
} | ||
|
||
func (o *orm) ClearTokenPricesByDestChain(ctx context.Context, destChainSelector uint64, to time.Time) error { | ||
stmt := `DELETE FROM ccip.observed_token_prices WHERE chain_selector = $1 AND created_at < $2` | ||
|
||
_, err := o.ds.ExecContext(ctx, stmt, destChainSelector, to) | ||
return err | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we typically let the database set this field instead of passing it explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my understanding is there isn't a single best practice when it comes to passing in timestamp v.s DB default, in this case:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think of best practice as using db clock for both inserts and deletes rather than golang clock.
now()
is an alias fortransaction_timestamp()
which returns the start time of the current transaction. That does have issues with tests, because we run all tests within a single transaction. However, I think any of the following should probably work without issues:statement_timestamp()
,clock_timestamp()
, or justTIMESTAMP 'now'
https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT