Skip to content

Commit 9283444

Browse files
authored
refactor validator (iotexproject#1970)
1 parent 06d7cc4 commit 9283444

30 files changed

+664
-957
lines changed

action/action.go

+5
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ func Verify(sealed SealedEnvelope) error {
8282
if sealed.SrcPubkey() == nil {
8383
return errors.New("empty public key")
8484
}
85+
// Reject action with insufficient gas limit
86+
intrinsicGas, err := sealed.IntrinsicGas()
87+
if intrinsicGas > sealed.GasLimit() || err != nil {
88+
return errors.Wrap(ErrInsufficientBalanceForGas, "insufficient gas")
89+
}
8590

8691
hash := sealed.Envelope.Hash()
8792
if sealed.SrcPubkey().Verify(hash[:], sealed.Signature()) {

action/action_test.go

+50-13
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,72 @@ package action
88

99
import (
1010
"encoding/hex"
11-
"fmt"
1211
"math/big"
12+
"strings"
1313
"testing"
1414

15+
"github.com/pkg/errors"
1516
"github.com/stretchr/testify/require"
1617

1718
"github.com/iotexproject/iotex-core/test/identityset"
1819
)
1920

20-
func TestActionProto(t *testing.T) {
21+
func TestActionProtoAndVerify(t *testing.T) {
2122
require := require.New(t)
2223
data, err := hex.DecodeString("")
2324
require.NoError(err)
2425
v, err := NewExecution("", 0, big.NewInt(10), uint64(10), big.NewInt(10), data)
2526
require.NoError(err)
26-
fmt.Println(v)
27+
t.Run("no error", func(t *testing.T) {
28+
bd := &EnvelopeBuilder{}
29+
elp := bd.SetGasPrice(big.NewInt(10)).
30+
SetGasLimit(uint64(100000)).
31+
SetAction(v).Build()
2732

28-
bd := &EnvelopeBuilder{}
29-
elp := bd.SetGasPrice(big.NewInt(10)).
30-
SetGasLimit(uint64(100000)).
31-
SetAction(v).Build()
33+
selp, err := Sign(elp, identityset.PrivateKey(28))
34+
require.NoError(err)
3235

33-
selp, err := Sign(elp, identityset.PrivateKey(28))
34-
require.NoError(err)
36+
require.NoError(Verify(selp))
37+
38+
nselp := &SealedEnvelope{}
39+
require.NoError(nselp.LoadProto(selp.Proto()))
40+
41+
require.Equal(selp.Hash(), nselp.Hash())
42+
})
43+
t.Run("empty public key", func(t *testing.T) {
44+
bd := &EnvelopeBuilder{}
45+
elp := bd.SetGasPrice(big.NewInt(10)).
46+
SetGasLimit(uint64(100000)).
47+
SetAction(v).Build()
48+
49+
selp, err := Sign(elp, identityset.PrivateKey(28))
50+
require.NoError(err)
51+
52+
selp.srcPubkey = nil
53+
54+
require.EqualError(Verify(selp), "empty public key")
55+
})
56+
t.Run("gas limit too low", func(t *testing.T) {
57+
bd := &EnvelopeBuilder{}
58+
elp := bd.SetGasPrice(big.NewInt(10)).
59+
SetGasLimit(uint64(1000)).
60+
SetAction(v).Build()
61+
62+
selp, err := Sign(elp, identityset.PrivateKey(28))
63+
require.NoError(err)
3564

36-
require.NoError(Verify(selp))
65+
require.Equal(ErrInsufficientBalanceForGas, errors.Cause(Verify(selp)))
66+
})
67+
t.Run("invalid signature", func(t *testing.T) {
68+
bd := &EnvelopeBuilder{}
69+
elp := bd.SetGasPrice(big.NewInt(10)).
70+
SetGasLimit(uint64(100000)).
71+
SetAction(v).Build()
3772

38-
nselp := &SealedEnvelope{}
39-
require.NoError(nselp.LoadProto(selp.Proto()))
73+
selp, err := Sign(elp, identityset.PrivateKey(28))
74+
require.NoError(err)
75+
selp.signature = []byte("invalid signature")
4076

41-
require.Equal(selp.Hash(), nselp.Hash())
77+
require.True(strings.Contains(Verify(selp).Error(), "failed to verify action hash"))
78+
})
4279
}

action/execution_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestExecutionSignVerify(t *testing.T) {
2727

2828
bd := &EnvelopeBuilder{}
2929
elp := bd.SetNonce(0).
30-
SetGasLimit(uint64(10)).
30+
SetGasLimit(uint64(100000)).
3131
SetGasPrice(big.NewInt(10)).
3232
SetAction(ex).Build()
3333

action/protocol/execution/protocol_test.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/iotexproject/iotex-core/action/protocol/rewarding"
3838
"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
3939
"github.com/iotexproject/iotex-core/blockchain"
40+
"github.com/iotexproject/iotex-core/blockchain/block"
4041
"github.com/iotexproject/iotex-core/blockchain/blockdao"
4142
"github.com/iotexproject/iotex-core/blockindex"
4243
"github.com/iotexproject/iotex-core/config"
@@ -320,12 +321,15 @@ func (sct *SmartContractTest) prepareBlockchain(
320321
dao,
321322
sf,
322323
blockchain.RegistryOption(registry),
324+
blockchain.BlockValidatorOption(block.NewValidator(
325+
sf,
326+
protocol.NewGenericValidator(sf, accountutil.AccountState),
327+
)),
323328
)
324329
reward := rewarding.NewProtocol(nil)
325330
r.NoError(reward.Register(registry))
326331

327332
r.NotNil(bc)
328-
bc.Validator().AddActionEnvelopeValidators(protocol.NewGenericValidator(sf, accountutil.AccountState))
329333
execution := NewProtocol(dao.GetBlockHash)
330334
r.NoError(execution.Register(registry))
331335
r.NoError(bc.Start(ctx))
@@ -498,10 +502,13 @@ func TestProtocol_Handle(t *testing.T) {
498502
dao,
499503
sf,
500504
blockchain.RegistryOption(registry),
505+
blockchain.BlockValidatorOption(block.NewValidator(
506+
sf,
507+
protocol.NewGenericValidator(sf, accountutil.AccountState),
508+
)),
501509
)
502510
exeProtocol := NewProtocol(dao.GetBlockHash)
503511
require.NoError(exeProtocol.Register(registry))
504-
bc.Validator().AddActionEnvelopeValidators(protocol.NewGenericValidator(sf, accountutil.AccountState))
505512
require.NoError(bc.Start(ctx))
506513
require.NotNil(bc)
507514
defer func() {

action/protocol/generic_validator.go

+27-13
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ package protocol
88

99
import (
1010
"context"
11-
"sync"
1211

12+
"github.com/iotexproject/iotex-address/address"
1313
"github.com/pkg/errors"
1414

1515
"github.com/iotexproject/iotex-core/action"
@@ -21,7 +21,6 @@ type (
2121
AccountState func(StateReader, string) (*state.Account, error)
2222
// GenericValidator is the validator for generic action verification
2323
GenericValidator struct {
24-
mu sync.RWMutex
2524
accountState AccountState
2625
sr StateReader
2726
}
@@ -36,26 +35,41 @@ func NewGenericValidator(sr StateReader, accountState AccountState) *GenericVali
3635
}
3736

3837
// Validate validates a generic action
39-
func (v *GenericValidator) Validate(ctx context.Context, act action.SealedEnvelope) error {
40-
actionCtx := MustGetActionCtx(ctx)
41-
// Reject action with insufficient gas limit
42-
intrinsicGas, err := act.IntrinsicGas()
43-
if intrinsicGas > act.GasLimit() || err != nil {
44-
return errors.Wrap(action.ErrInsufficientBalanceForGas, "insufficient gas")
45-
}
38+
func (v *GenericValidator) Validate(ctx context.Context, selp action.SealedEnvelope) error {
4639
// Verify action using action sender's public key
47-
if err := action.Verify(act); err != nil {
40+
if err := action.Verify(selp); err != nil {
4841
return errors.Wrap(err, "failed to verify action signature")
4942
}
43+
caller, err := address.FromBytes(selp.SrcPubkey().Hash())
44+
if err != nil {
45+
return err
46+
}
5047
// Reject action if nonce is too low
51-
confirmedState, err := v.accountState(v.sr, actionCtx.Caller.String())
48+
confirmedState, err := v.accountState(v.sr, caller.String())
5249
if err != nil {
53-
return errors.Wrapf(err, "invalid state of account %s", actionCtx.Caller.String())
50+
return errors.Wrapf(err, "invalid state of account %s", caller.String())
5451
}
5552

5653
pendingNonce := confirmedState.Nonce + 1
57-
if act.Nonce() > 0 && pendingNonce > act.Nonce() {
54+
if selp.Nonce() > 0 && pendingNonce > selp.Nonce() {
5855
return errors.Wrap(action.ErrNonce, "nonce is too low")
5956
}
57+
intrinsicGas, err := selp.IntrinsicGas()
58+
if err != nil {
59+
return err
60+
}
61+
ctx = WithActionCtx(ctx, ActionCtx{
62+
Caller: caller,
63+
ActionHash: selp.Hash(),
64+
GasPrice: selp.GasPrice(),
65+
IntrinsicGas: intrinsicGas,
66+
Nonce: selp.Nonce(),
67+
})
68+
bcCtx := MustGetBlockchainCtx(ctx)
69+
for _, validator := range bcCtx.Registry.All() {
70+
if err := validator.Validate(ctx, selp.Action()); err != nil {
71+
return err
72+
}
73+
}
6074
return nil
6175
}

action/protocol/generic_validator_test.go

+58-22
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,25 @@ import (
1414
"testing"
1515
"time"
1616

17+
"github.com/golang/mock/gomock"
18+
"github.com/iotexproject/iotex-address/address"
1719
"github.com/pkg/errors"
1820
"github.com/stretchr/testify/require"
1921

20-
"github.com/iotexproject/iotex-address/address"
2122
"github.com/iotexproject/iotex-core/action"
2223
"github.com/iotexproject/iotex-core/config"
2324
"github.com/iotexproject/iotex-core/state"
2425
"github.com/iotexproject/iotex-core/test/identityset"
2526
)
2627

27-
func TestActionProto(t *testing.T) {
28+
func TestActionProtoAndGenericValidator(t *testing.T) {
2829
require := require.New(t)
30+
ctrl := gomock.NewController(t)
31+
defer ctrl.Finish()
32+
33+
reg := NewRegistry()
34+
mp := NewMockProtocol(ctrl)
35+
require.NoError(reg.Register("1", mp))
2936
caller, err := address.FromString("io1mflp9m6hcgm2qcghchsdqj3z3eccrnekx9p0ms")
3037
require.NoError(err)
3138
producer, err := address.FromString("io1emxf8zzqckhgjde6dqd97ts0y3q496gm3fdrl6")
@@ -49,18 +56,21 @@ func TestActionProto(t *testing.T) {
4956
Hash: config.Default.Genesis.Hash(),
5057
Timestamp: time.Unix(config.Default.Genesis.Timestamp, 0),
5158
},
59+
Registry: reg,
5260
})
5361

5462
valid := NewGenericValidator(nil, func(sr StateReader, addr string) (*state.Account, error) {
55-
if strings.EqualFold("io1emxf8zzqckhgjde6dqd97ts0y3q496gm3fdrl6", addr) {
63+
pk := identityset.PrivateKey(27).PublicKey()
64+
eAddr, _ := address.FromBytes(pk.Hash())
65+
if strings.EqualFold(eAddr.String(), addr) {
5666
return nil, errors.New("MockChainManager nonce error")
5767
}
5868
return &state.Account{Nonce: 2}, nil
5969
})
6070
data, err := hex.DecodeString("")
6171
require.NoError(err)
62-
// Case I: Normal
63-
{
72+
t.Run("normal", func(t *testing.T) {
73+
mp.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(nil).Times(1)
6474
v, err := action.NewExecution("", 0, big.NewInt(10), uint64(10), big.NewInt(10), data)
6575
require.NoError(err)
6676
bd := &action.EnvelopeBuilder{}
@@ -72,9 +82,8 @@ func TestActionProto(t *testing.T) {
7282
nselp := action.SealedEnvelope{}
7383
require.NoError(nselp.LoadProto(selp.Proto()))
7484
require.NoError(valid.Validate(ctx, nselp))
75-
}
76-
// Case II: GasLimit lower
77-
{
85+
})
86+
t.Run("Gas limit low", func(t *testing.T) {
7887
v, err := action.NewExecution("", 0, big.NewInt(10), uint64(10), big.NewInt(10), data)
7988
require.NoError(err)
8089
bd := &action.EnvelopeBuilder{}
@@ -88,31 +97,23 @@ func TestActionProto(t *testing.T) {
8897
err = valid.Validate(ctx, nselp)
8998
require.Error(err)
9099
require.True(strings.Contains(err.Error(), "insufficient gas"))
91-
}
92-
// Case III: Call cm Nonce err
93-
{
94-
caller, err := address.FromString("io1emxf8zzqckhgjde6dqd97ts0y3q496gm3fdrl6")
95-
require.NoError(err)
96-
ctx := WithActionCtx(ctx,
97-
ActionCtx{
98-
Caller: caller,
99-
})
100+
})
101+
t.Run("state error", func(t *testing.T) {
100102
v, err := action.NewExecution("", 0, big.NewInt(10), uint64(10), big.NewInt(10), data)
101103
require.NoError(err)
102104
bd := &action.EnvelopeBuilder{}
103105
elp := bd.SetGasPrice(big.NewInt(10)).
104106
SetGasLimit(uint64(100000)).
105107
SetAction(v).Build()
106-
selp, err := action.Sign(elp, identityset.PrivateKey(28))
108+
selp, err := action.Sign(elp, identityset.PrivateKey(27))
107109
require.NoError(err)
108110
nselp := action.SealedEnvelope{}
109111
require.NoError(nselp.LoadProto(selp.Proto()))
110112
err = valid.Validate(ctx, nselp)
111113
require.Error(err)
112114
require.True(strings.Contains(err.Error(), "invalid state of account"))
113-
}
114-
// Case IV: Call Nonce err
115-
{
115+
})
116+
t.Run("nonce too low", func(t *testing.T) {
116117
v, err := action.NewExecution("", 1, big.NewInt(10), uint64(10), big.NewInt(10), data)
117118
require.NoError(err)
118119
bd := &action.EnvelopeBuilder{}
@@ -127,5 +128,40 @@ func TestActionProto(t *testing.T) {
127128
err = valid.Validate(ctx, nselp)
128129
require.Error(err)
129130
require.True(strings.Contains(err.Error(), "nonce is too low"))
130-
}
131+
})
132+
t.Run("wrong recipient", func(t *testing.T) {
133+
v, err := action.NewTransfer(1, big.NewInt(1), "io1qyqsyqcyq5narhapakcsrhksfajfcpl24us3xp38zwvsep", []byte{}, uint64(100000), big.NewInt(10))
134+
require.NoError(err)
135+
bd := &action.EnvelopeBuilder{}
136+
elp := bd.SetAction(v).SetGasLimit(100000).
137+
SetGasPrice(big.NewInt(10)).
138+
SetNonce(1).Build()
139+
selp, err := action.Sign(elp, identityset.PrivateKey(27))
140+
require.NoError(err)
141+
require.Error(valid.Validate(ctx, selp))
142+
})
143+
t.Run("wrong signature", func(t *testing.T) {
144+
unsignedTsf, err := action.NewTransfer(uint64(1), big.NewInt(1), caller.String(), []byte{}, uint64(100000), big.NewInt(0))
145+
require.NoError(err)
146+
147+
bd := &action.EnvelopeBuilder{}
148+
elp := bd.SetNonce(1).
149+
SetAction(unsignedTsf).
150+
SetGasLimit(100000).Build()
151+
selp := action.FakeSeal(elp, identityset.PrivateKey(27).PublicKey())
152+
require.True(strings.Contains(valid.Validate(ctx, selp).Error(), "failed to verify action signature"))
153+
})
154+
t.Run("protocol validation failed", func(t *testing.T) {
155+
me := errors.New("mock error")
156+
mp.EXPECT().Validate(gomock.Any(), gomock.Any()).Return(me).Times(1)
157+
v, err := action.NewExecution("", 0, big.NewInt(10), uint64(10), big.NewInt(10), data)
158+
require.NoError(err)
159+
bd := &action.EnvelopeBuilder{}
160+
elp := bd.SetGasPrice(big.NewInt(10)).
161+
SetGasLimit(uint64(100000)).
162+
SetAction(v).Build()
163+
selp, err := action.Sign(elp, identityset.PrivateKey(28))
164+
require.NoError(err)
165+
require.Equal(me, errors.Cause(valid.Validate(ctx, selp)))
166+
})
131167
}

action/protocol/protocol.go

-5
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ type ActionValidator interface {
5353
Validate(context.Context, action.Action) error
5454
}
5555

56-
// ActionEnvelopeValidator is the interface of validating an SealedEnvelope action
57-
type ActionEnvelopeValidator interface {
58-
Validate(context.Context, action.SealedEnvelope) error
59-
}
60-
6156
// ActionHandler is the interface for the action handlers. For each incoming action, the assembled actions will be
6257
// called one by one to process it. ActionHandler implementation is supposed to parse the sub-type of the action to
6358
// decide if it wants to handle this action or not.

action/sealedenvelopevalidator.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) 2019 IoTeX Foundation
2+
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
3+
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
4+
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
5+
// License 2.0 that can be found in the LICENSE file.
6+
7+
package action
8+
9+
import (
10+
"context"
11+
)
12+
13+
// SealedEnvelopeValidator is the interface of validating an SealedEnvelope action
14+
type SealedEnvelopeValidator interface {
15+
// Validate returns an error if any validation failed
16+
Validate(context.Context, SealedEnvelope) error
17+
}

0 commit comments

Comments
 (0)