Skip to content

Commit 4be6376

Browse files
committed
staticaddr: sql_store
1 parent 4a9aad4 commit 4be6376

File tree

1 file changed

+370
-0
lines changed

1 file changed

+370
-0
lines changed

staticaddr/loopin/sql_store.go

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

0 commit comments

Comments
 (0)