fabric-gateway 0.1.0 → 0.2.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.
@@ -0,0 +1,61 @@
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
+ def initialize(gateway, name)
15
+ @gateway = gateway
16
+ @name = name
17
+ end
18
+
19
+ def client
20
+ gateway.client
21
+ end
22
+
23
+ def signer
24
+ gateway.signer
25
+ end
26
+
27
+ #
28
+ # Creates a new contract instance
29
+ #
30
+ # @param [string] chaincode_name name of the chaincode
31
+ # @param [string] contract_name optional name of the contract
32
+ #
33
+ # @return [Fabric::Contract] new contract instance
34
+ #
35
+ def new_contract(chaincode_name, contract_name = '')
36
+ Contract.new(self, chaincode_name, contract_name)
37
+ end
38
+
39
+ #
40
+ # @TODO: original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
41
+ # @see https://github.com/hyperledger/fabric-gateway/blob/08118cf0a792898925d0b2710b0a9e7c5ec23228/node/src/network.ts
42
+ # @see https://github.com/hyperledger/fabric-gateway/blob/main/pkg/client/network.go
43
+ #
44
+ # @return [?] ?
45
+ #
46
+ def new_chaincode_events
47
+ raise NotYetImplemented
48
+ end
49
+
50
+ #
51
+ # @TODO: original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
52
+ # @see https://github.com/hyperledger/fabric-gateway/blob/08118cf0a792898925d0b2710b0a9e7c5ec23228/node/src/network.ts
53
+ # @see https://github.com/hyperledger/fabric-gateway/blob/main/pkg/client/network.go
54
+ #
55
+ # @return [?] ?
56
+ #
57
+ def new_chaincode_events_request
58
+ raise NotImplementedError
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ #
5
+ # Proposal represents a transaction proposal that can be sent to peers for endorsement or evaluated as a query.
6
+ #
7
+ # Combined ProposalBuilder with Proposal. Utilizing instance variables and functions in proposal seem adaquate enough
8
+ # to fully create the proposal. ProposalBuilder did not seem like a native ruby design pattern.
9
+ class Proposal
10
+ attr_reader :proposed_transaction
11
+
12
+ #
13
+ # Instantiates a new Proposal
14
+ #
15
+ # @param [Fabric::ProposedTransaction] proposed_transaction ProposedTransaction container class
16
+ #
17
+ def initialize(proposed_transaction)
18
+ @proposed_transaction = proposed_transaction
19
+ end
20
+
21
+ def contract
22
+ @proposed_transaction.contract
23
+ end
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
52
+
53
+ def transaction_id
54
+ proposed_transaction.transaction_id
55
+ end
56
+
57
+ #
58
+ # Returns the proposal message as a protobuf Message object.
59
+ #
60
+ # @return [Protos::Proposal|nil] Proposal message
61
+ #
62
+ def proposal
63
+ proposed_transaction.proposal
64
+ end
65
+
66
+ #
67
+ # Returns the signed proposal
68
+ #
69
+ # <rant>
70
+ # Fabric message naming scheme is a mess:
71
+ # ProposedTransaction has a Proposal which is a SignedProposal
72
+ # which has a Proposal which is a Proposal
73
+ # so.... which proposal do you want to access? Adding this function for clarity
74
+ # </rant>
75
+ #
76
+ # @return [Protos::SignedProposal|nil] SignedProposal message
77
+ #
78
+ def signed_proposal
79
+ proposed_transaction.proposed_transaction.proposal
80
+ end
81
+
82
+ #
83
+ # Serialized bytes of the proposal message in proto3 format.
84
+ #
85
+ # @return [String] Binary representation of the proposal message.
86
+ #
87
+ def to_proto
88
+ proposed_transaction.to_proto
89
+ end
90
+
91
+ #
92
+ # Proposal digest which can be utilized for offline signing.
93
+ # If signing offline, call signature= to set signature once
94
+ # computed.
95
+ #
96
+ # @return [String] raw binary digest of the proposal message.
97
+ #
98
+ def digest
99
+ signer.digest(proposal.to_proto)
100
+ end
101
+
102
+ #
103
+ # Sets the signature of the signed proposal in the proposed transaction
104
+ #
105
+ # @param [String] signature raw byte string signature of the proposal message
106
+ # (should be the signature of the proposed message digest)
107
+ #
108
+ def signature=(signature)
109
+ proposed_transaction.signed_proposal.signature = signature
110
+ end
111
+
112
+ #
113
+ # Returns the signed proposal signature
114
+ #
115
+ # @return [String] Raw byte string signature
116
+ #
117
+ def signature
118
+ proposed_transaction.signed_proposal.signature
119
+ end
120
+
121
+ #
122
+ # Returns true if the signed proposal has a signature
123
+ #
124
+ # @return [Boolean] true|false
125
+ #
126
+ def signed?
127
+ # signature cannot be nil because google protobuf won't let it
128
+ !proposed_transaction.signed_proposal.signature.empty?
129
+ end
130
+
131
+ #
132
+ # Utilizes the signer to sign the proposal message if it has not been signed yet.
133
+ #
134
+ def sign
135
+ return if signed?
136
+
137
+ self.signature = signer.sign proposal.to_proto
138
+ end
139
+
140
+ #
141
+ # Evaluate the transaction proposal and obtain its result, without updating the ledger. This runs the transaction
142
+ # on a peer to obtain a transaction result, but does not submit the endorsed transaction to the orderer to be
143
+ # committed to the ledger.
144
+ #
145
+ # @param [Hash] options gRPC call options @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
146
+ #
147
+ # @return [String] The result returned by the transaction function
148
+ #
149
+ def evaluate(options = {})
150
+ sign
151
+
152
+ evaluate_response = client.evaluate(new_evaluate_request, options)
153
+ evaluate_response.result.payload
154
+ end
155
+
156
+ def endorse
157
+ # TODO: endorse proposal
158
+ end
159
+
160
+ #
161
+ # Generates an evaluate request from this proposal.
162
+ #
163
+ # @return [Gateway::EvaluateRequest] evaluation request with the current proposal
164
+ #
165
+ def new_evaluate_request
166
+ ::Gateway::EvaluateRequest.new(
167
+ channel_id: network_name,
168
+ proposed_transaction: signed_proposal,
169
+ target_organizations: proposed_transaction.endorsing_organizations
170
+ )
171
+ end
172
+
173
+ def new_endorse_request
174
+ # TODO
175
+ end
176
+
177
+ def new_prepared_transaction
178
+ # TODO
179
+ # used in endorse
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,187 @@
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
+ def initialize(contract, transaction_name, arguments: [], transient_data: {}, endorsing_organizations: [])
23
+ @contract = contract
24
+ @transaction_name = transaction_name
25
+ @arguments = arguments
26
+ @transient_data = transient_data
27
+ @endorsing_organizations = endorsing_organizations
28
+
29
+ generate_proposed_transaction
30
+ end
31
+
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
+ #
61
+ # Builds the proposed transaction protobuf message
62
+ #
63
+ # @return [Gateway::ProposedTransaction]
64
+ #
65
+ def generate_proposed_transaction
66
+ @proposed_transaction = ::Gateway::ProposedTransaction.new(
67
+ transaction_id: transaction_id,
68
+ proposal: signed_proposal,
69
+ endorsing_organizations: endorsing_organizations
70
+ )
71
+ end
72
+
73
+ def signed_proposal
74
+ @signed_proposal ||= Protos::SignedProposal.new(
75
+ proposal_bytes: proposal.to_proto
76
+ )
77
+ end
78
+
79
+ def proposal
80
+ @proposal ||= Protos::Proposal.new header: header.to_proto,
81
+ payload: chaincode_proposal_payload.to_proto
82
+ end
83
+
84
+ def header
85
+ Common::Header.new channel_header: channel_header.to_proto,
86
+ signature_header: signature_header.to_proto
87
+ end
88
+
89
+ def channel_header
90
+ Common::ChannelHeader.new type: Common::HeaderType::ENDORSER_TRANSACTION,
91
+ channel_id: network_name, tx_id: transaction_id,
92
+ extension: channel_header_extension.to_proto,
93
+ timestamp: timestamp, epoch: 0
94
+ # version: Constants::CHANNEL_HEADER_VERSION # official SDK does not send this.
95
+ end
96
+
97
+ def channel_header_extension
98
+ Protos::ChaincodeHeaderExtension.new chaincode_id: chaincode_id
99
+ end
100
+
101
+ def chaincode_id
102
+ Protos::ChaincodeID.new name: chaincode_name
103
+ end
104
+
105
+ def chaincode_proposal_payload
106
+ chaincode_input = Protos::ChaincodeInput.new args: [transaction_name] + arguments
107
+ chaincode_spec = Protos::ChaincodeSpec.new type: Protos::ChaincodeSpec::Type::NODE,
108
+ chaincode_id: chaincode_id,
109
+ input: chaincode_input
110
+ input = Protos::ChaincodeInvocationSpec.new chaincode_spec: chaincode_spec
111
+
112
+ Protos::ChaincodeProposalPayload.new input: input.to_proto, TransientMap: transient_data
113
+ end
114
+
115
+ #
116
+ # Returns the current timestamp
117
+ #
118
+ # @return [Google::Protobuf::Timestamp] gRPC timestamp
119
+ #
120
+ def timestamp
121
+ now = Time.now
122
+
123
+ @timestamp ||= Google::Protobuf::Timestamp.new seconds: now.to_i, nanos: now.nsec
124
+ end
125
+
126
+ #
127
+ # Generates a random nonce
128
+ #
129
+ # @return [String] random nonce
130
+ #
131
+ def nonce
132
+ @nonce ||= signer.crypto_suite.generate_nonce
133
+ end
134
+
135
+ #
136
+ # Generates a unique transaction ID for the transaction based on a random number and the signer
137
+ # or returns the existing transaction ID if it has already been generated.
138
+ #
139
+ # @return [String] transaction ID
140
+ #
141
+ def transaction_id
142
+ @transaction_id ||= signer.crypto_suite.hexdigest(nonce + signer.to_proto)
143
+ end
144
+
145
+ #
146
+ # Generates a SignatureHeader protobuf message from the signer and nonce
147
+ #
148
+ # @return [Common::SignatureHeader] signature header protobuf message instance
149
+ #
150
+ def signature_header
151
+ Common::SignatureHeader.new creator: signer.to_proto, nonce: nonce
152
+ end
153
+
154
+ # Dev note: if we have more classes that encapsulate protobuffer messages, consider
155
+ # creating an EncapsulatedPBMessage to hold the message and expose the following methods
156
+ # as common interface.
157
+
158
+ #
159
+ # Returns the protobuf message instance
160
+ #
161
+ # @return [Gateway::ProposedTransaction] protobuf message instance
162
+ #
163
+ def as_proto
164
+ proposed_transaction
165
+ end
166
+
167
+ #
168
+ # Returns the serialized Protobuf binary form of the proposed transaction
169
+ #
170
+ # @return [String] serialized Protobuf binary form of the proposed transaction
171
+ #
172
+ def to_proto
173
+ proposed_transaction.to_proto
174
+ end
175
+
176
+ #
177
+ # Returns the serialized JSON form of the proposed transaction
178
+ #
179
+ # @param [Hash] options JSON serialization options @see https://ruby-doc.org/stdlib-2.6.3/libdoc/json/rdoc/JSON.html#method-i-generate
180
+ #
181
+ # @return [String] serialized JSON form of the proposed transaction
182
+ #
183
+ def to_json(options = {})
184
+ proposed_transaction.to_json(options)
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ VERSION = '0.2.0'
5
+ end
data/lib/fabric.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fabric/constants'
4
+ require 'fabric/contract'
5
+ require 'fabric/client'
6
+ require 'fabric/ec_crypto_suite'
7
+ require 'fabric/gateway'
8
+ require 'fabric/identity'
9
+ require 'fabric/network'
10
+ require 'fabric/proposal'
11
+ require 'fabric/proposed_transaction'
12
+ require 'fabric/version'
13
+
14
+ require 'gateway/gateway_pb'
15
+ require 'gateway/gateway_services_pb'
16
+
17
+ #
18
+ # Hyperledger Fabric Gateway SDK
19
+ #
20
+ module Fabric
21
+ class Error < StandardError; end
22
+ class InvalidArgument < Error; end
23
+ class NotYetImplemented < Error; end
24
+
25
+ #
26
+ # CommitError
27
+ #
28
+ # @TODO: TEST ME!
29
+ #
30
+ class CommitError < Error
31
+ attr_reader :code, :transaction_id
32
+
33
+ def initialize(status)
34
+ super("Transaction #{status.transaction_id} failed to commit with status code #{status.code} -" +
35
+ Protos::TxValidationCode.lookup(status.code).to_s)
36
+ @code = code
37
+ @transaction_id = status.transaction_id
38
+ end
39
+ end
40
+
41
+ def self.crypto_suite(opts = {})
42
+ @crypto_suite ||= Fabric::ECCryptoSuite.new opts
43
+ end
44
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fabric-gateway
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Chan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-17 00:00:00.000000000 Z
11
+ date: 2021-12-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.42'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.6.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.6.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: factory_bot
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 6.2.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 6.2.0
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: grpc-tools
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +80,62 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '1.42'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.23.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.23.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.6.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.6.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.21.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.21.2
125
+ - !ruby/object:Gem::Dependency
126
+ name: timecop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.9.4
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.9.4
55
139
  description: 'Hyperledger Fabric Gateway gRPC SDK generated directly from protos found
56
140
  at: https://github.com/hyperledger/fabric-protos.'
57
141
  email:
@@ -60,33 +144,43 @@ executables: []
60
144
  extensions: []
61
145
  extra_rdoc_files: []
62
146
  files:
147
+ - ".editorconfig"
148
+ - ".github/workflows/rspec.yml"
149
+ - ".github/workflows/rubocop.yml"
63
150
  - ".gitignore"
64
151
  - ".gitmodules"
65
152
  - ".rspec"
153
+ - ".rubocop.yml"
154
+ - ".ruby-version"
66
155
  - ".travis.yml"
156
+ - ".vscode/settings.json"
67
157
  - CHANGELOG.md
68
158
  - CODE_OF_CONDUCT.md
69
159
  - Gemfile
70
- - Gemfile.lock
71
160
  - LICENSE.txt
72
161
  - README.md
73
162
  - Rakefile
74
163
  - TAGS
75
164
  - bin/console
76
165
  - bin/regenerate
166
+ - bin/release
77
167
  - bin/setup
78
168
  - fabric-gateway.gemspec
79
169
  - lib/.DS_Store
80
170
  - lib/common/common_pb.rb
81
171
  - lib/common/policies_pb.rb
172
+ - lib/fabric.rb
82
173
  - lib/fabric/.DS_Store
174
+ - lib/fabric/client.rb
175
+ - lib/fabric/constants.rb
176
+ - lib/fabric/contract.rb
177
+ - lib/fabric/ec_crypto_suite.rb
83
178
  - lib/fabric/gateway.rb
84
- - lib/fabric/gateway/client.rb
85
- - lib/fabric/gateway/constants.rb
86
- - lib/fabric/gateway/ec_crypto_suite.rb
87
- - lib/fabric/gateway/identity.rb
88
- - lib/fabric/gateway/proposal.rb
89
- - lib/fabric/gateway/version.rb
179
+ - lib/fabric/identity.rb
180
+ - lib/fabric/network.rb
181
+ - lib/fabric/proposal.rb
182
+ - lib/fabric/proposed_transaction.rb
183
+ - lib/fabric/version.rb
90
184
  - lib/gateway/gateway_pb.rb
91
185
  - lib/gateway/gateway_services_pb.rb
92
186
  - lib/gossip/message_pb.rb
@@ -104,10 +198,7 @@ homepage: https://github.com/ethicalidentity/fabric-gateway-ruby
104
198
  licenses:
105
199
  - MIT
106
200
  metadata:
107
- allowed_push_host: https://rubygems.org
108
- homepage_uri: https://github.com/ethicalidentity/fabric-gateway-ruby
109
- source_code_uri: https://github.com/ethicalidentity/fabric-gateway-ruby
110
- changelog_uri: https://github.com/EthicalIdentity/fabric-gateway-ruby/blob/master/CHANGELOG.md
201
+ rubygems_mfa_required: 'true'
111
202
  post_install_message:
112
203
  rdoc_options: []
113
204
  require_paths:
@@ -116,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
207
  requirements:
117
208
  - - ">="
118
209
  - !ruby/object:Gem::Version
119
- version: 2.3.0
210
+ version: 2.6.0
120
211
  required_rubygems_version: !ruby/object:Gem::Requirement
121
212
  requirements:
122
213
  - - ">="
data/Gemfile.lock DELETED
@@ -1,44 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- fabric-gateway (0.0.2)
5
- google-protobuf (>= 3.19.1)
6
- grpc (~> 1.42)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- diff-lcs (1.4.4)
12
- google-protobuf (3.19.1)
13
- googleapis-common-protos-types (1.3.0)
14
- google-protobuf (~> 3.14)
15
- grpc (1.42.0)
16
- google-protobuf (~> 3.18)
17
- googleapis-common-protos-types (~> 1.0)
18
- grpc-tools (1.42.0)
19
- rake (12.3.2)
20
- rspec (3.10.0)
21
- rspec-core (~> 3.10.0)
22
- rspec-expectations (~> 3.10.0)
23
- rspec-mocks (~> 3.10.0)
24
- rspec-core (3.10.1)
25
- rspec-support (~> 3.10.0)
26
- rspec-expectations (3.10.1)
27
- diff-lcs (>= 1.2.0, < 2.0)
28
- rspec-support (~> 3.10.0)
29
- rspec-mocks (3.10.2)
30
- diff-lcs (>= 1.2.0, < 2.0)
31
- rspec-support (~> 3.10.0)
32
- rspec-support (3.10.3)
33
-
34
- PLATFORMS
35
- ruby
36
-
37
- DEPENDENCIES
38
- fabric-gateway!
39
- grpc-tools (~> 1.42)
40
- rake (~> 12.0)
41
- rspec (~> 3.0)
42
-
43
- BUNDLED WITH
44
- 2.1.4