Class: Ethereum::Transaction

Inherits:
Object
  • Object
show all
Extended by:
Sedes
Includes:
RLP::Sedes::Serializable
Defined in:
lib/ethereum/transaction.rb

Overview

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.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Sedes

address, big_endian_int, binary, hash32, int20, int256, int32, trie_root

Constructor Details

#initialize(*args) ⇒ Transaction

Returns a new instance of Transaction.

Raises:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ethereum/transaction.rb', line 52

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

Class Method Details

.contract(nonce, gasprice, startgas, endowment, code, v = 0, r = 0, s = 0) ⇒ Object

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



47
48
49
# File 'lib/ethereum/transaction.rb', line 47

def contract(nonce, gasprice, startgas, endowment, code, v=0, r=0, s=0)
  new nonce, gasprice, startgas, '', endowment, code, v, r, s
end

Instance Method Details

#==(other) ⇒ Object



174
175
176
# File 'lib/ethereum/transaction.rb', line 174

def ==(other)
  other.instance_of?(self.class) && full_hash == other.full_hash
end

#check_low_sObject

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.

Raises:



123
124
125
# File 'lib/ethereum/transaction.rb', line 123

def check_low_s
  raise InvalidTransaction, "Invalid signature S value!" if s > Secp256k1::N/2 || s == 0
end

#createsObject

returns the address of a contract created by this tx



170
171
172
# File 'lib/ethereum/transaction.rb', line 170

def creates
  Utils.mk_contract_address(sender, nonce) if [Address::BLANK, Address::ZERO].include?(to)
end

#full_hashObject



127
128
129
# File 'lib/ethereum/transaction.rb', line 127

def full_hash
  Utils.keccak256_rlp self
end

#hashObject



178
179
180
# File 'lib/ethereum/transaction.rb', line 178

def hash
  Utils.big_endian_to_int full_hash
end

#intrinsic_gas_usedObject



140
141
142
143
144
145
146
147
# File 'lib/ethereum/transaction.rb', line 140

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_bloomObject



131
132
133
134
# File 'lib/ethereum/transaction.rb', line 131

def log_bloom
  bloomables = @logs.map {|l| l.bloomables }
  Bloom.from_array bloomables.flatten
end

#log_bloom_b256Object



136
137
138
# File 'lib/ethereum/transaction.rb', line 136

def log_bloom_b256
  Bloom.b256 log_bloom
end

#log_dictObject



160
161
162
163
164
165
# File 'lib/ethereum/transaction.rb', line 160

def log_dict
  h = to_h
  h[:sender] = Utils.encode_hex(h[:sender] || '')
  h[:to] = Utils.encode_hex(h[:to])
  h
end

#senderObject



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ethereum/transaction.rb', line 67

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) ⇒ Object



92
93
94
# File 'lib/ethereum/transaction.rb', line 92

def sender=(v)
  @sender = v
end

#sign(key) ⇒ Object

Sign this transaction with a private key.

A potentially already existing signature would be override.

Raises:



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ethereum/transaction.rb', line 101

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_hObject



149
150
151
152
153
154
155
156
157
158
# File 'lib/ethereum/transaction.rb', line 149

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_sObject



182
183
184
# File 'lib/ethereum/transaction.rb', line 182

def to_s
  "#<#{self.class.name}:#{object_id} #{Utils.encode_hex(full_hash)[0,8]}"
end