diff --git a/loopdb/loopout.go b/loopdb/loopout.go index 7dc173a..3df0654 100644 --- a/loopdb/loopout.go +++ b/loopdb/loopout.go @@ -45,6 +45,12 @@ type LoopOutContract struct { // MaxPrepayRoutingFee is the maximum off-chain fee in msat that may be // paid for the prepayment to the server. MaxPrepayRoutingFee btcutil.Amount + + // SwapPublicationDeadline is a timestamp that the server commits to + // have the on-chain swap published by. It is set by the client to + // allow the server to delay the publication in exchange for possibly + // lower fees. + SwapPublicationDeadline time.Time } // LoopOut is a combination of the contract and the updates. @@ -158,6 +164,13 @@ func deserializeLoopOutContract(value []byte, chainParams *chaincfg.Params) ( contract.UnchargeChannel = &unchargeChannel } + var deadlineNano int64 + err = binary.Read(r, byteOrder, &deadlineNano) + if err != nil { + return nil, err + } + contract.SwapPublicationDeadline = time.Unix(0, deadlineNano) + return &contract, nil } @@ -243,5 +256,10 @@ func serializeLoopOutContract(swap *LoopOutContract) ( return nil, err } + err = binary.Write(&b, byteOrder, swap.SwapPublicationDeadline.UnixNano()) + if err != nil { + return nil, err + } + return b.Bytes(), nil } diff --git a/loopdb/meta.go b/loopdb/meta.go index e1a3127..0d21f47 100644 --- a/loopdb/meta.go +++ b/loopdb/meta.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/btcsuite/btcd/chaincfg" "github.com/coreos/bbolt" ) @@ -24,14 +25,17 @@ var ( // migration is a function which takes a prior outdated version of the database // instances and mutates the key/bucket structure to arrive at a more // up-to-date version of the database. -type migration func(tx *bbolt.Tx) error +type migration func(tx *bbolt.Tx, chainParams *chaincfg.Params) error var ( // dbVersions is storing all versions of database. If current version // of database don't match with latest version this list will be used // for retrieving all migration function that are need to apply to the // current db. - migrations = []migration{migrateCosts} + migrations = []migration{ + migrateCosts, + migrateSwapPublicationDeadline, + } latestDBVersion = uint32(len(migrations)) ) @@ -76,7 +80,7 @@ func setDBVersion(tx *bbolt.Tx, version uint32) error { // syncVersions function is used for safe db version synchronization. It // applies migration functions to the current database and recovers the // previous state of db if at least one error/panic appeared during migration. -func syncVersions(db *bbolt.DB) error { +func syncVersions(db *bbolt.DB, chainParams *chaincfg.Params) error { currentVersion, err := getDBVersion(db) if err != nil { return err @@ -112,7 +116,7 @@ func syncVersions(db *bbolt.DB) error { log.Infof("Applying migration #%v", v+1) migration := migrations[v] - if err := migration(tx); err != nil { + if err := migration(tx, chainParams); err != nil { log.Infof("Unable to apply migration #%v", v+1) return err diff --git a/loopdb/migration_01_costs.go b/loopdb/migration_01_costs.go index 55d1df5..b56053e 100644 --- a/loopdb/migration_01_costs.go +++ b/loopdb/migration_01_costs.go @@ -4,12 +4,13 @@ import ( "errors" "fmt" + "github.com/btcsuite/btcd/chaincfg" "github.com/coreos/bbolt" ) // noMigrationAvailable is the fall back migration in case there is no migration // implemented. -func migrateCosts(tx *bbolt.Tx) error { +func migrateCosts(tx *bbolt.Tx, _ *chaincfg.Params) error { if err := migrateCostsForBucket(tx, loopInBucketKey); err != nil { return err } diff --git a/loopdb/migration_02_swap_publication_deadline.go b/loopdb/migration_02_swap_publication_deadline.go new file mode 100644 index 0000000..b6879cd --- /dev/null +++ b/loopdb/migration_02_swap_publication_deadline.go @@ -0,0 +1,58 @@ +package loopdb + +import ( + "bytes" + "errors" + "fmt" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/coreos/bbolt" +) + +// migrateSwapPublicationDeadline migrates the database to v02, by adding the +// SwapPublicationDeadline field to loop out contracts. +func migrateSwapPublicationDeadline(tx *bbolt.Tx, chainParams *chaincfg.Params) error { + rootBucket := tx.Bucket(loopOutBucketKey) + if rootBucket == nil { + return errors.New("bucket does not exist") + } + + return rootBucket.ForEach(func(swapHash, v []byte) error { + // Only go into things that we know are sub-bucket + // keys. + if v != nil { + return nil + } + + // From the root bucket, we'll grab the next swap + // bucket for this swap from its swaphash. + swapBucket := rootBucket.Bucket(swapHash) + if swapBucket == nil { + return fmt.Errorf("swap bucket %x not found", + swapHash) + } + + // With the main swap bucket obtained, we'll grab the + // raw swap contract bytes. + contractBytes := swapBucket.Get(contractKey) + if contractBytes == nil { + return errors.New("contract not found") + } + + // Write the current contract serialization into a buffer. + b := &bytes.Buffer{} + if _, err := b.Write(contractBytes); err != nil { + return err + } + + // We migrate to the new format by copying the first 8 bytes + // (the creation time) to the end (the swap deadline) + var swapDeadline [8]byte + copy(swapDeadline[:], contractBytes[:8]) + if _, err := b.Write(swapDeadline[:]); err != nil { + return err + } + + return swapBucket.Put(contractKey, b.Bytes()) + }) +} diff --git a/loopdb/store.go b/loopdb/store.go index 2a90ce8..e075495 100644 --- a/loopdb/store.go +++ b/loopdb/store.go @@ -135,7 +135,7 @@ func NewBoltSwapStore(dbPath string, chainParams *chaincfg.Params) ( // Finally, before we start, we'll sync the DB versions to pick up any // possible DB migrations. - err = syncVersions(bdb) + err = syncVersions(bdb, chainParams) if err != nil { return nil, err } diff --git a/loopdb/store_test.go b/loopdb/store_test.go index 64d9451..1d9ceaa 100644 --- a/loopdb/store_test.go +++ b/loopdb/store_test.go @@ -82,12 +82,13 @@ func TestLoopOutStore(t *testing.T) { // doesn't interfere with DeepEqual. InitiationTime: time.Unix(0, initiationTime.UnixNano()), }, - MaxPrepayRoutingFee: 40, - PrepayInvoice: "prepayinvoice", - DestAddr: destAddr, - SwapInvoice: "swapinvoice", - MaxSwapRoutingFee: 30, - SweepConfTarget: 2, + MaxPrepayRoutingFee: 40, + PrepayInvoice: "prepayinvoice", + DestAddr: destAddr, + SwapInvoice: "swapinvoice", + MaxSwapRoutingFee: 30, + SweepConfTarget: 2, + SwapPublicationDeadline: time.Unix(0, initiationTime.UnixNano()), } // checkSwap is a test helper function that'll assert the state of a