|
package keymigrate
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/google/orderedcode"
|
|
"github.com/stretchr/testify/require"
|
|
dbm "github.com/tendermint/tm-db"
|
|
)
|
|
|
|
func makeKey(t *testing.T, elems ...interface{}) []byte {
|
|
t.Helper()
|
|
out, err := orderedcode.Append([]byte{}, elems...)
|
|
require.NoError(t, err)
|
|
return out
|
|
}
|
|
|
|
func getLegacyPrefixKeys(val int) map[string][]byte {
|
|
return map[string][]byte{
|
|
"Height": []byte(fmt.Sprintf("H:%d", val)),
|
|
"BlockPart": []byte(fmt.Sprintf("P:%d:%d", val, val)),
|
|
"BlockPartTwo": []byte(fmt.Sprintf("P:%d:%d", val+2, val+val)),
|
|
"BlockCommit": []byte(fmt.Sprintf("C:%d", val)),
|
|
"SeenCommit": []byte(fmt.Sprintf("SC:%d", val)),
|
|
"BlockHeight": []byte(fmt.Sprintf("BH:%d", val)),
|
|
"Validators": []byte(fmt.Sprintf("validatorsKey:%d", val)),
|
|
"ConsensusParams": []byte(fmt.Sprintf("consensusParamsKey:%d", val)),
|
|
"ABCIResponse": []byte(fmt.Sprintf("abciResponsesKey:%d", val)),
|
|
"State": []byte("stateKey"),
|
|
"CommittedEvidence": append([]byte{0x00}, []byte(fmt.Sprintf("%0.16X/%X", int64(val), []byte("committed")))...),
|
|
"PendingEvidence": append([]byte{0x01}, []byte(fmt.Sprintf("%0.16X/%X", int64(val), []byte("pending")))...),
|
|
"LightBLock": []byte(fmt.Sprintf("lb/foo/%020d", val)),
|
|
"Size": []byte("size"),
|
|
"UserKey0": []byte(fmt.Sprintf("foo/bar/%d/%d", val, val)),
|
|
"UserKey1": []byte(fmt.Sprintf("foo/bar/baz/%d/%d", val, val)),
|
|
"TxHeight": []byte(fmt.Sprintf("tx.height/%s/%d/%d", fmt.Sprint(val), val, val)),
|
|
"TxHash": append(
|
|
bytes.Repeat([]byte{fmt.Sprint(val)[0]}, 16),
|
|
bytes.Repeat([]byte{fmt.Sprint(val)[len([]byte(fmt.Sprint(val)))-1]}, 16)...,
|
|
),
|
|
}
|
|
}
|
|
|
|
func getNewPrefixKeys(t *testing.T, val int) map[string][]byte {
|
|
t.Helper()
|
|
return map[string][]byte{
|
|
"Height": makeKey(t, int64(0), int64(val)),
|
|
"BlockPart": makeKey(t, int64(1), int64(val), int64(val)),
|
|
"BlockPartTwo": makeKey(t, int64(1), int64(val+2), int64(val+val)),
|
|
"BlockCommit": makeKey(t, int64(2), int64(val)),
|
|
"SeenCommit": makeKey(t, int64(3), int64(val)),
|
|
"BlockHeight": makeKey(t, int64(4), int64(val)),
|
|
"Validators": makeKey(t, int64(5), int64(val)),
|
|
"ConsensusParams": makeKey(t, int64(6), int64(val)),
|
|
"ABCIResponse": makeKey(t, int64(7), int64(val)),
|
|
"State": makeKey(t, int64(8)),
|
|
"CommittedEvidence": makeKey(t, int64(9), int64(val)),
|
|
"PendingEvidence": makeKey(t, int64(10), int64(val)),
|
|
"LightBLock": makeKey(t, int64(11), int64(val)),
|
|
"Size": makeKey(t, int64(12)),
|
|
"UserKey0": makeKey(t, "foo", "bar", int64(val), int64(val)),
|
|
"UserKey1": makeKey(t, "foo", "bar/baz", int64(val), int64(val)),
|
|
"TxHeight": makeKey(t, "tx.height", fmt.Sprint(val), int64(val), int64(val+2), int64(val+val)),
|
|
"TxHash": makeKey(t, "tx.hash", string(bytes.Repeat([]byte{[]byte(fmt.Sprint(val))[0]}, 32))),
|
|
}
|
|
}
|
|
|
|
func getLegacyDatabase(t *testing.T) (int, dbm.DB) {
|
|
db := dbm.NewMemDB()
|
|
batch := db.NewBatch()
|
|
ct := 0
|
|
|
|
generated := []map[string][]byte{
|
|
getLegacyPrefixKeys(8),
|
|
getLegacyPrefixKeys(9001),
|
|
getLegacyPrefixKeys(math.MaxInt32 << 1),
|
|
getLegacyPrefixKeys(math.MaxInt64 - 8),
|
|
}
|
|
|
|
// populate database
|
|
for _, km := range generated {
|
|
for _, key := range km {
|
|
ct++
|
|
require.NoError(t, batch.Set(key, []byte(fmt.Sprintf(`{"value": %d}`, ct))))
|
|
}
|
|
}
|
|
require.NoError(t, batch.WriteSync())
|
|
require.NoError(t, batch.Close())
|
|
return ct - (2 * len(generated)) + 2, db
|
|
}
|
|
|
|
func TestMigration(t *testing.T) {
|
|
t.Run("Idempotency", func(t *testing.T) {
|
|
// we want to make sure that the key space for new and
|
|
// legacy keys are entirely non-overlapping.
|
|
|
|
legacyPrefixes := getLegacyPrefixKeys(42)
|
|
|
|
newPrefixes := getNewPrefixKeys(t, 42)
|
|
|
|
require.Equal(t, len(legacyPrefixes), len(newPrefixes))
|
|
|
|
t.Run("Legacy", func(t *testing.T) {
|
|
for kind, le := range legacyPrefixes {
|
|
require.True(t, keyIsLegacy(le), kind)
|
|
}
|
|
})
|
|
t.Run("New", func(t *testing.T) {
|
|
for kind, ne := range newPrefixes {
|
|
require.False(t, keyIsLegacy(ne), kind)
|
|
}
|
|
})
|
|
t.Run("Conversion", func(t *testing.T) {
|
|
for kind, le := range legacyPrefixes {
|
|
nk, err := migarateKey(le)
|
|
require.NoError(t, err, kind)
|
|
require.False(t, keyIsLegacy(nk), kind)
|
|
}
|
|
})
|
|
t.Run("Hashes", func(t *testing.T) {
|
|
t.Run("NewKeysAreNotHashes", func(t *testing.T) {
|
|
for _, key := range getNewPrefixKeys(t, 9001) {
|
|
require.True(t, len(key) != 32)
|
|
}
|
|
})
|
|
t.Run("ContrivedLegacyKeyDetection", func(t *testing.T) {
|
|
require.True(t, keyIsLegacy([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")))
|
|
require.False(t, keyIsLegacy([]byte("xxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx")))
|
|
})
|
|
})
|
|
})
|
|
t.Run("Migrations", func(t *testing.T) {
|
|
t.Run("Errors", func(t *testing.T) {
|
|
table := map[string][]byte{
|
|
"Height": []byte(fmt.Sprintf("H:%f", 4.22222)),
|
|
"BlockPart": []byte(fmt.Sprintf("P:%f", 4.22222)),
|
|
"BlockPartTwo": []byte(fmt.Sprintf("P:%d", 42)),
|
|
"BlockPartThree": []byte(fmt.Sprintf("P:%f:%f", 4.222, 8.444)),
|
|
"BlockPartFour": []byte(fmt.Sprintf("P:%d:%f", 4222, 8.444)),
|
|
"BlockCommit": []byte(fmt.Sprintf("C:%f", 4.22222)),
|
|
"SeenCommit": []byte(fmt.Sprintf("SC:%f", 4.22222)),
|
|
"BlockHeight": []byte(fmt.Sprintf("BH:%f", 4.22222)),
|
|
"Validators": []byte(fmt.Sprintf("validatorsKey:%f", 4.22222)),
|
|
"ConsensusParams": []byte(fmt.Sprintf("consensusParamsKey:%f", 4.22222)),
|
|
"ABCIResponse": []byte(fmt.Sprintf("abciResponsesKey:%f", 4.22222)),
|
|
"LightBlockShort": []byte(fmt.Sprintf("lb/foo/%010d", 42)),
|
|
"LightBlockLong": []byte("lb/foo/12345678910.1234567890"),
|
|
"Invalid": {0x03},
|
|
"BadTXHeight0": []byte(fmt.Sprintf("tx.height/%s/%f/%f", "boop", 4.4, 4.5)),
|
|
"BadTXHeight1": []byte(fmt.Sprintf("tx.height/%s/%f", "boop", 4.4)),
|
|
"UserKey0": []byte("foo/bar/1.3/3.4"),
|
|
"UserKey1": []byte("foo/bar/1/3.4"),
|
|
"UserKey2": []byte("foo/bar/baz/1/3.4"),
|
|
"UserKey3": []byte("foo/bar/baz/1.2/4"),
|
|
}
|
|
for kind, key := range table {
|
|
out, err := migarateKey(key)
|
|
require.Error(t, err, kind)
|
|
require.Nil(t, out, kind)
|
|
}
|
|
})
|
|
t.Run("Replacement", func(t *testing.T) {
|
|
t.Run("MissingKey", func(t *testing.T) {
|
|
db := dbm.NewMemDB()
|
|
require.NoError(t, replaceKey(db, keyID("hi"), nil))
|
|
})
|
|
t.Run("ReplacementFails", func(t *testing.T) {
|
|
db := dbm.NewMemDB()
|
|
key := keyID("hi")
|
|
require.NoError(t, db.Set(key, []byte("world")))
|
|
require.Error(t, replaceKey(db, key, func(k keyID) (keyID, error) {
|
|
return nil, errors.New("hi")
|
|
}))
|
|
})
|
|
t.Run("KeyDisapears", func(t *testing.T) {
|
|
db := dbm.NewMemDB()
|
|
key := keyID("hi")
|
|
require.NoError(t, db.Set(key, []byte("world")))
|
|
require.Error(t, replaceKey(db, key, func(k keyID) (keyID, error) {
|
|
require.NoError(t, db.Delete(key))
|
|
return keyID("wat"), nil
|
|
}))
|
|
|
|
exists, err := db.Has(key)
|
|
require.NoError(t, err)
|
|
require.False(t, exists)
|
|
|
|
exists, err = db.Has(keyID("wat"))
|
|
require.NoError(t, err)
|
|
require.False(t, exists)
|
|
})
|
|
})
|
|
})
|
|
t.Run("Integration", func(t *testing.T) {
|
|
t.Run("KeyDiscovery", func(t *testing.T) {
|
|
size, db := getLegacyDatabase(t)
|
|
keys, err := getAllLegacyKeys(db)
|
|
require.NoError(t, err)
|
|
require.Equal(t, size, len(keys))
|
|
legacyKeys := 0
|
|
for _, k := range keys {
|
|
if keyIsLegacy(k) {
|
|
legacyKeys++
|
|
}
|
|
}
|
|
require.Equal(t, size, legacyKeys)
|
|
})
|
|
t.Run("KeyIdempotency", func(t *testing.T) {
|
|
for _, key := range getNewPrefixKeys(t, 84) {
|
|
require.False(t, keyIsLegacy(key))
|
|
}
|
|
})
|
|
t.Run("ChannelConversion", func(t *testing.T) {
|
|
ch := makeKeyChan([]keyID{
|
|
makeKey(t, "abc", int64(2), int64(42)),
|
|
makeKey(t, int64(42)),
|
|
})
|
|
count := 0
|
|
for range ch {
|
|
count++
|
|
}
|
|
require.Equal(t, 2, count)
|
|
})
|
|
t.Run("Migrate", func(t *testing.T) {
|
|
_, db := getLegacyDatabase(t)
|
|
|
|
ctx := context.Background()
|
|
err := Migrate(ctx, db)
|
|
require.NoError(t, err)
|
|
keys, err := getAllLegacyKeys(db)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(keys))
|
|
|
|
})
|
|
})
|
|
}
|