Class: Ethereum::Chain
Overview
Manages the chain and requests to it.
Constant Summary collapse
- HEAD_KEY =
'HEAD'.freeze
Instance Attribute Summary collapse
-
#env ⇒ Object
readonly
Returns the value of attribute env.
-
#head_candidate ⇒ Object
readonly
Returns the value of attribute head_candidate.
-
#index ⇒ Object
readonly
Returns the value of attribute index.
Instance Method Summary collapse
-
#add_block(block, forward_pending_transaction = true) ⇒ Object
Returns ‘true` if block was added successfully.
-
#add_transaction(transaction) ⇒ Bool, NilClass
Add a transaction to the ‘head_candidate` block.
- #coinbase ⇒ Object
- #coinbase=(v) ⇒ Object
- #commit ⇒ Object
- #get(blockhash) ⇒ Object
- #get_bloom(blockhash) ⇒ Object
-
#get_brothers(block) ⇒ Object
Return the uncles of the hypothetical child of ‘block`.
-
#get_chain(start: '', count: 10) ⇒ Object
Return ‘count` of blocks starting from head or `start`.
- #get_children(block) ⇒ Object
- #get_descendants(block, count: 1) ⇒ Object
-
#get_transactions ⇒ Object
Get a list of new transactions not yet included in a mined block but known to the chain.
-
#get_uncles(block) ⇒ Object
Return the uncles of ‘block`.
- #has_block(blockhash) ⇒ Object (also: #include?, #has_key?)
- #head ⇒ Object
- #in_main_branch?(block) ⇒ Boolean
-
#initialize(env, genesis: nil, new_head_cb: nil, coinbase: Address::ZERO) ⇒ Chain
constructor
A new instance of Chain.
- #transaction_count ⇒ Object
- #update_head(block, forward_pending_transaction = true) ⇒ Object
Constructor Details
#initialize(env, genesis: nil, new_head_cb: nil, coinbase: Address::ZERO) ⇒ Chain
Returns a new instance of Chain.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/ethereum/chain.rb', line 17 def initialize(env, genesis: nil, new_head_cb: nil, coinbase: Address::ZERO) raise ArgumentError, "env must be instance of Env" unless env.instance_of?(Env) @env = env @db = env.db @new_head_cb = new_head_cb @index = Index.new env @coinbase = coinbase initialize_blockchain(genesis) unless @db.has_key?(HEAD_KEY) logger.debug "chain @ head_hash=#{head}" @genesis = get @index.get_block_by_number(0) logger.debug "got genesis", nonce: Utils.encode_hex(@genesis.nonce), difficulty: @genesis.difficulty @head_candidate = nil update_head_candidate end |
Instance Attribute Details
#env ⇒ Object (readonly)
Returns the value of attribute env.
12 13 14 |
# File 'lib/ethereum/chain.rb', line 12 def env @env end |
#head_candidate ⇒ Object (readonly)
Returns the value of attribute head_candidate.
12 13 14 |
# File 'lib/ethereum/chain.rb', line 12 def head_candidate @head_candidate end |
#index ⇒ Object (readonly)
Returns the value of attribute index.
12 13 14 |
# File 'lib/ethereum/chain.rb', line 12 def index @index end |
Instance Method Details
#add_block(block, forward_pending_transaction = true) ⇒ Object
Returns ‘true` if block was added successfully.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/ethereum/chain.rb', line 109 def add_block(block, forward_pending_transaction=true) unless block.has_parent? || block.genesis? logger.debug "missing parent", block_hash: block return false end unless block.validate_uncles logger.debug "invalid uncles", block_hash: block return false end unless block.header.check_pow || block.genesis? logger.debug "invalid nonce", block_hash: block return false end if block.has_parent? begin Block.verify(block, block.get_parent) rescue InvalidBlock => e log.fatal "VERIFICATION FAILED", block_hash: block, error: e f = File.join Utils.data_dir, 'badblock.log' File.write(f, Utils.encode_hex(RLP.encode(block))) return false end end if block.number < head.number logger.debug "older than head", block_hash: block, head_hash: head end @index.add_block block store_block block # set to head if this makes the longest chain w/ most work for that number if block.chain_difficulty > head.chain_difficulty logger.debug "new head", block_hash: block, num_tx: block.transaction_count update_head block, forward_pending_transaction elsif block.number > head.number logger.warn "has higher blk number than head but lower chain_difficulty", block_has: block, head_hash: head, block_difficulty: block.chain_difficulty, head_difficulty: head.chain_difficulty end # Refactor the long calling chain block.transactions.clear_all block.receipts.clear_all block.state.db.commit_refcount_changes block.number block.state.db.cleanup block.number commit # batch commits all changes that came with the new block true end |
#add_transaction(transaction) ⇒ Bool, NilClass
Add a transaction to the ‘head_candidate` block.
If the transaction is invalid, the block will not be changed.
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 203 204 205 206 207 208 209 210 211 |
# File 'lib/ethereum/chain.rb', line 174 def add_transaction(transaction) raise AssertError, "head candiate cannot be nil" unless @head_candidate hc = @head_candidate logger.debug "add tx", num_txs: transaction_count, tx: transaction, on: hc if @head_candidate.include_transaction?(transaction.full_hash) logger.debug "known tx" return end old_state_root = hc.state_root # revert finalization hc.state_root = @pre_finalize_state_root begin success, output = hc.apply_transaction(transaction) rescue InvalidTransaction => e # if unsuccessful the prerequisites were not fullfilled and the tx is # invalid, state must not have changed logger.debug "invalid tx", error: e hc.state_root = old_state_root return false end logger.debug "valid tx" # we might have a new head_candidate (due to ctx switches in up layer) if @head_candidate != hc logger.debug "head_candidate changed during validation, trying again" return add_transaction(transaction) end @pre_finalize_state_root = hc.state_root hc.finalize logger.debug "tx applied", result: output raise AssertError, "state root unchanged!" unless old_state_root != hc.state_root true end |
#coinbase ⇒ Object
43 44 45 46 |
# File 'lib/ethereum/chain.rb', line 43 def coinbase raise AssertError, "coinbase changed!" unless @head_candidate.coinbase == @coinbase @coinbase end |
#coinbase=(v) ⇒ Object
48 49 50 51 52 |
# File 'lib/ethereum/chain.rb', line 48 def coinbase=(v) @coinbase = v # block reward goes to different address => redo finalization of head candidate update_head head end |
#commit ⇒ Object
102 103 104 |
# File 'lib/ethereum/chain.rb', line 102 def commit @db.commit end |
#get(blockhash) ⇒ Object
83 84 85 86 87 |
# File 'lib/ethereum/chain.rb', line 83 def get(blockhash) raise ArgumentError, "blockhash must be a String" unless blockhash.instance_of?(String) raise ArgumentError, "blockhash size must be 32" unless blockhash.size == 32 Block.find(@env, blockhash) end |
#get_bloom(blockhash) ⇒ Object
89 90 91 92 |
# File 'lib/ethereum/chain.rb', line 89 def get_bloom(blockhash) b = RLP.decode RLP.descend(@db.get(blockhash), 0, 6) Utils.big_endian_to_int b end |
#get_brothers(block) ⇒ Object
Return the uncles of the hypothetical child of ‘block`.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/ethereum/chain.rb', line 68 def get_brothers(block) o = [] i = 0 while block.has_parent? && i < @env.config[:max_uncle_depth] parent = block.get_parent children = get_children(parent).select {|c| c != block } o.concat children block = parent i += 1 end o end |
#get_chain(start: '', count: 10) ⇒ Object
Return ‘count` of blocks starting from head or `start`.
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/ethereum/chain.rb', line 233 def get_chain(start: '', count: 10) logger.debug "get_chain", start: Utils.encode_hex(start), count: count if start.true? return [] unless @index.db.include?(start) block = get start return [] unless in_main_branch?(block) else block = head end blocks = [] count.times do |i| blocks.push block break if block.genesis? block = block.get_parent end blocks end |
#get_children(block) ⇒ Object
162 163 164 |
# File 'lib/ethereum/chain.rb', line 162 def get_children(block) @index.get_children(block.full_hash).map {|c| get(c) } end |
#get_descendants(block, count: 1) ⇒ Object
261 262 263 264 265 266 267 |
# File 'lib/ethereum/chain.rb', line 261 def get_descendants(block, count: 1) logger.debug "get_descendants", block_hash: block raise AssertError, "cannot find block hash in current chain" unless include?(block.full_hash) block_numbers = (block.number+1)...([head.number+1, block.number+count+1].min) block_numbers.map {|n| get @index.get_block_by_number(n) } end |
#get_transactions ⇒ Object
Get a list of new transactions not yet included in a mined block but known to the chain.
217 218 219 220 221 222 223 224 |
# File 'lib/ethereum/chain.rb', line 217 def get_transactions if @head_candidate logger.debug "get_transactions called", on: @head_candidate @head_candidate.get_transactions else [] end end |
#get_uncles(block) ⇒ Object
Return the uncles of ‘block`.
57 58 59 60 61 62 63 |
# File 'lib/ethereum/chain.rb', line 57 def get_uncles(block) if block.has_parent? get_brothers(block.get_parent) else [] end end |
#has_block(blockhash) ⇒ Object Also known as: include?, has_key?
94 95 96 97 98 |
# File 'lib/ethereum/chain.rb', line 94 def has_block(blockhash) raise ArgumentError, "blockhash must be a String" unless blockhash.instance_of?(String) raise ArgumentError, "blockhash size must be 32" unless blockhash.size == 32 @db.include?(blockhash) end |
#head ⇒ Object
37 38 39 40 41 |
# File 'lib/ethereum/chain.rb', line 37 def head initialize_blockchain unless @db && @db.has_key?(HEAD_KEY) ptr = @db.get HEAD_KEY Block.find @env, ptr end |
#in_main_branch?(block) ⇒ Boolean
255 256 257 258 259 |
# File 'lib/ethereum/chain.rb', line 255 def in_main_branch?(block) block.full_hash == @index.get_block_by_number(block.number) rescue KeyError false end |
#transaction_count ⇒ Object
226 227 228 |
# File 'lib/ethereum/chain.rb', line 226 def transaction_count @head_candidate ? @head_candidate.transaction_count : 0 end |
#update_head(block, forward_pending_transaction = true) ⇒ Object
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/ethereum/chain.rb', line 269 def update_head(block, forward_pending_transaction=true) logger.debug "updating head" logger.debug "New Head is on a different branch", head_hash: block, old_head_hash: head if !block.genesis? && block.get_parent != head # Some temporary auditing to make sure pruning is working well if block.number > 0 && block.number % 500 == 0 && @db.instance_of?(DB::RefcountDB) # TODO end # Fork detected, revert death row and change logs if block.number > 0 b = block.get_parent h = head b_children = [] if b.full_hash != h.full_hash logger.warn "reverting" while h.number > b.number h.state.db.revert_refcount_changes h.number h = h.get_parent end while b.number > h.number b_children.push b b = b.get_parent end while b.full_hash != h.full_hash h.state.db.revert_refcount_changes h.number h = h.get_parent b_children.push b b = b.get_parent end b_children.each do |bc| Block.verify(bc, bc.get_parent) end end end @db.put HEAD_KEY, block.full_hash raise "Chain write error!" unless @db.get(HEAD_KEY) == block.full_hash @index.update_blocknumbers(head) raise "Fail to update head!" unless head == block logger.debug "set new head", head: head update_head_candidate forward_pending_transaction @new_head_cb.call(block) if @new_head_cb && !block.genesis? end |