From 19c69618c5fb68b779e10591d727c5d8d6573bf2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 10 Sep 2020 19:28:16 +0300 Subject: [PATCH] transaction: cache tx size, don't serialize it over and over again --- pkg/core/blockchain.go | 4 ++-- pkg/core/blockchain_test.go | 2 +- pkg/core/transaction/transaction.go | 24 ++++++++++++++++-------- pkg/core/transaction/transaction_test.go | 1 + pkg/rpc/server/server_test.go | 1 + 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 6a7b6526a..13266c65c 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1181,7 +1181,7 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra ) blockSize = uint32(io.GetVarSize(new(block.Block)) + io.GetVarSize(len(txes)+1)) for i, tx := range txes { - blockSize += uint32(io.GetVarSize(tx)) + blockSize += uint32(tx.Size()) sysFee += tx.SystemFee if blockSize > maxBlockSize || sysFee > maxBlockSysFee { txes = txes[:i] @@ -1234,7 +1234,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool. // Only one %w can be used. return fmt.Errorf("%w: %v", ErrPolicy, err) } - size := io.GetVarSize(t) + size := t.Size() if size > transaction.MaxTransactionSize { return fmt.Errorf("%w: (%d > MaxTransactionSize %d)", ErrTxTooBig, size, transaction.MaxTransactionSize) } diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index bbf3c6351..7d2732ed1 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -524,8 +524,8 @@ func TestGetTransaction(t *testing.T) { tx, height, err := bc.GetTransaction(block.Transactions[0].Hash()) require.Nil(t, err) assert.Equal(t, block.Index, height) + assert.Equal(t, txSize, tx.Size()) assert.Equal(t, block.Transactions[0], tx) - assert.Equal(t, txSize, io.GetVarSize(tx)) assert.Equal(t, 1, io.GetVarSize(tx.Attributes)) assert.Equal(t, 1, io.GetVarSize(tx.Scripts)) assert.NoError(t, bc.persist()) diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go index 021708d67..bcf4d2af8 100644 --- a/pkg/core/transaction/transaction.go +++ b/pkg/core/transaction/transaction.go @@ -62,8 +62,8 @@ type Transaction struct { // for correct signing/verification. Network netmode.Magic - // feePerByte is the ratio of NetworkFee and tx size, used for calculating tx priority. - feePerByte int64 + // size is transaction's serialized size. + size int // Hash of the transaction (double SHA256). hash util.Uint256 @@ -158,6 +158,7 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) { // to do it anymore. if br.Err == nil { br.Err = t.createHash() + _ = t.Size() } } @@ -252,18 +253,22 @@ func NewTransactionFromBytes(network netmode.Magic, b []byte) (*Transaction, err if r.Err == nil { return nil, errors.New("additional data after the transaction") } - tx.feePerByte = tx.NetworkFee / int64(len(b)) + tx.size = len(b) return tx, nil } // FeePerByte returns NetworkFee of the transaction divided by // its size func (t *Transaction) FeePerByte() int64 { - if t.feePerByte != 0 { - return t.feePerByte + return t.NetworkFee / int64(t.Size()) +} + +// Size returns size of the serialized transaction. +func (t *Transaction) Size() int { + if t.size == 0 { + t.size = io.GetVarSize(t) } - t.feePerByte = t.NetworkFee / int64(io.GetVarSize(t)) - return t.feePerByte + return t.size } // Sender returns the sender of the transaction which is always on the first place @@ -296,7 +301,7 @@ type transactionJSON struct { func (t *Transaction) MarshalJSON() ([]byte, error) { tx := transactionJSON{ TxID: t.Hash(), - Size: io.GetVarSize(t), + Size: t.Size(), Version: t.Version, Nonce: t.Nonce, Sender: address.Uint160ToString(t.Sender()), @@ -329,6 +334,9 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { if t.Hash() != tx.TxID { return errors.New("txid doesn't match transaction hash") } + if t.Size() != tx.Size { + return errors.New("'size' doesn't match transaction size") + } return t.isValid() } diff --git a/pkg/core/transaction/transaction_test.go b/pkg/core/transaction/transaction_test.go index 216e7ca27..fd7705477 100644 --- a/pkg/core/transaction/transaction_test.go +++ b/pkg/core/transaction/transaction_test.go @@ -73,6 +73,7 @@ func TestNew(t *testing.T) { assert.Equal(t, script, tx.Script) // Update hash fields to match tx2 that is gonna autoupdate them on decode. _ = tx.Hash() + _ = tx.Size() testserdes.EncodeDecodeBinary(t, tx, &Transaction{Network: netmode.UnitTestNet}) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index bf2ea7ed3..f7f5eb87c 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -811,6 +811,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) { block, _ := chain.GetBlock(chain.GetHeaderHash(0)) TXHash := block.Transactions[0].Hash() + _ = block.Transactions[0].Size() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 1]}"`, TXHash.StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) txOut := checkErrGetResult(t, body, false)