fabric-gateway 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d791b2c862c686b8d4d6877dbf5fedd6563b4d5ba2092c832a018deb5d48c575
4
- data.tar.gz: 239128c906f1ac613d562d929a6ff5b9cc72d0e9dc1adfba3b9b4a8e306228f1
3
+ metadata.gz: 84dbcb3d572350e328ca48178859e0edfc7f57bbc331980612c4c5f537701b12
4
+ data.tar.gz: 9aaeed65be8ea7b5296e4e4fb12d8027583581cca24c96ccdf4f7a3afb781758
5
5
  SHA512:
6
- metadata.gz: ec91a9bca5003ec961e93ce670d8006a698c071f51b81a7d9ab6dcd011480597e7decc396607f6f4fbef5b29cd0b7ab2986239f558711ebd333599da8d07eddd
7
- data.tar.gz: 6795e691fa38795aacc3e9cf0d9472cb9924105b184d283aa13ad1b7732445a334a713bc6ed79e6c283c70aa51ebbee37ace55f84e5a9d68a0c96aee5711f499
6
+ metadata.gz: a931d1e6fff596991560afedd817f3091e0c393c4b6aefa9eed5f3dfee19f37db8b1f744c9ea9b4375d661bdf1c0b4c72402eb9048225be7d028569520fb8d23
7
+ data.tar.gz: 553c329d4dbb5b9b7729e5c1226f2c680901e40e27f630ce1f577fd9f4c33eb648b06a8ab92630873c8ded4be7ba4870b82fbef236a0520d848371b09d5de848
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Fabric::Gateway
2
2
 
3
- ![Rspec Tests](https://github.com/EthicalIdentity/fabric-gateway-ruby/actions/workflows/rspec.yml/badge.svg) [![codecov](https://codecov.io/gh/EthicalIdentity/fabric-gateway-ruby/branch/master/graph/badge.svg?token=AXHQEN0R2R)](https://codecov.io/gh/EthicalIdentity/fabric-gateway-ruby) [![Maintainability](https://api.codeclimate.com/v1/badges/84bab9bb5911d3564df6/maintainability)](https://codeclimate.com/github/EthicalIdentity/fabric-gateway-ruby/maintainability) ![Gem](https://img.shields.io/gem/v/fabric-gateway) ![Downloads](https://img.shields.io/gem/dt/fabric-gateway) [![GitHub license](https://img.shields.io/github/license/EthicalIdentity/fabric-gateway-ruby)](https://github.com/EthicalIdentity/fabric-gateway-ruby/blob/master/LICENSE.txt)
3
+ [![Rspec Tests](https://github.com/EthicalIdentity/fabric-gateway-ruby/actions/workflows/rspec.yml/badge.svg)](https://github.com/EthicalIdentity/fabric-gateway-ruby/actions/workflows/rspec.yml?query=branch%3Amaster) [![codecov](https://codecov.io/gh/EthicalIdentity/fabric-gateway-ruby/branch/master/graph/badge.svg?token=AXHQEN0R2R)](https://codecov.io/gh/EthicalIdentity/fabric-gateway-ruby) [![Maintainability](https://api.codeclimate.com/v1/badges/84bab9bb5911d3564df6/maintainability)](https://codeclimate.com/github/EthicalIdentity/fabric-gateway-ruby/maintainability) [![Gem](https://img.shields.io/gem/v/fabric-gateway)](https://rubygems.org/gems/fabric-gateway) [![Downloads](https://img.shields.io/gem/dt/fabric-gateway)](https://rubygems.org/gems/fabric-gateway) [![GitHub license](https://img.shields.io/github/license/EthicalIdentity/fabric-gateway-ruby)](https://github.com/EthicalIdentity/fabric-gateway-ruby/blob/master/LICENSE.txt)
4
4
 
5
5
 
6
6
 
@@ -32,11 +32,13 @@ Will update to new version of grpc when fix is released.
32
32
 
33
33
  ## Usage
34
34
 
35
- This is an alpha stage library suitable for early adopters. Not all Hyperledger Fabric Gateway operations have been implemented. However the operations that have been implemented have pretty good unit test coverage.
35
+ This is an alpha stage library suitable for early adopters. The basic evaluate and submit gateway functions are implemented. Chaincode events monitoring is not yet implemented.
36
36
 
37
- ```
37
+ ```ruby
38
38
  $ bin/console
39
39
 
40
+ # for running in application or script; not needed from bin/console
41
+ require 'fabric'
40
42
 
41
43
  def load_certs
42
44
  data_dir ='/your/certs/directory' # aka test-network/organizations
@@ -80,12 +82,9 @@ contract = network.new_contract('basic')
80
82
  # Evaluate
81
83
  puts contract.evaluate_transaction('GetAllAssets')
82
84
 
83
- # Submit - Not Yet Implemented!
84
- puts contract.submit_transaction('CreateAsset', 'asset13', 'yellow', '5', 'Tom', '1300')
85
-
86
- # Endorse - Not Yet Implemented!
87
-
88
- # Commit Status - Not Yet Implemented!
85
+ # Submit
86
+ puts contract.submit_transaction('CreateAsset', ['asset13', 'yellow', '5', 'Tom', '1300'])
87
+ puts contract.submit_transaction('UpdateAsset', %w[asset999 yellow 5 Tom 5555])
89
88
 
90
89
  # Chaincode Events - Not Yet Implemented!
91
90
 
@@ -133,13 +132,15 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/ethica
133
132
  - [x] Setup auto-generation of API docs on rubydoc.info
134
133
  - [x] Abstract connection and calls such that the protos aren't being interacted directly
135
134
  - [x] Implement, Document & Test Evaluate
136
- - [ ] Implement, Document & Test Endorse
137
- - [ ] Implement, Document & Test Submit
138
- - [ ] Implement, Document & Test CommitStatus
135
+ - [x] Implement, Document & Test Endorse
136
+ - [x] Implement, Document & Test Submit
137
+ - [x] Implement, Document & Test CommitStatus
139
138
  - [ ] Implement, Document & Test ChaincodeEvents
140
- - [ ] Implement off-line signing - https://github.com/hyperledger/fabric-gateway/blob/1e4a926ddb98ec8ee969da3fc1500642ab389d01/node/src/contract.ts#L63
139
+ - [ ] Support Submit Async (currently blocks waiting for the transaction to be committed)
141
140
  - [ ] Consider adding error handling, invalid transaction proposals will result in random GRPC::FailedPrecondition type errors
141
+ - [ ] Consider adding transaction_id information to Fabric::Errors that are raised; would help a lot for debugging.
142
142
  - [ ] Consider adding integration tests against blockchain; might be a ton of stuff to setup
143
+ - [ ] Implement off-line signing - https://github.com/hyperledger/fabric-gateway/blob/1e4a926ddb98ec8ee969da3fc1500642ab389d01/node/src/contract.ts#L63
143
144
  - [ ] Support for offline transaction signing (write scenario test for this) - https://github.com/hyperledger/fabric-gateway/blob/cf78fc11a439ced7dfd2f9b55886c55c73119b25/pkg/client/offlinesign_test.go
144
145
 
145
146
 
data/Rakefile CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
+ require 'rake/notes/rake_task'
5
6
 
6
7
  RSpec::Core::RakeTask.new(:spec)
7
8
 
8
9
  task default: :spec
10
+
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency('codecov', '~> 0.6.0')
34
34
  spec.add_development_dependency('factory_bot', '~> 6.2.0')
35
35
  spec.add_development_dependency('grpc-tools', '~> 1.42')
36
+ spec.add_development_dependency('rake-notes', '~> 0.2.0')
36
37
  spec.add_development_dependency('rubocop', '~> 1.23.0')
37
38
  spec.add_development_dependency('rubocop-rspec', '~> 2.6.0')
38
39
  spec.add_development_dependency('simplecov', '~> 0.21.2')
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ module Accessors
5
+ #
6
+ # Add accessor methods to the given class.
7
+ #
8
+ # Usage: make sure the class has a contract accessor method
9
+ # and then `include Fabric::Accessors::Contract`
10
+ #
11
+ module Contract
12
+ # @!visibility private
13
+ def self.included(base)
14
+ base.send :include, Fabric::Accessors::Network
15
+ end
16
+
17
+ # @!parse include Fabric::Accessors::Network
18
+ # @!parse include Fabric::Accessors::Gateway
19
+
20
+ #
21
+ # Returns the network instance
22
+ #
23
+ # @return [Fabric::Network] network
24
+ # @!parse attr_reader :network
25
+ #
26
+ def network
27
+ contract.network
28
+ end
29
+
30
+ #
31
+ # Returns the contract name
32
+ #
33
+ # @return [String] contract name
34
+ # @!parse attr_reader :contract_name
35
+ #
36
+ def contract_name
37
+ contract.contract_name
38
+ end
39
+
40
+ #
41
+ # Returns the chaincode name
42
+ #
43
+ # @return [String] chaincode name
44
+ # @!parse attr_reader :chaincode_name
45
+ #
46
+ def chaincode_name
47
+ contract.chaincode_name
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ module Accessors
5
+ #
6
+ # Add accessor methods to the given class.
7
+ #
8
+ # Usage: make sure the class has a gateway accessor method
9
+ # and then `include Fabric::Accessors::Gateway`
10
+ #
11
+ module Gateway
12
+ #
13
+ # Returns the client instance
14
+ #
15
+ # @return [Fabric::Client] client
16
+ # @!parse attr_reader :client
17
+ #
18
+ def client
19
+ gateway.client
20
+ end
21
+
22
+ #
23
+ # Returns the signer identity instance
24
+ #
25
+ # @return [Fabric::Identity] signer
26
+ # @!parse attr_reader :signer
27
+ #
28
+ def signer
29
+ gateway.signer
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fabric
4
+ module Accessors
5
+ #
6
+ # Add accessor methods to the given class.
7
+ #
8
+ # Usage: make sure the class has a network accessor method
9
+ # and then `include Fabric::Accessors::Network`
10
+ #
11
+ module Network
12
+ # @!visibility private
13
+ def self.included(base)
14
+ base.send :include, Fabric::Accessors::Gateway
15
+ end
16
+
17
+ # @!parse include Fabric::Accessors::Gateway
18
+
19
+ #
20
+ # Returns the gateway instance
21
+ #
22
+ # @return [Fabric::Gateway] gateway
23
+ # @!parse attr_reader :gateway
24
+ #
25
+ def gateway
26
+ network.gateway
27
+ end
28
+
29
+ #
30
+ # Network name or the channel name or channel id
31
+ #
32
+ # @return [String] network name
33
+ # @!parse attr_reader :network_name
34
+ #
35
+ def network_name
36
+ network.name
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/fabric/client.rb CHANGED
@@ -10,22 +10,34 @@ module Fabric
10
10
  #
11
11
  # Initializes a client
12
12
  #
13
- # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:initialize
14
- #
15
- # @param [Gateway::Gateway::Stub] grpc_client grpc gateway client stub
16
- # @param [string] host hostname and port of the gateway
17
- # @param [GRPC::Core::ChannelCredentials|GRPC::Core::XdsChannelCredentials|Symbol] creds channel credentials
18
- # (usually the CA certificate)
19
- # @param [Hash] default_call_options call options to use by default for different operations
20
- # @option default_call_options [Hash] :endorse_options default options for endorse call; @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
21
- # @option default_call_options [Hash] :evaluate_options default options for evaluate call; @see keyword arguments in
22
- # https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
23
- # @option default_call_options [Hash] :submit_options default options for submit call; @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
24
- # @option default_call_options [Hash] :commit_status_options default options for commit_status call;
25
- # @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
26
- # @option default_call_options [Hash] :chaincode_events_options default options for chaincode_events call;
27
- # @see keyword arguments in https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
28
- # @param [Hash] **client_opts client initialization options; @see keyword arguments at https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:initialize
13
+ #
14
+ # @overload initialize(grpc_client: client, default_call_options: {}, **client_opts)
15
+ # Initializes a client from a gRPC Gateway client stub
16
+ # @param [Gateway::Gateway::Stub] grpc_client grpc gateway client stub
17
+ # @param [Hash] default_call_options call options to use by default for different operations
18
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response Keyword Argument call options for
19
+ # *_options default_call_options
20
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:initialize Keyword arguments for **client_opts
21
+ # @option default_call_options [Hash] :endorse_options default options for endorse call
22
+ # @option default_call_options [Hash] :evaluate_options default options for evaluate call
23
+ # @option default_call_options [Hash] :submit_options default options for submit call
24
+ # @option default_call_options [Hash] :commit_status_options default options for commit_status call
25
+ # @option default_call_options [Hash] :chaincode_events_options default options for chaincode_events call
26
+ # @param [Hash] **client_opts client initialization options
27
+ # @overload initialize(host: host, creds: creds, default_call_options: {}, **client_opts)
28
+ # Instantiates a new gRPC Gateway client stub from the parameters
29
+ # @param [string] host hostname and port of the gateway
30
+ # @param [GRPC::Core::ChannelCredentials|GRPC::Core::XdsChannelCredentials|Symbol] creds channel credentials
31
+ # (usually the CA certificate)
32
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response Keyword Argument call options for
33
+ # *_options default_call_options
34
+ # @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:initialize Keyword arguments for **client_opts
35
+ # @option default_call_options [Hash] :endorse_options default options for endorse call
36
+ # @option default_call_options [Hash] :evaluate_options default options for evaluate call
37
+ # @option default_call_options [Hash] :submit_options default options for submit call
38
+ # @option default_call_options [Hash] :commit_status_options default options for commit_status call
39
+ # @option default_call_options [Hash] :chaincode_events_options default options for chaincode_events call
40
+ # @param [Hash] **client_opts client initialization options
29
41
  #
30
42
  def initialize(grpc_client: nil, host: nil, creds: nil, default_call_options: {}, **client_opts)
31
43
  if grpc_client
@@ -89,8 +101,8 @@ module Fabric
89
101
  #
90
102
  # Subscribe to chaincode events
91
103
  #
92
- # @NOTE: This function has never been utilized or tested. This function is probably wrong, missing a block.
93
- # @TODO: add testing!
104
+ # @note This function has never been utilized or tested. This function is probably wrong, missing a block.
105
+ # @todo add testing!
94
106
  #
95
107
  # @param [Gateway::ChaincodeEventsRequest] chaincode_events_request
96
108
  # @param [Hash] options gRPC call options (merged with default options) @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:server_streamer
@@ -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
104
+ transaction.submit
120
105
 
121
- status = submitted.get_status
122
-
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
  #
@@ -156,6 +139,14 @@ module Fabric
156
139
  Proposal.new(proposed_transaction)
157
140
  end
158
141
 
142
+ #
143
+ # Generates the qualified transaction name for the contract. (prepends the contract name to the transaction name if
144
+ # contract name is set)
145
+ #
146
+ # @param [string] transaction_name
147
+ #
148
+ # @return [string] qualified transaction name
149
+ #
159
150
  def qualified_transaction_name(transaction_name)
160
151
  contract_name.nil? || contract_name.empty? ? transaction_name : "#{contract_name}:#{transaction_name}"
161
152
  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,153 @@
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
+ def result
59
+ @result ||= parse_result_from_payload
60
+ end
61
+
62
+ #
63
+ # Returns the deserialized payload.
64
+ #
65
+ # @return [Common::Payload] Envelope payload
66
+ #
67
+ def payload
68
+ @payload ||= Common::Payload.decode(envelope.payload)
69
+ end
70
+
71
+ #
72
+ # Returns the envelope payload header.
73
+ #
74
+ # Envelope => Payload => Header
75
+ #
76
+ # @return [Common::Header] Envelope Payload Header
77
+ #
78
+ def header
79
+ raise Fabric::Error, 'Missing header' if payload.header.nil?
80
+
81
+ @header ||= payload.header
82
+ end
83
+
84
+ #
85
+ # Returns the deserialized transaction channel header
86
+ #
87
+ # Envelope => Payload => Header => ChannelHeader
88
+ #
89
+ # @return [Common::ChannelHeader] envelop payload header channel header
90
+ #
91
+ def channel_header
92
+ @channel_header ||= Common::ChannelHeader.decode(header.channel_header)
93
+ end
94
+
95
+ #
96
+ # Grabs the channel_name frmo the depths of the envelope.
97
+ #
98
+ # @return [String] channel name
99
+ #
100
+ def channel_name
101
+ channel_header.channel_id
102
+ end
103
+
104
+ #
105
+ # Returns the deserialized transaction
106
+ #
107
+ # @return [Protos::Transaction] transaction
108
+ #
109
+ def transaction
110
+ @transaction ||= Protos::Transaction.decode(payload.data)
111
+ end
112
+
113
+ private
114
+
115
+ #
116
+ # Parse the transaction actinos from the payload looking for the transaction result payload.
117
+ #
118
+ # @return [String] result payload
119
+ # @raise [Fabric::Error] if the transaction result payload is not found
120
+ #
121
+ def parse_result_from_payload
122
+ errors = []
123
+ transaction.actions.each do |action|
124
+ return parse_result_from_transaction_action(action)
125
+ rescue Fabric::Error => e
126
+ errors << e
127
+ end
128
+
129
+ raise Fabric::Error, "No proposal response found: #{errors.inspect}"
130
+ end
131
+
132
+ #
133
+ # Parse a single transaction action looking for the transaction result payload.
134
+ #
135
+ # @param [Protos::TransactionAction] transaction_action
136
+ #
137
+ # @return [Payload] transaction result payload
138
+ # @raise [Fabric::Error] if the endorsed_action is missing or the chaincode response is missing
139
+ #
140
+ def parse_result_from_transaction_action(transaction_action)
141
+ action_payload = Protos::ChaincodeActionPayload.decode(transaction_action.payload)
142
+ endorsed_action = action_payload.action
143
+ raise Fabric::Error, 'Missing endorsed action' if endorsed_action.nil?
144
+
145
+ response_payload = Protos::ProposalResponsePayload.decode(endorsed_action.proposal_response_payload)
146
+ chaincode_action = Protos::ChaincodeAction.decode(response_payload.extension)
147
+ chaincode_response = chaincode_action.response
148
+ raise Fabric::Error, 'Missing chaincode response' if chaincode_response.nil?
149
+
150
+ chaincode_response.payload
151
+ end
152
+ end
153
+ 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
@@ -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
@@ -11,19 +11,14 @@ module Fabric
11
11
  class Network
12
12
  attr_reader :gateway, :name
13
13
 
14
+ # @!parse include Fabric::Accessors::Gateway
15
+ include Fabric::Accessors::Gateway
16
+
14
17
  def initialize(gateway, name)
15
18
  @gateway = gateway
16
19
  @name = name
17
20
  end
18
21
 
19
- def client
20
- gateway.client
21
- end
22
-
23
- def signer
24
- gateway.signer
25
- end
26
-
27
22
  #
28
23
  # Creates a new contract instance
29
24
  #
@@ -37,7 +32,7 @@ module Fabric
37
32
  end
38
33
 
39
34
  #
40
- # @TODO: original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
35
+ # @todo original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
41
36
  # @see https://github.com/hyperledger/fabric-gateway/blob/08118cf0a792898925d0b2710b0a9e7c5ec23228/node/src/network.ts
42
37
  # @see https://github.com/hyperledger/fabric-gateway/blob/main/pkg/client/network.go
43
38
  #
@@ -48,7 +43,7 @@ module Fabric
48
43
  end
49
44
 
50
45
  #
51
- # @TODO: original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
46
+ # @todo original SDK has getChaincodeEvents and newChaincodeEventsRequest methods
52
47
  # @see https://github.com/hyperledger/fabric-gateway/blob/08118cf0a792898925d0b2710b0a9e7c5ec23228/node/src/network.ts
53
48
  # @see https://github.com/hyperledger/fabric-gateway/blob/main/pkg/client/network.go
54
49
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fabric
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/fabric.rb CHANGED
@@ -1,19 +1,27 @@
1
1
  # frozen_string_literal: true
2
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
+
3
17
  require 'fabric/constants'
4
18
  require 'fabric/contract'
5
19
  require 'fabric/client'
6
20
  require 'fabric/ec_crypto_suite'
7
21
  require 'fabric/gateway'
8
- require 'fabric/identity'
9
22
  require 'fabric/network'
10
- require 'fabric/proposal'
11
- require 'fabric/proposed_transaction'
12
23
  require 'fabric/version'
13
24
 
14
- require 'gateway/gateway_pb'
15
- require 'gateway/gateway_services_pb'
16
-
17
25
  #
18
26
  # Hyperledger Fabric Gateway SDK
19
27
  #
@@ -25,14 +33,19 @@ module Fabric
25
33
  #
26
34
  # CommitError
27
35
  #
28
- # @TODO: TEST ME!
36
+ # @todo TEST ME!
29
37
  #
30
38
  class CommitError < Error
31
39
  attr_reader :code, :transaction_id
32
40
 
41
+ #
42
+ # Creates a transaction commit error from the status
43
+ #
44
+ # @param [Fabric::Status] status transaction status
45
+ #
33
46
  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)
47
+ super("Transaction #{status.transaction_id} failed to commit with status code #{status.code} - " +
48
+ Status::TRANSACTION_STATUSES.key(status.code).to_s)
36
49
  @code = code
37
50
  @transaction_id = status.transaction_id
38
51
  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.2.0
4
+ version: 0.3.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-29 00:00:00.000000000 Z
11
+ date: 2022-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.42'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake-notes
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.0
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubocop
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -154,6 +168,7 @@ files:
154
168
  - ".ruby-version"
155
169
  - ".travis.yml"
156
170
  - ".vscode/settings.json"
171
+ - ".yardopts"
157
172
  - CHANGELOG.md
158
173
  - CODE_OF_CONDUCT.md
159
174
  - Gemfile
@@ -171,15 +186,21 @@ files:
171
186
  - lib/common/policies_pb.rb
172
187
  - lib/fabric.rb
173
188
  - lib/fabric/.DS_Store
189
+ - lib/fabric/accessors/contract.rb
190
+ - lib/fabric/accessors/gateway.rb
191
+ - lib/fabric/accessors/network.rb
174
192
  - lib/fabric/client.rb
175
193
  - lib/fabric/constants.rb
176
194
  - lib/fabric/contract.rb
177
195
  - lib/fabric/ec_crypto_suite.rb
196
+ - lib/fabric/entities/envelope.rb
197
+ - lib/fabric/entities/identity.rb
198
+ - lib/fabric/entities/proposal.rb
199
+ - lib/fabric/entities/proposed_transaction.rb
200
+ - lib/fabric/entities/status.rb
201
+ - lib/fabric/entities/transaction.rb
178
202
  - lib/fabric/gateway.rb
179
- - lib/fabric/identity.rb
180
203
  - lib/fabric/network.rb
181
- - lib/fabric/proposal.rb
182
- - lib/fabric/proposed_transaction.rb
183
204
  - lib/fabric/version.rb
184
205
  - lib/gateway/gateway_pb.rb
185
206
  - lib/gateway/gateway_services_pb.rb