fabric-gateway 0.2.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,28 +25,16 @@ module Fabric
25
25
  class Contract
26
26
  attr_reader :network, :chaincode_name, :contract_name
27
27
 
28
+ # @!parse include Fabric::Accessors::Network
29
+ # @!parse include Fabric::Accessors::Gateway
30
+ include Fabric::Accessors::Network
31
+
28
32
  def initialize(network, chaincode_name, contract_name = '')
29
33
  @network = network
30
34
  @chaincode_name = chaincode_name
31
35
  @contract_name = contract_name
32
36
  end
33
37
 
34
- def client
35
- network.client
36
- end
37
-
38
- def signer
39
- network.signer
40
- end
41
-
42
- def gateway
43
- network.gateway
44
- end
45
-
46
- def network_name
47
- network.name
48
- end
49
-
50
38
  #
51
39
  # Evaluate a transaction function and return its results. A transaction proposal will be evaluated on endorsing
52
40
  # peers but the transaction will not be sent to the ordering service and so will not be committed to the ledger.
@@ -66,7 +54,6 @@ module Fabric
66
54
  # transaction function will be evaluated on endorsing peers and then submitted to the ordering service to be
67
55
  # committed to the ledger.
68
56
  #
69
- # @TODO: Not yet complete
70
57
  #
71
58
  # @param [String] transaction_name
72
59
  # @param [Array] arguments array of arguments to pass to the transaction
@@ -102,8 +89,6 @@ module Fabric
102
89
  # transaction function will be evaluated on endorsing peers and then submitted to the ordering service to be
103
90
  # committed to the ledger.
104
91
  #
105
- # @TODO: Implement Me! - LEFT OFF HERE!
106
- #
107
92
  # @param [String] transaction_name
108
93
  # @param [Hash] proposal_options
109
94
  # @option proposal_options [Array] :arguments array of arguments to pass to the transaction
@@ -116,17 +101,15 @@ module Fabric
116
101
  #
117
102
  def submit(transaction_name, proposal_options = {})
118
103
  transaction = new_proposal(transaction_name, **proposal_options).endorse
119
- submitted = transaction.submit
120
-
121
- status = submitted.get_status
104
+ transaction.submit
122
105
 
123
- raise CommitError, status unless status.get_status == ::GRPC::Core::StatusCodes::OK
106
+ transaction.result
124
107
  end
125
108
 
126
109
  #
127
- # @TODO: unimplemented, not sure if this can be implemented because
128
- # the official grpc ruby client does not support non-blocking async
129
- # calls (https://github.com/grpc/grpc/issues/10973)
110
+ # @todo unimplemented, not sure if this can be implemented because
111
+ # the official grpc ruby client does not support non-blocking async
112
+ # calls (https://github.com/grpc/grpc/issues/10973)
130
113
  #
131
114
  # not 100% sure if grpc support is necessary for this.
132
115
  #
@@ -134,6 +117,30 @@ module Fabric
134
117
  raise NotYetImplemented
135
118
  end
136
119
 
120
+ #
121
+ # Get chaincode events emitted by transaction functions of the chaincode.
122
+ #
123
+ # @see Fabric::Client#chaincode_events Fabric::Client#chaincode_events - explanation of the different return types
124
+ # and example usage.
125
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:server_streamer Call options for options parameter
126
+ #
127
+ # @param [Integer] start_block Block number at which to start reading chaincode events.
128
+ # @param [Hash] call_options gRPC call options (merged with default_call_options from initializer)
129
+ # @yield [chaincode_event] loops through the chaincode events
130
+ # @yieldparam [Gateway::ChaincodeEventsResponse] chaincode_event the chaincode event
131
+ #
132
+ # @return [Enumerator|GRPC::ActiveCall::Operation|nil] Dependent on parameters passed;
133
+ # please see Fabric::Client#get_chaincode_events
134
+ #
135
+ def chaincode_events(start_block: nil, call_options: {}, &block)
136
+ network.chaincode_events(
137
+ self,
138
+ start_block: start_block,
139
+ call_options: call_options,
140
+ &block
141
+ )
142
+ end
143
+
137
144
  #
138
145
  # Creates a transaction proposal that can be evaluated or endorsed. Supports off-line signing flow.
139
146
  #
@@ -156,6 +163,14 @@ module Fabric
156
163
  Proposal.new(proposed_transaction)
157
164
  end
158
165
 
166
+ #
167
+ # Generates the qualified transaction name for the contract. (prepends the contract name to the transaction name if
168
+ # contract name is set)
169
+ #
170
+ # @param [string] transaction_name
171
+ #
172
+ # @return [string] qualified transaction name
173
+ #
159
174
  def qualified_transaction_name(transaction_name)
160
175
  contract_name.nil? || contract_name.empty? ? transaction_name : "#{contract_name}:#{transaction_name}"
161
176
  end
@@ -8,7 +8,7 @@ module Fabric
8
8
  #
9
9
  # Elliptic-curve Crypto Suite using OpenSSL
10
10
  #
11
- # @TODO missing tests
11
+ # @todo missing tests
12
12
  class ECCryptoSuite # rubocop:disable Metrics/ClassLength
13
13
  DEFAULT_KEY_SIZE = 256
14
14
  DEFAULT_DIGEST_ALGORITHM = 'SHA256'
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Encapsulates a Chaincode Events Request protobuf message
6
+ #
7
+ class ChaincodeEventsRequest
8
+ attr_reader :contract,
9
+ :start_block
10
+
11
+ # @!parse include Fabric::Accessors::Network
12
+ # @!parse include Fabric::Accessors::Gateway
13
+ include Fabric::Accessors::Contract
14
+
15
+ #
16
+ # Creates a new ChaincodeEventsRequest
17
+ #
18
+ # @param [Fabric::Contract] contract an instance of a contract
19
+ # @param [Integer] start_block Block number at which to start reading chaincode events.
20
+ #
21
+ def initialize(contract, start_block: nil)
22
+ @contract = contract
23
+ @start_block = start_block
24
+ end
25
+
26
+ #
27
+ # Returns the signed request
28
+ #
29
+ # @return [Gateway::SignedChaincodeEventsRequest] generated signed request
30
+ #
31
+ def signed_request
32
+ @signed_request ||= ::Gateway::SignedChaincodeEventsRequest.new(request: chaincode_events_request.to_proto)
33
+ end
34
+
35
+ #
36
+ # Returns the chaincode events request
37
+ #
38
+ # @return [Gateway::ChaincodeEventsRequest] chaincode events request - controls what events are returned
39
+ # from a chaincode events request
40
+ #
41
+ def chaincode_events_request
42
+ @chaincode_events_request ||= new_chaincode_events_request
43
+ end
44
+
45
+ #
46
+ # Get the serialized chaincode events request protobuffer message.
47
+ #
48
+ # @return [String] protobuffer serialized chaincode events request
49
+ #
50
+ def request_bytes
51
+ signed_request.request
52
+ end
53
+
54
+ #
55
+ # Get the digest of the chaincode events request. This is used to generate a digital signature.
56
+ #
57
+ # @return [String] chaincode events request digest
58
+ #
59
+ def request_digest
60
+ Fabric.crypto_suite.digest(request_bytes)
61
+ end
62
+
63
+ #
64
+ # Sets the signed request signature.
65
+ #
66
+ # @param [String] signature
67
+ #
68
+ # @return [void]
69
+ #
70
+ def signature=(signature)
71
+ signed_request.signature = signature
72
+ end
73
+
74
+ #
75
+ # Returns the signed_request signature
76
+ #
77
+ # @return [String] Raw byte string signature
78
+ #
79
+ def signature
80
+ signed_request.signature
81
+ end
82
+
83
+ #
84
+ # Sign the chaincode events request; Noop if request already signed.
85
+ #
86
+ # @return [void]
87
+ #
88
+ def sign
89
+ return if signed?
90
+
91
+ self.signature = signer.sign(request_bytes)
92
+ end
93
+
94
+ #
95
+ # Checks if the signed chaincode events has been signed.
96
+ #
97
+ # @return [Boolean] true if the signed chaincode events has been signed; otherwise false.
98
+ #
99
+ def signed?
100
+ !signed_request.signature.empty?
101
+ end
102
+
103
+ #
104
+ # Get chaincode events emitted by transaction functions of a specific chaincode.
105
+ #
106
+ # @see Fabric::Client#chaincode_events Fabric::Client#chaincode_events - explanation of the different return types
107
+ # and example usage.
108
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:server_streamer Call options for options parameter
109
+ #
110
+ # @param [Hash] options gRPC call options (merged with default_call_options from initializer)
111
+ # @yield [chaincode_event] loops through the chaincode events
112
+ # @yieldparam [Gateway::ChaincodeEventsResponse] chaincode_event the chaincode event
113
+ #
114
+ # @return [Enumerator|GRPC::ActiveCall::Operation|nil] Dependent on parameters passed;
115
+ # please see Fabric::Client#get_chaincode_events
116
+ #
117
+ def get_events(options = {}, &block)
118
+ sign
119
+
120
+ client.chaincode_events(signed_request, options, &block)
121
+ end
122
+
123
+ private
124
+
125
+ #
126
+ # Generates a new chaincode events request
127
+ #
128
+ # @return [Gateway::ChaincodeEventsRequest] chaincode events request - controls what events are returned
129
+ #
130
+ def new_chaincode_events_request
131
+ ::Gateway::ChaincodeEventsRequest.new(
132
+ channel_id: network_name,
133
+ chaincode_id: chaincode_name,
134
+ identity: signer.to_proto,
135
+ start_position: start_position
136
+ )
137
+ end
138
+
139
+ #
140
+ # Generates the start_position for the chaincode events request or returns the cached start_position
141
+ #
142
+ # @return [Orderer::SeekPosition] start position for the chaincode events request
143
+ #
144
+ def start_position
145
+ @start_position ||= new_start_position
146
+ end
147
+
148
+ #
149
+ # Generates the start position for the chaincode events request; if no start_block is specified,
150
+ # generates a seek next commit start position, otherwise generates a start_position to the start_block
151
+ #
152
+ # @return [Orderer::SeekPosition] start position for the chaincode events request
153
+ #
154
+ def new_start_position
155
+ specified = nil
156
+ next_commit = nil
157
+
158
+ if start_block
159
+ specified = ::Orderer::SeekSpecified.new(number: start_block)
160
+ else
161
+ next_commit = ::Orderer::SeekNextCommit.new
162
+ end
163
+ Orderer::SeekPosition.new(specified: specified, next_commit: next_commit)
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Encapsulates an Envelop protobuf message
6
+ #
7
+ class Envelope
8
+ # @return [Common::Envelope] transaction envelope
9
+ attr_reader :envelope
10
+
11
+ #
12
+ # Creates a new Envelope instance.
13
+ #
14
+ # @param [Common::Envelope] envelope
15
+ #
16
+ def initialize(envelope)
17
+ @envelope = envelope
18
+ end
19
+
20
+ #
21
+ # Checks if the envelope has been signed.
22
+ #
23
+ # @return [Boolean] true if the envelope has been signed; otherwise false.
24
+ #
25
+ def signed?
26
+ !envelope.signature.empty?
27
+ end
28
+
29
+ #
30
+ # The protobuffer serialized form of the envelope payload.
31
+ #
32
+ # @return [String] serialized payload
33
+ #
34
+ def payload_bytes
35
+ envelope.payload
36
+ end
37
+
38
+ #
39
+ # The digest of the payload.
40
+ #
41
+ # @return [String] payload digest
42
+ #
43
+ def payload_digest
44
+ Fabric.crypto_suite.digest(envelope.payload)
45
+ end
46
+
47
+ #
48
+ # Sets the envelope signature.
49
+ #
50
+ # @param [String] signature
51
+ #
52
+ # @return [void]
53
+ #
54
+ def signature=(signature)
55
+ envelope.signature = signature
56
+ end
57
+
58
+ #
59
+ # Returns the results from the transaction result payload.
60
+ #
61
+ # @return [Payload] transaction result payload
62
+ #
63
+ def result
64
+ @result ||= parse_result_from_payload
65
+ end
66
+
67
+ #
68
+ # Returns the deserialized payload.
69
+ #
70
+ # @return [Common::Payload] Envelope payload
71
+ #
72
+ def payload
73
+ @payload ||= Common::Payload.decode(envelope.payload)
74
+ end
75
+
76
+ #
77
+ # Returns the envelope payload header.
78
+ #
79
+ # Envelope => Payload => Header
80
+ #
81
+ # @return [Common::Header] Envelope Payload Header
82
+ #
83
+ def header
84
+ raise Fabric::Error, 'Missing header' if payload.header.nil?
85
+
86
+ @header ||= payload.header
87
+ end
88
+
89
+ #
90
+ # Returns the deserialized transaction channel header
91
+ #
92
+ # Envelope => Payload => Header => ChannelHeader
93
+ #
94
+ # @return [Common::ChannelHeader] envelop payload header channel header
95
+ #
96
+ def channel_header
97
+ @channel_header ||= Common::ChannelHeader.decode(header.channel_header)
98
+ end
99
+
100
+ #
101
+ # Grabs the channel_name frmo the depths of the envelope.
102
+ #
103
+ # @return [String] channel name
104
+ #
105
+ def channel_name
106
+ channel_header.channel_id
107
+ end
108
+
109
+ #
110
+ # Returns the deserialized transaction
111
+ #
112
+ # @return [Protos::Transaction] transaction
113
+ #
114
+ def transaction
115
+ @transaction ||= Protos::Transaction.decode(payload.data)
116
+ end
117
+
118
+ private
119
+
120
+ #
121
+ # Parse the transaction actions from the payload looking for the transaction result payload.
122
+ #
123
+ # @return [String] result payload
124
+ # @raise [Fabric::Error] if the transaction result payload is not found
125
+ #
126
+ def parse_result_from_payload
127
+ errors = []
128
+ transaction.actions.each do |action|
129
+ return parse_result_from_transaction_action(action)
130
+ rescue Fabric::Error => e
131
+ errors << e
132
+ end
133
+
134
+ raise Fabric::Error, "No proposal response found: #{errors.inspect}"
135
+ end
136
+
137
+ #
138
+ # Parse a single transaction action looking for the transaction result payload.
139
+ #
140
+ # @param [Protos::TransactionAction] transaction_action
141
+ #
142
+ # @return [Payload] transaction result payload
143
+ # @raise [Fabric::Error] if the endorsed_action is missing or the chaincode response is missing
144
+ #
145
+ def parse_result_from_transaction_action(transaction_action)
146
+ action_payload = Protos::ChaincodeActionPayload.decode(transaction_action.payload)
147
+ endorsed_action = action_payload.action
148
+ raise Fabric::Error, 'Missing endorsed action' if endorsed_action.nil?
149
+
150
+ response_payload = Protos::ProposalResponsePayload.decode(endorsed_action.proposal_response_payload)
151
+ chaincode_action = Protos::ChaincodeAction.decode(response_payload.extension)
152
+ chaincode_response = chaincode_action.response
153
+ raise Fabric::Error, 'Missing chaincode response' if chaincode_response.nil?
154
+
155
+ chaincode_response.payload
156
+ end
157
+ end
158
+ end
@@ -76,9 +76,9 @@ module Fabric
76
76
  #
77
77
  # Creates a new gateway passing in the current identity
78
78
  #
79
- # @param [Gateway::Gateway::Stub] connection <description>
79
+ # @param [Fabric::Client] client
80
80
  #
81
- # @return [Fabric::Gateway] <description>
81
+ # @return [Fabric::Gateway] gateway
82
82
  #
83
83
  def new_gateway(client)
84
84
  Fabric::Gateway.new(self, client)
@@ -22,33 +22,7 @@ module Fabric
22
22
  @proposed_transaction.contract
23
23
  end
24
24
 
25
- def network
26
- contract.network
27
- end
28
-
29
- def client
30
- network.client
31
- end
32
-
33
- def signer
34
- network.signer
35
- end
36
-
37
- def gateway
38
- network.gateway
39
- end
40
-
41
- def network_name
42
- network.name
43
- end
44
-
45
- def contract_name
46
- contract.contract_name
47
- end
48
-
49
- def chaincode_name
50
- contract.chaincode_name
51
- end
25
+ include Fabric::Accessors::Contract
52
26
 
53
27
  def transaction_id
54
28
  proposed_transaction.transaction_id
@@ -96,7 +70,7 @@ module Fabric
96
70
  # @return [String] raw binary digest of the proposal message.
97
71
  #
98
72
  def digest
99
- signer.digest(proposal.to_proto)
73
+ Fabric.crypto_suite.digest(proposal.to_proto)
100
74
  end
101
75
 
102
76
  #
@@ -153,8 +127,22 @@ module Fabric
153
127
  evaluate_response.result.payload
154
128
  end
155
129
 
156
- def endorse
157
- # TODO: endorse proposal
130
+ #
131
+ # Obtain endorsement for the transaction proposal from sufficient peers to allow it to be committed to the ledger.
132
+ #
133
+ # @param [Hash] options gRPC call options @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
134
+ #
135
+ # @return [Fabric::Transaction] An endorsed transaction that can be submitted to the ledger.
136
+ #
137
+ def endorse(options = {})
138
+ sign
139
+ endorse_response = client.endorse(new_endorse_request, options)
140
+
141
+ raise Fabric::Error, 'Missing transaction envelope' if endorse_response.prepared_transaction.nil?
142
+
143
+ prepared_transaction = new_prepared_transaction(endorse_response.prepared_transaction)
144
+
145
+ Fabric::Transaction.new(network, prepared_transaction)
158
146
  end
159
147
 
160
148
  #
@@ -170,13 +158,32 @@ module Fabric
170
158
  )
171
159
  end
172
160
 
161
+ #
162
+ # Creates a new endorse request from this proposal.
163
+ #
164
+ # @return [Gateway::EndorseRequest] EndorseRequest protobuf message
165
+ #
173
166
  def new_endorse_request
174
- # TODO
167
+ ::Gateway::EndorseRequest.new(
168
+ transaction_id: transaction_id,
169
+ channel_id: network_name,
170
+ proposed_transaction: signed_proposal,
171
+ endorsing_organizations: proposed_transaction.endorsing_organizations
172
+ )
175
173
  end
176
174
 
177
- def new_prepared_transaction
178
- # TODO
179
- # used in endorse
175
+ #
176
+ # Creates a new prepared transaction from a transaction envelope.
177
+ #
178
+ # @param [Common::Envelope] envelope transaction envelope
179
+ #
180
+ # @return [Gateway::PreparedTransaction] prepared transaction protobuf message
181
+ #
182
+ def new_prepared_transaction(envelope)
183
+ ::Gateway::PreparedTransaction.new(
184
+ transaction_id: transaction_id,
185
+ envelope: envelope
186
+ )
180
187
  end
181
188
  end
182
189
  end
@@ -19,6 +19,10 @@ module Fabric
19
19
  # This is usually used in conjunction with transientData for private data scenarios.
20
20
  attr_reader :endorsing_organizations
21
21
 
22
+ # @!parse include Fabric::Accessors::Network
23
+ # @!parse include Fabric::Accessors::Gateway
24
+ include Fabric::Accessors::Contract
25
+
22
26
  def initialize(contract, transaction_name, arguments: [], transient_data: {}, endorsing_organizations: [])
23
27
  @contract = contract
24
28
  @transaction_name = transaction_name
@@ -29,34 +33,6 @@ module Fabric
29
33
  generate_proposed_transaction
30
34
  end
31
35
 
32
- def network
33
- contract.network
34
- end
35
-
36
- def client
37
- network.client
38
- end
39
-
40
- def signer
41
- network.signer
42
- end
43
-
44
- def gateway
45
- network.gateway
46
- end
47
-
48
- def network_name
49
- network.name
50
- end
51
-
52
- def contract_name
53
- contract.contract_name
54
- end
55
-
56
- def chaincode_name
57
- contract.chaincode_name
58
- end
59
-
60
36
  #
61
37
  # Builds the proposed transaction protobuf message
62
38
  #
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Status of a transaction that is to be committed to the ledger.
6
+ #
7
+ class Status
8
+ TRANSACTION_STATUSES = ::Protos::TxValidationCode.constants.map(&::Protos::TxValidationCode.method(:const_get))
9
+ .collect do |i|
10
+ [::Protos::TxValidationCode.lookup(i), i]
11
+ end.to_h
12
+
13
+ # @return [Integer] Block number in which the transaction committed.
14
+ attr_reader :block_number
15
+
16
+ # @return [Integer] Transaction status
17
+ attr_reader :code
18
+
19
+ # @return [Boolean] `true` if the transaction committed successfully; otherwise `false`.
20
+ attr_reader :successful
21
+
22
+ # @return [String] The ID of the transaction.
23
+ attr_reader :transaction_id
24
+
25
+ def initialize(transaction_id, block_number, code)
26
+ @transaction_id = transaction_id
27
+ @block_number = block_number
28
+ @code = code
29
+ @successful = @code == TRANSACTION_STATUSES[:VALID]
30
+ end
31
+ end
32
+ end