github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/dao/dao_test.go (about) 1 package dao 2 3 import ( 4 "encoding/binary" 5 "testing" 6 7 "github.com/nspcc-dev/neo-go/internal/random" 8 "github.com/nspcc-dev/neo-go/pkg/core/block" 9 "github.com/nspcc-dev/neo-go/pkg/core/state" 10 "github.com/nspcc-dev/neo-go/pkg/core/storage" 11 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 12 "github.com/nspcc-dev/neo-go/pkg/io" 13 "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" 14 "github.com/nspcc-dev/neo-go/pkg/util" 15 "github.com/nspcc-dev/neo-go/pkg/vm/opcode" 16 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestPutGetAndDecode(t *testing.T) { 21 dao := NewSimple(storage.NewMemoryStore(), false) 22 serializable := &TestSerializable{field: random.String(4)} 23 hash := []byte{1} 24 require.NoError(t, dao.putWithBuffer(serializable, hash, io.NewBufBinWriter())) 25 26 gotAndDecoded := &TestSerializable{} 27 err := dao.GetAndDecode(gotAndDecoded, hash) 28 require.NoError(t, err) 29 } 30 31 // TestSerializable structure used in testing. 32 type TestSerializable struct { 33 field string 34 } 35 36 func (t *TestSerializable) EncodeBinary(writer *io.BinWriter) { 37 writer.WriteString(t.field) 38 } 39 40 func (t *TestSerializable) DecodeBinary(reader *io.BinReader) { 41 t.field = reader.ReadString() 42 } 43 44 func TestPutGetStorageItem(t *testing.T) { 45 dao := NewSimple(storage.NewMemoryStore(), false) 46 id := int32(random.Int(0, 1024)) 47 key := []byte{0} 48 storageItem := state.StorageItem{} 49 dao.PutStorageItem(id, key, storageItem) 50 gotStorageItem := dao.GetStorageItem(id, key) 51 require.Equal(t, storageItem, gotStorageItem) 52 } 53 54 func TestDeleteStorageItem(t *testing.T) { 55 dao := NewSimple(storage.NewMemoryStore(), false) 56 id := int32(random.Int(0, 1024)) 57 key := []byte{0} 58 storageItem := state.StorageItem{} 59 dao.PutStorageItem(id, key, storageItem) 60 dao.DeleteStorageItem(id, key) 61 gotStorageItem := dao.GetStorageItem(id, key) 62 require.Nil(t, gotStorageItem) 63 } 64 65 func TestGetBlock_NotExists(t *testing.T) { 66 dao := NewSimple(storage.NewMemoryStore(), false) 67 hash := random.Uint256() 68 block, err := dao.GetBlock(hash) 69 require.Error(t, err) 70 require.Nil(t, block) 71 } 72 73 func TestPutGetBlock(t *testing.T) { 74 dao := NewSimple(storage.NewMemoryStore(), false) 75 b := &block.Block{ 76 Header: block.Header{ 77 Script: transaction.Witness{ 78 VerificationScript: []byte{byte(opcode.PUSH1)}, 79 InvocationScript: []byte{byte(opcode.NOP)}, 80 }, 81 }, 82 } 83 hash := b.Hash() 84 appExecResult1 := &state.AppExecResult{ 85 Container: hash, 86 Execution: state.Execution{ 87 Trigger: trigger.OnPersist, 88 Events: []state.NotificationEvent{}, 89 Stack: []stackitem.Item{}, 90 }, 91 } 92 appExecResult2 := &state.AppExecResult{ 93 Container: hash, 94 Execution: state.Execution{ 95 Trigger: trigger.PostPersist, 96 Events: []state.NotificationEvent{}, 97 Stack: []stackitem.Item{}, 98 }, 99 } 100 err := dao.StoreAsBlock(b, appExecResult1, appExecResult2) 101 require.NoError(t, err) 102 gotBlock, err := dao.GetBlock(hash) 103 require.NoError(t, err) 104 require.NotNil(t, gotBlock) 105 gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All) 106 require.NoError(t, err) 107 require.Equal(t, 2, len(gotAppExecResult)) 108 require.Equal(t, *appExecResult1, gotAppExecResult[0]) 109 require.Equal(t, *appExecResult2, gotAppExecResult[1]) 110 } 111 112 func TestGetVersion_NoVersion(t *testing.T) { 113 dao := NewSimple(storage.NewMemoryStore(), false) 114 version, err := dao.GetVersion() 115 require.Error(t, err) 116 require.Equal(t, "", version.Value) 117 } 118 119 func TestGetVersion(t *testing.T) { 120 dao := NewSimple(storage.NewMemoryStore(), false) 121 expected := Version{ 122 StoragePrefix: 0x42, 123 P2PSigExtensions: true, 124 StateRootInHeader: true, 125 Value: "testVersion", 126 } 127 dao.PutVersion(expected) 128 actual, err := dao.GetVersion() 129 require.NoError(t, err) 130 require.Equal(t, expected, actual) 131 132 t.Run("invalid", func(t *testing.T) { 133 dao := NewSimple(storage.NewMemoryStore(), false) 134 dao.Store.Put([]byte{byte(storage.SYSVersion)}, []byte("0.1.2\x00x")) 135 136 _, err := dao.GetVersion() 137 require.Error(t, err) 138 }) 139 t.Run("old format", func(t *testing.T) { 140 dao := NewSimple(storage.NewMemoryStore(), false) 141 dao.Store.Put([]byte{byte(storage.SYSVersion)}, []byte("0.1.2")) 142 143 version, err := dao.GetVersion() 144 require.NoError(t, err) 145 require.Equal(t, "0.1.2", version.Value) 146 }) 147 } 148 149 func TestGetCurrentHeaderHeight_NoHeader(t *testing.T) { 150 dao := NewSimple(storage.NewMemoryStore(), false) 151 height, err := dao.GetCurrentBlockHeight() 152 require.Error(t, err) 153 require.Equal(t, uint32(0), height) 154 } 155 156 func TestGetCurrentHeaderHeight_Store(t *testing.T) { 157 dao := NewSimple(storage.NewMemoryStore(), false) 158 b := &block.Block{ 159 Header: block.Header{ 160 Script: transaction.Witness{ 161 VerificationScript: []byte{byte(opcode.PUSH1)}, 162 InvocationScript: []byte{byte(opcode.NOP)}, 163 }, 164 }, 165 } 166 dao.StoreAsCurrentBlock(b) 167 height, err := dao.GetCurrentBlockHeight() 168 require.NoError(t, err) 169 require.Equal(t, uint32(0), height) 170 } 171 172 func TestStoreAsTransaction(t *testing.T) { 173 t.Run("no conflicts", func(t *testing.T) { 174 dao := NewSimple(storage.NewMemoryStore(), false) 175 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1) 176 tx.Signers = append(tx.Signers, transaction.Signer{}) 177 tx.Scripts = append(tx.Scripts, transaction.Witness{}) 178 hash := tx.Hash() 179 aer := &state.AppExecResult{ 180 Container: hash, 181 Execution: state.Execution{ 182 Trigger: trigger.Application, 183 Events: []state.NotificationEvent{}, 184 Stack: []stackitem.Item{}, 185 }, 186 } 187 err := dao.StoreAsTransaction(tx, 0, aer) 188 require.NoError(t, err) 189 err = dao.HasTransaction(hash, nil, 0, 0) 190 require.ErrorIs(t, err, ErrAlreadyExists) 191 gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All) 192 require.NoError(t, err) 193 require.Equal(t, 1, len(gotAppExecResult)) 194 require.Equal(t, *aer, gotAppExecResult[0]) 195 }) 196 197 t.Run("with conflicts", func(t *testing.T) { 198 dao := NewSimple(storage.NewMemoryStore(), false) 199 conflictsH := util.Uint256{1, 2, 3} 200 signer1 := util.Uint160{1, 2, 3} 201 signer2 := util.Uint160{4, 5, 6} 202 signer3 := util.Uint160{7, 8, 9} 203 signerMalicious := util.Uint160{10, 11, 12} 204 tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 1) 205 tx1.Signers = append(tx1.Signers, transaction.Signer{Account: signer1}, transaction.Signer{Account: signer2}) 206 tx1.Scripts = append(tx1.Scripts, transaction.Witness{}, transaction.Witness{}) 207 tx1.Attributes = []transaction.Attribute{ 208 { 209 Type: transaction.ConflictsT, 210 Value: &transaction.Conflicts{Hash: conflictsH}, 211 }, 212 } 213 hash1 := tx1.Hash() 214 tx2 := transaction.New([]byte{byte(opcode.PUSH1)}, 1) 215 tx2.Signers = append(tx2.Signers, transaction.Signer{Account: signer3}) 216 tx2.Scripts = append(tx2.Scripts, transaction.Witness{}) 217 tx2.Attributes = []transaction.Attribute{ 218 { 219 Type: transaction.ConflictsT, 220 Value: &transaction.Conflicts{Hash: conflictsH}, 221 }, 222 } 223 hash2 := tx2.Hash() 224 aer1 := &state.AppExecResult{ 225 Container: hash1, 226 Execution: state.Execution{ 227 Trigger: trigger.Application, 228 Events: []state.NotificationEvent{}, 229 Stack: []stackitem.Item{}, 230 }, 231 } 232 const blockIndex = 5 233 err := dao.StoreAsTransaction(tx1, blockIndex, aer1) 234 require.NoError(t, err) 235 aer2 := &state.AppExecResult{ 236 Container: hash2, 237 Execution: state.Execution{ 238 Trigger: trigger.Application, 239 Events: []state.NotificationEvent{}, 240 Stack: []stackitem.Item{}, 241 }, 242 } 243 err = dao.StoreAsTransaction(tx2, blockIndex, aer2) 244 require.NoError(t, err) 245 246 // A special transaction that conflicts with genesis block. 247 genesis := &block.Block{ 248 Header: block.Header{ 249 Version: 0, 250 Timestamp: 123, 251 Nonce: 1, 252 Index: 0, 253 NextConsensus: util.Uint160{1, 2, 3}, 254 }, 255 } 256 genesisAer1 := &state.AppExecResult{ 257 Container: genesis.Hash(), 258 Execution: state.Execution{ 259 Trigger: trigger.OnPersist, 260 Events: []state.NotificationEvent{}, 261 Stack: []stackitem.Item{}, 262 }, 263 } 264 genesisAer2 := &state.AppExecResult{ 265 Container: genesis.Hash(), 266 Execution: state.Execution{ 267 Trigger: trigger.PostPersist, 268 Events: []state.NotificationEvent{}, 269 Stack: []stackitem.Item{}, 270 }, 271 } 272 require.NoError(t, dao.StoreAsBlock(genesis, genesisAer1, genesisAer2)) 273 tx3 := transaction.New([]byte{byte(opcode.PUSH1)}, 1) 274 tx3.Signers = append(tx3.Signers, transaction.Signer{Account: signer1}) 275 tx3.Scripts = append(tx3.Scripts, transaction.Witness{}) 276 tx3.Attributes = []transaction.Attribute{ 277 { 278 Type: transaction.ConflictsT, 279 Value: &transaction.Conflicts{Hash: genesis.Hash()}, 280 }, 281 } 282 hash3 := tx3.Hash() 283 aer3 := &state.AppExecResult{ 284 Container: hash3, 285 Execution: state.Execution{ 286 Trigger: trigger.Application, 287 Events: []state.NotificationEvent{}, 288 Stack: []stackitem.Item{}, 289 }, 290 } 291 292 err = dao.HasTransaction(hash1, nil, 0, 0) 293 require.ErrorIs(t, err, ErrAlreadyExists) 294 err = dao.HasTransaction(hash2, nil, 0, 0) 295 require.ErrorIs(t, err, ErrAlreadyExists) 296 297 // Conflicts: unimportant payer. 298 err = dao.HasTransaction(conflictsH, nil, 0, 0) 299 require.ErrorIs(t, err, ErrHasConflicts) 300 301 // Conflicts: payer is important, conflict isn't malicious, test signer #1. 302 err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer1}}, blockIndex+1, 5) 303 require.ErrorIs(t, err, ErrHasConflicts) 304 305 // Conflicts: payer is important, conflict isn't malicious, test signer #2. 306 err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer2}}, blockIndex+1, 5) 307 require.ErrorIs(t, err, ErrHasConflicts) 308 309 // Conflicts: payer is important, conflict isn't malicious, test signer #3. 310 err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer3}}, blockIndex+1, 5) 311 require.ErrorIs(t, err, ErrHasConflicts) 312 313 // Conflicts: payer is important, conflict isn't malicious, but the conflict is far away than MTB. 314 err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer3}}, blockIndex+10, 5) 315 require.NoError(t, err) 316 317 // Conflicts: payer is important, conflict is malicious. 318 err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signerMalicious}}, blockIndex+1, 5) 319 require.NoError(t, err) 320 321 gotAppExecResult, err := dao.GetAppExecResults(hash1, trigger.All) 322 require.NoError(t, err) 323 require.Equal(t, 1, len(gotAppExecResult)) 324 require.Equal(t, *aer1, gotAppExecResult[0]) 325 326 gotAppExecResult, err = dao.GetAppExecResults(hash2, trigger.All) 327 require.NoError(t, err) 328 require.Equal(t, 1, len(gotAppExecResult)) 329 require.Equal(t, *aer2, gotAppExecResult[0]) 330 331 // Ensure block is not treated as transaction. 332 err = dao.HasTransaction(genesis.Hash(), nil, 0, 0) 333 require.NoError(t, err) 334 335 // Store tx3 and ensure genesis executable record is not corrupted. 336 require.NoError(t, dao.StoreAsTransaction(tx3, 0, aer3)) 337 err = dao.HasTransaction(hash3, nil, 0, 0) 338 require.ErrorIs(t, err, ErrAlreadyExists) 339 actualAer, err := dao.GetAppExecResults(hash3, trigger.All) 340 require.NoError(t, err) 341 require.Equal(t, 1, len(actualAer)) 342 require.Equal(t, *aer3, actualAer[0]) 343 actualGenesisAer, err := dao.GetAppExecResults(genesis.Hash(), trigger.All) 344 require.NoError(t, err) 345 require.Equal(t, 2, len(actualGenesisAer)) 346 require.Equal(t, *genesisAer1, actualGenesisAer[0]) 347 require.Equal(t, *genesisAer2, actualGenesisAer[1]) 348 349 // A special requirement for transactions that conflict with block: they should 350 // not produce conflict record stub, ref. #3427. 351 err = dao.HasTransaction(genesis.Hash(), nil, 0, 0) 352 require.NoError(t, err) 353 }) 354 } 355 356 func BenchmarkStoreAsTransaction(b *testing.B) { 357 dao := NewSimple(storage.NewMemoryStore(), false) 358 tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1) 359 tx.Attributes = []transaction.Attribute{ 360 { 361 Type: transaction.ConflictsT, 362 Value: &transaction.Conflicts{ 363 Hash: util.Uint256{1, 2, 3}, 364 }, 365 }, 366 { 367 Type: transaction.ConflictsT, 368 Value: &transaction.Conflicts{ 369 Hash: util.Uint256{4, 5, 6}, 370 }, 371 }, 372 { 373 Type: transaction.ConflictsT, 374 Value: &transaction.Conflicts{ 375 Hash: util.Uint256{7, 8, 9}, 376 }, 377 }, 378 } 379 _ = tx.Hash() 380 aer := &state.AppExecResult{ 381 Container: tx.Hash(), 382 Execution: state.Execution{ 383 Trigger: trigger.Application, 384 Events: []state.NotificationEvent{}, 385 Stack: []stackitem.Item{}, 386 }, 387 } 388 389 b.ResetTimer() 390 b.ReportAllocs() 391 for n := 0; n < b.N; n++ { 392 err := dao.StoreAsTransaction(tx, 1, aer) 393 if err != nil { 394 b.FailNow() 395 } 396 } 397 } 398 399 func TestMakeStorageItemKey(t *testing.T) { 400 var id int32 = 5 401 402 dao := NewSimple(storage.NewMemoryStore(), true) 403 404 expected := []byte{byte(storage.STStorage), 0, 0, 0, 0, 1, 2, 3} 405 binary.LittleEndian.PutUint32(expected[1:5], uint32(id)) 406 actual := dao.makeStorageItemKey(id, []byte{1, 2, 3}) 407 require.Equal(t, expected, actual) 408 409 expected = expected[0:5] 410 actual = dao.makeStorageItemKey(id, nil) 411 require.Equal(t, expected, actual) 412 413 expected = []byte{byte(storage.STTempStorage), 0, 0, 0, 0, 1, 2, 3} 414 binary.LittleEndian.PutUint32(expected[1:5], uint32(id)) 415 dao.Version.StoragePrefix = storage.STTempStorage 416 actual = dao.makeStorageItemKey(id, []byte{1, 2, 3}) 417 require.Equal(t, expected, actual) 418 } 419 420 func TestPutGetStateSyncPoint(t *testing.T) { 421 dao := NewSimple(storage.NewMemoryStore(), true) 422 423 // empty store 424 _, err := dao.GetStateSyncPoint() 425 require.Error(t, err) 426 427 // non-empty store 428 var expected uint32 = 5 429 dao.PutStateSyncPoint(expected) 430 actual, err := dao.GetStateSyncPoint() 431 require.NoError(t, err) 432 require.Equal(t, expected, actual) 433 } 434 435 func TestPutGetStateSyncCurrentBlockHeight(t *testing.T) { 436 dao := NewSimple(storage.NewMemoryStore(), true) 437 438 // empty store 439 _, err := dao.GetStateSyncCurrentBlockHeight() 440 require.Error(t, err) 441 442 // non-empty store 443 var expected uint32 = 5 444 dao.PutStateSyncCurrentBlockHeight(expected) 445 actual, err := dao.GetStateSyncCurrentBlockHeight() 446 require.NoError(t, err) 447 require.Equal(t, expected, actual) 448 }