Skip to content

Commit

Permalink
Implemented TestHandleTransaction_BasicValidation
Browse files Browse the repository at this point in the history
  • Loading branch information
Olshansk committed May 24, 2023
1 parent bb643d2 commit 6a4330a
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 47 deletions.
14 changes: 13 additions & 1 deletion shared/core/types/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error {
}
}

// NextCode: 136
// NextCode: 138
type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota?

//nolint:gosec // G101 - Not hard-coded credentials
Expand All @@ -53,6 +53,7 @@ const (
CodeNewPublicKeyFromBytesError Code = 8
CodeNewAddressFromBytesError Code = 9
CodeSignatureVerificationFailedError Code = 10
CodeRetrievingSignableBytesError Code = 137
CodeHexDecodeFromStringError Code = 11
CodeInvalidHashLengthError Code = 12
CodeEmptyNetworkIDError Code = 13
Expand All @@ -74,6 +75,7 @@ const (
CodeEmptyNonceError Code = 30
CodeEmptyPublicKeyError Code = 31
CodeEmptySignatureError Code = 32
CodeEmptySignatureStructureError Code = 136
CodeDuplicateTransactionError Code = 35
CodeTransactionSignError Code = 36
CodeGetAllValidatorsError Code = 37
Expand Down Expand Up @@ -227,11 +229,13 @@ const (
InvalidNonceError = "the nonce field is invalid; cannot be converted to big.Int"
NewPublicKeyFromBytesError = "unable to convert the raw bytes to a valid public key"
SignatureVerificationFailedError = "the public key / signature combination is not valid for the msg"
RetrievingSignableBytesError = "error retrieving signable bytes"
ProtoFromAnyError = "an error occurred getting the structure from the protobuf any"
NewFeeFromStringError = "the fee string is unable to be converted to a valid base 10 number"
EmptyNonceError = "the nonce in the transaction is empty"
EmptyPublicKeyError = "the public key field is empty"
EmptySignatureError = "the signature field is empty"
EmptySignatureStructureError = "the signature structure is empty"
TransactionSignError = "an error occurred signing the transaction"
InterfaceConversionError = "an error occurred converting the interface to an expected type: "
SetStatusPausedBeforeError = "an error occurred setting the actor status that were paused before"
Expand Down Expand Up @@ -598,10 +602,18 @@ func ErrEmptySignature() Error {
return NewError(CodeEmptySignatureError, EmptySignatureError)
}

func ErrEmptySignatureStructure() Error {
return NewError(CodeEmptySignatureStructureError, EmptySignatureStructureError)
}

func ErrSignatureVerificationFailed() Error {
return NewError(CodeSignatureVerificationFailedError, SignatureVerificationFailedError)
}

func ErrRetrievingSignableBytes(err error) Error {
return NewError(CodeRetrievingSignableBytesError, fmt.Sprintf("%s: %s", RetrievingSignableBytesError, err.Error()))
}

func ErrDecodeMessage(err error) Error {
return NewError(CodeDecodeMessageError, fmt.Sprintf("%s: %s", DecodeMessageError, err.Error()))
}
Expand Down
6 changes: 2 additions & 4 deletions shared/core/types/signature.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package types

import "fmt"

func (s *Signature) ValidateBasic() error {
if s.Signature == nil {
return fmt.Errorf("signature cannot be empty")
return ErrEmptySignature()
}
if s.PublicKey == nil {
return fmt.Errorf("public key cannot be empty")
return ErrEmptyPublicKey()
}
return nil
}
13 changes: 6 additions & 7 deletions shared/core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package types

import (
"bytes"
"fmt"

"github.com/pokt-network/pocket/shared/codec"
"github.com/pokt-network/pocket/shared/crypto"
Expand Down Expand Up @@ -40,12 +39,12 @@ type ITransaction interface {
func (tx *Transaction) ValidateBasic() error {
// Nonce cannot be empty to avoid transaction replays
if tx.Nonce == "" {
return fmt.Errorf("nonce cannot be empty") // ErrEmptyNonce
return ErrEmptyNonce()
}

// Is there a signature we can verify?
if tx.Signature == nil {
return fmt.Errorf("signature cannot be empty") // ErrEmptySignature
return ErrEmptySignatureStructure()
}
if err := tx.Signature.ValidateBasic(); err != nil {
return err
Expand All @@ -54,21 +53,21 @@ func (tx *Transaction) ValidateBasic() error {
// Does the transaction have a valid key?
publicKey, err := crypto.NewPublicKeyFromBytes(tx.Signature.PublicKey)
if err != nil {
return err // ErrEmptyPublicKey or ErrNewPublicKeyFromBytes
return ErrNewPublicKeyFromBytes(err)
}

// Is there a valid msg that can be decoded?
if _, err := tx.GetMessage(); err != nil {
return err // ? ErrBadMessage
return ErrDecodeMessage(err)
}

signBytes, err := tx.SignableBytes()
if err != nil {
return err // ? ErrBadSignature
return ErrRetrievingSignableBytes(err)
}

if ok := publicKey.Verify(signBytes, tx.Signature.Signature); !ok {
return fmt.Errorf("signature verification failed") // ErrSignatureVerificationFailed
return ErrSignatureVerificationFailed()
}

return nil
Expand Down
203 changes: 182 additions & 21 deletions utility/transaction_test.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,185 @@
package utility

import (
"fmt"
"strconv"
"testing"

"github.com/golang/mock/gomock"
"github.com/pokt-network/pocket/persistence/indexer"
"github.com/pokt-network/pocket/shared/codec"
"github.com/pokt-network/pocket/shared/core/types"
coreTypes "github.com/pokt-network/pocket/shared/core/types"
"github.com/pokt-network/pocket/shared/crypto"
typesUtil "github.com/pokt-network/pocket/utility/types"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)

func TestGetIndexedTransaction(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

// Test data - Prepare Transaction
func TestHandleTransaction_ErrorAlreadyInMempool(t *testing.T) {
// Prepare test data
emptyTx := types.Transaction{}
txProtoBytes, err := proto.Marshal(&emptyTx)
require.NoError(t, err)

// Test data - Prepare IndexedTransaction
idxTx := &coreTypes.IndexedTransaction{
Tx: txProtoBytes,
Height: 0,
Index: 0,
ResultCode: 0,
Error: "Olshansky",
SignerAddr: "Olshansky",
RecipientAddr: "Olshansky",
MessageType: "Olshansky",
}
// Prepare the environment
_, utilityMod, _ := prepareEnvironment(t, 0, 0, 0, 0)

// Manually add the tx to the mempool
err = utilityMod.GetMempool().AddTx(txProtoBytes)
require.NoError(t, err)

// Error on having a duplciate transaction
err = utilityMod.HandleTransaction(txProtoBytes)
require.Error(t, err)
require.EqualError(t, err, coreTypes.ErrDuplicateTransaction().Error())
}

func TestHandleTransaction_ErrorAlreadyCommitted(t *testing.T) {
// Prepare the environment
_, utilityMod, persistenceMod := prepareEnvironment(t, 0, 0, 0, 0)
_, idxTx := prepareEmptyIndexedTransaction(t, persistenceMod.GetTxIndexer())

// Index a test transaction
txIndexer := persistenceMod.GetTxIndexer()
err = txIndexer.Index(idxTx)
// Error on having an indexed transaction
err := utilityMod.HandleTransaction(idxTx.Tx)
require.Error(t, err)
require.EqualError(t, err, coreTypes.ErrTransactionAlreadyCommitted().Error())
}

func TestHandleTransaction_BasicValidation(t *testing.T) {
privKey, err := crypto.GeneratePrivateKey()
require.NoError(t, err)

pubKey := privKey.PublicKey()

message := &typesUtil.MessageSend{
FromAddress: []byte("from"),
ToAddress: []byte("to"),
Amount: "10",
}
anyMessage, err := codec.GetCodec().ToAny(message)
require.NoError(t, err)

validTx := &types.Transaction{
Nonce: strconv.Itoa(int(crypto.GetNonce())),
Signature: &types.Signature{
PublicKey: []byte("public key"),
Signature: []byte("signature"),
},
Msg: anyMessage,
}
err = validTx.Sign(privKey)
require.NoError(t, err)

testCases := []struct {
name string
txProto *coreTypes.Transaction
expectedErr error
}{
{
name: "Invalid transaction: Missing Nonce",
txProto: &types.Transaction{},
expectedErr: types.ErrEmptyNonce(),
},
{
name: "Invalid transaction: Missing Signature Structure",
txProto: &types.Transaction{
Nonce: strconv.Itoa(int(crypto.GetNonce())),
},
expectedErr: types.ErrEmptySignatureStructure(),
},
{
name: "Invalid transaction: Missing Signature",
txProto: &types.Transaction{
Nonce: strconv.Itoa(int(crypto.GetNonce())),
Signature: &types.Signature{
PublicKey: nil,
Signature: nil,
},
},
expectedErr: types.ErrEmptySignature(),
},
{
name: "Invalid transaction: Missing Public Key",
txProto: &types.Transaction{
Nonce: strconv.Itoa(int(crypto.GetNonce())),
Signature: &types.Signature{
PublicKey: nil,
Signature: []byte("bytes in place for signature but not actually valid"),
},
},
expectedErr: types.ErrEmptyPublicKey(),
},
{
name: "Invalid transaction: Invalid Public Key",
txProto: &types.Transaction{
Nonce: strconv.Itoa(int(crypto.GetNonce())),
Signature: &types.Signature{
PublicKey: []byte("invalid pub key"),
Signature: []byte("bytes in place for signature but not actually valid"),
},
},
expectedErr: types.ErrNewPublicKeyFromBytes(fmt.Errorf("the public key length is not valid, expected length 32, actual length: 15")),
},
{
name: "Invalid transaction: Invalid Message",
txProto: &types.Transaction{
Nonce: strconv.Itoa(int(crypto.GetNonce())),
Signature: &types.Signature{
PublicKey: pubKey.Bytes(),
Signature: []byte("bytes in place for signature but not actually valid"),
},
},
expectedErr: types.ErrDecodeMessage(fmt.Errorf("proto:\u00a0invalid empty type URL")),
},
{
name: "Invalid transaction: Invalid Signature",
txProto: &types.Transaction{
Nonce: strconv.Itoa(int(crypto.GetNonce())),
Signature: &types.Signature{
PublicKey: pubKey.Bytes(),
Signature: []byte("invalid signature"),
},
Msg: anyMessage,
},
expectedErr: types.ErrSignatureVerificationFailed(),
},
{
name: "Valid well-formatted transaction with valid signature",
txProto: validTx,
expectedErr: nil,
},
}

// Prepare the environment
_, utilityMod, _ := prepareEnvironment(t, 0, 0, 0, 0)

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
txProtoBytes, err := proto.Marshal(tc.txProto)
require.NoError(t, err)

err = utilityMod.HandleTransaction(txProtoBytes)
if tc.expectedErr != nil {
require.EqualError(t, err, tc.expectedErr.Error())
} else {
require.NoError(t, err)
}
})
}
}

func TestGetIndexedTransaction(t *testing.T) {
// Prepare the environment
_, utilityMod, persistenceMod := prepareEnvironment(t, 0, 0, 0, 0)
_, idxTx := prepareEmptyIndexedTransaction(t, persistenceMod.GetTxIndexer())

tests := []struct {
name string
txProtoBytes []byte
txExists bool
expectErr error
}{
{"returns indexed transaction when it exists", txProtoBytes, true, nil},
{"returns indexed transaction when it exists", idxTx.Tx, true, nil},
{"returns error when transaction doesn't exist", []byte("Does not exist"), false, types.ErrTransactionNotCommitted()},
}
for _, test := range tests {
Expand All @@ -61,3 +195,30 @@ func TestGetIndexedTransaction(t *testing.T) {
})
}
}

func prepareEmptyIndexedTransaction(t *testing.T, txIndexer indexer.TxIndexer) (types.Transaction, *coreTypes.IndexedTransaction) {
t.Helper()

// Test data - Prepare Transaction
emptyTx := types.Transaction{}
txProtoBytes, err := proto.Marshal(&emptyTx)
require.NoError(t, err)

// Test data - Prepare IndexedTransaction
idxTx := &coreTypes.IndexedTransaction{
Tx: txProtoBytes,
Height: 0,
Index: 0,
ResultCode: 0,
Error: "Olshansky",
SignerAddr: "Olshansky",
RecipientAddr: "Olshansky",
MessageType: "Olshansky",
}

// Index a test transaction
err = txIndexer.Index(idxTx)
require.NoError(t, err)

return emptyTx, idxTx
}
Loading

0 comments on commit 6a4330a

Please sign in to comment.