diff --git a/modules/brc20/database/postgresql/migrations/000001_initialize_tables.up.sql b/modules/brc20/database/postgresql/migrations/000001_initialize_tables.up.sql index 9460bab..8b80078 100644 --- a/modules/brc20/database/postgresql/migrations/000001_initialize_tables.up.sql +++ b/modules/brc20/database/postgresql/migrations/000001_initialize_tables.up.sql @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS "brc20_tickers" ( "limit_per_mint" DECIMAL NOT NULL, "is_self_mint" BOOLEAN NOT NULL, "deploy_inscription_id" TEXT NOT NULL, - "created_at" TIMESTAMP NOT NULL + "created_at" TIMESTAMP NOT NULL, "created_at_height" INT NOT NULL ); @@ -69,7 +69,7 @@ CREATE INDEX IF NOT EXISTS brc20_deploy_events_block_height_idx ON "brc20_deploy CREATE TABLE IF NOT EXISTS "brc20_mint_events" ( "id" BIGSERIAL PRIMARY KEY, - "inscription_id" TEXT NOT NULL,, + "inscription_id" TEXT NOT NULL, "tick" TEXT NOT NULL, -- lowercase of original_tick "original_tick" TEXT NOT NULL, "tx_hash" TEXT NOT NULL, @@ -79,13 +79,13 @@ CREATE TABLE IF NOT EXISTS "brc20_mint_events" ( "pkscript" TEXT NOT NULL, "amount" DECIMAL NOT NULL, - "parent_id" TEXT, -- requires parent deploy inscription id if minting a self-mint ticker + "parent_id" TEXT -- requires parent deploy inscription id if minting a self-mint ticker ); CREATE INDEX IF NOT EXISTS brc20_mint_events_block_height_idx ON "brc20_mint_events" USING BTREE ("block_height"); CREATE TABLE IF NOT EXISTS "brc20_transfer_events" ( "id" BIGSERIAL PRIMARY KEY, - "inscription_id" TEXT NOT NULL,, + "inscription_id" TEXT NOT NULL, "tick" TEXT NOT NULL, -- lowercase of original_tick "original_tick" TEXT NOT NULL, "tx_hash" TEXT NOT NULL, @@ -95,7 +95,7 @@ CREATE TABLE IF NOT EXISTS "brc20_transfer_events" ( "from_pkscript" TEXT, -- if null, it's inscribe transfer. Otherwise, it's transfer transfer "to_pkscript" TEXT NOT NULL, - "amount" DECIMAL NOT NULL, + "amount" DECIMAL NOT NULL ); CREATE INDEX IF NOT EXISTS brc20_transfer_events_block_height_idx ON "brc20_transfer_events" USING BTREE ("block_height"); @@ -129,7 +129,7 @@ CREATE TABLE IF NOT EXISTS "brc20_inscription_locations" ( "block_height" INT NOT NULL, "tx_hash" TEXT NOT NULL, "tx_idx" INT NOT NULL, -- output index - "sat_offset" BIGINT NOT NULL + "sat_offset" BIGINT NOT NULL, PRIMARY KEY ("inscription_id", "block_height") ); CREATE INDEX IF NOT EXISTS brc20_inscription_locations_tx_hash_tx_idx_idx ON "brc20_inscription_locations" USING BTREE ("tx_hash", "tx_idx"); diff --git a/modules/brc20/database/postgresql/queries/info.sql b/modules/brc20/database/postgresql/queries/info.sql new file mode 100644 index 0000000..ca71fdc --- /dev/null +++ b/modules/brc20/database/postgresql/queries/info.sql @@ -0,0 +1,11 @@ +-- name: GetLatestIndexerState :one +SELECT * FROM brc20_indexer_state ORDER BY created_at DESC LIMIT 1; + +-- name: SetIndexerState :exec +INSERT INTO brc20_indexer_state (db_version, event_hash_version) VALUES ($1, $2); + +-- name: GetLatestIndexerStats :one +SELECT "client_version", "network" FROM brc20_indexer_stats ORDER BY id DESC LIMIT 1; + +-- name: UpdateIndexerStats :exec +INSERT INTO brc20_indexer_stats (client_version, network) VALUES ($1, $2); diff --git a/modules/brc20/internal/datagateway/brc20.go b/modules/brc20/internal/datagateway/brc20.go new file mode 100644 index 0000000..69bb1ee --- /dev/null +++ b/modules/brc20/internal/datagateway/brc20.go @@ -0,0 +1,26 @@ +package datagateway + +import ( + "context" +) + +type BRC20DataGateway interface { + BRC20ReaderDataGateway + BRC20WriterDataGateway + + // BeginBRC20Tx returns a new BRC20DataGateway with transaction enabled. All write operations performed in this datagateway must be committed to persist changes. + BeginBRC20Tx(ctx context.Context) (BRC20DataGatewayWithTx, error) +} + +type BRC20DataGatewayWithTx interface { + BRC20DataGateway + Tx +} + +type BRC20ReaderDataGateway interface { + // TODO: add methods +} + +type BRC20WriterDataGateway interface { + // TODO: add methods +} diff --git a/modules/brc20/internal/datagateway/tx.go b/modules/brc20/internal/datagateway/tx.go new file mode 100644 index 0000000..56455f6 --- /dev/null +++ b/modules/brc20/internal/datagateway/tx.go @@ -0,0 +1,12 @@ +package datagateway + +import "context" + +type Tx interface { + // Commit commits the DB transaction. All changes made after Begin() will be persisted. Calling Commit() will close the current transaction. + // If Commit() is called without a prior Begin(), it must be a no-op. + Commit(ctx context.Context) error + // Rollback rolls back the DB transaction. All changes made after Begin() will be discarded. + // Rollback() must be safe to call even if no transaction is active. Hence, a defer Rollback() is safe, even if Commit() was called prior with non-error conditions. + Rollback(ctx context.Context) error +} diff --git a/modules/brc20/internal/repository/postgres/gen/db.go b/modules/brc20/internal/repository/postgres/gen/db.go new file mode 100644 index 0000000..3ccd3c9 --- /dev/null +++ b/modules/brc20/internal/repository/postgres/gen/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package gen + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/modules/brc20/internal/repository/postgres/gen/info.sql.go b/modules/brc20/internal/repository/postgres/gen/info.sql.go new file mode 100644 index 0000000..ff87561 --- /dev/null +++ b/modules/brc20/internal/repository/postgres/gen/info.sql.go @@ -0,0 +1,70 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 +// source: info.sql + +package gen + +import ( + "context" +) + +const getLatestIndexerState = `-- name: GetLatestIndexerState :one +SELECT id, db_version, event_hash_version, created_at FROM brc20_indexer_state ORDER BY created_at DESC LIMIT 1 +` + +func (q *Queries) GetLatestIndexerState(ctx context.Context) (Brc20IndexerState, error) { + row := q.db.QueryRow(ctx, getLatestIndexerState) + var i Brc20IndexerState + err := row.Scan( + &i.Id, + &i.DbVersion, + &i.EventHashVersion, + &i.CreatedAt, + ) + return i, err +} + +const getLatestIndexerStats = `-- name: GetLatestIndexerStats :one +SELECT "client_version", "network" FROM brc20_indexer_stats ORDER BY id DESC LIMIT 1 +` + +type GetLatestIndexerStatsRow struct { + ClientVersion string + Network string +} + +func (q *Queries) GetLatestIndexerStats(ctx context.Context) (GetLatestIndexerStatsRow, error) { + row := q.db.QueryRow(ctx, getLatestIndexerStats) + var i GetLatestIndexerStatsRow + err := row.Scan(&i.ClientVersion, &i.Network) + return i, err +} + +const setIndexerState = `-- name: SetIndexerState :exec +INSERT INTO brc20_indexer_state (db_version, event_hash_version) VALUES ($1, $2) +` + +type SetIndexerStateParams struct { + DbVersion int32 + EventHashVersion int32 +} + +func (q *Queries) SetIndexerState(ctx context.Context, arg SetIndexerStateParams) error { + _, err := q.db.Exec(ctx, setIndexerState, arg.DbVersion, arg.EventHashVersion) + return err +} + +const updateIndexerStats = `-- name: UpdateIndexerStats :exec +INSERT INTO brc20_indexer_stats (client_version, network) VALUES ($1, $2) +` + +type UpdateIndexerStatsParams struct { + ClientVersion string + Network string +} + +func (q *Queries) UpdateIndexerStats(ctx context.Context, arg UpdateIndexerStatsParams) error { + _, err := q.db.Exec(ctx, updateIndexerStats, arg.ClientVersion, arg.Network) + return err +} diff --git a/modules/brc20/internal/repository/postgres/gen/models.go b/modules/brc20/internal/repository/postgres/gen/models.go new file mode 100644 index 0000000..48a00ef --- /dev/null +++ b/modules/brc20/internal/repository/postgres/gen/models.go @@ -0,0 +1,128 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package gen + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type Brc20Balance struct { + Pkscript string + BlockHeight int32 + Ticker string + OverallBalance pgtype.Numeric + AvailableBalance pgtype.Numeric +} + +type Brc20DeployEvent struct { + Id int64 + InscriptionID string + Tick string + OriginalTick string + TxHash string + BlockHeight int32 + TxIndex int32 + Timestamp pgtype.Timestamp + Pkscript string + TotalSupply pgtype.Numeric + Decimals int16 + LimitPerMint pgtype.Numeric + IsSelfMint bool +} + +type Brc20IndexedBlock struct { + Height int32 + Hash string + PrevHash string + EventHash string + CumulativeEventHash string +} + +type Brc20IndexerStat struct { + Id int64 + ClientVersion string + Network string + CreatedAt pgtype.Timestamptz +} + +type Brc20IndexerState struct { + Id int64 + DbVersion int32 + EventHashVersion int32 + CreatedAt pgtype.Timestamptz +} + +type Brc20Inscription struct { + Id string + Number int64 + SequenceNumber int64 + Delegate pgtype.Text + Metadata []byte + Metaprotocol pgtype.Text + Parent pgtype.Text + Pointer pgtype.Int8 + Content []byte + ContentType string + TransferCount int32 + CreatedAt pgtype.Timestamp + CreatedAtHeight int32 +} + +type Brc20InscriptionLocation struct { + InscriptionID string + BlockHeight int32 + TxHash string + TxIdx int32 + SatOffset int64 +} + +type Brc20MintEvent struct { + Id int64 + InscriptionID string + Tick string + OriginalTick string + TxHash string + BlockHeight int32 + TxIndex int32 + Timestamp pgtype.Timestamp + Pkscript string + Amount pgtype.Numeric + ParentID pgtype.Text +} + +type Brc20Ticker struct { + Tick string + OriginalTick string + TotalSupply pgtype.Numeric + Decimals int16 + LimitPerMint pgtype.Numeric + IsSelfMint bool + DeployInscriptionID string + CreatedAt pgtype.Timestamp + CreatedAtHeight int32 +} + +type Brc20TickerState struct { + Tick string + BlockHeight int32 + MintedAmount pgtype.Numeric + BurnedAmount pgtype.Numeric + CompletedAt pgtype.Timestamp + CompletedAtHeight pgtype.Int4 +} + +type Brc20TransferEvent struct { + Id int64 + InscriptionID string + Tick string + OriginalTick string + TxHash string + BlockHeight int32 + TxIndex int32 + Timestamp pgtype.Timestamp + FromPkscript pgtype.Text + ToPkscript string + Amount pgtype.Numeric +} diff --git a/modules/brc20/internal/repository/postgres/postgres.go b/modules/brc20/internal/repository/postgres/postgres.go new file mode 100644 index 0000000..2ec2b65 --- /dev/null +++ b/modules/brc20/internal/repository/postgres/postgres.go @@ -0,0 +1,20 @@ +package postgres + +import ( + "github.com/gaze-network/indexer-network/internal/postgres" + "github.com/gaze-network/indexer-network/modules/brc20/internal/repository/postgres/gen" + "github.com/jackc/pgx/v5" +) + +type Repository struct { + db postgres.DB + queries *gen.Queries + tx pgx.Tx +} + +func NewRepository(db postgres.DB) *Repository { + return &Repository{ + db: db, + queries: gen.New(db), + } +} diff --git a/modules/brc20/internal/repository/postgres/tx.go b/modules/brc20/internal/repository/postgres/tx.go new file mode 100644 index 0000000..95257b2 --- /dev/null +++ b/modules/brc20/internal/repository/postgres/tx.go @@ -0,0 +1,62 @@ +package postgres + +import ( + "context" + + "github.com/cockroachdb/errors" + "github.com/gaze-network/indexer-network/modules/brc20/internal/datagateway" + "github.com/gaze-network/indexer-network/pkg/logger" + "github.com/jackc/pgx/v5" +) + +var ErrTxAlreadyExists = errors.New("Transaction already exists. Call Commit() or Rollback() first.") + +func (r *Repository) begin(ctx context.Context) (*Repository, error) { + if r.tx != nil { + return nil, errors.WithStack(ErrTxAlreadyExists) + } + tx, err := r.db.Begin(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to begin transaction") + } + return &Repository{ + db: r.db, + queries: r.queries.WithTx(tx), + tx: tx, + }, nil +} + +func (r *Repository) BeginBRC20Tx(ctx context.Context) (datagateway.BRC20DataGatewayWithTx, error) { + repo, err := r.begin(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + return repo, nil +} + +func (r *Repository) Commit(ctx context.Context) error { + if r.tx == nil { + return nil + } + err := r.tx.Commit(ctx) + if err != nil { + return errors.Wrap(err, "failed to commit transaction") + } + r.tx = nil + return nil +} + +func (r *Repository) Rollback(ctx context.Context) error { + if r.tx == nil { + return nil + } + err := r.tx.Rollback(ctx) + if err != nil && !errors.Is(err, pgx.ErrTxClosed) { + return errors.Wrap(err, "failed to rollback transaction") + } + if err == nil { + logger.DebugContext(ctx, "rolled back transaction") + } + r.tx = nil + return nil +} diff --git a/sqlc.yaml b/sqlc.yaml index 73e9cff..9ecda04 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -17,3 +17,13 @@ sql: sql_package: "pgx/v5" rename: id: "Id" + - schema: "./modules/brc20/database/postgresql/migrations" + queries: "./modules/brc20/database/postgresql/queries" + engine: "postgresql" + gen: + go: + package: "gen" + out: "./modules/brc20/internal/repository/postgres/gen" + sql_package: "pgx/v5" + rename: + id: "Id"