From 7ee1ddff61e9ed127cd6971cd5308b5898a4379f Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 19 Aug 2020 16:39:49 +0300 Subject: [PATCH] transaction: add tests for (*Transaction).isValid() --- pkg/core/transaction/transaction.go | 32 ++++++--- pkg/core/transaction/transaction_test.go | 83 ++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go index cb9dde82e..9e343c780 100644 --- a/pkg/core/transaction/transaction.go +++ b/pkg/core/transaction/transaction.go @@ -3,6 +3,7 @@ package transaction import ( "encoding/json" "errors" + "fmt" "math/rand" "github.com/nspcc-dev/neo-go/pkg/config/netmode" @@ -332,30 +333,43 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { return t.isValid() } +// Various errors for transaction validation. +var ( + ErrInvalidVersion = errors.New("only version 0 is supported") + ErrNegativeSystemFee = errors.New("negative system fee") + ErrNegativeNetworkFee = errors.New("negative network fee") + ErrTooBigFees = errors.New("too big fees: int64 overflow") + ErrEmptySigners = errors.New("signers array should contain sender") + ErrInvalidScope = errors.New("FeeOnly scope can be used only for sender") + ErrNonUniqueSigners = errors.New("transaction signers should be unique") + ErrInvalidAttribute = errors.New("invalid attribute") + ErrEmptyScript = errors.New("no script") +) + // isValid checks whether decoded/unmarshalled transaction has all fields valid. func (t *Transaction) isValid() error { if t.Version > 0 { - return errors.New("only version 0 is supported") + return ErrInvalidVersion } if t.SystemFee < 0 { - return errors.New("negative system fee") + return ErrNegativeSystemFee } if t.NetworkFee < 0 { - return errors.New("negative network fee") + return ErrNegativeNetworkFee } if t.NetworkFee+t.SystemFee < t.SystemFee { - return errors.New("too big fees: int64 overflow") + return ErrTooBigFees } if len(t.Signers) == 0 { - return errors.New("signers array should contain sender") + return ErrEmptySigners } for i := 0; i < len(t.Signers); i++ { if i > 0 && t.Signers[i].Scopes == FeeOnly { - return errors.New("FeeOnly scope can be used only for sender") + return ErrInvalidScope } for j := i + 1; j < len(t.Signers); j++ { if t.Signers[i].Account.Equals(t.Signers[j].Account) { - return errors.New("transaction signers should be unique") + return ErrNonUniqueSigners } } } @@ -364,13 +378,13 @@ func (t *Transaction) isValid() error { switch t.Attributes[i].Type { case HighPriority: if hasHighPrio { - return errors.New("multiple high priority attributes") + return fmt.Errorf("%w: multiple high priority attributes", ErrInvalidAttribute) } hasHighPrio = true } } if len(t.Script) == 0 { - return errors.New("no script") + return ErrEmptyScript } return nil } diff --git a/pkg/core/transaction/transaction_test.go b/pkg/core/transaction/transaction_test.go index d3ea4c772..216e7ca27 100644 --- a/pkg/core/transaction/transaction_test.go +++ b/pkg/core/transaction/transaction_test.go @@ -3,6 +3,8 @@ package transaction import ( "encoding/base64" "encoding/hex" + "errors" + "math" "testing" "github.com/nspcc-dev/neo-go/pkg/config/netmode" @@ -128,3 +130,84 @@ func TestTransaction_HasAttribute(t *testing.T) { tx.Attributes = append(tx.Attributes, Attribute{Type: HighPriority}) require.True(t, tx.HasAttribute(HighPriority)) } + +func TestTransaction_isValid(t *testing.T) { + newTx := func() *Transaction { + return &Transaction{ + Version: 0, + SystemFee: 100, + NetworkFee: 100, + Signers: []Signer{ + {Account: util.Uint160{1, 2, 3}}, + { + Account: util.Uint160{4, 5, 6}, + Scopes: Global, + }, + }, + Script: []byte{1, 2, 3, 4}, + Attributes: []Attribute{}, + Scripts: []Witness{}, + Trimmed: false, + } + } + + t.Run("Valid", func(t *testing.T) { + t.Run("NoAttributes", func(t *testing.T) { + tx := newTx() + require.NoError(t, tx.isValid()) + }) + t.Run("HighPriority", func(t *testing.T) { + tx := newTx() + tx.Attributes = []Attribute{{Type: HighPriority}} + require.NoError(t, tx.isValid()) + }) + }) + t.Run("InvalidVersion", func(t *testing.T) { + tx := newTx() + tx.Version = 1 + require.True(t, errors.Is(tx.isValid(), ErrInvalidVersion)) + }) + t.Run("NegativeSystemFee", func(t *testing.T) { + tx := newTx() + tx.SystemFee = -1 + require.True(t, errors.Is(tx.isValid(), ErrNegativeSystemFee)) + }) + t.Run("NegativeNetworkFee", func(t *testing.T) { + tx := newTx() + tx.NetworkFee = -1 + require.True(t, errors.Is(tx.isValid(), ErrNegativeNetworkFee)) + }) + t.Run("TooBigFees", func(t *testing.T) { + tx := newTx() + tx.SystemFee = math.MaxInt64 - tx.NetworkFee + 1 + require.True(t, errors.Is(tx.isValid(), ErrTooBigFees)) + }) + t.Run("EmptySigners", func(t *testing.T) { + tx := newTx() + tx.Signers = tx.Signers[:0] + require.True(t, errors.Is(tx.isValid(), ErrEmptySigners)) + }) + t.Run("InvalidScope", func(t *testing.T) { + tx := newTx() + tx.Signers[1].Scopes = FeeOnly + require.True(t, errors.Is(tx.isValid(), ErrInvalidScope)) + }) + t.Run("NonUniqueSigners", func(t *testing.T) { + tx := newTx() + tx.Signers[1].Account = tx.Signers[0].Account + require.True(t, errors.Is(tx.isValid(), ErrNonUniqueSigners)) + }) + t.Run("MultipleHighPriority", func(t *testing.T) { + tx := newTx() + tx.Attributes = []Attribute{ + {Type: HighPriority}, + {Type: HighPriority}, + } + require.True(t, errors.Is(tx.isValid(), ErrInvalidAttribute)) + }) + t.Run("NoScript", func(t *testing.T) { + tx := newTx() + tx.Script = []byte{} + require.True(t, errors.Is(tx.isValid(), ErrEmptyScript)) + }) +}