Skip to content

Commit d7e668a

Browse files
committed
staticaddr: sql_store
1 parent ad31112 commit d7e668a

File tree

1 file changed

+363
-0
lines changed

1 file changed

+363
-0
lines changed

staticaddr/loopin/sql_store.go

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

0 commit comments

Comments
 (0)