Skip to content

Commit

Permalink
skeleton Endorser implemention with a CLI driver
Browse files Browse the repository at this point in the history
This patch is for https://jira.hyperledger.org/browse/FAB-181,
FAB-182.

The patch is around endorser.go which implements the basic
Endorser service. The "peer deploy ..." and "peer invoke .."
CLI driver commands have been modified to redirected to use
endorser.ProcessProposal instead of the original "devops"
calls.

The deploy, invoke (and query) CLI calls are unchanged in
non-dev mode from user point of view - but for one difference.
The response is a ProposalResponse.

In dev mode (ie, when peer is started with --peer-chaincodedev)
the deploy command would need to set "CORE_CHAINCODE_MODE=dev"
in the "peer chaincode deploy ..." command. This is because, the
command now computes the chaincode code just like the SDK as
opposed to leaving it to be done by devops in the peer. Example
CORE_CHAINCODE_MODE=dev CORE_LOGGING_LEVEL=debug ./peer chaincode
deploy -n mycc -c '{"Args":["init","a","100","b","200"]}'

Change-Id: Ie6e44cef880bfcbeb7619f135566a7dce9dcdbc2
Signed-off-by: Srinivasan Muralidharan <muralisr@us.ibm.com>
  • Loading branch information
Srinivasan Muralidharan committed Sep 22, 2016
1 parent 9ec4873 commit ec50ad1
Show file tree
Hide file tree
Showing 13 changed files with 1,180 additions and 59 deletions.
6 changes: 3 additions & 3 deletions core/chaincode/chaincode_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,9 +597,9 @@ func (chaincodeSupport *ChaincodeSupport) Deploy(context context.Context, t *pb.
chaincodeLogger.Debugf("deploying chaincode %s(networkid:%s,peerid:%s)", chaincode, chaincodeSupport.peerNetworkID, chaincodeSupport.peerID)

//create image and create container
_, err = container.VMCProcess(context, vmtype, cir)
if err != nil {
err = fmt.Errorf("Error starting container: %s", err)
resp, err2 := container.VMCProcess(context, vmtype, cir)
if err2 != nil || (resp != nil && resp.(container.VMCResp).Err != nil) {
err = fmt.Errorf("Error creating image: %s", err2)
}

return cds, err
Expand Down
63 changes: 63 additions & 0 deletions core/chaincode/chaincodeexec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright IBM Corp. 2016 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package chaincode

import (
"golang.org/x/net/context"

"fmt"

"github.com/hyperledger/fabric/core/ledger"
"github.com/hyperledger/fabric/core/util"
pb "github.com/hyperledger/fabric/protos"
)

//create a Transactions - this has to change to Proposal when we move chaincode to use Proposals
func createTx(typ pb.Transaction_Type, ccname string, args [][]byte) (*pb.Transaction, error) {
var tx *pb.Transaction
var err error
uuid := util.GenerateUUID()
spec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: 1, ChaincodeID: &pb.ChaincodeID{Name: ccname}, CtorMsg: &pb.ChaincodeInput{Args: args}}}
tx, err = pb.NewChaincodeExecute(spec, uuid, typ)
if nil != err {
return nil, err
}
return tx, nil
}

// ExecuteChaincode executes a given chaincode given chaincode name and arguments
func ExecuteChaincode(typ pb.Transaction_Type, chainname string, ccname string, args [][]byte) ([]byte, error) {
var tx *pb.Transaction
var err error
var b []byte
var lgr *ledger.Ledger
tx, err = createTx(typ, ccname, args)
lgr, err = ledger.GetLedger()
if err != nil {
return nil, fmt.Errorf("Failed to get handle to ledger: %s ", err)
}
//TODO - new ledger access will change this call to take a context
lgr.BeginTxBatch("1")
b, _, err = Execute(context.Background(), GetChain(ChainName(chainname)), tx)
if err != nil {
return nil, fmt.Errorf("Error deploying chaincode: %s", err)
}
//TODO - new ledger access will change this call to take a context
lgr.CommitTxBatch("1", []*pb.Transaction{tx}, nil, nil)

return b, err
}
6 changes: 5 additions & 1 deletion core/db/db_test_exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ func (testDB *TestDBWrapper) CleanDB(t testing.TB) {
// at the end of the test
testDB.cleanup()
testDB.removeDBPath()
t.Logf("Creating testDB")

//if we are cleanup once in Main, we don't have a t
if t != nil {
t.Logf("Creating testDB")
}

Start()
testDB.performCleanup = true
Expand Down
6 changes: 3 additions & 3 deletions core/devops.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ func (*Devops) Build(context context.Context, spec *pb.ChaincodeSpec) (*pb.Chain
return chaincodeDeploymentSpec, nil
}

// get chaincode bytes
func (*Devops) getChaincodeBytes(context context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
// GetChaincodeBytes get chaincode deployment spec given the chaincode spec
func GetChaincodeBytes(context context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
mode := viper.GetString("chaincode.mode")
var codePackageBytes []byte
if mode != chaincode.DevModeUserRunsChaincode {
Expand All @@ -147,7 +147,7 @@ func (*Devops) getChaincodeBytes(context context.Context, spec *pb.ChaincodeSpec
// Deploy deploys the supplied chaincode image to the validators through a transaction
func (d *Devops) Deploy(ctx context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
// get the deployment spec
chaincodeDeploymentSpec, err := d.getChaincodeBytes(ctx, spec)
chaincodeDeploymentSpec, err := GetChaincodeBytes(ctx, spec)

if err != nil {
devopsLogger.Error(fmt.Sprintf("Error deploying chaincode spec: %v\n\n error: %s", spec, err))
Expand Down
71 changes: 71 additions & 0 deletions core/endorser/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copyright IBM Corp. 2016 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package endorser

import (
"flag"
"fmt"
"strings"

"github.com/op/go-logging"
"github.com/spf13/viper"
)

// Config the config wrapper structure
type Config struct {
}

func init() {

}

// SetupTestLogging setup the logging during test execution
func SetupTestLogging() {
level, err := logging.LogLevel(viper.GetString("logging.peer"))
if err == nil {
// No error, use the setting
logging.SetLevel(level, "main")
logging.SetLevel(level, "server")
logging.SetLevel(level, "peer")
} else {
logging.SetLevel(logging.ERROR, "main")
logging.SetLevel(logging.ERROR, "server")
logging.SetLevel(logging.ERROR, "peer")
}
}

// SetupTestConfig setup the config during test execution
func SetupTestConfig() {
flag.Parse()

// Now set the configuration file
viper.SetEnvPrefix("CORE")
viper.AutomaticEnv()
replacer := strings.NewReplacer(".", "_")
viper.SetEnvKeyReplacer(replacer)
viper.SetConfigName("endorser") // name of config file (without extension)
viper.AddConfigPath("./") // path to look for the config file in
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

SetupTestLogging()

// Set the number of maxprocs
viper.GetInt("peer.gomaxprocs")
}
116 changes: 103 additions & 13 deletions core/endorser/endorser.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,135 @@ import (
"github.com/op/go-logging"
"golang.org/x/net/context"

"github.com/hyperledger/fabric/core/chaincode"
"github.com/hyperledger/fabric/core/peer"
"github.com/hyperledger/fabric/core/util"
pb "github.com/hyperledger/fabric/protos"
)

var devopsLogger = logging.MustGetLogger("devops")

// The Jira issue that documents Endorser flow along with its relationship to
// the lifecycle chaincode - https://jira.hyperledger.org/browse/FAB-181

// Endorser provides the Endorser service ProcessProposal
type Endorser struct {
coord peer.MessageHandlerCoordinator
}

// NewDevopsServer creates and returns a new Devops server instance.
// NewEndorserServer creates and returns a new Endorser server instance.
func NewEndorserServer(coord peer.MessageHandlerCoordinator) pb.EndorserServer {
e := new(Endorser)
e.coord = coord
return e
}

// ProcessProposal process the Proposal
func (e *Endorser) ProcessProposal(ctx context.Context, proposal *pb.Proposal) (*pb.ProposalResponse, error) {
// if err := crypto.RegisterClient(secret.EnrollId, nil, secret.EnrollId, secret.EnrollSecret); nil != err {
// return &pb.Response{Status: pb.Response_FAILURE, Msg: []byte(err.Error())}, nil
// }
//get the ChaincodeInvocationSpec from the proposal
func (*Endorser) getChaincodeInvocationSpec(prop *pb.Proposal) (*pb.ChaincodeInvocationSpec, error) {
cis := &pb.ChaincodeInvocationSpec{}
err := proto.Unmarshal(prop.Payload, cis)
if err != nil {
return nil, err
}
return cis, nil
}

// Create a dummy action
action := &pb.Action{ProposalHash: util.ComputeCryptoHash(proposal.Payload), SimulationResult: []byte("TODO: Simulated Result")}
//TODO - what would Endorser's ACL be ?
func (*Endorser) checkACL(prop *pb.Proposal) error {
return nil
}

actionBytes, err := proto.Marshal(action)
//TODO - check for escc and vscc
func (*Endorser) checkEsccAndVscc(prop *pb.Proposal) error {
return nil
}

//call specified chaincode (system or user)
func (*Endorser) callChaincode(cis *pb.ChaincodeInvocationSpec) ([]byte, error) {
//TODO - get chainname from cis when defined
chainName := string(chaincode.DefaultChain)
b, err := chaincode.ExecuteChaincode(pb.Transaction_CHAINCODE_INVOKE, chainName, cis.ChaincodeSpec.ChaincodeID.Name, cis.ChaincodeSpec.CtorMsg.Args)
return b, err
}

//simulate the proposal by calling the chaincode
func (e *Endorser) simulateProposal(prop *pb.Proposal) ([]byte, []byte, error) {
//we do expect the payload to be a ChaincodeInvocationSpec
//if we are supporting other payloads in future, this be glaringly point
//as something that should change
cis, err := e.getChaincodeInvocationSpec(prop)
if err != nil {
return nil, err
return nil, nil, err
}
//---1. check ACL
if err = e.checkACL(prop); err != nil {
return nil, nil, err
}

//---2. check ESCC and VSCC for the chaincode
if err = e.checkEsccAndVscc(prop); err != nil {
return nil, nil, err
}

sig, err := e.coord.GetSecHelper().Sign(actionBytes)
//---3. execute the proposal
var resp []byte
resp, err = e.callChaincode(cis)
if err != nil {
return nil, err
return nil, nil, err
}

endorsement := &pb.Endorsement{Signature: sig}
//---4. get simulation results

simulationResult := []byte("TODO: sim results")
return resp, simulationResult, nil
}

//endorse the proposal by calling the ESCC
func (e *Endorser) endorseProposal(proposal *pb.Proposal) (*pb.Endorsement, error) {
/************ TODO
//---4. call ESCC
args := util.ToChaincodeArgs("", "serialized_action", "serialized_proposal", "any", "other", "args")
ecccis := &pb.ChaincodeInvocationSpec{ ChaincodeSpec: &pb.ChaincodeSpec{ Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{ Name: "escc" }, CtorMsg: &pb.ChaincodeInput{ Args: args }}}
var sig []byte
sig, err = e.callChaincode(ecccis)
if err != nil {
return err
}
************/

endorsement := &pb.Endorsement{Signature: []byte("TODO Signature")}
return endorsement, nil
}

// ProcessProposal process the Proposal
func (e *Endorser) ProcessProposal(ctx context.Context, prop *pb.Proposal) (*pb.ProposalResponse, error) {
//1 -- simulate
//TODO what do we do with response ? We need it for Invoke responses for sure
//Which field in PayloadResponse will carry return value ?
_, simulationResult, err := e.simulateProposal(prop)
if err != nil {
return &pb.ProposalResponse{Response: &pb.Response2{Status: 500, Message: err.Error()}}, err
}

//2 -- endorse
//TODO what do we do with response ? We need it for Invoke responses for sure
endorsement, err := e.endorseProposal(prop)
if err != nil {
return &pb.ProposalResponse{Response: &pb.Response2{Status: 500, Message: err.Error()}}, err
}

//3 -- respond
// Create action
action := &pb.Action{ProposalHash: util.ComputeCryptoHash(prop.Payload), SimulationResult: simulationResult}

actionBytes, err := proto.Marshal(action)
if err != nil {
return nil, err
}

//TODO when we have additional field in response, use "resp" bytes from the simulation
resp := &pb.Response2{Status: 200, Message: "Proposal accepted"}

return &pb.ProposalResponse{Response: resp, ActionBytes: actionBytes, Endorsement: endorsement}, nil
}

0 comments on commit ec50ad1

Please sign in to comment.