From 6efa62347b5ab5f6cf9e2120466922698f1a7c35 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 15 May 2019 14:01:27 +0200 Subject: [PATCH] multi: store swap cost in database --- cmd/loopd/view.go | 13 +++++- interface.go | 9 ----- loopdb/loop.go | 26 +++++++++++- loopdb/meta.go | 2 +- loopdb/migration_01_costs.go | 78 ++++++++++++++++++++++++++++++++++++ loopdb/swapstate.go | 18 +++++++++ loopin.go | 4 +- loopout.go | 8 +++- swap.go | 3 +- 9 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 loopdb/migration_01_costs.go diff --git a/cmd/loopd/view.go b/cmd/loopd/view.go index c2583b0..eefc47f 100644 --- a/cmd/loopd/view.go +++ b/cmd/loopd/view.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/swap" ) @@ -77,9 +78,19 @@ func viewOut(swapClient *loop.Client, chainParams *chaincfg.Params) error { s.Contract.AmountRequested, s.Contract.CltvExpiry, ) for i, e := range s.Events { - fmt.Printf(" Update %v, Time %v, State: %v\n", + fmt.Printf(" Update %v, Time %v, State: %v", i, e.Time, e.State, ) + if e.State.Type() != loopdb.StateTypePending { + fmt.Printf(", Cost: server=%v, onchain=%v, "+ + "offchain=%v", + e.Cost.Server, + e.Cost.Onchain, + e.Cost.Offchain, + ) + } + + fmt.Println() } fmt.Println() } diff --git a/interface.go b/interface.go index bee3179..04756f5 100644 --- a/interface.go +++ b/interface.go @@ -83,15 +83,6 @@ type Out struct { SwapInfoKit } -// SwapCost is a breakdown of the final swap costs. -type SwapCost struct { - // Swap is the amount paid to the server. - Server btcutil.Amount - - // Onchain is the amount paid to miners for the onchain tx. - Onchain btcutil.Amount -} - // LoopOutQuoteRequest specifies the swap parameters for which a quote is // requested. type LoopOutQuoteRequest struct { diff --git a/loopdb/loop.go b/loopdb/loop.go index df44ee7..870df9f 100644 --- a/loopdb/loop.go +++ b/loopdb/loop.go @@ -94,7 +94,19 @@ func serializeLoopEvent(time time.Time, state SwapStateData) ( return nil, err } - if err := binary.Write(&b, byteOrder, state); err != nil { + if err := binary.Write(&b, byteOrder, state.State); err != nil { + return nil, err + } + + if err := binary.Write(&b, byteOrder, state.Cost.Server); err != nil { + return nil, err + } + + if err := binary.Write(&b, byteOrder, state.Cost.Onchain); err != nil { + return nil, err + } + + if err := binary.Write(&b, byteOrder, state.Cost.Offchain); err != nil { return nil, err } @@ -118,5 +130,17 @@ func deserializeLoopEvent(value []byte) (*LoopEvent, error) { return nil, err } + if err := binary.Read(r, byteOrder, &update.Cost.Server); err != nil { + return nil, err + } + + if err := binary.Read(r, byteOrder, &update.Cost.Onchain); err != nil { + return nil, err + } + + if err := binary.Read(r, byteOrder, &update.Cost.Offchain); err != nil { + return nil, err + } + return update, nil } diff --git a/loopdb/meta.go b/loopdb/meta.go index 1a3c8c4..e1a3127 100644 --- a/loopdb/meta.go +++ b/loopdb/meta.go @@ -31,7 +31,7 @@ var ( // 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{} + migrations = []migration{migrateCosts} latestDBVersion = uint32(len(migrations)) ) diff --git a/loopdb/migration_01_costs.go b/loopdb/migration_01_costs.go new file mode 100644 index 0000000..55d1df5 --- /dev/null +++ b/loopdb/migration_01_costs.go @@ -0,0 +1,78 @@ +package loopdb + +import ( + "errors" + "fmt" + + "github.com/coreos/bbolt" +) + +// noMigrationAvailable is the fall back migration in case there is no migration +// implemented. +func migrateCosts(tx *bbolt.Tx) error { + if err := migrateCostsForBucket(tx, loopInBucketKey); err != nil { + return err + } + if err := migrateCostsForBucket(tx, loopOutBucketKey); err != nil { + return err + } + return nil +} + +func migrateCostsForBucket(tx *bbolt.Tx, bucketKey []byte) error { + // First, we'll grab our main loop in bucket key. + rootBucket := tx.Bucket(bucketKey) + if rootBucket == nil { + return errors.New("bucket does not exist") + } + + // We'll now traverse the root bucket for all active swaps. The + // primary key is the swap hash itself. + 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) + } + + // Get the updates bucket. + updatesBucket := swapBucket.Bucket(updatesBucketKey) + if updatesBucket == nil { + return errors.New("updates bucket not found") + } + + // Get list of all update ids. + var ids [][]byte + err := updatesBucket.ForEach(func(k, v []byte) error { + ids = append(ids, k) + return nil + }) + if err != nil { + return err + } + + // Append three zeroed cost factors to all updates. + var emptyCosts [3 * 8]byte + for _, id := range ids { + v := updatesBucket.Get(id) + if v == nil { + return errors.New("empty value") + } + v = append(v, emptyCosts[:]...) + err := updatesBucket.Put(id, v) + if err != nil { + return err + } + } + + return nil + }) +} diff --git a/loopdb/swapstate.go b/loopdb/swapstate.go index be3393d..eb10f3f 100644 --- a/loopdb/swapstate.go +++ b/loopdb/swapstate.go @@ -1,5 +1,7 @@ package loopdb +import "github.com/btcsuite/btcutil" + // SwapState indicates the current state of a swap. This enumeration is the // union of loop in and loop out states. A single type is used for both swap // types to be able to reduce code duplication that would otherwise be required. @@ -126,7 +128,23 @@ func (s SwapState) String() string { } } +// SwapCost is a breakdown of the final swap costs. +type SwapCost struct { + // Swap is the amount paid to the server. + Server btcutil.Amount + + // Onchain is the amount paid to miners for the onchain tx. + Onchain btcutil.Amount + + // Offchain is the amount paid in routing fees. + Offchain btcutil.Amount +} + // SwapStateData is all persistent data to describe the current swap state. type SwapStateData struct { + // SwapState is the state the swap is in. State SwapState + + // Cost are the accrued (final) costs so far. + Cost SwapCost } diff --git a/loopin.go b/loopin.go index f0a0376..cc9a9f9 100644 --- a/loopin.go +++ b/loopin.go @@ -261,10 +261,11 @@ func (s *loopInSwap) execute(mainCtx context.Context, } s.log.Infof("Loop in swap completed: %v "+ - "(final cost: server %v, onchain %v)", + "(final cost: server %v, onchain %v, offchain %v)", s.state, s.cost.Server, s.cost.Onchain, + s.cost.Offchain, ) return nil @@ -620,6 +621,7 @@ func (s *loopInSwap) persistState(ctx context.Context) error { s.hash, s.lastUpdateTime, loopdb.SwapStateData{ State: s.state, + Cost: s.cost, }, ) if err != nil { diff --git a/loopout.go b/loopout.go index ab9b9ee..f2129c8 100644 --- a/loopout.go +++ b/loopout.go @@ -224,6 +224,7 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error { continue } s.cost.Server += result.PaidAmt + s.cost.Offchain += result.PaidFee case result := <-s.prePaymentChan: s.prePaymentChan = nil @@ -235,6 +236,7 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error { continue } s.cost.Server += result.PaidAmt + s.cost.Offchain += result.PaidFee case <-globalCtx.Done(): return globalCtx.Err() @@ -243,10 +245,11 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error { // Mark swap completed in store. s.log.Infof("Swap completed: %v "+ - "(final cost: server %v, onchain %v)", + "(final cost: server %v, onchain %v, offchain %v)", s.state, s.cost.Server, s.cost.Onchain, + s.cost.Offchain, ) return s.persistState(globalCtx) @@ -346,6 +349,7 @@ func (s *loopOutSwap) persistState(ctx context.Context) error { s.hash, updateTime, loopdb.SwapStateData{ State: s.state, + Cost: s.cost, }, ) if err != nil { @@ -446,6 +450,7 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) ( return nil, nil } s.cost.Server += result.PaidAmt + s.cost.Offchain += result.PaidFee // If the prepay fails, abandon the swap. Because we // didn't reveal the preimage, the swap payment will be @@ -460,6 +465,7 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) ( return nil, nil } s.cost.Server += result.PaidAmt + s.cost.Offchain += result.PaidFee // Unexpected error on the confirm channel happened, // abandon the swap. diff --git a/swap.go b/swap.go index ad4b02c..95f6227 100644 --- a/swap.go +++ b/swap.go @@ -19,7 +19,7 @@ type swapKit struct { log *SwapLog lastUpdateTime time.Time - cost SwapCost + cost loopdb.SwapCost state loopdb.SwapState executeConfig swapConfig @@ -70,6 +70,7 @@ func (s *swapKit) sendUpdate(ctx context.Context) error { LastUpdate: s.lastUpdateTime, SwapStateData: loopdb.SwapStateData{ State: s.state, + Cost: s.cost, }, HtlcAddress: s.htlc.Address, }