Class: Klay::Tx::Legacy

Inherits:
Object
  • Object
show all
Defined in:
lib/klay/tx/legacy.rb

Overview

Provides legacy support for transactions on blockchains that do not implement EIP-1559, EIP-2718, or EIP-2930.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params, chain_id = Chain::CYPRESS) ⇒ Legacy

Create a legacy transaction object that can be prepared for signature and broadcast. Should not be used unless there is no EIP-1559 support.

Parameters:

  • params (Hash)

    all necessary transaction fields.

  • chain_id (Integer) (defaults to: Chain::CYPRESS)

    the EIP-155 Chain ID.

Options Hash (params):

  • :nonce (Integer)

    the signer nonce.

  • :gas_price (Integer)

    the gas price.

  • :gas_limit (Integer)

    the gas limit.

  • :from (Klay::Address)

    the sender address.

  • :to (Klay::Address)

    the reciever address.

  • :value (Integer)

    the transaction value.

  • :data (String)

    the transaction data payload.

Raises:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/klay/tx/legacy.rb', line 76

def initialize(params, chain_id = Chain::CYPRESS)
  fields = { v: chain_id, r: 0, s: 0 }.merge params

  # populate optional fields with serializable empty values
  fields[:value] = Tx.sanitize_amount fields[:value]
  fields[:from] = Tx.sanitize_address fields[:from]
  fields[:to] = Tx.sanitize_address fields[:to]
  fields[:data] = Tx.sanitize_data fields[:data]

  # ensure sane values for all mandatory fields
  fields = Tx.validate_legacy_params fields

  # ensure gas limit is not too low
  minimum_cost = Tx.estimate_intrinsic_gas fields[:data]
  raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost

  # populate class attributes
  @signer_nonce = fields[:nonce].to_i
  @gas_price = fields[:gas_price].to_i
  @gas_limit = fields[:gas_limit].to_i
  @sender = fields[:from].to_s
  @destination = fields[:to].to_s
  @amount = fields[:value].to_i
  @payload = fields[:data]

  # the signature v is set to the chain id for unsigned transactions
  @signature_v = fields[:v]
  @chain_id = chain_id

  # the signature fields are empty for unsigned transactions.
  @signature_r = fields[:r]
  @signature_s = fields[:s]

  # last but not least, set the type.
  @type = TYPE_LEGACY
end

Instance Attribute Details

#amountObject (readonly)

The transaction amount in Wei.



38
39
40
# File 'lib/klay/tx/legacy.rb', line 38

def amount
  @amount
end

#chain_idObject (readonly)

The EIP-155 chain ID field. Ref: https://eips.ethereum.org/EIPS/eip-155



54
55
56
# File 'lib/klay/tx/legacy.rb', line 54

def chain_id
  @chain_id
end

#destinationObject (readonly)

The recipient address.



35
36
37
# File 'lib/klay/tx/legacy.rb', line 35

def destination
  @destination
end

#gas_limitObject (readonly)

The gas limit for the transaction.



32
33
34
# File 'lib/klay/tx/legacy.rb', line 32

def gas_limit
  @gas_limit
end

#gas_priceObject (readonly)

The gas price for the transaction in Wei.



29
30
31
# File 'lib/klay/tx/legacy.rb', line 29

def gas_price
  @gas_price
end

#payloadObject (readonly)

The transaction data payload.



41
42
43
# File 'lib/klay/tx/legacy.rb', line 41

def payload
  @payload
end

#senderObject (readonly)

The sender address.



57
58
59
# File 'lib/klay/tx/legacy.rb', line 57

def sender
  @sender
end

#signature_rObject (readonly)

The signature r value.



47
48
49
# File 'lib/klay/tx/legacy.rb', line 47

def signature_r
  @signature_r
end

#signature_sObject (readonly)

The signature s value.



50
51
52
# File 'lib/klay/tx/legacy.rb', line 50

def signature_s
  @signature_s
end

#signature_vObject (readonly)

The signature v byte.



44
45
46
# File 'lib/klay/tx/legacy.rb', line 44

def signature_v
  @signature_v
end

#signer_nonceObject (readonly)

The transaction nonce provided by the signer.



26
27
28
# File 'lib/klay/tx/legacy.rb', line 26

def signer_nonce
  @signer_nonce
end

#typeObject (readonly)

The transaction type.



60
61
62
# File 'lib/klay/tx/legacy.rb', line 60

def type
  @type
end

Instance Method Details

#decode(hex) ⇒ Klay::Tx::Legacy

Decodes a raw transaction hex into an Klay::Tx::Legacy transaction object.

Parameters:

  • hex (String)

    the raw transaction hex-string.

Returns:

Raises:



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/klay/tx/legacy.rb', line 122

def decode(hex)
  bin = Util.hex_to_bin hex
  tx = Rlp.decode bin

  # decoded transactions always have 9 fields, even if they are empty or zero
  raise ParameterError, "Transaction missing fields!" if tx.size < 9

  # populate the 9 fields
  nonce = Util.deserialize_big_endian_to_int tx[0]
  gas_price = Util.deserialize_big_endian_to_int tx[1]
  gas_limit = Util.deserialize_big_endian_to_int tx[2]
  to = Util.bin_to_hex tx[3]
  value = Util.deserialize_big_endian_to_int tx[4]
  data = tx[5]
  v = Util.bin_to_hex tx[6]
  r = Util.bin_to_hex tx[7]
  s = Util.bin_to_hex tx[8]

  # try to recover the chain id from v
  chain_id = Chain.to_chain_id Util.deserialize_big_endian_to_int tx[6]

  # populate class attributes
  @signer_nonce = nonce.to_i
  @gas_price = gas_price.to_i
  @gas_limit = gas_limit.to_i
  @destination = to.to_s
  @amount = value.to_i
  @payload = data
  @chain_id = chain_id

  # allows us to force-setting a signature if the transaction is signed already
  _set_signature(v, r, s)

  unless chain_id.nil?

    # recover sender address
    public_key = Signature.recover(unsigned_hash, "#{r}#{s}#{v}", chain_id)
    address = Util.public_key_to_address(public_key).to_s
    @sender = Tx.sanitize_address address
  else

    # keep the 'from' field blank
    @sender = Tx.sanitize_address nil
  end

  # last but not least, set the type.
  @type = TYPE_LEGACY
end

#encodedString

Encodes a raw transaction object.

Returns:

  • (String)

    a raw, RLP-encoded legacy transaction.

Raises:



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/klay/tx/legacy.rb', line 231

def encoded
  unless Tx.is_signed? self
    raise Signature::SignatureError, "Transaction is not signed!"
  end
  tx_data = []
  tx_data.push Util.serialize_int_to_big_endian @signer_nonce
  tx_data.push Util.serialize_int_to_big_endian @gas_price
  tx_data.push Util.serialize_int_to_big_endian @gas_limit
  tx_data.push Util.hex_to_bin @destination
  tx_data.push Util.serialize_int_to_big_endian @amount
  tx_data.push Rlp::Sedes.binary.serialize @payload
  tx_data.push Util.serialize_int_to_big_endian @signature_v
  tx_data.push Util.serialize_int_to_big_endian @signature_r
  tx_data.push Util.serialize_int_to_big_endian @signature_s
  Rlp.encode tx_data
end

#hashString

Gets the transaction hash.

Returns:

  • (String)

    the transaction hash.



258
259
260
# File 'lib/klay/tx/legacy.rb', line 258

def hash
  Util.bin_to_hex Util.keccak256 encoded
end

#hexString

Gets the encoded, raw transaction hex.

Returns:

  • (String)

    the raw transaction hex.



251
252
253
# File 'lib/klay/tx/legacy.rb', line 251

def hex
  Util.bin_to_hex encoded
end

#sign(key) ⇒ String

Sign the transaction with a given key.

Parameters:

  • key (Klay::Key)

    the key-pair to use for signing.

Returns:

  • (String)

    a transaction hash.

Raises:



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/klay/tx/legacy.rb', line 206

def sign(key)
  if Tx.is_signed? self
    raise Signature::SignatureError, "Transaction is already signed!"
  end

  # ensure the sender address matches the given key
  unless @sender.nil? or sender.empty?
    signer_address = Tx.sanitize_address key.address.to_s
    from_address = Tx.sanitize_address @sender
    raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
  end

  # sign a keccak hash of the unsigned, encoded transaction
  signature = key.sign(unsigned_hash, @chain_id)
  r, s, v = Signature.dissect signature
  @signature_v = v
  @signature_r = r
  @signature_s = s
  return hash
end

#unsigned_copy(tx) ⇒ Klay::Tx::Legacy

Creates an unsigned copy of a transaction.

Parameters:

Returns:

Raises:



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/klay/tx/legacy.rb', line 176

def unsigned_copy(tx)

  # not checking transaction validity unless it's of a different class
  raise TransactionTypeError, "Cannot copy transaction of different type!" unless tx.instance_of? Tx::Legacy

  # populate class attributes
  @signer_nonce = tx.signer_nonce
  @gas_price = tx.gas_price
  @gas_limit = tx.gas_limit
  @destination = tx.destination
  @amount = tx.amount
  @payload = tx.payload
  @chain_id = tx.chain_id

  # force-set signature to unsigned
  _set_signature(tx.chain_id, 0, 0)

  # keep the 'from' field blank
  @sender = Tx.sanitize_address nil

  # last but not least, set the type.
  @type = TYPE_LEGACY
end

#unsigned_encodedString

Encodes the unsigned transaction object, required for signing.

Returns:

  • (String)

    an RLP-encoded, unsigned transaction.



265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/klay/tx/legacy.rb', line 265

def unsigned_encoded
  tx_data = []
  tx_data.push Util.serialize_int_to_big_endian @signer_nonce
  tx_data.push Util.serialize_int_to_big_endian @gas_price
  tx_data.push Util.serialize_int_to_big_endian @gas_limit
  tx_data.push Util.hex_to_bin @destination
  tx_data.push Util.serialize_int_to_big_endian @amount
  tx_data.push Rlp::Sedes.binary.serialize @payload
  tx_data.push Util.serialize_int_to_big_endian @chain_id
  tx_data.push Util.serialize_int_to_big_endian 0
  tx_data.push Util.serialize_int_to_big_endian 0
  Rlp.encode tx_data
end

#unsigned_hashString

Gets the sign-hash required to sign a raw transaction.

Returns:

  • (String)

    a Keccak-256 hash of an unsigned transaction.



282
283
284
# File 'lib/klay/tx/legacy.rb', line 282

def unsigned_hash
  Util.keccak256 unsigned_encoded
end