2020-04-07 09:41:12 +00:00
|
|
|
package dao
|
2019-11-27 10:29:54 +00:00
|
|
|
|
|
|
|
import (
|
2020-06-18 10:50:30 +00:00
|
|
|
"encoding/binary"
|
2019-11-27 10:29:54 +00:00
|
|
|
"testing"
|
|
|
|
|
2020-11-23 11:09:00 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/internal/random"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
2020-11-11 15:43:28 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
2021-08-17 15:33:09 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-03-03 14:21:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
2020-07-31 12:48:35 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2019-11-27 10:29:54 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestPutGetAndDecode(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2019-12-04 12:24:49 +00:00
|
|
|
serializable := &TestSerializable{field: random.String(4)}
|
2019-11-27 10:29:54 +00:00
|
|
|
hash := []byte{1}
|
2022-02-16 14:48:15 +00:00
|
|
|
require.NoError(t, dao.putWithBuffer(serializable, hash, io.NewBufBinWriter()))
|
2019-11-27 10:29:54 +00:00
|
|
|
|
|
|
|
gotAndDecoded := &TestSerializable{}
|
2022-02-16 14:48:15 +00:00
|
|
|
err := dao.GetAndDecode(gotAndDecoded, hash)
|
2019-11-27 10:29:54 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestSerializable structure used in testing.
|
|
|
|
type TestSerializable struct {
|
|
|
|
field string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestSerializable) EncodeBinary(writer *io.BinWriter) {
|
|
|
|
writer.WriteString(t.field)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestSerializable) DecodeBinary(reader *io.BinReader) {
|
|
|
|
t.field = reader.ReadString()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPutGetStorageItem(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2020-06-18 10:50:30 +00:00
|
|
|
id := int32(random.Int(0, 1024))
|
2019-11-27 10:29:54 +00:00
|
|
|
key := []byte{0}
|
2021-03-05 14:06:54 +00:00
|
|
|
storageItem := state.StorageItem{}
|
2022-02-16 14:48:15 +00:00
|
|
|
dao.PutStorageItem(id, key, storageItem)
|
2020-06-18 10:50:30 +00:00
|
|
|
gotStorageItem := dao.GetStorageItem(id, key)
|
2019-11-27 10:29:54 +00:00
|
|
|
require.Equal(t, storageItem, gotStorageItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDeleteStorageItem(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2020-06-18 10:50:30 +00:00
|
|
|
id := int32(random.Int(0, 1024))
|
2019-11-27 10:29:54 +00:00
|
|
|
key := []byte{0}
|
2021-03-05 14:06:54 +00:00
|
|
|
storageItem := state.StorageItem{}
|
2022-02-16 14:48:15 +00:00
|
|
|
dao.PutStorageItem(id, key, storageItem)
|
|
|
|
dao.DeleteStorageItem(id, key)
|
2020-06-18 10:50:30 +00:00
|
|
|
gotStorageItem := dao.GetStorageItem(id, key)
|
2019-11-27 10:29:54 +00:00
|
|
|
require.Nil(t, gotStorageItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetBlock_NotExists(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2019-12-04 12:24:49 +00:00
|
|
|
hash := random.Uint256()
|
2020-06-04 19:59:34 +00:00
|
|
|
block, err := dao.GetBlock(hash)
|
2019-11-27 10:29:54 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Nil(t, block)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPutGetBlock(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2020-01-14 12:32:07 +00:00
|
|
|
b := &block.Block{
|
2021-03-01 13:44:47 +00:00
|
|
|
Header: block.Header{
|
2024-12-13 10:39:36 +00:00
|
|
|
Timestamp: 42,
|
2019-11-27 10:29:54 +00:00
|
|
|
Script: transaction.Witness{
|
|
|
|
VerificationScript: []byte{byte(opcode.PUSH1)},
|
|
|
|
InvocationScript: []byte{byte(opcode.NOP)},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2020-01-14 12:32:07 +00:00
|
|
|
hash := b.Hash()
|
2021-12-07 20:05:28 +00:00
|
|
|
appExecResult1 := &state.AppExecResult{
|
|
|
|
Container: hash,
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.OnPersist,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
appExecResult2 := &state.AppExecResult{
|
|
|
|
Container: hash,
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.PostPersist,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
2022-02-16 20:33:53 +00:00
|
|
|
err := dao.StoreAsBlock(b, appExecResult1, appExecResult2)
|
2019-11-27 10:29:54 +00:00
|
|
|
require.NoError(t, err)
|
2020-06-04 19:59:34 +00:00
|
|
|
gotBlock, err := dao.GetBlock(hash)
|
2019-11-27 10:29:54 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, gotBlock)
|
2021-12-07 20:05:28 +00:00
|
|
|
gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 2, len(gotAppExecResult))
|
|
|
|
require.Equal(t, *appExecResult1, gotAppExecResult[0])
|
|
|
|
require.Equal(t, *appExecResult2, gotAppExecResult[1])
|
2024-12-12 15:27:07 +00:00
|
|
|
|
2024-12-13 10:39:36 +00:00
|
|
|
ts, err := dao.DeleteBlock(hash, false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, uint64(42), ts)
|
2024-12-12 15:27:07 +00:00
|
|
|
gotBlock, err = dao.GetBlock(hash) // It's just a header, but it's still there.
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, gotBlock)
|
|
|
|
|
2024-12-13 10:39:36 +00:00
|
|
|
ts, err = dao.DeleteBlock(hash, true)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, uint64(42), ts)
|
2024-12-12 15:27:07 +00:00
|
|
|
_, err = dao.GetBlock(hash)
|
|
|
|
require.Error(t, err)
|
2019-11-27 10:29:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetVersion_NoVersion(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2019-11-27 10:29:54 +00:00
|
|
|
version, err := dao.GetVersion()
|
|
|
|
require.Error(t, err)
|
2021-10-20 14:19:16 +00:00
|
|
|
require.Equal(t, "", version.Value)
|
2019-11-27 10:29:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetVersion(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2021-10-22 07:58:53 +00:00
|
|
|
expected := Version{
|
|
|
|
StoragePrefix: 0x42,
|
|
|
|
P2PSigExtensions: true,
|
|
|
|
StateRootInHeader: true,
|
|
|
|
Value: "testVersion",
|
|
|
|
}
|
2022-02-16 14:48:15 +00:00
|
|
|
dao.PutVersion(expected)
|
2021-10-22 07:58:53 +00:00
|
|
|
actual, err := dao.GetVersion()
|
2019-11-27 10:29:54 +00:00
|
|
|
require.NoError(t, err)
|
2021-10-22 07:58:53 +00:00
|
|
|
require.Equal(t, expected, actual)
|
|
|
|
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2022-02-18 12:19:57 +00:00
|
|
|
dao.Store.Put([]byte{byte(storage.SYSVersion)}, []byte("0.1.2\x00x"))
|
2021-10-20 14:19:16 +00:00
|
|
|
|
2021-10-22 07:58:53 +00:00
|
|
|
_, err := dao.GetVersion()
|
|
|
|
require.Error(t, err)
|
|
|
|
})
|
2021-10-20 14:19:16 +00:00
|
|
|
t.Run("old format", func(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2022-02-18 12:19:57 +00:00
|
|
|
dao.Store.Put([]byte{byte(storage.SYSVersion)}, []byte("0.1.2"))
|
2021-10-20 14:19:16 +00:00
|
|
|
|
|
|
|
version, err := dao.GetVersion()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "0.1.2", version.Value)
|
|
|
|
})
|
2019-11-27 10:29:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetCurrentHeaderHeight_NoHeader(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2019-11-27 10:29:54 +00:00
|
|
|
height, err := dao.GetCurrentBlockHeight()
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, uint32(0), height)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetCurrentHeaderHeight_Store(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2020-01-14 12:32:07 +00:00
|
|
|
b := &block.Block{
|
2021-03-01 13:44:47 +00:00
|
|
|
Header: block.Header{
|
2019-11-27 10:29:54 +00:00
|
|
|
Script: transaction.Witness{
|
|
|
|
VerificationScript: []byte{byte(opcode.PUSH1)},
|
|
|
|
InvocationScript: []byte{byte(opcode.NOP)},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2022-02-16 20:33:53 +00:00
|
|
|
dao.StoreAsCurrentBlock(b)
|
2019-11-27 10:29:54 +00:00
|
|
|
height, err := dao.GetCurrentBlockHeight()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, uint32(0), height)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStoreAsTransaction(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
t.Run("no conflicts", func(t *testing.T) {
|
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2021-08-17 15:33:09 +00:00
|
|
|
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
|
2021-12-07 20:05:28 +00:00
|
|
|
tx.Signers = append(tx.Signers, transaction.Signer{})
|
|
|
|
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
2021-08-17 15:33:09 +00:00
|
|
|
hash := tx.Hash()
|
2021-12-07 20:05:28 +00:00
|
|
|
aer := &state.AppExecResult{
|
|
|
|
Container: hash,
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.Application,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
2022-02-16 20:33:53 +00:00
|
|
|
err := dao.StoreAsTransaction(tx, 0, aer)
|
2021-08-17 15:33:09 +00:00
|
|
|
require.NoError(t, err)
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(hash, nil, 0, 0)
|
2023-07-14 08:57:18 +00:00
|
|
|
require.ErrorIs(t, err, ErrAlreadyExists)
|
2021-12-07 20:05:28 +00:00
|
|
|
gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(gotAppExecResult))
|
|
|
|
require.Equal(t, *aer, gotAppExecResult[0])
|
2021-08-17 15:33:09 +00:00
|
|
|
})
|
|
|
|
|
2023-09-04 13:48:16 +00:00
|
|
|
t.Run("with conflicts", func(t *testing.T) {
|
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2021-08-17 15:33:09 +00:00
|
|
|
conflictsH := util.Uint256{1, 2, 3}
|
2023-07-14 08:57:18 +00:00
|
|
|
signer1 := util.Uint160{1, 2, 3}
|
|
|
|
signer2 := util.Uint160{4, 5, 6}
|
|
|
|
signer3 := util.Uint160{7, 8, 9}
|
|
|
|
signerMalicious := util.Uint160{10, 11, 12}
|
|
|
|
tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
|
|
|
|
tx1.Signers = append(tx1.Signers, transaction.Signer{Account: signer1}, transaction.Signer{Account: signer2})
|
|
|
|
tx1.Scripts = append(tx1.Scripts, transaction.Witness{}, transaction.Witness{})
|
|
|
|
tx1.Attributes = []transaction.Attribute{
|
2021-08-17 15:33:09 +00:00
|
|
|
{
|
|
|
|
Type: transaction.ConflictsT,
|
|
|
|
Value: &transaction.Conflicts{Hash: conflictsH},
|
|
|
|
},
|
|
|
|
}
|
2023-07-14 08:57:18 +00:00
|
|
|
hash1 := tx1.Hash()
|
|
|
|
tx2 := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
|
|
|
|
tx2.Signers = append(tx2.Signers, transaction.Signer{Account: signer3})
|
|
|
|
tx2.Scripts = append(tx2.Scripts, transaction.Witness{})
|
|
|
|
tx2.Attributes = []transaction.Attribute{
|
|
|
|
{
|
|
|
|
Type: transaction.ConflictsT,
|
|
|
|
Value: &transaction.Conflicts{Hash: conflictsH},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
hash2 := tx2.Hash()
|
|
|
|
aer1 := &state.AppExecResult{
|
|
|
|
Container: hash1,
|
2021-12-07 20:05:28 +00:00
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.Application,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
2023-09-19 14:35:51 +00:00
|
|
|
const blockIndex = 5
|
|
|
|
err := dao.StoreAsTransaction(tx1, blockIndex, aer1)
|
2023-07-14 08:57:18 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
aer2 := &state.AppExecResult{
|
|
|
|
Container: hash2,
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.Application,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.StoreAsTransaction(tx2, blockIndex, aer2)
|
2021-08-17 15:33:09 +00:00
|
|
|
require.NoError(t, err)
|
core: allow transaction to conflict with block
Transaction
0x289c235dcdab8be7426d05f0fbb5e86c619f81481ea136493fa95deee5dbb7cc is
already on mainnet at block 5272006 and we can't do anything with it.
This transaction has genesis block hash in Conflicts attribute. It leads
to the following consequences:
1. Genesis block executable record is overwritten by conflict record
stub. Genesis block can't be retrieved anymore. This bug is described
in #3427.
2. Somehow this transaction has passed verification on NeoGo CN without
any warnings:
```
Apr 24 16:12:30 kangra neo-go[2453907]: 2024-04-24T16:12:30.865+0300 INFO initializing dbft {"height": 5272006, "view": 0, "index": 6, "role": "Backup"}
Apr 24 16:12:31 kangra neo-go[2453907]: 2024-04-24T16:12:31.245+0300 INFO persisted to disk {"blocks": 1, "keys": 37, "headerHeight": 5272005, "blockHeight": 5272005, "took": "14.548903ms"}
Apr 24 16:12:34 kangra neo-go[2453907]: 2024-04-24T16:12:34.977+0300 ERROR can't add SV-signed state root {"error": "stateroot mismatch at block 5272005: 9d5f95784f26c862d6f889f213aad1e3330611880c02330e88db8802c750aa46 vs d25304d518645df725014897d13bbf023919928e79074abcea48f31cf9f32a25"}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.820+0300 INFO received PrepareRequest {"validator": 5, "tx": 1}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.821+0300 INFO sending PrepareResponse {"height": 5272006, "view": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.827+0300 INFO received PrepareResponse {"validator": 4}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.830+0300 INFO received PrepareResponse {"validator": 3}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.875+0300 INFO received PrepareResponse {"validator": 2}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.878+0300 INFO sending Commit {"height": 5272006, "view": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.879+0300 INFO received Commit {"validator": 4}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.881+0300 INFO received PrepareResponse {"validator": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.881+0300 INFO received Commit {"validator": 3}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.906+0300 INFO received Commit {"validator": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.907+0300 INFO received PrepareResponse {"validator": 1}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.915+0300 INFO received Commit {"validator": 1}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.915+0300 INFO approving block {"height": 5272006, "hash": "6b111519537343ce579d04ccad71c43318b12c680d0f374dfcd466aa22643fb6", "tx_count": 1, "merkle": "ccb7dbe5ee5da93f4936a11e48819f616ce8b5fbf0056d42e78babcd5d239c28", "prev": "12ad6cc5d0cd357b9fc9fb0c1a016ba8014d3cdd5a96818598e6a40a1a4a2a21"}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.917+0300 WARN contract invocation failed {"tx": "289c235dcdab8be7426d05f0fbb5e86c619f81481ea136493fa95deee5dbb7cc", "block": 5272006, "error": "at instruction 86 (ASSERT): ASSERT failed"}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.950+0300 INFO initializing dbft {"height": 5272007, "view": 0, "index": 6, "role": "Primary"}
Apr 24 16:12:46 kangra neo-go[2453907]: 2024-04-24T16:12:46.256+0300 INFO persisted to disk {"blocks": 1, "keys": 67, "headerHeight": 5272006, "blockHeight": 5272006, "took": "16.576594ms"}
```
And thus, we must treat this transaction as valid for this behaviour
to be reproducable.
This commit contains two fixes:
1. Do not overwrite block executable records by conflict record stubs.
If some transaction conflicts with block, then just skip the conflict
record stub for this attribute since it's impossible to create
transaction with the same hash.
2. Do not fail verification for those transactions that have Conflicts
attribute with block hash inside. This one is controversial, but we
have to adjust this code to treat already accepted transaction as
valid.
Close #3427.
The transaction itself:
```
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"attributes" : [
{
"height" : 0,
"type" : "NotValidBefore"
},
{
"hash" : "0x1f4d1defa46faa5e7b9b8d3f79a06bec777d7c26c4aa5f6f5899a291daa87c15",
"type" : "Conflicts"
}
],
"blockhash" : "0xb63f6422aa66d4fc4d370f0d682cb11833c471adcc049d57ce4373531915116b",
"blocktime" : 1713964365700,
"confirmations" : 108335,
"hash" : "0x289c235dcdab8be7426d05f0fbb5e86c619f81481ea136493fa95deee5dbb7cc",
"netfee" : "237904",
"nonce" : 0,
"script" : "CxAMFIPvkoyXujYCRmgq9qEfMJQ4wNveDBSD75KMl7o2AkZoKvahHzCUOMDb3hTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1I5",
"sender" : "NbcGB1tBEGM5MfhNbDAimvpJKzvVjLQ3jW",
"signers" : [
{
"account" : "0x649ca095e38a790d6c15ff78e0c6175099b428ac",
"scopes" : "None"
},
{
"account" : "0xdedbc03894301fa1f62a68460236ba978c92ef83",
"scopes" : "None"
}
],
"size" : 412,
"sysfee" : "997778",
"validuntilblock" : 5277629,
"version" : 0,
"vmstate" : "FAULT",
"witnesses" : [
{
"invocation" : "DECw8XNuyRg5vPeHxisQXlZ7VYNDxxK4xEm8zwpPyWJSSu+JaRKQxdrlPkXxXj34wc4ZSrZvKICGgPFE0ZHXhLPo",
"verification" : "DCEC+PI2tRSlp0wGwnjRuQdWdI0tBXNS7SlzSBBHFsaKUsdBVuezJw=="
},
{
"invocation" : "DEAxwi97t+rg9RsccOUzdJTJK7idbR7uUqQp0/0/ob9FbuW/tFius3/FOi82PDZtwdhk7s7KiNM/pU7vZLsgIbM0",
"verification" : "DCEDbInkzF5llzmgljE4HSMvtrNgPaz73XO5wgVJXLHNLXRBVuezJw=="
}
]
}
}
```
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2024-05-15 11:06:38 +00:00
|
|
|
|
|
|
|
// A special transaction that conflicts with genesis block.
|
|
|
|
genesis := &block.Block{
|
|
|
|
Header: block.Header{
|
|
|
|
Version: 0,
|
|
|
|
Timestamp: 123,
|
|
|
|
Nonce: 1,
|
|
|
|
Index: 0,
|
|
|
|
NextConsensus: util.Uint160{1, 2, 3},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
genesisAer1 := &state.AppExecResult{
|
|
|
|
Container: genesis.Hash(),
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.OnPersist,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
genesisAer2 := &state.AppExecResult{
|
|
|
|
Container: genesis.Hash(),
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.PostPersist,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
require.NoError(t, dao.StoreAsBlock(genesis, genesisAer1, genesisAer2))
|
|
|
|
tx3 := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
|
|
|
|
tx3.Signers = append(tx3.Signers, transaction.Signer{Account: signer1})
|
|
|
|
tx3.Scripts = append(tx3.Scripts, transaction.Witness{})
|
|
|
|
tx3.Attributes = []transaction.Attribute{
|
|
|
|
{
|
|
|
|
Type: transaction.ConflictsT,
|
|
|
|
Value: &transaction.Conflicts{Hash: genesis.Hash()},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
hash3 := tx3.Hash()
|
|
|
|
aer3 := &state.AppExecResult{
|
|
|
|
Container: hash3,
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.Application,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(hash1, nil, 0, 0)
|
2023-07-14 08:57:18 +00:00
|
|
|
require.ErrorIs(t, err, ErrAlreadyExists)
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(hash2, nil, 0, 0)
|
2023-05-04 14:03:06 +00:00
|
|
|
require.ErrorIs(t, err, ErrAlreadyExists)
|
2023-07-14 08:57:18 +00:00
|
|
|
|
|
|
|
// Conflicts: unimportant payer.
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(conflictsH, nil, 0, 0)
|
2023-05-04 14:03:06 +00:00
|
|
|
require.ErrorIs(t, err, ErrHasConflicts)
|
2023-07-14 08:57:18 +00:00
|
|
|
|
|
|
|
// Conflicts: payer is important, conflict isn't malicious, test signer #1.
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer1}}, blockIndex+1, 5)
|
2023-07-14 08:57:18 +00:00
|
|
|
require.ErrorIs(t, err, ErrHasConflicts)
|
|
|
|
|
|
|
|
// Conflicts: payer is important, conflict isn't malicious, test signer #2.
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer2}}, blockIndex+1, 5)
|
2023-07-14 08:57:18 +00:00
|
|
|
require.ErrorIs(t, err, ErrHasConflicts)
|
|
|
|
|
|
|
|
// Conflicts: payer is important, conflict isn't malicious, test signer #3.
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer3}}, blockIndex+1, 5)
|
2023-07-14 08:57:18 +00:00
|
|
|
require.ErrorIs(t, err, ErrHasConflicts)
|
|
|
|
|
2023-09-19 14:35:51 +00:00
|
|
|
// Conflicts: payer is important, conflict isn't malicious, but the conflict is far away than MTB.
|
|
|
|
err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signer3}}, blockIndex+10, 5)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-07-14 08:57:18 +00:00
|
|
|
// Conflicts: payer is important, conflict is malicious.
|
2023-09-19 14:35:51 +00:00
|
|
|
err = dao.HasTransaction(conflictsH, []transaction.Signer{{Account: signerMalicious}}, blockIndex+1, 5)
|
2023-07-14 08:57:18 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
gotAppExecResult, err := dao.GetAppExecResults(hash1, trigger.All)
|
2021-12-07 20:05:28 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(gotAppExecResult))
|
2023-07-14 08:57:18 +00:00
|
|
|
require.Equal(t, *aer1, gotAppExecResult[0])
|
|
|
|
|
|
|
|
gotAppExecResult, err = dao.GetAppExecResults(hash2, trigger.All)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(gotAppExecResult))
|
|
|
|
require.Equal(t, *aer2, gotAppExecResult[0])
|
core: allow transaction to conflict with block
Transaction
0x289c235dcdab8be7426d05f0fbb5e86c619f81481ea136493fa95deee5dbb7cc is
already on mainnet at block 5272006 and we can't do anything with it.
This transaction has genesis block hash in Conflicts attribute. It leads
to the following consequences:
1. Genesis block executable record is overwritten by conflict record
stub. Genesis block can't be retrieved anymore. This bug is described
in #3427.
2. Somehow this transaction has passed verification on NeoGo CN without
any warnings:
```
Apr 24 16:12:30 kangra neo-go[2453907]: 2024-04-24T16:12:30.865+0300 INFO initializing dbft {"height": 5272006, "view": 0, "index": 6, "role": "Backup"}
Apr 24 16:12:31 kangra neo-go[2453907]: 2024-04-24T16:12:31.245+0300 INFO persisted to disk {"blocks": 1, "keys": 37, "headerHeight": 5272005, "blockHeight": 5272005, "took": "14.548903ms"}
Apr 24 16:12:34 kangra neo-go[2453907]: 2024-04-24T16:12:34.977+0300 ERROR can't add SV-signed state root {"error": "stateroot mismatch at block 5272005: 9d5f95784f26c862d6f889f213aad1e3330611880c02330e88db8802c750aa46 vs d25304d518645df725014897d13bbf023919928e79074abcea48f31cf9f32a25"}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.820+0300 INFO received PrepareRequest {"validator": 5, "tx": 1}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.821+0300 INFO sending PrepareResponse {"height": 5272006, "view": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.827+0300 INFO received PrepareResponse {"validator": 4}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.830+0300 INFO received PrepareResponse {"validator": 3}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.875+0300 INFO received PrepareResponse {"validator": 2}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.878+0300 INFO sending Commit {"height": 5272006, "view": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.879+0300 INFO received Commit {"validator": 4}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.881+0300 INFO received PrepareResponse {"validator": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.881+0300 INFO received Commit {"validator": 3}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.906+0300 INFO received Commit {"validator": 0}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.907+0300 INFO received PrepareResponse {"validator": 1}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.915+0300 INFO received Commit {"validator": 1}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.915+0300 INFO approving block {"height": 5272006, "hash": "6b111519537343ce579d04ccad71c43318b12c680d0f374dfcd466aa22643fb6", "tx_count": 1, "merkle": "ccb7dbe5ee5da93f4936a11e48819f616ce8b5fbf0056d42e78babcd5d239c28", "prev": "12ad6cc5d0cd357b9fc9fb0c1a016ba8014d3cdd5a96818598e6a40a1a4a2a21"}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.917+0300 WARN contract invocation failed {"tx": "289c235dcdab8be7426d05f0fbb5e86c619f81481ea136493fa95deee5dbb7cc", "block": 5272006, "error": "at instruction 86 (ASSERT): ASSERT failed"}
Apr 24 16:12:45 kangra neo-go[2453907]: 2024-04-24T16:12:45.950+0300 INFO initializing dbft {"height": 5272007, "view": 0, "index": 6, "role": "Primary"}
Apr 24 16:12:46 kangra neo-go[2453907]: 2024-04-24T16:12:46.256+0300 INFO persisted to disk {"blocks": 1, "keys": 67, "headerHeight": 5272006, "blockHeight": 5272006, "took": "16.576594ms"}
```
And thus, we must treat this transaction as valid for this behaviour
to be reproducable.
This commit contains two fixes:
1. Do not overwrite block executable records by conflict record stubs.
If some transaction conflicts with block, then just skip the conflict
record stub for this attribute since it's impossible to create
transaction with the same hash.
2. Do not fail verification for those transactions that have Conflicts
attribute with block hash inside. This one is controversial, but we
have to adjust this code to treat already accepted transaction as
valid.
Close #3427.
The transaction itself:
```
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"attributes" : [
{
"height" : 0,
"type" : "NotValidBefore"
},
{
"hash" : "0x1f4d1defa46faa5e7b9b8d3f79a06bec777d7c26c4aa5f6f5899a291daa87c15",
"type" : "Conflicts"
}
],
"blockhash" : "0xb63f6422aa66d4fc4d370f0d682cb11833c471adcc049d57ce4373531915116b",
"blocktime" : 1713964365700,
"confirmations" : 108335,
"hash" : "0x289c235dcdab8be7426d05f0fbb5e86c619f81481ea136493fa95deee5dbb7cc",
"netfee" : "237904",
"nonce" : 0,
"script" : "CxAMFIPvkoyXujYCRmgq9qEfMJQ4wNveDBSD75KMl7o2AkZoKvahHzCUOMDb3hTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1I5",
"sender" : "NbcGB1tBEGM5MfhNbDAimvpJKzvVjLQ3jW",
"signers" : [
{
"account" : "0x649ca095e38a790d6c15ff78e0c6175099b428ac",
"scopes" : "None"
},
{
"account" : "0xdedbc03894301fa1f62a68460236ba978c92ef83",
"scopes" : "None"
}
],
"size" : 412,
"sysfee" : "997778",
"validuntilblock" : 5277629,
"version" : 0,
"vmstate" : "FAULT",
"witnesses" : [
{
"invocation" : "DECw8XNuyRg5vPeHxisQXlZ7VYNDxxK4xEm8zwpPyWJSSu+JaRKQxdrlPkXxXj34wc4ZSrZvKICGgPFE0ZHXhLPo",
"verification" : "DCEC+PI2tRSlp0wGwnjRuQdWdI0tBXNS7SlzSBBHFsaKUsdBVuezJw=="
},
{
"invocation" : "DEAxwi97t+rg9RsccOUzdJTJK7idbR7uUqQp0/0/ob9FbuW/tFius3/FOi82PDZtwdhk7s7KiNM/pU7vZLsgIbM0",
"verification" : "DCEDbInkzF5llzmgljE4HSMvtrNgPaz73XO5wgVJXLHNLXRBVuezJw=="
}
]
}
}
```
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2024-05-15 11:06:38 +00:00
|
|
|
|
|
|
|
// Ensure block is not treated as transaction.
|
|
|
|
err = dao.HasTransaction(genesis.Hash(), nil, 0, 0)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Store tx3 and ensure genesis executable record is not corrupted.
|
|
|
|
require.NoError(t, dao.StoreAsTransaction(tx3, 0, aer3))
|
|
|
|
err = dao.HasTransaction(hash3, nil, 0, 0)
|
|
|
|
require.ErrorIs(t, err, ErrAlreadyExists)
|
|
|
|
actualAer, err := dao.GetAppExecResults(hash3, trigger.All)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(actualAer))
|
|
|
|
require.Equal(t, *aer3, actualAer[0])
|
|
|
|
actualGenesisAer, err := dao.GetAppExecResults(genesis.Hash(), trigger.All)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 2, len(actualGenesisAer))
|
|
|
|
require.Equal(t, *genesisAer1, actualGenesisAer[0])
|
|
|
|
require.Equal(t, *genesisAer2, actualGenesisAer[1])
|
|
|
|
|
|
|
|
// A special requirement for transactions that conflict with block: they should
|
|
|
|
// not produce conflict record stub, ref. #3427.
|
|
|
|
err = dao.HasTransaction(genesis.Hash(), nil, 0, 0)
|
|
|
|
require.NoError(t, err)
|
2021-08-17 15:33:09 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkStoreAsTransaction(b *testing.B) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), false)
|
2021-03-25 16:18:01 +00:00
|
|
|
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
|
2021-08-17 15:33:09 +00:00
|
|
|
tx.Attributes = []transaction.Attribute{
|
|
|
|
{
|
|
|
|
Type: transaction.ConflictsT,
|
|
|
|
Value: &transaction.Conflicts{
|
|
|
|
Hash: util.Uint256{1, 2, 3},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: transaction.ConflictsT,
|
|
|
|
Value: &transaction.Conflicts{
|
|
|
|
Hash: util.Uint256{4, 5, 6},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: transaction.ConflictsT,
|
|
|
|
Value: &transaction.Conflicts{
|
|
|
|
Hash: util.Uint256{7, 8, 9},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
_ = tx.Hash()
|
2021-12-07 20:05:28 +00:00
|
|
|
aer := &state.AppExecResult{
|
|
|
|
Container: tx.Hash(),
|
|
|
|
Execution: state.Execution{
|
|
|
|
Trigger: trigger.Application,
|
|
|
|
Events: []state.NotificationEvent{},
|
|
|
|
Stack: []stackitem.Item{},
|
|
|
|
},
|
|
|
|
}
|
2021-08-17 15:33:09 +00:00
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
b.ReportAllocs()
|
2024-08-30 18:41:02 +00:00
|
|
|
for range b.N {
|
2022-02-16 20:33:53 +00:00
|
|
|
err := dao.StoreAsTransaction(tx, 1, aer)
|
2021-08-17 15:33:09 +00:00
|
|
|
if err != nil {
|
|
|
|
b.FailNow()
|
|
|
|
}
|
|
|
|
}
|
2019-11-27 10:29:54 +00:00
|
|
|
}
|
2020-06-18 10:50:30 +00:00
|
|
|
|
|
|
|
func TestMakeStorageItemKey(t *testing.T) {
|
|
|
|
var id int32 = 5
|
|
|
|
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), true)
|
2022-02-16 20:33:53 +00:00
|
|
|
|
2020-06-18 10:50:30 +00:00
|
|
|
expected := []byte{byte(storage.STStorage), 0, 0, 0, 0, 1, 2, 3}
|
|
|
|
binary.LittleEndian.PutUint32(expected[1:5], uint32(id))
|
2022-02-16 20:33:53 +00:00
|
|
|
actual := dao.makeStorageItemKey(id, []byte{1, 2, 3})
|
2020-06-18 10:50:30 +00:00
|
|
|
require.Equal(t, expected, actual)
|
|
|
|
|
|
|
|
expected = expected[0:5]
|
2022-02-16 20:33:53 +00:00
|
|
|
actual = dao.makeStorageItemKey(id, nil)
|
2021-09-27 13:35:25 +00:00
|
|
|
require.Equal(t, expected, actual)
|
|
|
|
|
|
|
|
expected = []byte{byte(storage.STTempStorage), 0, 0, 0, 0, 1, 2, 3}
|
|
|
|
binary.LittleEndian.PutUint32(expected[1:5], uint32(id))
|
2022-02-16 20:33:53 +00:00
|
|
|
dao.Version.StoragePrefix = storage.STTempStorage
|
|
|
|
actual = dao.makeStorageItemKey(id, []byte{1, 2, 3})
|
2020-06-18 10:50:30 +00:00
|
|
|
require.Equal(t, expected, actual)
|
|
|
|
}
|
2021-08-03 06:56:39 +00:00
|
|
|
|
|
|
|
func TestPutGetStateSyncPoint(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), true)
|
2021-08-03 06:56:39 +00:00
|
|
|
|
|
|
|
// empty store
|
|
|
|
_, err := dao.GetStateSyncPoint()
|
|
|
|
require.Error(t, err)
|
|
|
|
|
|
|
|
// non-empty store
|
|
|
|
var expected uint32 = 5
|
2022-02-16 14:48:15 +00:00
|
|
|
dao.PutStateSyncPoint(expected)
|
2021-08-03 06:56:39 +00:00
|
|
|
actual, err := dao.GetStateSyncPoint()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, expected, actual)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPutGetStateSyncCurrentBlockHeight(t *testing.T) {
|
2023-09-04 13:48:16 +00:00
|
|
|
dao := NewSimple(storage.NewMemoryStore(), true)
|
2021-08-03 06:56:39 +00:00
|
|
|
|
|
|
|
// empty store
|
|
|
|
_, err := dao.GetStateSyncCurrentBlockHeight()
|
|
|
|
require.Error(t, err)
|
|
|
|
|
|
|
|
// non-empty store
|
|
|
|
var expected uint32 = 5
|
2022-02-16 14:48:15 +00:00
|
|
|
dao.PutStateSyncCurrentBlockHeight(expected)
|
2021-08-03 06:56:39 +00:00
|
|
|
actual, err := dao.GetStateSyncCurrentBlockHeight()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, expected, actual)
|
|
|
|
}
|