github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/protocol_state/kvstore/models_test.go (about)

     1  package kvstore_test
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/onflow/flow-go/state/protocol"
    11  	"github.com/onflow/flow-go/state/protocol/protocol_state"
    12  	"github.com/onflow/flow-go/state/protocol/protocol_state/kvstore"
    13  	"github.com/onflow/flow-go/utils/unittest"
    14  )
    15  
    16  // TestEncodeDecode tests encoding and decoding all supported model versions.
    17  //   - VersionedEncode should return the correct version
    18  //   - instances should be equal after encoding, then decoding
    19  func TestEncodeDecode(t *testing.T) {
    20  	t.Run("v0", func(t *testing.T) {
    21  		model := &kvstore.Modelv0{
    22  			UpgradableModel: kvstore.UpgradableModel{
    23  				VersionUpgrade: &protocol.ViewBasedActivator[uint64]{
    24  					Data:           13,
    25  					ActivationView: 1000,
    26  				},
    27  			},
    28  			EpochStateID: unittest.IdentifierFixture(),
    29  		}
    30  
    31  		version, encoded, err := model.VersionedEncode()
    32  		require.NoError(t, err)
    33  		assert.Equal(t, uint64(0), version)
    34  
    35  		decoded, err := kvstore.VersionedDecode(version, encoded)
    36  		require.NoError(t, err)
    37  		assert.Equal(t, model, decoded)
    38  	})
    39  
    40  	t.Run("v1", func(t *testing.T) {
    41  		model := &kvstore.Modelv1{}
    42  
    43  		version, encoded, err := model.VersionedEncode()
    44  		require.NoError(t, err)
    45  		assert.Equal(t, uint64(1), version)
    46  
    47  		decoded, err := kvstore.VersionedDecode(version, encoded)
    48  		require.NoError(t, err)
    49  		assert.Equal(t, model, decoded)
    50  	})
    51  }
    52  
    53  // TestKVStoreAPI tests that all supported model versions satisfy the public interfaces.
    54  //   - should be able to read/write supported keys
    55  //   - should return the appropriate sentinel for unsupported keys
    56  func TestKVStoreAPI(t *testing.T) {
    57  	t.Run("v0", func(t *testing.T) {
    58  		model := &kvstore.Modelv0{}
    59  
    60  		// v0
    61  		assertModelIsUpgradable(t, model)
    62  
    63  		version := model.GetProtocolStateVersion()
    64  		assert.Equal(t, uint64(0), version)
    65  	})
    66  
    67  	t.Run("v1", func(t *testing.T) {
    68  		model := &kvstore.Modelv1{}
    69  
    70  		// v0
    71  		assertModelIsUpgradable(t, model)
    72  
    73  		version := model.GetProtocolStateVersion()
    74  		assert.Equal(t, uint64(1), version)
    75  	})
    76  }
    77  
    78  // TestKVStoreAPI_Replicate tests that replication logic of KV store correctly works. All versions need to be support this.
    79  // There are a few invariants that needs to be met:
    80  // - if model M is replicated and the requested version is equal to M.Version then an exact copy needs to be returned.
    81  // - if model M is replicated and the requested version is lower than M.Version then an error has to be returned.
    82  // - if model M is replicated and the requested version is greater than M.Version then behavior depends on concrete model.
    83  // If replication from version v to v' is not supported a sentinel error should be returned, otherwise component needs to return
    84  // a new model with version which is equal to the requested version.
    85  func TestKVStoreAPI_Replicate(t *testing.T) {
    86  	t.Run("v0", func(t *testing.T) {
    87  		model := &kvstore.Modelv0{
    88  			UpgradableModel: kvstore.UpgradableModel{
    89  				VersionUpgrade: &protocol.ViewBasedActivator[uint64]{
    90  					Data:           13,
    91  					ActivationView: 1000,
    92  				},
    93  			},
    94  		}
    95  		cpy, err := model.Replicate(model.GetProtocolStateVersion())
    96  		require.NoError(t, err)
    97  		require.True(t, reflect.DeepEqual(model, cpy)) // expect the same model
    98  		require.Equal(t, cpy.ID(), model.ID())
    99  
   100  		model.VersionUpgrade.ActivationView++ // change
   101  		require.False(t, reflect.DeepEqual(model, cpy), "expect to have a deep copy")
   102  	})
   103  	t.Run("v0->v1", func(t *testing.T) {
   104  		model := &kvstore.Modelv0{
   105  			UpgradableModel: kvstore.UpgradableModel{
   106  				VersionUpgrade: &protocol.ViewBasedActivator[uint64]{
   107  					Data:           13,
   108  					ActivationView: 1000,
   109  				},
   110  			},
   111  		}
   112  		newVersion, err := model.Replicate(1)
   113  		require.NoError(t, err)
   114  		require.Equal(t, uint64(1), newVersion.GetProtocolStateVersion())
   115  		require.NotEqual(t, newVersion.ID(), model.ID(), "two models with the same data but different version must have different ID")
   116  		_, ok := newVersion.(*kvstore.Modelv1)
   117  		require.True(t, ok, "expected Modelv1")
   118  		require.Equal(t, newVersion.GetVersionUpgrade(), model.GetVersionUpgrade())
   119  	})
   120  	t.Run("v0-invalid-upgrade", func(t *testing.T) {
   121  		model := &kvstore.Modelv0{}
   122  		newVersion, err := model.Replicate(model.GetProtocolStateVersion() + 10)
   123  		require.ErrorIs(t, err, kvstore.ErrIncompatibleVersionChange)
   124  		require.Nil(t, newVersion)
   125  	})
   126  	t.Run("v1", func(t *testing.T) {
   127  		model := &kvstore.Modelv1{
   128  			Modelv0: kvstore.Modelv0{
   129  				UpgradableModel: kvstore.UpgradableModel{
   130  					VersionUpgrade: &protocol.ViewBasedActivator[uint64]{
   131  						Data:           13,
   132  						ActivationView: 1000,
   133  					},
   134  				},
   135  				EpochStateID: unittest.IdentifierFixture(),
   136  			},
   137  		}
   138  		cpy, err := model.Replicate(model.GetProtocolStateVersion())
   139  		require.NoError(t, err)
   140  		require.True(t, reflect.DeepEqual(model, cpy))
   141  
   142  		model.VersionUpgrade.ActivationView++ // change
   143  		require.False(t, reflect.DeepEqual(model, cpy))
   144  	})
   145  	t.Run("v1-invalid-upgrade", func(t *testing.T) {
   146  		model := &kvstore.Modelv1{}
   147  
   148  		for _, version := range []uint64{
   149  			model.GetProtocolStateVersion() - 1,
   150  			model.GetProtocolStateVersion() + 1,
   151  			model.GetProtocolStateVersion() + 10,
   152  		} {
   153  			newVersion, err := model.Replicate(version)
   154  			require.ErrorIs(t, err, kvstore.ErrIncompatibleVersionChange)
   155  			require.Nil(t, newVersion)
   156  		}
   157  	})
   158  }
   159  
   160  // assertModelIsUpgradable tests that the model satisfies the version upgrade interface.
   161  //   - should be able to set and get the upgrade version
   162  //   - setting nil version upgrade should work
   163  //
   164  // This has to be tested for every model version since version upgrade should be supported by all models.
   165  func assertModelIsUpgradable(t *testing.T, api protocol_state.KVStoreMutator) {
   166  	oldVersion := api.GetProtocolStateVersion()
   167  	activationView := uint64(1000)
   168  	expected := &protocol.ViewBasedActivator[uint64]{
   169  		Data:           oldVersion + 1,
   170  		ActivationView: activationView,
   171  	}
   172  
   173  	// check if setting version upgrade works
   174  	api.SetVersionUpgrade(expected)
   175  	actual := api.GetVersionUpgrade()
   176  	assert.Equal(t, expected, actual, "version upgrade should be set")
   177  
   178  	// check if setting nil version upgrade works
   179  	api.SetVersionUpgrade(nil)
   180  	assert.Nil(t, api.GetVersionUpgrade(), "version upgrade should be nil")
   181  }