*: store application long along with tx/block
Two times less keys inserted into the DB per tx leads to ~13% TPS improvement. We also drop one goroutine with it which isn't bad as well.
This commit is contained in:
parent
f87c595d94
commit
4c39b6600d
10 changed files with 168 additions and 160 deletions
|
@ -62,11 +62,11 @@ func (b *Block) RebuildMerkleRoot() {
|
|||
b.MerkleRoot = b.ComputeMerkleRoot()
|
||||
}
|
||||
|
||||
// NewBlockFromTrimmedBytes returns a new block from trimmed data.
|
||||
// NewTrimmedFromReader returns a new block from trimmed data.
|
||||
// This is commonly used to create a block from stored data.
|
||||
// Blocks created from trimmed data will have their Trimmed field
|
||||
// set to true.
|
||||
func NewBlockFromTrimmedBytes(stateRootEnabled bool, b []byte) (*Block, error) {
|
||||
func NewTrimmedFromReader(stateRootEnabled bool, br *io.BinReader) (*Block, error) {
|
||||
block := &Block{
|
||||
Header: Header{
|
||||
StateRootEnabled: stateRootEnabled,
|
||||
|
@ -74,7 +74,6 @@ func NewBlockFromTrimmedBytes(stateRootEnabled bool, b []byte) (*Block, error) {
|
|||
Trimmed: true,
|
||||
}
|
||||
|
||||
br := io.NewBinReaderFromBuf(b)
|
||||
block.Header.DecodeBinary(br)
|
||||
lenHashes := br.ReadVarUint()
|
||||
if lenHashes > MaxTransactionsPerBlock {
|
||||
|
|
|
@ -58,7 +58,8 @@ func TestTrimmedBlock(t *testing.T) {
|
|||
b, err := block.Trim()
|
||||
require.NoError(t, err)
|
||||
|
||||
trimmedBlock, err := NewBlockFromTrimmedBytes(false, b)
|
||||
r := io.NewBinReaderFromBuf(b)
|
||||
trimmedBlock, err := NewTrimmedFromReader(false, r)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, trimmedBlock.Trimmed)
|
||||
|
|
|
@ -44,7 +44,7 @@ import (
|
|||
// Tuning parameters.
|
||||
const (
|
||||
headerBatchCount = 2000
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
|
||||
defaultInitialGAS = 52000000_00000000
|
||||
defaultMemPoolSize = 50000
|
||||
|
@ -883,13 +883,14 @@ func (bc *Blockchain) addHeaders(verify bool, headers ...*block.Header) error {
|
|||
continue
|
||||
}
|
||||
bc.headerHashes = append(bc.headerHashes, h.Hash())
|
||||
buf.WriteB(storage.ExecBlock)
|
||||
h.EncodeBinary(buf.BinWriter)
|
||||
buf.BinWriter.WriteB(0)
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
}
|
||||
|
||||
key := storage.AppendPrefix(storage.DataBlock, h.Hash().BytesBE())
|
||||
key := storage.AppendPrefix(storage.DataExecutable, h.Hash().BytesBE())
|
||||
batch.Put(key, buf.Bytes())
|
||||
buf.Reset()
|
||||
lastHeader = h
|
||||
|
@ -936,38 +937,25 @@ func (bc *Blockchain) GetStateSyncModule() blockchainer.StateSync {
|
|||
func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error {
|
||||
var (
|
||||
cache = bc.dao.GetWrapped()
|
||||
blockCache = bc.dao.GetWrapped()
|
||||
aerCache = bc.dao.GetWrapped()
|
||||
appExecResults = make([]*state.AppExecResult, 0, 2+len(block.Transactions))
|
||||
aerchan = make(chan *state.AppExecResult, len(block.Transactions)/8) // Tested 8 and 4 with no practical difference, but feel free to test more and tune.
|
||||
aerdone = make(chan error)
|
||||
blockdone = make(chan error)
|
||||
)
|
||||
go func() {
|
||||
var (
|
||||
kvcache = blockCache
|
||||
writeBuf = io.NewBufBinWriter()
|
||||
kvcache = aerCache
|
||||
writeBuf = io.NewBufBinWriter()
|
||||
err error
|
||||
txCnt int
|
||||
baer1, baer2 *state.AppExecResult
|
||||
transCache = make(map[util.Uint160]transferData)
|
||||
)
|
||||
if err := kvcache.StoreAsBlock(block, writeBuf); err != nil {
|
||||
blockdone <- err
|
||||
return
|
||||
}
|
||||
writeBuf.Reset()
|
||||
|
||||
if err := kvcache.StoreAsCurrentBlock(block, writeBuf); err != nil {
|
||||
blockdone <- err
|
||||
aerdone <- err
|
||||
return
|
||||
}
|
||||
writeBuf.Reset()
|
||||
|
||||
for _, tx := range block.Transactions {
|
||||
if err := kvcache.StoreAsTransaction(tx, block.Index, writeBuf); err != nil {
|
||||
blockdone <- err
|
||||
return
|
||||
}
|
||||
|
||||
writeBuf.Reset()
|
||||
}
|
||||
if bc.config.RemoveUntraceableBlocks {
|
||||
var start, stop uint32
|
||||
if bc.config.P2PStateExchangeExtensions {
|
||||
|
@ -994,24 +982,16 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
writeBuf.Reset()
|
||||
}
|
||||
}
|
||||
close(blockdone)
|
||||
}()
|
||||
go func() {
|
||||
var (
|
||||
kvcache = aerCache
|
||||
writeBuf = io.NewBufBinWriter()
|
||||
err error
|
||||
appendBlock bool
|
||||
transCache = make(map[util.Uint160]transferData)
|
||||
)
|
||||
for aer := range aerchan {
|
||||
if aer.Container == block.Hash() && appendBlock {
|
||||
err = kvcache.AppendAppExecResult(aer, writeBuf)
|
||||
} else {
|
||||
err = kvcache.PutAppExecResult(aer, writeBuf)
|
||||
if aer.Container == block.Hash() {
|
||||
appendBlock = true
|
||||
if aer.Container == block.Hash() {
|
||||
if baer1 == nil {
|
||||
baer1 = aer
|
||||
} else {
|
||||
baer2 = aer
|
||||
}
|
||||
} else {
|
||||
err = kvcache.StoreAsTransaction(block.Transactions[txCnt], block.Index, aer, writeBuf)
|
||||
txCnt++
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to store exec result: %w", err)
|
||||
|
@ -1028,6 +1008,11 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
aerdone <- err
|
||||
return
|
||||
}
|
||||
if err := kvcache.StoreAsBlock(block, baer1, baer2, writeBuf); err != nil {
|
||||
aerdone <- err
|
||||
return
|
||||
}
|
||||
writeBuf.Reset()
|
||||
for acc, trData := range transCache {
|
||||
err = kvcache.PutTokenTransferInfo(acc, &trData.Info)
|
||||
if err != nil {
|
||||
|
@ -1055,7 +1040,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
if err != nil {
|
||||
// Release goroutines, don't care about errors, we already have one.
|
||||
close(aerchan)
|
||||
<-blockdone
|
||||
<-aerdone
|
||||
return fmt.Errorf("onPersist failed: %w", err)
|
||||
}
|
||||
|
@ -1077,7 +1061,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
if err != nil {
|
||||
// Release goroutines, don't care about errors, we already have one.
|
||||
close(aerchan)
|
||||
<-blockdone
|
||||
<-aerdone
|
||||
return fmt.Errorf("failed to persist invocation results: %w", err)
|
||||
}
|
||||
|
@ -1107,7 +1090,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
if err != nil {
|
||||
// Release goroutines, don't care about errors, we already have one.
|
||||
close(aerchan)
|
||||
<-blockdone
|
||||
<-aerdone
|
||||
return fmt.Errorf("postPersist failed: %w", err)
|
||||
}
|
||||
|
@ -1119,7 +1101,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
mpt, sr, err := bc.stateRoot.AddMPTBatch(block.Index, b, d.Store)
|
||||
if err != nil {
|
||||
// Release goroutines, don't care about errors, we already have one.
|
||||
<-blockdone
|
||||
<-aerdone
|
||||
// Here MPT can be left in a half-applied state.
|
||||
// However if this error occurs, this is a bug somewhere in code
|
||||
|
@ -1135,7 +1116,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
}
|
||||
if err != nil {
|
||||
// Release goroutines, don't care about errors, we already have one.
|
||||
<-blockdone
|
||||
<-aerdone
|
||||
return err
|
||||
}
|
||||
|
@ -1152,22 +1132,12 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
mpt.Collapse(10)
|
||||
}
|
||||
|
||||
// Wait for _both_ goroutines to finish.
|
||||
blockerr := <-blockdone
|
||||
aererr := <-aerdone
|
||||
if blockerr != nil {
|
||||
return blockerr
|
||||
}
|
||||
if aererr != nil {
|
||||
return aererr
|
||||
}
|
||||
|
||||
bc.lock.Lock()
|
||||
_, err = blockCache.Persist()
|
||||
if err != nil {
|
||||
bc.lock.Unlock()
|
||||
return err
|
||||
}
|
||||
_, err = aerCache.Persist()
|
||||
if err != nil {
|
||||
bc.lock.Unlock()
|
||||
|
|
|
@ -126,7 +126,7 @@ func TestAddBlock(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
for _, block := range blocks {
|
||||
key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesBE())
|
||||
key := storage.AppendPrefix(storage.DataExecutable, block.Hash().BytesBE())
|
||||
_, err := bc.dao.Store.Get(key)
|
||||
require.NoErrorf(t, err, "block %s not persisted", block.Hash())
|
||||
}
|
||||
|
@ -805,7 +805,7 @@ func TestVerifyTx(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, bc.dao.StoreAsTransaction(conflicting, bc.blockHeight, nil))
|
||||
require.NoError(t, bc.dao.StoreAsTransaction(conflicting, bc.blockHeight, nil, nil))
|
||||
require.True(t, errors.Is(bc.VerifyTx(tx), ErrHasConflicts))
|
||||
})
|
||||
t.Run("attribute on-chain conflict", func(t *testing.T) {
|
||||
|
|
|
@ -30,7 +30,6 @@ var (
|
|||
|
||||
// DAO is a data access object.
|
||||
type DAO interface {
|
||||
AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error
|
||||
DeleteBlock(h util.Uint256, buf *io.BufBinWriter) error
|
||||
DeleteContractID(id int32) error
|
||||
DeleteStorageItem(id int32, key []byte) error
|
||||
|
@ -55,7 +54,6 @@ type DAO interface {
|
|||
HasTransaction(hash util.Uint256) error
|
||||
Persist() (int, error)
|
||||
PersistSync() (int, error)
|
||||
PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error
|
||||
PutContractID(id int32, hash util.Uint160) error
|
||||
PutCurrentHeader(hashAndIndex []byte) error
|
||||
PutTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo) error
|
||||
|
@ -66,9 +64,9 @@ type DAO interface {
|
|||
PutVersion(v Version) error
|
||||
Seek(id int32, prefix []byte, f func(k, v []byte))
|
||||
SeekAsync(ctx context.Context, id int32, prefix []byte) chan storage.KeyValue
|
||||
StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||
StoreAsBlock(block *block.Block, aer1 *state.AppExecResult, aer2 *state.AppExecResult, buf *io.BufBinWriter) error
|
||||
StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||
StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error
|
||||
StoreAsTransaction(tx *transaction.Transaction, index uint32, aer *state.AppExecResult, buf *io.BufBinWriter) error
|
||||
putTokenTransferInfo(acc util.Uint160, bs *state.TokenTransferInfo, buf *io.BufBinWriter) error
|
||||
}
|
||||
|
||||
|
@ -220,12 +218,26 @@ func (dao *Simple) PutTokenTransferLog(acc util.Uint160, index uint32, isNEP11 b
|
|||
// GetAppExecResults gets application execution results with the specified trigger from the
|
||||
// given store.
|
||||
func (dao *Simple) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) {
|
||||
key := storage.AppendPrefix(storage.STNotification, hash.BytesBE())
|
||||
aers, err := dao.Store.Get(key)
|
||||
key := storage.AppendPrefix(storage.DataExecutable, hash.BytesBE())
|
||||
bs, err := dao.Store.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := io.NewBinReaderFromBuf(aers)
|
||||
r := io.NewBinReaderFromBuf(bs)
|
||||
switch r.ReadB() {
|
||||
case storage.ExecBlock:
|
||||
_, err = block.NewTrimmedFromReader(dao.Version.StateRootInHeader, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case storage.ExecTransaction:
|
||||
_ = r.ReadU32LE()
|
||||
tx := &transaction.Transaction{}
|
||||
tx.DecodeBinary(r)
|
||||
}
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
result := make([]state.AppExecResult, 0, 2)
|
||||
for {
|
||||
aer := new(state.AppExecResult)
|
||||
|
@ -243,39 +255,6 @@ func (dao *Simple) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]st
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// AppendAppExecResult appends given application execution result to the existing
|
||||
// set of execution results for the corresponding hash. It can reuse given buffer
|
||||
// for the purpose of value serialization.
|
||||
func (dao *Simple) AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error {
|
||||
key := storage.AppendPrefix(storage.STNotification, aer.Container.BytesBE())
|
||||
aers, err := dao.Store.Get(key)
|
||||
if err != nil && err != storage.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
if len(aers) == 0 {
|
||||
return dao.PutAppExecResult(aer, buf)
|
||||
}
|
||||
if buf == nil {
|
||||
buf = io.NewBufBinWriter()
|
||||
}
|
||||
aer.EncodeBinary(buf.BinWriter)
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
}
|
||||
aers = append(aers, buf.Bytes()...)
|
||||
return dao.Store.Put(key, aers)
|
||||
}
|
||||
|
||||
// PutAppExecResult puts given application execution result into the
|
||||
// given store. It can reuse given buffer for the purpose of value serialization.
|
||||
func (dao *Simple) PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error {
|
||||
key := storage.AppendPrefix(storage.STNotification, aer.Container.BytesBE())
|
||||
if buf == nil {
|
||||
return dao.Put(aer, key)
|
||||
}
|
||||
return dao.putWithBuffer(aer, key, buf)
|
||||
}
|
||||
|
||||
// -- end notification event.
|
||||
|
||||
// -- start storage item.
|
||||
|
@ -363,13 +342,17 @@ func makeStorageItemKey(prefix storage.KeyPrefix, id int32, key []byte) []byte {
|
|||
|
||||
// GetBlock returns Block by the given hash if it exists in the store.
|
||||
func (dao *Simple) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||
key := storage.AppendPrefix(storage.DataBlock, hash.BytesBE())
|
||||
key := storage.AppendPrefix(storage.DataExecutable, hash.BytesBE())
|
||||
b, err := dao.Store.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := block.NewBlockFromTrimmedBytes(dao.Version.StateRootInHeader, b)
|
||||
r := io.NewBinReaderFromBuf(b)
|
||||
if r.ReadB() != storage.ExecBlock {
|
||||
return nil, errors.New("internal DB inconsistency")
|
||||
}
|
||||
block, err := block.NewTrimmedFromReader(dao.Version.StateRootInHeader, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -525,18 +508,22 @@ func (dao *Simple) GetHeaderHashes() ([]util.Uint256, error) {
|
|||
// GetTransaction returns Transaction and its height by the given hash
|
||||
// if it exists in the store. It does not return dummy transactions.
|
||||
func (dao *Simple) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
|
||||
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesBE())
|
||||
key := storage.AppendPrefix(storage.DataExecutable, hash.BytesBE())
|
||||
b, err := dao.Store.Get(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if len(b) < 5 {
|
||||
if len(b) < 6 {
|
||||
return nil, 0, errors.New("bad transaction bytes")
|
||||
}
|
||||
if b[4] == transaction.DummyVersion {
|
||||
if b[0] != storage.ExecTransaction {
|
||||
return nil, 0, errors.New("internal DB inconsistency")
|
||||
}
|
||||
if b[5] == transaction.DummyVersion {
|
||||
return nil, 0, storage.ErrKeyNotFound
|
||||
}
|
||||
r := io.NewBinReaderFromBuf(b)
|
||||
_ = r.ReadB()
|
||||
|
||||
var height = r.ReadU32LE()
|
||||
|
||||
|
@ -591,16 +578,16 @@ func read2000Uint256Hashes(b []byte) ([]util.Uint256, error) {
|
|||
// Transaction hash. It returns an error in case if transaction is in chain
|
||||
// or in the list of conflicting transactions.
|
||||
func (dao *Simple) HasTransaction(hash util.Uint256) error {
|
||||
key := storage.AppendPrefix(storage.DataTransaction, hash.BytesBE())
|
||||
key := storage.AppendPrefix(storage.DataExecutable, hash.BytesBE())
|
||||
bytes, err := dao.Store.Get(key)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(bytes) < 5 {
|
||||
if len(bytes) < 6 {
|
||||
return nil
|
||||
}
|
||||
if bytes[4] == transaction.DummyVersion {
|
||||
if bytes[5] == transaction.DummyVersion {
|
||||
return ErrHasConflicts
|
||||
}
|
||||
return ErrAlreadyExists
|
||||
|
@ -608,18 +595,25 @@ func (dao *Simple) HasTransaction(hash util.Uint256) error {
|
|||
|
||||
// StoreAsBlock stores given block as DataBlock. It can reuse given buffer for
|
||||
// the purpose of value serialization.
|
||||
func (dao *Simple) StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error {
|
||||
func (dao *Simple) StoreAsBlock(block *block.Block, aer1 *state.AppExecResult, aer2 *state.AppExecResult, buf *io.BufBinWriter) error {
|
||||
var (
|
||||
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesBE())
|
||||
key = storage.AppendPrefix(storage.DataExecutable, block.Hash().BytesBE())
|
||||
)
|
||||
if buf == nil {
|
||||
buf = io.NewBufBinWriter()
|
||||
}
|
||||
buf.WriteB(storage.ExecBlock)
|
||||
b, err := block.Trim()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf.WriteBytes(b)
|
||||
if aer1 != nil {
|
||||
aer1.EncodeBinary(buf.BinWriter)
|
||||
}
|
||||
if aer2 != nil {
|
||||
aer2.EncodeBinary(buf.BinWriter)
|
||||
}
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
}
|
||||
|
@ -630,14 +624,18 @@ func (dao *Simple) StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error
|
|||
func (dao *Simple) DeleteBlock(h util.Uint256, w *io.BufBinWriter) error {
|
||||
batch := dao.Store.Batch()
|
||||
key := make([]byte, util.Uint256Size+1)
|
||||
key[0] = byte(storage.DataBlock)
|
||||
key[0] = byte(storage.DataExecutable)
|
||||
copy(key[1:], h.BytesBE())
|
||||
bs, err := dao.Store.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := block.NewBlockFromTrimmedBytes(dao.Version.StateRootInHeader, bs)
|
||||
r := io.NewBinReaderFromBuf(bs)
|
||||
if r.ReadB() != storage.ExecBlock {
|
||||
return errors.New("internal DB inconsistency")
|
||||
}
|
||||
b, err := block.NewTrimmedFromReader(dao.Version.StateRootInHeader, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -645,6 +643,7 @@ func (dao *Simple) DeleteBlock(h util.Uint256, w *io.BufBinWriter) error {
|
|||
if w == nil {
|
||||
w = io.NewBufBinWriter()
|
||||
}
|
||||
w.WriteB(storage.ExecBlock)
|
||||
b.Header.EncodeBinary(w.BinWriter)
|
||||
w.BinWriter.WriteB(0)
|
||||
if w.Err != nil {
|
||||
|
@ -652,7 +651,6 @@ func (dao *Simple) DeleteBlock(h util.Uint256, w *io.BufBinWriter) error {
|
|||
}
|
||||
batch.Put(key, w.Bytes())
|
||||
|
||||
key[0] = byte(storage.DataTransaction)
|
||||
for _, tx := range b.Transactions {
|
||||
copy(key[1:], tx.Hash().BytesBE())
|
||||
batch.Delete(key)
|
||||
|
@ -663,14 +661,8 @@ func (dao *Simple) DeleteBlock(h util.Uint256, w *io.BufBinWriter) error {
|
|||
batch.Delete(key)
|
||||
}
|
||||
}
|
||||
key[0] = byte(storage.STNotification)
|
||||
batch.Delete(key)
|
||||
}
|
||||
|
||||
key[0] = byte(storage.STNotification)
|
||||
copy(key[1:], h.BytesBE())
|
||||
batch.Delete(key)
|
||||
|
||||
return dao.Store.PutBatch(batch)
|
||||
}
|
||||
|
||||
|
@ -690,13 +682,17 @@ func (dao *Simple) StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter)
|
|||
// 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())
|
||||
func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32, aer *state.AppExecResult, buf *io.BufBinWriter) error {
|
||||
key := storage.AppendPrefix(storage.DataExecutable, tx.Hash().BytesBE())
|
||||
if buf == nil {
|
||||
buf = io.NewBufBinWriter()
|
||||
}
|
||||
buf.WriteB(storage.ExecTransaction)
|
||||
buf.WriteU32LE(index)
|
||||
tx.EncodeBinary(buf.BinWriter)
|
||||
if aer != nil {
|
||||
aer.EncodeBinary(buf.BinWriter)
|
||||
}
|
||||
if buf.Err != nil {
|
||||
return buf.Err
|
||||
}
|
||||
|
@ -711,6 +707,7 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32,
|
|||
copy(key[1:], hash.BytesBE())
|
||||
if value == nil {
|
||||
buf.Reset()
|
||||
buf.WriteB(storage.ExecTransaction)
|
||||
buf.WriteU32LE(index)
|
||||
buf.BinWriter.WriteB(transaction.DummyVersion)
|
||||
value = buf.Bytes()
|
||||
|
|
|
@ -43,24 +43,6 @@ func (t *TestSerializable) DecodeBinary(reader *io.BinReader) {
|
|||
t.field = reader.ReadString()
|
||||
}
|
||||
|
||||
func TestPutGetAppExecResult(t *testing.T) {
|
||||
dao := NewSimple(storage.NewMemoryStore(), false, false)
|
||||
hash := random.Uint256()
|
||||
appExecResult := &state.AppExecResult{
|
||||
Container: hash,
|
||||
Execution: state.Execution{
|
||||
Trigger: trigger.Application,
|
||||
Events: []state.NotificationEvent{},
|
||||
Stack: []stackitem.Item{},
|
||||
},
|
||||
}
|
||||
err := dao.AppendAppExecResult(appExecResult, nil)
|
||||
require.NoError(t, err)
|
||||
gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []state.AppExecResult{*appExecResult}, gotAppExecResult)
|
||||
}
|
||||
|
||||
func TestPutGetStorageItem(t *testing.T) {
|
||||
dao := NewSimple(storage.NewMemoryStore(), false, false)
|
||||
id := int32(random.Int(0, 1024))
|
||||
|
@ -104,11 +86,32 @@ func TestPutGetBlock(t *testing.T) {
|
|||
},
|
||||
}
|
||||
hash := b.Hash()
|
||||
err := dao.StoreAsBlock(b, nil)
|
||||
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{},
|
||||
},
|
||||
}
|
||||
err := dao.StoreAsBlock(b, appExecResult1, appExecResult2, nil)
|
||||
require.NoError(t, err)
|
||||
gotBlock, err := dao.GetBlock(hash)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, gotBlock)
|
||||
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])
|
||||
}
|
||||
|
||||
func TestGetVersion_NoVersion(t *testing.T) {
|
||||
|
@ -177,17 +180,33 @@ func TestStoreAsTransaction(t *testing.T) {
|
|||
t.Run("P2PSigExtensions off", func(t *testing.T) {
|
||||
dao := NewSimple(storage.NewMemoryStore(), false, false)
|
||||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
|
||||
tx.Signers = append(tx.Signers, transaction.Signer{})
|
||||
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
||||
hash := tx.Hash()
|
||||
err := dao.StoreAsTransaction(tx, 0, nil)
|
||||
aer := &state.AppExecResult{
|
||||
Container: hash,
|
||||
Execution: state.Execution{
|
||||
Trigger: trigger.Application,
|
||||
Events: []state.NotificationEvent{},
|
||||
Stack: []stackitem.Item{},
|
||||
},
|
||||
}
|
||||
err := dao.StoreAsTransaction(tx, 0, aer, nil)
|
||||
require.NoError(t, err)
|
||||
err = dao.HasTransaction(hash)
|
||||
require.NotNil(t, err)
|
||||
gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(gotAppExecResult))
|
||||
require.Equal(t, *aer, gotAppExecResult[0])
|
||||
})
|
||||
|
||||
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.Signers = append(tx.Signers, transaction.Signer{})
|
||||
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
||||
tx.Attributes = []transaction.Attribute{
|
||||
{
|
||||
Type: transaction.ConflictsT,
|
||||
|
@ -195,12 +214,24 @@ func TestStoreAsTransaction(t *testing.T) {
|
|||
},
|
||||
}
|
||||
hash := tx.Hash()
|
||||
err := dao.StoreAsTransaction(tx, 0, nil)
|
||||
aer := &state.AppExecResult{
|
||||
Container: hash,
|
||||
Execution: state.Execution{
|
||||
Trigger: trigger.Application,
|
||||
Events: []state.NotificationEvent{},
|
||||
Stack: []stackitem.Item{},
|
||||
},
|
||||
}
|
||||
err := dao.StoreAsTransaction(tx, 0, aer, 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))
|
||||
gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(gotAppExecResult))
|
||||
require.Equal(t, *aer, gotAppExecResult[0])
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -228,11 +259,19 @@ func BenchmarkStoreAsTransaction(b *testing.B) {
|
|||
},
|
||||
}
|
||||
_ = tx.Hash()
|
||||
aer := &state.AppExecResult{
|
||||
Container: tx.Hash(),
|
||||
Execution: state.Execution{
|
||||
Trigger: trigger.Application,
|
||||
Events: []state.NotificationEvent{},
|
||||
Stack: []stackitem.Item{},
|
||||
},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
err := dao.StoreAsTransaction(tx, 1, nil)
|
||||
err := dao.StoreAsTransaction(tx, 1, aer, nil)
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestLedgerGetTransactionHeight(t *testing.T) {
|
|||
for i := 0; i < 13; i++ {
|
||||
require.NoError(t, chain.AddBlock(chain.newBlock()))
|
||||
}
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 13, nil))
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 13, nil, nil))
|
||||
t.Run("good", func(t *testing.T) {
|
||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionHeight", tx.Hash().BytesBE())
|
||||
require.NoError(t, err)
|
||||
|
@ -41,7 +41,7 @@ func TestLedgerGetTransaction(t *testing.T) {
|
|||
ledger := chain.contracts.ByName(nativenames.Ledger).Metadata().Hash
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil))
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil, nil))
|
||||
|
||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesBE())
|
||||
require.NoError(t, err)
|
||||
|
@ -63,13 +63,13 @@ func TestLedgerGetTransaction(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("isn't traceable", func(t *testing.T) {
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 2, nil)) // block 1 is added above
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 2, nil, nil)) // block 1 is added above
|
||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesBE())
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.Null{})
|
||||
})
|
||||
t.Run("bad hash", func(t *testing.T) {
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil))
|
||||
require.NoError(t, chain.dao.StoreAsTransaction(tx, 0, nil, nil))
|
||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransaction", tx.Hash().BytesLE())
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.Null{})
|
||||
|
@ -120,7 +120,7 @@ func TestLedgerGetTransactionFromBlock(t *testing.T) {
|
|||
})
|
||||
t.Run("isn't traceable", func(t *testing.T) {
|
||||
b.Index = chain.BlockHeight() + 1
|
||||
require.NoError(t, chain.dao.StoreAsBlock(b, nil))
|
||||
require.NoError(t, chain.dao.StoreAsBlock(b, nil, nil, nil))
|
||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getTransactionFromBlock", bhash.BytesBE(), int64(0))
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.Null{})
|
||||
|
@ -166,7 +166,7 @@ func TestLedgerGetBlock(t *testing.T) {
|
|||
})
|
||||
t.Run("isn't traceable", func(t *testing.T) {
|
||||
b.Index = chain.BlockHeight() + 1
|
||||
require.NoError(t, chain.dao.StoreAsBlock(b, nil))
|
||||
require.NoError(t, chain.dao.StoreAsBlock(b, nil, nil, nil))
|
||||
res, err := invokeContractMethod(chain, 100000000, ledger, "getBlock", bhash.BytesBE())
|
||||
require.NoError(t, err)
|
||||
checkResult(t, res, stackitem.Null{})
|
||||
|
|
|
@ -315,7 +315,7 @@ func (s *Module) AddBlock(block *block.Block) error {
|
|||
}
|
||||
cache := s.dao.GetWrapped()
|
||||
writeBuf := io.NewBufBinWriter()
|
||||
if err := cache.StoreAsBlock(block, writeBuf); err != nil {
|
||||
if err := cache.StoreAsBlock(block, nil, nil, writeBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
writeBuf.Reset()
|
||||
|
@ -326,7 +326,7 @@ func (s *Module) AddBlock(block *block.Block) error {
|
|||
}
|
||||
|
||||
for _, tx := range block.Transactions {
|
||||
if err := cache.StoreAsTransaction(tx, block.Index, writeBuf); err != nil {
|
||||
if err := cache.StoreAsTransaction(tx, block.Index, nil, writeBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
writeBuf.Reset()
|
||||
|
|
|
@ -8,13 +8,11 @@ import (
|
|||
|
||||
// KeyPrefix constants.
|
||||
const (
|
||||
DataBlock KeyPrefix = 0x01
|
||||
DataTransaction KeyPrefix = 0x02
|
||||
DataMPT KeyPrefix = 0x03
|
||||
STAccount KeyPrefix = 0x40
|
||||
STNotification KeyPrefix = 0x4d
|
||||
STContractID KeyPrefix = 0x51
|
||||
STStorage KeyPrefix = 0x70
|
||||
DataExecutable KeyPrefix = 0x01
|
||||
DataMPT KeyPrefix = 0x03
|
||||
STAccount KeyPrefix = 0x40
|
||||
STContractID KeyPrefix = 0x51
|
||||
STStorage KeyPrefix = 0x70
|
||||
// STTempStorage is used to store contract storage items during state sync process
|
||||
// in order not to mess up the previous state which has its own items stored by
|
||||
// STStorage prefix. Once state exchange process is completed, all items with
|
||||
|
@ -33,6 +31,12 @@ const (
|
|||
SYSVersion KeyPrefix = 0xf0
|
||||
)
|
||||
|
||||
// Executable subtypes.
|
||||
const (
|
||||
ExecBlock byte = 1
|
||||
ExecTransaction byte = 2
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxStorageKeyLen is the maximum length of a key for storage items.
|
||||
MaxStorageKeyLen = 64
|
||||
|
|
|
@ -8,8 +8,7 @@ import (
|
|||
|
||||
var (
|
||||
prefixes = []KeyPrefix{
|
||||
DataBlock,
|
||||
DataTransaction,
|
||||
DataExecutable,
|
||||
STAccount,
|
||||
STStorage,
|
||||
IXHeaderHashList,
|
||||
|
@ -20,7 +19,6 @@ var (
|
|||
|
||||
expected = []uint8{
|
||||
0x01,
|
||||
0x02,
|
||||
0x40,
|
||||
0x70,
|
||||
0x80,
|
||||
|
|
Loading…
Reference in a new issue