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  }