Skip to content

Commit 71847ce

Browse files
committedSep 24, 2017
[FAB-6218] Validate block before pulling private data
This commit makes block validation happen before the private data is fetched from the transient store or from peers, and also removes the validation from the preCommit() step of the committer. Change-Id: Ic6ad105293a942e43c0697b4573a2b652b2c933d Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 900850f commit 71847ce

File tree

8 files changed

+183
-31
lines changed

8 files changed

+183
-31
lines changed
 

‎core/committer/committer_impl.go

+6-14
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"fmt"
2121

2222
"github.com/hyperledger/fabric/common/flogging"
23-
"github.com/hyperledger/fabric/core/committer/txvalidator"
2423
"github.com/hyperledger/fabric/core/ledger"
2524
"github.com/hyperledger/fabric/events/producer"
2625
"github.com/hyperledger/fabric/protos/common"
@@ -43,9 +42,8 @@ func init() {
4342
// it keeps the reference to the ledger to commit blocks and retrieve
4443
// chain information
4544
type LedgerCommitter struct {
46-
ledger ledger.PeerLedger
47-
validator txvalidator.Validator
48-
eventer ConfigBlockEventer
45+
ledger ledger.PeerLedger
46+
eventer ConfigBlockEventer
4947
}
5048

5149
// ConfigBlockEventer callback function proto type to define action
@@ -54,15 +52,15 @@ type ConfigBlockEventer func(block *common.Block) error
5452

5553
// NewLedgerCommitter is a factory function to create an instance of the committer
5654
// which passes incoming blocks via validation and commits them into the ledger.
57-
func NewLedgerCommitter(ledger ledger.PeerLedger, validator txvalidator.Validator) *LedgerCommitter {
58-
return NewLedgerCommitterReactive(ledger, validator, func(_ *common.Block) error { return nil })
55+
func NewLedgerCommitter(ledger ledger.PeerLedger) *LedgerCommitter {
56+
return NewLedgerCommitterReactive(ledger, func(_ *common.Block) error { return nil })
5957
}
6058

6159
// NewLedgerCommitterReactive is a factory function to create an instance of the committer
6260
// same as way as NewLedgerCommitter, while also provides an option to specify callback to
6361
// be called upon new configuration block arrival and commit event
64-
func NewLedgerCommitterReactive(ledger ledger.PeerLedger, validator txvalidator.Validator, eventer ConfigBlockEventer) *LedgerCommitter {
65-
return &LedgerCommitter{ledger: ledger, validator: validator, eventer: eventer}
62+
func NewLedgerCommitterReactive(ledger ledger.PeerLedger, eventer ConfigBlockEventer) *LedgerCommitter {
63+
return &LedgerCommitter{ledger: ledger, eventer: eventer}
6664
}
6765

6866
// Commit commits block to into the ledger
@@ -88,12 +86,6 @@ func (lc *LedgerCommitter) Commit(block *common.Block) error {
8886
// preCommit takes care to validate the block and update based on its
8987
// content
9088
func (lc *LedgerCommitter) preCommit(block *common.Block) error {
91-
// Validate and mark invalid transactions
92-
logger.Debug("Validating block")
93-
if err := lc.validator.Validate(block); err != nil {
94-
return err
95-
}
96-
9789
// Updating CSCC with new configuration block
9890
if utils.IsConfigBlock(block) {
9991
logger.Debug("Received configuration update, calling CSCC ConfigUpdate")

‎core/committer/committer_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/hyperledger/fabric/common/tools/configtxgen/provisional"
2727
"github.com/hyperledger/fabric/common/util"
2828
"github.com/hyperledger/fabric/core/ledger/ledgermgmt"
29-
"github.com/hyperledger/fabric/core/mocks/validator"
3029
"github.com/hyperledger/fabric/protos/common"
3130
"github.com/spf13/viper"
3231
"github.com/stretchr/testify/assert"
@@ -42,7 +41,7 @@ func TestKVLedgerBlockStorage(t *testing.T) {
4241
assert.NoError(t, err, "Error while creating ledger: %s", err)
4342
defer ledger.Close()
4443

45-
committer := NewLedgerCommitter(ledger, &validator.MockValidator{})
44+
committer := NewLedgerCommitter(ledger)
4645
height, err := committer.LedgerHeight()
4746
assert.Equal(t, uint64(1), height)
4847
assert.NoError(t, err)
@@ -92,7 +91,7 @@ func TestNewLedgerCommitterReactive(t *testing.T) {
9291
defer ledger.Close()
9392

9493
var configArrived int32
95-
committer := NewLedgerCommitterReactive(ledger, &validator.MockValidator{}, func(_ *common.Block) error {
94+
committer := NewLedgerCommitterReactive(ledger, func(_ *common.Block) error {
9695
atomic.AddInt32(&configArrived, 1)
9796
return nil
9897
})

‎core/peer/peer.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ func createChain(cid string, ledger ledger.PeerLedger, cb *common.Block) error {
281281
cs.Resources = bundleSource
282282
cs.bundleSource = bundleSource
283283

284-
c := committer.NewLedgerCommitterReactive(ledger, txvalidator.NewTxValidator(cs), func(block *common.Block) error {
284+
validator := txvalidator.NewTxValidator(cs)
285+
c := committer.NewLedgerCommitterReactive(ledger, func(block *common.Block) error {
285286
chainID, err := utils.GetChainIDFromBlock(block)
286287
if err != nil {
287288
return err
@@ -300,6 +301,7 @@ func createChain(cid string, ledger ledger.PeerLedger, cb *common.Block) error {
300301
return errors.Wrapf(err, "Failed opening transient store for %s", bundle.ConfigtxManager().ChainID())
301302
}
302303
service.GetGossipService().InitializeChannel(bundle.ConfigtxManager().ChainID(), ordererAddresses, service.Support{
304+
Validator: validator,
303305
Committer: c,
304306
Store: store,
305307
Pp: &noopPolicyParser{},

‎gossip/privdata/coordinator.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import (
1212

1313
util2 "github.com/hyperledger/fabric/common/util"
1414
"github.com/hyperledger/fabric/core/committer"
15+
"github.com/hyperledger/fabric/core/committer/txvalidator"
1516
"github.com/hyperledger/fabric/core/ledger"
1617
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
1718
"github.com/hyperledger/fabric/core/transientstore"
1819
"github.com/hyperledger/fabric/gossip/util"
1920
"github.com/hyperledger/fabric/protos/common"
2021
gossip2 "github.com/hyperledger/fabric/protos/gossip"
2122
"github.com/hyperledger/fabric/protos/ledger/rwset"
23+
"github.com/hyperledger/fabric/protos/peer"
2224
"github.com/hyperledger/fabric/protos/utils"
2325
"github.com/op/go-logging"
2426
"github.com/pkg/errors"
@@ -71,14 +73,15 @@ type fetcher interface {
7173
}
7274

7375
type coordinator struct {
76+
txvalidator.Validator
7477
committer.Committer
7578
TransientStore
7679
gossipFetcher fetcher
7780
}
7881

7982
// NewCoordinator creates a new instance of coordinator
80-
func NewCoordinator(committer committer.Committer, store TransientStore, gossipFetcher fetcher) Coordinator {
81-
return &coordinator{Committer: committer, TransientStore: store, gossipFetcher: gossipFetcher}
83+
func NewCoordinator(committer committer.Committer, store TransientStore, gossipFetcher fetcher, validator txvalidator.Validator) Coordinator {
84+
return &coordinator{Committer: committer, TransientStore: store, gossipFetcher: gossipFetcher, Validator: validator}
8285
}
8386

8487
// StorePvtData used to persist private date into transient store
@@ -94,6 +97,13 @@ func (c *coordinator) StoreBlock(block *common.Block, privateDataSets util.PvtDa
9497
if block.Header == nil {
9598
return errors.New("Block header is nil")
9699
}
100+
101+
logger.Debug("Validating block", block.Header.Number)
102+
err := c.Validator.Validate(block)
103+
if err != nil {
104+
return errors.WithMessage(err, "Validation failed")
105+
}
106+
97107
blockAndPvtData := &ledger.BlockAndPvtData{
98108
Block: block,
99109
BlockPvtData: make(map[uint64]*ledger.TxPvtData),
@@ -387,10 +397,21 @@ func (k *rwSetKey) toTxPvtReadWriteSet(rws []byte) *rwset.TxPvtReadWriteSet {
387397
}
388398

389399
func (c *coordinator) listMissingPrivateData(block *common.Block, ownedRWsets map[rwSetKey][]byte) (rwSetKeysByTxIDs, error) {
400+
if block.Metadata == nil || len(block.Metadata.Metadata) <= int(common.BlockMetadataIndex_TRANSACTIONS_FILTER) {
401+
return nil, errors.New("Block.Metadata is nil or Block.Metadata lacks a Tx filter bitmap")
402+
}
403+
txsFilter := txValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
404+
if len(txsFilter) != len(block.Data.Data) {
405+
return nil, errors.Errorf("Block data size(%d) is different from Tx filter size(%d)", len(block.Data.Data), len(txsFilter))
406+
}
407+
390408
privateRWsetsInBlock := make(map[rwSetKey]struct{})
391409
missing := make(rwSetKeysByTxIDs)
392-
393410
for seqInBlock, envBytes := range block.Data.Data {
411+
if txsFilter[seqInBlock] != uint8(peer.TxValidationCode_VALID) {
412+
logger.Debug("Skipping Tx", seqInBlock, "because it's invalid. Status is", txsFilter[seqInBlock])
413+
continue
414+
}
394415
env, err := utils.GetEnvelopeFromBlock(envBytes)
395416
if err != nil {
396417
return nil, err

‎gossip/privdata/coordinator_test.go

+92-3
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ func (mock *committerMock) Close() {
159159
mock.Called()
160160
}
161161

162+
type validatorMock struct {
163+
err error
164+
}
165+
166+
func (v *validatorMock) Validate(block *common.Block) error {
167+
if v.err != nil {
168+
return v.err
169+
}
170+
return nil
171+
}
172+
162173
type digests []*proto.PvtDataDigest
163174

164175
func (d digests) Equal(other digests) bool {
@@ -425,6 +436,84 @@ var expectedCommittedPrivateData2 = map[uint64]*ledger.TxPvtData{
425436
}},
426437
}
427438

439+
func TestCoordinatorStoreInvalidBlock(t *testing.T) {
440+
hash := util2.ComputeSHA256([]byte("rws-pre-image"))
441+
committer := &committerMock{}
442+
committer.On("CommitWithPvtData", mock.Anything).Run(func(args mock.Arguments) {
443+
t.Fatal("Shouldn't have committed")
444+
}).Return(nil)
445+
store := &mockTransientStore{t: t}
446+
fetcher := &fetcherMock{t: t}
447+
pdFactory := &pvtDataFactory{}
448+
bf := &blockFactory{
449+
channelID: "test",
450+
}
451+
452+
block := bf.withoutMetadata().create()
453+
// Scenario I: Block we got doesn't have any metadata with it
454+
pvtData := pdFactory.create()
455+
coordinator := NewCoordinator(committer, store, fetcher, &validatorMock{})
456+
err := coordinator.StoreBlock(block, pvtData)
457+
assert.Error(t, err)
458+
assert.Contains(t, err.Error(), "Block.Metadata is nil or Block.Metadata lacks a Tx filter bitmap")
459+
460+
// Scenario II: Validator has an error while validating the block
461+
block = bf.create()
462+
pvtData = pdFactory.create()
463+
coordinator = NewCoordinator(committer, store, fetcher, &validatorMock{fmt.Errorf("failed validating block")})
464+
err = coordinator.StoreBlock(block, pvtData)
465+
assert.Error(t, err)
466+
assert.Contains(t, err.Error(), "failed validating block")
467+
468+
// Scenario III: Block we got contains an inadequate length of Tx filter in the metadata
469+
block = bf.withMetadataSize(100).create()
470+
pvtData = pdFactory.create()
471+
coordinator = NewCoordinator(committer, store, fetcher, &validatorMock{})
472+
err = coordinator.StoreBlock(block, pvtData)
473+
assert.Error(t, err)
474+
assert.Contains(t, err.Error(), "Block data size")
475+
assert.Contains(t, err.Error(), "is different from Tx filter size")
476+
477+
// Scenario IV: The second transaction in the block we got is invalid, and we have no private data for that.
478+
// If the coordinator would try to fetch private data, the test would fall because we haven't defined the
479+
// mock operations for the transientstore (or for gossip) in this test.
480+
var commitHappened bool
481+
assertCommitHappened := func() {
482+
assert.True(t, commitHappened)
483+
commitHappened = false
484+
}
485+
committer = &committerMock{}
486+
committer.On("CommitWithPvtData", mock.Anything).Run(func(args mock.Arguments) {
487+
var privateDataPassed2Ledger privateData = args.Get(0).(*ledger.BlockAndPvtData).BlockPvtData
488+
commitHappened = true
489+
// Only the first transaction's private data is passed to the ledger
490+
assert.Len(t, privateDataPassed2Ledger, 1)
491+
assert.Equal(t, 0, int(privateDataPassed2Ledger[0].SeqInBlock))
492+
// The private data passed to the ledger contains "ns1" and has 2 collections in it
493+
assert.Len(t, privateDataPassed2Ledger[0].WriteSet.NsPvtRwset, 1)
494+
assert.Equal(t, "ns1", privateDataPassed2Ledger[0].WriteSet.NsPvtRwset[0].Namespace)
495+
assert.Len(t, privateDataPassed2Ledger[0].WriteSet.NsPvtRwset[0].CollectionPvtRwset, 2)
496+
}).Return(nil)
497+
block = bf.withInvalidTxns(1).AddTxn("tx1", "ns1", hash, "c1", "c2").AddTxn("tx2", "ns2", hash, "c1").create()
498+
pvtData = pdFactory.addRWSet().addNSRWSet("ns1", "c1", "c2").create()
499+
coordinator = NewCoordinator(committer, store, fetcher, &validatorMock{})
500+
err = coordinator.StoreBlock(block, pvtData)
501+
assert.NoError(t, err)
502+
assertCommitHappened()
503+
504+
// Scenario V: Block doesn't contain a header
505+
block.Header = nil
506+
err = coordinator.StoreBlock(block, pvtData)
507+
assert.Error(t, err)
508+
assert.Contains(t, err.Error(), "Block header is nil")
509+
510+
// Scenario V: Block doesn't contain Data
511+
block.Data = nil
512+
err = coordinator.StoreBlock(block, pvtData)
513+
assert.Error(t, err)
514+
assert.Contains(t, err.Error(), "Block data is empty")
515+
}
516+
428517
func TestCoordinatorStoreBlock(t *testing.T) {
429518
// Green path test, all private data should be obtained successfully
430519
var commitHappened bool
@@ -452,7 +541,7 @@ func TestCoordinatorStoreBlock(t *testing.T) {
452541
// If the coordinator tries fetching from the transientstore, or peers it would result in panic,
453542
// because we didn't define yet the "On(...)" invocation of the transient store or other peers.
454543
pvtData := pdFactory.addRWSet().addNSRWSet("ns1", "c1", "c2").addRWSet().addNSRWSet("ns2", "c1").create()
455-
coordinator := NewCoordinator(committer, store, fetcher)
544+
coordinator := NewCoordinator(committer, store, fetcher, &validatorMock{})
456545
err := coordinator.StoreBlock(block, pvtData)
457546
assert.NoError(t, err)
458547
assertCommitHappened()
@@ -551,7 +640,7 @@ func TestCoordinatorStoreBlock(t *testing.T) {
551640
assert.True(t, privateDataPassed2Ledger.Equal(expectedCommittedPrivateData2))
552641
commitHappened = true
553642
}).Return(nil)
554-
coordinator = NewCoordinator(committer, store, fetcher)
643+
coordinator = NewCoordinator(committer, store, fetcher, &validatorMock{})
555644
err = coordinator.StoreBlock(block, nil)
556645
assert.NoError(t, err)
557646
assertCommitHappened()
@@ -561,7 +650,7 @@ func TestCoordinatorGetBlocks(t *testing.T) {
561650
committer := &committerMock{}
562651
store := &mockTransientStore{t: t}
563652
fetcher := &fetcherMock{t: t}
564-
coordinator := NewCoordinator(committer, store, fetcher)
653+
coordinator := NewCoordinator(committer, store, fetcher, &validatorMock{})
565654

566655
// Bad path: block is not returned
567656
committer.On("GetBlocks", mock.Anything).Return([]*common.Block{})

‎gossip/privdata/util.go

+45-4
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ import (
1616
"github.com/hyperledger/fabric/protos/peer"
1717
)
1818

19+
type txValidationFlags []uint8
20+
1921
type blockFactory struct {
20-
channelID string
21-
transactions [][]byte
22+
channelID string
23+
transactions [][]byte
24+
metadataSize int
25+
lacksMetadata bool
26+
invalidTxns map[int]struct{}
2227
}
2328

2429
func (bf *blockFactory) AddTxn(txID string, nsName string, hash []byte, collections ...string) *blockFactory {
@@ -91,16 +96,52 @@ func (bf *blockFactory) AddTxn(txID string, nsName string, hash []byte, collecti
9196

9297
func (bf *blockFactory) create() *common.Block {
9398
defer func() {
94-
bf.transactions = nil
99+
*bf = blockFactory{}
95100
}()
96-
return &common.Block{
101+
block := &common.Block{
97102
Header: &common.BlockHeader{
98103
Number: 1,
99104
},
100105
Data: &common.BlockData{
101106
Data: bf.transactions,
102107
},
103108
}
109+
110+
if bf.lacksMetadata {
111+
return block
112+
}
113+
block.Metadata = &common.BlockMetadata{
114+
Metadata: make([][]byte, common.BlockMetadataIndex_TRANSACTIONS_FILTER+1),
115+
}
116+
if bf.metadataSize > 0 {
117+
block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = make([]uint8, bf.metadataSize)
118+
} else {
119+
block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = make([]uint8, len(block.Data.Data))
120+
}
121+
122+
for txSeqInBlock := range bf.invalidTxns {
123+
block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER][txSeqInBlock] = uint8(peer.TxValidationCode_INVALID_ENDORSER_TRANSACTION)
124+
}
125+
126+
return block
127+
}
128+
129+
func (bf *blockFactory) withoutMetadata() *blockFactory {
130+
bf.lacksMetadata = true
131+
return bf
132+
}
133+
134+
func (bf *blockFactory) withMetadataSize(mdSize int) *blockFactory {
135+
bf.metadataSize = mdSize
136+
return bf
137+
}
138+
139+
func (bf *blockFactory) withInvalidTxns(sequences ...int) *blockFactory {
140+
bf.invalidTxns = make(map[int]struct{})
141+
for _, seq := range sequences {
142+
bf.invalidTxns[seq] = struct{}{}
143+
}
144+
return bf
104145
}
105146

106147
func sampleNsRwSet(ns string, hash []byte, collections ...string) *rwsetutil.NsRwSet {

‎gossip/service/gossip_service.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hyperledger/fabric/protos/ledger/rwset"
1313

1414
"github.com/hyperledger/fabric/core/committer"
15+
"github.com/hyperledger/fabric/core/committer/txvalidator"
1516
"github.com/hyperledger/fabric/core/common/privdata"
1617
"github.com/hyperledger/fabric/core/deliverservice"
1718
"github.com/hyperledger/fabric/core/deliverservice/blocksprovider"
@@ -201,6 +202,7 @@ func (g *gossipServiceImpl) NewConfigEventer() ConfigProcessor {
201202
}
202203

203204
type Support struct {
205+
Validator txvalidator.Validator
204206
Committer committer.Committer
205207
Store privdata2.TransientStore
206208
Pp privdata.PolicyParser
@@ -215,7 +217,7 @@ func (g *gossipServiceImpl) InitializeChannel(chainID string, endpoints []string
215217
logger.Debug("Creating state provider for chainID", chainID)
216218
servicesAdapter := &state.ServicesMediator{GossipAdapter: g, MCSAdapter: g.mcs}
217219
fetcher := privdata2.NewPuller(support.Ps, support.Pp, g.gossipSvc, NewDataRetriever(support.Store), chainID)
218-
coordinator := privdata2.NewCoordinator(support.Committer, support.Store, fetcher)
220+
coordinator := privdata2.NewCoordinator(support.Committer, support.Store, fetcher, support.Validator)
219221
g.privateHandlers[chainID] = privateHandler{
220222
support: support,
221223
coordinator: coordinator,

‎gossip/state/state_test.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ func newGossipInstance(config *gossip.Config, mcs api.MessageCryptoService) goss
246246
func newCommitter(id int) committer.Committer {
247247
cb, _ := test.MakeGenesisBlock(strconv.Itoa(id))
248248
ledger, _ := ledgermgmt.CreateLedger(cb)
249-
return committer.NewLedgerCommitter(ledger, &validator.MockValidator{})
249+
return committer.NewLedgerCommitter(ledger)
250250
}
251251

252252
// Constructing pseudo peer node, simulating only gossip and state transfer part
@@ -264,7 +264,8 @@ func newPeerNodeWithGossip(config *gossip.Config, committer committer.Committer,
264264
// basic parts
265265

266266
servicesAdapater := &ServicesMediator{GossipAdapter: g, MCSAdapter: cs}
267-
sp := NewGossipStateProvider(util.GetTestChainID(), servicesAdapater, privdata.NewCoordinator(committer, &mockTransientStore{}, nil))
267+
coord := privdata.NewCoordinator(committer, &mockTransientStore{}, nil, &validator.MockValidator{})
268+
sp := NewGossipStateProvider(util.GetTestChainID(), servicesAdapater, coord)
268269
if sp == nil {
269270
return nil
270271
}
@@ -573,6 +574,11 @@ func TestGossipReception(t *testing.T) {
573574
Data: &pcomm.BlockData{
574575
Data: [][]byte{},
575576
},
577+
Metadata: &pcomm.BlockMetadata{
578+
Metadata: [][]byte{
579+
{}, {}, {}, {},
580+
},
581+
},
576582
}
577583
b, _ := pb.Marshal(rawblock)
578584

0 commit comments

Comments
 (0)
Please sign in to comment.