github.com/prysmaticlabs/prysm@v1.4.4/validator/db/kv/migration_optimal_attester_protection_test.go (about) 1 package kv 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 8 types "github.com/prysmaticlabs/eth2-types" 9 "github.com/prysmaticlabs/prysm/shared/bytesutil" 10 "github.com/prysmaticlabs/prysm/shared/testutil/require" 11 bolt "go.etcd.io/bbolt" 12 ) 13 14 func Test_migrateOptimalAttesterProtectionUp(t *testing.T) { 15 tests := []struct { 16 name string 17 setup func(t *testing.T, validatorDB *Store) 18 eval func(t *testing.T, validatorDB *Store) 19 }{ 20 { 21 name: "only runs once", 22 setup: func(t *testing.T, validatorDB *Store) { 23 err := validatorDB.update(func(tx *bolt.Tx) error { 24 return tx.Bucket(migrationsBucket).Put(migrationOptimalAttesterProtectionKey, migrationCompleted) 25 }) 26 require.NoError(t, err) 27 }, 28 eval: func(t *testing.T, validatorDB *Store) { 29 err := validatorDB.view(func(tx *bolt.Tx) error { 30 data := tx.Bucket(migrationsBucket).Get(migrationOptimalAttesterProtectionKey) 31 require.DeepEqual(t, data, migrationCompleted) 32 return nil 33 }) 34 require.NoError(t, err) 35 }, 36 }, 37 { 38 name: "populates optimized schema buckets", 39 setup: func(t *testing.T, validatorDB *Store) { 40 ctx := context.Background() 41 pubKey := [48]byte{1} 42 history := newDeprecatedAttestingHistory(0) 43 // Attest all epochs from genesis to 50. 44 numEpochs := types.Epoch(50) 45 for i := types.Epoch(1); i <= numEpochs; i++ { 46 var sr [32]byte 47 copy(sr[:], fmt.Sprintf("%d", i)) 48 newHist, err := history.setTargetData(ctx, i, &deprecatedHistoryData{ 49 Source: i - 1, 50 SigningRoot: sr[:], 51 }) 52 require.NoError(t, err) 53 history = newHist 54 } 55 newHist, err := history.setLatestEpochWritten(ctx, numEpochs) 56 require.NoError(t, err) 57 58 err = validatorDB.update(func(tx *bolt.Tx) error { 59 bucket := tx.Bucket(deprecatedAttestationHistoryBucket) 60 return bucket.Put(pubKey[:], newHist) 61 }) 62 require.NoError(t, err) 63 }, 64 eval: func(t *testing.T, validatorDB *Store) { 65 // Verify we indeed have the data for all epochs 66 // since genesis to epoch 50 under the new schema. 67 err := validatorDB.view(func(tx *bolt.Tx) error { 68 pubKey := [48]byte{1} 69 bucket := tx.Bucket(pubKeysBucket) 70 pkBucket := bucket.Bucket(pubKey[:]) 71 signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket) 72 sourceEpochsBucket := pkBucket.Bucket(attestationSourceEpochsBucket) 73 numEpochs := uint64(50) 74 75 // Verify we have signing roots for target epochs 1 to 50 correctly. 76 for targetEpoch := uint64(1); targetEpoch <= numEpochs; targetEpoch++ { 77 var sr [32]byte 78 copy(sr[:], fmt.Sprintf("%d", targetEpoch)) 79 targetEpochBytes := bytesutil.Uint64ToBytesBigEndian(targetEpoch) 80 migratedSigningRoot := signingRootsBucket.Get(targetEpochBytes) 81 require.DeepEqual(t, sr[:], migratedSigningRoot) 82 } 83 84 // Verify we have (source epoch, target epoch) pairs for epochs 0 to 50 correctly. 85 for sourceEpoch := uint64(0); sourceEpoch < numEpochs; sourceEpoch++ { 86 sourceEpochBytes := bytesutil.Uint64ToBytesBigEndian(sourceEpoch) 87 targetEpochBytes := sourceEpochsBucket.Get(sourceEpochBytes) 88 targetEpoch := bytesutil.BytesToUint64BigEndian(targetEpochBytes) 89 require.Equal(t, sourceEpoch+1, targetEpoch) 90 } 91 return nil 92 }) 93 require.NoError(t, err) 94 }, 95 }, 96 { 97 name: "partial data saved for both types still completes the migration successfully", 98 setup: func(t *testing.T, validatorDB *Store) { 99 ctx := context.Background() 100 pubKey := [48]byte{1} 101 history := newDeprecatedAttestingHistory(0) 102 // Attest all epochs from genesis to 50. 103 numEpochs := types.Epoch(50) 104 for i := types.Epoch(1); i <= numEpochs; i++ { 105 var sr [32]byte 106 copy(sr[:], fmt.Sprintf("%d", i)) 107 newHist, err := history.setTargetData(ctx, i, &deprecatedHistoryData{ 108 Source: i - 1, 109 SigningRoot: sr[:], 110 }) 111 require.NoError(t, err) 112 history = newHist 113 } 114 newHist, err := history.setLatestEpochWritten(ctx, numEpochs) 115 require.NoError(t, err) 116 117 err = validatorDB.update(func(tx *bolt.Tx) error { 118 bucket := tx.Bucket(deprecatedAttestationHistoryBucket) 119 return bucket.Put(pubKey[:], newHist) 120 }) 121 require.NoError(t, err) 122 123 // Run the migration. 124 require.NoError(t, validatorDB.migrateOptimalAttesterProtectionUp(ctx)) 125 126 // Then delete the migration completed key. 127 err = validatorDB.update(func(tx *bolt.Tx) error { 128 mb := tx.Bucket(migrationsBucket) 129 return mb.Delete(migrationOptimalAttesterProtectionKey) 130 }) 131 require.NoError(t, err) 132 133 // Write one more entry to the DB with the old format. 134 var sr [32]byte 135 copy(sr[:], fmt.Sprintf("%d", numEpochs+1)) 136 newHist, err = newHist.setTargetData(ctx, numEpochs+1, &deprecatedHistoryData{ 137 Source: numEpochs, 138 SigningRoot: sr[:], 139 }) 140 require.NoError(t, err) 141 newHist, err = newHist.setLatestEpochWritten(ctx, numEpochs+1) 142 require.NoError(t, err) 143 144 err = validatorDB.update(func(tx *bolt.Tx) error { 145 bucket := tx.Bucket(deprecatedAttestationHistoryBucket) 146 return bucket.Put(pubKey[:], newHist) 147 }) 148 require.NoError(t, err) 149 }, 150 eval: func(t *testing.T, validatorDB *Store) { 151 // Verify we indeed have the data for all epochs 152 // since genesis to epoch 50+1 under the new schema. 153 err := validatorDB.view(func(tx *bolt.Tx) error { 154 pubKey := [48]byte{1} 155 bucket := tx.Bucket(pubKeysBucket) 156 pkBucket := bucket.Bucket(pubKey[:]) 157 signingRootsBucket := pkBucket.Bucket(attestationSigningRootsBucket) 158 sourceEpochsBucket := pkBucket.Bucket(attestationSourceEpochsBucket) 159 numEpochs := uint64(50) 160 161 // Verify we have signing roots for target epochs 1 to 50 correctly. 162 for targetEpoch := uint64(1); targetEpoch <= numEpochs+1; targetEpoch++ { 163 var sr [32]byte 164 copy(sr[:], fmt.Sprintf("%d", targetEpoch)) 165 targetEpochBytes := bytesutil.Uint64ToBytesBigEndian(targetEpoch) 166 migratedSigningRoot := signingRootsBucket.Get(targetEpochBytes) 167 require.DeepEqual(t, sr[:], migratedSigningRoot) 168 } 169 170 // Verify we have (source epoch, target epoch) pairs for epochs 0 to 50 correctly. 171 for sourceEpoch := uint64(0); sourceEpoch < numEpochs+1; sourceEpoch++ { 172 sourceEpochBytes := bytesutil.Uint64ToBytesBigEndian(sourceEpoch) 173 targetEpochBytes := sourceEpochsBucket.Get(sourceEpochBytes) 174 targetEpoch := bytesutil.BytesToUint64BigEndian(targetEpochBytes) 175 require.Equal(t, sourceEpoch+1, targetEpoch) 176 } 177 return nil 178 }) 179 require.NoError(t, err) 180 }, 181 }, 182 } 183 for _, tt := range tests { 184 t.Run(tt.name, func(t *testing.T) { 185 validatorDB := setupDB(t, nil) 186 tt.setup(t, validatorDB) 187 require.NoError(t, validatorDB.migrateOptimalAttesterProtectionUp(context.Background())) 188 tt.eval(t, validatorDB) 189 }) 190 } 191 } 192 193 func Test_migrateOptimalAttesterProtectionDown(t *testing.T) { 194 tests := []struct { 195 name string 196 setup func(t *testing.T, validatorDB *Store) 197 eval func(t *testing.T, validatorDB *Store) 198 }{ 199 { 200 name: "unsets the migration completed key upon completion", 201 setup: func(t *testing.T, validatorDB *Store) { 202 err := validatorDB.update(func(tx *bolt.Tx) error { 203 return tx.Bucket(migrationsBucket).Put(migrationOptimalAttesterProtectionKey, migrationCompleted) 204 }) 205 require.NoError(t, err) 206 }, 207 eval: func(t *testing.T, validatorDB *Store) { 208 err := validatorDB.view(func(tx *bolt.Tx) error { 209 data := tx.Bucket(migrationsBucket).Get(migrationOptimalAttesterProtectionKey) 210 require.DeepEqual(t, true, data == nil) 211 return nil 212 }) 213 require.NoError(t, err) 214 }, 215 }, 216 { 217 name: "unsets the migration, even if unset already (no panic)", 218 setup: func(t *testing.T, validatorDB *Store) {}, 219 eval: func(t *testing.T, validatorDB *Store) { 220 // Ensure the migration is not marked as complete. 221 err := validatorDB.view(func(tx *bolt.Tx) error { 222 data := tx.Bucket(migrationsBucket).Get(migrationOptimalAttesterProtectionKey) 223 require.DeepNotEqual(t, data, migrationCompleted) 224 return nil 225 }) 226 require.NoError(t, err) 227 }, 228 }, 229 { 230 name: "populates old format from data using the new schema", 231 setup: func(t *testing.T, validatorDB *Store) { 232 pubKeys := [][48]byte{{1}, {2}} 233 // Create attesting history for two public keys 234 err := validatorDB.update(func(tx *bolt.Tx) error { 235 bkt := tx.Bucket(pubKeysBucket) 236 for _, pubKey := range pubKeys { 237 pkBucket, err := bkt.CreateBucketIfNotExists(pubKey[:]) 238 if err != nil { 239 return err 240 } 241 sourceEpochsBucket, err := pkBucket.CreateBucketIfNotExists(attestationSourceEpochsBucket) 242 if err != nil { 243 return err 244 } 245 signingRootsBucket, err := pkBucket.CreateBucketIfNotExists(attestationSigningRootsBucket) 246 if err != nil { 247 return err 248 } 249 // The highest epoch we write is 50. 250 highestEpoch := uint64(50) 251 for i := uint64(1); i <= highestEpoch; i++ { 252 source := bytesutil.Uint64ToBytesBigEndian(i - 1) 253 target := bytesutil.Uint64ToBytesBigEndian(i) 254 if err := sourceEpochsBucket.Put(source, target); err != nil { 255 return err 256 } 257 var signingRoot [32]byte 258 copy(signingRoot[:], fmt.Sprintf("%d", target)) 259 if err := signingRootsBucket.Put(target, signingRoot[:]); err != nil { 260 return err 261 } 262 } 263 } 264 // Finally, we mark the migration as completed to show that we have the 265 // new, optimized format for attester protection in the database. 266 migrationBkt := tx.Bucket(migrationsBucket) 267 return migrationBkt.Put(migrationOptimalAttesterProtectionKey, migrationCompleted) 268 }) 269 require.NoError(t, err) 270 }, 271 eval: func(t *testing.T, validatorDB *Store) { 272 ctx := context.Background() 273 pubKeys := [][48]byte{{1}, {2}} 274 // Next up, we validate that we have indeed rolled back our data 275 // into the old format for attesting history. 276 err := validatorDB.view(func(tx *bolt.Tx) error { 277 bkt := tx.Bucket(deprecatedAttestationHistoryBucket) 278 for _, pubKey := range pubKeys { 279 encodedHistoryBytes := bkt.Get(pubKey[:]) 280 require.NotNil(t, encodedHistoryBytes) 281 attestingHistory := deprecatedEncodedAttestingHistory(encodedHistoryBytes) 282 highestEpoch, err := attestingHistory.getLatestEpochWritten(ctx) 283 require.NoError(t, err) 284 // Verify the highest epoch written is 50 from the setup stage. 285 require.Equal(t, types.Epoch(50), highestEpoch) 286 } 287 return nil 288 }) 289 require.NoError(t, err) 290 }, 291 }, 292 } 293 for _, tt := range tests { 294 t.Run(tt.name, func(t *testing.T) { 295 validatorDB := setupDB(t, nil) 296 tt.setup(t, validatorDB) 297 require.NoError(t, validatorDB.migrateOptimalAttesterProtectionDown(context.Background())) 298 tt.eval(t, validatorDB) 299 }) 300 } 301 }