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  }