Skip to content

Commit adf7475

Browse files
committed
[FAB-7224] Enhance custom tx processor
This CR enhances custom transaction processor in ledger such that the processor is able to distinguish whether it is being invoked during ledger initialization or for on-line tx processing. In the former situation, the processor needs to just rely on the states stored previously in the ledger and in the latter it can assume that the peer is up and running and hence other peer structures can be used. Other than this, in the initialization situation only valid transactions are passed to the processor which makes the custom validation in the processor optional. Change-Id: Idff54ab124dd588dfa5172a5b882e521217ca62a Signed-off-by: manish <manish.sethi@gmail.com>
1 parent 3974f15 commit adf7475

File tree

15 files changed

+159
-66
lines changed

15 files changed

+159
-66
lines changed

core/aclmgmt/aclmgmt.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ var aclMgmtCfgTxProcessor = &AclMgmtConfigTxProcessor{}
7575

7676
//GenerateSimulationResults this is just a proxy to delegate registered aclProvider.
7777
//Need this as aclmgmt is initialized with ledger initialization as required by ledger
78-
func (*AclMgmtConfigTxProcessor) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator) error {
78+
func (*AclMgmtConfigTxProcessor) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator, initializingLedger bool) error {
7979
configtxLock.RLock()
8080
defer configtxLock.RUnlock()
8181

8282
//this should not be nil (aclProvider is initialized at the outset to either
8383
//rscc or default)
8484
if aclProvider != nil {
85-
return aclProvider.GenerateSimulationResults(txEnvelop, simulator)
85+
return aclProvider.GenerateSimulationResults(txEnvelop, simulator, initializingLedger)
8686
}
8787

8888
return fmt.Errorf("warning! call to handle config tx before setting ACL provider")

core/aclmgmt/aclmgmt_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ func registerACLProvider() *mocks.MockACLProvider {
3535

3636
func TestACLProcessor(t *testing.T) {
3737
reinit()
38-
assert.NotNil(t, GetConfigTxProcessor().GenerateSimulationResults(nil, nil), "Expected non-nil error")
38+
assert.NotNil(t, GetConfigTxProcessor().GenerateSimulationResults(nil, nil, false), "Expected non-nil error")
3939
RegisterACLProvider(nil)
40-
assert.Nil(t, GetConfigTxProcessor().GenerateSimulationResults(nil, nil), "Expected nil error")
40+
assert.Nil(t, GetConfigTxProcessor().GenerateSimulationResults(nil, nil, false), "Expected nil error")
4141
}
4242

4343
func TestPanicOnUnregistered(t *testing.T) {

core/aclmgmt/aclmgmtimpl.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ func newACLMgmt(r ACLProvider) ACLProvider {
5151
return &aclMgmtImpl{aclOverrides: make(map[string]aclMethod)}
5252
}
5353

54-
func (am *aclMgmtImpl) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator) error {
54+
func (am *aclMgmtImpl) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator, initializingLedger bool) error {
5555
if rscc == nil {
5656
panic("-----RegisterACLProvider not called ----")
5757
}
5858

59-
return rscc.GenerateSimulationResults(txEnvelop, simulator)
59+
return rscc.GenerateSimulationResults(txEnvelop, simulator, initializingLedger)
6060
}

core/aclmgmt/defaultaclprovider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,6 @@ func (d *defaultACLProvider) CheckACL(resName string, channelID string, idinfo i
124124

125125
//GenerateSimulationResults does nothing for default provider currently as it defaults to
126126
//1.0 behavior
127-
func (d *defaultACLProvider) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator) error {
127+
func (d *defaultACLProvider) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator, initializingLedger bool) error {
128128
return nil
129129
}

core/aclmgmt/mocks/mocks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (m *MockACLProvider) CheckACL(resName string, channelID string, idinfo inte
3030
return args.Error(0)
3131
}
3232

33-
func (m *MockACLProvider) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator) error {
33+
func (m *MockACLProvider) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator, initializingLedger bool) error {
3434
return nil
3535
}
3636

core/ledger/customtx/custom_tx_processor.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package customtx
99
import (
1010
"sync"
1111

12-
"github.com/hyperledger/fabric/core/ledger"
1312
"github.com/hyperledger/fabric/protos/common"
1413
)
1514

@@ -19,15 +18,6 @@ var once sync.Once
1918
// Processors maintains the association between a custom transaction type to its corresponding tx processor
2019
type Processors map[common.HeaderType]Processor
2120

22-
// Processor allows to generate simulation results during commit time for custom transactions.
23-
// A custom processor may represent the information in a propriety fashion and can use this process to translate
24-
// the information into the form of `TxSimulationResults`. Because, the original information is signed in a
25-
// custom representation, an implementation of a `Processor` should be cautious that the custom representation
26-
// is used for simulation in an deterministic fashion and should take care of compatibility cross fabric versions.
27-
type Processor interface {
28-
GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator) error
29-
}
30-
3121
// Initialize sets the custom processors. This function is expected to be invoked only during ledgermgmt.Initialize() function.
3222
func Initialize(customTxProcessors Processors) {
3323
once.Do(func() {

core/ledger/customtx/interface.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package customtx
8+
9+
import (
10+
"github.com/hyperledger/fabric/core/ledger"
11+
"github.com/hyperledger/fabric/protos/common"
12+
)
13+
14+
// InvalidTxError is expected to be thrown by a custom transaction processor (an implementation of interface `Processor`)
15+
// if it wants the ledger to record a particular transaction as invalid
16+
type InvalidTxError struct {
17+
Msg string
18+
}
19+
20+
func (e *InvalidTxError) Error() string {
21+
return e.Msg
22+
}
23+
24+
// Processor allows to generate simulation results during commit time for custom transactions.
25+
// A custom processor may represent the information in a propriety fashion and can use this process to translate
26+
// the information into the form of `TxSimulationResults`. Because, the original information is signed in a
27+
// custom representation, an implementation of a `Processor` should be cautious that the custom representation
28+
// is used for simulation in an deterministic fashion and should take care of compatibility cross fabric versions.
29+
// 'initializingLedger' true indicates that either the transaction being processed is from the genesis block or the ledger is
30+
// synching the state (which could happen during peer startup if the statedb is found to be lagging behind the blockchain).
31+
// In the former case, the transactions processed are expected to be valid and in the latter case, only valid transactions
32+
// are reprocessed and hence any validation can be skipped.
33+
type Processor interface {
34+
GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator, initializingLedger bool) error
35+
}

core/ledger/customtx/test_export.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package customtx
8+
9+
// InitializeTestEnv initializes custom tx processors for test
10+
func InitializeTestEnv(processors Processors) {
11+
initialize(processors)
12+
}

core/ledger/kvledger/custom_processor_test.go

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,35 @@ package kvledger
99
import (
1010
"testing"
1111

12+
"github.com/golang/protobuf/proto"
13+
"github.com/hyperledger/fabric/protos/ledger/rwset/kvrwset"
14+
"github.com/hyperledger/fabric/protos/peer"
15+
1216
"github.com/hyperledger/fabric/common/ledger/testutil"
17+
lgrutil "github.com/hyperledger/fabric/core/ledger/util"
18+
1319
"github.com/hyperledger/fabric/core/ledger"
1420
"github.com/hyperledger/fabric/core/ledger/customtx"
1521
"github.com/hyperledger/fabric/protos/common"
22+
"github.com/hyperledger/fabric/protos/utils"
1623
"github.com/stretchr/testify/assert"
1724
)
1825

1926
type customTxProcessor struct {
20-
namespace string
21-
initData map[string][]byte
2227
}
2328

24-
func (ctp *customTxProcessor) GenerateSimulationResults(
25-
txEnvelop *common.Envelope, simulator ledger.TxSimulator) error {
26-
for k, v := range ctp.initData {
27-
simulator.SetState(ctp.namespace, k, []byte(v))
29+
func (ctp *customTxProcessor) GenerateSimulationResults(txEnvelop *common.Envelope, simulator ledger.TxSimulator, initializingLedger bool) error {
30+
payload := utils.UnmarshalPayloadOrPanic(txEnvelop.Payload)
31+
chHdr, _ := utils.UnmarshalChannelHeader(payload.Header.ChannelHeader)
32+
chainid := chHdr.ChannelId
33+
kvw := &kvrwset.KVWrite{}
34+
if err := proto.Unmarshal(payload.Data, kvw); err != nil {
35+
return err
2836
}
29-
return nil
37+
if len(kvw.Key) == 0 {
38+
return &customtx.InvalidTxError{Msg: "Nil key"}
39+
}
40+
return simulator.SetState(chainid, kvw.Key, kvw.Value)
3041
}
3142

3243
func TestCustomProcessor(t *testing.T) {
@@ -35,27 +46,59 @@ func TestCustomProcessor(t *testing.T) {
3546
provider, _ := NewProvider()
3647
defer provider.Close()
3748

38-
// create a custom tx processor and register it to handle 'common.HeaderType_CONFIG' type of transaction
39-
testNamespace := "TestNamespace"
40-
testKVs := map[string][]byte{"one": []byte("1"), "two": []byte("2")}
41-
customtx.Initialize(customtx.Processors{
42-
common.HeaderType_CONFIG: &customTxProcessor{
43-
testNamespace,
44-
testKVs,
45-
}})
49+
// create a custom tx processor and register it to handle '100 and 101' type of transaction
50+
chainid := "testLedger"
51+
customTxProcessor := &customTxProcessor{}
52+
customtx.InitializeTestEnv(customtx.Processors{
53+
100: customTxProcessor,
54+
101: customTxProcessor})
4655

4756
// Create a genesis block with a common.HeaderType_CONFIG transaction
48-
_, gb := testutil.NewBlockGenerator(t, "testLedger", false)
49-
ledger, err := provider.Create(gb)
50-
defer ledger.Close()
57+
_, gb := testutil.NewBlockGenerator(t, chainid, false)
58+
lgr, err := provider.Create(gb)
59+
defer lgr.Close()
5160
assert.NoError(t, err)
5261

62+
// commit a block with three custom trans
63+
tx1 := createCustomTx(t, 100, chainid, "custom_key1", "value1")
64+
tx2 := createCustomTx(t, 101, chainid, "custom_key2", "value2")
65+
tx3 := createCustomTx(t, 101, chainid, "", "")
66+
blk1 := testutil.NewBlock([]*common.Envelope{tx1, tx2, tx3}, 1, gb.Header.Hash())
67+
assert.NoError(t, lgr.CommitWithPvtData(&ledger.BlockAndPvtData{Block: blk1}))
5368
// verify that the state changes caused by the custom processor took place during ledger creation
54-
txSim, err := ledger.NewTxSimulator("testTxid")
69+
qe, err := lgr.NewQueryExecutor()
5570
assert.NoError(t, err)
56-
for k, v := range testKVs {
57-
value, err := txSim.GetState(testNamespace, k)
58-
assert.NoError(t, err)
59-
assert.Equal(t, v, value)
60-
}
71+
val, err := qe.GetState(chainid, "custom_key1")
72+
assert.NoError(t, err)
73+
assert.Equal(t, "value1", string(val))
74+
75+
val, err = qe.GetState(chainid, "custom_key2")
76+
assert.NoError(t, err)
77+
assert.Equal(t, "value2", string(val))
78+
qe.Done()
79+
80+
blockPersisted, err := lgr.GetBlockByNumber(1)
81+
assert.NoError(t, err)
82+
var txFilter lgrutil.TxValidationFlags
83+
txFilter = blockPersisted.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]
84+
assert.Equal(t, peer.TxValidationCode_VALID, txFilter.Flag(0))
85+
assert.Equal(t, peer.TxValidationCode_VALID, txFilter.Flag(1))
86+
assert.Equal(t, peer.TxValidationCode_INVALID_OTHER_REASON, txFilter.Flag(2))
87+
88+
tx4 := createCustomTx(t, 100, chainid, "custom_key4", "value4")
89+
blk2 := testutil.NewBlock([]*common.Envelope{tx4}, 2, blk1.Header.Hash())
90+
assert.NoError(t, lgr.CommitWithPvtData(&ledger.BlockAndPvtData{Block: blk2}))
91+
qe, err = lgr.NewQueryExecutor()
92+
assert.NoError(t, err)
93+
val, err = qe.GetState(chainid, "custom_key4")
94+
qe.Done()
95+
assert.NoError(t, err)
96+
assert.Equal(t, "value4", string(val))
97+
}
98+
99+
func createCustomTx(t *testing.T, txType common.HeaderType, chainid, key, val string) *common.Envelope {
100+
kvWrite := &kvrwset.KVWrite{Key: key, Value: []byte(val)}
101+
txEnv, err := utils.CreateSignedEnvelope(txType, chainid, nil, kvWrite, 0, 0)
102+
assert.NoError(t, err)
103+
return txEnv
61104
}

core/ledger/kvledger/txmgmt/validator/valimpl/default_impl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (impl *DefaultImpl) ValidateAndPrepareBatch(blockAndPvtdata *ledger.BlockAn
4444
var err error
4545

4646
logger.Debug("preprocessing ProtoBlock...")
47-
if internalBlock, err = preprocessProtoBlock(impl.txmgr, block); err != nil {
47+
if internalBlock, err = preprocessProtoBlock(impl.txmgr, block, doMVCCValidation); err != nil {
4848
return nil, err
4949
}
5050

core/ledger/kvledger/txmgmt/validator/valimpl/helper.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func validatePvtdata(tx *valinternal.Transaction, pvtdata *ledger.TxPvtData) err
8888

8989
// preprocessProtoBlock parses the proto instance of block into 'Block' structure.
9090
// The retuned 'Block' structure contains only transactions that are endorser transactions and are not alredy marked as invalid
91-
func preprocessProtoBlock(txmgr txmgr.TxMgr, block *common.Block) (*valinternal.Block, error) {
91+
func preprocessProtoBlock(txmgr txmgr.TxMgr, block *common.Block, doMVCCValidation bool) (*valinternal.Block, error) {
9292
b := &valinternal.Block{Num: block.Header.Number}
9393
// Committer validator has already set validation flags based on well formed tran checks
9494
txsFilter := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
@@ -135,7 +135,11 @@ func preprocessProtoBlock(txmgr txmgr.TxMgr, block *common.Block) (*valinternal.
135135
continue
136136
}
137137
} else {
138-
rwsetProto, err := processNonEndorserTx(env, chdr.TxId, txType, txmgr)
138+
rwsetProto, err := processNonEndorserTx(env, chdr.TxId, txType, txmgr, !doMVCCValidation)
139+
if _, ok := err.(*customtx.InvalidTxError); ok {
140+
txsFilter.SetFlag(txIndex, peer.TxValidationCode_INVALID_OTHER_REASON)
141+
continue
142+
}
139143
if err != nil {
140144
return nil, err
141145
}
@@ -152,22 +156,22 @@ func preprocessProtoBlock(txmgr txmgr.TxMgr, block *common.Block) (*valinternal.
152156
return b, nil
153157
}
154158

155-
func processNonEndorserTx(txEnv *common.Envelope, txid string, txType common.HeaderType, txmgr txmgr.TxMgr) (*rwset.TxReadWriteSet, error) {
159+
func processNonEndorserTx(txEnv *common.Envelope, txid string, txType common.HeaderType, txmgr txmgr.TxMgr, synchingState bool) (*rwset.TxReadWriteSet, error) {
156160
logger.Debugf("Performing custom processing for transaction [txid=%s], [txType=%s]", txid, txType)
157161
processor := customtx.GetProcessor(txType)
158-
logger.Debug("Processor for custom tx processing:%#v", processor)
162+
logger.Debugf("Processor for custom tx processing:%#v", processor)
159163
if processor == nil {
160164
return nil, nil
161165
}
162166

163167
var err error
164168
var sim ledger.TxSimulator
165169
var simRes *ledger.TxSimulationResults
166-
167170
if sim, err = txmgr.NewTxSimulator(txid); err != nil {
168171
return nil, err
169172
}
170-
if err = processor.GenerateSimulationResults(txEnv, sim); err != nil {
173+
defer sim.Done()
174+
if err = processor.GenerateSimulationResults(txEnv, sim, synchingState); err != nil {
171175
return nil, err
172176
}
173177
if simRes, err = sim.GetTxSimulationResults(); err != nil {

core/ledger/kvledger/txmgmt/validator/valimpl/helper_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@ func TestPreprocessProtoBlock(t *testing.T) {
2525
// good block
2626
//_, gb := testutil.NewBlockGenerator(t, "testLedger", false)
2727
gb := testutil.ConstructTestBlock(t, 10, 1, 1)
28-
_, err := preprocessProtoBlock(nil, gb)
28+
_, err := preprocessProtoBlock(nil, gb, false)
2929
assert.NoError(t, err)
3030
// bad envelope
3131
gb = testutil.ConstructTestBlock(t, 11, 1, 1)
3232
gb.Data = &common.BlockData{Data: [][]byte{[]byte{123}}}
3333
gb.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] =
3434
lutils.NewTxValidationFlags(len(gb.Data.Data))
35-
_, err = preprocessProtoBlock(nil, gb)
35+
_, err = preprocessProtoBlock(nil, gb, false)
3636
assert.Error(t, err)
3737
t.Log(err)
3838
// bad payload
3939
gb = testutil.ConstructTestBlock(t, 12, 1, 1)
4040
envBytes, _ := putils.GetBytesEnvelope(&common.Envelope{Payload: []byte{123}})
4141
gb.Data = &common.BlockData{Data: [][]byte{envBytes}}
42-
_, err = preprocessProtoBlock(nil, gb)
42+
_, err = preprocessProtoBlock(nil, gb, false)
4343
assert.Error(t, err)
4444
t.Log(err)
4545
// bad channel header
@@ -49,7 +49,7 @@ func TestPreprocessProtoBlock(t *testing.T) {
4949
})
5050
envBytes, _ = putils.GetBytesEnvelope(&common.Envelope{Payload: payloadBytes})
5151
gb.Data = &common.BlockData{Data: [][]byte{envBytes}}
52-
_, err = preprocessProtoBlock(nil, gb)
52+
_, err = preprocessProtoBlock(nil, gb, false)
5353
assert.Error(t, err)
5454
t.Log(err)
5555

@@ -63,7 +63,7 @@ func TestPreprocessProtoBlock(t *testing.T) {
6363
flags := lutils.NewTxValidationFlags(len(gb.Data.Data))
6464
flags.SetFlag(0, peer.TxValidationCode_BAD_CHANNEL_HEADER)
6565
gb.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = flags
66-
_, err = preprocessProtoBlock(nil, gb)
66+
_, err = preprocessProtoBlock(nil, gb, false)
6767
assert.NoError(t, err) // invalid filter should take precendence
6868

6969
// new block
@@ -77,7 +77,7 @@ func TestPreprocessProtoBlock(t *testing.T) {
7777
// set logging backend for test
7878
backend := logging.NewMemoryBackend(1)
7979
logging.SetBackend(backend)
80-
_, err = preprocessProtoBlock(nil, gb)
80+
_, err = preprocessProtoBlock(nil, gb, false)
8181
assert.NoError(t, err)
8282
expected := fmt.Sprintf("Channel [%s]: Block [%d] Transaction index [%d] TxId [%s]"+
8383
" marked as invalid by committer. Reason code [%s]",

core/ledger/ledgermgmt/ledger_mgmt_test_exports.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package ledgermgmt
1919
import (
2020
"os"
2121

22+
"github.com/hyperledger/fabric/core/ledger/customtx"
23+
2224
"github.com/hyperledger/fabric/core/ledger/ledgerconfig"
2325

2426
"fmt"
@@ -30,6 +32,13 @@ func InitializeTestEnv() {
3032
initialize(nil)
3133
}
3234

35+
// InitializeTestEnvWithCustomProcessors initializes ledgermgmt for tests with the supplied custom tx processors
36+
func InitializeTestEnvWithCustomProcessors(customTxProcessors customtx.Processors) {
37+
remove()
38+
customtx.InitializeTestEnv(customTxProcessors)
39+
initialize(customTxProcessors)
40+
}
41+
3342
// CleanupTestEnv closes the ledgermagmt and removes the store directory
3443
func CleanupTestEnv() {
3544
Close()

core/scc/rscc/rscc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (rscc *Rscc) CheckACL(resName string, channelID string, idinfo interface{})
117117

118118
//GenerateSimulationResults called to add config state. Currently only handles "join" requests.
119119
//Note that this is just a ledger hook and does not modify RSCC data structures
120-
func (rscc *Rscc) GenerateSimulationResults(txEnv *common.Envelope, sim ledger.TxSimulator) error {
120+
func (rscc *Rscc) GenerateSimulationResults(txEnv *common.Envelope, sim ledger.TxSimulator, initializingLedger bool) error {
121121
//should never happen, but check anyway
122122
if txEnv == nil || sim == nil {
123123
return fmt.Errorf("nil parameters")

0 commit comments

Comments
 (0)