2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-09 19:10:47 +00:00
loop/loopdb/store_mock.go
Andras Banki-Horvath f40ef193e9
loop: add migration for loopout swaps to fix negative stored costs
Previously we may have stored negative costs for some loop out swaps
which this commit attempts to correct by fetching all completed swap,
calculating the correct costs and overriding them in the database.
2024-06-03 14:54:18 +02:00

387 lines
9.1 KiB
Go

package loopdb
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
// StoreMock implements a mock client swap store.
type StoreMock struct {
LoopOutSwaps map[lntypes.Hash]*LoopOutContract
LoopOutUpdates map[lntypes.Hash][]SwapStateData
loopOutStoreChan chan LoopOutContract
loopOutUpdateChan chan SwapStateData
LoopInSwaps map[lntypes.Hash]*LoopInContract
LoopInUpdates map[lntypes.Hash][]SwapStateData
loopInStoreChan chan LoopInContract
loopInUpdateChan chan SwapStateData
migrations map[string]struct{}
t *testing.T
}
// NewStoreMock instantiates a new mock store.
func NewStoreMock(t *testing.T) *StoreMock {
return &StoreMock{
loopOutStoreChan: make(chan LoopOutContract, 1),
loopOutUpdateChan: make(chan SwapStateData, 1),
LoopOutSwaps: make(map[lntypes.Hash]*LoopOutContract),
LoopOutUpdates: make(map[lntypes.Hash][]SwapStateData),
loopInStoreChan: make(chan LoopInContract, 1),
loopInUpdateChan: make(chan SwapStateData, 1),
LoopInSwaps: make(map[lntypes.Hash]*LoopInContract),
LoopInUpdates: make(map[lntypes.Hash][]SwapStateData),
migrations: make(map[string]struct{}),
t: t,
}
}
// FetchLoopOutSwaps returns all swaps currently in the store.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut, error) {
result := []*LoopOut{}
for hash, contract := range s.LoopOutSwaps {
updates := s.LoopOutUpdates[hash]
events := make([]*LoopEvent, len(updates))
for i, u := range updates {
events[i] = &LoopEvent{
SwapStateData: u,
}
}
swap := &LoopOut{
Loop: Loop{
Hash: hash,
Events: events,
},
Contract: contract,
}
result = append(result, swap)
}
return result, nil
}
// FetchLoopOutSwaps returns all swaps currently in the store.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) FetchLoopOutSwap(ctx context.Context,
hash lntypes.Hash) (*LoopOut, error) {
contract, ok := s.LoopOutSwaps[hash]
if !ok {
return nil, errors.New("swap not found")
}
updates := s.LoopOutUpdates[hash]
events := make([]*LoopEvent, len(updates))
for i, u := range updates {
events[i] = &LoopEvent{
SwapStateData: u,
}
}
swap := &LoopOut{
Loop: Loop{
Hash: hash,
Events: events,
},
Contract: contract,
}
return swap, nil
}
// CreateLoopOut adds an initiated swap to the store.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) CreateLoopOut(ctx context.Context, hash lntypes.Hash,
swap *LoopOutContract) error {
_, ok := s.LoopOutSwaps[hash]
if ok {
return errors.New("swap already exists")
}
s.LoopOutSwaps[hash] = swap
s.LoopOutUpdates[hash] = []SwapStateData{}
s.loopOutStoreChan <- *swap
return nil
}
// FetchLoopInSwaps returns all in swaps currently in the store.
func (s *StoreMock) FetchLoopInSwaps(ctx context.Context) ([]*LoopIn,
error) {
result := []*LoopIn{}
for hash, contract := range s.LoopInSwaps {
updates := s.LoopInUpdates[hash]
events := make([]*LoopEvent, len(updates))
for i, u := range updates {
events[i] = &LoopEvent{
SwapStateData: u,
}
}
swap := &LoopIn{
Loop: Loop{
Hash: hash,
Events: events,
},
Contract: contract,
}
result = append(result, swap)
}
return result, nil
}
// CreateLoopIn adds an initiated loop in swap to the store.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) CreateLoopIn(ctx context.Context, hash lntypes.Hash,
swap *LoopInContract) error {
_, ok := s.LoopInSwaps[hash]
if ok {
return errors.New("swap already exists")
}
s.LoopInSwaps[hash] = swap
s.LoopInUpdates[hash] = []SwapStateData{}
s.loopInStoreChan <- *swap
return nil
}
// UpdateLoopOut stores a new event for a target loop out swap. This appends to
// the event log for a particular swap as it goes through the various stages in
// its lifetime.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) UpdateLoopOut(ctx context.Context, hash lntypes.Hash,
time time.Time, state SwapStateData) error {
updates, ok := s.LoopOutUpdates[hash]
if !ok {
return errors.New("swap does not exists")
}
updates = append(updates, state)
s.LoopOutUpdates[hash] = updates
s.loopOutUpdateChan <- state
return nil
}
// UpdateLoopIn stores a new event for a target loop in swap. This appends to
// the event log for a particular swap as it goes through the various stages in
// its lifetime.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) UpdateLoopIn(ctx context.Context, hash lntypes.Hash,
time time.Time, state SwapStateData) error {
updates, ok := s.LoopInUpdates[hash]
if !ok {
return errors.New("swap does not exists")
}
updates = append(updates, state)
s.LoopInUpdates[hash] = updates
s.loopInUpdateChan <- state
return nil
}
// PutLiquidityParams writes the serialized `manager.Parameters` bytes into the
// bucket.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) PutLiquidityParams(ctx context.Context,
params []byte) error {
return nil
}
// FetchLiquidityParams reads the serialized `manager.Parameters` bytes from
// the bucket.
//
// NOTE: Part of the SwapStore interface.
func (s *StoreMock) FetchLiquidityParams(ctx context.Context) ([]byte, error) {
return nil, nil
}
// Close closes the store.
func (s *StoreMock) Close() error {
return nil
}
// isDone asserts that the store mock has no pending operations.
func (s *StoreMock) IsDone() error {
select {
case <-s.loopOutStoreChan:
return errors.New("storeChan not empty")
default:
}
select {
case <-s.loopOutUpdateChan:
return errors.New("updateChan not empty")
default:
}
return nil
}
// AssertLoopOutStored asserts that a swap is stored.
func (s *StoreMock) AssertLoopOutStored() {
s.t.Helper()
select {
case <-s.loopOutStoreChan:
case <-time.After(test.Timeout):
s.t.Fatalf("expected swap to be stored")
}
}
// AssertLoopOutState asserts that a specified state transition is persisted to
// disk.
func (s *StoreMock) AssertLoopOutState(expectedState SwapState) {
s.t.Helper()
select {
case state := <-s.loopOutUpdateChan:
require.Equal(s.t, expectedState, state.State)
case <-time.After(test.Timeout):
s.t.Fatalf("expected swap state to be stored")
}
}
// AssertLoopInStored asserts that a loop-in swap is stored.
func (s *StoreMock) AssertLoopInStored() {
s.t.Helper()
select {
case <-s.loopInStoreChan:
case <-time.After(test.Timeout):
s.t.Fatalf("expected swap to be stored")
}
}
// assertLoopInState asserts that a specified state transition is persisted to
// disk.
func (s *StoreMock) AssertLoopInState(
expectedState SwapState) SwapStateData {
s.t.Helper()
state := <-s.loopInUpdateChan
require.Equal(s.t, expectedState, state.State)
return state
}
// AssertStorePreimageReveal asserts that a swap is marked as preimage revealed.
func (s *StoreMock) AssertStorePreimageReveal() {
s.t.Helper()
select {
case state := <-s.loopOutUpdateChan:
require.Equal(s.t, StatePreimageRevealed, state.State)
case <-time.After(test.Timeout):
s.t.Fatalf("expected swap to be marked as preimage revealed")
}
}
// AssertStoreFinished asserts that a swap is marked as finished.
func (s *StoreMock) AssertStoreFinished(expectedResult SwapState) {
s.t.Helper()
select {
case state := <-s.loopOutUpdateChan:
require.Equal(s.t, expectedResult, state.State)
case <-time.After(test.Timeout):
s.t.Fatalf("expected swap to be finished")
}
}
// BatchCreateLoopOut creates many loop out swaps in a batch.
func (b *StoreMock) BatchCreateLoopOut(ctx context.Context,
swaps map[lntypes.Hash]*LoopOutContract) error {
return errors.New("not implemented")
}
// BatchCreateLoopIn creates many loop in swaps in a batch.
func (b *StoreMock) BatchCreateLoopIn(ctx context.Context,
swaps map[lntypes.Hash]*LoopInContract) error {
return errors.New("not implemented")
}
// BatchInsertUpdate inserts many updates for a swap in a batch.
func (b *StoreMock) BatchInsertUpdate(ctx context.Context,
updateData map[lntypes.Hash][]BatchInsertUpdateData) error {
return errors.New("not implemented")
}
// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of loop out
// swaps.
func (s *StoreMock) BatchUpdateLoopOutSwapCosts(ctx context.Context,
costs map[lntypes.Hash]SwapCost) error {
for hash, cost := range costs {
if _, ok := s.LoopOutUpdates[hash]; !ok {
return fmt.Errorf("swap has no updates: %v", hash)
}
updates, ok := s.LoopOutUpdates[hash]
if !ok {
return fmt.Errorf("swap has no updates: %v", hash)
}
updates[len(updates)-1].Cost = cost
}
return nil
}
// HasMigration returns true if the migration with the given ID has been done.
func (s *StoreMock) HasMigration(ctx context.Context, migrationID string) (
bool, error) {
_, ok := s.migrations[migrationID]
return ok, nil
}
// SetMigration marks the migration with the given ID as done.
func (s *StoreMock) SetMigration(ctx context.Context,
migrationID string) error {
if _, ok := s.migrations[migrationID]; ok {
return errors.New("migration already done")
}
s.migrations[migrationID] = struct{}{}
return nil
}