Commit ce733d22 authored by jeffgarratt's avatar jeffgarratt
Browse files

Basic endorser service and BDD



Change-Id: Id811b29aa319e242d505ab80bb330934cf07f352
Signed-off-by: default avatarjeffgarratt <garratt.jeff@gmail.com>
parent 543baa3c
......@@ -6,4 +6,4 @@ tags=~@issue_767
~@issue_1565
~@issue_RBAC_TCERT_With_Attributes
~@sdk
~@endorser
~@FAB-314
This diff is collapsed.
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: chaincodeevent.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='chaincodeevent.proto',
package='protos',
syntax='proto3',
serialized_pb=_b('\n\x14\x63haincodeevent.proto\x12\x06protos\"W\n\x0e\x43haincodeEvent\x12\x13\n\x0b\x63haincodeID\x18\x01 \x01(\t\x12\x0c\n\x04txID\x18\x02 \x01(\t\x12\x11\n\teventName\x18\x03 \x01(\t\x12\x0f\n\x07payload\x18\x04 \x01(\x0c\x42\x18\n\x16org.hyperledger.protosb\x06proto3')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_CHAINCODEEVENT = _descriptor.Descriptor(
name='ChaincodeEvent',
full_name='protos.ChaincodeEvent',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='chaincodeID', full_name='protos.ChaincodeEvent.chaincodeID', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='txID', full_name='protos.ChaincodeEvent.txID', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='eventName', full_name='protos.ChaincodeEvent.eventName', index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='payload', full_name='protos.ChaincodeEvent.payload', index=3,
number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=32,
serialized_end=119,
)
DESCRIPTOR.message_types_by_name['ChaincodeEvent'] = _CHAINCODEEVENT
ChaincodeEvent = _reflection.GeneratedProtocolMessageType('ChaincodeEvent', (_message.Message,), dict(
DESCRIPTOR = _CHAINCODEEVENT,
__module__ = 'chaincodeevent_pb2'
# @@protoc_insertion_point(class_scope:protos.ChaincodeEvent)
))
_sym_db.RegisterMessage(ChaincodeEvent)
DESCRIPTOR.has_options = True
DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\026org.hyperledger.protos'))
import grpc
from grpc.beta import implementations as beta_implementations
from grpc.beta import interfaces as beta_interfaces
from grpc.framework.common import cardinality
from grpc.framework.interfaces.face import utilities as face_utilities
# @@protoc_insertion_point(module_scope)
membersrvc0:
extends:
file: compose-defaults.yml
service: membersrvc
orderer0:
extends:
file: docker-compose-orderer-solo.yml
service: orderer0
vp0:
extends:
file: docker-compose-next.yml
service: vpNext
environment:
- CORE_PEER_ID=vp0
- CORE_SECURITY_ENROLLID=test_vp0
- CORE_SECURITY_ENROLLSECRET=MwYpmSRjupbT
- CORE_PEER_PROFILE_ENABLED=true
links:
- membersrvc0
- orderer0
# ports:
# - 7050:6060
vp1:
extends:
file: docker-compose-next.yml
service: vpNext
environment:
- CORE_PEER_ID=vp1
- CORE_PEER_DISCOVERY_ROOTNODE=vp0:7051
- CORE_SECURITY_ENROLLID=test_vp1
- CORE_SECURITY_ENROLLSECRET=5wgHK9qqYaPy
links:
- membersrvc0
- orderer0
- vp0
vp2:
extends:
file: docker-compose-next.yml
service: vpNext
environment:
- CORE_PEER_ID=vp2
- CORE_PEER_DISCOVERY_ROOTNODE=vp0:7051
- CORE_SECURITY_ENROLLID=test_vp2
- CORE_SECURITY_ENROLLSECRET=vQelbRvja7cJ
links:
- membersrvc0
- orderer0
- vp0
vp3:
extends:
file: docker-compose-next.yml
service: vpNext
environment:
- CORE_PEER_ID=vp3
- CORE_PEER_DISCOVERY_ROOTNODE=vp0:7051
- CORE_SECURITY_ENROLLID=test_vp3
- CORE_SECURITY_ENROLLSECRET=9LKqKH5peurL
links:
- membersrvc0
- orderer0
- vp0
vpNext:
extends:
file: compose-defaults.yml
service: vp
environment:
- CORE_NEXT=true
- CORE_PEER_ENDORSER_ENABLED=true
- CORE_SECURITY_ENABLED=true
- CORE_PEER_PKI_ECA_PADDR=membersrvc0:7054
- CORE_PEER_PKI_TCA_PADDR=membersrvc0:7054
- CORE_PEER_PKI_TLSCA_PADDR=membersrvc0:7054
- CORE_PEER_PKI_TLS_ROOTCERT_FILE=./bddtests/tlsca.cert
#
# Test Endorser function
#
# Tags that can be used and will affect test internals:
# @doNotDecompose will NOT decompose the named compose_yaml after scenario ends. Useful for setting up environment and reviewing after scenario.
# @chaincodeImagesUpToDate use this if all scenarios chaincode images are up to date, and do NOT require building. BE SURE!!!
#@chaincodeImagesUpToDate
@endorser
Feature: Endorser
As a application developer
I want to get endorsements and submit transactions and receive events
Scenario: Peers list test, single peer issue #827
Given we compose "docker-compose-1.yml"
When requesting "/network/peers" from "vp0"
Then I should get a JSON response with array "peers" contains "1" elements
# @doNotDecompose
@FAB-184
Scenario Outline: Basic deploy endorsement for chaincode through GRPC to multiple endorsers
Given we compose "<ComposeFile>"
And I wait "1" seconds
And I register with CA supplying username "binhn" and secret "7avZQLwcUe9q" on peers:
| vp0 |
When user "binhn" creates a chaincode spec of type "GOLANG" for chaincode "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" aliased as "cc_spec" with args
| funcName | arg1 | arg2 | arg3 | arg4 |
| init | a | 100 | b | 200 |
And user "binhn" creates a deployment proposal "proposal1" using chaincode spec "cc_spec"
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "2" seconds:
| vp0 | vp1 | vp2 | vp3 |
And user "binhn" stores their last result as "proposal1Responses"
Then user "binhn" expects proposal responses "proposal1Responses" with status "200" from endorsers:
| vp0 | vp1 | vp2 | vp3 |
Examples: Orderer Options
| ComposeFile | Waittime |
| docker-compose-next-4.yml | 60 |
# @doNotDecompose
@FAB-314
Scenario Outline: Advanced deploy endorsement with ESCC for chaincode through GRPC to single endorser
Given we compose "<ComposeFile>"
And I register with CA supplying username "binhn" and secret "7avZQLwcUe9q" on peers:
| vp0 |
When user "binhn" creates a chaincode spec of type "GOLANG" for chaincode "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" aliased as "cc_spec" with args
| funcName | arg1 | arg2 | arg3 | arg4 |
| init | a | 100 | b | 200 |
And user "binhn" sets ESCC to "my_escc" for chaincode spec "cc_spec"
And user "binhn" creates a deployment proposal "proposal1" using chaincode spec "cc_spec"
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "2" seconds:
| vp0 | vp1 | vp2 | vp3 |
And user "binhn" stores their last result as "proposal1Responses"
Then user "binhn" expects proposal responses "proposal1Responses" with status "200" from endorsers:
| vp0 | vp1 | vp2 | vp3 |
Examples: Orderer Options
| ComposeFile | Waittime |
| docker-compose-next-4.yml | 60 |
# @doNotDecompose
@FAB-314
Scenario Outline: Advanced deploy endorsement with VSCC for chaincode through GRPC to single endorser
Given we compose "<ComposeFile>"
And I register with CA supplying username "binhn" and secret "7avZQLwcUe9q" on peers:
| vp0 |
When user "binhn" creates a chaincode spec of type "GOLANG" for chaincode "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" aliased as "cc_spec" with args
| funcName | arg1 | arg2 | arg3 | arg4 |
| init | a | 100 | b | 200 |
And user "binhn" sets VSCC to "my_vscc" for chaincode spec "cc_spec"
And user "binhn" creates a deployment proposal "proposal1" using chaincode spec "cc_spec"
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "2" seconds:
| vp0 | vp1 | vp2 | vp3 |
And user "binhn" stores their last result as "proposal1Responses"
Then user "binhn" expects proposal responses "proposal1Responses" with status "200" from endorsers:
| vp0 | vp1 | vp2 | vp3 |
Examples: Orderer Options
| ComposeFile | Waittime |
| docker-compose-next-4.yml | 60 |
This diff is collapsed.
......@@ -125,19 +125,33 @@ def getArgsFromContextForUser(context, enrollId):
# Update the chaincodeSpec ctorMsg for invoke
args = []
if 'table' in context:
# There are function arguments
userRegistration = bdd_test_util.getUserRegistration(context, enrollId)
# Allow the user to specify expressions referencing tags in the args list
pattern = re.compile('\{(.*)\}$')
for arg in context.table[0].cells:
m = pattern.match(arg)
if m:
# tagName reference found in args list
tagName = m.groups()[0]
# make sure the tagName is found in the users tags
assert tagName in userRegistration.tags, "TagName '{0}' not found for user '{1}'".format(tagName, userRegistration.getUserName())
args.append(userRegistration.tags[tagName])
else:
#No tag referenced, pass the arg
args.append(arg)
if context.table:
# There are function arguments
userRegistration = bdd_test_util.getUserRegistration(context, enrollId)
# Allow the user to specify expressions referencing tags in the args list
pattern = re.compile('\{(.*)\}$')
for arg in context.table[0].cells:
m = pattern.match(arg)
if m:
# tagName reference found in args list
tagName = m.groups()[0]
# make sure the tagName is found in the users tags
assert tagName in userRegistration.tags, "TagName '{0}' not found for user '{1}'".format(tagName, userRegistration.getUserName())
args.append(userRegistration.tags[tagName])
else:
#No tag referenced, pass the arg
args.append(arg)
return args
def toStringArray(items):
itemsAsStr = []
for item in items:
if type(item) == str:
itemsAsStr.append(item)
elif type(item) == unicode:
itemsAsStr.append(str(item))
else:
raise Exception("Error tring to convert to string: unexpected type '{0}'".format(type(item)))
return itemsAsStr
# 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.
#
import endorser_util
import bdd_grpc_util
import bdd_test_util
@when(u'user "{enrollId}" creates a chaincode spec of type "{ccType}" for chaincode "{chaincodePath}" aliased as "{ccSpecAlias}" with args')
def step_impl(context, enrollId, ccType, chaincodePath, ccSpecAlias):
userRegistration = bdd_test_util.getUserRegistration(context, enrollId)
args = bdd_grpc_util.getArgsFromContextForUser(context, enrollId)
ccSpec = endorser_util.getChaincodeSpec(ccType, chaincodePath, bdd_grpc_util.toStringArray(args))
print("ccSpec = {0}".format(ccSpec))
userRegistration.tags[ccSpecAlias] = ccSpec
@when(u'user "{enrollId}" creates a deployment proposal "{proposalAlias}" using chaincode spec "{ccSpecAlias}"')
def step_impl(context, enrollId, proposalAlias, ccSpecAlias):
userRegistration = bdd_test_util.getUserRegistration(context, enrollId)
assert ccSpecAlias in userRegistration.tags, "ChaincodeSpec alias '{0}' not found for user '{1}'".format(ccSpecAlias, enrollId)
ccSpec = userRegistration.tags[ccSpecAlias]
proposal = endorser_util.createDeploymentProposalForBDD(ccSpec)
assert not proposalAlias in userRegistration.tags, "Proposal alias '{0}' already exists for '{1}'".format(proposalAlias, enrollId)
userRegistration.tags[proposalAlias] = proposal
@when(u'user "{enrollId}" sends proposal "{proposalAlias}" to endorsers with timeout of "{timeout}" seconds')
def step_impl(context, enrollId, proposalAlias, timeout):
assert 'table' in context, "Expected table of endorsers"
userRegistration = bdd_test_util.getUserRegistration(context, enrollId)
assert proposalAlias in userRegistration.tags, "Proposal alias '{0}' not found for user '{1}'".format(proposalAlias, enrollId)
proposal = userRegistration.tags[proposalAlias]
# Send proposal to each specified endorser, waiting 'timeout' seconds for response/error
endorsers = context.table.headings
proposalResponseFutures = [endorserStub.ProcessProposal.future(proposal, int(timeout)) for endorserStub in endorser_util.getEndorserStubs(context, endorsers)]
resultsDict = dict(zip(endorsers, [respFuture.result() for respFuture in proposalResponseFutures]))
userRegistration.lastResult = resultsDict
@then(u'user "{enrollId}" expects proposal responses "{proposalResponsesAlias}" with status "{statusCode}" from endorsers')
def step_impl(context, enrollId, proposalResponsesAlias, statusCode):
assert 'table' in context, "Expected table of endorsers"
userRegistration = bdd_test_util.getUserRegistration(context, enrollId)
# Make sure proposalResponseAlias not already defined
assert proposalResponsesAlias in userRegistration.tags, "Expected proposal responses at tag '{0}', for user '{1}'".format(proposalResponsesAlias, enrollId)
proposalRespDict = userRegistration.tags[proposalResponsesAlias]
# Loop through endorser proposal Responses
endorsers = context.table.headings
for respSatusCode in [proposalRespDict[endorser].response.status for endorser in endorsers]:
assert int(statusCode) == respSatusCode, "Expected proposal response status code of {0} from {1}, received {2}".format(statusCode, endorser, respSatusCode)
import chaincode_pb2
import fabric_next_pb2
import bdd_test_util
import bdd_grpc_util
def getChaincodeSpec(ccType, path, args):
# make chaincode spec for chaincode to be deployed
ccSpec = chaincode_pb2.ChaincodeSpec(type = ccType,
chaincodeID = chaincode_pb2.ChaincodeID(path=path),
ctorMsg = chaincode_pb2.ChaincodeInput(args = args))
return ccSpec
def createPropsalId():
return 'TODO proposal Id'
def createDeploymentProposalForBDD(ccSpec):
"Returns a deployment proposal of chaincode type"
lc_chaincode_spec = getChaincodeSpec(chaincode_pb2.ChaincodeSpec.GOLANG, "lccc", ['deployBDD', ccSpec.SerializeToString()])
# make proposal
proposal = fabric_next_pb2.Proposal(type = fabric_next_pb2.Proposal.CHAINCODE, id = createPropsalId())
proposal.payload = lc_chaincode_spec.SerializeToString()
return proposal
def getEndorserStubs(context, composeServices):
stubs = []
for composeService in composeServices:
ipAddress = bdd_test_util.ipFromContainerNamePart(composeService, context.compose_containers)
channel = bdd_grpc_util.getGRPCChannel(ipAddress)
newEndorserStub = fabric_next_pb2.beta_create_Endorser_stub(channel)
stubs.append(newEndorserStub)
return stubs
......@@ -90,7 +90,7 @@ class DeliverStreamHelper(StreamHelper):
#Set the ack flag
trueOptions = ['true', 'True','yes','Yes']
falseOptions = ['false', 'False', 'no', 'No']
assert sendAck in trueOptions + falseOptions, "sendAck of '{0}' not recognized, expected one of '{1}'".format(sendAck, trueOptions + falseOptions)
assert sendAck in trueOptions + falseOptions, "sendAck of '{0}' not recognized, expected one of '{1}'".format(sendAck, trueOptions + falseOptions)
self.sendAck = sendAck in trueOptions
# Set the UpdateMessage and start the stream
self.deliverUpdateMsg = createDeliverUpdateMsg(Start, SpecifiedNumber, WindowSize)
......@@ -101,7 +101,7 @@ class DeliverStreamHelper(StreamHelper):
def seekToBlock(self, blockNum):
deliverUpdateMsg = ab_pb2.DeliverUpdate()
deliverUpdateMsg.CopyFrom(self.deliverUpdateMsg)
deliverUpdateMsg.Seek.SpecifiedNumber = blockNum
deliverUpdateMsg.Seek.SpecifiedNumber = blockNum
self.sendQueue.put(deliverUpdateMsg)
def sendAcknowledgment(self, blockNum):
......@@ -130,8 +130,8 @@ class DeliverStreamHelper(StreamHelper):
class UserRegistration:
def __init__(self, secretMsg, composeService):
self.enrollId = secretMsg['enrollId']
self.secretMsg = secretMsg
......@@ -174,7 +174,7 @@ class UserRegistration:
print("Got error")
print("Done")
assert counter == int(numMsgsToBroadcast), "counter = {0}, expected {1}".format(counter, numMsgsToBroadcast)
def getABStubForComposeService(self, context, composeService):
'Return a Stub for the supplied composeService, will cache'
if composeService in self.atomicBroadcastStubsDict:
......@@ -190,26 +190,26 @@ class UserRegistration:
# Registerses a user on a specific composeService
def registerUser(context, secretMsg, composeService):
userName = secretMsg['enrollId']
if 'users' in context:
if 'ordererUsers' in context:
pass
else:
context.users = {}
if userName in context.users:
raise Exception("User already registered: {0}".format(userName))
context.ordererUsers = {}
if userName in context.ordererUsers:
raise Exception("Orderer user already registered: {0}".format(userName))
userRegistration = UserRegistration(secretMsg, composeService)
context.users[userName] = userRegistration
context.ordererUsers[userName] = userRegistration
return userRegistration
def getUserRegistration(context, enrollId):
userRegistration = None
if 'users' in context:
if 'ordererUsers' in context:
pass
else:
context.users = {}
if enrollId in context.users:
userRegistration = context.users[enrollId]
ordererContext.ordererUsers = {}
if enrollId in context.ordererUsers:
userRegistration = context.ordererUsers[enrollId]
else:
raise Exception("User has not been registered: {0}".format(enrollId))
raise Exception("Orderer user has not been registered: {0}".format(enrollId))
return userRegistration
def createDeliverUpdateMsg(Start, SpecifiedNumber, WindowSize):
......@@ -223,10 +223,10 @@ def createDeliverUpdateMsg(Start, SpecifiedNumber, WindowSize):
def generateBroadcastMessages(numToGenerate = 1, timeToHoldOpen = 1):
messages = []
for i in range(0, numToGenerate):
messages.append(ab_pb2.BroadcastMessage(Data = str("BDD test: {0}".format(datetime.datetime.utcnow()))))
messages.append(ab_pb2.BroadcastMessage(Data = str("BDD test: {0}".format(datetime.datetime.utcnow()))))
for msg in messages:
yield msg
time.sleep(timeToHoldOpen)
time.sleep(timeToHoldOpen)
def getGRPCChannel(ipAddress):
......
/*
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 (
"github.com/golang/protobuf/proto"
"github.com/op/go-logging"
"golang.org/x/net/context"
"github.com/hyperledger/fabric/core/peer"
"github.com/hyperledger/fabric/core/util"
pb "github.com/hyperledger/fabric/protos"
)
var devopsLogger = logging.MustGetLogger("devops")
type Endorser struct {
coord peer.MessageHandlerCoordinator
}
// NewDevopsServer creates and returns a new Devops 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
// }
// Create a dummy action
action := &pb.Action{ProposalHash: util.ComputeCryptoHash(proposal.Payload), SimulationResult: []byte("TODO: Simulated Result")}
actionBytes, err := proto.Marshal(action)
if err != nil {
return nil, err
}
sig, err := e.coord.GetSecHelper().Sign(actionBytes)
if err != nil {
return nil, err
}
endorsement := &pb.Endorsement{Signature: sig}
resp := &pb.Response2{Status: 200, Message: "Proposal accepted"}
return &pb.ProposalResponse{Response: resp, ActionBytes: actionBytes, Endorsement: endorsement}, nil
}
......@@ -35,6 +35,7 @@ import (
"github.com/hyperledger/fabric/core/comm"
"github.com/hyperledger/fabric/core/crypto"
"github.com/hyperledger/fabric/core/db"
"github.com/hyperledger/fabric/core/endorser"
"github.com/hyperledger/fabric/core/ledger/genesis"
"github.com/hyperledger/fabric/core/peer"
"github.com/hyperledger/fabric/core/rest"
......@@ -194,6 +195,10 @@ func serve(args []string) error {
go rest.StartOpenchainRESTServer(serverOpenchain, serverDevops)
}
// Register the Endorser server
serverEndorser := endorser.NewEndorserServer(peerServer)
pb.RegisterEndorserServer(grpcServer, serverEndorser)
logger.Infof("Starting peer with ID=%s, network ID=%s, address=%s, rootnodes=%v, validator=%v",
peerEndpoint.ID, viper.GetString("peer.networkId"), peerEndpoint.Address,
viper.GetString("peer.discovery.rootnode"), peer.ValidatorEnabled())
......
......@@ -11,7 +11,7 @@ It is generated from these files:
chaincode.proto