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.
- checksums.yaml +4 -4
- data/.editorconfig +3 -0
- data/.github/workflows/rspec.yml +37 -0
- data/.github/workflows/rubocop.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +7 -0
- data/.yardopts +2 -0
- data/CODE_OF_CONDUCT.md +105 -46
- data/Gemfile +5 -3
- data/LICENSE.txt +1 -1
- data/README.md +105 -7
- data/Rakefile +7 -3
- data/bin/console +4 -3
- data/bin/regenerate +19 -0
- data/bin/release +5 -0
- data/fabric-gateway.gemspec +32 -17
- data/lib/common/common_pb.rb +113 -0
- data/lib/common/policies_pb.rb +61 -0
- data/lib/fabric/accessors/contract.rb +51 -0
- data/lib/fabric/accessors/gateway.rb +33 -0
- data/lib/fabric/accessors/network.rb +40 -0
- data/lib/fabric/client.rb +146 -0
- data/lib/fabric/constants.rb +8 -0
- data/lib/fabric/contract.rb +154 -0
- data/lib/fabric/ec_crypto_suite.rb +199 -0
- data/lib/fabric/entities/envelope.rb +153 -0
- data/lib/fabric/entities/identity.rb +87 -0
- data/lib/fabric/entities/proposal.rb +189 -0
- data/lib/fabric/entities/proposed_transaction.rb +163 -0
- data/lib/fabric/entities/status.rb +32 -0
- data/lib/fabric/entities/transaction.rb +247 -0
- data/lib/fabric/gateway.rb +31 -6
- data/lib/fabric/network.rb +56 -0
- data/lib/fabric/version.rb +5 -0
- data/lib/fabric.rb +57 -0
- data/lib/gossip/message_pb.rb +236 -0
- data/lib/gossip/message_services_pb.rb +31 -0
- data/lib/msp/identities_pb.rb +25 -0
- data/lib/msp/msp_principal_pb.rb +57 -0
- data/lib/orderer/ab_pb.rb +64 -0
- data/lib/orderer/ab_services_pb.rb +30 -0
- data/lib/peer/chaincode_event_pb.rb +19 -0
- data/lib/peer/chaincode_pb.rb +69 -0
- data/lib/peer/proposal_pb.rb +41 -0
- data/lib/peer/proposal_response_pb.rb +52 -0
- data/lib/peer/transaction_pb.rb +74 -0
- metadata +184 -10
- data/lib/fabric/gateway/version.rb +0 -5
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
# Adapted from:
|
6
|
+
# https://github.com/kirshin/hyperledger-fabric-sdk/blob/95a5a1a37001852312df25946e960a9ff149207e/lib/fabric/crypto_suite.rb
|
7
|
+
module Fabric
|
8
|
+
#
|
9
|
+
# Elliptic-curve Crypto Suite using OpenSSL
|
10
|
+
#
|
11
|
+
# @todo missing tests
|
12
|
+
class ECCryptoSuite # rubocop:disable Metrics/ClassLength
|
13
|
+
DEFAULT_KEY_SIZE = 256
|
14
|
+
DEFAULT_DIGEST_ALGORITHM = 'SHA256'
|
15
|
+
DEFAULT_AES_KEY_SIZE = 128
|
16
|
+
|
17
|
+
EC_CURVES = { 256 => 'prime256v1', 384 => 'secp384r1' }.freeze
|
18
|
+
|
19
|
+
CIPHER = 'aes-256-cbc'
|
20
|
+
|
21
|
+
attr_reader :key_size, :digest_algorithm, :digest_instance, :curve, :cipher
|
22
|
+
|
23
|
+
def initialize(opts = {})
|
24
|
+
@key_size = opts[:key_size] || DEFAULT_KEY_SIZE
|
25
|
+
@digest_algorithm = opts[:digest_algorithm] || DEFAULT_DIGEST_ALGORITHM
|
26
|
+
@digest_instance = OpenSSL::Digest.new digest_algorithm
|
27
|
+
@curve = EC_CURVES[key_size]
|
28
|
+
@cipher = opts[:cipher] || CIPHER
|
29
|
+
end
|
30
|
+
|
31
|
+
def sign(private_key, message)
|
32
|
+
digest = digest message
|
33
|
+
key = pkey_from_private_key private_key
|
34
|
+
signature = key.dsa_sign_asn1 digest
|
35
|
+
sequence = OpenSSL::ASN1.decode signature
|
36
|
+
sequence = prevent_malleability sequence, key.group.order
|
37
|
+
|
38
|
+
sequence.to_der
|
39
|
+
end
|
40
|
+
|
41
|
+
def verify(public_key, message, signature)
|
42
|
+
digest = digest message
|
43
|
+
openssl_pkey = openssl_pkey_from_public_key public_key
|
44
|
+
sequence = OpenSSL::ASN1.decode signature
|
45
|
+
return false unless check_malleability sequence, openssl_pkey.group.order
|
46
|
+
|
47
|
+
openssl_pkey.dsa_verify_asn1(digest, signature)
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_private_key
|
51
|
+
key = OpenSSL::PKey::EC.new curve
|
52
|
+
key.generate_key!
|
53
|
+
|
54
|
+
key.private_key.to_s(16).downcase
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_csr(private_key, attrs = [])
|
58
|
+
key = pkey_from_private_key private_key
|
59
|
+
|
60
|
+
req = OpenSSL::X509::Request.new
|
61
|
+
req.public_key = key
|
62
|
+
req.subject = OpenSSL::X509::Name.new attrs
|
63
|
+
req.sign key, @digest_instance
|
64
|
+
|
65
|
+
req
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_nonce(length = 24)
|
69
|
+
OpenSSL::Random.random_bytes length
|
70
|
+
end
|
71
|
+
|
72
|
+
def hexdigest(message)
|
73
|
+
@digest_instance.hexdigest message
|
74
|
+
end
|
75
|
+
|
76
|
+
def digest(message)
|
77
|
+
@digest_instance.digest message
|
78
|
+
end
|
79
|
+
|
80
|
+
def encode_hex(bytes)
|
81
|
+
bytes.unpack1('H*')
|
82
|
+
end
|
83
|
+
|
84
|
+
def decode_hex(string)
|
85
|
+
[string].pack('H*')
|
86
|
+
end
|
87
|
+
|
88
|
+
def restore_public_key(private_key)
|
89
|
+
private_bn = OpenSSL::BN.new private_key, 16
|
90
|
+
group = OpenSSL::PKey::EC::Group.new curve
|
91
|
+
public_bn = group.generator.mul(private_bn).to_bn
|
92
|
+
public_bn = OpenSSL::PKey::EC::Point.new(group, public_bn).to_bn
|
93
|
+
|
94
|
+
public_bn.to_s(16).downcase
|
95
|
+
end
|
96
|
+
|
97
|
+
def address_from_public_key(public_key)
|
98
|
+
bytes = decode_hex public_key
|
99
|
+
address_bytes = digest(bytes[1..])[-20..]
|
100
|
+
|
101
|
+
encode_hex address_bytes
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_shared_key(private_key, public_key)
|
105
|
+
pkey = pkey_from_private_key private_key
|
106
|
+
public_bn = OpenSSL::BN.new public_key, 16
|
107
|
+
group = OpenSSL::PKey::EC::Group.new curve
|
108
|
+
public_point = OpenSSL::PKey::EC::Point.new group, public_bn
|
109
|
+
|
110
|
+
encode_hex pkey.dh_compute_key(public_point)
|
111
|
+
end
|
112
|
+
|
113
|
+
def encrypt(secret, data)
|
114
|
+
aes = OpenSSL::Cipher.new cipher
|
115
|
+
aes.encrypt
|
116
|
+
aes.key = decode_hex(secret)
|
117
|
+
iv = aes.random_iv
|
118
|
+
aes.iv = iv
|
119
|
+
|
120
|
+
Base64.strict_encode64(iv + aes.update(data) + aes.final)
|
121
|
+
end
|
122
|
+
|
123
|
+
def decrypt(secret, data)
|
124
|
+
return unless data
|
125
|
+
|
126
|
+
encrypted_data = Base64.strict_decode64 data
|
127
|
+
aes = OpenSSL::Cipher.new cipher
|
128
|
+
aes.decrypt
|
129
|
+
aes.key = decode_hex(secret)
|
130
|
+
aes.iv = encrypted_data[0..15]
|
131
|
+
encrypted_data = encrypted_data[16..]
|
132
|
+
|
133
|
+
aes.update(encrypted_data) + aes.final
|
134
|
+
end
|
135
|
+
|
136
|
+
def pkey_pem_from_private_key(private_key)
|
137
|
+
public_key = restore_public_key private_key
|
138
|
+
key = OpenSSL::PKey::EC.new curve
|
139
|
+
key.private_key = OpenSSL::BN.new private_key, 16
|
140
|
+
key.public_key = OpenSSL::PKey::EC::Point.new key.group,
|
141
|
+
OpenSSL::BN.new(public_key, 16)
|
142
|
+
|
143
|
+
pkey = OpenSSL::PKey::EC.new(key.public_key.group)
|
144
|
+
pkey.public_key = key.public_key
|
145
|
+
|
146
|
+
pkey.to_pem
|
147
|
+
end
|
148
|
+
|
149
|
+
def key_from_pem(pem)
|
150
|
+
key = OpenSSL::PKey::EC.new(pem)
|
151
|
+
key.private_key.to_s(16).downcase
|
152
|
+
end
|
153
|
+
|
154
|
+
def pkey_from_x509_certificate(certificate)
|
155
|
+
cert = OpenSSL::X509::Certificate.new(certificate)
|
156
|
+
cert.public_key.public_key.to_bn.to_s(16).downcase
|
157
|
+
end
|
158
|
+
|
159
|
+
def openssl_pkey_from_public_key(public_key)
|
160
|
+
pkey = OpenSSL::PKey::EC.new curve
|
161
|
+
pkey.public_key = OpenSSL::PKey::EC::Point.new(pkey.group, OpenSSL::BN.new(public_key, 16))
|
162
|
+
|
163
|
+
pkey
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def pkey_from_private_key(private_key)
|
169
|
+
public_key = restore_public_key private_key
|
170
|
+
key = OpenSSL::PKey::EC.new curve
|
171
|
+
key.private_key = OpenSSL::BN.new private_key, 16
|
172
|
+
key.public_key = OpenSSL::PKey::EC::Point.new key.group,
|
173
|
+
OpenSSL::BN.new(public_key, 16)
|
174
|
+
|
175
|
+
key
|
176
|
+
end
|
177
|
+
|
178
|
+
# barely understand this code - this link provides a good explanation:
|
179
|
+
# http://coders-errand.com/malleability-ecdsa-signatures/
|
180
|
+
def prevent_malleability(sequence, order)
|
181
|
+
half_order = order >> 1
|
182
|
+
|
183
|
+
if (half_key = sequence.value[1].value) > half_order
|
184
|
+
sequence.value[1].value = order - half_key
|
185
|
+
end
|
186
|
+
|
187
|
+
sequence
|
188
|
+
end
|
189
|
+
|
190
|
+
# ported from python code, understanding extremely limited.
|
191
|
+
# from what I gather, sequence.value[0] and sequence.value[1]
|
192
|
+
# are the r and s values from the python implementation
|
193
|
+
# https://github.com/hyperledger/fabric-sdk-py/blob/25209f61518873da68d28313582607c29b5bae7d/hfc/util/crypto/crypto.py#L259
|
194
|
+
def check_malleability(sequence, order)
|
195
|
+
half_order = order >> 1
|
196
|
+
sequence.value[1].value <= half_order
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -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
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'msp/identities_pb'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
# Adapted from:
|
7
|
+
# https://github.com/kirshin/hyperledger-fabric-sdk/blob/95a5a1a37001852312df25946e960a9ff149207e/lib/fabric/identity.rb
|
8
|
+
|
9
|
+
module Fabric
|
10
|
+
#
|
11
|
+
# @attr_reader [String] private_key raw private key in hex format
|
12
|
+
# @attr_reader [String] public_key raw public key in hex format
|
13
|
+
# @attr_reader [String] certificate raw certificate in pem format
|
14
|
+
# @attr_reader [String] msp_id MSP (Membership Service Provider) Identifier
|
15
|
+
#
|
16
|
+
class Identity
|
17
|
+
attr_reader :private_key,
|
18
|
+
:public_key,
|
19
|
+
:address, # TODO: possibly unnecessary
|
20
|
+
:crypto_suite
|
21
|
+
|
22
|
+
attr_accessor :certificate, :msp_id
|
23
|
+
|
24
|
+
def initialize(private_key: nil, public_key: nil, certificate: nil, msp_id: nil, crypto_suite: nil)
|
25
|
+
@crypto_suite = crypto_suite || Fabric.crypto_suite
|
26
|
+
|
27
|
+
@private_key = private_key || @crypto_suite.generate_private_key
|
28
|
+
@public_key = public_key || @crypto_suite.restore_public_key(@private_key)
|
29
|
+
@certificate = certificate
|
30
|
+
@msp_id = msp_id
|
31
|
+
|
32
|
+
@address = @crypto_suite.address_from_public_key @public_key
|
33
|
+
|
34
|
+
return unless @certificate
|
35
|
+
|
36
|
+
raise Fabric::Error, 'Key mismatch (public_key or certificate) for identity' unless validate_key_integrity
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Validates that the private_key, public_key, and certificate are valid and match
|
41
|
+
#
|
42
|
+
# @return [boolean] true if valid, false otherwise
|
43
|
+
#
|
44
|
+
def validate_key_integrity
|
45
|
+
cert_pubkey = @crypto_suite.pkey_from_x509_certificate(certificate)
|
46
|
+
priv_pubkey = @crypto_suite.restore_public_key(@private_key)
|
47
|
+
|
48
|
+
@public_key == cert_pubkey && @public_key == priv_pubkey
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_csr(attrs = [])
|
52
|
+
@crypto_suite.generate_csr private_key, attrs
|
53
|
+
end
|
54
|
+
|
55
|
+
def sign(message)
|
56
|
+
@crypto_suite.sign(private_key, message)
|
57
|
+
end
|
58
|
+
|
59
|
+
def digest(message)
|
60
|
+
@crypto_suite.digest message
|
61
|
+
end
|
62
|
+
|
63
|
+
# TODO: Do we need this?
|
64
|
+
def shared_secret_by(public_key)
|
65
|
+
@crypto_suite.build_shared_key private_key, public_key
|
66
|
+
end
|
67
|
+
|
68
|
+
def as_proto
|
69
|
+
@as_proto ||= Msp::SerializedIdentity.new(mspid: msp_id, id_bytes: certificate)
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_proto
|
73
|
+
@to_proto ||= Msp::SerializedIdentity.new(mspid: msp_id, id_bytes: certificate).to_proto
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Creates a new gateway passing in the current identity
|
78
|
+
#
|
79
|
+
# @param [Fabric::Client] client
|
80
|
+
#
|
81
|
+
# @return [Fabric::Gateway] gateway
|
82
|
+
#
|
83
|
+
def new_gateway(client)
|
84
|
+
Fabric::Gateway.new(self, client)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,189 @@
|
|
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
|
+
include Fabric::Accessors::Contract
|
26
|
+
|
27
|
+
def transaction_id
|
28
|
+
proposed_transaction.transaction_id
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Returns the proposal message as a protobuf Message object.
|
33
|
+
#
|
34
|
+
# @return [Protos::Proposal|nil] Proposal message
|
35
|
+
#
|
36
|
+
def proposal
|
37
|
+
proposed_transaction.proposal
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Returns the signed proposal
|
42
|
+
#
|
43
|
+
# <rant>
|
44
|
+
# Fabric message naming scheme is a mess:
|
45
|
+
# ProposedTransaction has a Proposal which is a SignedProposal
|
46
|
+
# which has a Proposal which is a Proposal
|
47
|
+
# so.... which proposal do you want to access? Adding this function for clarity
|
48
|
+
# </rant>
|
49
|
+
#
|
50
|
+
# @return [Protos::SignedProposal|nil] SignedProposal message
|
51
|
+
#
|
52
|
+
def signed_proposal
|
53
|
+
proposed_transaction.proposed_transaction.proposal
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Serialized bytes of the proposal message in proto3 format.
|
58
|
+
#
|
59
|
+
# @return [String] Binary representation of the proposal message.
|
60
|
+
#
|
61
|
+
def to_proto
|
62
|
+
proposed_transaction.to_proto
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Proposal digest which can be utilized for offline signing.
|
67
|
+
# If signing offline, call signature= to set signature once
|
68
|
+
# computed.
|
69
|
+
#
|
70
|
+
# @return [String] raw binary digest of the proposal message.
|
71
|
+
#
|
72
|
+
def digest
|
73
|
+
Fabric.crypto_suite.digest(proposal.to_proto)
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Sets the signature of the signed proposal in the proposed transaction
|
78
|
+
#
|
79
|
+
# @param [String] signature raw byte string signature of the proposal message
|
80
|
+
# (should be the signature of the proposed message digest)
|
81
|
+
#
|
82
|
+
def signature=(signature)
|
83
|
+
proposed_transaction.signed_proposal.signature = signature
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Returns the signed proposal signature
|
88
|
+
#
|
89
|
+
# @return [String] Raw byte string signature
|
90
|
+
#
|
91
|
+
def signature
|
92
|
+
proposed_transaction.signed_proposal.signature
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Returns true if the signed proposal has a signature
|
97
|
+
#
|
98
|
+
# @return [Boolean] true|false
|
99
|
+
#
|
100
|
+
def signed?
|
101
|
+
# signature cannot be nil because google protobuf won't let it
|
102
|
+
!proposed_transaction.signed_proposal.signature.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Utilizes the signer to sign the proposal message if it has not been signed yet.
|
107
|
+
#
|
108
|
+
def sign
|
109
|
+
return if signed?
|
110
|
+
|
111
|
+
self.signature = signer.sign proposal.to_proto
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# Evaluate the transaction proposal and obtain its result, without updating the ledger. This runs the transaction
|
116
|
+
# on a peer to obtain a transaction result, but does not submit the endorsed transaction to the orderer to be
|
117
|
+
# committed to the ledger.
|
118
|
+
#
|
119
|
+
# @param [Hash] options gRPC call options @see https://www.rubydoc.info/gems/grpc/GRPC%2FClientStub:request_response
|
120
|
+
#
|
121
|
+
# @return [String] The result returned by the transaction function
|
122
|
+
#
|
123
|
+
def evaluate(options = {})
|
124
|
+
sign
|
125
|
+
|
126
|
+
evaluate_response = client.evaluate(new_evaluate_request, options)
|
127
|
+
evaluate_response.result.payload
|
128
|
+
end
|
129
|
+
|
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)
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Generates an evaluate request from this proposal.
|
150
|
+
#
|
151
|
+
# @return [Gateway::EvaluateRequest] evaluation request with the current proposal
|
152
|
+
#
|
153
|
+
def new_evaluate_request
|
154
|
+
::Gateway::EvaluateRequest.new(
|
155
|
+
channel_id: network_name,
|
156
|
+
proposed_transaction: signed_proposal,
|
157
|
+
target_organizations: proposed_transaction.endorsing_organizations
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Creates a new endorse request from this proposal.
|
163
|
+
#
|
164
|
+
# @return [Gateway::EndorseRequest] EndorseRequest protobuf message
|
165
|
+
#
|
166
|
+
def new_endorse_request
|
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
|
+
)
|
173
|
+
end
|
174
|
+
|
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
|
+
)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|