diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 0d699e416..cfa8ecd79 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -732,13 +732,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error } writeBuf.Reset() - if bc.config.P2PSigExtensions { - err := kvcache.StoreConflictingTransactions(tx, block.Index, writeBuf) - if err != nil { - blockdone <- err - return - } - } } if bc.config.RemoveUntraceableBlocks { var start, stop uint32 diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 8b58e2a1f..2b0ed4a0b 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -792,9 +792,16 @@ func TestVerifyTx(t *testing.T) { t.Run("dummy on-chain conflict", func(t *testing.T) { tx := bc.newTestTx(h, testScript) require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - dummyTx := transaction.NewTrimmedTX(tx.Hash()) - dummyTx.Version = transaction.DummyVersion - require.NoError(t, bc.dao.StoreAsTransaction(dummyTx, bc.blockHeight, nil)) + conflicting := transaction.New([]byte{byte(opcode.RET)}, 1) + conflicting.Attributes = []transaction.Attribute{ + { + Type: transaction.ConflictsT, + Value: &transaction.Conflicts{ + Hash: tx.Hash(), + }, + }, + } + require.NoError(t, bc.dao.StoreAsTransaction(conflicting, bc.blockHeight, nil)) require.True(t, errors.Is(bc.VerifyTx(tx), ErrHasConflicts)) }) t.Run("attribute on-chain conflict", func(t *testing.T) { diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index 93a0d4a56..2a509344f 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -68,7 +68,6 @@ type DAO interface { StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error - StoreConflictingTransactions(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error putNEP17TransferInfo(acc util.Uint160, bs *state.NEP17TransferInfo, buf *io.BufBinWriter) error } @@ -618,7 +617,8 @@ func (dao *Simple) StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) return dao.Store.Put(storage.SYSCurrentBlock.Bytes(), buf.Bytes()) } -// StoreAsTransaction stores given TX as DataTransaction. It can reuse given +// StoreAsTransaction stores given TX as DataTransaction. It also stores transactions +// given tx has conflicts with as DataTransaction with dummy version. It can reuse given // buffer for the purpose of value serialization. func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error { key := storage.AppendPrefix(storage.DataTransaction, tx.Hash().BytesBE()) @@ -626,32 +626,30 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32, buf = io.NewBufBinWriter() } buf.WriteU32LE(index) - if tx.Version == transaction.DummyVersion { - buf.BinWriter.WriteB(tx.Version) - } else { - tx.EncodeBinary(buf.BinWriter) - } + tx.EncodeBinary(buf.BinWriter) if buf.Err != nil { return buf.Err } - return dao.Store.Put(key, buf.Bytes()) -} - -// StoreConflictingTransactions stores transactions given tx has conflicts with -// as DataTransaction with dummy version. It can reuse given buffer for the -// purpose of value serialization. -func (dao *Simple) StoreConflictingTransactions(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error { - if buf == nil { - buf = io.NewBufBinWriter() + err := dao.Store.Put(key, buf.Bytes()) + if err != nil { + return err } - for _, attr := range tx.GetAttributes(transaction.ConflictsT) { - hash := attr.Value.(*transaction.Conflicts).Hash - dummyTx := transaction.NewTrimmedTX(hash) - dummyTx.Version = transaction.DummyVersion - if err := dao.StoreAsTransaction(dummyTx, index, buf); err != nil { - return fmt.Errorf("failed to store conflicting transaction %s for transaction %s: %w", hash.StringLE(), tx.Hash().StringLE(), err) + if dao.p2pSigExtensions { + var value []byte + for _, attr := range tx.GetAttributes(transaction.ConflictsT) { + hash := attr.Value.(*transaction.Conflicts).Hash + copy(key[1:], hash.BytesBE()) + if value == nil { + buf.Reset() + buf.WriteU32LE(index) + buf.BinWriter.WriteB(transaction.DummyVersion) + value = buf.Bytes() + } + err = dao.Store.Put(key, value) + if err != nil { + return fmt.Errorf("failed to store conflicting transaction %s for transaction %s: %w", hash.StringLE(), tx.Hash().StringLE(), err) + } } - buf.Reset() } return nil } diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index f6caf6993..00b6ee1fd 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -2,6 +2,7 @@ package dao import ( "encoding/binary" + "errors" "testing" "github.com/nspcc-dev/neo-go/internal/random" @@ -11,6 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" @@ -150,13 +152,69 @@ func TestGetCurrentHeaderHeight_Store(t *testing.T) { } func TestStoreAsTransaction(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore(), false, false) + t.Run("P2PSigExtensions off", func(t *testing.T) { + dao := NewSimple(storage.NewMemoryStore(), false, false) + tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1) + hash := tx.Hash() + err := dao.StoreAsTransaction(tx, 0, nil) + require.NoError(t, err) + err = dao.HasTransaction(hash) + require.NotNil(t, err) + }) + + t.Run("P2PSigExtensions on", func(t *testing.T) { + dao := NewSimple(storage.NewMemoryStore(), false, true) + conflictsH := util.Uint256{1, 2, 3} + tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1) + tx.Attributes = []transaction.Attribute{ + { + Type: transaction.ConflictsT, + Value: &transaction.Conflicts{Hash: conflictsH}, + }, + } + hash := tx.Hash() + err := dao.StoreAsTransaction(tx, 0, nil) + require.NoError(t, err) + err = dao.HasTransaction(hash) + require.True(t, errors.Is(err, ErrAlreadyExists)) + err = dao.HasTransaction(conflictsH) + require.True(t, errors.Is(err, ErrHasConflicts)) + }) +} + +func BenchmarkStoreAsTransaction(b *testing.B) { + dao := NewSimple(storage.NewMemoryStore(), false, true) tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1) - hash := tx.Hash() - err := dao.StoreAsTransaction(tx, 0, nil) - require.NoError(t, err) - err = dao.HasTransaction(hash) - require.NotNil(t, err) + 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() + + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + err := dao.StoreAsTransaction(tx, 1, nil) + if err != nil { + b.FailNow() + } + } } func TestMakeStorageItemKey(t *testing.T) {