Skip to content

Commit 309f35f

Browse files
committed
staticaddr: sql_store
1 parent 45b2fdf commit 309f35f

File tree

1 file changed

+361
-0
lines changed

1 file changed

+361
-0
lines changed

staticaddr/loopin/sql_store.go

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
package loopin
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"strings"
8+
9+
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/btcsuite/btcd/btcutil"
11+
"github.com/btcsuite/btcd/chaincfg"
12+
"github.com/btcsuite/btcd/chaincfg/chainhash"
13+
"github.com/lightninglabs/loop/fsm"
14+
"github.com/lightninglabs/loop/loopdb"
15+
"github.com/lightninglabs/loop/loopdb/sqlc"
16+
"github.com/lightninglabs/loop/staticaddr/version"
17+
"github.com/lightningnetwork/lnd/clock"
18+
"github.com/lightningnetwork/lnd/keychain"
19+
"github.com/lightningnetwork/lnd/lntypes"
20+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
21+
)
22+
23+
const outpointSeparator = ";"
24+
25+
var (
26+
// ErrInvalidOutpoint is returned when an outpoint contains the outpoint
27+
// separator.
28+
ErrInvalidOutpoint = errors.New("outpoint contains outpoint separator")
29+
)
30+
31+
// Querier is the interface that contains all the queries generated by sqlc for
32+
// the static_address_swaps table.
33+
type Querier interface {
34+
// InsertSwap inserts a new base swap.
35+
InsertSwap(ctx context.Context, arg sqlc.InsertSwapParams) error
36+
37+
// InsertHtlcKeys inserts the htlc keys for a swap.
38+
InsertHtlcKeys(ctx context.Context, arg sqlc.InsertHtlcKeysParams) error
39+
40+
// InsertStaticAddressLoopIn inserts a new static address loop-in swap.
41+
InsertStaticAddressLoopIn(ctx context.Context,
42+
arg sqlc.InsertStaticAddressLoopInParams) error
43+
44+
// InsertStaticAddressMetaUpdate inserts metadata about loop-in
45+
// updates.
46+
InsertStaticAddressMetaUpdate(ctx context.Context,
47+
arg sqlc.InsertStaticAddressMetaUpdateParams) error
48+
49+
// UpdateStaticAddressLoopIn updates a loop-in swap.
50+
UpdateStaticAddressLoopIn(ctx context.Context,
51+
arg sqlc.UpdateStaticAddressLoopInParams) error
52+
53+
// GetStaticAddressLoopInSwap retrieves a loop-in swap by its swap hash.
54+
GetStaticAddressLoopInSwap(ctx context.Context,
55+
swapHash []byte) (sqlc.GetStaticAddressLoopInSwapRow, error)
56+
57+
// GetStaticAddressLoopInSwapsByStates retrieves all swaps with the
58+
// given states.
59+
GetStaticAddressLoopInSwapsByStates(ctx context.Context,
60+
states []string) ([]sqlc.GetStaticAddressLoopInSwapsByStatesRow,
61+
error)
62+
63+
// GetLoopInSwapUpdates retrieves all updates for a loop-in swap.
64+
GetLoopInSwapUpdates(ctx context.Context,
65+
swapHash []byte) ([]sqlc.StaticAddressSwapUpdate, error)
66+
67+
// IsStored returns true if a swap with the given hash is stored in the
68+
// database, false otherwise.
69+
IsStored(ctx context.Context, swapHash []byte) (bool, error)
70+
}
71+
72+
// BaseDB is the interface that contains all the queries generated by sqlc for
73+
// the static_address_swaps table and transaction functionality.
74+
type BaseDB interface {
75+
Querier
76+
77+
// ExecTx allows for executing a function in the context of a database
78+
// transaction.
79+
ExecTx(ctx context.Context, txOptions loopdb.TxOptions,
80+
txBody func(Querier) error) error
81+
}
82+
83+
// SqlStore is the backing store for static address loop-ins.
84+
type SqlStore struct {
85+
baseDB BaseDB
86+
clock clock.Clock
87+
network *chaincfg.Params
88+
}
89+
90+
// NewSqlStore constructs a new SQLStore from a BaseDB. The BaseDB is agnostic
91+
// to the underlying driver which can be postgres or sqlite.
92+
func NewSqlStore(db BaseDB, clock clock.Clock,
93+
network *chaincfg.Params) *SqlStore {
94+
95+
return &SqlStore{
96+
baseDB: db,
97+
clock: clock,
98+
network: network,
99+
}
100+
}
101+
102+
// GetStaticAddressLoopInSwapsByStates returns all static address loop-ins from
103+
// the db that are in the given states.
104+
func (s *SqlStore) GetStaticAddressLoopInSwapsByStates(ctx context.Context,
105+
states []fsm.StateType) ([]*StaticAddressLoopIn, error) {
106+
107+
var (
108+
err error
109+
rows []sqlc.GetStaticAddressLoopInSwapsByStatesRow
110+
updates []sqlc.StaticAddressSwapUpdate
111+
loopIn *StaticAddressLoopIn
112+
)
113+
114+
rows, err = s.baseDB.GetStaticAddressLoopInSwapsByStates(
115+
ctx, toStrings(states),
116+
)
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
loopIns := make([]*StaticAddressLoopIn, 0, len(rows))
122+
for _, row := range rows {
123+
updates, err = s.baseDB.GetLoopInSwapUpdates(
124+
ctx, row.SwapHash,
125+
)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
loopIn, err = toStaticAddressLoopIn(
131+
ctx, s.network, sqlc.GetStaticAddressLoopInSwapRow(row),
132+
updates,
133+
)
134+
if err != nil {
135+
return nil, err
136+
}
137+
138+
loopIns = append(loopIns, loopIn)
139+
}
140+
141+
return loopIns, nil
142+
}
143+
144+
func toStrings(states []fsm.StateType) []string {
145+
stringStates := make([]string, len(states))
146+
for i, state := range states {
147+
stringStates[i] = string(state)
148+
}
149+
150+
return stringStates
151+
}
152+
153+
// CreateLoopIn inserts a new loop-in swap into the database. Basic loop-in
154+
// parameters are stored in the swaps table, htlc key information is stored in
155+
// the htlc_keys table, and loop-in specific information is stored in the
156+
// static_address_swaps table.
157+
func (s *SqlStore) CreateLoopIn(ctx context.Context,
158+
loopIn *StaticAddressLoopIn) error {
159+
160+
swapArgs := sqlc.InsertSwapParams{
161+
SwapHash: loopIn.SwapHash[:],
162+
Preimage: loopIn.SwapPreimage[:],
163+
InitiationTime: loopIn.InitiationTime,
164+
AmountRequested: int64(loopIn.TotalDepositAmount()),
165+
CltvExpiry: loopIn.HtlcCltvExpiry,
166+
MaxSwapFee: int64(loopIn.MaxSwapFee),
167+
InitiationHeight: int32(loopIn.InitiationHeight),
168+
ProtocolVersion: int32(loopIn.ProtocolVersion),
169+
Label: loopIn.Label,
170+
}
171+
172+
htlcKeyArgs := sqlc.InsertHtlcKeysParams{
173+
SwapHash: loopIn.SwapHash[:],
174+
SenderScriptPubkey: loopIn.ClientPubkey.SerializeCompressed(),
175+
ReceiverScriptPubkey: loopIn.ServerPubkey.SerializeCompressed(),
176+
ClientKeyFamily: int32(loopIn.HtlcKeyLocator.Family),
177+
ClientKeyIndex: int32(loopIn.HtlcKeyLocator.Index),
178+
}
179+
180+
// Sanity check, if any of the outpoints contain the outpoint separator.
181+
// If so, we reject the loop-in to prevent potential issues with
182+
// parsing.
183+
for _, outpoint := range loopIn.DepositOutpoints {
184+
if strings.Contains(outpoint, outpointSeparator) {
185+
return ErrInvalidOutpoint
186+
}
187+
}
188+
189+
joinedOutpoints := strings.Join(
190+
loopIn.DepositOutpoints, outpointSeparator,
191+
)
192+
staticAddressLoopInParams := sqlc.InsertStaticAddressLoopInParams{
193+
SwapHash: loopIn.SwapHash[:],
194+
SwapInvoice: loopIn.SwapInvoice,
195+
LastHop: loopIn.LastHop,
196+
QuotedSwapFeeSatoshis: int64(loopIn.QuotedSwapFee),
197+
HtlcTimeoutSweepAddress: loopIn.HtlcTimeoutSweepAddress.String(),
198+
HtlcTxFeeRateSatKw: int64(loopIn.HtlcTxFeeRate),
199+
DepositOutpoints: joinedOutpoints,
200+
PaymentTimeoutSeconds: int32(loopIn.PaymentTimeoutSeconds),
201+
}
202+
203+
updateArgs := sqlc.InsertStaticAddressMetaUpdateParams{
204+
SwapHash: loopIn.SwapHash[:],
205+
UpdateTimestamp: s.clock.Now(),
206+
UpdateState: string(loopIn.GetState()),
207+
}
208+
209+
return s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
210+
func(q Querier) error {
211+
err := q.InsertSwap(ctx, swapArgs)
212+
if err != nil {
213+
return err
214+
}
215+
216+
err = q.InsertHtlcKeys(ctx, htlcKeyArgs)
217+
if err != nil {
218+
return err
219+
}
220+
221+
err = q.InsertStaticAddressLoopIn(
222+
ctx, staticAddressLoopInParams,
223+
)
224+
if err != nil {
225+
return err
226+
}
227+
228+
return q.InsertStaticAddressMetaUpdate(ctx, updateArgs)
229+
})
230+
}
231+
232+
// UpdateLoopIn updates the loop-in in the database.
233+
func (s *SqlStore) UpdateLoopIn(ctx context.Context,
234+
loopIn *StaticAddressLoopIn) error {
235+
236+
var htlcTimeoutSweepTxID string
237+
if loopIn.HtlcTimeoutSweepTxHash != nil {
238+
htlcTimeoutSweepTxID = loopIn.HtlcTimeoutSweepTxHash.String()
239+
}
240+
241+
updateParams := sqlc.UpdateStaticAddressLoopInParams{
242+
SwapHash: loopIn.SwapHash[:],
243+
HtlcTxFeeRateSatKw: int64(loopIn.HtlcTxFeeRate),
244+
HtlcTimeoutSweepTxID: sql.NullString{
245+
String: htlcTimeoutSweepTxID,
246+
Valid: htlcTimeoutSweepTxID != "",
247+
},
248+
}
249+
250+
updateArgs := sqlc.InsertStaticAddressMetaUpdateParams{
251+
SwapHash: loopIn.SwapHash[:],
252+
UpdateState: string(loopIn.GetState()),
253+
UpdateTimestamp: s.clock.Now(),
254+
}
255+
256+
return s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
257+
func(q Querier) error {
258+
err := q.UpdateStaticAddressLoopIn(ctx, updateParams)
259+
if err != nil {
260+
return err
261+
}
262+
263+
return q.InsertStaticAddressMetaUpdate(ctx, updateArgs)
264+
},
265+
)
266+
}
267+
268+
// IsStored returns true if a swap with the given hash is stored in the
269+
// database, false otherwise.
270+
func (s *SqlStore) IsStored(ctx context.Context, swapHash lntypes.Hash) (bool,
271+
error) {
272+
273+
return s.baseDB.IsStored(ctx, swapHash[:])
274+
}
275+
276+
// toStaticAddressLoopIn converts sql rows to an instant out struct.
277+
func toStaticAddressLoopIn(_ context.Context, network *chaincfg.Params,
278+
row sqlc.GetStaticAddressLoopInSwapRow,
279+
updates []sqlc.StaticAddressSwapUpdate) (*StaticAddressLoopIn, error) {
280+
281+
swapHash, err := lntypes.MakeHash(row.SwapHash)
282+
if err != nil {
283+
return nil, err
284+
}
285+
286+
swapPreImage, err := lntypes.MakePreimage(row.Preimage)
287+
if err != nil {
288+
return nil, err
289+
}
290+
291+
clientKey, err := btcec.ParsePubKey(row.SenderScriptPubkey)
292+
if err != nil {
293+
return nil, err
294+
}
295+
296+
serverKey, err := btcec.ParsePubKey(row.ReceiverScriptPubkey)
297+
if err != nil {
298+
return nil, err
299+
}
300+
301+
var htlcTimeoutSweepTxHash *chainhash.Hash
302+
if row.HtlcTimeoutSweepTxID.Valid {
303+
htlcTimeoutSweepTxHash, err = chainhash.NewHashFromStr(
304+
row.HtlcTimeoutSweepTxID.String,
305+
)
306+
if err != nil {
307+
return nil, err
308+
}
309+
}
310+
311+
depositOutpoints := strings.Split(
312+
row.DepositOutpoints, outpointSeparator,
313+
)
314+
315+
timeoutAddressString := row.HtlcTimeoutSweepAddress
316+
var timeoutAddress btcutil.Address
317+
if timeoutAddressString != "" {
318+
timeoutAddress, err = btcutil.DecodeAddress(
319+
timeoutAddressString, network,
320+
)
321+
if err != nil {
322+
return nil, err
323+
}
324+
}
325+
326+
loopIn := &StaticAddressLoopIn{
327+
SwapHash: swapHash,
328+
SwapPreimage: swapPreImage,
329+
HtlcCltvExpiry: row.CltvExpiry,
330+
MaxSwapFee: btcutil.Amount(row.MaxSwapFee),
331+
InitiationHeight: uint32(row.InitiationHeight),
332+
InitiationTime: row.InitiationTime,
333+
ProtocolVersion: version.AddressProtocolVersion(
334+
row.ProtocolVersion,
335+
),
336+
Label: row.Label,
337+
ClientPubkey: clientKey,
338+
ServerPubkey: serverKey,
339+
HtlcKeyLocator: keychain.KeyLocator{
340+
Family: keychain.KeyFamily(row.ClientKeyFamily),
341+
Index: uint32(row.ClientKeyIndex),
342+
},
343+
SwapInvoice: row.SwapInvoice,
344+
PaymentTimeoutSeconds: uint32(row.PaymentTimeoutSeconds),
345+
LastHop: row.LastHop,
346+
QuotedSwapFee: btcutil.Amount(row.QuotedSwapFeeSatoshis),
347+
DepositOutpoints: depositOutpoints,
348+
HtlcTxFeeRate: chainfee.SatPerKWeight(
349+
row.HtlcTxFeeRateSatKw,
350+
),
351+
HtlcTimeoutSweepAddress: timeoutAddress,
352+
HtlcTimeoutSweepTxHash: htlcTimeoutSweepTxHash,
353+
}
354+
355+
if len(updates) > 0 {
356+
lastUpdate := updates[len(updates)-1]
357+
loopIn.SetState(fsm.StateType(lastUpdate.UpdateState))
358+
}
359+
360+
return loopIn, nil
361+
}

0 commit comments

Comments
 (0)