Class: Reth::Synchronizer
- Inherits:
-
Object
- Object
- Reth::Synchronizer
- Defined in:
- lib/reth/synchronizer.rb
Overview
Handles the synchronization of blocks.
There’s only one sync task active at a time. In order to deal with the worst case of initially syncing the wrong chain, a checkpoint blockhash can be specified and synced via force_sync.
Received blocks are given to chainservice.add_block, which has a fixed size queue, the synchronization blocks if the queue is full.
on_status:
if peer.head.chain_difficulty > chain.head.chain_difficulty
fetch peer.head and handle as newblock
on_newblock:
if block.parent
add
else
sync
on_blocks/on_blockhashes:
if synctask
handle to requester
elsif unkonwn and has parent
add to chain
else
drop
Constant Summary collapse
- MAX_NEWBLOCK_AGE =
maximum age (in blocks) of blocks received as newblock
5
Instance Attribute Summary collapse
-
#chain ⇒ Object
readonly
Returns the value of attribute chain.
-
#chainservice ⇒ Object
readonly
Returns the value of attribute chainservice.
-
#synctask ⇒ Object
readonly
Returns the value of attribute synctask.
Instance Method Summary collapse
-
#initialize(chainservice, force_sync = nil) ⇒ Synchronizer
constructor
A new instance of Synchronizer.
- #protocols ⇒ Object
- #receive_blockhashes(proto, blockhashes) ⇒ Object
- #receive_blocks(proto, t_blocks) ⇒ Object
-
#receive_newblock(proto, t_block, chain_difficulty) ⇒ Object
Called if there’s a newblock announced on the network.
-
#receive_newblockhashes(proto, newblockhashes) ⇒ Object
No way to check if this really an interesting block at this point.
-
#receive_status(proto, blockhash, chain_difficulty) ⇒ Object
Called if a new peer is connected.
- #syncing? ⇒ Boolean
- #synctask_exited(success = false) ⇒ Object
Constructor Details
#initialize(chainservice, force_sync = nil) ⇒ Synchronizer
Returns a new instance of Synchronizer.
45 46 47 48 49 50 51 |
# File 'lib/reth/synchronizer.rb', line 45 def initialize(chainservice, force_sync=nil) @chainservice = chainservice @force_sync = force_sync @chain = chainservice.chain @protocols = {} # proto => chain_difficulty @synctask = nil end |
Instance Attribute Details
#chain ⇒ Object (readonly)
Returns the value of attribute chain.
37 38 39 |
# File 'lib/reth/synchronizer.rb', line 37 def chain @chain end |
#chainservice ⇒ Object (readonly)
Returns the value of attribute chainservice.
37 38 39 |
# File 'lib/reth/synchronizer.rb', line 37 def chainservice @chainservice end |
#synctask ⇒ Object (readonly)
Returns the value of attribute synctask.
37 38 39 |
# File 'lib/reth/synchronizer.rb', line 37 def synctask @synctask end |
Instance Method Details
#protocols ⇒ Object
62 63 64 65 66 67 68 69 |
# File 'lib/reth/synchronizer.rb', line 62 def protocols @protocols = @protocols .map {|proto, diff| [proto, diff] } .select {|tuple| !tuple[0].stopped? } .to_h @protocols.keys.sort_by {|proto| -@protocols[proto] } end |
#receive_blockhashes(proto, blockhashes) ⇒ Object
176 177 178 179 180 181 182 183 |
# File 'lib/reth/synchronizer.rb', line 176 def receive_blockhashes(proto, blockhashes) logger.debug 'blockhashes received', proto: proto, num: blockhashes.size if @synctask @synctask.receive_blockhashes proto, blockhashes else logger.warn 'no synctask, not expecting blockhashes' end end |
#receive_blocks(proto, t_blocks) ⇒ Object
167 168 169 170 171 172 173 174 |
# File 'lib/reth/synchronizer.rb', line 167 def receive_blocks(proto, t_blocks) logger.debug 'blocks received', proto: proto, num: t_blocks.size if @synctask @synctask.receive_blocks proto, t_blocks else logger.warn 'no synctask, not expecting blocks' end end |
#receive_newblock(proto, t_block, chain_difficulty) ⇒ Object
Called if there’s a newblock announced on the network.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/reth/synchronizer.rb', line 74 def receive_newblock(proto, t_block, chain_difficulty) logger.debug 'newblock', proto: proto, block: t_block, chain_difficulty: chain_difficulty, client: proto.peer.remote_client_version if @chain.include?(t_block.header.full_hash) raise AssertError, 'chain difficulty mismatch' unless chain_difficulty == @chain.get(t_block.header.full_hash).chain_difficulty end @protocols[proto] = chain_difficulty if @chainservice.knows_block(t_block.header.full_hash) logger.debug 'known block' return end expected_difficulty = @chain.head.chain_difficulty + t_block.header.difficulty if chain_difficulty >= @chain.head.chain_difficulty # broadcast duplicates filtering is done in chainservice logger.debug 'sufficient difficulty, broadcasting', client: proto.peer.remote_client_version @chainservice.broadcast_newblock t_block, chain_difficulty, proto else age = @chain.head.number - t_block.header.number logger.debug "low difficulty", client: proto.peer.remote_client_version, chain_difficulty: chain_difficulty, expected_difficulty: expected_difficulty, block_age: age if age > MAX_NEWBLOCK_AGE logger.debug 'newblock is too old, not adding', block_age: age, max_age: MAX_NEWBLOCK_AGE return end end if @chainservice.knows_block(t_block.header.prevhash) logger.debug 'adding block' @chainservice.add_block t_block, proto else logger.debug 'missing parent' if @synctask logger.debug 'existing task, discarding' else @synctask = SyncTask.new self, proto, t_block.header.full_hash, chain_difficulty end end end |
#receive_newblockhashes(proto, newblockhashes) ⇒ Object
No way to check if this really an interesting block at this point. Might lead to an amplification attack, need to track this proto and judge usefulness.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/reth/synchronizer.rb', line 147 def receive_newblockhashes(proto, newblockhashes) logger.debug 'received newblockhashes', num: newblockhashes.size, proto: proto newblockhashes = newblockhashes.select {|h| !@chainservice.knows_block(h) } known = @protocols.include?(proto) if !known || newblockhashes.empty? || @synctask logger.debug 'discarding', known: known, synctask: syncing?, num: newblockhashes.size return end if newblockhashes.size != 1 logger.warn 'supporting only one newblockhash', num: newblockhashes.size end blockhash = newblockhashes[0] logger.debug 'starting synctask for newblockhashes', blockhash: Utils.encode_hex(blockhash) @synctask = SyncTask.new self, proto, blockhash, 0, true end |
#receive_status(proto, blockhash, chain_difficulty) ⇒ Object
Called if a new peer is connected.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/reth/synchronizer.rb', line 119 def receive_status(proto, blockhash, chain_difficulty) logger.debug 'status received', proto: proto, chain_difficulty: chain_difficulty @protocols[proto] = chain_difficulty if @chainservice.knows_block(blockhash) || @synctask logger.debug 'existing task or known hash, discarding' return end if @force_sync blockhash, difficulty = force_sync logger.debug 'starting forced synctask', blockhash: Utils.encode_hex(blockhash) @synctask = SyncTask.new self, proto, blockhash, difficulty elsif chain_difficulty > @chain.head.chain_difficulty logger.debug 'sufficient difficulty' @synctask = SyncTask.new self, proto, blockhash, chain_difficulty end rescue logger.debug $! logger.debug $!.backtrace[0,10].join("\n") end |
#syncing? ⇒ Boolean
53 54 55 |
# File 'lib/reth/synchronizer.rb', line 53 def syncing? !!@synctask end |
#synctask_exited(success = false) ⇒ Object
57 58 59 60 |
# File 'lib/reth/synchronizer.rb', line 57 def synctask_exited(success=false) @force_sync = nil if success @synctask = nil end |