diff --git a/pkg/solana/logpoller/models.go b/pkg/solana/logpoller/models.go index 34da57ef9..fe6092333 100644 --- a/pkg/solana/logpoller/models.go +++ b/pkg/solana/logpoller/models.go @@ -14,7 +14,7 @@ type Filter struct { EventSig []byte StartingBlock int64 EventIDL string - SubKeyPaths SubKeyPaths + SubkeyPaths SubkeyPaths Retention time.Duration MaxLogsKept int64 } @@ -26,10 +26,10 @@ type Log struct { LogIndex int64 BlockHash Hash BlockNumber int64 - BLockTimestamp time.Time + BlockTimestamp time.Time Address PublicKey EventSig []byte - SubKeyValues pq.ByteaArray + SubkeyValues pq.ByteaArray TxHash Signature Data []byte CreatedAt time.Time diff --git a/pkg/solana/logpoller/orm.go b/pkg/solana/logpoller/orm.go index 8704ce92b..d17538d59 100644 --- a/pkg/solana/logpoller/orm.go +++ b/pkg/solana/logpoller/orm.go @@ -46,7 +46,7 @@ func (o *DSORM) InsertFilter(ctx context.Context, filter Filter) (id int64, err withEventSig(filter.EventSig). withStartingBlock(filter.StartingBlock). withEventIDL(filter.EventIDL). - withSubKeyPaths(filter.SubKeyPaths). + withSubkeyPaths(filter.SubkeyPaths). toArgs() if err != nil { return 0, err @@ -56,9 +56,9 @@ func (o *DSORM) InsertFilter(ctx context.Context, filter Filter) (id int64, err // https://github.com/jmoiron/sqlx/issues/91, https://github.com/jmoiron/sqlx/issues/428 query := ` INSERT INTO solana.log_poller_filters - (chain_id, name, address, event_name, event_sig, starting_block, event_idl, sub_key_paths, retention, max_logs_kept) - VALUES (:chain_id, :name, :address, :event_name, :event_sig, :starting_block, :event_idl, :sub_key_paths, :retention, :max_logs_kept) - ON CONFLICT (solana.f_log_poller_filter_hash(name, chain_id, address, event_sig, sub_key_paths)) + (chain_id, name, address, event_name, event_sig, starting_block, event_idl, subkey_paths, retention, max_logs_kept) + VALUES (:chain_id, :name, :address, :event_name, :event_sig, :starting_block, :event_idl, :subkey_paths, :retention, :max_logs_kept) + ON CONFLICT (solana.f_log_poller_filter_hash(name, chain_id, address, event_sig, subkey_paths)) DO UPDATE SET retention=:retention ::::BIGINT, max_logs_kept=:max_logs_kept ::::NUMERIC, starting_block=:starting_block ::::NUMERIC RETURNING id;` @@ -74,7 +74,7 @@ func (o *DSORM) InsertFilter(ctx context.Context, filter Filter) (id int64, err // GetFilterByID returns filter by ID func (o *DSORM) GetFilterByID(ctx context.Context, id int64) (Filter, error) { - query := `SELECT id, name, address, event_name, event_sig, starting_block, event_idl, sub_key_paths, retention, max_logs_kept + query := `SELECT id, name, address, event_name, event_sig, starting_block, event_idl, subkey_paths, retention, max_logs_kept FROM solana.log_poller_filters WHERE id = $1` var result Filter err := o.ds.GetContext(ctx, &result, query, id) diff --git a/pkg/solana/logpoller/orm_test.go b/pkg/solana/logpoller/orm_test.go index 0367f3758..9eac6f97c 100644 --- a/pkg/solana/logpoller/orm_test.go +++ b/pkg/solana/logpoller/orm_test.go @@ -30,57 +30,94 @@ func TestLogPollerFilters(t *testing.T) { privateKey, err := solana.NewRandomPrivateKey() require.NoError(t, err) pubKey := privateKey.PublicKey() - filters := []Filter{ - { - Name: "happy path", - Address: PublicKey(pubKey), - EventName: "event", - EventSig: []byte{1, 2, 3}, - StartingBlock: 1, - EventIDL: "{}", - SubKeyPaths: SubKeyPaths([][]string{{"a", "b"}, {"c"}}), - Retention: 1000, - MaxLogsKept: 3, - }, - { - Name: "empty sub key paths", - Address: PublicKey(pubKey), - EventName: "event", - EventSig: []byte{1, 2, 3}, - StartingBlock: 1, - EventIDL: "{}", - SubKeyPaths: SubKeyPaths([][]string{}), - Retention: 1000, - MaxLogsKept: 3, - }, - { - Name: "nil sub key paths", - Address: PublicKey(pubKey), - EventName: "event", - EventSig: []byte{1, 2, 3}, - StartingBlock: 1, - EventIDL: "{}", - SubKeyPaths: nil, - Retention: 1000, - MaxLogsKept: 3, - }, - } + t.Run("Ensure all fields are readable/writable", func(t *testing.T) { + filters := []Filter{ + { + Name: "happy path", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: SubkeyPaths([][]string{{"a", "b"}, {"c"}}), + Retention: 1000, + MaxLogsKept: 3, + }, + { + Name: "empty sub key paths", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: SubkeyPaths([][]string{}), + Retention: 1000, + MaxLogsKept: 3, + }, + { + Name: "nil sub key paths", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: nil, + Retention: 1000, + MaxLogsKept: 3, + }, + } - for _, filter := range filters { - t.Run("Save filter: "+filter.Name, func(t *testing.T) { - ctx := tests.Context(t) - id, err := orm.InsertFilter(ctx, filter) - require.NoError(t, err) - filter.ID = id - dbFilter, err := orm.GetFilterByID(ctx, id) - require.NoError(t, err) - require.Equal(t, filter, dbFilter) + for _, filter := range filters { + t.Run("Read/write filter: "+filter.Name, func(t *testing.T) { + ctx := tests.Context(t) + id, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + filter.ID = id + dbFilter, err := orm.GetFilterByID(ctx, id) + require.NoError(t, err) + require.Equal(t, filter, dbFilter) - // subsequent insert of the same filter won't produce new db row - secondID, err := orm.InsertFilter(ctx, filter) - require.NoError(t, err) - require.Equal(t, secondID, id) - }) + // subsequent insert of the same filter won't produce new db row + secondID, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + require.Equal(t, secondID, id) + }) + } + }) + t.Run("Subsequent insert does not produce a new id", func(t *testing.T) { + filter := newRandomFilter(t) + ctx := tests.Context(t) + firstID, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + secondID, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + require.Equal(t, firstID, secondID) + }) + t.Run("Returns and error if name is not unique", func(t *testing.T) { + filter := newRandomFilter(t) + ctx := tests.Context(t) + _, err = orm.InsertFilter(ctx, filter) + require.NoError(t, err) + filter.EventSig = []byte(uuid.NewString()) + _, err = orm.InsertFilter(ctx, filter) + require.EqualError(t, err, `ERROR: duplicate key value violates unique constraint "solana_log_poller_filter_name" (SQLSTATE 23505)`) + }) +} + +func newRandomFilter(t *testing.T) Filter { + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + pubKey := privateKey.PublicKey() + return Filter{ + Name: uuid.NewString(), + Address: PublicKey(pubKey), + EventName: "event", + EventSig: []byte{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: [][]string{{"a", "b"}, {"c"}}, + Retention: 1000, + MaxLogsKept: 3, } } @@ -105,7 +142,7 @@ func TestLogPollerLogs(t *testing.T) { EventSig: []byte{1, 2, 3}, StartingBlock: 1, EventIDL: "{}", - SubKeyPaths: [][]string{{"a", "b"}, {"c"}}, + SubkeyPaths: [][]string{{"a", "b"}, {"c"}}, Retention: 1000, MaxLogsKept: 3, }) @@ -119,17 +156,22 @@ func TestLogPollerLogs(t *testing.T) { LogIndex: 1, BlockHash: Hash(pubKey), BlockNumber: 10, - BLockTimestamp: time.Now(), + BlockTimestamp: time.Unix(1731590113, 0), Address: PublicKey(pubKey), EventSig: []byte{3, 2, 1}, - SubKeyValues: pq.ByteaArray([][]byte{{3, 2, 1}, {1}, {1, 2}, pubKey.Bytes()}), + SubkeyValues: pq.ByteaArray([][]byte{{3, 2, 1}, {1}, {1, 2}, pubKey.Bytes()}), TxHash: Signature(signature), Data: data, } err = orm.InsertLogs(ctx, []Log{log}) require.NoError(t, err) + // insert of the same Log should not produce two instances + err = orm.InsertLogs(ctx, []Log{log}) + require.NoError(t, err) dbLogs, err := orm.SelectLogs(ctx, 0, 100, log.Address, log.EventSig) require.NoError(t, err) require.Len(t, dbLogs, 1) + log.ID = dbLogs[0].ID + log.CreatedAt = dbLogs[0].CreatedAt require.Equal(t, log, dbLogs[0]) } diff --git a/pkg/solana/logpoller/query.go b/pkg/solana/logpoller/query.go index 8757718d4..bb516d4da 100644 --- a/pkg/solana/logpoller/query.go +++ b/pkg/solana/logpoller/query.go @@ -93,9 +93,9 @@ func (q *queryArgs) withEventIDL(eventIDL string) *queryArgs { return q.withField("event_idl", eventIDL) } -// withSubKeyPaths sets the SubKeyPaths field in queryArgs. -func (q *queryArgs) withSubKeyPaths(subKeyPaths [][]string) *queryArgs { - return q.withField("sub_key_paths", subKeyPaths) +// withSubkeyPaths sets the SubkeyPaths field in queryArgs. +func (q *queryArgs) withSubkeyPaths(subkeyPaths [][]string) *queryArgs { + return q.withField("subkey_paths", subkeyPaths) } // withRetention sets the Retention field in queryArgs. @@ -143,9 +143,9 @@ func (q *queryArgs) withBlockTimestamp(blockTimestamp time.Time) *queryArgs { return q.withField("block_timestamp", blockTimestamp) } -// withSubKeyValues sets the SubKeyValues field in Log. -func (q *queryArgs) withSubKeyValues(subKeyValues pq.ByteaArray) *queryArgs { - return q.withField("sub_key_values", subKeyValues) +// withSubkeyValues sets the SubkeyValues field in Log. +func (q *queryArgs) withSubkeyValues(subkeyValues pq.ByteaArray) *queryArgs { + return q.withField("subkey_values", subkeyValues) } // withTxHash sets the TxHash field in Log. diff --git a/pkg/solana/logpoller/types.go b/pkg/solana/logpoller/types.go index c2fc948ea..5b42877a6 100644 --- a/pkg/solana/logpoller/types.go +++ b/pkg/solana/logpoller/types.go @@ -68,13 +68,13 @@ func scanFixedLengthArray(name string, maxLength int, src interface{}, dest []by return nil } -type SubKeyPaths [][]string +type SubkeyPaths [][]string -func (k SubKeyPaths) Value() (driver.Value, error) { +func (k SubkeyPaths) Value() (driver.Value, error) { return json.Marshal([][]string(k)) } -func (p *SubKeyPaths) Scan(src interface{}) error { +func (p *SubkeyPaths) Scan(src interface{}) error { var bSrc []byte switch src := src.(type) { case string: @@ -82,7 +82,7 @@ func (p *SubKeyPaths) Scan(src interface{}) error { case []byte: bSrc = src default: - return fmt.Errorf("can't scan %T into SubKeyPaths", src) + return fmt.Errorf("can't scan %T into SubkeyPaths", src) } if len(bSrc) == 0 || string(bSrc) == "null" { @@ -91,7 +91,7 @@ func (p *SubKeyPaths) Scan(src interface{}) error { err := json.Unmarshal(bSrc, p) if err != nil { - return fmt.Errorf("failed to scan %v into SubKeyPaths: %w", string(bSrc), err) + return fmt.Errorf("failed to scan %v into SubkeyPaths: %w", string(bSrc), err) } return nil