class Ethereum::Block

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.

Constants

HeaderGetters
HeaderSetters

Attributes

ancestor_hashes[RW]
env[RW]
ether_delta[RW]
log_listeners[RW]
logs[RW]
receipts[RW]
refunds[RW]
state[RW]
suicides[RW]
transactions[RW]

Public Class Methods

build_from_header(header_rlp, env) click to toggle source

Create a block without specifying transactions or uncles.

@param header_rlp [String] the RLP encoded block header @param env [Env] provide database for the block

@return [Block]

# 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) click to toggle source

Create a new block based on a parent block.

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

# 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) click to toggle source
# 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[: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) click to toggle source
# File lib/ethereum/block.rb, line 126
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) click to toggle source
# File lib/ethereum/block.rb, line 141
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) click to toggle source

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

cached including hash.
# File lib/ethereum/block.rb, line 35
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={}) click to toggle source

Build the genesis block.

# File lib/ethereum/block.rb, line 150
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
new(*args) click to toggle source

Arguments in format of:

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

@param args [Array] mix of arguments:

* header {BlockHeader} optional. if given, will be used as block
  header. if not given, you must specify header by `options[:header]`
* 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
# File lib/ethereum/block.rb, line 225
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
verify(block, parent) click to toggle source
# 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

Public Instance Methods

<(other) click to toggle source
# File lib/ethereum/block.rb, line 1112
def <(other)
  number < other.number
end
==(other) click to toggle source

Two blocks are equal iff they have the same hash.

# File lib/ethereum/block.rb, line 1099
def ==(other)
  (other.instance_of?(Block) || other.instance_of?(CachedBlock)) &&
    full_hash == other.full_hash
end
>(other) click to toggle source
# File lib/ethereum/block.rb, line 1108
def >(other)
  number > other.number
end
account_exists(address) click to toggle source
# File lib/ethereum/block.rb, line 704
def account_exists(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) click to toggle source

Serialize an account to a hash with human readable entries.

@param address [String] the account address @param with_storage_root [Bool] include the account's storage root @param with_storage [Bool] include the whole account's storage

@return [Hash] hash represent the account

# File lib/ethereum/block.rb, line 1024
def account_to_dict(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 = {}
  account = get_account address

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

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

  storage_trie = SecureTrie.new PruningTrie.new(db, account.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) click to toggle source
# File lib/ethereum/block.rb, line 290
def add_listener(l)
  log_listeners.push l
end
add_log(log) click to toggle source
# File lib/ethereum/block.rb, line 709
def add_log(log)
  logs.push log
  log_listeners.each {|l| l.call log }
end
add_refund(x) click to toggle source
# File lib/ethereum/block.rb, line 394
def add_refund(x)
  self.refunds += x
end
add_transaction_to_list(tx) click to toggle source

Add a transaction to the transaction trie.

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

# File lib/ethereum/block.rb, line 403
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) click to toggle source
# File lib/ethereum/block.rb, line 418
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?
    del_account 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) click to toggle source
# File lib/ethereum/block.rb, line 414
def build_external_call(tx)
  ExternalCall.new self, tx
end
chain_difficulty() click to toggle source

Get the summarized difficulty.

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

# File lib/ethereum/block.rb, line 651
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_state() click to toggle source

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

# File lib/ethereum/block.rb, line 666
def commit_state
  return if @journal.empty?

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

  addresses.each do |addr|
    acct = get_account 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_db() click to toggle source
# File lib/ethereum/block.rb, line 700
def commit_state_db
  @state.db.commit
end
del_account(address) click to toggle source

Delete an account.

@param address [String] the address of the account (binary or hex string)

# File lib/ethereum/block.rb, line 1009
def del_account(address)
  address = Utils.normalize_address address
  commit_state
  @state.delete address
end
delta_balance(address, value) click to toggle source

Increase the balance of an account.

@param address [String] the address of the account (binary or hex string) @param value [Integer] can be positive or negative

@return [Bool] return `true` if successful, otherwise `false`

# File lib/ethereum/block.rb, line 722
def delta_balance(address, value)
  delta_account_item(address, :balance, value)
end
finalize() click to toggle source

Apply rewards and commit.

# File lib/ethereum/block.rb, line 550
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_hash() click to toggle source

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

# File lib/ethereum/block.rb, line 297
def full_hash
  Utils.keccak256_rlp header
end
full_hash_hex() click to toggle source

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

# File lib/ethereum/block.rb, line 304
def full_hash_hex
  Utils.encode_hex full_hash
end
genesis?() click to toggle source
# File lib/ethereum/block.rb, line 1092
def genesis?
  number == 0
end
get_ancestor_hash(n) click to toggle source
# File lib/ethereum/block.rb, line 1078
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) click to toggle source

Return `n` ancestors of this block.

@return [Array] array of ancestors in format of `[parent, parent.parent, …]

# File lib/ethereum/block.rb, line 1070
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) click to toggle source

Get the balance of an account.

@param address [String] the address of the account (binary or hex string)

@return [Integer] balance value

# File lib/ethereum/block.rb, line 877
def get_balance(address)
  get_account_item address, :balance
end
get_code(address) click to toggle source

Get the code of an account.

@param address [String] the address of the account (binary or hex string)

@return [String] account code

# File lib/ethereum/block.rb, line 928
def get_code(address)
  get_account_item address, :code
end
get_intrinsic_gas(tx) click to toggle source
# File lib/ethereum/block.rb, line 493
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) click to toggle source

Get the nonce of an account.

@param address [String] the address of the account (binary or hex string)

@return [Integer] the nonce value

# File lib/ethereum/block.rb, line 839
def get_nonce(address)
  get_account_item address, :nonce
end
get_parent() click to toggle source

Get the parent of this block.

# File lib/ethereum/block.rb, line 638
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_header() click to toggle source
# File lib/ethereum/block.rb, line 628
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) click to toggle source

Get the receipt of the `num`th transaction.

@raise [IndexError] if receipt at index is not found

@return [Receipt]

# File lib/ethereum/block.rb, line 805
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_receipts() click to toggle source

Build a list of all receipts in this block.

# File lib/ethereum/block.rb, line 819
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) click to toggle source

Get the trie holding an account's storage.

@param address [String] the address of the account (binary or hex string)

@return [Trie] the storage trie of account

# File lib/ethereum/block.rb, line 951
def get_storage(address)
  storage_root = get_account_item address, :storage
  SecureTrie.new PruningTrie.new(db, storage_root)
end
get_storage_data(address, index) click to toggle source

Get a specific item in the storage of an account.

@param address [String] the address of the account (binary or hex string) @param index [Integer] the index of the requested item in the storage

@return [Integer] the value at storage index

# File lib/ethereum/block.rb, line 973
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) click to toggle source

Get the `num`th transaction in this block.

@raise [IndexError] if the transaction does not exist

# File lib/ethereum/block.rb, line 508
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_hashes() click to toggle source

helper to check if block contains a tx.

# File lib/ethereum/block.rb, line 532
def get_transaction_hashes
  @transaction_count.times.map do |i|
    Utils.keccak256 @transactions[RLP.encode(i)]
  end
end
get_transactions() click to toggle source

Build a list of all transactions in this block.

# File lib/ethereum/block.rb, line 519
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?() click to toggle source

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

# File lib/ethereum/block.rb, line 621
def has_parent?
  get_parent
  true
rescue UnknownParentError
  false
end
hash() click to toggle source
# File lib/ethereum/block.rb, line 1104
def hash
  Utils.big_endian_to_int full_hash
end
include_transaction?(tx_hash) click to toggle source
# File lib/ethereum/block.rb, line 538
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) click to toggle source

Increment the nonce of an account.

@param address [String] the address of the account (binary or hex string)

@return [Bool] `true` if successful, otherwise `false`

# File lib/ethereum/block.rb, line 862
def increment_nonce(address)
  if get_nonce(address) == 0
    delta_account_item address, :nonce, config[:account_initial_nonce]+1
  else
    delta_account_item address, :nonce, 1
  end
end
inspect()
Alias for: to_s
mining_hash() click to toggle source
# File lib/ethereum/block.rb, line 614
def mining_hash
  header.mining_hash
end
receipts_root() click to toggle source
# File lib/ethereum/block.rb, line 316
def receipts_root
  @receipts.root_hash
end
receipts_root=(v) click to toggle source
# File lib/ethereum/block.rb, line 320
def receipts_root=(v)
  @receipts = PruningTrie.new db, v
end
reset_cache() click to toggle source

Reset cache and journal without commiting any changes.

# File lib/ethereum/block.rb, line 729
def reset_cache
  @caches = {
    all: {},
    balance: {},
    nonce: {},
    code: {},
    storage: {}
  }
  @journal = []
end
reset_storage(address) click to toggle source
# File lib/ethereum/block.rb, line 956
def reset_storage(address)
  set_account_item 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) click to toggle source

Revert to a previously made snapshot.

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

# File lib/ethereum/block.rb, line 765
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) click to toggle source

Set the balance of an account.

@param address [String] the address of the account (binary or hex string) @param value [Integer] the new balance value

@return [Bool] `true` if successful, otherwise `false`

# File lib/ethereum/block.rb, line 889
def set_balance(address, value)
  set_account_item address, :balance, value
end
set_code(address, value) click to toggle source

Set the code of an account.

@param address [String] the address of the account (binary or hex string) @param value [String] the new code bytes

@return [Bool] `true` if successful, otherwise `false`

# File lib/ethereum/block.rb, line 940
def set_code(address, value)
  set_account_item address, :code, value
end
set_nonce(address, value) click to toggle source

Set the nonce of an account.

@param address [String] the address of the account (binary or hex string) @param value [Integer] the new nonce

@return [Bool] `true` if successful, otherwise `false`

# File lib/ethereum/block.rb, line 851
def set_nonce(address, value)
  set_account_item address, :nonce, value
end
set_storage_data(address, index, value) click to toggle source

Set a specific item in the storage of an account.

@param address [String] the address of the account (binary or hex string) @param index [Integer] the index of the requested item in the storage @param value [Integer] the new value of the item

# File lib/ethereum/block.rb, line 992
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
snapshot() click to toggle source

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

# File lib/ethereum/block.rb, line 743
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_root() click to toggle source
# File lib/ethereum/block.rb, line 324
def state_root
  commit_state
  @state.root_hash
end
state_root=(v) click to toggle source
# File lib/ethereum/block.rb, line 329
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) click to toggle source

Serialize the block to a readable hash.

@param with_state [Bool] include state for all accounts @param full_transactions [Bool] include serialized transactions (hashes

otherwise)

@param with_storage_roots [Bool] if account states are included also

include their storage roots

@param with_uncles [Bool] include uncle hashes

@return [Hash] a hash represents the block

# File lib/ethereum/block.rb, line 578
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)] = account_to_dict(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_s() click to toggle source
# File lib/ethereum/block.rb, line 1116
def to_s
  "#<#{self.class.name}:#{object_id} ##{number} #{Utils.encode_hex full_hash[0,8]}>"
end
Also aliased as: inspect
transaction_count() click to toggle source
# File lib/ethereum/block.rb, line 543
def transaction_count
  @transaction_count
end
transaction_list() click to toggle source
# File lib/ethereum/block.rb, line 334
def transaction_list
  @transaction_count.times.map {|i| get_transaction(i) }
end
transfer_value(from, to, value) click to toggle source

Transfer a value between two account balance.

@param from [String] the address of the sending account (binary or hex

string)

@param to [String] the address of the receiving account (binary or hex

string)

@param value [Integer] the (positive) value to send

@return [Bool] `true` if successful, otherwise `false`

# File lib/ethereum/block.rb, line 916
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_root() click to toggle source
# File lib/ethereum/block.rb, line 308
def tx_list_root
  @transactions.root_hash
end
tx_list_root=(v) click to toggle source
# File lib/ethereum/block.rb, line 312
def tx_list_root=(v)
  @transactions = PruningTrie.new db, v
end
uncles_hash() click to toggle source
# File lib/ethereum/block.rb, line 338
def uncles_hash
  Utils.keccak256_rlp(uncles)
end
validate_transaction(tx) click to toggle source
# File lib/ethereum/block.rb, line 1121
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_uncles() click to toggle source

Validate the uncles of this block.

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

Private Instance Methods

delta_account_item(address, param, value) click to toggle source

Add a value to an account item.

If the resulting value would be negative, it is left unchanged and `false` is returned.

@param address [String] the address of the account (binary or hex string) @param param [Symbol] the parameter to increase or decrease (`:nonce`,

`:balance`, `:storage`, or `:code`)

@param value [Integer] can be positive or negative

@return [Bool] `true` if the operation was successful, `false` if not

# File lib/ethereum/block.rb, line 1271
def delta_account_item(address, param, value)
  new_value = get_account_item(address, param) + value
  return false if new_value < 0

  set_account_item(address, param, new_value % 2**256)
  true
end
get_account(address) click to toggle source

Get the account with the given address.

Note that this method ignores cached account items.

# File lib/ethereum/block.rb, line 1319
def get_account(address)
  address = Utils.normalize_address address, allow_blank: true
  rlpdata = @state[address]

  if rlpdata == Trie::BLANK_NODE
    Account.build_blank db, config[:account_initial_nonce]
  else
    RLP.decode(rlpdata, sedes: Account, db: db).tap do |acct|
      acct.make_mutable!
      acct._cached_rlp = nil
    end
  end
end
get_account_item(address, param) click to toggle source

Get a specific parameter of a specific account.

@param address [String] the address of the account (binary or hex string) @param param [Symbol] the requested parameter (`:nonce`, `:balance`,

`:storage` or `:code`)

@return [Object] the value

# File lib/ethereum/block.rb, line 1288
def get_account_item(address, param)
  address = Utils.normalize_address address, allow_blank: true
  return @caches[param][address] if @caches[param].has_key?(address)

  account = get_account address
  v = account.send param
  @caches[param][address] = v
  v
end
hard_fork_initialize(parent) click to toggle source
# File lib/ethereum/block.rb, line 1188
def hard_fork_initialize(parent)
  if number == @config[:metropolis_fork_blknum]
    set_code Utils.normalize_address(@config[:metropolis_stateroot_store]), @config[:metropolis_getter_code]
    set_code Utils.normalize_address(@config[:metropolis_blockhash_store]), @config[:metropolis_getter_code]
  end

  if number >= @config[:metropolis_fork_blknum]
    set_storage_data Utils.normalize_address(@config[:metropolis_stateroot_store]), (number % @config[:metropolis_wrapround]), parent.state_root
    set_storage_data Utils.normalize_address(@config[:metropolis_blockhash_store]), (number % @config[:metropolis_wrapround]), prevhash
  end

  if number == @config[:dao_fork_blknum]
    @config[:child_dao_list].each do |acct|
      transfer_value acct, @config[:dao_withdrawer], get_balance(acct)
    end
  end
end
initialize_state(transaction_list, parent, making) click to toggle source
# File lib/ethereum/block.rb, line 1154
def initialize_state(transaction_list, parent, making)
  state_unknown =
    prevhash != @config[:genesis_prevhash] &&
    number != 0 &&
    header.state_root != PruningTrie::BLANK_ROOT &&
    (header.state_root.size != 32 || !db.has_key?("validated:#{full_hash}")) &&
    !making

  if state_unknown
    raise ArgumentError, "transaction list cannot be nil" unless transaction_list

    parent ||= get_parent_header
    @state = SecureTrie.new PruningTrie.new(db, parent.state_root)
    @transaction_count = 0 # TODO - should always equal @transactions.size
    self.gas_used = 0

    hard_fork_initialize(parent)

    transaction_list.each {|tx| apply_transaction tx }

    finalize
  else # trust the state root in the header
    @state = SecureTrie.new PruningTrie.new(db, header._state_root)
    @transaction_count = 0

    transaction_list.each {|tx| add_transaction_to_list(tx) } if transaction_list
    raise ValidationError, "Transaction list root hash does not match" if @transactions.root_hash != header.tx_list_root

    # receipts trie populated by add_transaction_to_list is incorrect (it
    # doesn't know intermediate states), so reset it
    @receipts = PruningTrie.new db, header.receipts_root
  end
end
logger() click to toggle source
# File lib/ethereum/block.rb, line 1150
def logger
  @logger ||= Logger.new 'eth.block'
end
mk_transaction_receipt(tx) click to toggle source
# File lib/ethereum/block.rb, line 1346
def mk_transaction_receipt(tx)
  if number >= @config[:metropolis_fork_blknum]
    Receipt.new Constant::HASH_ZERO, gas_used, logs
  else
    Receipt.new state_root, gas_used, logs
  end
end
set_account_item(address, param, value) click to toggle source

Set a specific parameter of a specific account.

@param address [String] the address of the account (binary or hex string) @param param [Symbol] the requested parameter (`:nonce`, `:balance`,

`:storage` or `:code`)

@param value [Object] the new value

# File lib/ethereum/block.rb, line 1306
def set_account_item(address, param, value)
  raise ArgumentError, "invalid address: #{address}" unless address.size == 20 || address.size == 40
  address = Utils.decode_hex(address) if address.size == 40

  set_and_journal(param, address, value)
  set_and_journal(:all, address, true)
end
set_and_journal(ns, k, v) click to toggle source

@param ns [Symbol] cache namespace @param k [String] cache key @param v [Object] cache value

# File lib/ethereum/block.rb, line 1338
def set_and_journal(ns, k, v)
  prev = @caches[ns][k]
  if prev != v
    @journal.push [ns, k, prev, v]
    @caches[ns][k] = v
  end
end
validate_block!(original_values) click to toggle source

Validate (transaction applied) block against its header, plus fields and value check.

# File lib/ethereum/block.rb, line 1225
def validate_block!(original_values)
  raise InvalidBlock, "gas_used mistmatch actual: #{gas_used} target: #{original_values[:gas_used]}" if gas_used != original_values[:gas_used]
  raise InvalidBlock, "timestamp mistmatch actual: #{timestamp} target: #{original_values[:timestamp]}" if timestamp != original_values[:timestamp]
  raise InvalidBlock, "difficulty mistmatch actual: #{difficulty} target: #{original_values[:difficulty]}" if difficulty != original_values[:difficulty]
  raise InvalidBlock, "bloom mistmatch actual: #{bloom} target: #{original_values[:bloom]}" if bloom != original_values[:bloom]

  uh = Utils.keccak256_rlp uncles
  raise InvalidBlock, "uncles_hash mistmatch actual: #{uh} target: #{original_values[:uncles_hash]}" if uh != original_values[:uncles_hash]

  raise InvalidBlock, "header must reference no block" unless header.block.nil?

  raise InvalidBlock, "state_root mistmatch actual: #{Utils.encode_hex @state.root_hash} target: #{Utils.encode_hex header.state_root}" if @state.root_hash != header.state_root
  raise InvalidBlock, "tx_list_root mistmatch actual: #{@transactions.root_hash} target: #{header.tx_list_root}" if @transactions.root_hash != header.tx_list_root
  raise InvalidBlock, "receipts_root mistmatch actual: #{@receipts.root_hash} target: #{header.receipts_root}" if @receipts.root_hash != header.receipts_root

  raise ValueError, "Block is invalid" unless validate_fields

  raise ValueError, "Extra data cannot exceed #{config[:max_extradata_length]} bytes" if header.extra_data.size > config[:max_extradata_length]
  raise ValueError, "Coinbase cannot be empty address" if header.coinbase.false?
  raise ValueError, "State merkle root of block #{self} not found in database" unless @state.root_hash_valid?
  raise InvalidNonce, "PoW check failed" if !genesis? && nonce.true? && !header.check_pow
end
validate_fields() click to toggle source

Check that the values of all fields are well formed.

Serialize and deserialize and check that the values didn't change.

# File lib/ethereum/block.rb, line 1253
def validate_fields
  l = Block.serialize self
  RLP.decode(RLP.encode(l)) == l
end
validate_parent!(parent) click to toggle source

Validate block (header) against previous block.

# File lib/ethereum/block.rb, line 1209
def validate_parent!(parent)
  raise ValidationError, "Parent lives in different database" if parent && db != parent.db && db.db != parent.db # TODO: refactor the db.db mess
  raise ValidationError, "Block's prevhash and parent's hash do not match" if prevhash != parent.full_hash
  raise ValidationError, "Block's number is not the successor of its parent number" if number != parent.number+1
  raise ValidationError, "Block's gaslimit is inconsistent with its parent's gaslimit" unless Block.check_gaslimit(parent, gas_limit)
  raise ValidationError, "Block's difficulty is inconsistent with its parent's difficulty" if difficulty != Block.calc_difficulty(parent, timestamp)
  raise ValidationError, "Gas used exceeds gas limit" if gas_used > gas_limit
  raise ValidationError, "Block's gaslimit went too high!" if gas_limit > @config[:max_gas_limit]
  raise ValidationError, "Timestamp equal to or before parent" if timestamp <= parent.timestamp
  raise ValidationError, "Timestamp way too large" if timestamp > Constant::UINT_MAX
end