Skip to content

Commit 0cf4c35

Browse files
committed
[FAB-5353]: Qualify sys. failure vs validation error
Currently as stated in [FAB-5353], there is no clear separation during transaction validation during block commmit, between invalid transaction and some system failure which migh lead to inability to validate the transaction. For example db is down or file system is unavailable. This might lead to inconsistency of the state accross peers, therefore this commit takes care to distinguish between real case of invalid transaction versus system failure, later the error propagated down to the committer and forces peer to stop with panic, so admin will be able to take manual control and fix the problem therefore preventing peer state to diverge. Change-Id: I384e16d37e2f2b0fe144d504f566e0b744a5095c Signed-off-by: Artem Barger <[email protected]>
1 parent 3a4b1f2 commit 0cf4c35

File tree

3 files changed

+214
-14
lines changed

3 files changed

+214
-14
lines changed

core/committer/txvalidator/validator.go

+65-13
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,40 @@ type txValidator struct {
8686
vscc vsccValidator
8787
}
8888

89+
// VSCCInfoLookupFailureError error to indicate inability
90+
// to obtain VSCC information from LCCC
91+
type VSCCInfoLookupFailureError struct {
92+
reason string
93+
}
94+
95+
// Error returns reasons which lead to the failure
96+
func (e VSCCInfoLookupFailureError) Error() string {
97+
return e.reason
98+
}
99+
100+
// VSCCEndorsementPolicyError error to mark transaction
101+
// failed endrosement policy check
102+
type VSCCEndorsementPolicyError struct {
103+
reason string
104+
}
105+
106+
// Error returns reasons which lead to the failure
107+
func (e VSCCEndorsementPolicyError) Error() string {
108+
return e.reason
109+
}
110+
111+
// VSCCExecutionFailureError error to indicate
112+
// failure during attempt of executing VSCC
113+
// endorsement policy check
114+
type VSCCExecutionFailureError struct {
115+
reason string
116+
}
117+
118+
// Error returns reasons which lead to the failure
119+
func (e VSCCExecutionFailureError) Error() string {
120+
return e.reason
121+
}
122+
89123
var logger *logging.Logger // package-level logger
90124

91125
func init() {
@@ -170,8 +204,15 @@ func (v *txValidator) Validate(block *common.Block) error {
170204
if err != nil {
171205
txID := txID
172206
logger.Errorf("VSCCValidateTx for transaction txId = %s returned error %s", txID, err)
173-
txsfltr.SetFlag(tIdx, cde)
174-
continue
207+
switch err.(type) {
208+
case *VSCCExecutionFailureError:
209+
return err
210+
case *VSCCInfoLookupFailureError:
211+
return err
212+
default:
213+
txsfltr.SetFlag(tIdx, cde)
214+
continue
215+
}
175216
}
176217

177218
invokeCC, upgradeCC, err := v.getTxCCInstance(payload)
@@ -370,7 +411,8 @@ func (v *vsccValidatorImpl) GetInfoForValidate(txid, chID, ccID string) (*sysccp
370411
// obtain name of the VSCC and the policy from LSCC
371412
cd, err := v.getCDataForCC(ccID)
372413
if err != nil {
373-
logger.Errorf("Unable to get chaincode data from ledger for txid %s, due to %s", txid, err)
414+
msg := fmt.Sprintf("Unable to get chaincode data from ledger for txid %s, due to %s", txid, err)
415+
logger.Errorf(msg)
374416
return nil, nil, nil, err
375417
}
376418
cc.ChaincodeName = cd.Name
@@ -514,8 +556,12 @@ func (v *vsccValidatorImpl) VSCCValidateTx(payload *common.Payload, envBytes []b
514556

515557
// do VSCC validation
516558
if err = v.VSCCValidateTxForCC(envBytes, chdr.TxId, chdr.ChannelId, vscc.ChaincodeName, vscc.ChaincodeVersion, policy); err != nil {
517-
return fmt.Errorf("VSCCValidateTxForCC failed for cc %s, error %s", ccID, err),
518-
peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE
559+
switch err.(type) {
560+
case VSCCEndorsementPolicyError:
561+
return err, peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE
562+
default:
563+
return err, peer.TxValidationCode_INVALID_OTHER_REASON
564+
}
519565
}
520566
}
521567
} else {
@@ -541,8 +587,12 @@ func (v *vsccValidatorImpl) VSCCValidateTx(payload *common.Payload, envBytes []b
541587
// user creates a new system chaincode which is invokable from the outside
542588
// they have to modify VSCC to provide appropriate validation
543589
if err = v.VSCCValidateTxForCC(envBytes, chdr.TxId, vscc.ChainID, vscc.ChaincodeName, vscc.ChaincodeVersion, policy); err != nil {
544-
return fmt.Errorf("VSCCValidateTxForCC failed for cc %s, error %s", ccID, err),
545-
peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE
590+
switch err.(type) {
591+
case VSCCEndorsementPolicyError:
592+
return err, peer.TxValidationCode_ENDORSEMENT_POLICY_FAILURE
593+
default:
594+
return err, peer.TxValidationCode_INVALID_OTHER_REASON
595+
}
546596
}
547597
}
548598

@@ -552,8 +602,9 @@ func (v *vsccValidatorImpl) VSCCValidateTx(payload *common.Payload, envBytes []b
552602
func (v *vsccValidatorImpl) VSCCValidateTxForCC(envBytes []byte, txid, chid, vsccName, vsccVer string, policy []byte) error {
553603
ctxt, err := v.ccprovider.GetContext(v.support.Ledger())
554604
if err != nil {
555-
logger.Errorf("Cannot obtain context for txid=%s, err %s", txid, err)
556-
return err
605+
msg := fmt.Sprintf("Cannot obtain context for txid=%s, err %s", txid, err)
606+
logger.Errorf(msg)
607+
return &VSCCExecutionFailureError{msg}
557608
}
558609
defer v.ccprovider.ReleaseContext()
559610

@@ -571,12 +622,13 @@ func (v *vsccValidatorImpl) VSCCValidateTxForCC(envBytes []byte, txid, chid, vsc
571622
logger.Debug("Invoking VSCC txid", txid, "chaindID", chid)
572623
res, _, err := v.ccprovider.ExecuteChaincode(ctxt, cccid, args)
573624
if err != nil {
574-
logger.Errorf("Invoke VSCC failed for transaction txid=%s, error %s", txid, err)
575-
return err
625+
msg := fmt.Sprintf("Invoke VSCC failed for transaction txid=%s, error %s", txid, err)
626+
logger.Errorf(msg)
627+
return &VSCCExecutionFailureError{msg}
576628
}
577629
if res.Status != shim.OK {
578630
logger.Errorf("VSCC check failed for transaction txid=%s, error %s", txid, res.Message)
579-
return fmt.Errorf("%s", res.Message)
631+
return &VSCCEndorsementPolicyError{fmt.Sprintf("%s", res.Message)}
580632
}
581633

582634
return nil
@@ -596,7 +648,7 @@ func (v *vsccValidatorImpl) getCDataForCC(ccid string) (*ccprovider.ChaincodeDat
596648

597649
bytes, err := qe.GetState("lscc", ccid)
598650
if err != nil {
599-
return nil, fmt.Errorf("Could not retrieve state for chaincode %s, error %s", ccid, err)
651+
return nil, &VSCCInfoLookupFailureError{fmt.Sprintf("Could not retrieve state for chaincode %s, error %s", ccid, err)}
600652
}
601653

602654
if bytes == nil {

core/committer/txvalidator/validator_test.go

+146
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ limitations under the License.
1717
package txvalidator
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"os"
2223
"testing"
2324

2425
"github.com/hyperledger/fabric/common/cauthdsl"
2526
ctxt "github.com/hyperledger/fabric/common/configtx/test"
27+
ledger2 "github.com/hyperledger/fabric/common/ledger"
2628
"github.com/hyperledger/fabric/common/ledger/testutil"
2729
"github.com/hyperledger/fabric/common/mocks/scc"
2830
"github.com/hyperledger/fabric/common/util"
@@ -41,6 +43,7 @@ import (
4143
"github.com/hyperledger/fabric/protos/utils"
4244
"github.com/spf13/viper"
4345
"github.com/stretchr/testify/assert"
46+
"github.com/stretchr/testify/mock"
4447
)
4548

4649
func signedByAnyMember(ids []string) []byte {
@@ -385,6 +388,149 @@ func TestInvokeNoBlock(t *testing.T) {
385388
assert.NoError(t, err)
386389
}
387390

391+
// mockLedger structure used to test ledger
392+
// failure, therefore leveraging mocking
393+
// library as need to simulate ledger which not
394+
// able to get access to state db
395+
type mockLedger struct {
396+
mock.Mock
397+
}
398+
399+
// GetTransactionByID returns transaction by ud
400+
func (m *mockLedger) GetTransactionByID(txID string) (*peer.ProcessedTransaction, error) {
401+
args := m.Called(txID)
402+
return args.Get(0).(*peer.ProcessedTransaction), args.Error(1)
403+
}
404+
405+
// GetBlockByHash returns block using its hash value
406+
func (m *mockLedger) GetBlockByHash(blockHash []byte) (*common.Block, error) {
407+
args := m.Called(blockHash)
408+
return args.Get(0).(*common.Block), nil
409+
}
410+
411+
// GetBlockByTxID given transaction id return block transaction was committed with
412+
func (m *mockLedger) GetBlockByTxID(txID string) (*common.Block, error) {
413+
args := m.Called(txID)
414+
return args.Get(0).(*common.Block), nil
415+
}
416+
417+
// GetTxValidationCodeByTxID returns validation code of give tx
418+
func (m *mockLedger) GetTxValidationCodeByTxID(txID string) (peer.TxValidationCode, error) {
419+
args := m.Called(txID)
420+
return args.Get(0).(peer.TxValidationCode), nil
421+
}
422+
423+
// NewTxSimulator creates new transaction simulator
424+
func (m *mockLedger) NewTxSimulator() (ledger.TxSimulator, error) {
425+
args := m.Called()
426+
return args.Get(0).(ledger.TxSimulator), nil
427+
}
428+
429+
// NewQueryExecutor creates query executor
430+
func (m *mockLedger) NewQueryExecutor() (ledger.QueryExecutor, error) {
431+
args := m.Called()
432+
return args.Get(0).(ledger.QueryExecutor), nil
433+
}
434+
435+
// NewHistoryQueryExecutor history query executor
436+
func (m *mockLedger) NewHistoryQueryExecutor() (ledger.HistoryQueryExecutor, error) {
437+
args := m.Called()
438+
return args.Get(0).(ledger.HistoryQueryExecutor), nil
439+
}
440+
441+
// Prune prune using policy
442+
func (m *mockLedger) Prune(policy ledger2.PrunePolicy) error {
443+
return nil
444+
}
445+
446+
func (m *mockLedger) GetBlockchainInfo() (*common.BlockchainInfo, error) {
447+
args := m.Called()
448+
return args.Get(0).(*common.BlockchainInfo), nil
449+
}
450+
451+
func (m *mockLedger) GetBlockByNumber(blockNumber uint64) (*common.Block, error) {
452+
args := m.Called(blockNumber)
453+
return args.Get(0).(*common.Block), nil
454+
}
455+
456+
func (m *mockLedger) GetBlocksIterator(startBlockNumber uint64) (ledger2.ResultsIterator, error) {
457+
args := m.Called(startBlockNumber)
458+
return args.Get(0).(ledger2.ResultsIterator), nil
459+
}
460+
461+
func (m *mockLedger) Close() {
462+
463+
}
464+
465+
func (m *mockLedger) Commit(block *common.Block) error {
466+
return nil
467+
}
468+
469+
// mockQueryExecutor mock of the query executor,
470+
// needed to simulate inability to access state db, e.g.
471+
// the case where due to db failure it's not possible to
472+
// query for state, for example if we would like to query
473+
// the lccc for VSCC info and db is not avaible we expect
474+
// to stop validating block and fail commit procedure with
475+
// an error.
476+
type mockQueryExecutor struct {
477+
mock.Mock
478+
}
479+
480+
func (exec *mockQueryExecutor) GetState(namespace string, key string) ([]byte, error) {
481+
args := exec.Called(namespace, key)
482+
return args.Get(0).([]byte), args.Error(1)
483+
}
484+
485+
func (exec *mockQueryExecutor) GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error) {
486+
args := exec.Called(namespace, keys)
487+
return args.Get(0).([][]byte), args.Error(1)
488+
}
489+
490+
func (exec *mockQueryExecutor) GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ledger2.ResultsIterator, error) {
491+
args := exec.Called(namespace, startKey, endKey)
492+
return args.Get(0).(ledger2.ResultsIterator), args.Error(1)
493+
}
494+
495+
func (exec *mockQueryExecutor) ExecuteQuery(namespace, query string) (ledger2.ResultsIterator, error) {
496+
args := exec.Called(namespace)
497+
return args.Get(0).(ledger2.ResultsIterator), args.Error(1)
498+
}
499+
500+
func (exec *mockQueryExecutor) Done() {
501+
}
502+
503+
// TestLedgerIsNoAvailable simulates and provides a test for following scenario,
504+
// which is based on FAB-535. Test checks the validation path which expects that
505+
// DB won't available while trying to lookup for VSCC from LCCC and therefore
506+
// transaction validation will have to fail. In such case the outcome should be
507+
// the error return from validate block method and proccessing of transactions
508+
// has to stop. There is suppose to be clear indication of the failure with error
509+
// returned from the function call.
510+
func TestLedgerIsNoAvailable(t *testing.T) {
511+
theLedger := new(mockLedger)
512+
validator := NewTxValidator(&mockSupport{l: theLedger})
513+
514+
ccID := "mycc"
515+
tx := getEnv(ccID, createRWset(t, ccID), t)
516+
517+
theLedger.On("GetTransactionByID", mock.Anything).Return(&peer.ProcessedTransaction{}, errors.New("Cannot find the transaction"))
518+
519+
queryExecutor := new(mockQueryExecutor)
520+
queryExecutor.On("GetState", mock.Anything, mock.Anything).Return([]byte{}, errors.New("Unable to connect to DB"))
521+
theLedger.On("NewQueryExecutor", mock.Anything).Return(queryExecutor, nil)
522+
523+
b := &common.Block{Data: &common.BlockData{Data: [][]byte{utils.MarshalOrPanic(tx)}}}
524+
525+
err := validator.Validate(b)
526+
527+
assertion := assert.New(t)
528+
// We suppose to get the error which indicates we cannot commit the block
529+
assertion.Error(err)
530+
// The error exptected to be of type VSCCInfoLookupFailureError
531+
assertion.NotNil(err.(*VSCCInfoLookupFailureError))
532+
}
533+
388534
var signer msp.SigningIdentity
389535
var signerSerialized []byte
390536

gossip/state/state.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,9 @@ func (s *GossipStateProviderImpl) deliverPayloads() {
428428
continue
429429
}
430430
logger.Debug("New block with claimed sequence number ", payload.SeqNum, " transactions num ", len(rawBlock.Data.Data))
431-
s.commitBlock(rawBlock)
431+
if err := s.commitBlock(rawBlock); err != nil {
432+
logger.Panicf("Cannot commit block to the ledger due to %s", err)
433+
}
432434
}
433435
case <-s.stopCh:
434436
s.stopCh <- struct{}{}

0 commit comments

Comments
 (0)