From 126b2a35831897393873f7ce7b0e7baf2513e9bb Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Thu, 9 Nov 2023 19:07:48 +0100 Subject: [PATCH] loopdb: static address store --- loopdb/sqlite.go | 6 ++ staticaddr/interface.go | 58 ++++++++++++++++ staticaddr/sql_store.go | 146 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 staticaddr/interface.go create mode 100644 staticaddr/sql_store.go diff --git a/loopdb/sqlite.go b/loopdb/sqlite.go index 068fb9a..ae2fa41 100644 --- a/loopdb/sqlite.go +++ b/loopdb/sqlite.go @@ -310,6 +310,12 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error { return tx.Commit() } +// GetNetwork returns the network(mainnet, testnet...) that the database is +// running on. +func (db *BaseDB) GetNetwork() *chaincfg.Params { + return db.network +} + // TxOptions represents a set of options one can use to control what type of // database transaction is created. Transaction can whether be read or write. type TxOptions interface { diff --git a/staticaddr/interface.go b/staticaddr/interface.go new file mode 100644 index 0000000..8b424fb --- /dev/null +++ b/staticaddr/interface.go @@ -0,0 +1,58 @@ +package staticaddr + +import ( + "context" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/keychain" +) + +var ( + ErrAddressAlreadyExists = fmt.Errorf("address already exists") + ErrAddressNotFound = fmt.Errorf("address not found") +) + +// AddressStore is the database interface that is used to store and retrieve +// static addresses. +type AddressStore interface { + // CreateStaticAddress inserts a new static address with its parameters + // into the store. + CreateStaticAddress(ctx context.Context, + addrParams *AddressParameters) error + + // GetStaticAddress fetches static address parameters for a given + // address ID. + GetStaticAddress(ctx context.Context, + pkScript []byte) (*AddressParameters, error) + + // GetAllStaticAddresses retrieves all static addresses from the store. + GetAllStaticAddresses(ctx context.Context) ( + []*AddressParameters, error) +} + +// AddressParameters holds all the necessary information for the 2-of-2 multisig +// address. +type AddressParameters struct { + // ClientPubkey is the client's pubkey for the static address. It is + // used for the 2-of-2 funding output as well as for the client's + // timeout path. + ClientPubkey *btcec.PublicKey + + // ClientPubkey is the client's pubkey for the static address. It is + // used for the 2-of-2 funding output. + ServerPubkey *btcec.PublicKey + + // Expiry is the CSV timout value at which the client can claim the + // static address's timout path. + Expiry uint32 + + // PkScript is the unique static address's output script. + PkScript []byte + + // KeyLocator is the locator of the client's key. + KeyLocator keychain.KeyLocator + + // ProtocolVersion is the protocol version of the static address. + ProtocolVersion AddressProtocolVersion +} diff --git a/staticaddr/sql_store.go b/staticaddr/sql_store.go new file mode 100644 index 0000000..7f14f7c --- /dev/null +++ b/staticaddr/sql_store.go @@ -0,0 +1,146 @@ +package staticaddr + +import ( + "context" + "errors" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/jackc/pgx/v4" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightninglabs/loop/loopdb/sqlc" + "github.com/lightningnetwork/lnd/keychain" +) + +// SqlStore is the backing store for static addresses. +type SqlStore struct { + baseDB *loopdb.BaseDB +} + +// NewSqlStore constructs a new SQLStore from a BaseDB. The BaseDB is agnostic +// to the underlying driver which can be postgres or sqlite. +func NewSqlStore(db *loopdb.BaseDB) *SqlStore { + return &SqlStore{ + baseDB: db, + } +} + +// ExecTx is a wrapper for txBody to abstract the creation and commit of a db +// transaction. The db transaction is embedded in a `*sqlc.Queries` that txBody +// needs to use when executing each one of the queries that need to be applied +// atomically. +func (s *SqlStore) ExecTx(ctx context.Context, txOptions loopdb.TxOptions, + txBody func(queries *sqlc.Queries) error) error { + + // Create the db transaction. + tx, err := s.baseDB.BeginTx(ctx, txOptions) + if err != nil { + return err + } + + // Rollback is safe to call even if the tx is already closed, so if the + // tx commits successfully, this is a no-op. + defer func() { + err := tx.Rollback() + switch { + // If the tx was already closed (it was successfully executed) + // we do not need to log that error. + case errors.Is(err, pgx.ErrTxClosed): + return + + // If this is an unexpected error, log it. + case err != nil: + log.Errorf("unable to rollback db tx: %v", err) + } + }() + + if err := txBody(s.baseDB.Queries.WithTx(tx)); err != nil { + return err + } + + // Commit transaction. + return tx.Commit() +} + +// CreateStaticAddress creates a static address record in the database. +func (s *SqlStore) CreateStaticAddress(ctx context.Context, + addrParams *AddressParameters) error { + + createArgs := sqlc.CreateStaticAddressParams{ + ClientPubkey: addrParams.ClientPubkey.SerializeCompressed(), + ServerPubkey: addrParams.ServerPubkey.SerializeCompressed(), + Expiry: int32(addrParams.Expiry), + ClientKeyFamily: int32(addrParams.KeyLocator.Family), + ClientKeyIndex: int32(addrParams.KeyLocator.Index), + Pkscript: addrParams.PkScript, + ProtocolVersion: int32(addrParams.ProtocolVersion), + } + + return s.baseDB.Queries.CreateStaticAddress(ctx, createArgs) +} + +// GetStaticAddress retrieves static address parameters for a given pkScript. +func (s *SqlStore) GetStaticAddress(ctx context.Context, + pkScript []byte) (*AddressParameters, error) { + + staticAddress, err := s.baseDB.Queries.GetStaticAddress(ctx, pkScript) + if err != nil { + return nil, err + } + + return s.toAddressParameters(staticAddress) +} + +// GetAllStaticAddresses returns all address known to the server. +func (s *SqlStore) GetAllStaticAddresses(ctx context.Context) ( + []*AddressParameters, error) { + + staticAddresses, err := s.baseDB.Queries.AllStaticAddresses(ctx) + if err != nil { + return nil, err + } + + var result []*AddressParameters + for _, address := range staticAddresses { + res, err := s.toAddressParameters(address) + if err != nil { + return nil, err + } + + result = append(result, res) + } + + return result, nil +} + +// Close closes the database connection. +func (s *SqlStore) Close() { + s.baseDB.DB.Close() +} + +// toAddressParameters transforms a database representation of a static address +// to an AddressParameters struct. +func (s *SqlStore) toAddressParameters(row sqlc.StaticAddress) ( + *AddressParameters, error) { + + clientPubkey, err := btcec.ParsePubKey(row.ClientPubkey) + if err != nil { + return nil, err + } + + serverPubkey, err := btcec.ParsePubKey(row.ServerPubkey) + if err != nil { + return nil, err + } + + return &AddressParameters{ + ClientPubkey: clientPubkey, + ServerPubkey: serverPubkey, + PkScript: row.Pkscript, + Expiry: uint32(row.Expiry), + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily(row.ClientKeyFamily), + Index: uint32(row.ClientKeyIndex), + }, + ProtocolVersion: AddressProtocolVersion(row.ProtocolVersion), + }, nil +}