Class: Ethereum::Block

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

Overview

A block.

All attributes from the block header are accessible via properties (i.e. ‘block.prevhash` is equivalent to `block.header.prevhash`). It is ensured that no discrepancies between header and the block occur.

Constant Summary collapse

HeaderGetters =
(BlockHeader.serializable_fields.keys - %i(state_root receipts_root tx_list_root)).freeze
HeaderSetters =
HeaderGetters.map {|field| :"#{field}=" }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Block

Arguments in format of:

`header, transaction_list=[], uncles=[], env=nil, parent=nil,
making=false`

Parameters:

  • args (Array)

    mix of arguments:

    • header Ethereum::BlockHeader optional. if given, will be used as block header. if not given, you must specify header by 'options`

    • options (Hash) optional.

      - transaction_list {Array[Transaction]} a list of transactions
        which are replayed if the state given by the header is not known.
        If the state is known, `nil` can be used instead of the empty
        list.
      - uncles {Array[BlockHeader]} a list of the headers of the uncles
        of this block
      - env {Env} env including db in which the block's state,
        transactions and receipts are stored (required)
      - parent {Block} optional parent which if not given may have to be
        loaded from the database for replay
      

Raises:

  • (ArgumentError)


226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/ethereum/block.rb', line 226

def initialize(*args)
  header = args.first.instance_of?(BlockHeader) ? args.first : nil
  options = args.last.instance_of?(Hash) ? args.last : {}

  header = options.delete(:header) if options.has_key?(:header)
  transaction_list = options.has_key?(:transaction_list) ? options[:transaction_list] : []
  uncles = options.has_key?(:uncles) ? options[:uncles] : []
  env = options.delete(:env)
  parent = options.delete(:parent)
  making = options.has_key?(:making) ? options.delete(:making) : false

  raise ArgumentError, "No Env object given" unless env.instance_of?(Env)
  raise ArgumentError, "No database object given. db=#{env.db}" unless env.db.is_a?(DB::BaseDB)

  @env = env
  @db = env.db
  @config = env.config

  _set_field :header, header
  _set_field :uncles, uncles

  reset_cache
  @get_transactions_cache = []

  self.suicides = []
  self.logs = []
  self.log_listeners = []

  self.refunds = 0
  self.ether_delta = 0

  self.ancestor_hashes = number > 0 ? [prevhash] : [nil]*256

  validate_parent!(parent) if parent

  original_values = {
    bloom: bloom,
    gas_used: gas_used,
    timestamp: timestamp,
    difficulty: difficulty,
    uncles_hash: uncles_hash,
    header_mutable: header.mutable?
  }

  make_mutable!
  header.make_mutable!

  @transactions = PruningTrie.new db
  @receipts = PruningTrie.new db

  initialize_state(transaction_list, parent, making)

  validate_block!(original_values)
  unless db.has_key?("validated:#{full_hash}")
    if number == 0
      db.put "validated:#{full_hash}", '1'
    else
      db.put_temporarily "validated:#{full_hash}", '1'
    end
  end

  header.block = self
  header.instance_variable_set :@_mutable, original_values[:header_mutable]
end

Instance Attribute Details

#ancestor_hashesObject

Returns the value of attribute ancestor_hashes.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def ancestor_hashes
  @ancestor_hashes
end

#configObject (readonly)

Returns the value of attribute config.



29
30
31
# File 'lib/ethereum/block.rb', line 29

def config
  @config
end

#dbObject (readonly)

Returns the value of attribute db.



29
30
31
# File 'lib/ethereum/block.rb', line 29

def db
  @db
end

#envObject (readonly)

Returns the value of attribute env.



29
30
31
# File 'lib/ethereum/block.rb', line 29

def env
  @env
end

#ether_deltaObject

Returns the value of attribute ether_delta.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def ether_delta
  @ether_delta
end

#log_listenersObject

Returns the value of attribute log_listeners.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def log_listeners
  @log_listeners
end

#logsObject

Returns the value of attribute logs.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def logs
  @logs
end

#receiptsObject

Returns the value of attribute receipts.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def receipts
  @receipts
end

#refundsObject

Returns the value of attribute refunds.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def refunds
  @refunds
end

#stateObject

Returns the value of attribute state.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def state
  @state
end

#suicidesObject

Returns the value of attribute suicides.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def suicides
  @suicides
end

#transactionsObject

Returns the value of attribute transactions.



30
31
32
# File 'lib/ethereum/block.rb', line 30

def transactions
  @transactions
end

Class Method Details

.build_from_header(header_rlp, env) ⇒ Block

Create a block without specifying transactions or uncles.

Parameters:

  • header_rlp (String)

    the RLP encoded block header

  • env (Env)

    provide database for the block

Returns:



59
60
61
62
# File 'lib/ethereum/block.rb', line 59

def build_from_header(header_rlp, env)
  header = RLP.decode header_rlp, sedes: BlockHeader
  new header, transaction_list: nil, uncles: [], env: env
end

.build_from_parent(parent, coinbase, nonce: Constant::BYTE_EMPTY, extra_data: Constant::BYTE_EMPTY, timestamp: Time.now.to_i, uncles: [], env: nil) ⇒ Object

Create a new block based on a parent block.

The block will not include any transactions and will not be finalized.



69
70
71
72
73
74
75
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
# File 'lib/ethereum/block.rb', line 69

def build_from_parent(parent, coinbase, nonce: Constant::BYTE_EMPTY, extra_data: Constant::BYTE_EMPTY, timestamp: Time.now.to_i, uncles: [], env: nil)
  env ||= parent.env

  header = BlockHeader.new(
    prevhash: parent.full_hash,
    uncles_hash: Utils.keccak256_rlp(uncles),
    coinbase: coinbase,
    state_root: parent.state_root,
    tx_list_root: Trie::BLANK_ROOT,
    receipts_root: Trie::BLANK_ROOT,
    bloom: 0,
    difficulty: calc_difficulty(parent, timestamp),
    mixhash: Constant::BYTE_EMPTY,
    number: parent.number+1,
    gas_limit: calc_gaslimit(parent),
    gas_used: 0,
    timestamp: timestamp,
    extra_data: extra_data,
    nonce: nonce
  )

  Block.new(
    header,
    transaction_list: [],
    uncles: uncles,
    env: env,
    parent: parent,
    making: true
  ).tap do |blk|
    blk.ancestor_hashes = [parent.full_hash] + parent.ancestor_hashes
    blk.log_listeners = parent.log_listeners
  end
end

.calc_difficulty(parent, ts) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/ethereum/block.rb', line 103

def calc_difficulty(parent, ts)
  config = parent.config
  offset = parent.difficulty / config[:block_diff_factor]

  if parent.number >= (config[:metropolis_fork_blknum] - 1)
    sign = [parent.uncles.size - ((ts - parent.timestamp) / config[:metropolis_diff_adjustment_cutoff]), -99].max
  elsif parent.number >= (config[:homestead_fork_blknum] - 1)
    sign = [1 - ((ts - parent.timestamp) / config[:homestead_diff_adjustment_cutoff]), -99].max
  else
    sign = (ts - parent.timestamp) < config[:diff_adjustment_cutoff] ? 1 : -1
  end

  # If we enter a special mode where the genesis difficulty starts off
  # below the minimal difficulty, we allow low-difficulty blocks (this will
  # never happen in the official protocol)
  o = [parent.difficulty + offset*sign, [parent.difficulty, config[:min_diff]].min].max
  period_count = (parent.number + 1) / config[:expdiff_period]
  if period_count >= config[:expdiff_free_periods]
    o = [o + 2**(period_count - config[:expdiff_free_periods]), config[:min_diff]].max
  end

  o
end

.calc_gaslimit(parent) ⇒ Object

Raises:



127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ethereum/block.rb', line 127

def calc_gaslimit(parent)
  config = parent.config
  decay = parent.gas_limit / config[:gaslimit_ema_factor]
  new_contribution = ((parent.gas_used * config[:blklim_factor_nom]) / config[:blklim_factor_den] / config[:gaslimit_ema_factor])

  gl = [parent.gas_limit - decay + new_contribution, config[:min_gas_limit]].max
  if gl < config[:genesis_gas_limit]
    gl2 = parent.gas_limit + decay
    gl = [config[:genesis_gas_limit], gl2].min
  end
  raise ValueError, "invalid gas limit" unless check_gaslimit(parent, gl)

  gl
end

.check_gaslimit(parent, gas_limit) ⇒ Object



142
143
144
145
146
# File 'lib/ethereum/block.rb', line 142

def check_gaslimit(parent, gas_limit)
  config = parent.config
  adjmax = parent.gas_limit / config[:gaslimit_adjmax_factor]
  (gas_limit - parent.gas_limit).abs <= adjmax && gas_limit >= parent.config[:min_gas_limit]
end

.find(env, hash) ⇒ Object

Assumption: blocks loaded from the db are not manipulated -> can be

cached including hash.

Raises:

  • (ArgumentError)


36
37
38
39
40
# File 'lib/ethereum/block.rb', line 36

def find(env, hash)
  raise ArgumentError, "env must be instance of Env" unless env.instance_of?(Env)
  blk = RLP.decode env.db.get(hash), sedes: Block, env: env
  CachedBlock.create_cached blk
end

.genesis(env, options = {}) ⇒ Object

Build the genesis block.

Raises:

  • (ArgumentError)


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/ethereum/block.rb', line 151

def genesis(env, options={})
  allowed_args = %i(start_alloc bloom prevhash coinbase difficulty gas_limit gas_used timestamp extra_data mixhash nonce)
  invalid_options = options.keys - allowed_args
  raise ArgumentError, "invalid options: #{invalid_options}" unless invalid_options.empty?

  start_alloc = options[:start_alloc] || env.config[:genesis_initial_alloc]

  header = BlockHeader.new(
    prevhash: options[:prevhash] || env.config[:genesis_prevhash],
    uncles_hash: Utils.keccak256_rlp([]),
    coinbase: options[:coinbase] || env.config[:genesis_coinbase],
    state_root: Trie::BLANK_ROOT,
    tx_list_root: Trie::BLANK_ROOT,
    receipts_root: Trie::BLANK_ROOT,
    bloom: options[:bloom] || 0,
    difficulty: options[:difficulty] || env.config[:genesis_difficulty],
    number: 0,
    gas_limit: options[:gas_limit] || env.config[:genesis_gas_limit],
    gas_used: options[:gas_used] || 0,
    timestamp: options[:timestamp] || 0,
    extra_data: options[:extra_data] || env.config[:genesis_extra_data],
    mixhash: options[:mixhash] || env.config[:genesis_mixhash],
    nonce: options[:nonce] || env.config[:genesis_nonce]
  )

  block = Block.new header, transaction_list: [], uncles: [], env: env

  start_alloc.each do |addr, data|
    addr = Utils.normalize_address addr

    block.set_balance addr, Utils.parse_int_or_hex(data[:wei]) if data[:wei]
    block.set_balance addr, Utils.parse_int_or_hex(data[:balance]) if data[:balance]
    block.set_code addr, Utils.decode_hex(Utils.remove_0x_head(data[:code])) if data[:code]
    block.set_nonce addr, Utils.parse_int_or_hex(data[:nonce]) if data[:nonce]

    if data[:storage]
      data[:storage].each do |k, v|
        k = Utils.big_endian_to_int Utils.decode_hex(Utils.remove_0x_head(k))
        v = Utils.big_endian_to_int Utils.decode_hex(Utils.remove_0x_head(v))
        block.set_storage_data addr, k, v
      end
    end

  end

  block.commit_state
  block.commit_state_db

  # genesis block has predefined state root (so no additional
  # finalization necessary)
  block
end

.verify(block, parent) ⇒ Object



43
44
45
46
47
48
49
# File 'lib/ethereum/block.rb', line 43

def verify(block, parent)
  block2 = RLP.decode RLP.encode(block), sedes: Block, env: parent.env, parent: parent
  raise "block not match" unless block == block2
  true
rescue InvalidBlock
  false
end

Instance Method Details

#<(other) ⇒ Object



1113
1114
1115
# File 'lib/ethereum/block.rb', line 1113

def <(other)
  number < other.number
end

#==(other) ⇒ Object

Two blocks are equal iff they have the same hash.



1100
1101
1102
1103
# File 'lib/ethereum/block.rb', line 1100

def ==(other)
  (other.instance_of?(Block) || other.instance_of?(CachedBlock)) &&
    full_hash == other.full_hash
end

#>(other) ⇒ Object



1109
1110
1111
# File 'lib/ethereum/block.rb', line 1109

def >(other)
  number > other.number
end

#account_exists(address) ⇒ Object



705
706
707
708
# File 'lib/ethereum/block.rb', line 705

def (address)
  address = Utils.normalize_address address
  @state[address].size > 0 || @caches[:all].has_key?(address)
end

#account_to_dict(address, with_storage_root: false, with_storage: true) ⇒ Hash

Serialize an account to a hash with human readable entries.

Parameters:

  • address (String)

    the account address

  • with_storage_root (Bool) (defaults to: false)

    include the account's storage root

  • with_storage (Bool) (defaults to: true)

    include the whole account's storage

Returns:

  • (Hash)

    hash represent the account

Raises:

  • (ArgumentError)


1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
# File 'lib/ethereum/block.rb', line 1025

def (address, with_storage_root: false, with_storage: true)
  address = Utils.normalize_address address

  # if there are uncommited account changes the current storage root is
  # meaningless
  raise ArgumentError, "cannot include storage root with uncommited account changes" if with_storage_root && !@journal.empty?

  h = {}
   =  address

  h[:nonce] = (@caches[:nonce][address] || .nonce).to_s
  h[:balance] = (@caches[:balance][address] || .balance).to_s

  code = @caches[:code][address] || .code
  h[:code] = "0x#{Utils.encode_hex code}"

  storage_trie = SecureTrie.new PruningTrie.new(db, .storage)
  h[:storage_root] = Utils.encode_hex storage_trie.root_hash if with_storage_root
  if with_storage
    h[:storage] = {}
    sh = storage_trie.to_h

    cache = @caches["storage:#{address}"] || {}
    keys = cache.keys.map {|k| Utils.zpad Utils.coerce_to_bytes(k), 32 }

    (sh.keys + keys).each do |k|
      hexkey = "0x#{Utils.encode_hex Utils.zunpad(k)}"

      v = cache[Utils.big_endian_to_int(k)]
      if v.true?
        h[:storage][hexkey] = "0x#{Utils.encode_hex Utils.int_to_big_endian(v)}"
      else
        v = sh[k]
        h[:storage][hexkey] = "0x#{Utils.encode_hex RLP.decode(v)}" if v
      end
    end
  end

  h
end

#add_listener(l) ⇒ Object



291
292
293
# File 'lib/ethereum/block.rb', line 291

def add_listener(l)
  log_listeners.push l
end

#add_log(log) ⇒ Object



710
711
712
713
# File 'lib/ethereum/block.rb', line 710

def add_log(log)
  logs.push log
  log_listeners.each {|l| l.call log }
end

#add_refund(x) ⇒ Object



395
396
397
# File 'lib/ethereum/block.rb', line 395

def add_refund(x)
  self.refunds += x
end

#add_transaction_to_list(tx) ⇒ Object

Add a transaction to the transaction trie.

Note that this does not execute anything, i.e. the state is not updated.



404
405
406
407
408
409
410
411
412
413
# File 'lib/ethereum/block.rb', line 404

def add_transaction_to_list(tx)
  k = RLP.encode @transaction_count
  @transactions[k] = RLP.encode(tx)

  r = mk_transaction_receipt tx
  @receipts[k] = RLP.encode(r)

  self.bloom |= r.bloom
  @transaction_count += 1
end

#apply_transaction(tx) ⇒ Object

Raises:



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/ethereum/block.rb', line 419

def apply_transaction(tx)
  validate_transaction tx

  intrinsic_gas = get_intrinsic_gas tx

  logger.debug "apply transaction", tx: tx.log_dict
  increment_nonce tx.sender

  # buy startgas
  delta_balance tx.sender, -tx.startgas*tx.gasprice

  message_gas = tx.startgas - intrinsic_gas
  message_data = VM::CallData.new tx.data.bytes, 0, tx.data.size
  message = VM::Message.new tx.sender, tx.to, tx.value, message_gas, message_data, code_address: tx.to

  ec = build_external_call tx

  if tx.to.true? && tx.to != Address::CREATE_CONTRACT
    result, gas_remained, data = ec.apply_msg message
    logger.debug "_res_", result: result, gas_remained: gas_remained, data: data
  else # CREATE
    result, gas_remained, data = ec.create message
    raise ValueError, "gas remained is not numeric" unless gas_remained.is_a?(Numeric)
    logger.debug "_create_", result: result, gas_remained: gas_remained, data: data
  end
  raise ValueError, "gas remained cannot be negative" unless gas_remained >= 0
  logger.debug "TX APPLIED", result: result, gas_remained: gas_remained, data: data

  if result.true?
    logger.debug "TX SUCCESS", data: data

    gas_used = tx.startgas - gas_remained

    self.refunds += self.suicides.uniq.size * Opcodes::GSUICIDEREFUND
    if refunds > 0
      gas_refund = [refunds, gas_used/2].min

      logger.debug "Refunding", gas_refunded: gas_refund
      gas_remained += gas_refund
      gas_used -= gas_refund
      self.refunds = 0
    end

    delta_balance tx.sender, tx.gasprice * gas_remained
    delta_balance coinbase, tx.gasprice * gas_used
    self.gas_used += gas_used

    output = tx.to.true? ? Utils.int_array_to_bytes(data) : data
    success = 1
  else # 0 = OOG failure in both cases
    logger.debug "TX FAILED", reason: 'out of gas', startgas: tx.startgas, gas_remained: gas_remained

    self.gas_used += tx.startgas
    delta_balance coinbase, tx.gasprice*tx.startgas

    output = Constant::BYTE_EMPTY
    success = 0
  end

  commit_state

  suicides.each do |s|
    self.ether_delta -= get_balance(s)
    set_balance s, 0 # TODO: redundant with code in SUICIDE op?
     s
  end
  self.suicides = []

  add_transaction_to_list tx
  self.logs = []

  # TODO: change success to Bool type
  return success, output
end

#build_external_call(tx) ⇒ Object



415
416
417
# File 'lib/ethereum/block.rb', line 415

def build_external_call(tx)
  ExternalCall.new self, tx
end

#chain_difficultyObject

Get the summarized difficulty.

If the summarized difficulty is not stored in the database, it will be calculated recursively and put int the database.



652
653
654
655
656
657
658
659
660
661
# File 'lib/ethereum/block.rb', line 652

def chain_difficulty
  return difficulty if genesis?

  k = "difficulty:#{Utils.encode_hex(full_hash)}"
  return Utils.decode_int(db.get(k)) if db.has_key?(k)

  o = difficulty + get_parent.chain_difficulty
  @state.db.put_temporarily k, Utils.encode_int(o)
  o
end

#commit_stateObject

Commit account caches. Write the account caches on the corresponding tries.



667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# File 'lib/ethereum/block.rb', line 667

def commit_state
  return if @journal.empty?

  changes = []
  addresses = @caches[:all].keys.sort

  addresses.each do |addr|
    acct =  addr

    %i(balance nonce code storage).each do |field|
      if v = @caches[field][addr]
        changes.push [field, addr, v]
        acct.send :"#{field}=", v
      end
    end

    t = SecureTrie.new PruningTrie.new(db, acct.storage)
    @caches.fetch("storage:#{addr}", {}).each do |k, v|
      enckey = Utils.zpad Utils.coerce_to_bytes(k), 32
      val = RLP.encode v
      changes.push ['storage', addr, k, v]

      v.true? ? t.set(enckey, val) : t.delete(enckey)
    end

    acct.storage = t.root_hash
    @state[addr] = RLP.encode(acct)
  end
  logger.debug "delta changes=#{changes}"

  reset_cache
  db.put_temporarily "validated:#{full_hash}", '1'
end

#commit_state_dbObject



701
702
703
# File 'lib/ethereum/block.rb', line 701

def commit_state_db
  @state.db.commit
end

#del_account(address) ⇒ Object

Delete an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)



1010
1011
1012
1013
1014
# File 'lib/ethereum/block.rb', line 1010

def (address)
  address = Utils.normalize_address address
  commit_state
  @state.delete address
end

#delta_balance(address, value) ⇒ Bool

Increase the balance of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

  • value (Integer)

    can be positive or negative

Returns:

  • (Bool)

    'true` if successful, otherwise `false`



723
724
725
# File 'lib/ethereum/block.rb', line 723

def delta_balance(address, value)
  (address, :balance, value)
end

#finalizeObject

Apply rewards and commit.



551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/ethereum/block.rb', line 551

def finalize
  delta = @config[:block_reward] + @config[:nephew_reward] * uncles.size

  delta_balance coinbase, delta
  self.ether_delta += delta

  uncles.each do |uncle|
    r = @config[:block_reward] * (@config[:uncle_depth_penalty_factor] + uncle.number - number) / @config[:uncle_depth_penalty_factor]

    delta_balance uncle.coinbase, r
    self.ether_delta += r
  end

  commit_state
end

#full_hashObject

The binary block hash. This is equivalent to ‘header.full_hash`.



298
299
300
# File 'lib/ethereum/block.rb', line 298

def full_hash
  Utils.keccak256_rlp header
end

#full_hash_hexObject

The hex encoded block hash. This is equivalent to ‘header.full_hash_hex`.



305
306
307
# File 'lib/ethereum/block.rb', line 305

def full_hash_hex
  Utils.encode_hex full_hash
end

#genesis?Boolean

Returns:

  • (Boolean)


1093
1094
1095
# File 'lib/ethereum/block.rb', line 1093

def genesis?
  number == 0
end

#get_ancestor_hash(n) ⇒ Object

Raises:

  • (ArgumentError)


1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
# File 'lib/ethereum/block.rb', line 1079

def get_ancestor_hash(n)
  raise ArgumentError, "n must be greater than 0 and less or equal than 256" unless n > 0 && n <= 256

  while ancestor_hashes.size < n
    if number == ancestor_hashes.size - 1
      ancestor_hashes.push nil
    else
      ancestor_hashes.push self.class.find(env, ancestor_hashes[-1]).get_parent().full_hash
    end
  end

  ancestor_hashes[n-1]
end

#get_ancestor_list(n) ⇒ Array

Return ‘n` ancestors of this block.

Returns:

  • (Array)

    array of ancestors in format of '[parent, parent.parent, ...]

Raises:

  • (ArgumentError)


1071
1072
1073
1074
1075
1076
1077
# File 'lib/ethereum/block.rb', line 1071

def get_ancestor_list(n)
  raise ArgumentError, "n must be greater or equal than zero" unless n >= 0

  return [] if n == 0 || number == 0
  parent = get_parent
  [parent] + parent.get_ancestor_list(n-1)
end

#get_balance(address) ⇒ Integer

Get the balance of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

Returns:

  • (Integer)

    balance value



878
879
880
# File 'lib/ethereum/block.rb', line 878

def get_balance(address)
   address, :balance
end

#get_code(address) ⇒ String

Get the code of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

Returns:



929
930
931
# File 'lib/ethereum/block.rb', line 929

def get_code(address)
   address, :code
end

#get_intrinsic_gas(tx) ⇒ Object



494
495
496
497
498
499
500
501
502
# File 'lib/ethereum/block.rb', line 494

def get_intrinsic_gas(tx)
  intrinsic_gas = tx.intrinsic_gas_used

  if number >= config[:homestead_fork_blknum]
    intrinsic_gas += Opcodes::CREATE[3] if tx.to.false? || tx.to == Address::CREATE_CONTRACT
  end

  intrinsic_gas
end

#get_nonce(address) ⇒ Integer

Get the nonce of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

Returns:

  • (Integer)

    the nonce value



840
841
842
# File 'lib/ethereum/block.rb', line 840

def get_nonce(address)
   address, :nonce
end

#get_parentObject

Get the parent of this block.



639
640
641
642
643
644
# File 'lib/ethereum/block.rb', line 639

def get_parent
  raise UnknownParentError, "Genesis block has no parent" if number == 0
  Block.find env, prevhash
rescue KeyError
  raise UnknownParentError, Utils.encode_hex(prevhash)
end

#get_parent_headerObject



629
630
631
632
633
634
# File 'lib/ethereum/block.rb', line 629

def get_parent_header
  raise UnknownParentError, "Genesis block has no parent" if number == 0
  BlockHeader.find db, prevhash
rescue KeyError
  raise UnknownParentError, Utils.encode_hex(prevhash)
end

#get_receipt(num) ⇒ Receipt

Get the receipt of the ‘num`th transaction.

Returns:

Raises:

  • (IndexError)

    if receipt at index is not found



806
807
808
809
810
811
812
813
814
815
# File 'lib/ethereum/block.rb', line 806

def get_receipt(num)
  index = RLP.encode num
  receipt = @receipts[index]

  if receipt == Trie::BLANK_NODE
    raise IndexError, "Receipt does not exist"
  else
    RLP.decode receipt, sedes: Receipt
  end
end

#get_receiptsObject

Build a list of all receipts in this block.



820
821
822
823
824
825
826
827
828
829
830
831
# File 'lib/ethereum/block.rb', line 820

def get_receipts
  receipts = []
  i = 0
  loop do
    begin
      receipts.push get_receipt(i)
      i += 1
    rescue IndexError
      return receipts
    end
  end
end

#get_storage(address) ⇒ Trie

Get the trie holding an account’s storage.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

Returns:

  • (Trie)

    the storage trie of account



952
953
954
955
# File 'lib/ethereum/block.rb', line 952

def get_storage(address)
  storage_root =  address, :storage
  SecureTrie.new PruningTrie.new(db, storage_root)
end

#get_storage_data(address, index) ⇒ Integer

Get a specific item in the storage of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

  • index (Integer)

    the index of the requested item in the storage

Returns:

  • (Integer)

    the value at storage index



974
975
976
977
978
979
980
981
982
983
984
# File 'lib/ethereum/block.rb', line 974

def get_storage_data(address, index)
  address = Utils.normalize_address address

  cache = @caches["storage:#{address}"]
  return cache[index] if cache && cache.has_key?(index)

  key = Utils.zpad Utils.coerce_to_bytes(index), 32
  value = get_storage(address)[key]

  value.true? ? RLP.decode(value, sedes: Sedes.big_endian_int) : 0
end

#get_transaction(num) ⇒ Object

Get the ‘num`th transaction in this block.

Raises:

  • (IndexError)

    if the transaction does not exist



509
510
511
512
513
514
515
# File 'lib/ethereum/block.rb', line 509

def get_transaction(num)
  index = RLP.encode num
  tx = @transactions.get index

  raise IndexError, "Transaction does not exist" if tx == Trie::BLANK_NODE
  RLP.decode tx, sedes: Transaction
end

#get_transaction_hashesObject

helper to check if block contains a tx.



533
534
535
536
537
# File 'lib/ethereum/block.rb', line 533

def get_transaction_hashes
  @transaction_count.times.map do |i|
    Utils.keccak256 @transactions[RLP.encode(i)]
  end
end

#get_transactionsObject

Build a list of all transactions in this block.



520
521
522
523
524
525
526
527
528
# File 'lib/ethereum/block.rb', line 520

def get_transactions
  # FIXME: such memoization is potentially buggy - what if pop b from and
  # push a to the cache? size will not change while content changed.
  if @get_transactions_cache.size != @transaction_count
    @get_transactions_cache = transaction_list
  end

  @get_transactions_cache
end

#has_parent?Boolean

‘true` if this block has a known parent, otherwise `false`.

Returns:

  • (Boolean)


622
623
624
625
626
627
# File 'lib/ethereum/block.rb', line 622

def has_parent?
  get_parent
  true
rescue UnknownParentError
  false
end

#hashObject



1105
1106
1107
# File 'lib/ethereum/block.rb', line 1105

def hash
  Utils.big_endian_to_int full_hash
end

#include_transaction?(tx_hash) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


539
540
541
542
# File 'lib/ethereum/block.rb', line 539

def include_transaction?(tx_hash)
  raise ArgumentError, "argument must be transaction hash in bytes" unless tx_hash.size == 32
  get_transaction_hashes.include?(tx_hash)
end

#increment_nonce(address) ⇒ Bool

Increment the nonce of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

Returns:

  • (Bool)

    'true` if successful, otherwise `false`



863
864
865
866
867
868
869
# File 'lib/ethereum/block.rb', line 863

def increment_nonce(address)
  if get_nonce(address) == 0
     address, :nonce, config[:account_initial_nonce]+1
  else
     address, :nonce, 1
  end
end

#mining_hashObject



615
616
617
# File 'lib/ethereum/block.rb', line 615

def mining_hash
  header.mining_hash
end

#receipts_rootObject



317
318
319
# File 'lib/ethereum/block.rb', line 317

def receipts_root
  @receipts.root_hash
end

#receipts_root=(v) ⇒ Object



321
322
323
# File 'lib/ethereum/block.rb', line 321

def receipts_root=(v)
  @receipts = PruningTrie.new db, v
end

#reset_cacheObject

Reset cache and journal without commiting any changes.



730
731
732
733
734
735
736
737
738
739
# File 'lib/ethereum/block.rb', line 730

def reset_cache
  @caches = {
    all: {},
    balance: {},
    nonce: {},
    code: {},
    storage: {}
  }
  @journal = []
end

#reset_storage(address) ⇒ Object



957
958
959
960
961
962
963
964
# File 'lib/ethereum/block.rb', line 957

def reset_storage(address)
   address, :storage, Constant::BYTE_EMPTY

  cache_key = "storage:#{address}"
  if @caches.has_key?(cache_key)
    @caches[cache_key].each {|k, v| set_and_journal cache_key, k, 0 }
  end
end

#revert(mysnapshot) ⇒ Object

Revert to a previously made snapshot.

Reverting is for example neccessary when a contract runs out of gas during execution.



766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
# File 'lib/ethereum/block.rb', line 766

def revert(mysnapshot)
  logger.debug "REVERTING"

  @journal = mysnapshot[:journal]
  # if @journal changed after snapshot
  while @journal.size > mysnapshot[:journal_size]
    cache, index, prev, post = @journal.pop
    logger.debug "revert journal", cache: cache, index: index, prev: prev, post: post
    if prev
      @caches[cache][index] = prev
    else
      @caches[cache].delete index
    end
  end

  self.suicides = mysnapshot[:suicides]
  suicides.pop while suicides.size > mysnapshot[:suicides_size]

  self.logs = mysnapshot[:logs]
  logs.pop while logs.size > mysnapshot[:logs_size]

  self.refunds = mysnapshot[:refunds]
  self.gas_used = mysnapshot[:gas]
  self.ether_delta = mysnapshot[:ether_delta]

  @transactions = mysnapshot[:txs]
  @transaction_count = mysnapshot[:txcount]

  @state.set_root_hash mysnapshot[:state]

  @get_transactions_cache = []
end

#set_balance(address, value) ⇒ Bool

Set the balance of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

  • value (Integer)

    the new balance value

Returns:

  • (Bool)

    'true` if successful, otherwise `false`



890
891
892
# File 'lib/ethereum/block.rb', line 890

def set_balance(address, value)
   address, :balance, value
end

#set_code(address, value) ⇒ Bool

Set the code of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

  • value (String)

    the new code bytes

Returns:

  • (Bool)

    'true` if successful, otherwise `false`



941
942
943
# File 'lib/ethereum/block.rb', line 941

def set_code(address, value)
   address, :code, value
end

#set_nonce(address, value) ⇒ Bool

Set the nonce of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

  • value (Integer)

    the new nonce

Returns:

  • (Bool)

    'true` if successful, otherwise `false`



852
853
854
# File 'lib/ethereum/block.rb', line 852

def set_nonce(address, value)
   address, :nonce, value
end

#set_storage_data(address, index, value) ⇒ Object

Set a specific item in the storage of an account.

Parameters:

  • address (String)

    the address of the account (binary or hex string)

  • index (Integer)

    the index of the requested item in the storage

  • value (Integer)

    the new value of the item



993
994
995
996
997
998
999
1000
1001
1002
1003
# File 'lib/ethereum/block.rb', line 993

def set_storage_data(address, index, value)
  address = Utils.normalize_address address

  cache_key = "storage:#{address}"
  unless @caches.has_key?(cache_key)
    @caches[cache_key] = {}
    set_and_journal :all, address, true
  end

  set_and_journal cache_key, index, value
end

#snapshotObject

Make a snapshot of the current state to enable later reverting.



744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
# File 'lib/ethereum/block.rb', line 744

def snapshot
  { state: @state.root_hash,
    gas: gas_used,
    txs: @transactions,
    txcount: @transaction_count,
    refunds: refunds,
    suicides: suicides,
    suicides_size: suicides.size,
    logs: logs,
    logs_size: logs.size,
    journal: @journal, # pointer to reference, so is not static
    journal_size: @journal.size,
    ether_delta: ether_delta
  }
end

#state_rootObject



325
326
327
328
# File 'lib/ethereum/block.rb', line 325

def state_root
  commit_state
  @state.root_hash
end

#state_root=(v) ⇒ Object



330
331
332
333
# File 'lib/ethereum/block.rb', line 330

def state_root=(v)
  @state = SecureTrie.new PruningTrie.new(db, v)
  reset_cache
end

#to_h(with_state: false, full_transactions: false, with_storage_roots: false, with_uncles: false) ⇒ Hash

Serialize the block to a readable hash.

Parameters:

  • with_state (Bool) (defaults to: false)

    include state for all accounts

  • full_transactions (Bool) (defaults to: false)

    include serialized transactions (hashes otherwise)

  • with_storage_roots (Bool) (defaults to: false)

    if account states are included also include their storage roots

  • with_uncles (Bool) (defaults to: false)

    include uncle hashes

Returns:

  • (Hash)

    a hash represents the block



579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/ethereum/block.rb', line 579

def to_h(with_state: false, full_transactions: false, with_storage_roots: false, with_uncles: false)
  b = { header: header.to_h }

  txlist = []
  get_transactions.each_with_index do |tx, i|
    receipt_rlp = @receipts[RLP.encode(i)]
    receipt = RLP.decode receipt_rlp, sedes: Receipt
    txjson = full_transactions ? tx.to_h : tx.full_hash

    logs = receipt.logs.map {|l| Log.serialize(l) }

    txlist.push(
      tx: txjson,
      medstate: Utils.encode_hex(receipt.state_root),
      gas: receipt.gas_used.to_s,
      logs: logs,
      bloom: Sedes.int256.serialize(receipt.bloom)
    )
  end
  b[:transactions] = txlist

  if with_state
    state_dump = {}
    @state.each do |address, v|
      state_dump[Utils.encode_hex(address)] = (address, with_storage_root: with_storage_roots)
    end
    b[:state] = state_dump
  end

  if with_uncles
    b[:uncles] = uncles.map {|u| RLP.decode(u, sedes: BlockHeader) }
  end

  b
end

#to_sObject Also known as: inspect



1117
1118
1119
# File 'lib/ethereum/block.rb', line 1117

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

#transaction_countObject



544
545
546
# File 'lib/ethereum/block.rb', line 544

def transaction_count
  @transaction_count
end

#transaction_listObject



335
336
337
# File 'lib/ethereum/block.rb', line 335

def transaction_list
  @transaction_count.times.map {|i| get_transaction(i) }
end

#transfer_value(from, to, value) ⇒ Bool

Transfer a value between two account balance.

Parameters:

  • from (String)

    the address of the sending account (binary or hex string)

  • to (String)

    the address of the receiving account (binary or hex string)

  • value (Integer)

    the (positive) value to send

Returns:

  • (Bool)

    'true` if successful, otherwise `false`

Raises:

  • (ArgumentError)


917
918
919
920
# File 'lib/ethereum/block.rb', line 917

def transfer_value(from, to, value)
  raise ArgumentError, "value must be greater or equal than zero" unless value >= 0
  delta_balance(from, -value) && delta_balance(to, value)
end

#tx_list_rootObject



309
310
311
# File 'lib/ethereum/block.rb', line 309

def tx_list_root
  @transactions.root_hash
end

#tx_list_root=(v) ⇒ Object



313
314
315
# File 'lib/ethereum/block.rb', line 313

def tx_list_root=(v)
  @transactions = PruningTrie.new db, v
end

#uncles_hashObject



339
340
341
# File 'lib/ethereum/block.rb', line 339

def uncles_hash
  Utils.keccak256_rlp(uncles)
end

#validate_transaction(tx) ⇒ Object

Raises:



1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
# File 'lib/ethereum/block.rb', line 1122

def validate_transaction(tx)
  unless tx.sender
    if number >= config[:metropolis_fork_blknum]
      tx.sender = Utils.normalize_address(config[:metropolis_entry_point])
    else
      raise UnsignedTransactionError.new(tx)
    end
  end

  acct_nonce = get_nonce tx.sender
  raise InvalidNonce, "#{tx}: nonce actual: #{tx.nonce} target: #{acct_nonce}" if acct_nonce != tx.nonce

  min_gas = get_intrinsic_gas tx
  raise InsufficientStartGas, "#{tx}: startgas actual: #{tx.startgas} target: #{min_gas}" if tx.startgas < min_gas

  total_cost = tx.value + tx.gasprice * tx.startgas
  balance = get_balance tx.sender
  raise InsufficientBalance, "#{tx}: balance actual: #{balance} target: #{total_cost}" if balance < total_cost

  accum_gas = gas_used + tx.startgas
  raise BlockGasLimitReached, "#{tx}: gaslimit actual: #{accum_gas} target: #{gas_limit}" if accum_gas > gas_limit

  tx.check_low_s if number >= config[:homestead_fork_blknum]

  true
end

#validate_unclesObject

Validate the uncles of this block.

Raises:



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/ethereum/block.rb', line 346

def validate_uncles
  return false if Utils.keccak256_rlp(uncles) != uncles_hash
  return false if uncles.size > config[:max_uncles]

  uncles.each do |uncle|
    raise InvalidUncles, "Cannot find uncle prevhash in db" unless db.include?(uncle.prevhash)
    if uncle.number == number
      logger.error "uncle at same block height", block: self
      return false
    end
  end

  max_uncle_depth = config[:max_uncle_depth]
  ancestor_chain = [self] + get_ancestor_list(max_uncle_depth+1)
  raise ValueError, "invalid ancestor chain" unless ancestor_chain.size == [number+1, max_uncle_depth+2].min

  # Uncles of this block cannot be direct ancestors and cannot also be
  # uncles included 1-6 blocks ago.
  ineligible = []
  ancestor_chain.safe_slice(1..-1).each {|a| ineligible.concat a.uncles }
  ineligible.concat(ancestor_chain.map {|a| a.header })

  eligible_ancestor_hashes = ancestor_chain.safe_slice(2..-1).map(&:full_hash)

  uncles.each do |uncle|
    parent = Block.find env, uncle.prevhash
    return false if uncle.difficulty != Block.calc_difficulty(parent, uncle.timestamp)
    return false if uncle.number != parent.number + 1
    return false if uncle.timestamp < parent.timestamp
    return false unless uncle.check_pow

    unless eligible_ancestor_hashes.include?(uncle.prevhash)
      eligible = eligible_ancestor_hashes.map {|h| Utils.encode_hex(h) }
      logger.error "Uncle does not have a valid ancestor", block: self, eligible: eligible, uncle_prevhash: Utils.encode_hex(uncle.prevhash)
      return false
    end

    if ineligible.include?(uncle)
      logger.error "Duplicate uncle", block: self, uncle: Utils.encode_hex(Utils.keccak256_rlp(uncle))
      return false
    end

    # FIXME: what if uncles include previously rewarded uncle?
    ineligible.push uncle
  end

  true
end