fabric-gateway 0.2.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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