Skip to content

Commit

Permalink
[FAB-6719] Allow system chaincode plugins
Browse files Browse the repository at this point in the history
Change-Id: I2db25bdfd8ebffbcb22c3d7374364f6d2d8a6fd6
Signed-off-by: Divyank Katira <[email protected]>
  • Loading branch information
d1vyank committed Oct 26, 2017
1 parent aa69bc7 commit 81641a9
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 2 deletions.
11 changes: 11 additions & 0 deletions core/scc/buildtags_experimental_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// +build experimental

/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package scc

var goBuildTags = "experimental"
11 changes: 11 additions & 0 deletions core/scc/buildtags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// +build !experimental

/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package scc

var goBuildTags = ""
2 changes: 2 additions & 0 deletions core/scc/importsysccs.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ var systemChaincodes = []*SystemChaincode{
//RegisterSysCCs is the hook for system chaincodes where system chaincodes are registered with the fabric
//note the chaincode must still be deployed and launched like a user chaincode will be
func RegisterSysCCs() {
systemChaincodes = append(systemChaincodes, loadSysCCs()...)

var aclProvider aclmgmt.ACLProvider
for _, sysCC := range systemChaincodes {
if reg, _ := registerSysCC(sysCC); reg {
Expand Down
90 changes: 90 additions & 0 deletions core/scc/loadsysccs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package scc

import (
"fmt"
"os"
"plugin"
"sync"

"github.com/hyperledger/fabric/common/viperutil"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/pkg/errors"
)

const (
sccFactoryMethod = "New"
)

// PluginConfig SCC plugin configuration
type PluginConfig struct {
Enabled bool `mapstructure:"enabled" yaml:"enabled"`
Name string `mapstructure:"name" yaml:"name"`
Path string `mapstructure:"path" yaml:"path"`
InvokableExternal bool `mapstructure:"invokableExternal" yaml:"invokableExternal"`
InvokableCC2CC bool `mapstructure:"invokableCC2CC" yaml:"invokableCC2CC"`
}

var once sync.Once
var sccPlugins []*SystemChaincode

// loadSysCCs reads system chaincode plugin configuration and loads them
func loadSysCCs() []*SystemChaincode {
once.Do(func() {
var config []*PluginConfig
err := viperutil.EnhancedExactUnmarshalKey("chaincode.systemPlugins", &config)
if err != nil {
panic(errors.WithMessage(err, "could not load YAML config"))
}
loadSysCCsWithConfig(config)
})
return sccPlugins
}

func loadSysCCsWithConfig(configs []*PluginConfig) {
for _, conf := range configs {
plugin := loadPlugin(conf.Path)
chaincode := &SystemChaincode{
Enabled: conf.Enabled,
Name: conf.Name,
Path: conf.Path,
Chaincode: *plugin,
InvokableExternal: conf.InvokableExternal,
InvokableCC2CC: conf.InvokableCC2CC,
}
sccPlugins = append(sccPlugins, chaincode)
sysccLogger.Infof("Successfully loaded SCC %s from path %s", chaincode.Name, chaincode.Path)
}
}

func loadPlugin(path string) *shim.Chaincode {
if _, err := os.Stat(path); err != nil {
panic(fmt.Errorf("Could not find plugin at path %s: %s", path, err))
}

p, err := plugin.Open(path)
if err != nil {
panic(fmt.Errorf("Error opening plugin at path %s: %s", path, err))
}

sccFactorySymbol, err := p.Lookup(sccFactoryMethod)
if err != nil {
panic(fmt.Errorf(
"Could not find symbol %s. Plugin must export this method", sccFactoryMethod))
}

sccFactory, ok := sccFactorySymbol.(func() shim.Chaincode)
if !ok {
panic(fmt.Errorf("Function %s does not match expected definition func() shim.Chaincode",
sccFactoryMethod))
}

scc := sccFactory()

return &scc
}
65 changes: 65 additions & 0 deletions core/scc/loadsysccs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// +build go1.9,linux,cgo
// +build !ppc64le

/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package scc

import (
"bytes"
"fmt"
"os"
"os/exec"
"testing"

"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)

const (
examplePluginPackage = "github.com/hyperledger/fabric/examples/plugins/scc"
pluginName = "testscc"
)

var pluginPath = os.TempDir() + "/scc-plugin.so"

func TestLoadSCCPlugin(t *testing.T) {
buildExamplePlugin(pluginPath, examplePluginPackage)
defer os.Remove(pluginPath)

testConfig := fmt.Sprintf(`
chaincode:
systemPlugins:
- enabled: true
name: %s
path: %s
invokableExternal: true
invokableCC2CC: true
`, pluginName, pluginPath)
viper.SetConfigType("yaml")
viper.ReadConfig(bytes.NewBuffer([]byte(testConfig)))

sccs := loadSysCCs()
assert.Len(t, sccs, 1, "expected one SCC to be loaded")
resp := sccs[0].Chaincode.Invoke(nil)
assert.Equal(t, int32(shim.OK), resp.Status, "expected success response from scc")
}

func TestLoadSCCPluginInvalid(t *testing.T) {
assert.Panics(t, func() { loadPlugin("/invalid/path.so") },
"expected panic with invalid path")
}

func buildExamplePlugin(path, pluginPackage string) {
cmd := exec.Command("go", "build", "-tags", goBuildTags, "-o", path, "-buildmode=plugin",
pluginPackage)
output, err := cmd.CombinedOutput()
if err != nil {
panic(fmt.Errorf("Error: %s, Could not build plugin: %s", err, string(output)))
}
}
3 changes: 2 additions & 1 deletion core/scc/scc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func init() {
viper.Set("chaincode.system", map[string]string{"lscc": "enable", "a": "enable"})
viper.Set("peer.fileSystemPath", os.TempDir())
ccprovider.RegisterChaincodeProviderFactory(&ccprovider2.MockCcProviderFactory{})
RegisterSysCCs()
}

func TestDeploy(t *testing.T) {
Expand Down Expand Up @@ -103,6 +102,8 @@ func TestMockRegisterAndResetSysCCs(t *testing.T) {
}

func TestRegisterSysCC(t *testing.T) {
assert.NotPanics(t, func() { RegisterSysCCs() }, "expected successful init")

_, err := registerSysCC(&SystemChaincode{
Name: "lscc",
Path: "path",
Expand Down
31 changes: 31 additions & 0 deletions examples/plugins/scc/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)

// New returns an implementation of the chaincode interface
func New() shim.Chaincode {
return &scc{}
}

type scc struct{}

// Init implements the chaincode shim interface
func (s *scc) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}

// Invoke implements the chaincode shim interface
func (s *scc) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}

func main() {}
16 changes: 15 additions & 1 deletion sampleconfig/core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,20 @@ chaincode:
qscc: enable
rscc: disable

# System chaincode plugins: in addition to being imported and compiled
# into fabric through core/chaincode/importsysccs.go, system chaincodes
# can also be loaded as shared objects compiled as Go plugins.
# See examples/plugins/scc for an example.
# Like regular system chaincodes, plugins must also be white listed in the
# chaincode.system section above.
systemPlugins:
# example configuration:
# - enabled: true
# name: myscc
# path: /opt/lib/myscc.so
# invokableExternal: true
# invokableCC2CC: true

# Logging section for the chaincode container
logging:
# Default level for all loggers within the chaincode container
Expand Down Expand Up @@ -534,4 +548,4 @@ metrics:
promReporter:

# prometheus http server listen address for pull metrics
listenAddress: 0.0.0.0:8080
listenAddress: 0.0.0.0:8080

0 comments on commit 81641a9

Please sign in to comment.