fabric-gateway 0.0.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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