@@ -6,7 +6,10 @@ import (
66 "fmt"
77 "net/url"
88 "path/filepath"
9+ "strconv"
10+ "strings"
911 "testing"
12+ "time"
1013
1114 "github.com/btcsuite/btcd/chaincfg"
1215 sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite"
@@ -109,13 +112,25 @@ func NewSqliteStore(cfg *SqliteConfig, network *chaincfg.Params) (*SqliteSwapSto
109112
110113 queries := sqlc .New (db )
111114
115+ baseDB := & BaseDB {
116+ DB : db ,
117+ Queries : queries ,
118+ network : network ,
119+ }
120+
121+ // Fix faulty timestamps in the database.
122+ ctx , cancel := context .WithTimeout (context .Background (), time .Minute )
123+ defer cancel ()
124+
125+ err = baseDB .FixFaultyTimestamps (ctx , parseSqliteTimeStamp )
126+ if err != nil {
127+ log .Errorf ("Failed to fix faulty timestamps: %v" , err )
128+ return nil , err
129+ }
130+
112131 return & SqliteSwapStore {
113- cfg : cfg ,
114- BaseDB : & BaseDB {
115- DB : db ,
116- Queries : queries ,
117- network : network ,
118- },
132+ cfg : cfg ,
133+ BaseDB : baseDB ,
119134 }, nil
120135}
121136
@@ -127,6 +142,7 @@ func NewTestSqliteDB(t *testing.T) *SqliteSwapStore {
127142 t .Logf ("Creating new SQLite DB for testing" )
128143
129144 dbFileName := filepath .Join (t .TempDir (), "tmp.db" )
145+
130146 sqlDB , err := NewSqliteStore (& SqliteConfig {
131147 DatabaseFileName : dbFileName ,
132148 SkipMigrations : false ,
@@ -191,6 +207,79 @@ func (db *BaseDB) ExecTx(ctx context.Context, txOptions TxOptions,
191207 return nil
192208}
193209
210+ // FixFaultyTimestamps fixes faulty timestamps in the database, caused
211+ // by using milliseconds instead of seconds as the publication deadline.
212+ func (b * BaseDB ) FixFaultyTimestamps (ctx context.Context ,
213+ parseTimeFunc func (string ) (time.Time , error )) error {
214+
215+ // Manually fetch all the loop out swaps.
216+ rows , err := b .DB .QueryContext (
217+ ctx , "SELECT swap_hash, publication_deadline FROM loopout_swaps" ,
218+ )
219+ if err != nil {
220+ return err
221+ }
222+
223+ // Parse the rows into a struct. We need to do this manually because
224+ // the sqlite driver will fail on faulty timestamps.
225+ type LoopOutRow struct {
226+ Hash []byte `json:"swap_hash"`
227+ PublicationDeadline string `json:"publication_deadline"`
228+ }
229+
230+ var loopOutSwaps []LoopOutRow
231+
232+ for rows .Next () {
233+ var swap LoopOutRow
234+ err := rows .Scan (
235+ & swap .Hash , & swap .PublicationDeadline ,
236+ )
237+ if err != nil {
238+ return err
239+ }
240+
241+ loopOutSwaps = append (loopOutSwaps , swap )
242+ }
243+
244+ tx , err := b .BeginTx (ctx , & SqliteTxOptions {})
245+ if err != nil {
246+ return err
247+ }
248+ defer tx .Rollback () //nolint: errcheck
249+
250+ for _ , swap := range loopOutSwaps {
251+ faultyTime , err := parseTimeFunc (swap .PublicationDeadline )
252+ if err != nil {
253+ return err
254+ }
255+
256+ // Skip if the time is not faulty.
257+ if ! isMilisecondsTime (faultyTime .Unix ()) {
258+ continue
259+ }
260+
261+ // Update the faulty time to a valid time.
262+ secs := faultyTime .Unix () / 1000
263+ correctTime := time .Unix (secs , 0 )
264+ _ , err = tx .ExecContext (
265+ ctx , `
266+ UPDATE
267+ loopout_swaps
268+ SET
269+ publication_deadline = $1
270+ WHERE
271+ swap_hash = $2;
272+ ` ,
273+ correctTime , swap .Hash ,
274+ )
275+ if err != nil {
276+ return err
277+ }
278+ }
279+
280+ return tx .Commit ()
281+ }
282+
194283// TxOptions represents a set of options one can use to control what type of
195284// database transaction is created. Transaction can wither be read or write.
196285type TxOptions interface {
@@ -219,3 +308,85 @@ func NewSqlReadOpts() *SqliteTxOptions {
219308func (r * SqliteTxOptions ) ReadOnly () bool {
220309 return r .readOnly
221310}
311+
312+ // parseSqliteTimeStamp parses a timestamp string in the format of
313+ // "YYYY-MM-DD HH:MM:SS +0000 UTC" and returns a time.Time value.
314+ // NOTE: we can't use time.Parse() because it doesn't support having years
315+ // with more than 4 digits.
316+ func parseSqliteTimeStamp (dateTimeStr string ) (time.Time , error ) {
317+ // Split the date and time parts.
318+ parts := strings .Fields (strings .TrimSpace (dateTimeStr ))
319+ datePart , timePart := parts [0 ], parts [1 ]
320+
321+ return parseTimeParts (datePart , timePart )
322+ }
323+
324+ // parseSqliteTimeStamp parses a timestamp string in the format of
325+ // "YYYY-MM-DDTHH:MM:SSZ" and returns a time.Time value.
326+ // NOTE: we can't use time.Parse() because it doesn't support having years
327+ // with more than 4 digits.
328+ func parsePostgresTimeStamp (dateTimeStr string ) (time.Time , error ) {
329+ // Split the date and time parts.
330+ parts := strings .Split (dateTimeStr , "T" )
331+ datePart , timePart := parts [0 ], strings .TrimSuffix (parts [1 ], "Z" )
332+
333+ return parseTimeParts (datePart , timePart )
334+ }
335+
336+ // parseTimeParts takes a datePart string in the format of "YYYY-MM-DD" and
337+ // a timePart string in the format of "HH:MM:SS" and returns a time.Time value.
338+ func parseTimeParts (datePart , timePart string ) (time.Time , error ) {
339+ // Parse the date.
340+ dateParts := strings .Split (datePart , "-" )
341+
342+ year , err := strconv .Atoi (dateParts [0 ])
343+ if err != nil {
344+ return time.Time {}, err
345+ }
346+
347+ month , err := strconv .Atoi (dateParts [1 ])
348+ if err != nil {
349+ return time.Time {}, err
350+ }
351+
352+ day , err := strconv .Atoi (dateParts [2 ])
353+ if err != nil {
354+ return time.Time {}, err
355+ }
356+
357+ // Parse the time.
358+ timeParts := strings .Split (timePart , ":" )
359+
360+ hour , err := strconv .Atoi (timeParts [0 ])
361+ if err != nil {
362+ return time.Time {}, err
363+ }
364+
365+ minute , err := strconv .Atoi (timeParts [1 ])
366+ if err != nil {
367+ return time.Time {}, err
368+ }
369+
370+ second , err := strconv .Atoi (timeParts [2 ])
371+ if err != nil {
372+ return time.Time {}, err
373+ }
374+
375+ // Construct a time.Time value.
376+ return time .Date (
377+ year , time .Month (month ), day , hour , minute , second , 0 , time .UTC ,
378+ ), nil
379+ }
380+
381+ // isMilisecondsTime returns true if the unix timestamp is likely in
382+ // milliseconds.
383+ func isMilisecondsTime (unixTimestamp int64 ) bool {
384+ length := len (fmt .Sprintf ("%d" , unixTimestamp ))
385+ if length >= 13 {
386+ // Likely a millisecond timestamp
387+ return true
388+ } else {
389+ // Likely a second timestamp
390+ return false
391+ }
392+ }
0 commit comments