class Ethereum::Transaction

A transaction is stored as:

`[nonce, gasprice, startgas, to, value, data, v, r, s]`

`nonce` is the number of transactions already sent by that account, encoded in binary form (eg. 0 -> “”, 7 -> “x07”, 1000 -> “x03xd8”).

`(v,r,s)` is the raw Electrum-style signature of the transaction without the signature made with the private key corresponding to the sending account, with `0 <= v <= 3`. From an Electrum-style signature (65 bytes) it is possible to extract the public key, and thereby the address, directly.

A valid transaction is one where:

  1. the signature is well-formed (ie. `0 <= v <= 3, 0 <= r < P, 0 <= s < N, 0

<= r < P - N if v >= 2`), and

  1. the sending account has enough funds to pay the fee and the value.

Public Class Methods

contract(nonce, gasprice, startgas, endowment, code, v=0, r=0, s=0) click to toggle source

A contract is a special transaction without the `to` argument.

# File lib/ethereum/transaction.rb, line 46
def contract(nonce, gasprice, startgas, endowment, code, v=0, r=0, s=0)
  new nonce, gasprice, startgas, '', endowment, code, v, r, s
end
new(*args) click to toggle source
# File lib/ethereum/transaction.rb, line 51
def initialize(*args)
  fields = {v: 0, r: 0, s: 0}.merge parse_field_args(args)
  fields[:to] = Utils.normalize_address(fields[:to], allow_blank: true)

  serializable_initialize fields

  @sender = nil
  @logs = []

  raise InvalidTransaction, "Values way too high!" if [gasprice, startgas, value, nonce].max > Constant::UINT_MAX
  raise InvalidTransaction, "Startgas too low" if startgas < intrinsic_gas_used

  logger.debug "deserialized tx #{Utils.encode_hex(full_hash)[0,8]}"
end

Public Instance Methods

==(other) click to toggle source
# File lib/ethereum/transaction.rb, line 173
def ==(other)
  other.instance_of?(self.class) && full_hash == other.full_hash
end
check_low_s() click to toggle source

This method should be called for block numbers >= config only. The >= operator is replaced by > because the integer division N/2 always produces the value which is by 0.5 less than the real N/2.

# File lib/ethereum/transaction.rb, line 122
def check_low_s
  raise InvalidTransaction, "Invalid signature S value!" if s > Secp256k1::N/2 || s == 0
end
creates() click to toggle source

returns the address of a contract created by this tx

# File lib/ethereum/transaction.rb, line 169
def creates
  Utils.mk_contract_address(sender, nonce) if [Address::BLANK, Address::ZERO].include?(to)
end
full_hash() click to toggle source
# File lib/ethereum/transaction.rb, line 126
def full_hash
  Utils.keccak256_rlp self
end
hash() click to toggle source
# File lib/ethereum/transaction.rb, line 177
def hash
  Utils.big_endian_to_int full_hash
end
intrinsic_gas_used() click to toggle source
# File lib/ethereum/transaction.rb, line 139
def intrinsic_gas_used
  num_zero_bytes = data.count(Constant::BYTE_ZERO)
  num_non_zero_bytes = data.size - num_zero_bytes

  Opcodes::GTXCOST +
    Opcodes::GTXDATAZERO*num_zero_bytes +
    Opcodes::GTXDATANONZERO*num_non_zero_bytes
end
log_bloom() click to toggle source
# File lib/ethereum/transaction.rb, line 130
def log_bloom
  bloomables = @logs.map {|l| l.bloomables }
  Bloom.from_array bloomables.flatten
end
log_bloom_b256() click to toggle source
# File lib/ethereum/transaction.rb, line 135
def log_bloom_b256
  Bloom.b256 log_bloom
end
log_dict() click to toggle source
# File lib/ethereum/transaction.rb, line 159
def log_dict
  h = to_h
  h[:sender] = Utils.encode_hex(h[:sender] || '')
  h[:to] = Utils.encode_hex(h[:to])
  h
end
sender() click to toggle source
# File lib/ethereum/transaction.rb, line 66
def sender
  unless @sender
    if v && v > 0
      raise InvalidTransaction, "Invalid signature values!" if r >= Secp256k1::N || s >= Secp256k1::N || v < 27 || v > 28 || r == 0 || s == 0

      logger.debug "recovering sender"
      rlpdata = RLP.encode(self, sedes: UnsignedTransaction)
      rawhash = Utils.keccak256 rlpdata

      pub = nil
      begin
        pub = Secp256k1.recover_pubkey rawhash, [v,r,s]
      rescue
        raise InvalidTransaction, "Invalid signature values (x^3+7 is non-residue)"
      end

      raise InvalidTransaction, "Invalid signature (zero privkey cannot sign)" if pub[1..-1] == Constant::PUBKEY_ZERO

      @sender = PublicKey.new(pub).to_address
    end
  end

  @sender
end
sender=(v) click to toggle source
# File lib/ethereum/transaction.rb, line 91
def sender=(v)
  @sender = v
end
sign(key) click to toggle source

Sign this transaction with a private key.

A potentially already existing signature would be override.

# File lib/ethereum/transaction.rb, line 100
def sign(key)
  raise InvalidTransaction, "Zero privkey cannot sign" if [0, '', Constant::PRIVKEY_ZERO, Constant::PRIVKEY_ZERO_HEX].include?(key)

  rawhash = Utils.keccak256 RLP.encode(self, sedes: UnsignedTransaction)
  key = PrivateKey.new(key).encode(:bin)

  vrs = Secp256k1.recoverable_sign rawhash, key
  self.v = vrs[0]
  self.r = vrs[1]
  self.s = vrs[2]

  self.sender = PrivateKey.new(key).to_address

  self
end
to_h() click to toggle source
# File lib/ethereum/transaction.rb, line 148
def to_h
  h = {}
  self.class.serializable_fields.keys.each do |field|
    h[field] = send field
  end

  h[:sender] = sender
  h[:hash] = Utils.encode_hex full_hash
  h
end
to_s() click to toggle source
# File lib/ethereum/transaction.rb, line 181
def to_s
  "#<#{self.class.name}:#{object_id} #{Utils.encode_hex(full_hash)[0,8]}"
end

Private Instance Methods

logger() click to toggle source
# File lib/ethereum/transaction.rb, line 187
def logger
  @logger ||= Logger.new 'eth.chain.tx'
end