diff --git a/lib/Member.js b/lib/Member.js index b570b30ee6..4c0461db6d 100644 --- a/lib/Member.js +++ b/lib/Member.js @@ -25,7 +25,10 @@ var sdkUtils = require('./utils.js'); var grpc = require('grpc'); var _ccProto = grpc.load(__dirname + '/protos/chaincode.proto').protos; -var _fabricProto = grpc.load(__dirname + '/protos/fabric_next.proto').protos; +var _ccProposalProto = grpc.load(__dirname + '/protos/chaincode_proposal.proto').protos; +var _headerProto = grpc.load(__dirname + '/protos/fabric_transaction_header.proto').protos; +var _proposalProto = grpc.load(__dirname + '/protos/fabric_proposal.proto').protos; +var _responseProto = grpc.load(__dirname + '/protos/fabric_proposal_response.proto').protos; var logger = sdkUtils.getLogger('Member.js'); @@ -411,7 +414,7 @@ var Member = class { } }; - // // step 2: construct the ChaincodeDeploymentSpec + // step 2: construct the ChaincodeDeploymentSpec let chaincodeDeploymentSpec = new _ccProto.ChaincodeDeploymentSpec(); chaincodeDeploymentSpec.setChaincodeSpec(ccSpec); @@ -432,14 +435,25 @@ var Member = class { } }; - // // step 4: construct the ChaincodeInvocationSpec to call the LCCC + // step 3: construct the ChaincodeInvocationSpec to call the LCCC let cciSpec = new _ccProto.ChaincodeInvocationSpec(); cciSpec.setChaincodeSpec(lcccSpec); + cciSpec.setIdGenerationAlg(''); + + // step 4: construct the enveloping Proposal object + // step 4.1: the header part of the proposal + let headerExt = new _ccProposalProto.ChaincodeHeaderExtension(); + let header = new _headerProto.Header(); + header.setType(_headerProto.Header.Type.CHAINCODE); + header.setExtensions(headerExt.toBuffer()); + + // step 4.2: the payload part of the proposal for chaincode deploy is ChaincodeProposalPayload + let payload = new _ccProposalProto.ChaincodeProposalPayload(); + payload.setInput(cciSpec.toBuffer()); let proposal = { - type: _fabricProto.Proposal.Type.CHAINCODE, - id: 'someUniqueName', - payload: cciSpec.toBuffer() + header: header.toBuffer(), + payload: payload.toBuffer() }; let peer = new Peer(request.endorserUrl); diff --git a/lib/Peer.js b/lib/Peer.js index 88b9aef26c..5c33ed01e6 100644 --- a/lib/Peer.js +++ b/lib/Peer.js @@ -21,7 +21,7 @@ var utils = require('./utils.js'); var Remote = require('./Remote'); var grpc = require('grpc'); -var _fabricProto = grpc.load(__dirname + '/protos/fabric_next.proto').protos; +var _serviceProto = grpc.load(__dirname + '/protos/fabric_service.proto').protos; var logger = utils.getLogger('Peer.js'); @@ -42,7 +42,7 @@ var Peer = class extends Remote { constructor(url, opts) { super(url, opts); logger.info('Peer.const - url: %s options ',url, this._options); - this._endorserClient = new _fabricProto.Endorser(this._endpoint.addr, this._endpoint.creds, this._options); + this._endorserClient = new _serviceProto.Endorser(this._endpoint.addr, this._endpoint.creds, this._options); } /** diff --git a/lib/protos/chaincode_proposal.proto b/lib/protos/chaincode_proposal.proto new file mode 100644 index 0000000000..e332abe450 --- /dev/null +++ b/lib/protos/chaincode_proposal.proto @@ -0,0 +1,133 @@ +/* +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. +*/ + +syntax = "proto3"; + +package protos; + +/* +The flow to get a CHAINCODE transaction approved goes as follows: + +1. client sends proposal to endorser +==================================== + +The proposal is basically a request to do something on a chaincode, that will +result on some action - some change in the state of a chaincode and/or some +data to be committed to the ledger; a proposal in general contains a header +(with some metadata describing it, such as the type, the identity of the +invoker, the time, the ID of the chain, a cryptographic nonce..) and a payload +(the chaincode ID, invocation arguments..). Optionally, it may contain actions +that the endorser may be asked to endorse, to emulate a submitting peer. A +chaincode proposal contains the following messages: + +SignedProposal +|\_ Signature (signature on the Proposal message by the creator specified in the header) + \_ Proposal + |\_ Header (the header for this proposal) + |\_ ChaincodeProposalPayload (the payload for this proposal) + \_ ChaincodeAction (the actions for this proposal - optional for a proposal) + +2. endorser sends proposal response back to client +================================================== + +The proposal response contains an endorser's response to a client's proposal. A +proposal response contains a success/error code, a response payload and a +signature (also referred to as endorsement) over the response payload. The +response payload contains a hash of the proposal (to securely link this +response to the corresponding proposal), a description of the action resulting +from the proposal and the endorser's signature over its payload. Formally, a +chaincode proposal response contains the following messages: + +ProposalResponse +|\_ Endorsement (the endorser's signature over the whole response payload) + \_ ProposalResponsePayload + \_ ChaincodeAction (the actions for this proposal) + +3. client assembles endorsements into a transaction +=================================================== + +A transaction message assembles one or more proposals and corresponding +responses into a message to be sent to orderers. After ordering, (batches of) +transactions are delivered to committing peers for validation and final +delivery into the ledger. A transaction contains one or more actions. Each of +them contains a header (same as that of the proposal that requested it), a +proposal payload (same as that of the proposal that requested it), a +description of the resulting action and signatures from each of the endorsers +that endorsed the action. + +SignedTransaction +|\_ Signature (signature on the Transaction message by the creator specified in the header) + \_ Transaction + \_ TransactionAction (1...n) + |\_ Header (1) (the header of the proposal that requested this action) + \_ ChaincodeActionPayload (1) + |\_ ChaincodeProposalPayload (1) (payload of the proposal that requested this action) + \_ ChaincodeEndorsedAction (1) + |\_ Endorsement (1...n) (endorsers' signatures over the whole response payload) + \_ ProposalResponsePayload + \_ ChaincodeAction (the actions for this proposal) +*/ + +// ChaincodeHeaderExtension is the Header's extentions message to be used when +// the Header's type is CHAINCODE. This extensions is used to specify which +// chaincode to invoke and what should appear on the ledger. +message ChaincodeHeaderExtension { + + // The PayloadVisibility field controls to what extent the Proposal's payload + // (recall that for the type CHAINCODE, it is ChaincodeProposalPayload + // message) field will be visible in the final transaction and in the ledger. + // Ideally, it would be configurable, supporting at least 3 main “visibility + // modes”: + // 1. all bytes of the payload are visible; + // 2. only a hash of the payload is visible; + // 3. nothing is visible. + // Notice that the visibility function may be potentially part of the ESCC. + // In that case it overrides PayloadVisibility field. Finally notice that + // this field impacts the content of ProposalResponsePayload.proposalHash. + bytes payloadVisibility = 1; + + // The ID of the chaincode to target. + bytes chaincodeID = 2; +} + +// ChaincodeProposalPayload is the Proposal's payload message to be used when +// the Header's type is CHAINCODE. It contains the arguments for this +// invocation. +message ChaincodeProposalPayload { + + // Input contains the arguments for this invocation. If this invocation + // deploys a new chaincode, ESCC/VSCC are part of this field. + bytes Input = 1; + + // Transient contains data (e.g. cryptographic material) that might be used + // to implement some form of application-level confidentiality. The contents + // of this field are supposed to always be omitted from the transaction and + // excluded from the ledger. + bytes Transient = 2; +} + +// ChaincodeAction contains the actions the events generated by the execution +// of the chaincode. +message ChaincodeAction { + + // This field contains the read set and the write set produced by the + // chaincode executing this invocation. + bytes results = 1; + + // This field contains the events generated by the chaincode executing this + // invocation. + bytes events = 2; +} diff --git a/lib/protos/fabric_proposal.proto b/lib/protos/fabric_proposal.proto new file mode 100644 index 0000000000..23f3646ad4 --- /dev/null +++ b/lib/protos/fabric_proposal.proto @@ -0,0 +1,140 @@ +/* +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. +*/ + +syntax = "proto3"; + +package protos; + +/* +The flow to get a generic transaction approved goes as follows: + +1. client sends proposal to endorser +==================================== + +The proposal is basically a request to do something that will result on some +action with impact on the ledger; a proposal contains a header (with some +metadata describing it, such as the type, the identity of the invoker, the +time, the ID of the chain, a cryptographic nonce..) and an opaque payload that +depends on the type specified in the header. A proposal contains the following +messages: + +SignedProposal +|\_ Signature (signature on the Proposal message by the creator specified in the header) + \_ Proposal + |\_ Header (the header for this proposal) + \_ Payload (the payload for this proposal) + +2. endorser sends proposal response back to client +================================================== + +The proposal response contains an endorser's response to a client's proposal. A +proposal response contains a success/error code, a response payload and a +signature (also referred to as endorsement) over the response payload. The +response payload contains a hash of the proposal (to securely link this +response to the corresponding proposal) and an opaque extension field that +depends on the type specified in the header of the corresponding proposal. A +proposal response contains the following messages: + +ProposalResponse +|\_ Endorsement (the endorser's signature over the whole response payload) + \_ ProposalResponsePayload (the payload of the proposal response) + +3. client assembles endorsements into a transaction +=================================================== + +A transaction message assembles one or more proposals and corresponding +responses into a message to be sent to orderers. After ordering, (batches of) +transactions are delivered to committing peers for validation and final +delivery into the ledger. A transaction contains one or more actions. Each of +them contains a header (same as that of the proposal that requested it) and an +opaque payload that depends on the type specified in the header. + +SignedTransaction +|\_ Signature (signature on the Transaction message by the creator specified in the header) + \_ Transaction + \_ TransactionAction (1...n) + |\_ Header (1) (the header of the proposal that requested this action) + \_ Payload (1) (the payload for this action) +*/ + +// This structure is necessary to sign the proposal which contains the header +// and the payload. Without this structure, we would have to concatenate the +// header and the payload to verify the signature, which could be expensive +// with large payload +// +// When an endorser receives a SignedProposal message, it should verify the +// signature over the proposal bytes. This verification requires the following +// steps: +// 1. Verification of the validity of the certificate that was used to produce +// the signature. The certificate will be available once proposalBytes has +// been unmarshalled to a Proposal message, and Proposal.header has been +// unmarshalled to a Header message. While this unmarshalling-before-verifying +// might not be ideal, it is unavoidable because i) the signature needs to also +// protect the signing certificate; ii) it is desirable that Header is created +// once by the client and never changed (for the sake of accountability and +// non-repudiation). Note also that it is actually impossible to conclusively +// verify the validity of the certificate included in a Proposal, because the +// proposal needs to first be endorsed and ordered with respect to certificate +// expiration transactions. Still, it is useful to pre-filter expired +// certificates at this stage. +// 2. Verification that the certificate is trusted (signed by a trusted CA) and +// that it is allowed to transact with us (with respect to some ACLs); +// 3. Verification that the signature on proposalBytes is valid; +// 4. Detect replay attacks; +message SignedProposal { + + // The bytes of Proposal + bytes proposalBytes = 1; + + // Signaure over proposalBytes; this signature is to be verified against + // the creator identity contained in the header of the Proposal message + // marshaled as proposalBytes + bytes signature = 2; +} + +// A Proposal is sent to an endorser for endorsement. The proposal contains: +// 1. A header which should be unmarshaled to a Header message. Note that +// Header is both the header of a Proposal and of a Transaction, in that i) +// both headers should be unmarshaled to this message; and ii) it is used to +// compute cryptographic hashes and signatures. The header has fields common +// to all proposals/transactions. In addition it has a type field for +// additional customization. An example of this is the ChaincodeHeaderExtension +// message used to extend the Header for type CHAINCODE. +// 2. A payload whose type depends on the header's type field. +// 3. An extension whose type depends on the header's type field. +// +// Let us see an example. For type CHAINCODE (see the Header message), +// we have the following: +// 1. The header is a Header message whose extensions field is a +// ChaincodeHeaderExtension message. +// 2. The payload is a ChaincodeProposalPayload message. +// 3. The extension is a ChaincodeAction that might be used to ask the +// endorsers to endorse a specific ChaincodeAction, thus emulating the +// submitting peer model. +message Proposal { + + // The header of the proposal. It is the bytes of the Header + bytes header = 1; + + // The payload of the proposal as defined by the type in the proposal + // header. + bytes payload = 2; + + // Optional extensions to the proposal. Its content depends on the Header's + // type field. For the type CHAINCODE, it might be the bytes of a + // ChaincodeAction message. + bytes extension = 3; +} diff --git a/lib/protos/fabric_proposal_response.proto b/lib/protos/fabric_proposal_response.proto new file mode 100644 index 0000000000..c7738b5e69 --- /dev/null +++ b/lib/protos/fabric_proposal_response.proto @@ -0,0 +1,123 @@ +/* +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. +*/ + +syntax = "proto3"; + +package protos; + +import "google/protobuf/timestamp.proto"; + +// A ProposalResponse is returned from an endorser to the proposal submitter. +// The idea is that this message contains the endorser's response to the +// request of a client to perform an action over a chaincode (or more +// generically on the ledger); the response might be success/error (conveyed in +// the Response field) together with a description of the action and a +// signature over it by that endorser. If a sufficient number of distinct +// endorsers agree on the same action and produce signature to that effect, a +// transaction can be generated and sent for ordering. +message ProposalResponse { + + // Version indicates message protocol version + int32 version = 1; + + // Timestamp is the time that the message + // was created as defined by the sender + google.protobuf.Timestamp timestamp = 2; + + // A response message indicating whether the + // endorsement of the action was successful + Response2 response = 4; + + // The payload of response. It is the bytes of ProposalResponsePayload + bytes payload = 5; + + // The endorsement of the proposal, basically + // the endorser's signature over the payload + Endorsement endorsement = 6; +} + +// A response with a representation similar to an HTTP response that can +// be used within another message. +message Response2 { + + // A status code that should follow the HTTP status codes. + int32 status = 1; + + // A message associated with the response code. + string message = 2; + + // A payload that can be used to include metadata with this response. + bytes payload = 3; +} + +// ProposalResponsePayload is the payload of a proposal response. This message +// is the "bridge" between the client's request and the endorser's action in +// response to that request. Concretely, for chaincodes, it contains a hashed +// representation of the proposal (proposalHash) and a representation of the +// chaincode state changes and events inside the extension field. +message ProposalResponsePayload { + + // Hash of the proposal that triggered this response. The hash is used to + // link a response with its proposal, both for bookeeping purposes on an + // asynchronous system and for security reasons (accountability, + // non-repudiation). The hash usually covers the entire Proposal message + // (byte-by-byte). However this implies that the hash can only be verified + // if the entire proposal message is available when ProposalResponsePayload is + // included in a transaction or stored in the ledger. For confidentiality + // reasons, with chaincodes it might be undesirable to store the proposal + // payload in the ledger. If the type is CHAINCODE, this is handled by + // separating the proposal's header and + // the payload: the header is always hashed in its entirety whereas the + // payload can either be hashed fully, or only its hash may be hashed, or + // nothing from the payload can be hashed. The PayloadVisibility field in the + // Header's extension controls to which extent the proposal payload is + // "visible" in the sense that was just explained. + bytes proposalHash = 1; + + // Epoch in which the response has been generated. This field identifies a + // logical window of time. A proposal response is accepted by a peer only if + // two conditions hold: + // 1. the epoch specified in the message is the current epoch + // 2. this message has been only seen once during this epoch (i.e. it hasn't + // been replayed) + bytes epoch = 2; + + // Extension should be unmarshaled to a type-specific message. The type of + // the extension in any proposal response depends on the type of the proposal + // that the client selected when the proposal was initially sent out. In + // particular, this information is stored in the type field of a Header. For + // chaincode, it's a ChaincodeAction message + bytes extension = 3; +} + +// An endorsement is a signature of an endorser over a proposal response. By +// producing an endorsement message, an endorser implicitly "approves" that +// proposal response and the actions contained therein. When enough +// endorsements have been collected, a transaction can be generated out of a +// set of proposal responses. Note that this message only contains an identity +// and a signature but no signed payload. This is intentional because +// endorsements are supposed to be collected in a transaction, and they are all +// expected to endorse a single proposal response/action (many endorsements +// over a single proposal response) +message Endorsement { + + // Identity of the endorser (e.g. its certificate) + bytes endorser = 1; + + // Signature of the payload included in ProposalResponse concatenated with + // the endorser's certificate; ie, sign(ProposalResponse.payload + endorser) + bytes signature = 2; +} diff --git a/lib/protos/fabric_service.proto b/lib/protos/fabric_service.proto new file mode 100644 index 0000000000..d4e9d726ff --- /dev/null +++ b/lib/protos/fabric_service.proto @@ -0,0 +1,26 @@ +/* +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. +*/ + +syntax = "proto3"; + +package protos; + +import "fabric_proposal.proto"; +import "fabric_proposal_response.proto"; + +service Endorser { + rpc ProcessProposal(Proposal) returns (ProposalResponse) {} +} diff --git a/lib/protos/fabric_transaction_header.proto b/lib/protos/fabric_transaction_header.proto new file mode 100644 index 0000000000..17839754fa --- /dev/null +++ b/lib/protos/fabric_transaction_header.proto @@ -0,0 +1,69 @@ +/* +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. +*/ + +syntax = "proto3"; + +package protos; + +import "google/protobuf/timestamp.proto"; + +// A Header contains fields that are common to all proposals and all +// transactions, no matter their type. It can include also type-dependant +// fields by using the 'extensions' field. This header is on purpose the same +// header for proposals (a request to do "something" on the ledger) and a +// transaction (the endorsed actions following from some request). +// Furthermore, a proposal, its endorsements and the resulting transaction are +// linked together by this message, as follows +// 1. a Proposal contains a Header +// 2. the hash of the Header of a proposal is included in the proposal response +// generated by each endorser as a result of that proposal +// 3. a TransactionAction contains both i) the *same* Header (byte-by-byte) of +// the corresponsing Proposal and ii) the hash of the Header in each of the +// endorsed actions +message Header { + + enum Type { + UNDEFINED = 0; + CHAINCODE = 1; + } + + // Version indicates message protocol version + int32 version = 1; + + // Timestamp is the local time when the message was created + // by the sender + google.protobuf.Timestamp timestamp = 2; + + // Type of the transaction + Type type = 3; + + // Creator of the header (and encapsulating message). This is usually a tcert + // or ecert identifying the entity who submits the proposal/transaction. The + // creator identifies the signer of + // 1. a proposal (if this is the header of a Proposal message) + // 2. a transaction (if this is the header of a TransactionAction message) + bytes creator = 4; + + // Arbitrary number that may only be used once. This ensures the hash of + // the proposal is unique and may be used in replay detection + bytes nonce = 5; + + // Identifier of the chain this header targets to + bytes chainID = 6; + + // Extensions is used to include type-dependant fields + bytes extensions = 7; +} diff --git a/test/unit/endorser-tests.js b/test/unit/endorser-tests.js index e80bf99c8d..11faac89a2 100644 --- a/test/unit/endorser-tests.js +++ b/test/unit/endorser-tests.js @@ -62,8 +62,8 @@ test('endorser test', function(t) { t.end(); } ).then( - function(status) { - if (status === 200) { + function(response) { + if (response && response.response && response.response.status === 200) { t.pass('Successfully obtained endorsement.'); } else { t.fail('Failed to obtain endorsement. Error code: ' + status);