@@ -53,13 +53,6 @@ defmodule Entry do
5353 h (entry_hash || state_root || receipts_logs_extra_root)
5454 """
5555
56- # :root_chain — PHASE 1 schema-only rollout.
57- # The field is whitelisted in @fields_header on every node, but proposers
58- # do NOT populate it yet (see build_next/3 below). Because Map.take only
59- # picks keys that exist on the source, current blocks (which don't carry
60- # the key) hash identically to pre-rollout. Once enough peers have shipped
61- # Phase 1, the next release flips proposers on (Phase 2). Old nodes that
62- # missed Phase 1 will reject Phase-2 blocks.
6356 @ fields [ :header , :hash , :signature , :txs , :mask , :mask_size , :mask_set_size ]
6457 @ fields_header [ :height , :prev_hash , :slot , :prev_slot , :signer , :dr , :vr , :root_tx , :root_validator , :root_chain ]
6558
@@ -146,15 +139,19 @@ defmodule Entry do
146139 if ! is_list ( e . txs ) , do: throw ( % { error: :txs_not_list } )
147140 if length ( e . txs ) > 100 , do: throw ( % { error: :TEMPORARY_txs_only_100_per_entry } )
148141
142+ # Duplicate TX check
143+ tx_hashes = Enum . map ( e . txs , & & 1 . hash )
144+ if length ( tx_hashes ) != length ( Enum . uniq ( tx_hashes ) ) , do: throw ( % { error: :duplicate_tx_in_entry } )
145+
149146 if ! is_binary ( eh . root_tx ) , do: throw ( % { error: :root_tx_not_binary } )
150147 if byte_size ( eh . root_tx ) != 32 , do: throw ( % { error: :root_tx_not_256_bits } )
151- if eh . root_tx != root_tx ( Enum . map ( e . txs , & & 1 . hash ) ) , do: throw ( % { error: :root_tx_invalid } )
148+ if eh . root_tx != root_tx ( tx_hashes , eh . height ) , do: throw ( % { error: :root_tx_invalid } )
152149
153150 if ! is_binary ( eh . root_validator ) , do: throw ( % { error: :root_validator_not_binary } )
154151 if byte_size ( eh . root_validator ) != 32 , do: throw ( % { error: :root_validator_not_256_bits } )
155152 validators = DB.Chain . validators_for_height ( eh . height )
156153 validators_last_change_height = DB.Chain . validators_last_change_height ( eh . height )
157- if eh . root_validator != root_validator ( validators , validators_last_change_height ) , do: throw ( % { error: :root_validator_invalid } )
154+ if eh . root_validator != root_validator ( validators , validators_last_change_height , eh . height ) , do: throw ( % { error: :root_validator_invalid } )
158155
159156 is_special_meeting_block = ! ! e [ :mask ]
160157 steam = Task . async_stream ( e . txs , fn txu ->
@@ -213,6 +210,13 @@ defmodule Entry do
213210 end
214211 end
215212
213+ def root_chain_height ( ) , do: RDBProtocol . forkheight ( )
214+
215+ def check_root_chain ( header , mmr ) do
216+ ours = MMR . root_chain ( DB.MMR . chain_id ( ) , mmr )
217+ if header . root_chain == ours , do: :ok , else: { :mismatch , ours , header . root_chain }
218+ end
219+
216220 def validate_next ( cur_entry , next_entry ) do
217221 try do
218222 ceh = cur_entry . header
@@ -224,26 +228,15 @@ defmodule Entry do
224228 if :crypto . hash ( :sha256 , ceh . dr ) != neh . dr , do: throw ( % { error: :invalid_dr } )
225229 if ! BlsEx . verify? ( neh . signer , neh . vr , ceh . vr , BLS12AggSig . dst_vrf ( ) ) , do: throw ( % { error: :invalid_vr } )
226230
227- # root_chain soft validation (log-only for now). Only runs when the
228- # proposer set the field AND our local MMR is in sync at this height.
229- # Filters bad blocks out of the candidate pool BEFORE expensive
230- # contract exec in apply_entry — so a persistently bad block doesn't
231- # DoS our apply path. Flip the IO.inspect to a `throw` to enforce.
232- case neh [ :root_chain ] do
233- nil -> :ok
234- theirs ->
235- mmr = DB.MMR . load_or_empty ( )
236- if mmr . size == neh . height do
237- ours = MMR . root_chain ( DB.MMR . chain_id ( ) , mmr )
238- if theirs != ours do
239- IO . inspect (
240- { :root_chain_mismatch , neh . height ,
241- ours: Base . encode16 ( ours , case: :lower ) ,
242- theirs: Base . encode16 ( theirs , case: :lower ) }
243- )
244- # throw(%{error: :root_chain_mismatch})
245- end
231+ if neh . height >= root_chain_height ( ) do
232+ mmr = DB.MMR . load_or_empty ( )
233+ if mmr . size == neh . height do
234+ case check_root_chain ( neh , mmr ) do
235+ :ok -> :ok
236+ { :mismatch , ours , theirs } ->
237+ throw ( % { error: :root_chain_mismatch , height: neh . height , ours: Base58 . encode ( ours ) , theirs: theirs && Base58 . encode ( theirs ) } )
246238 end
239+ end
247240 end
248241
249242 chain_epoch = div ( neh . height , 100_000 )
@@ -278,20 +271,30 @@ defmodule Entry do
278271 validators = DB.Chain . validators_for_height ( next_height )
279272 validators_last_change_height = DB.Chain . validators_last_change_height ( next_height )
280273
281- % {
282- header: % {
283- slot: cur_entry . header . slot + 1 ,
284- height: next_height ,
285- prev_slot: cur_entry . header . slot ,
286- prev_hash: cur_entry . hash ,
287- dr: dr ,
288- vr: vr ,
289- signer: pk ,
290- root_tx: root_tx ( Enum . map ( txus , & & 1 . hash ) ) ,
291- root_validator: root_validator ( validators , validators_last_change_height )
292- } ,
293- txs: txus
274+ header = % {
275+ slot: cur_entry . header . slot + 1 ,
276+ height: next_height ,
277+ prev_slot: cur_entry . header . slot ,
278+ prev_hash: cur_entry . hash ,
279+ dr: dr ,
280+ vr: vr ,
281+ signer: pk ,
282+ root_tx: root_tx ( Enum . map ( txus , & & 1 . hash ) , next_height ) ,
283+ root_validator: root_validator ( validators , validators_last_change_height , next_height )
294284 }
285+
286+ header =
287+ if next_height >= root_chain_height ( ) do
288+ mmr = DB.MMR . load_or_empty ( )
289+ if mmr . size != next_height do
290+ raise "build_next root_chain: MMR size #{ mmr . size } != next_height #{ next_height } ; refusing to build (out-of-sync root_chain would halt consensus)"
291+ end
292+ Map . put ( header , :root_chain , MMR . root_chain ( DB.MMR . chain_id ( ) , mmr ) )
293+ else
294+ header
295+ end
296+
297+ % { header: header , txs: txus }
295298 end
296299
297300 def sign ( seed , entry ) do
@@ -313,8 +316,12 @@ defmodule Entry do
313316 entry . header . height
314317 end
315318
316- def root_tx ( hashes ) do
317- RDB . bintree_root ( root_tx_build ( hashes ) )
319+ defp merkle_root ( kvs , height ) do
320+ if height >= root_chain_height ( ) , do: RDB . hbsmt_root ( kvs ) , else: RDB . bintree_root ( kvs )
321+ end
322+
323+ def root_tx ( hashes , height ) do
324+ merkle_root ( root_tx_build ( hashes ) , height )
318325 end
319326 def root_tx_build ( hashes ) do
320327 by_index_hash = Enum . flat_map ( Enum . with_index ( hashes ) , fn { hash , index } ->
@@ -323,8 +330,8 @@ defmodule Entry do
323330 by_index_hash ++ [ { nil , "count" , "#{ length ( hashes ) } " } ]
324331 end
325332
326- def root_validator ( validator_pks , last_change_height ) do
327- RDB . bintree_root ( root_validator_build ( validator_pks , last_change_height ) )
333+ def root_validator ( validator_pks , last_change_height , height ) do
334+ merkle_root ( root_validator_build ( validator_pks , last_change_height ) , height )
328335 end
329336 def root_validator_build ( validator_pks , last_change_height ) do
330337 by_index_hash = Enum . flat_map ( Enum . with_index ( validator_pks ) , fn { hash , index } ->
0 commit comments