You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
8.2 KiB

  1. package keymigrate
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "math"
  8. "testing"
  9. "github.com/google/orderedcode"
  10. "github.com/stretchr/testify/require"
  11. dbm "github.com/tendermint/tm-db"
  12. )
  13. func makeKey(t *testing.T, elems ...interface{}) []byte {
  14. t.Helper()
  15. out, err := orderedcode.Append([]byte{}, elems...)
  16. require.NoError(t, err)
  17. return out
  18. }
  19. func getLegacyPrefixKeys(val int) map[string][]byte {
  20. return map[string][]byte{
  21. "Height": []byte(fmt.Sprintf("H:%d", val)),
  22. "BlockPart": []byte(fmt.Sprintf("P:%d:%d", val, val)),
  23. "BlockPartTwo": []byte(fmt.Sprintf("P:%d:%d", val+2, val+val)),
  24. "BlockCommit": []byte(fmt.Sprintf("C:%d", val)),
  25. "SeenCommit": []byte(fmt.Sprintf("SC:%d", val)),
  26. "BlockHeight": []byte(fmt.Sprintf("BH:%d", val)),
  27. "Validators": []byte(fmt.Sprintf("validatorsKey:%d", val)),
  28. "ConsensusParams": []byte(fmt.Sprintf("consensusParamsKey:%d", val)),
  29. "ABCIResponse": []byte(fmt.Sprintf("abciResponsesKey:%d", val)),
  30. "State": []byte("stateKey"),
  31. "CommittedEvidence": append([]byte{0x00}, []byte(fmt.Sprintf("%0.16X/%X", int64(val), []byte("committed")))...),
  32. "PendingEvidence": append([]byte{0x01}, []byte(fmt.Sprintf("%0.16X/%X", int64(val), []byte("pending")))...),
  33. "LightBLock": []byte(fmt.Sprintf("lb/foo/%020d", val)),
  34. "Size": []byte("size"),
  35. "UserKey0": []byte(fmt.Sprintf("foo/bar/%d/%d", val, val)),
  36. "UserKey1": []byte(fmt.Sprintf("foo/bar/baz/%d/%d", val, val)),
  37. "TxHeight": []byte(fmt.Sprintf("tx.height/%s/%d/%d", fmt.Sprint(val), val, val)),
  38. "TxHash": append(
  39. bytes.Repeat([]byte{fmt.Sprint(val)[0]}, 16),
  40. bytes.Repeat([]byte{fmt.Sprint(val)[len([]byte(fmt.Sprint(val)))-1]}, 16)...,
  41. ),
  42. }
  43. }
  44. func getNewPrefixKeys(t *testing.T, val int) map[string][]byte {
  45. t.Helper()
  46. return map[string][]byte{
  47. "Height": makeKey(t, int64(0), int64(val)),
  48. "BlockPart": makeKey(t, int64(1), int64(val), int64(val)),
  49. "BlockPartTwo": makeKey(t, int64(1), int64(val+2), int64(val+val)),
  50. "BlockCommit": makeKey(t, int64(2), int64(val)),
  51. "SeenCommit": makeKey(t, int64(3), int64(val)),
  52. "BlockHeight": makeKey(t, int64(4), int64(val)),
  53. "Validators": makeKey(t, int64(5), int64(val)),
  54. "ConsensusParams": makeKey(t, int64(6), int64(val)),
  55. "ABCIResponse": makeKey(t, int64(7), int64(val)),
  56. "State": makeKey(t, int64(8)),
  57. "CommittedEvidence": makeKey(t, int64(9), int64(val)),
  58. "PendingEvidence": makeKey(t, int64(10), int64(val)),
  59. "LightBLock": makeKey(t, int64(11), int64(val)),
  60. "Size": makeKey(t, int64(12)),
  61. "UserKey0": makeKey(t, "foo", "bar", int64(val), int64(val)),
  62. "UserKey1": makeKey(t, "foo", "bar/baz", int64(val), int64(val)),
  63. "TxHeight": makeKey(t, "tx.height", fmt.Sprint(val), int64(val), int64(val+2), int64(val+val)),
  64. "TxHash": makeKey(t, "tx.hash", string(bytes.Repeat([]byte{[]byte(fmt.Sprint(val))[0]}, 32))),
  65. }
  66. }
  67. func getLegacyDatabase(t *testing.T) (int, dbm.DB) {
  68. db := dbm.NewMemDB()
  69. batch := db.NewBatch()
  70. ct := 0
  71. generated := []map[string][]byte{
  72. getLegacyPrefixKeys(8),
  73. getLegacyPrefixKeys(9001),
  74. getLegacyPrefixKeys(math.MaxInt32 << 1),
  75. getLegacyPrefixKeys(math.MaxInt64 - 8),
  76. }
  77. // populate database
  78. for _, km := range generated {
  79. for _, key := range km {
  80. ct++
  81. require.NoError(t, batch.Set(key, []byte(fmt.Sprintf(`{"value": %d}`, ct))))
  82. }
  83. }
  84. require.NoError(t, batch.WriteSync())
  85. require.NoError(t, batch.Close())
  86. return ct - (2 * len(generated)) + 2, db
  87. }
  88. func TestMigration(t *testing.T) {
  89. t.Run("Idempotency", func(t *testing.T) {
  90. // we want to make sure that the key space for new and
  91. // legacy keys are entirely non-overlapping.
  92. legacyPrefixes := getLegacyPrefixKeys(42)
  93. newPrefixes := getNewPrefixKeys(t, 42)
  94. require.Equal(t, len(legacyPrefixes), len(newPrefixes))
  95. t.Run("Legacy", func(t *testing.T) {
  96. for kind, le := range legacyPrefixes {
  97. require.True(t, keyIsLegacy(le), kind)
  98. }
  99. })
  100. t.Run("New", func(t *testing.T) {
  101. for kind, ne := range newPrefixes {
  102. require.False(t, keyIsLegacy(ne), kind)
  103. }
  104. })
  105. t.Run("Conversion", func(t *testing.T) {
  106. for kind, le := range legacyPrefixes {
  107. nk, err := migarateKey(le)
  108. require.NoError(t, err, kind)
  109. require.False(t, keyIsLegacy(nk), kind)
  110. }
  111. })
  112. t.Run("Hashes", func(t *testing.T) {
  113. t.Run("NewKeysAreNotHashes", func(t *testing.T) {
  114. for _, key := range getNewPrefixKeys(t, 9001) {
  115. require.True(t, len(key) != 32)
  116. }
  117. })
  118. t.Run("ContrivedLegacyKeyDetection", func(t *testing.T) {
  119. require.True(t, keyIsLegacy([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")))
  120. require.False(t, keyIsLegacy([]byte("xxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx")))
  121. })
  122. })
  123. })
  124. t.Run("Migrations", func(t *testing.T) {
  125. t.Run("Errors", func(t *testing.T) {
  126. table := map[string][]byte{
  127. "Height": []byte(fmt.Sprintf("H:%f", 4.22222)),
  128. "BlockPart": []byte(fmt.Sprintf("P:%f", 4.22222)),
  129. "BlockPartTwo": []byte(fmt.Sprintf("P:%d", 42)),
  130. "BlockPartThree": []byte(fmt.Sprintf("P:%f:%f", 4.222, 8.444)),
  131. "BlockPartFour": []byte(fmt.Sprintf("P:%d:%f", 4222, 8.444)),
  132. "BlockCommit": []byte(fmt.Sprintf("C:%f", 4.22222)),
  133. "SeenCommit": []byte(fmt.Sprintf("SC:%f", 4.22222)),
  134. "BlockHeight": []byte(fmt.Sprintf("BH:%f", 4.22222)),
  135. "Validators": []byte(fmt.Sprintf("validatorsKey:%f", 4.22222)),
  136. "ConsensusParams": []byte(fmt.Sprintf("consensusParamsKey:%f", 4.22222)),
  137. "ABCIResponse": []byte(fmt.Sprintf("abciResponsesKey:%f", 4.22222)),
  138. "LightBlockShort": []byte(fmt.Sprintf("lb/foo/%010d", 42)),
  139. "LightBlockLong": []byte("lb/foo/12345678910.1234567890"),
  140. "Invalid": {0x03},
  141. "BadTXHeight0": []byte(fmt.Sprintf("tx.height/%s/%f/%f", "boop", 4.4, 4.5)),
  142. "BadTXHeight1": []byte(fmt.Sprintf("tx.height/%s/%f", "boop", 4.4)),
  143. "UserKey0": []byte("foo/bar/1.3/3.4"),
  144. "UserKey1": []byte("foo/bar/1/3.4"),
  145. "UserKey2": []byte("foo/bar/baz/1/3.4"),
  146. "UserKey3": []byte("foo/bar/baz/1.2/4"),
  147. }
  148. for kind, key := range table {
  149. out, err := migarateKey(key)
  150. require.Error(t, err, kind)
  151. require.Nil(t, out, kind)
  152. }
  153. })
  154. t.Run("Replacement", func(t *testing.T) {
  155. t.Run("MissingKey", func(t *testing.T) {
  156. db := dbm.NewMemDB()
  157. require.NoError(t, replaceKey(db, keyID("hi"), nil))
  158. })
  159. t.Run("ReplacementFails", func(t *testing.T) {
  160. db := dbm.NewMemDB()
  161. key := keyID("hi")
  162. require.NoError(t, db.Set(key, []byte("world")))
  163. require.Error(t, replaceKey(db, key, func(k keyID) (keyID, error) {
  164. return nil, errors.New("hi")
  165. }))
  166. })
  167. t.Run("KeyDisapears", func(t *testing.T) {
  168. db := dbm.NewMemDB()
  169. key := keyID("hi")
  170. require.NoError(t, db.Set(key, []byte("world")))
  171. require.Error(t, replaceKey(db, key, func(k keyID) (keyID, error) {
  172. require.NoError(t, db.Delete(key))
  173. return keyID("wat"), nil
  174. }))
  175. exists, err := db.Has(key)
  176. require.NoError(t, err)
  177. require.False(t, exists)
  178. exists, err = db.Has(keyID("wat"))
  179. require.NoError(t, err)
  180. require.False(t, exists)
  181. })
  182. })
  183. })
  184. t.Run("Integration", func(t *testing.T) {
  185. t.Run("KeyDiscovery", func(t *testing.T) {
  186. size, db := getLegacyDatabase(t)
  187. keys, err := getAllLegacyKeys(db)
  188. require.NoError(t, err)
  189. require.Equal(t, size, len(keys))
  190. legacyKeys := 0
  191. for _, k := range keys {
  192. if keyIsLegacy(k) {
  193. legacyKeys++
  194. }
  195. }
  196. require.Equal(t, size, legacyKeys)
  197. })
  198. t.Run("KeyIdempotency", func(t *testing.T) {
  199. for _, key := range getNewPrefixKeys(t, 84) {
  200. require.False(t, keyIsLegacy(key))
  201. }
  202. })
  203. t.Run("ChannelConversion", func(t *testing.T) {
  204. ch := makeKeyChan([]keyID{
  205. makeKey(t, "abc", int64(2), int64(42)),
  206. makeKey(t, int64(42)),
  207. })
  208. count := 0
  209. for range ch {
  210. count++
  211. }
  212. require.Equal(t, 2, count)
  213. })
  214. t.Run("Migrate", func(t *testing.T) {
  215. _, db := getLegacyDatabase(t)
  216. ctx := context.Background()
  217. err := Migrate(ctx, db)
  218. require.NoError(t, err)
  219. keys, err := getAllLegacyKeys(db)
  220. require.NoError(t, err)
  221. require.Equal(t, 0, len(keys))
  222. })
  223. })
  224. }