Skip to content

Commit dcf2151

Browse files
committed
staticaddr: deposit interface and sql store
1 parent c1661d0 commit dcf2151

File tree

3 files changed

+273
-0
lines changed

3 files changed

+273
-0
lines changed

staticaddr/deposit.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package staticaddr
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
7+
"github.com/btcsuite/btcd/btcutil"
8+
"github.com/btcsuite/btcd/wire"
9+
"github.com/lightninglabs/loop/fsm"
10+
)
11+
12+
// ID is a unique identifier for a deposit.
13+
type ID [IdLength]byte
14+
15+
// FromByteSlice creates a deposit id from a byte slice.
16+
func (r *ID) FromByteSlice(b []byte) error {
17+
if len(b) != IdLength {
18+
return fmt.Errorf("deposit id must be 32 bytes, got %d, %x",
19+
len(b), b)
20+
}
21+
22+
copy(r[:], b)
23+
24+
return nil
25+
}
26+
27+
// Deposit bundles an utxo at a static address together with manager-relevant
28+
// data.
29+
type Deposit struct {
30+
// ID is the unique identifier of the deposit.
31+
ID ID
32+
33+
// State is the current state of the deposit.
34+
State fsm.StateType
35+
36+
// The outpoint of the deposit.
37+
wire.OutPoint
38+
39+
// Value is the amount of the deposit.
40+
Value btcutil.Amount
41+
42+
// ConfirmationHeight is the absolute height at which the deposit was
43+
// first confirmed.
44+
ConfirmationHeight int64
45+
46+
// TimeOutSweepPkScript is the pk script that is used to sweep the
47+
// deposit to after it is expired.
48+
TimeOutSweepPkScript []byte
49+
}
50+
51+
// IsPending returns true if the deposit is pending.
52+
func (d *Deposit) IsPending() bool {
53+
return !d.IsFinal()
54+
}
55+
56+
// IsFinal returns true if the deposit is final.
57+
func (d *Deposit) IsFinal() bool {
58+
return d.State == SweptExpiredDeposit || d.State == Failed
59+
}
60+
61+
// GetRandomDepositID generates a random deposit ID.
62+
func GetRandomDepositID() (ID, error) {
63+
var id ID
64+
_, err := rand.Read(id[:])
65+
return id, err
66+
}

staticaddr/interface.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ var (
1313
ErrAddressNotFound = fmt.Errorf("address not found")
1414
)
1515

16+
const (
17+
IdLength = 32
18+
)
19+
1620
// AddressStore is the database interface that is used to store and retrieve
1721
// static addresses.
1822
type AddressStore interface {
@@ -29,6 +33,18 @@ type AddressStore interface {
2933
// GetAllStaticAddresses retrieves all static addresses from the store.
3034
GetAllStaticAddresses(ctx context.Context) (
3135
[]*AddressParameters, error)
36+
37+
// CreateDeposit inserts a new deposit into the store.
38+
CreateDeposit(ctx context.Context, deposit *Deposit) error
39+
40+
// UpdateDeposit updates the deposit in the database.
41+
UpdateDeposit(ctx context.Context, deposit *Deposit) error
42+
43+
// GetDeposit retrieves a deposit with depositID from the database.
44+
GetDeposit(ctx context.Context, depositID ID) (*Deposit, error)
45+
46+
// AllDeposits retrieves all deposits from the store.
47+
AllDeposits(ctx context.Context) ([]*Deposit, error)
3248
}
3349

3450
// AddressParameters holds all the necessary information for the 2-of-2 multisig

staticaddr/sql_store.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,35 @@ package staticaddr
22

33
import (
44
"context"
5+
"database/sql"
56
"errors"
67

78
"github.com/btcsuite/btcd/btcec/v2"
9+
"github.com/btcsuite/btcd/btcutil"
10+
"github.com/btcsuite/btcd/chaincfg/chainhash"
11+
"github.com/btcsuite/btcd/wire"
812
"github.com/jackc/pgx/v4"
13+
"github.com/lightninglabs/loop/fsm"
914
"github.com/lightninglabs/loop/loopdb"
1015
"github.com/lightninglabs/loop/loopdb/sqlc"
16+
"github.com/lightningnetwork/lnd/clock"
1117
"github.com/lightningnetwork/lnd/keychain"
1218
)
1319

1420
// SqlStore is the backing store for static addresses.
1521
type SqlStore struct {
1622
baseDB *loopdb.BaseDB
23+
24+
clock clock.Clock
1725
}
1826

1927
// NewSqlStore constructs a new SQLStore from a BaseDB. The BaseDB is agnostic
2028
// to the underlying driver which can be postgres or sqlite.
2129
func NewSqlStore(db *loopdb.BaseDB) *SqlStore {
2230
return &SqlStore{
2331
baseDB: db,
32+
33+
clock: clock.NewDefaultClock(),
2434
}
2535
}
2636

@@ -112,6 +122,179 @@ func (s *SqlStore) GetAllStaticAddresses(ctx context.Context) (
112122
return result, nil
113123
}
114124

125+
// CreateDeposit creates a static address record in the database.
126+
func (s *SqlStore) CreateDeposit(ctx context.Context, deposit *Deposit) error {
127+
createArgs := sqlc.CreateDepositParams{
128+
DepositID: deposit.ID[:],
129+
TxHash: deposit.Hash[:],
130+
OutIndex: int32(deposit.Index),
131+
Amount: int64(deposit.Value),
132+
ConfirmationHeight: deposit.ConfirmationHeight,
133+
TimeOutSweepPkScript: deposit.TimeOutSweepPkScript,
134+
}
135+
136+
updateArgs := sqlc.InsertDepositUpdateParams{
137+
DepositID: deposit.ID[:],
138+
UpdateTimestamp: s.clock.Now().UTC(),
139+
UpdateState: string(deposit.State),
140+
}
141+
142+
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
143+
func(q *sqlc.Queries) error {
144+
err := q.CreateDeposit(ctx, createArgs)
145+
if err != nil {
146+
return err
147+
}
148+
149+
return q.InsertDepositUpdate(ctx, updateArgs)
150+
})
151+
}
152+
153+
// UpdateDeposit updates the deposit in the database.
154+
func (s *SqlStore) UpdateDeposit(ctx context.Context, deposit *Deposit) error {
155+
insertUpdateArgs := sqlc.InsertDepositUpdateParams{
156+
DepositID: deposit.ID[:],
157+
UpdateTimestamp: s.clock.Now().UTC(),
158+
UpdateState: string(deposit.State),
159+
}
160+
161+
var (
162+
txHash = deposit.Hash[:]
163+
outIndex = sql.NullInt32{
164+
Int32: int32(deposit.Index),
165+
Valid: true,
166+
}
167+
)
168+
169+
updateArgs := sqlc.UpdateDepositParams{
170+
DepositID: deposit.ID[:],
171+
TxHash: txHash,
172+
OutIndex: outIndex.Int32,
173+
ConfirmationHeight: marshalSqlNullInt64(
174+
deposit.ConfirmationHeight,
175+
).Int64,
176+
}
177+
178+
return s.baseDB.ExecTx(ctx, &loopdb.SqliteTxOptions{},
179+
func(q *sqlc.Queries) error {
180+
err := q.UpdateDeposit(ctx, updateArgs)
181+
if err != nil {
182+
return err
183+
}
184+
185+
return q.InsertDepositUpdate(ctx, insertUpdateArgs)
186+
})
187+
}
188+
189+
// GetDeposit retrieves the deposit from the database.
190+
func (s *SqlStore) GetDeposit(ctx context.Context, id ID) (*Deposit, error) {
191+
var deposit *Deposit
192+
err := s.baseDB.ExecTx(ctx, loopdb.NewSqlReadOpts(),
193+
func(q *sqlc.Queries) error {
194+
var err error
195+
row, err := q.GetDeposit(ctx, id[:])
196+
if err != nil {
197+
return err
198+
}
199+
200+
updates, err := q.GetDepositUpdates(ctx, id[:])
201+
if err != nil {
202+
return err
203+
}
204+
205+
if len(updates) == 0 {
206+
return errors.New("no deposit updates")
207+
}
208+
209+
deposit, err = s.toDeposit(row, updates[len(updates)-1])
210+
if err != nil {
211+
return err
212+
}
213+
214+
return nil
215+
})
216+
if err != nil {
217+
return nil, err
218+
}
219+
220+
return deposit, nil
221+
}
222+
223+
// AllDeposits retrieves all known deposits to our static address.
224+
func (s *SqlStore) AllDeposits(ctx context.Context) ([]*Deposit, error) {
225+
var allDeposits []*Deposit
226+
227+
err := s.baseDB.ExecTx(ctx, loopdb.NewSqlReadOpts(),
228+
func(q *sqlc.Queries) error {
229+
var err error
230+
231+
deposits, err := q.AllDeposits(ctx)
232+
if err != nil {
233+
return err
234+
}
235+
236+
for _, deposit := range deposits {
237+
updates, err := q.GetDepositUpdates(
238+
ctx, deposit.DepositID,
239+
)
240+
if err != nil {
241+
return err
242+
}
243+
244+
if len(updates) == 0 {
245+
return errors.New("no deposit updates")
246+
}
247+
248+
lastUpdate := updates[len(updates)-1]
249+
250+
d, err := s.toDeposit(deposit, lastUpdate)
251+
if err != nil {
252+
return err
253+
}
254+
255+
allDeposits = append(allDeposits, d)
256+
}
257+
258+
return nil
259+
})
260+
if err != nil {
261+
return nil, err
262+
}
263+
264+
return allDeposits, nil
265+
}
266+
267+
// toDeposit converts an sql deposit to a deposit.
268+
func (s *SqlStore) toDeposit(row sqlc.Deposit,
269+
lastUpdate sqlc.DepositUpdate) (*Deposit, error) {
270+
271+
id := ID{}
272+
err := id.FromByteSlice(row.DepositID)
273+
if err != nil {
274+
return nil, err
275+
}
276+
277+
var txHash *chainhash.Hash
278+
if row.TxHash != nil {
279+
txHash, err = chainhash.NewHash(row.TxHash)
280+
if err != nil {
281+
return nil, err
282+
}
283+
}
284+
285+
return &Deposit{
286+
ID: id,
287+
State: fsm.StateType(lastUpdate.UpdateState),
288+
OutPoint: wire.OutPoint{
289+
Hash: *txHash,
290+
Index: uint32(row.OutIndex),
291+
},
292+
Value: btcutil.Amount(row.Amount),
293+
ConfirmationHeight: row.ConfirmationHeight,
294+
TimeOutSweepPkScript: row.TimeOutSweepPkScript,
295+
}, nil
296+
}
297+
115298
// Close closes the database connection.
116299
func (s *SqlStore) Close() {
117300
s.baseDB.DB.Close()
@@ -144,3 +327,11 @@ func (s *SqlStore) toAddressParameters(row sqlc.StaticAddress) (
144327
ProtocolVersion: AddressProtocolVersion(row.ProtocolVersion),
145328
}, nil
146329
}
330+
331+
// marshalSqlNullInt64 converts an int64 to a sql.NullInt64.
332+
func marshalSqlNullInt64(i int64) sql.NullInt64 {
333+
return sql.NullInt64{
334+
Int64: i,
335+
Valid: i != 0,
336+
}
337+
}

0 commit comments

Comments
 (0)