diff --git a/src/Ouroboros_Praos_Implementation.thy b/src/Ouroboros_Praos_Implementation.thy index 08751a7..e5e0963 100644 --- a/src/Ouroboros_Praos_Implementation.thy +++ b/src/Ouroboros_Praos_Implementation.thy @@ -4,6 +4,7 @@ theory Ouroboros_Praos_Implementation imports "HOL-Library.BNF_Corec" "HOL-Library.Sublist" + "HOL-Library.FSet" "Finite_Map_Extras_Test" "Thorn_Calculus.Thorn_Calculus-Processes" Complex_Main @@ -375,9 +376,9 @@ text \ type_synonym ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) unsigned_block = " slot \ \ \ Slot when the block was created \ 'hash option \ \ \ Previous block hash (if any), denoted by \st\ in the paper \ - 'sig transaction list \ \ \ Transaction data, denoted by \d\ in the paper \ ('vrf\<^sub>y, 'vrf\<^sub>\) block_proof \ \ \ Block proof, denoted by \B\<^sub>\\ in the paper \ - ('vrf\<^sub>y, 'vrf\<^sub>\) vrf\<^sub>o" \ \ Nonce proof, denoted by $\rho = (\rho_y, \rho_\pi)$ in the paper \ + ('vrf\<^sub>y, 'vrf\<^sub>\) vrf\<^sub>o \ \ \ Nonce proof, denoted by \\\ = (\\\<^sub>y\, \\\<^sub>\\) in the paper \ + 'sig transaction list" \ \ Transaction data, denoted by \d\ in the paper \ type_synonym ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block = "('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) unsigned_block \ 'sig" @@ -385,10 +386,10 @@ fun bl_slot :: "('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block \y, 'vrf\<^sub>\, 'sig) block \ 'sig transaction list" where - "bl_txs ((_, _, d, _, _), _) = d" + "bl_txs ((_, _, _, _, d), _) = d" fun bl_nonce_proof :: "('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block \ ('vrf\<^sub>y, 'vrf\<^sub>\) vrf\<^sub>o" where - "bl_nonce_proof ((_, _, _, _, \), _) = \" + "bl_nonce_proof ((_, _, _, \, _), _) = \" text \ We can apply a block to a stake distribution by applying all transactions in the block to the @@ -674,17 +675,17 @@ where drop (length hlcp) \)" text \ - Also, we can compute the list of the longest chains in a given list of chains \\\: + Also, we can compute the set of the longest chains in a given set of chains \\\: \ -abbreviation (input) "is_longest_chain \ \ \ \\' \ set \. length \ \ length \'" +abbreviation (input) "is_longest_chain \ \ \ \\'. \' |\| \ \ length \ \ length \'" definition longest_chains :: " - ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain list \ - ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain list" + ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain fset \ + ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain fset" where - "longest_chains \ = [\ \ \. is_longest_chain \ \]" + "longest_chains \ = ffilter (\\. is_longest_chain \ \) \" text \ Now we can define the function that implements the `longest chain' rule, namely the @@ -694,15 +695,15 @@ text \ fun max_valid :: " ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain \ - ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain list \ + ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain fset \ ('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain" where "max_valid \ \ = ( let - \' = [\' \ \ # \. forks_at_most k \ \']; \ \the \k\ parameter\ + \' = ffilter (forks_at_most k \) ({|\|} |\| \); \ \the \k\ parameter\ \'' = longest_chains \' in - if \ \ set \'' then \ else hd \'')" + if \ |\| \'' then \ else SOME \'. \' |\| \'')" end @@ -781,7 +782,7 @@ record ('skey, 'vkey, 'hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig, 'nonce) stake ss_G :: "('vkey, 'nonce) genesis" \ \ genesis block \ ss_\ :: "('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain" \ \ current chain \ ss_\ :: 'nonce \ \ current epoch nonce \\\<^sub>j\ \ - ss_\ :: "('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain list" \ \ chains received during the current slot \ + ss_\ :: "('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain fset" \ \ chains received during the current slot \ ss_txs :: "'sig transaction list" \ \ transactions received during the current slot \ ss_st :: "'hash option" \ \ current state, namely the hash of the head of \ss_\\ \ ss_sk\<^sub>v\<^sub>r\<^sub>f :: 'skey \ \ secret key for \\\$_{\mathsf{VRF}}$ \ @@ -878,7 +879,7 @@ private definition where [iff]: "verify_block B oh\<^sub>p\<^sub>r\<^sub>e\<^sub>v G \ T \ ( let - ((sl, st, d, B\<^sub>\, \), \) = B; + ((sl, st, B\<^sub>\, \, d), \) = B; (U\<^sub>s, y, \) = B\<^sub>\; (\\<^sub>y, \\<^sub>\) = \ in @@ -887,7 +888,7 @@ where Some (v\<^sub>v\<^sub>r\<^sub>f, v\<^sub>k\<^sub>e\<^sub>s, v\<^sub>d\<^sub>s\<^sub>i\<^sub>g) = (fst G) U\<^sub>s \ \ \ \U\<^sub>s\'s verification keys \ verify_block_proof sl v\<^sub>v\<^sub>r\<^sub>f \ (y, \) T \ verify_block_nonce sl v\<^sub>v\<^sub>r\<^sub>f \ (\\<^sub>y, \\<^sub>\) \ - verify\<^sub>K\<^sub>E\<^sub>S v\<^sub>k\<^sub>e\<^sub>s (sl, st, d, B\<^sub>\, \) \) \ ( + verify\<^sub>K\<^sub>E\<^sub>S v\<^sub>k\<^sub>e\<^sub>s (sl, st, B\<^sub>\, \, d) \) \ ( case oh\<^sub>p\<^sub>r\<^sub>e\<^sub>v of None \ True | Some h\<^sub>p\<^sub>r\<^sub>e\<^sub>v \ the st = h\<^sub>p\<^sub>r\<^sub>e\<^sub>v))" \ \ if \oh\<^sub>p\<^sub>r\<^sub>e\<^sub>v\ is not \None\ then \st\ is not \None\ either \ @@ -905,7 +906,7 @@ where let (_, \\<^sub>0, _) = G; B = \ ! i; \ \ current block \ - ((sl, _, _, B\<^sub>\, _), _) = B; + ((sl, _, B\<^sub>\, _, _), _) = B; U\<^sub>s = fst B\<^sub>\; oh\<^sub>p\<^sub>r\<^sub>e\<^sub>v = if i = 0 then None else fst (snd (fst (\ ! (i - 1)))); \ \ previous block hash \ j = slot_epoch sl; @@ -935,7 +936,7 @@ where ss_G = G, ss_\ = [], ss_\ = \\<^sub>0, - ss_\ = [], + ss_\ = {||} , ss_txs = [], ss_st = None, ss_sk\<^sub>v\<^sub>r\<^sub>f = sk\<^sub>v\<^sub>r\<^sub>f, @@ -966,7 +967,7 @@ where d = ss_txs ss; B\<^sub>\ = (U, v); \ = evaluate\<^sub>V\<^sub>R\<^sub>F (ss_sk\<^sub>v\<^sub>r\<^sub>f ss) (\ \\ sl \\ NONCE); - uB = (sl, st, d, B\<^sub>\, \); + uB = (sl, st, B\<^sub>\, \, d); \ = sign\<^sub>K\<^sub>E\<^sub>S (ss_sk\<^sub>k\<^sub>e\<^sub>s ss) uB in (uB, \))" @@ -1027,7 +1028,7 @@ where if verify_chain \\<^sub>p (ss_G ss) (ss_\ ss) then - ss\ss_\ := \\<^sub>p # ss_\ ss\ + ss\ss_\ := {|\\<^sub>p|} |\| ss_\ ss\ else ss)" @@ -1064,7 +1065,7 @@ where ss\ ss_\ := \\<^sub>m\<^sub>a\<^sub>x, ss_st := Some (\\<^sub>B (last \\<^sub>m\<^sub>a\<^sub>x)), \ \ update `state' with hash of best chain's head \ - ss_\ := [] \ \ empty chains mempool \ + ss_\ := {||} \ \ empty chains mempool \ \)" text \ @@ -2166,12 +2167,12 @@ begin let ?tx\<^sub>4 = "((U\<^bsub>1\<^esub>, U\<^bsub>2\<^esub>, 2), t\\<^sub>4)::'sig transaction" let ?tx\<^sub>5 = "((U\<^bsub>1\<^esub>, U\<^bsub>2\<^esub>, 1), t\\<^sub>5)::'sig transaction" let ?tx\<^sub>6 = "((U\<^bsub>2\<^esub>, U\<^bsub>1\<^esub>, 2), t\\<^sub>6)::'sig transaction" - let ?B\<^sub>1 = "((1, oh\<^sub>1, [?tx\<^sub>1\<^sub>1, ?tx\<^sub>1\<^sub>2], B\<^sub>\\<^sub>1, \\<^sub>1), B\\<^sub>1)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" - let ?B\<^sub>2 = "((3, oh\<^sub>2, [?tx\<^sub>2], B\<^sub>\\<^sub>2, \\<^sub>2), B\\<^sub>2)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" - let ?B\<^sub>3 = "((4, oh\<^sub>3, [?tx\<^sub>3], B\<^sub>\\<^sub>3, \\<^sub>3), B\\<^sub>3)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" - let ?B\<^sub>4 = "((6, oh\<^sub>4, [?tx\<^sub>4], B\<^sub>\\<^sub>4, \\<^sub>4), B\\<^sub>4)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" - let ?B\<^sub>5 = "((7, oh\<^sub>5, [?tx\<^sub>5], B\<^sub>\\<^sub>5, \\<^sub>5), B\\<^sub>5)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" - let ?B\<^sub>6 = "((8, oh\<^sub>6, [?tx\<^sub>6], B\<^sub>\\<^sub>6, \\<^sub>6), B\\<^sub>6)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" + let ?B\<^sub>1 = "((1, oh\<^sub>1, B\<^sub>\\<^sub>1, \\<^sub>1, [?tx\<^sub>1\<^sub>1, ?tx\<^sub>1\<^sub>2]), B\\<^sub>1)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" + let ?B\<^sub>2 = "((3, oh\<^sub>2, B\<^sub>\\<^sub>2, \\<^sub>2, [?tx\<^sub>2]), B\\<^sub>2)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" + let ?B\<^sub>3 = "((4, oh\<^sub>3, B\<^sub>\\<^sub>3, \\<^sub>3, [?tx\<^sub>3]), B\\<^sub>3)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" + let ?B\<^sub>4 = "((6, oh\<^sub>4, B\<^sub>\\<^sub>4, \\<^sub>4, [?tx\<^sub>4]), B\\<^sub>4)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" + let ?B\<^sub>5 = "((7, oh\<^sub>5, B\<^sub>\\<^sub>5, \\<^sub>5, [?tx\<^sub>5]), B\\<^sub>5)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" + let ?B\<^sub>6 = "((8, oh\<^sub>6, B\<^sub>\\<^sub>6, \\<^sub>6, [?tx\<^sub>6]), B\\<^sub>6)::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) block" let ?\ = "[?B\<^sub>1, ?B\<^sub>2, ?B\<^sub>3, ?B\<^sub>4, ?B\<^sub>5, ?B\<^sub>6]::('hash, 'vrf\<^sub>y, 'vrf\<^sub>\, 'sig) chain" have "\\<^bsub>1\<^esub>(?\\<^sub>0, ?\) = ?\\<^sub>0" @@ -2321,33 +2322,27 @@ qed lemma longest_chains_shorter_element: assumes "\ is_longest_chain \ \" - shows "longest_chains (\ # \) = longest_chains \" + shows "longest_chains ({|\|} |\| \) = longest_chains \" proof - - from assms have "[\' \ \. is_longest_chain \' (\ # \)] = [\' \ \. is_longest_chain \' \]" - by (metis (no_types, lifting) dual_order.trans linorder_le_cases list.set_intros(2) set_ConsD) + from assms have " + ffilter (\\'. is_longest_chain \' ({|\|} |\| \)) \ = ffilter (\\'. is_longest_chain \' \) \" + by fastforce with assms show ?thesis - unfolding longest_chains_def by auto + unfolding longest_chains_def by fastforce qed lemma longest_chains_empty: - shows "longest_chains [] = []" - unfolding longest_chains_def by simp + shows "longest_chains {||} = {||}" + unfolding longest_chains_def by fastforce lemma longest_chains_elem_is_longest: assumes "is_longest_chain \ \" - shows "\ \ set (longest_chains (\ # \))" -proof - - have "\ \ set (\ # \)" - by simp - moreover from assms have "is_longest_chain \ (\ # \)" - by simp - ultimately show ?thesis - unfolding longest_chains_def by simp -qed + shows "\ |\| (longest_chains ({|\|} |\| \))" + using assms unfolding longest_chains_def by simp lemma longest_chains_singleton: - shows "longest_chains [\] = [\]" - unfolding longest_chains_def by simp + shows "longest_chains {|\|} = {|\|}" + unfolding longest_chains_def by fastforce lemma trimmed_hashed_lcp_is_first_suffix: assumes "\ disjoint_chains \ \'" @@ -2456,44 +2451,30 @@ lemma forks_at_mostI [intro]: using first_suffix_eq_chains and forks_at_most_impl by simp lemma max_valid_no_candidates: - shows "max_valid \ [] = \" + shows "max_valid \ {||} = \" proof - - have "longest_chains [\] = [\]" + have "longest_chains {|\|} = {|\|}" by (rule longest_chains_singleton) moreover have "forks_at_most k \ \" by (intro forks_at_mostI) ultimately show ?thesis - by auto + using longest_chains_def by fastforce qed lemma max_valid_local_bias: assumes "is_longest_chain \ \" shows "max_valid \ \ = \" proof - - from assms have "\ \ set (longest_chains (\ # [\' \ \. forks_at_most k \ \']))" - using longest_chains_elem_is_longest by auto + from assms have "\ |\| longest_chains ({|\|} |\| ffilter (forks_at_most k \) \)" + using longest_chains_elem_is_longest by simp moreover have "forks_at_most k \ \" by (intro forks_at_mostI) - ultimately have "\ \ set (longest_chains [\' \ \ # \. forks_at_most k \ \'])" - by auto + ultimately have "\ |\| longest_chains (ffilter (forks_at_most k \) ({|\|} |\| \))" + using longest_chains_def by fastforce then show ?thesis by (simp del: forks_at_most_def) qed -lemma max_valid_preserves_order: - assumes "\ is_longest_chain \ [\' \ \. forks_at_most k \ \']" - and "\\<^sub>m\<^sub>a\<^sub>x = longest_chains [\' \ \. forks_at_most k \ \']" - and "\\<^sub>m\<^sub>a\<^sub>x \ []" - shows "max_valid \ \ = hd \\<^sub>m\<^sub>a\<^sub>x" -proof - - from assms have "\\<^sub>m\<^sub>a\<^sub>x = longest_chains [\' \ \ # \. forks_at_most k \ \']" - using longest_chains_shorter_element by simp - moreover from assms have "\ \ set \\<^sub>m\<^sub>a\<^sub>x" - unfolding longest_chains_def by simp - ultimately show ?thesis - by (simp del: forks_at_most_def) -qed - end text \ @@ -2515,7 +2496,7 @@ abbreviation test_\\<^sub>B :: "test_block \ test_block" where "t definition "test_forks_at_most = chain_selection.forks_at_most test_\\<^sub>B" definition "test_first_suffix = chain_selection.first_suffix test_\\<^sub>B" definition "test_max_valid = chain_selection.max_valid test_k test_\\<^sub>B" -definition test_longest_chains :: "test_chain list \ test_chain list" where +definition test_longest_chains :: "test_chain fset \ test_chain fset" where "test_longest_chains = chain_selection.longest_chains" interpretation test_chain_selection: chain_selection test_k test_R test_f test_ha test_\\<^sub>B @@ -2532,11 +2513,33 @@ interpretation test_chain_selection: chain_selection test_k test_R test_f test_h lemmas [code] = test_chain_selection.longest_chains_def abbreviation make_test_block :: "slot \ test_block" ("B\<^bsub>_\<^esub>") where - "B\<^bsub>sl\<^esub> \ ((sl, None, [], (U\<^bsub>0\<^esub>, ((), ())), ((), ())), ())" + "B\<^bsub>sl\<^esub> \ ((sl, None, (U\<^bsub>0\<^esub>, ((), ())), ((), ()), []), ())" abbreviation make_test_chain :: "slot list \ test_chain" ("\_\") where "\ss\ \ map make_test_block ss" +lemma Longest_common_prefix_pair [simp]: + shows "Longest_common_prefix {xs, ys} = longest_common_prefix xs ys" +proof (induction xs ys rule: longest_common_prefix.induct) + case (1 x xs y ys) + then show ?case + proof (cases "x = y") + case True + have "Longest_common_prefix {x # xs, x # ys} = x # Longest_common_prefix {xs, ys}" + using Longest_common_prefix_image_Cons [OF insert_not_empty [where A = "{ys}"]] by simp + also have "\ = x # longest_common_prefix xs ys" + by (simp only: "1.IH" [OF True]) + finally show ?thesis + by (simp add: True) + next + case False + then have "Longest_common_prefix {x # xs, y # ys} = []" + using Longest_common_prefix_eq_Nil by (metis insertCI) + with False show ?thesis + by simp + qed +qed (simp_all add: Longest_common_prefix_Nil) + notepad begin @@ -2586,37 +2589,101 @@ begin \ \ \No candidate chains were broadcast during the current slot, so the local chain is not updated:\ - value "test_chain_selection.max_valid \[0, 1]\ [] = \[0, 1]\" + have "test_chain_selection.max_valid \[0, 1]\ {||} = \[0, 1]\" + by (fact test_chain_selection.max_valid_no_candidates) \ \The only candidate chain is longer than the local chain and is a valid fork, so it is adopted:\ - value "test_chain_selection.max_valid \[0, 1]\ [\[0, 1, 2]\] = \[0, 1, 2]\" + have "test_chain_selection.max_valid \[0, 1]\ {|\[0, 1, 2]\|} = \[0, 1, 2]\" + (is \test_chain_selection.max_valid ?\ {|?\'|} = _\) + proof - + have "ffilter (test_chain_selection.forks_at_most test_k ?\) ({|?\|} |\| {|?\'|}) = {|?\, ?\'|}" + by force + moreover have "test_chain_selection.longest_chains {|?\, ?\'|} = {|?\'|}" + using test_longest_chains_def and test_chain_selection.longest_chains_shorter_element + and test_chain_selection.longest_chains_singleton + by auto + ultimately show ?thesis + by simp + qed \ \ The only candidate chain is shorter than the local chain and a is valid fork, so it's not adopted: \ - value "test_chain_selection.max_valid \[0, 1, 2]\ [\[0, 1]\] = \[0, 1, 2]\" + have "test_chain_selection.max_valid \[0, 1, 2]\ {|\[0, 1]\|} = \[0, 1, 2]\" + by (intro test_chain_selection.max_valid_local_bias) simp \ \ The only candidate chain is equal length as the local chain and is a valid fork, so it's not adopted (bias towards the local chain): \ - value "test_chain_selection.max_valid \[0, 1]\ [\[3, 4]\] = \[0, 1]\" + have "test_chain_selection.max_valid \[0, 1]\ {|\[3, 4]\|} = \[0, 1]\" + by (intro test_chain_selection.max_valid_local_bias) simp \ \ The only candidate chain is longer than the local chain but is an invalid fork, so it's not adopted: \ - value "test_chain_selection.max_valid \[0, 1, 2]\ [\[3, 4, 5, 6]\] = \[0, 1, 2]\" + have "test_chain_selection.max_valid \[0, 1, 2]\ {|\[3, 4, 5, 6]\|} = \[0, 1, 2]\" + (is \test_chain_selection.max_valid ?\ {|?\'|} = _\) + proof - + have "test_chain_selection.forks_at_most test_k ?\ ?\" + by (intro test_chain_selection.forks_at_mostI) + moreover have "\ test_chain_selection.forks_at_most test_k ?\ ?\'" + by simp + ultimately have "ffilter (test_chain_selection.forks_at_most test_k ?\) ({|?\|} |\| {|?\'|}) = {|?\|}" + by auto + moreover have "test_chain_selection.longest_chains {|?\|} = {|?\|}" + by (fact test_chain_selection.longest_chains_singleton) + ultimately show ?thesis + by simp + qed \ \The longest candidate is adopted:\ - value "test_chain_selection.max_valid \[0, 1]\ [\[3, 4, 5, 6]\, \[0, 1, 2]\] = \[3, 4, 5, 6]\" + have "test_chain_selection.max_valid \[0, 1]\ {|\[3, 4, 5, 6]\, \[0, 1, 2]\|} = \[3, 4, 5, 6]\" + (is \test_chain_selection.max_valid ?\ {|?\', ?\''|} = _\) + proof - + have "ffilter (test_chain_selection.forks_at_most test_k ?\) ({|?\|} |\| {|?\', ?\''|}) = {|?\, ?\', ?\''|}" + by auto + moreover have "test_chain_selection.longest_chains {|?\, ?\', ?\''|} = {|?\'|}" + unfolding test_chain_selection.longest_chains_def by fastforce + ultimately show ?thesis + by simp + qed - \ \The first longest candidate is adopted:\ - value "test_chain_selection.max_valid \[0, 1]\ [\[0, 1, 2]\, \[0, 1, 3]\] = \[0, 1, 2]\" + \ \Either of the candidates is adopted:\ + have "test_chain_selection.max_valid \[0, 1]\ {|\[0, 1, 2]\, \[0, 1, 3]\|} |\| {|\[0, 1, 2]\, \[0, 1, 3]\|}" + (is \test_chain_selection.max_valid ?\ {|?\', ?\''|} |\| _\) + proof - + have "ffilter (test_chain_selection.forks_at_most test_k ?\) ({|?\|} |\| {|?\', ?\''|}) = {|?\, ?\', ?\''|}" + by auto + moreover have "test_chain_selection.longest_chains {|?\, ?\', ?\''|} = {|?\', ?\''|}" + unfolding test_chain_selection.longest_chains_def by fastforce + moreover have "\\'. \' |\| {|?\', ?\''|}" + by blast + then have "(SOME \'. \' |\| {|?\', ?\''|}) |\| {|?\', ?\''|}" + by (iprover intro: someI2_ex) + ultimately show ?thesis + by simp + qed \ \Invalid forks are filtered out:\ - value "test_chain_selection.max_valid \[0, 1, 2]\ [\[3, 4, 5, 6]\, \[0, 1, 2, 3]\] = \[0, 1, 2, 3]\" + have "test_chain_selection.max_valid \[0, 1, 2]\ {|\[3, 4, 5, 6]\, \[0, 1, 2, 3]\|} = \[0, 1, 2, 3]\" + (is \test_chain_selection.max_valid ?\ {|?\', ?\''|} = _\) + proof - + have "test_chain_selection.forks_at_most test_k ?\ ?\" + by (intro test_chain_selection.forks_at_mostI) + moreover + have "\ test_chain_selection.forks_at_most test_k ?\ ?\'" + and "test_chain_selection.forks_at_most test_k ?\ ?\''" + by simp_all + ultimately have "ffilter (test_chain_selection.forks_at_most test_k ?\) ({|?\|} |\| {|?\', ?\''|}) = {|?\, ?\''|}" + by auto + moreover have "test_chain_selection.longest_chains {|?\, ?\''|} = {|?\''|}" + unfolding test_chain_selection.longest_chains_def by fastforce + ultimately show ?thesis + by simp + qed code_thms test_chain_selection.forks_at_most test_chain_selection.max_valid