diff --git a/go.mod b/go.mod index e677de42..f587c85f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/breez/lspd go 1.19 require ( + github.com/GoWebProd/uuid7 v0.0.0-20230623091058-5f5954faed6a github.com/aws/aws-sdk-go v1.34.0 github.com/breez/lntest v0.0.27 github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd @@ -28,6 +29,7 @@ require ( ) require ( + github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect diff --git a/lsps2/mocks.go b/lsps2/mocks.go index 90be1133..7db88793 100644 --- a/lsps2/mocks.go +++ b/lsps2/mocks.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/GoWebProd/uuid7" "github.com/breez/lspd/chain" "github.com/breez/lspd/common" "github.com/breez/lspd/lightning" @@ -83,7 +84,7 @@ func (s *mockLsps2Store) SetChannelOpened(ctx context.Context, channelOpened *Ch return s.err } -func (s *mockLsps2Store) SetCompleted(ctx context.Context, registrationId uint64) error { +func (s *mockLsps2Store) SetCompleted(ctx context.Context, registrationId uuid7.UUID) error { return nil } diff --git a/lsps2/store.go b/lsps2/store.go index 5d186ed5..d202f736 100644 --- a/lsps2/store.go +++ b/lsps2/store.go @@ -6,6 +6,7 @@ import ( "log" "time" + "github.com/GoWebProd/uuid7" "github.com/breez/lspd/common" "github.com/breez/lspd/lightning" "github.com/breez/lspd/lsps0" @@ -27,7 +28,7 @@ type RegisterBuy struct { } type BuyRegistration struct { - Id uint64 + Id uuid7.UUID LspId string PeerId string // TODO: Make peerId in the registration a byte array. Token string @@ -54,7 +55,7 @@ func (b *BuyRegistration) IsExpired() bool { } type ChannelOpened struct { - RegistrationId uint64 + RegistrationId uuid7.UUID Outpoint *wire.OutPoint FeeMsat uint64 PaymentSizeMsat uint64 @@ -68,6 +69,6 @@ type Lsps2Store interface { RegisterBuy(ctx context.Context, req *RegisterBuy) error GetBuyRegistration(ctx context.Context, scid lightning.ShortChannelID) (*BuyRegistration, error) SetChannelOpened(ctx context.Context, channelOpened *ChannelOpened) error - SetCompleted(ctx context.Context, registrationId uint64) error + SetCompleted(ctx context.Context, registrationId uuid7.UUID) error RemoveUnusedExpired(ctx context.Context, before time.Time) error } diff --git a/postgresql/connect.go b/postgresql/connect.go index 92f885a2..45bf9e00 100644 --- a/postgresql/connect.go +++ b/postgresql/connect.go @@ -4,14 +4,22 @@ import ( "context" "fmt" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) func PgConnect(databaseUrl string) (*pgxpool.Pool, error) { - var err error - pgxPool, err := pgxpool.New(context.Background(), databaseUrl) + dbconfig, err := pgxpool.ParseConfig(databaseUrl) if err != nil { - return nil, fmt.Errorf("pgxpool.Connect(%v): %w", databaseUrl, err) + return nil, err + } + dbconfig.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error { + RegisterUuid7(conn.TypeMap()) + return nil + } + pgxPool, err := pgxpool.NewWithConfig(context.Background(), dbconfig) + if err != nil { + return nil, fmt.Errorf("pgxpool.NewWithConfig(%v): %w", databaseUrl, err) } return pgxPool, nil } diff --git a/postgresql/lsps2_store.go b/postgresql/lsps2_store.go index 093ac7ef..89457b7d 100644 --- a/postgresql/lsps2_store.go +++ b/postgresql/lsps2_store.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/GoWebProd/uuid7" "github.com/breez/lspd/common" "github.com/breez/lspd/lightning" "github.com/breez/lspd/lsps0" @@ -17,11 +18,13 @@ import ( ) type Lsps2Store struct { - pool *pgxpool.Pool + pool *pgxpool.Pool + generator *uuid7.Generator } func NewLsps2Store(pool *pgxpool.Pool) *Lsps2Store { - return &Lsps2Store{pool: pool} + generator := uuid7.New() + return &Lsps2Store{pool: pool, generator: generator} } func (s *Lsps2Store) RegisterBuy( @@ -41,10 +44,12 @@ func (s *Lsps2Store) RegisterBuy( return fmt.Errorf("promise does not have matching token") } + uuid := s.generator.Next() _, err = s.pool.Exec( ctx, `INSERT INTO lsps2.buy_registrations ( - lsp_id + id + , lsp_id , peer_id , scid , mode @@ -57,7 +62,8 @@ func (s *Lsps2Store) RegisterBuy( , params_promise , token ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, + uuid, req.LspId, req.PeerId, int64(uint64(req.Scid)), @@ -106,7 +112,7 @@ func (s *Lsps2Store) GetBuyRegistration(ctx context.Context, scid lightning.Shor WHERE r.scid = $1`, int64(uint64(scid)), ) - var db_id uint64 + var db_id uuid7.UUID var db_lsp_id string var db_peer_id string var db_scid int64 @@ -187,16 +193,19 @@ func (s *Lsps2Store) GetBuyRegistration(ctx context.Context, scid lightning.Shor } func (s *Lsps2Store) SetChannelOpened(ctx context.Context, channelOpened *lsps2.ChannelOpened) error { + uuid := s.generator.Next() _, err := s.pool.Exec( ctx, `INSERT INTO lsps2.bought_channels ( + id, registration_id, funding_tx_id, funding_tx_outnum, fee_msat, payment_size_msat, is_completed - ) VALUES ($1, $2, $3, $4, $5, false)`, + ) VALUES ($1, $2, $3, $4, $5, $6, false)`, + uuid, channelOpened.RegistrationId, channelOpened.Outpoint.Hash[:], channelOpened.Outpoint.Index, @@ -207,7 +216,7 @@ func (s *Lsps2Store) SetChannelOpened(ctx context.Context, channelOpened *lsps2. return err } -func (s *Lsps2Store) SetCompleted(ctx context.Context, registrationId uint64) error { +func (s *Lsps2Store) SetCompleted(ctx context.Context, registrationId uuid7.UUID) error { rows, err := s.pool.Exec( ctx, `UPDATE lsps2.bought_channels diff --git a/postgresql/migrations/000014_lsps2_buy.up.sql b/postgresql/migrations/000014_lsps2_buy.up.sql index 0abf2e09..e36db01d 100644 --- a/postgresql/migrations/000014_lsps2_buy.up.sql +++ b/postgresql/migrations/000014_lsps2_buy.up.sql @@ -1,6 +1,6 @@ CREATE SCHEMA lsps2; CREATE TABLE lsps2.buy_registrations ( - id bigserial PRIMARY KEY, + id uuid PRIMARY KEY, lsp_id varchar NOT NULL, peer_id varchar NOT NULL, scid bigint NOT NULL, @@ -18,8 +18,8 @@ CREATE UNIQUE INDEX idx_lsps2_buy_registrations_scid ON lsps2.buy_registrations CREATE INDEX idx_lsps2_buy_registrations_valid_until ON lsps2.buy_registrations (params_valid_until); CREATE TABLE lsps2.bought_channels ( - id bigserial PRIMARY KEY, - registration_id bigint NOT NULL, + id uuid PRIMARY KEY, + registration_id uuid NOT NULL, funding_tx_id bytea NOT NULL, funding_tx_outnum bigint NOT NULL, fee_msat bigint NOT NULL, diff --git a/postgresql/uuid7.go b/postgresql/uuid7.go new file mode 100644 index 00000000..4a478948 --- /dev/null +++ b/postgresql/uuid7.go @@ -0,0 +1,96 @@ +package postgresql + +import ( + "fmt" + + "github.com/GoWebProd/uuid7" + "github.com/jackc/pgx/v5/pgtype" +) + +type UUID uuid7.UUID + +func (u *UUID) ScanUUID(v pgtype.UUID) error { + if !v.Valid { + return fmt.Errorf("cannot scan NULL into *uuid.UUID") + } + + *u = v.Bytes + return nil +} + +func (u UUID) UUIDValue() (pgtype.UUID, error) { + return pgtype.UUID{Bytes: [16]byte(u), Valid: true}, nil +} + +func TryWrapUUIDEncodePlan(value interface{}) (plan pgtype.WrappedEncodePlanNextSetter, nextValue interface{}, ok bool) { + switch value := value.(type) { + case uuid7.UUID: + return &wrapUUIDEncodePlan{}, UUID(value), true + } + + return nil, nil, false +} + +type wrapUUIDEncodePlan struct { + next pgtype.EncodePlan +} + +func (plan *wrapUUIDEncodePlan) SetNext(next pgtype.EncodePlan) { plan.next = next } + +func (plan *wrapUUIDEncodePlan) Encode(value interface{}, buf []byte) (newBuf []byte, err error) { + return plan.next.Encode(UUID(value.(uuid7.UUID)), buf) +} + +func TryWrapUUIDScanPlan(target interface{}) (plan pgtype.WrappedScanPlanNextSetter, nextDst interface{}, ok bool) { + switch target := target.(type) { + case *uuid7.UUID: + return &wrapUUIDScanPlan{}, (*UUID)(target), true + } + + return nil, nil, false +} + +type wrapUUIDScanPlan struct { + next pgtype.ScanPlan +} + +func (plan *wrapUUIDScanPlan) SetNext(next pgtype.ScanPlan) { plan.next = next } + +func (plan *wrapUUIDScanPlan) Scan(src []byte, dst interface{}) error { + return plan.next.Scan(src, (*UUID)(dst.(*uuid7.UUID))) +} + +type UUIDCodec struct { + pgtype.UUIDCodec +} + +func (UUIDCodec) DecodeValue(tm *pgtype.Map, oid uint32, format int16, src []byte) (interface{}, error) { + if src == nil { + return nil, nil + } + + var target uuid7.UUID + scanPlan := tm.PlanScan(oid, format, &target) + if scanPlan == nil { + return nil, fmt.Errorf("PlanScan did not find a plan") + } + + err := scanPlan.Scan(src, &target) + if err != nil { + return nil, err + } + + return target, nil +} + +// RegisterUuid7 registers the github.com/GoWebProd/uuid7 integration with a pgtype.Map. +func RegisterUuid7(tm *pgtype.Map) { + tm.TryWrapEncodePlanFuncs = append([]pgtype.TryWrapEncodePlanFunc{TryWrapUUIDEncodePlan}, tm.TryWrapEncodePlanFuncs...) + tm.TryWrapScanPlanFuncs = append([]pgtype.TryWrapScanPlanFunc{TryWrapUUIDScanPlan}, tm.TryWrapScanPlanFuncs...) + + tm.RegisterType(&pgtype.Type{ + Name: "uuid", + OID: pgtype.UUIDOID, + Codec: UUIDCodec{}, + }) +}