diff --git a/core/handlers/library/registry.go b/core/handlers/library/registry.go index 3ee953e4995..911654c912c 100644 --- a/core/handlers/library/registry.go +++ b/core/handlers/library/registry.go @@ -17,6 +17,7 @@ import ( "github.com/hyperledger/fabric/core/handlers/auth" "github.com/hyperledger/fabric/core/handlers/decoration" endorsement2 "github.com/hyperledger/fabric/core/handlers/endorsement/api" + "github.com/hyperledger/fabric/core/handlers/validation/api" ) var logger = flogging.MustGetLogger("core/handlers") @@ -40,16 +41,18 @@ const ( // passed to the chaincode Decoration Endorsement + Validation - authPluginFactory = "NewFilter" - decoratorPluginFactory = "NewDecorator" - endorsementPluginFactory = "NewPluginFactory" + authPluginFactory = "NewFilter" + decoratorPluginFactory = "NewDecorator" + pluginFactory = "NewPluginFactory" ) type registry struct { filters []auth.Filter decorators []decoration.Decorator endorsers map[string]endorsement2.PluginFactory + validators map[string]validation.PluginFactory } var once sync.Once @@ -61,6 +64,7 @@ type Config struct { AuthFilters []*HandlerConfig `mapstructure:"authFilters" yaml:"authFilters"` Decorators []*HandlerConfig `mapstructure:"decorators" yaml:"decorators"` Endorsers PluginMapping `mapstructure:"endorsers" yaml:"endorsers"` + Validators PluginMapping `mapstructure:"validators" yaml:"validators"` } type PluginMapping map[string]*HandlerConfig @@ -75,7 +79,10 @@ type HandlerConfig struct { // of the registry func InitRegistry(c Config) Registry { once.Do(func() { - reg = registry{endorsers: make(map[string]endorsement2.PluginFactory)} + reg = registry{ + endorsers: make(map[string]endorsement2.PluginFactory), + validators: make(map[string]validation.PluginFactory), + } reg.loadHandlers(c) }) return ® @@ -93,6 +100,10 @@ func (r *registry) loadHandlers(c Config) { for chaincodeID, config := range c.Endorsers { r.evaluateModeAndLoad(config, Endorsement, chaincodeID) } + + for chaincodeID, config := range c.Validators { + r.evaluateModeAndLoad(config, Validation, chaincodeID) + } } // evaluateModeAndLoad if a library path is provided, load the shared object @@ -124,6 +135,11 @@ func (r *registry) loadCompiled(handlerFactory string, handlerType HandlerType, logger.Panicf("expected 1 argument in extraArgs") } r.endorsers[extraArgs[0]] = inst.(endorsement2.PluginFactory) + } else if handlerType == Validation { + if len(extraArgs) != 1 { + logger.Panicf("expected 1 argument in extraArgs") + } + r.validators[extraArgs[0]] = inst.(validation.PluginFactory) } } @@ -143,6 +159,8 @@ func (r *registry) loadPlugin(pluginPath string, handlerType HandlerType, extraA r.initDecoratorPlugin(p) } else if handlerType == Endorsement { r.initEndorsementPlugin(p, extraArgs...) + } else if handlerType == Validation { + r.initValidationPlugin(p, extraArgs...) } } @@ -183,14 +201,14 @@ func (r *registry) initEndorsementPlugin(p *plugin.Plugin, extraArgs ...string) if len(extraArgs) != 1 { logger.Panicf("expected 1 argument in extraArgs") } - factorySymbol, err := p.Lookup(endorsementPluginFactory) + factorySymbol, err := p.Lookup(pluginFactory) if err != nil { - panicWithLookupError(endorsementPluginFactory, err) + panicWithLookupError(pluginFactory, err) } constructor, ok := factorySymbol.(func() endorsement2.PluginFactory) if !ok { - panicWithDefinitionError(endorsementPluginFactory) + panicWithDefinitionError(pluginFactory) } factory := constructor() if factory == nil { @@ -199,6 +217,26 @@ func (r *registry) initEndorsementPlugin(p *plugin.Plugin, extraArgs ...string) r.endorsers[extraArgs[0]] = factory } +func (r *registry) initValidationPlugin(p *plugin.Plugin, extraArgs ...string) { + if len(extraArgs) != 1 { + logger.Panicf("expected 1 argument in extraArgs") + } + factorySymbol, err := p.Lookup(pluginFactory) + if err != nil { + panicWithLookupError(pluginFactory, err) + } + + constructor, ok := factorySymbol.(func() validation.PluginFactory) + if !ok { + panicWithDefinitionError(pluginFactory) + } + factory := constructor() + if factory == nil { + logger.Panicf("factory instance returned nil") + } + r.validators[extraArgs[0]] = factory +} + // panicWithLookupError panics when a handler constructor lookup fails func panicWithLookupError(factory string, err error) { logger.Panicf(fmt.Sprintf("Plugin must contain constructor with name %s. Error from lookup: %s", @@ -221,6 +259,8 @@ func (r *registry) Lookup(handlerType HandlerType) interface{} { return r.decorators } else if handlerType == Endorsement { return r.endorsers + } else if handlerType == Validation { + return r.validators } return nil diff --git a/core/handlers/library/registry_plugin_test.go b/core/handlers/library/registry_plugin_test.go index 0f31d4a1b87..b599b577640 100644 --- a/core/handlers/library/registry_plugin_test.go +++ b/core/handlers/library/registry_plugin_test.go @@ -20,6 +20,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric/core/handlers/endorsement/api" + "github.com/hyperledger/fabric/core/handlers/validation/api" "github.com/hyperledger/fabric/protos/peer" "github.com/stretchr/testify/assert" ) @@ -28,6 +29,7 @@ const ( authPluginPackage = "github.com/hyperledger/fabric/core/handlers/auth/plugin" decoratorPluginPackage = "github.com/hyperledger/fabric/core/handlers/decoration/plugin" endorsementTestPlugin = "github.com/hyperledger/fabric/core/handlers/endorsement/testdata/" + validationTestPlugin = "github.com/hyperledger/fabric/core/handlers/validation/testdata/" ) func TestLoadAuthPlugin(t *testing.T) { @@ -99,6 +101,30 @@ func TestEndorsementPlugin(t *testing.T) { assert.Equal(t, []byte{1, 2, 3}, output) } +func TestValidationPlugin(t *testing.T) { + testDir, err := ioutil.TempDir("", "") + assert.NoError(t, err, "Could not create temp directory for plugins") + defer os.Remove(testDir) + + pluginPath := strings.Join([]string{testDir, "/", "validationplugin.so"}, "") + + cmd := exec.Command("go", "build", "-o", pluginPath, "-buildmode=plugin", + validationTestPlugin) + output, err := cmd.CombinedOutput() + assert.NoError(t, err, "Could not build plugin: "+string(output)) + + testReg := registry{validators: make(map[string]validation.PluginFactory)} + testReg.loadPlugin(pluginPath, Validation, "vscc") + mapping := testReg.Lookup(Validation).(map[string]validation.PluginFactory) + factory := mapping["vscc"] + assert.NotNil(t, factory) + instance := factory.New() + assert.NotNil(t, instance) + assert.NoError(t, instance.Init()) + err = instance.Validate(nil, "", 0, 0) + assert.NoError(t, err) +} + func TestLoadPluginInvalidPath(t *testing.T) { defer func() { if r := recover(); r == nil { diff --git a/core/handlers/validation/testdata/noop_validator.go b/core/handlers/validation/testdata/noop_validator.go new file mode 100644 index 00000000000..2f710029956 --- /dev/null +++ b/core/handlers/validation/testdata/noop_validator.go @@ -0,0 +1,41 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "github.com/hyperledger/fabric/core/handlers/validation/api" + "github.com/hyperledger/fabric/protos/common" +) + +// NoOpValidator is used to test validation plugin infrastructure +type NoOpValidator struct { +} + +// Validate valides the transactions with the given data +func (*NoOpValidator) Validate(_ *common.Block, _ string, _ int, _ int, _ ...validation.ContextDatum) error { + return nil +} + +// Init initializes the plugin with the given dependencies +func (*NoOpValidator) Init(dependencies ...validation.Dependency) error { + return nil +} + +// NoOpValidatorFactory creates new NoOpValidators +type NoOpValidatorFactory struct { +} + +// New returns an instance of a NoOpValidator +func (*NoOpValidatorFactory) New() validation.Plugin { + return &NoOpValidator{} +} + +// NewPluginFactory is called by the validation plugin framework to obtain an instance +// of the factory +func NewPluginFactory() validation.PluginFactory { + return &NoOpValidatorFactory{} +}