core: refactor and improve verification and pooling

Now we have VerifyTx() and PoolTx() APIs that either verify transaction in
isolation or verify it against the mempool (either the primary one or the one
given) and then add it there. There is no possibility to check against the
mempool, but not add a transaction to it, but I doubt we really need it.

It allows to remove some duplication between old PoolTx and verifyTx where
they both tried to check transaction against mempool (verifying first and then
adding it). It also saves us utility token balance check because it's done by
the mempool anyway and we no longer need to do that explicitly in verifyTx.

It makes AddBlock() and verifyBlock() transaction's checks more correct,
because previously they could miss that even though sender S has enough
balance to pay for A, B or C, he can't pay for all of them.

Caveats:
 * consensus is running concurrently to other processes, so things could
   change while verifyBlock() is iterating over transactions, this will be
   mitigated in subsequent commits

Improves TPS value for single node by at least 11%.

Fixes #667, fixes #668.
This commit is contained in:
Roman Khimov 2020-08-19 19:27:15 +03:00
parent 0d8cc437fe
commit 55b2cbb74d
6 changed files with 127 additions and 59 deletions

View file

@ -11,6 +11,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
"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"
@ -271,7 +272,7 @@ func TestVerifyTx(t *testing.T) {
checkResult(t, res, stackitem.NewBool(true))
checkErr := func(t *testing.T, expectedErr error, tx *transaction.Transaction) {
err := bc.verifyTx(tx, nil)
err := bc.VerifyTx(tx)
fmt.Println(err)
require.True(t, errors.Is(err, expectedErr))
}
@ -287,7 +288,7 @@ func TestVerifyTx(t *testing.T) {
t.Run("BlockedAccount", func(t *testing.T) {
tx := bc.newTestTx(accs[1].PrivateKey().GetScriptHash(), testScript)
require.NoError(t, accs[1].SignTx(tx))
err := bc.verifyTx(tx, nil)
err := bc.VerifyTx(tx)
require.True(t, errors.Is(err, ErrPolicy))
})
t.Run("InsufficientGas", func(t *testing.T) {
@ -314,12 +315,13 @@ func TestVerifyTx(t *testing.T) {
tx := bc.newTestTx(h, testScript)
tx.NetworkFee = balance / 2
require.NoError(t, accs[0].SignTx(tx))
checkErr(t, nil, tx)
require.NoError(t, bc.PoolTx(tx))
tx2 := bc.newTestTx(h, testScript)
tx2.NetworkFee = balance / 2
require.NoError(t, bc.memPool.Add(tx2, bc))
checkErr(t, ErrMemPoolConflict, tx)
require.NoError(t, accs[0].SignTx(tx2))
err := bc.PoolTx(tx2)
require.True(t, errors.Is(err, ErrMemPoolConflict))
})
t.Run("NotEnoughWitnesses", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
@ -337,6 +339,35 @@ func TestVerifyTx(t *testing.T) {
tx.Scripts[0].InvocationScript[10] = ^tx.Scripts[0].InvocationScript[10]
checkErr(t, ErrVerificationFailed, tx)
})
t.Run("OldTX", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(tx))
b := bc.newBlock(tx)
require.NoError(t, bc.AddBlock(b))
err := bc.VerifyTx(tx)
require.True(t, errors.Is(err, ErrAlreadyExists))
})
t.Run("MemPooledTX", func(t *testing.T) {
tx := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(tx))
require.NoError(t, bc.PoolTx(tx))
err := bc.PoolTx(tx)
require.True(t, errors.Is(err, ErrAlreadyExists))
})
t.Run("MemPoolOOM", func(t *testing.T) {
bc.memPool = mempool.New(1)
tx1 := bc.newTestTx(h, testScript)
tx1.NetworkFee += 10000 // Give it more priority.
require.NoError(t, accs[0].SignTx(tx1))
require.NoError(t, bc.PoolTx(tx1))
tx2 := bc.newTestTx(h, testScript)
require.NoError(t, accs[0].SignTx(tx2))
err := bc.PoolTx(tx2)
require.True(t, errors.Is(err, ErrOOM))
})
}
func TestVerifyHashAgainstScript(t *testing.T) {