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 }