Browse Source

loopdb: migrate to nested updates structure

pull/238/head
Joost Jager 9 months ago
parent
commit
12a7b34d88
No known key found for this signature in database GPG Key ID: A61B9D4C393C59C7
5 changed files with 226 additions and 6 deletions
  1. +1
    -0
      go.sum
  2. +1
    -0
      loopdb/meta.go
  3. +112
    -0
      loopdb/migration_04_updates.go
  4. +87
    -0
      loopdb/migration_04_updates_test.go
  5. +25
    -6
      loopdb/store.go

+ 1
- 0
go.sum View File

@ -237,6 +237,7 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

+ 1
- 0
loopdb/meta.go View File

@ -36,6 +36,7 @@ var (
migrateCosts,
migrateSwapPublicationDeadline,
migrateLastHop,
migrateUpdates,
}
latestDBVersion = uint32(len(migrations))

+ 112
- 0
loopdb/migration_04_updates.go View File

@ -0,0 +1,112 @@
package loopdb
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/coreos/bbolt"
)
// migrateUpdates migrates the swap updates to add an additional level of
// nesting, allowing for optional keys to be added.
func migrateUpdates(tx *bbolt.Tx, chainParams *chaincfg.Params) error {
for _, key := range [][]byte{loopInBucketKey, loopOutBucketKey} {
rootBucket := tx.Bucket(key)
if rootBucket == nil {
return fmt.Errorf("bucket %v does not exist", key)
}
err := migrateSwapTypeUpdates(rootBucket)
if err != nil {
return err
}
}
return nil
}
// migrateSwapTypeUpdates migrates updates for swaps in the specified bucket.
func migrateSwapTypeUpdates(rootBucket *bbolt.Bucket) error {
var swaps [][]byte
// Do not modify inside the for each.
err := rootBucket.ForEach(func(swapHash, v []byte) error {
// Only go into things that we know are sub-bucket
// keys.
if rootBucket.Bucket(swapHash) != nil {
swaps = append(swaps, swapHash)
}
return nil
})
if err != nil {
return err
}
// With the swaps listed, migrate them one by one.
for _, swapHash := range swaps {
swapBucket := rootBucket.Bucket(swapHash)
if swapBucket == nil {
return fmt.Errorf("swap bucket %x not found",
swapHash)
}
err := migrateSwapUpdates(swapBucket)
if err != nil {
return err
}
}
return nil
}
// migrateSwapUpdates migrates updates for the swap stored in the specified
// bucket.
func migrateSwapUpdates(swapBucket *bbolt.Bucket) error {
// With the main swap bucket obtained, we'll grab the
// raw swap contract bytes.
updatesBucket := swapBucket.Bucket(updatesBucketKey)
if updatesBucket == nil {
return errors.New("updates bucket not found")
}
type state struct {
id, state []byte
}
var existingStates []state
// Do not modify inside the for each.
err := updatesBucket.ForEach(func(k, v []byte) error {
existingStates = append(existingStates, state{id: k, state: v})
return nil
})
if err != nil {
return err
}
for _, existingState := range existingStates {
// Delete the existing state key.
err := updatesBucket.Delete(existingState.id)
if err != nil {
return err
}
// Re-create as a bucket.
updateBucket, err := updatesBucket.CreateBucket(
existingState.id,
)
if err != nil {
return err
}
// Write back the basic state as a sub-key.
err = updateBucket.Put(basicStateKey, existingState.state)
if err != nil {
return err
}
}
return nil
}

+ 87
- 0
loopdb/migration_04_updates_test.go View File

@ -0,0 +1,87 @@
package loopdb
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/coreos/bbolt"
"github.com/stretchr/testify/require"
)
// TestMigrationUpdates asserts that the swap updates migration is carried out
// correctly.
func TestMigrationUpdates(t *testing.T) {
var (
legacyDbVersion = Hex("00000003")
)
legacyDb := map[string]interface{}{
"metadata": map[string]interface{}{
"dbp": legacyDbVersion,
},
"loop-in": map[string]interface{}{
Hex("acae09fec9020b7996042613eede68a9eaf29eb28c21ea9943b19e344365a4bb"): map[string]interface{}{
"contract": Hex("161b25277262bdb5c7c2827b975b2cbc7eb13e222b30cf88ea6daef4bcf22bdac4116c23071472cb000000000000ea6003f2f513a8fd7958b6a229dfb8835f6ab2c9c63cc3e138784d3e8c0e0ebbdd4e61033f26c40666977ed497eea4694d6dd3f07dbcf037089234ff665cd0a07fea329400007b8a00000000000059a600000000000009ca000077a20000000600000000000000000000000000000000000000000000000000000000000000000000"),
"updates": map[string]interface{}{
Hex("0000000000000001"): Hex("161b252772cb524508000000000000000000000000000000000000000000000000"),
Hex("0000000000000002"): Hex("161b252837115e9b09ffffffffffff1f6a00000000000000000000000000000000"),
Hex("0000000000000003"): Hex("161b252ab670360d0200000000000009ca00000000000000000000000000000000"),
},
},
},
"uncharge-swaps": map[string]interface{}{
Hex("c3b3d7a145dbd2bab5aa1f505305f31ee432fe23b0801f065fac453dd9b1f923"): map[string]interface{}{
"contract": Hex("161b2526643767387ca76e58c964a8f2b6c0a13392b2dea93bde260226a263fb836954054ed1756b000000000000c350fd11016c6e6263727431333337306e3170303072343775707035366c7671663836753565766135647868686c706c78303733756a70676e3979767977376130766a37746d307678793276683576716471327770657832757270307963717a7279787139377a76757173703570373232733970686a6e6e6e706c3778716e796a78353373706863346c396735306b396e347836703761793577707539306b6673397179397173717a353766676a7a67676838343439377375716b383436787a3333336a713036736c6b38637a323872657466363672796b7876396a746e6a3072683979666a6170777065617265713071396679797a666664676d6874687973617370757565746e6b72306b32376370326173366a750269d66fd2cea620dc06f1f7de7838f0c8b145b82c7033080c398862f3421a23230382cb637badbb07f9926a06ecd88b6150513ea0060dc8d6dc1c1fb623926b0a0f000077d400000000000b458c00000000000005f10000000000000024000077a22c6263727431713271756332666777737971376463617a73666e3332636a7874667671647671366a6c70706574fd0f016c6e626372743530313834306e317030307234377570703563776561306732396d30667434646432726167397870306e726d6a72396c33726b7a717037706a6c34337a6e6d6b64336c79337364713877646d6b7a757163717a7279787139377a767571737035616478717538766168643730743776747165777578366d6d64337977636639767835736476717567753833327230676e373466733971793971737168746773636638386e377664767136716e71307a657775366d7471616e326c7a306e7534737a72376c6b36646d343673336c78726572656e333972616b7a6c777378346c613538733966773630356d6767766b766879716e743339713976737367777879367571707236713273780000000600000000000003f20000000000000000161b25262710ce00"),
"outgoing-chan-set": nil,
"updates": map[string]interface{}{
Hex("0000000000000001"): Hex("161b252a770e649b01000000000000053900000000000000000000000000000001"),
Hex("0000000000000002"): Hex("161b252ab671bdd90200000000000005f10000000000001a9c0000000000000003"),
},
},
},
}
// Restore a legacy database.
tempDirName, err := ioutil.TempDir("", "clientstore")
require.NoError(t, err)
defer os.RemoveAll(tempDirName)
tempPath := filepath.Join(tempDirName, dbFileName)
db, err := bbolt.Open(tempPath, 0600, nil)
require.NoError(t, err)
err = db.Update(func(tx *bbolt.Tx) error {
return RestoreDB(tx, legacyDb)
})
// Close database regardless of update result.
db.Close()
// Assert update was successful.
require.NoError(t, err)
// Open db and migrate to the latest version.
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
require.NoError(t, err)
// Fetch the legacy loop out swap and assert that the updates are still
// there.
outSwaps, err := store.FetchLoopOutSwaps()
require.NoError(t, err)
outSwap := outSwaps[0]
require.Len(t, outSwap.Events, 2)
require.Equal(t, StateSuccess, outSwap.Events[1].State)
// Fetch the legacy loop in swap and assert that the updates are still
// there.
inSwaps, err := store.FetchLoopInSwaps()
require.NoError(t, err)
inSwap := inSwaps[0]
require.Len(t, inSwap.Events, 3)
require.Equal(t, StateSuccess, outSwap.Events[1].State)
}

+ 25
- 6
loopdb/store.go View File

@ -45,6 +45,9 @@ var (
// maps: updateNumber -> time || state
updatesBucketKey = []byte("updates")
// basicStateKey contains the serialized basic swap state.
basicStateKey = []byte{0}
// contractKey is the key that stores the serialized swap contract. It
// is nested within the sub-bucket for each active swap.
//
@ -265,8 +268,18 @@ func deserializeUpdates(swapBucket *bbolt.Bucket) ([]*LoopEvent, error) {
// Deserialize and collect each swap update into our slice of swap
// events.
var updates []*LoopEvent
err := stateBucket.ForEach(func(_, v []byte) error {
event, err := deserializeLoopEvent(v)
err := stateBucket.ForEach(func(k, v []byte) error {
updateBucket := stateBucket.Bucket(k)
if updateBucket == nil {
return fmt.Errorf("expected state sub-bucket for %x", k)
}
basicState := updateBucket.Get(basicStateKey)
if basicState == nil {
return errors.New("no basic state for update")
}
event, err := deserializeLoopEvent(basicState)
if err != nil {
return err
}
@ -482,24 +495,30 @@ func (s *boltSwapStore) updateLoop(bucketKey []byte, hash lntypes.Hash,
if swapBucket == nil {
return errors.New("swap not found")
}
updateBucket := swapBucket.Bucket(updatesBucketKey)
if updateBucket == nil {
updatesBucket := swapBucket.Bucket(updatesBucketKey)
if updatesBucket == nil {
return errors.New("udpate bucket not found")
}
// Each update for this swap will get a new monotonically
// increasing ID number that we'll obtain now.
id, err := updateBucket.NextSequence()
id, err := updatesBucket.NextSequence()
if err != nil {
return err
}
nextUpdateBucket, err := updatesBucket.CreateBucket(itob(id))
if err != nil {
return fmt.Errorf("cannot create update bucket")
}
// With the ID obtained, we'll write out this new update value.
updateValue, err := serializeLoopEvent(time, state)
if err != nil {
return err
}
return updateBucket.Put(itob(id), updateValue)
return nextUpdateBucket.Put(basicStateKey, updateValue)
})
}

Loading…
Cancel
Save