fabric-gateway 0.0.1 → 0.3.0

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +3 -0
  3. data/.github/workflows/rspec.yml +37 -0
  4. data/.github/workflows/rubocop.yml +28 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +23 -0
  7. data/.ruby-version +1 -0
  8. data/.vscode/settings.json +7 -0
  9. data/.yardopts +2 -0
  10. data/CODE_OF_CONDUCT.md +105 -46
  11. data/Gemfile +5 -3
  12. data/LICENSE.txt +1 -1
  13. data/README.md +105 -7
  14. data/Rakefile +7 -3
  15. data/bin/console +4 -3
  16. data/bin/regenerate +19 -0
  17. data/bin/release +5 -0
  18. data/fabric-gateway.gemspec +32 -17
  19. data/lib/common/common_pb.rb +113 -0
  20. data/lib/common/policies_pb.rb +61 -0
  21. data/lib/fabric/accessors/contract.rb +51 -0
  22. data/lib/fabric/accessors/gateway.rb +33 -0
  23. data/lib/fabric/accessors/network.rb +40 -0
  24. data/lib/fabric/client.rb +146 -0
  25. data/lib/fabric/constants.rb +8 -0
  26. data/lib/fabric/contract.rb +154 -0
  27. data/lib/fabric/ec_crypto_suite.rb +199 -0
  28. data/lib/fabric/entities/envelope.rb +153 -0
  29. data/lib/fabric/entities/identity.rb +87 -0
  30. data/lib/fabric/entities/proposal.rb +189 -0
  31. data/lib/fabric/entities/proposed_transaction.rb +163 -0
  32. data/lib/fabric/entities/status.rb +32 -0
  33. data/lib/fabric/entities/transaction.rb +247 -0
  34. data/lib/fabric/gateway.rb +31 -6
  35. data/lib/fabric/network.rb +56 -0
  36. data/lib/fabric/version.rb +5 -0
  37. data/lib/fabric.rb +57 -0
  38. data/lib/gossip/message_pb.rb +236 -0
  39. data/lib/gossip/message_services_pb.rb +31 -0
  40. data/lib/msp/identities_pb.rb +25 -0
  41. data/lib/msp/msp_principal_pb.rb +57 -0
  42. data/lib/orderer/ab_pb.rb +64 -0
  43. data/lib/orderer/ab_services_pb.rb +30 -0
  44. data/lib/peer/chaincode_event_pb.rb +19 -0
  45. data/lib/peer/chaincode_pb.rb +69 -0
  46. data/lib/peer/proposal_pb.rb +41 -0
  47. data/lib/peer/proposal_response_pb.rb +52 -0
  48. data/lib/peer/transaction_pb.rb +74 -0
  49. metadata +184 -10
  50. data/lib/fabric/gateway/version.rb +0 -5
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Manages the instantiation and creation of the Gateway::ProposedTransaction Protobuf Message.
6
+ #
7
+ # Adapted from official fabric-gateway SDK ProposalBuilder and hyperledger-fabric-sdk:
8
+ # https://github.com/hyperledger/fabric-gateway/blob/1518e03ed3d6db1b6809e23e61a92744fd18e724/node/src/proposalbuilder.ts
9
+ # https://github.com/kirshin/hyperledger-fabric-sdk/blob/95a5a1a37001852312df25946e960a9ff149207e/lib/fabric/proposal.rb
10
+ class ProposedTransaction
11
+ attr_reader :contract,
12
+ :transaction_name,
13
+ :transient_data,
14
+ :arguments,
15
+ :proposed_transaction
16
+
17
+ # Specifies the set of organizations that will attempt to endorse the proposal.
18
+ # No other organizations' peers will be sent this proposal.
19
+ # This is usually used in conjunction with transientData for private data scenarios.
20
+ attr_reader :endorsing_organizations
21
+
22
+ # @!parse include Fabric::Accessors::Network
23
+ # @!parse include Fabric::Accessors::Gateway
24
+ include Fabric::Accessors::Contract
25
+
26
+ def initialize(contract, transaction_name, arguments: [], transient_data: {}, endorsing_organizations: [])
27
+ @contract = contract
28
+ @transaction_name = transaction_name
29
+ @arguments = arguments
30
+ @transient_data = transient_data
31
+ @endorsing_organizations = endorsing_organizations
32
+
33
+ generate_proposed_transaction
34
+ end
35
+
36
+ #
37
+ # Builds the proposed transaction protobuf message
38
+ #
39
+ # @return [Gateway::ProposedTransaction]
40
+ #
41
+ def generate_proposed_transaction
42
+ @proposed_transaction = ::Gateway::ProposedTransaction.new(
43
+ transaction_id: transaction_id,
44
+ proposal: signed_proposal,
45
+ endorsing_organizations: endorsing_organizations
46
+ )
47
+ end
48
+
49
+ def signed_proposal
50
+ @signed_proposal ||= Protos::SignedProposal.new(
51
+ proposal_bytes: proposal.to_proto
52
+ )
53
+ end
54
+
55
+ def proposal
56
+ @proposal ||= Protos::Proposal.new header: header.to_proto,
57
+ payload: chaincode_proposal_payload.to_proto
58
+ end
59
+
60
+ def header
61
+ Common::Header.new channel_header: channel_header.to_proto,
62
+ signature_header: signature_header.to_proto
63
+ end
64
+
65
+ def channel_header
66
+ Common::ChannelHeader.new type: Common::HeaderType::ENDORSER_TRANSACTION,
67
+ channel_id: network_name, tx_id: transaction_id,
68
+ extension: channel_header_extension.to_proto,
69
+ timestamp: timestamp, epoch: 0
70
+ # version: Constants::CHANNEL_HEADER_VERSION # official SDK does not send this.
71
+ end
72
+
73
+ def channel_header_extension
74
+ Protos::ChaincodeHeaderExtension.new chaincode_id: chaincode_id
75
+ end
76
+
77
+ def chaincode_id
78
+ Protos::ChaincodeID.new name: chaincode_name
79
+ end
80
+
81
+ def chaincode_proposal_payload
82
+ chaincode_input = Protos::ChaincodeInput.new args: [transaction_name] + arguments
83
+ chaincode_spec = Protos::ChaincodeSpec.new type: Protos::ChaincodeSpec::Type::NODE,
84
+ chaincode_id: chaincode_id,
85
+ input: chaincode_input
86
+ input = Protos::ChaincodeInvocationSpec.new chaincode_spec: chaincode_spec
87
+
88
+ Protos::ChaincodeProposalPayload.new input: input.to_proto, TransientMap: transient_data
89
+ end
90
+
91
+ #
92
+ # Returns the current timestamp
93
+ #
94
+ # @return [Google::Protobuf::Timestamp] gRPC timestamp
95
+ #
96
+ def timestamp
97
+ now = Time.now
98
+
99
+ @timestamp ||= Google::Protobuf::Timestamp.new seconds: now.to_i, nanos: now.nsec
100
+ end
101
+
102
+ #
103
+ # Generates a random nonce
104
+ #
105
+ # @return [String] random nonce
106
+ #
107
+ def nonce
108
+ @nonce ||= signer.crypto_suite.generate_nonce
109
+ end
110
+
111
+ #
112
+ # Generates a unique transaction ID for the transaction based on a random number and the signer
113
+ # or returns the existing transaction ID if it has already been generated.
114
+ #
115
+ # @return [String] transaction ID
116
+ #
117
+ def transaction_id
118
+ @transaction_id ||= signer.crypto_suite.hexdigest(nonce + signer.to_proto)
119
+ end
120
+
121
+ #
122
+ # Generates a SignatureHeader protobuf message from the signer and nonce
123
+ #
124
+ # @return [Common::SignatureHeader] signature header protobuf message instance
125
+ #
126
+ def signature_header
127
+ Common::SignatureHeader.new creator: signer.to_proto, nonce: nonce
128
+ end
129
+
130
+ # Dev note: if we have more classes that encapsulate protobuffer messages, consider
131
+ # creating an EncapsulatedPBMessage to hold the message and expose the following methods
132
+ # as common interface.
133
+
134
+ #
135
+ # Returns the protobuf message instance
136
+ #
137
+ # @return [Gateway::ProposedTransaction] protobuf message instance
138
+ #
139
+ def as_proto
140
+ proposed_transaction
141
+ end
142
+
143
+ #
144
+ # Returns the serialized Protobuf binary form of the proposed transaction
145
+ #
146
+ # @return [String] serialized Protobuf binary form of the proposed transaction
147
+ #
148
+ def to_proto
149
+ proposed_transaction.to_proto
150
+ end
151
+
152
+ #
153
+ # Returns the serialized JSON form of the proposed transaction
154
+ #
155
+ # @param [Hash] options JSON serialization options @see https://ruby-doc.org/stdlib-2.6.3/libdoc/json/rdoc/JSON.html#method-i-generate
156
+ #
157
+ # @return [String] serialized JSON form of the proposed transaction
158
+ #
159
+ def to_json(options = {})
160
+ proposed_transaction.to_json(options)
161
+ end
162
+ end
163
+ end
@@ -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
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Represents an endorsed transaction that can be submitted to the orderer for commit to the ledger,
6
+ # query the transaction results and its commit status.
7
+ #
8
+ class Transaction
9
+ attr_reader :network
10
+
11
+ include Fabric::Accessors::Network
12
+
13
+ # @return [Gateway::PreparedTransaction] Prepared Transaction
14
+ attr_reader :prepared_transaction
15
+
16
+ # @return [Fabric::Envelope]
17
+ attr_reader :envelope
18
+
19
+ #
20
+ # Creates a new Transaction instance.
21
+ #
22
+ # @param [Fabric::Network] network
23
+ # @param [Gateway::PreparedTransaction] prepared_transaction
24
+ #
25
+ def initialize(network, prepared_transaction)
26
+ @network = network
27
+ @prepared_transaction = prepared_transaction
28
+ @envelope = Envelope.new(prepared_transaction.envelope)
29
+ end
30
+
31
+ #
32
+ # Get the transaction result. This is obtained during the endorsement process when the transaction proposal is
33
+ # run on endorsing peers.
34
+ #
35
+ # @param [boolean] check_status set to true to raise exception if transaction has not yet been committed
36
+ #
37
+ # @return [String] Raw transaction result
38
+ #
39
+ def result(check_status: true)
40
+ raise Fabric::CommitError, status if check_status && !status.successful
41
+
42
+ envelope.result
43
+ end
44
+
45
+ #
46
+ # Returns the transaction ID from the prepared transaction.
47
+ #
48
+ # @return [String] transaction_id
49
+ #
50
+ def transaction_id
51
+ prepared_transaction.transaction_id
52
+ end
53
+
54
+ #
55
+ # Submit the transaction to the orderer to be committed to the ledger.
56
+ #
57
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
58
+ #
59
+ # @param [Hash] options gRPC call options
60
+ #
61
+ # @return [Fabric::Transaction] self
62
+ def submit(options = {})
63
+ sign_submit_request
64
+
65
+ client.submit(new_submit_request, options)
66
+
67
+ self
68
+ end
69
+
70
+ #
71
+ # Sign the transaction envelope.
72
+ #
73
+ # @return [void]
74
+ def sign_submit_request
75
+ return if submit_request_signed?
76
+
77
+ signature = signer.sign(envelope.payload_bytes)
78
+ self.submit_request_signature = signature
79
+ end
80
+
81
+ #
82
+ # Returns true if the transaction envelope has been signed.
83
+ #
84
+ # @return [Boolean] true if signed; false otherwise
85
+ #
86
+ def submit_request_signed?
87
+ @envelope.signed?
88
+ end
89
+
90
+ #
91
+ # Digest to be signed to support offline signing of the submit request
92
+ #
93
+ # @return [String] digest of the submit request
94
+ #
95
+ def submit_request_digest
96
+ envelope.payload_digest
97
+ end
98
+
99
+ #
100
+ # Sets the submit request signature. This is used to support offline signing of the submit request.
101
+ #
102
+ # @param [String] signature
103
+ #
104
+ # @return [void]
105
+ #
106
+ def submit_request_signature=(signature)
107
+ envelope.signature = signature
108
+ end
109
+
110
+ #
111
+ # Get status of the committed transaction. If the transaction has not yet committed, this method blocks until the
112
+ # commit occurs. If status is already queried, this returns status from cache and does not make additional queries.
113
+ #
114
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
115
+ #
116
+ # @param [Hash] options gRPC call options
117
+ #
118
+ # @return [Fabric::Status] status of the committed transaction
119
+ #
120
+ def status(options = {})
121
+ @status ||= query_status(options)
122
+ end
123
+
124
+ #
125
+ # Digest to be signed to support offline signing of the commit status request
126
+ #
127
+ # @return [String] digest of the commit status request
128
+ #
129
+ def status_request_digest
130
+ Fabric.crypto_suite.digest(signed_commit_status_request.request)
131
+ end
132
+
133
+ #
134
+ # Sets the status request signature. This is used to support offline signing of the commit status request.
135
+ #
136
+ # @param [String] signature
137
+ #
138
+ # @return [void]
139
+ #
140
+ def status_request_signature=(signature)
141
+ signed_commit_status_request.signature = signature
142
+ end
143
+
144
+ #
145
+ # Returns true if the signed commit status request has been signed.
146
+ #
147
+ # @return [Boolean] true if signed; false otherwise
148
+ #
149
+ def status_request_signed?
150
+ !signed_commit_status_request.signature.empty?
151
+ end
152
+
153
+ #
154
+ # Sign the signed commit status request
155
+ #
156
+ # @return [Fabric::Transaction] self
157
+ #
158
+ def sign_status_request
159
+ return if status_request_signed?
160
+
161
+ signature = signer.sign(signed_commit_status_request.request)
162
+ signed_commit_status_request.signature = signature
163
+
164
+ self
165
+ end
166
+
167
+ #
168
+ # Returns the current instance of the signed commit status request. Necessary so we can keep the state of the
169
+ # signature in the transaction object.
170
+ #
171
+ # @return [Gateway::SignedCommitStatusRequest] signed commit status request
172
+ #
173
+ def signed_commit_status_request
174
+ @signed_commit_status_request ||= new_signed_commit_status_request
175
+ end
176
+
177
+ private
178
+
179
+ #
180
+ # Actual status query call used by status method.
181
+ #
182
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
183
+ #
184
+ # @param [Hash] options gRPC call options
185
+ #
186
+ # @return [Fabric::Status] status of the committed transaction
187
+ #
188
+ def query_status(options = {})
189
+ sign_status_request
190
+
191
+ commit_status_response = client.commit_status(signed_commit_status_request, options)
192
+ new_status(commit_status_response)
193
+ end
194
+
195
+ #
196
+ # Generates a new signed commit status request
197
+ #
198
+ # @return [Gateway::SignedCommitStatusRequest] signed commit status request protobuf message
199
+ #
200
+ def new_signed_commit_status_request
201
+ ::Gateway::SignedCommitStatusRequest.new(
202
+ request: new_commit_status_request.to_proto
203
+ )
204
+ end
205
+
206
+ #
207
+ # Generates a new commit status request
208
+ #
209
+ # @return [Gateway::CommitStatusRequest] commit status request protobuf message
210
+ #
211
+ def new_commit_status_request
212
+ ::Gateway::CommitStatusRequest.new(
213
+ channel_id: network_name,
214
+ transaction_id: transaction_id,
215
+ identity: signer.to_proto
216
+ )
217
+ end
218
+
219
+ #
220
+ # Generates a new submit request.
221
+ #
222
+ # @return [Gateway::SubmitRequest] submit request protobuf message
223
+ #
224
+ def new_submit_request
225
+ ::Gateway::SubmitRequest.new(
226
+ transaction_id: transaction_id,
227
+ channel_id: network_name,
228
+ prepared_transaction: envelope.envelope
229
+ )
230
+ end
231
+
232
+ #
233
+ # New Status from CommitStatusResponse
234
+ #
235
+ # @param [Gateway::CommitStatusResponse] response commit status response
236
+ #
237
+ # @return [Fabric::Status] transaction status
238
+ #
239
+ def new_status(response)
240
+ Fabric::Status.new(
241
+ transaction_id,
242
+ response.block_number,
243
+ Fabric::Status::TRANSACTION_STATUSES[response.result]
244
+ )
245
+ end
246
+ end
247
+ end
@@ -1,10 +1,35 @@
1
- require "fabric/gateway/version"
2
- require "gateway/gateway_pb"
3
- require "gateway/gateway_services_pb"
1
+ # frozen_string_literal: true
4
2
 
5
3
  module Fabric
6
- module Gateway
7
- class Error < StandardError; end
8
- # Your code goes here...
4
+ #
5
+ # Gateway represents the connection of a specific client identity to a Fabric Gateway.
6
+ #
7
+ class Gateway
8
+ attr_reader :signer, :client
9
+
10
+ #
11
+ # Initialize a new Gateway
12
+ #
13
+ # @param [Fabric::Identity] signer identity utilized to sign transactions
14
+ # @param [Fabric::Client] client Gateway Client
15
+ #
16
+ def initialize(signer, client)
17
+ raise InvalidArgument, 'signer must be Fabric::Identity' unless signer.is_a? Fabric::Identity
18
+ raise InvalidArgument, 'client must be Fabric::Client' unless client.is_a? Fabric::Client
19
+
20
+ @signer = signer
21
+ @client = client
22
+ end
23
+
24
+ #
25
+ # Initialize new network from the Gateway
26
+ #
27
+ # @param [string] name channel name
28
+ #
29
+ # @return [Fabric::Network] returns a new network
30
+ #
31
+ def new_network(name)
32
+ Network.new(self, name)
33
+ end
9
34
  end
10
35
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Network represents a blockchain network, or Fabric channel. The Network can be used to access deployed smart
6
+ # contracts, and to listen for events emitted when blocks are committed to the ledger.
7
+ #
8
+ # JChan & THowe believe this should be called a Channel, however Hyperledger Fabric has decided upon the terminology
9
+ # network - https://github.com/hyperledger/fabric-gateway/issues/355#issuecomment-997888071
10
+ #
11
+ class Network
12
+ attr_reader :gateway, :name
13
+
14
+ # @!parse include Fabric::Accessors::Gateway
15
+ include Fabric::Accessors::Gateway
16
+
17
+ def initialize(gateway, name)
18
+ @gateway = gateway
19
+ @name = name
20
+ end
21
+
22
+ #
23
+ # Creates a new contract instance
24
+ #
25
+ # @param [string] chaincode_name name of the chaincode
26
+ # @param [string] contract_name optional name of the contract
27
+ #
28
+ # @return [Fabric::Contract] new contract instance
29
+ #
30
+ def new_contract(chaincode_name, contract_name = '')
31
+ Contract.new(self, chaincode_name, contract_name)
32
+ end
33
+
34
+ #
35
+ # @todo original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
36
+ # @see https://github.com/hyperledger/fabric-gateway/blob/08118cf0a792898925d0b2710b0a9e7c5ec23228/node/src/network.ts
37
+ # @see https://github.com/hyperledger/fabric-gateway/blob/main/pkg/client/network.go
38
+ #
39
+ # @return [?] ?
40
+ #
41
+ def new_chaincode_events
42
+ raise NotYetImplemented
43
+ end
44
+
45
+ #
46
+ # @todo original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
47
+ # @see https://github.com/hyperledger/fabric-gateway/blob/08118cf0a792898925d0b2710b0a9e7c5ec23228/node/src/network.ts
48
+ # @see https://github.com/hyperledger/fabric-gateway/blob/main/pkg/client/network.go
49
+ #
50
+ # @return [?] ?
51
+ #
52
+ def new_chaincode_events_request
53
+ raise NotImplementedError
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ VERSION = '0.3.0'
5
+ end
data/lib/fabric.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gateway/gateway_pb'
4
+ require 'gateway/gateway_services_pb'
5
+
6
+ require 'fabric/accessors/gateway'
7
+ require 'fabric/accessors/network'
8
+ require 'fabric/accessors/contract'
9
+
10
+ require 'fabric/entities/envelope'
11
+ require 'fabric/entities/identity'
12
+ require 'fabric/entities/proposal'
13
+ require 'fabric/entities/proposed_transaction'
14
+ require 'fabric/entities/status'
15
+ require 'fabric/entities/transaction'
16
+
17
+ require 'fabric/constants'
18
+ require 'fabric/contract'
19
+ require 'fabric/client'
20
+ require 'fabric/ec_crypto_suite'
21
+ require 'fabric/gateway'
22
+ require 'fabric/network'
23
+ require 'fabric/version'
24
+
25
+ #
26
+ # Hyperledger Fabric Gateway SDK
27
+ #
28
+ module Fabric
29
+ class Error < StandardError; end
30
+ class InvalidArgument < Error; end
31
+ class NotYetImplemented < Error; end
32
+
33
+ #
34
+ # CommitError
35
+ #
36
+ # @todo TEST ME!
37
+ #
38
+ class CommitError < Error
39
+ attr_reader :code, :transaction_id
40
+
41
+ #
42
+ # Creates a transaction commit error from the status
43
+ #
44
+ # @param [Fabric::Status] status transaction status
45
+ #
46
+ def initialize(status)
47
+ super("Transaction #{status.transaction_id} failed to commit with status code #{status.code} - " +
48
+ Status::TRANSACTION_STATUSES.key(status.code).to_s)
49
+ @code = code
50
+ @transaction_id = status.transaction_id
51
+ end
52
+ end
53
+
54
+ def self.crypto_suite(opts = {})
55
+ @crypto_suite ||= Fabric::ECCryptoSuite.new opts
56
+ end
57
+ end