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)


219
220
221
222
223
224
225
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
# File 'lib/ethereum/block.rb', line 219

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" 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:



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

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.



68
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
# File 'lib/ethereum/block.rb', line 68

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



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

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

  if 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:



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ethereum/block.rb', line 124

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



139
140
141
142
143
# File 'lib/ethereum/block.rb', line 139

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)


148
149
150
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
# File 'lib/ethereum/block.rb', line 148

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, data[:wei] if data[:wei]
    block.set_balance addr, data[:balance] if data[:balance]
    block.set_code addr, data[:code] if data[:code]
    block.set_nonce addr, data[:nonce] if data[:nonce]

    if data[:storage]
      data[:storage].each {|k, v| block.set_storage_data addr, k, v }
    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



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

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



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

def <(other)
  number < other.number
end

#==(other) ⇒ Object

Two blocks are equal iff they have the same hash.



1086
1087
1088
1089
# File 'lib/ethereum/block.rb', line 1086

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

#>(other) ⇒ Object



1095
1096
1097
# File 'lib/ethereum/block.rb', line 1095

def >(other)
  number > other.number
end

#account_exists(address) ⇒ Object



692
693
694
695
# File 'lib/ethereum/block.rb', line 692

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)


1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
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
# File 'lib/ethereum/block.rb', line 1011

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



284
285
286
# File 'lib/ethereum/block.rb', line 284

def add_listener(l)
  log_listeners.push l
end

#add_log(log) ⇒ Object



697
698
699
700
# File 'lib/ethereum/block.rb', line 697

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

#add_refund(x) ⇒ Object



382
383
384
# File 'lib/ethereum/block.rb', line 382

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.



391
392
393
394
395
396
397
398
399
400
# File 'lib/ethereum/block.rb', line 391

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:



406
407
408
409
410
411
412
413
414
415
416
417
418
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
# File 'lib/ethereum/block.rb', line 406

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



402
403
404
# File 'lib/ethereum/block.rb', line 402

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.



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

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.



654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
# File 'lib/ethereum/block.rb', line 654

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



688
689
690
# File 'lib/ethereum/block.rb', line 688

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)



996
997
998
999
1000
# File 'lib/ethereum/block.rb', line 996

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`



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

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

#finalizeObject

Apply rewards and commit.



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/ethereum/block.rb', line 538

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`.



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

def full_hash
  Utils.keccak256_rlp header
end

#full_hash_hexObject

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



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

def full_hash_hex
  Utils.encode_hex full_hash
end

#genesis?Boolean

Returns:

  • (Boolean)


1079
1080
1081
# File 'lib/ethereum/block.rb', line 1079

def genesis?
  number == 0
end

#get_ancestor_hash(n) ⇒ Object

Raises:

  • (ArgumentError)


1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
# File 'lib/ethereum/block.rb', line 1065

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)


1057
1058
1059
1060
1061
1062
1063
# File 'lib/ethereum/block.rb', line 1057

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



864
865
866
# File 'lib/ethereum/block.rb', line 864

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:



915
916
917
# File 'lib/ethereum/block.rb', line 915

def get_code(address)
   address, :code
end

#get_intrinsic_gas(tx) ⇒ Object



481
482
483
484
485
486
487
488
489
# File 'lib/ethereum/block.rb', line 481

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



826
827
828
# File 'lib/ethereum/block.rb', line 826

def get_nonce(address)
   address, :nonce
end

#get_parentObject

Get the parent of this block.



626
627
628
629
630
631
# File 'lib/ethereum/block.rb', line 626

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



616
617
618
619
620
621
# File 'lib/ethereum/block.rb', line 616

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



793
794
795
796
797
798
799
800
801
802
# File 'lib/ethereum/block.rb', line 793

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.



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

def get_receipts
  receipts = []
  i = 0
  loop do
    begin
      receipts.push get_receipt(i)
    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



938
939
940
941
# File 'lib/ethereum/block.rb', line 938

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



960
961
962
963
964
965
966
967
968
969
970
# File 'lib/ethereum/block.rb', line 960

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



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

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.



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

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.



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

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)


609
610
611
612
613
614
# File 'lib/ethereum/block.rb', line 609

def has_parent?
  get_parent
  true
rescue UnknownParentError
  false
end

#hashObject



1091
1092
1093
# File 'lib/ethereum/block.rb', line 1091

def hash
  Utils.big_endian_to_int full_hash
end

#include_transaction?(tx_hash) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


526
527
528
529
# File 'lib/ethereum/block.rb', line 526

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`



849
850
851
852
853
854
855
# File 'lib/ethereum/block.rb', line 849

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

#mining_hashObject



602
603
604
# File 'lib/ethereum/block.rb', line 602

def mining_hash
  header.mining_hash
end

#receipts_rootObject



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

def receipts_root
  @receipts.root_hash
end

#receipts_root=(v) ⇒ Object



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

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

#reset_cacheObject

Reset cache and journal without commiting any changes.



717
718
719
720
721
722
723
724
725
726
# File 'lib/ethereum/block.rb', line 717

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

#reset_storage(address) ⇒ Object



943
944
945
946
947
948
949
950
# File 'lib/ethereum/block.rb', line 943

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.



753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
# File 'lib/ethereum/block.rb', line 753

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`



876
877
878
# File 'lib/ethereum/block.rb', line 876

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`



927
928
929
# File 'lib/ethereum/block.rb', line 927

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`



838
839
840
# File 'lib/ethereum/block.rb', line 838

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



979
980
981
982
983
984
985
986
987
988
989
# File 'lib/ethereum/block.rb', line 979

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.



731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
# File 'lib/ethereum/block.rb', line 731

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



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

def state_root
  commit_state
  @state.root_hash
end

#state_root=(v) ⇒ Object



323
324
325
326
# File 'lib/ethereum/block.rb', line 323

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



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/ethereum/block.rb', line 566

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



1103
1104
1105
# File 'lib/ethereum/block.rb', line 1103

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

#transaction_countObject



531
532
533
# File 'lib/ethereum/block.rb', line 531

def transaction_count
  @transaction_count
end

#transaction_listObject



328
329
330
# File 'lib/ethereum/block.rb', line 328

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)


903
904
905
906
# File 'lib/ethereum/block.rb', line 903

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



302
303
304
# File 'lib/ethereum/block.rb', line 302

def tx_list_root
  @transactions.root_hash
end

#tx_list_root=(v) ⇒ Object



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

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

#validate_unclesObject

Validate the uncles of this block.

Raises:



335
336
337
338
339
340
341
342
343
344
345
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
# File 'lib/ethereum/block.rb', line 335

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 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