mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Merge pull request #630 from sputn1ck/timeparsing_fix
sqldb: fix timeparsing edge cases
This commit is contained in:
commit
cf59c4856f
2 changed files with 115 additions and 118 deletions
|
|
@ -328,8 +328,10 @@ func TestIssue615(t *testing.T) {
|
|||
|
||||
// Create a faulty loopout swap.
|
||||
destAddr := test.GetDestAddr(t, 0)
|
||||
faultyTime, err := parseSqliteTimeStamp("55563-06-27 02:09:24 +0000 UTC")
|
||||
require.NoError(t, err)
|
||||
// Corresponds to 55563-06-27 02:09:24 +0000 UTC.
|
||||
faultyTime := time.Unix(1691247002964, 0)
|
||||
|
||||
t.Log(faultyTime.Unix())
|
||||
|
||||
unrestrictedSwap := LoopOutContract{
|
||||
SwapContract: SwapContract{
|
||||
|
|
@ -362,7 +364,7 @@ func TestIssue615(t *testing.T) {
|
|||
SwapPublicationDeadline: faultyTime,
|
||||
}
|
||||
|
||||
err = sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap)
|
||||
err := sqlDB.CreateLoopOut(ctxb, testPreimage.Hash(), &unrestrictedSwap)
|
||||
require.NoError(t, err)
|
||||
|
||||
// This should fail because of the faulty timestamp.
|
||||
|
|
@ -394,30 +396,54 @@ func TestTimeConversions(t *testing.T) {
|
|||
},
|
||||
{
|
||||
timeString: "2018-11-01 00:00:01.10000 +0000 UTC",
|
||||
expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 0, time.UTC),
|
||||
expectedTime: time.Date(2018, 11, 1, 0, 0, 1, 100000000, time.UTC),
|
||||
},
|
||||
{
|
||||
timeString: "2053-12-29T02:40:44.269009408Z",
|
||||
expectedTime: time.Date(
|
||||
2053, 12, 29, 2, 40, 44, 0, time.UTC,
|
||||
time.Now().Year(), 12, 29, 2, 40, 44, 269009408, time.UTC,
|
||||
),
|
||||
},
|
||||
{
|
||||
timeString: "55563-06-27 02:09:24 +0000 UTC",
|
||||
expectedTime: time.Date(
|
||||
55563, 6, 27, 2, 9, 24, 0, time.UTC,
|
||||
time.Now().Year(), 6, 27, 2, 9, 24, 0, time.UTC,
|
||||
),
|
||||
},
|
||||
{
|
||||
timeString: "2172-03-11 10:01:11.849906176 +0000 UTC",
|
||||
expectedTime: time.Date(
|
||||
2172, 3, 11, 10, 1, 11, 0, time.UTC,
|
||||
time.Now().Year(), 3, 11, 10, 1, 11, 849906176, time.UTC,
|
||||
),
|
||||
},
|
||||
{
|
||||
timeString: "2023-08-04 16:07:49 +0800 CST",
|
||||
expectedTime: time.Date(
|
||||
2023, 8, 4, 8, 7, 49, 0, time.UTC,
|
||||
),
|
||||
},
|
||||
{
|
||||
timeString: "2023-08-04 16:07:49 -0700 MST",
|
||||
expectedTime: time.Date(
|
||||
2023, 8, 4, 23, 7, 49, 0, time.UTC,
|
||||
),
|
||||
},
|
||||
{
|
||||
timeString: "2023-08-04T16:07:49+08:00",
|
||||
expectedTime: time.Date(
|
||||
2023, 8, 4, 8, 7, 49, 0, time.UTC,
|
||||
),
|
||||
},
|
||||
{
|
||||
timeString: "2023-08-04T16:07:49+08:00",
|
||||
expectedTime: time.Date(
|
||||
2023, 8, 4, 8, 7, 49, 0, time.UTC,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
time, err := parseTimeStamp(test.timeString)
|
||||
time, err := fixTimeStamp(test.timeString)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expectedTime, time)
|
||||
}
|
||||
|
|
|
|||
191
loopdb/sqlite.go
191
loopdb/sqlite.go
|
|
@ -3,6 +3,7 @@ package loopdb
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
|
@ -247,19 +248,25 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
|
|||
defer tx.Rollback() //nolint: errcheck
|
||||
|
||||
for _, swap := range loopOutSwaps {
|
||||
faultyTime, err := parseTimeStamp(swap.PublicationDeadline)
|
||||
|
||||
// Get the year of the timestamp.
|
||||
year, err := getTimeStampYear(swap.PublicationDeadline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip if the time is not faulty.
|
||||
if !isMilisecondsTime(faultyTime.Unix()) {
|
||||
// Skip if the year is not in the future.
|
||||
thisYear := time.Now().Year()
|
||||
if year <= thisYear {
|
||||
continue
|
||||
}
|
||||
|
||||
fixedTime, err := fixTimeStamp(swap.PublicationDeadline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the faulty time to a valid time.
|
||||
secs := faultyTime.Unix() / 1000
|
||||
correctTime := time.Unix(secs, 0)
|
||||
_, err = tx.ExecContext(
|
||||
ctx, `
|
||||
UPDATE
|
||||
|
|
@ -269,7 +276,7 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
|
|||
WHERE
|
||||
swap_hash = $2;
|
||||
`,
|
||||
correctTime, swap.Hash,
|
||||
fixedTime, swap.Hash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -308,120 +315,84 @@ func (r *SqliteTxOptions) ReadOnly() bool {
|
|||
return r.readOnly
|
||||
}
|
||||
|
||||
// parseTimeStamp tries to parse a timestamp string with both the
|
||||
// fixTimeStamp tries to parse a timestamp string with both the
|
||||
// parseSqliteTimeStamp and parsePostgresTimeStamp functions.
|
||||
// If both fail, it returns an error.
|
||||
func parseTimeStamp(dateTimeStr string) (time.Time, error) {
|
||||
t, err := parseSqliteTimeStamp(dateTimeStr)
|
||||
func fixTimeStamp(dateTimeStr string) (time.Time, error) {
|
||||
year, err := getTimeStampYear(dateTimeStr)
|
||||
if err != nil {
|
||||
t, err = parsePostgresTimeStamp(dateTimeStr)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
// If the year is in the future. It was a faulty timestamp.
|
||||
thisYear := time.Now().Year()
|
||||
if year > thisYear {
|
||||
dateTimeStr = strings.Replace(
|
||||
dateTimeStr,
|
||||
fmt.Sprintf("%d", year),
|
||||
fmt.Sprintf("%d", thisYear),
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
parsedTime, err := parseLayouts(defaultLayouts(), dateTimeStr)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("unable to parse timestamp %v: %v",
|
||||
dateTimeStr, err)
|
||||
}
|
||||
|
||||
return parsedTime.UTC(), nil
|
||||
}
|
||||
|
||||
// parseLayouts parses time based on a list of provided layouts.
|
||||
// If layouts is empty list or nil, the error with unknown layout will be returned.
|
||||
func parseLayouts(layouts []string, dateTime string) (time.Time, error) {
|
||||
for _, layout := range layouts {
|
||||
parsedTime, err := time.Parse(layout, dateTime)
|
||||
if err == nil {
|
||||
return parsedTime, nil
|
||||
}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
return time.Time{}, errors.New("unknown layout")
|
||||
}
|
||||
|
||||
// parseSqliteTimeStamp parses a timestamp string in the format of
|
||||
// "YYYY-MM-DD HH:MM:SS +0000 UTC" and returns a time.Time value.
|
||||
// NOTE: we can't use time.Parse() because it doesn't support having years
|
||||
// with more than 4 digits.
|
||||
func parseSqliteTimeStamp(dateTimeStr string) (time.Time, error) {
|
||||
// Split the date and time parts.
|
||||
parts := strings.Fields(strings.TrimSpace(dateTimeStr))
|
||||
if len(parts) < 2 {
|
||||
return time.Time{}, fmt.Errorf("invalid timestamp format: %v",
|
||||
// defaultLayouts returns a default list of ALL supported layouts.
|
||||
// This function returns new copy of a slice.
|
||||
func defaultLayouts() []string {
|
||||
return []string{
|
||||
"2006-01-02 15:04:05.99999 -0700 MST", // Custom sqlite layout.
|
||||
time.RFC3339Nano,
|
||||
time.RFC3339,
|
||||
time.RFC1123Z,
|
||||
time.RFC1123,
|
||||
time.RFC850,
|
||||
time.RFC822Z,
|
||||
time.RFC822,
|
||||
time.Layout,
|
||||
time.RubyDate,
|
||||
time.UnixDate,
|
||||
time.ANSIC,
|
||||
time.StampNano,
|
||||
time.StampMicro,
|
||||
time.StampMilli,
|
||||
time.Stamp,
|
||||
time.Kitchen,
|
||||
}
|
||||
}
|
||||
|
||||
// getTimeStampYear returns the year of a timestamp string.
|
||||
func getTimeStampYear(dateTimeStr string) (int, error) {
|
||||
parts := strings.Split(dateTimeStr, "-")
|
||||
if len(parts) < 1 {
|
||||
return 0, fmt.Errorf("invalid timestamp format: %v",
|
||||
dateTimeStr)
|
||||
}
|
||||
|
||||
datePart, timePart := parts[0], parts[1]
|
||||
year, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to parse year: %v", err)
|
||||
}
|
||||
|
||||
return parseTimeParts(datePart, timePart)
|
||||
}
|
||||
|
||||
// parseSqliteTimeStamp parses a timestamp string in the format of
|
||||
// "YYYY-MM-DDTHH:MM:SSZ" and returns a time.Time value.
|
||||
// NOTE: we can't use time.Parse() because it doesn't support having years
|
||||
// with more than 4 digits.
|
||||
func parsePostgresTimeStamp(dateTimeStr string) (time.Time, error) {
|
||||
// Split the date and time parts.
|
||||
parts := strings.Split(dateTimeStr, "T")
|
||||
if len(parts) != 2 {
|
||||
return time.Time{}, fmt.Errorf("invalid timestamp format: %v",
|
||||
dateTimeStr)
|
||||
}
|
||||
|
||||
datePart, timePart := parts[0], strings.TrimSuffix(parts[1], "Z")
|
||||
|
||||
return parseTimeParts(datePart, timePart)
|
||||
}
|
||||
|
||||
// parseTimeParts takes a datePart string in the format of "YYYY-MM-DD" and
|
||||
// a timePart string in the format of "HH:MM:SS" and returns a time.Time value.
|
||||
func parseTimeParts(datePart, timePart string) (time.Time, error) {
|
||||
// Parse the date.
|
||||
dateParts := strings.Split(datePart, "-")
|
||||
if len(dateParts) != 3 {
|
||||
return time.Time{}, fmt.Errorf("invalid date format: %v",
|
||||
datePart)
|
||||
}
|
||||
|
||||
year, err := strconv.Atoi(dateParts[0])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
month, err := strconv.Atoi(dateParts[1])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
day, err := strconv.Atoi(dateParts[2])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
// Parse the time.
|
||||
timeParts := strings.Split(timePart, ":")
|
||||
if len(timeParts) != 3 {
|
||||
return time.Time{}, fmt.Errorf("invalid time format: %v",
|
||||
timePart)
|
||||
}
|
||||
|
||||
hour, err := strconv.Atoi(timeParts[0])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
minute, err := strconv.Atoi(timeParts[1])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
// Parse the seconds and ignore the fractional part.
|
||||
secondParts := strings.Split(timeParts[2], ".")
|
||||
|
||||
second, err := strconv.Atoi(secondParts[0])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
// Construct a time.Time value.
|
||||
return time.Date(
|
||||
year, time.Month(month), day, hour, minute, second, 0, time.UTC,
|
||||
), nil
|
||||
}
|
||||
|
||||
// isMilisecondsTime returns true if the unix timestamp is likely in
|
||||
// milliseconds.
|
||||
func isMilisecondsTime(unixTimestamp int64) bool {
|
||||
length := len(fmt.Sprintf("%d", unixTimestamp))
|
||||
if length >= 13 {
|
||||
// Likely a millisecond timestamp
|
||||
return true
|
||||
} else {
|
||||
// Likely a second timestamp
|
||||
return false
|
||||
}
|
||||
return year, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue