https://gitlab.com/tezos/tezos
Tip revision: 6969924ea43db1063a465896fa5b2635db95b6b1 authored by pecornilleau on 22 January 2024, 11:43:13 UTC
Etherlink: add MD for E2E scenarios
Etherlink: add MD for E2E scenarios
Tip revision: 6969924
test_scenario.ml
(* Testing
-------
Component: Protocol, delegate
Invocation: dune exec src/proto_alpha/lib_delegate/test/main.exe \
-- --file test_scenario.ml
Subject: Test different scenario for delegate
*)
open Mockup_simulator
let bootstrap1 = Signature.Public_key.hash bootstrap1
let bootstrap2 = Signature.Public_key.hash bootstrap2
let bootstrap3 = Signature.Public_key.hash bootstrap3
let bootstrap4 = Signature.Public_key.hash bootstrap4
let bootstrap5 = Signature.Public_key.hash bootstrap5
let some_seed s = Some (Protocol.State_hash.of_b58check_exn s)
(*
Test that the chain reaches the 5th level.
*)
let test_level_5 () =
let open Lwt_result_syntax in
let level_to_reach = 5l in
let module Hooks : Hooks = struct
include Default_hooks
let stop_on_event = function
| Baking_state.New_head_proposal {block; _} ->
(* Stop the node as soon as we receive a proposal with a level
higher than [level_to_reach]. *)
block.shell.level > level_to_reach
| _ -> false
let check_chain_on_success ~chain =
(* Make sure that all decided blocks have been decided at round 0. *)
let round_is_zero block =
let level = block.rpc_context.block_header.level in
let* round = get_block_round block in
if Int32.equal round 0l then return_unit
else failwith "block at level %ld was selected at round %ld" level round
in
List.iter_es round_is_zero chain
end in
(* Here we start two bakers, one with 3 delegates (bootstrap1, bootstrap2,
bootstrap3) and the other with 2 delegates (bootstrap4, bootstrap5).
The simulation continues till both nodes stop, see [stop_on_event]
above. *)
let config =
{
default_config with
timeout = Int32.to_int level_to_reach * 3 * 2;
round0 = 2L;
round1 = 3L;
}
in
run ~config [(3, (module Hooks)); (2, (module Hooks))]
let test_preattest_on_valid () =
let open Lwt_result_syntax in
let level_to_reach = 2l in
let round_to_reach = 1l in
let module Hooks : Hooks = struct
include Default_hooks
let on_new_head ~block_hash ~block_header =
(* Stop notifying heads on the level to reach, only notify that
it has been validated *)
if block_header.Block_header.shell.level < level_to_reach then
Lwt.return_some (block_hash, block_header)
else Lwt.return_none
let seen_candidate = ref None
let pqc_noticed = ref false
let qc_noticed = ref false
let stop_on_event = function
| Baking_state.Prequorum_reached (candidate, _) ->
(* Register the PQC notice. *)
(match !seen_candidate with
| Some seen_candidate
when Block_hash.(candidate.hash = seen_candidate) ->
pqc_noticed := true
| _ -> ()) ;
false
| Baking_state.Quorum_reached (candidate, _) ->
(* Because attestations are sent regardless of whether
a head has been applied, we can expect a quorum to
be received regardless of a head not being produced. *)
(match !seen_candidate with
| Some seen_candidate
when Block_hash.(candidate.hash = seen_candidate) ->
qc_noticed := true
| _ -> ()) ;
false
| New_head_proposal {block; _} ->
(* Ensure that we never notice a new head at the level where
we are not supposed to. *)
if block.shell.level = level_to_reach then
Stdlib.failwith "Unexpected new head event"
else false
| New_valid_proposal {block; _} ->
(* Register the seen valid proposal candidate. *)
if
block.shell.level = level_to_reach
&& Protocol.Alpha_context.Round.to_int32 block.round = 0l
then seen_candidate := Some block.hash ;
(* Stop the node when we reach level 2 / round 2. *)
block.shell.level = level_to_reach
&& Protocol.Alpha_context.Round.to_int32 block.round >= round_to_reach
| _ -> false
let check_chain_on_success ~chain:_ =
assert (!seen_candidate <> None) ;
assert !pqc_noticed ;
assert !qc_noticed ;
return_unit
end in
let config = {default_config with timeout = 10} in
run ~config [(1, (module Hooks))]
let test_reset_delayed_pqc () =
let open Lwt_syntax in
let module Hooks : Hooks = struct
include Default_hooks
let should_wait = ref true
let trigger = ref false
let on_new_operation x =
let* () =
if !should_wait then Lwt_unix.sleep 0.5 else Lwt_unix.sleep 0.2
in
if !trigger then (
trigger := false ;
Lwt.return_none)
else Lwt.return_some x
let on_new_head ~block_hash ~(block_header : Block_header.t) =
let block_round =
match
Protocol.Alpha_context.Fitness.round_from_raw
block_header.shell.fitness
with
| Error _ -> assert false
| Ok x -> x
in
if
block_header.Block_header.shell.level = 1l
&& Protocol.Alpha_context.Round.(block_round = zero)
then (
let* () = Lwt_unix.sleep 1. in
should_wait := false ;
trigger := true ;
Lwt.return_some (block_hash, block_header))
else Lwt.return_some (block_hash, block_header)
let stop_on_event = function
| Baking_state.New_valid_proposal {block; _} ->
let is_high_round =
let open Protocol.Alpha_context.Round in
match of_int 5 with
| Ok high_round -> block.round = high_round
| _ -> assert false
in
(block.shell.level = 1l && is_high_round) || block.shell.level > 1l
| _ -> false
let check_chain_on_success ~chain =
let head = Stdlib.List.hd chain in
if head.rpc_context.block_header.level = 1l then failwith "baker is stuck"
else return_ok_unit
end in
let config = {default_config with round0 = 2L; round1 = 3L; timeout = 50} in
run ~config [(1, (module Hooks))]
(*
Scenario T1
1. Node A proposes at the round 0.
2. Both node A and node B preattest.
3. Node A stops.
4. Node B attests in the round 0 and locks. No decision is taken at the
round 0 because A did not attest.
5. We check that in round 1 (the next slot for B), B proposes the same
value as A proposed in the round 0, not a new proposal.
*)
let test_scenario_t1 () =
let open Lwt_result_syntax in
let original_proposal = ref None in
let a_preattested = ref false in
let b_preattested = ref false in
let b_attested = ref false in
let b_reproposed = ref false in
(* Here we use custom hooks to make each node/baker behave according to
its role in the scenario. *)
let module Node_a_hooks : Hooks = struct
include Default_hooks
let check_mempool_after_processing ~mempool =
mempool_has_op_ref
~mempool
~predicate:
(op_is_both
(op_is_signed_by ~public_key:Mockup_simulator.bootstrap1)
(op_is_preattestation ~level:1l ~round:0l))
~var:a_preattested
let stop_on_event _ = !a_preattested
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let check_block_before_processing ~level ~round ~block_hash ~block_header
~(protocol_data : Protocol.Alpha_context.Block_header.protocol_data) =
let* () =
match (!b_attested, level, round) with
| false, 1l, 0l ->
(* If any of the checks fails the whole scenario will fail. *)
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap1
in
save_proposal_payload ~protocol_data ~var:original_proposal
| true, 1l, 1l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap2
in
let* () =
verify_payload_hash
~protocol_data
~original_proposal
~message:"a new block proposed instead of reproposal"
in
b_reproposed := true ;
return_unit
| _ -> failwith "unexpected level = %ld / round = %ld" level round
in
return_unit
let check_mempool_after_processing ~mempool =
let* () =
mempool_has_op_ref
~mempool
~predicate:
(op_is_both
(op_is_signed_by ~public_key:Mockup_simulator.bootstrap2)
(op_is_preattestation ~level:1l ~round:0l))
~var:b_preattested
in
mempool_has_op_ref
~mempool
~predicate:
(op_is_both
(op_is_signed_by ~public_key:Mockup_simulator.bootstrap2)
(op_is_preattestation ~level:1l ~round:0l))
~var:b_attested
let stop_on_event _ = !b_reproposed
end in
let config =
{
default_config with
initial_seed = None;
delegate_selection = [(1l, [(0l, bootstrap1); (1l, bootstrap2)])];
}
in
run ~config [(1, (module Node_a_hooks)); (1, (module Node_b_hooks))]
(*
Scenario T2
1. Node A should propose at the round 0, but it is dead.
2. Node B waits til it has its proposal slot at round 1 and proposes then.
*)
let test_scenario_t2 () =
let open Lwt_result_syntax in
let b_proposed = ref false in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let stop_on_event _ = true (* Node A stops immediately. *)
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let check_block_before_processing ~level ~round ~block_hash ~block_header
~protocol_data:_ =
(* Here we test that the only block that B observes is its own
proposal for level 1 at round 1. *)
match (level, round) with
| 1l, 1l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap2
in
b_proposed := true ;
return_unit
| _ -> failwith "unexpected level = %ld / round = %ld" level round
let stop_on_event _ =
(* Stop as soon as B has proposed. This ends the test. *)
!b_proposed
end in
let config =
{
default_config with
initial_seed = None;
delegate_selection = [(1l, [(0l, bootstrap1); (1l, bootstrap2)])];
}
in
run ~config [(1, (module Node_a_hooks)); (1, (module Node_b_hooks))]
(*
Scenario T3
1. There are four nodes: A, B, C, and D.
2. C is the proposer at the round 0. It sends the proposal, which is
received by all bakers except for D.
3. Due to how the messages propagate, only B sees 3 preattestations. It
attests and locks. Other nodes all see fewer than 3 preattestations.
A -> A and B
B -> B
C -> C and B
4. D proposes at the round 1. Its message reaches 3 nodes, including B.
D -> D, B, C
5. B does not preattest because it is locked.
6. No decision is taken at the round 1.
7. B proposes at the round 2. There are no more problems with propagation of
messages, so a decision is reached.
*)
let test_scenario_t3 () =
let open Lwt_result_syntax in
let b_observed_pqc = ref false in
let original_proposal = ref None in
let we_are_done = ref false in
let stop_on_event0 _ = !we_are_done in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_operation ~op_hash ~op =
if !b_observed_pqc then return (op_hash, op, [Pass; Pass; Pass; Pass])
else
let* is_preattestation =
op_is_preattestation ~level:1l ~round:0l op_hash op
in
if is_preattestation then
return (op_hash, op, [Pass; Pass; Block; Block])
else failwith "unexpected operation from the node D"
let stop_on_event = stop_on_event0
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~(protocol_data : Protocol.Alpha_context.Block_header.protocol_data) =
match (level, round) with
| 1l, 2l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap2
in
we_are_done := true ;
let* () =
verify_payload_hash
~protocol_data
~original_proposal
~message:"a new block proposed instead of reproposal"
in
return (block_hash, block_header, operations, [Pass; Pass; Pass; Pass])
| _ ->
failwith
"unexpected injection on the node B, level = %ld / round = %ld"
level
round
let on_inject_operation ~op_hash ~op =
if !b_observed_pqc then return (op_hash, op, [Pass; Pass; Pass; Pass])
else
let* is_preattestation =
op_is_preattestation ~level:1l ~round:0l op_hash op
in
if is_preattestation then
return (op_hash, op, [Block; Pass; Block; Block])
else failwith "unexpected operation from the node B"
let check_mempool_after_processing ~mempool =
let predicate op_hash op =
op_is_preattestation ~level:1l ~round:0l op_hash op
in
let* n = mempool_count_ops ~mempool ~predicate in
if n > 3 then
failwith "B received too many preattestations, expected to see only 3"
else if n = 3 then (
b_observed_pqc := true ;
return_unit)
else return_unit
let stop_on_event = stop_on_event0
end in
let module Node_c_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~(protocol_data : Protocol.Alpha_context.Block_header.protocol_data) =
match (level, round) with
| 1l, 0l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap3
in
let* () =
save_proposal_payload ~protocol_data ~var:original_proposal
in
return
(block_hash, block_header, operations, [Pass; Pass; Pass; Block])
| _ ->
failwith
"unexpected injection on the node C, level = %ld / round = %ld"
level
round
let on_inject_operation ~op_hash ~op =
if !b_observed_pqc then return (op_hash, op, [Pass; Pass; Pass; Pass])
else
let* is_preattestation =
op_is_preattestation ~level:1l ~round:0l op_hash op
in
if is_preattestation then
return (op_hash, op, [Block; Pass; Pass; Block])
else failwith "unexpected operation from the node C"
let stop_on_event = stop_on_event0
end in
let module Node_d_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
match (level, round) with
| 1l, 1l ->
return
(block_hash, block_header, operations, [Block; Pass; Pass; Pass])
| _ ->
failwith
"unexpected injection on the node D, level = %ld / round = %ld"
level
round
let on_inject_operation ~op_hash ~op =
if !b_observed_pqc then return (op_hash, op, [Pass; Pass; Pass; Pass])
else return (op_hash, op, [Block; Block; Block; Block])
let stop_on_event = stop_on_event0
end in
let config =
{
default_config with
initial_seed =
some_seed "rngFtAUcm1EneHCCrxxSWAaxSukwEhSPvpTnFjVdKLEjgkapUy1pP";
delegate_selection =
[
( 1l,
[
(0l, bootstrap3);
(1l, bootstrap4);
(2l, bootstrap2);
(3l, bootstrap1);
] );
( 2l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
];
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Node_b_hooks));
(1, (module Node_c_hooks));
(1, (module Node_d_hooks));
]
(* Scenario T4
1. There are two nodes A B:
A can propose at level 1, round 0 and level 2, round 1
B can propose at level 1, round 1 and level 2, round 0
4. Node A proposes at level 1, round 0,
5. Both nodes preattest the proposal at level 1, round 0,
6. Both nodes attest the proposal at level 1, round 0,
7. Only A receives the block application at level 1, round 0,
8. B does not propose at level 2, round 0,
9. A proposes at level 2, round 1.
Predecessor hash is block at level 1, round 0.
*)
let test_scenario_t4 () =
let open Lwt_result_syntax in
let level_to_reach = 4l in
let a_proposal_level_1 = ref None in
let a_proposal_level_2_predecessor = ref None in
let b_attested_level_1 = ref false in
let b_proposed_level_2 = ref false in
let stop_on_event0 = function
| Baking_state.New_head_proposal {block; _} ->
block.shell.level >= level_to_reach
| _ -> false
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
let () =
match (level, round) with
| 1l, 0l -> a_proposal_level_1 := Some block_hash
| 2l, 1l ->
a_proposal_level_2_predecessor :=
Some block_header.Block_header.shell.predecessor
| _ -> ()
in
return (block_hash, block_header, operations, [Pass; Pass])
let stop_on_event = stop_on_event0
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let on_new_head ~block_hash ~block_header =
(* Stop notifying heads to node B for block proposed at level 1, round 0. *)
match !a_proposal_level_1 with
| Some a_proposal_level_1_block_hash
when Block_hash.(a_proposal_level_1_block_hash = block_hash) ->
Lwt.return_none
| _ -> Lwt.return_some (block_hash, block_header)
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
let () =
match (level, round) with
| 2l, 0l -> b_proposed_level_2 := true
| _ -> ()
in
return (block_hash, block_header, operations, [Pass; Pass])
let on_inject_operation ~op_hash ~op =
let* is_attestation = op_is_attestation ~level:1l ~round:0l op_hash op in
if is_attestation then b_attested_level_1 := true ;
return (op_hash, op, [Pass; Pass; Pass; Pass])
let check_chain_on_success ~chain:_ =
if not !b_attested_level_1 then
failwith "Node B did not attest proposal at level 1, round 0"
else if
not
@@ Option.equal
Block_hash.equal
!a_proposal_level_2_predecessor
!a_proposal_level_1
then
failwith
"Invalid predecessor block for A's proposal at level 2: proposal had \
predecessor %a, expected %a\n"
(Format.pp_print_option Block_hash.pp_short)
!a_proposal_level_1
(Format.pp_print_option Block_hash.pp_short)
!a_proposal_level_2_predecessor
else return_unit
let stop_on_event = stop_on_event0
end in
let config =
{
default_config with
initial_seed =
some_seed "rngG9pS9mbDWnz6YLUFrd8sbb9KMUzfAUMpSpnxNHY9BFnSB8L3zq";
delegate_selection =
[
(1l, [(0l, bootstrap1); (1l, bootstrap2)]);
(2l, [(0l, bootstrap2); (1l, bootstrap1)]);
];
}
in
run ~config [(1, (module Node_a_hooks)); (1, (module Node_b_hooks))]
(*
Scenario F1
1. Node C (bootstrap3) proposes at level 1 round 0, its proposal reaches all
nodes.
2. Propagation of preattestations happens in such a way that only Node A
(bootstrap1) observes PQC:
A -> A
B -> B and A
C -> C and A
D -> D and A
Node A locks.
3. At the level 1 round 1 node D (bootstrap4) proposes. Propagation of
messages is normal.
4. Node A (bootstrap1) should propose at level 2 round 0.
*)
let test_scenario_f1 () =
let open Lwt_result_syntax in
let c_proposed_l1_r0 = ref false in
let d_proposed_l1_r1 = ref false in
let a_proposed_l2_r0 = ref false in
let stop_on_event0 _ = !a_proposed_l2_r0 in
let pass =
(* This is to be sure that the proposal for round r arrives while
the baker is in round r, and not in round r-1. (See related
issue:
https://gitlab.com/tezos/tezos/-/issues/4143). Otherwise, the
test does not perform as expected. *)
Delay 0.5
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
match (!c_proposed_l1_r0, !d_proposed_l1_r1, level, round) with
| true, true, 2l, 0l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap1
in
let* () =
a_proposed_l2_r0 := true ;
return_unit
in
return (block_hash, block_header, operations, [pass; pass; pass; pass])
| _ ->
failwith
"unexpected injection on the node A, level = %ld / round = %ld"
level
round
let on_inject_operation ~op_hash ~op =
match (!c_proposed_l1_r0, !d_proposed_l1_r1) with
| true, false -> return (op_hash, op, [Pass; Block; Block; Block])
| _ -> return (op_hash, op, [Pass; Pass; Pass; Pass])
let stop_on_event = stop_on_event0
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let on_inject_operation ~op_hash ~op =
match (!c_proposed_l1_r0, !d_proposed_l1_r1) with
| true, false -> return (op_hash, op, [Pass; Pass; Block; Block])
| _ -> return (op_hash, op, [Pass; Pass; Pass; Pass])
let stop_on_event = stop_on_event0
end in
let module Node_c_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
match (!c_proposed_l1_r0, !d_proposed_l1_r1, level, round) with
| false, false, 1l, 0l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap3
in
let* () =
c_proposed_l1_r0 := true ;
return_unit
in
return (block_hash, block_header, operations, [pass; pass; pass; pass])
| _ ->
failwith
"unexpected injection on the node C, level = %ld / round = %ld"
level
round
let on_inject_operation ~op_hash ~op =
match (!c_proposed_l1_r0, !d_proposed_l1_r1) with
| true, false -> return (op_hash, op, [Pass; Block; Pass; Block])
| _ -> return (op_hash, op, [Pass; Pass; Pass; Pass])
let stop_on_event = stop_on_event0
end in
let module Node_d_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
match (!d_proposed_l1_r1, level, round) with
| false, 1l, 1l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap4
in
let* () =
d_proposed_l1_r1 := true ;
return_unit
in
return (block_hash, block_header, operations, [pass; pass; pass; pass])
| _ ->
failwith
"unexpected injection on the node D, level = %ld / round = %ld"
level
round
let on_inject_operation ~op_hash ~op =
match (!c_proposed_l1_r0, !d_proposed_l1_r1) with
| true, false -> return (op_hash, op, [Pass; Block; Block; Pass])
| _ -> return (op_hash, op, [Pass; Pass; Pass; Pass])
let stop_on_event = stop_on_event0
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGohKUZjXzv69sxvDqAYRd4XPDQSxDoEpP72znu2jduBuhcYiSE";
delegate_selection =
[
( 1l,
[
(0l, bootstrap3);
(1l, bootstrap4);
(2l, bootstrap1);
(3l, bootstrap2);
] );
( 2l,
[
(0l, bootstrap1);
(1l, bootstrap4);
(2l, bootstrap2);
(3l, bootstrap3);
] );
];
timeout = 30;
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Node_b_hooks));
(1, (module Node_c_hooks));
(1, (module Node_d_hooks));
]
(*
Scenario F2
1. There are four nodes: A, B, C, and D.
2. A proposes at 1.0 and observes EQC.
3. A has the slot at 2.0 but somehow it doesn't propose or its proposal is lost.
4. B, C, and D have the rounds 1, 2, and 3 respectively, but they also do not propose.
5. A should still propose at 2.4.
*)
let test_scenario_f2 () =
let open Lwt_result_syntax in
let proposal_2_4_observed = ref false in
let module Hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
let propagation_vector =
match (level, round) with
| 1l, 0l -> [Pass; Pass; Pass; Pass]
| 2l, 0l -> [Pass; Block; Block; Block]
| 2l, 4l ->
proposal_2_4_observed := true ;
[Pass; Pass; Pass; Pass]
| _ -> [Block; Block; Block; Block]
in
return (block_hash, block_header, operations, propagation_vector)
let stop_on_event _ = !proposal_2_4_observed
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGPSm87ZqWxJmZu7rewiLiyKY72ffCQQvxDuWmFBw59dWAL5VTB";
delegate_selection =
[
(1l, [(0l, bootstrap1)]);
( 2l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
(4l, bootstrap1);
] );
];
timeout = 60;
round0 = 2L;
round1 = 3L;
}
in
run
~config
[
(1, (module Hooks));
(1, (module Hooks));
(1, (module Hooks));
(1, (module Hooks));
]
(*
Scenario M1
1. Four nodes start, each with 1 delegate.
2. As soon as 2nd level is proposed all communication between nodes becomes
impossible.
3. The situation continues for 5 seconds.
4. After communication is resumed the bakers must continue making progress.
*)
let test_scenario_m1 () =
let open Lwt_result_syntax in
let observed_level2_timestamp = ref None in
let network_down_sec = 5. in
let module Hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
let propagation_vector =
match !observed_level2_timestamp with
| None ->
if Compare.Int32.(level >= 2l) then (
observed_level2_timestamp := Some (Unix.time ()) ;
[Pass; Pass; Pass; Pass])
else [Pass; Pass; Pass; Pass]
| Some level2_observed ->
if Unix.time () -. level2_observed < network_down_sec then
[Block; Block; Block; Block]
else [Pass; Pass; Pass; Pass]
in
return (block_hash, block_header, operations, propagation_vector)
let on_inject_operation ~op_hash ~op =
let propagation_vector =
match !observed_level2_timestamp with
| None -> [Pass; Pass; Pass; Pass]
| Some level2_observed ->
if Unix.time () -. level2_observed < network_down_sec then
[Block; Block; Block; Block]
else [Pass; Pass; Pass; Pass]
in
return (op_hash, op, propagation_vector)
let stop_on_event = function
| Baking_state.New_head_proposal {block; _} -> block.shell.level > 4l
| _ -> false
end in
let config = {default_config with timeout = 60} in
run
~config
[
(1, (module Hooks));
(1, (module Hooks));
(1, (module Hooks));
(1, (module Hooks));
]
(*
Scenario M2
1. Five nodes start (single delegate per node).
2. They decide level 1.
3. However, the node that has the slot for level 2 round 0 is not there
to participate.
4. We check that the chain continues advancing despite that.
*)
let test_scenario_m2 () =
let module Normal_node : Hooks = struct
include Default_hooks
let stop_on_event = function
| Baking_state.New_head_proposal {block; _} -> block.shell.level > 5l
| _ -> false
end in
let module Missing_node : Hooks = struct
include Default_hooks
let stop_on_event _ = true (* stop immediately *)
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGo77zNC59bYiQMk2M14aDZZu4KXG8BV1C8pi7afjJ7cXyqB3M1";
delegate_selection =
[
( 1l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
( 2l,
[
(0l, bootstrap5);
(1l, bootstrap1);
(2l, bootstrap2);
(3l, bootstrap3);
(4l, bootstrap4);
] );
];
round0 = 2L;
round1 = 3L;
timeout = 60;
}
in
run
~config
[
(1, (module Normal_node));
(1, (module Normal_node));
(1, (module Normal_node));
(1, (module Normal_node));
(1, (module Missing_node));
]
(*
Scenario M3
1. There are four nodes: A, B, C, and D.
2. A and B propose in turns. Messages from A reach every node, but messages
from other nodes only go to A.
3. The chain should not make progress. Since we have both bootstrap1 and
bootstrap2 in delegate selection they have equal voting power. Therefore
it is necessary to have 2 votes for prequorums (which is achieved when A
is proposing) and 2 votes for quorums (impossible because B has no way to
obtain PQC and thus cannot send attestations).
*)
let test_scenario_m3 () =
let open Lwt_result_syntax in
let stop_on_event0 = function
| Baking_state.New_head_proposal {block; _} ->
block.shell.level = 1l
&& Protocol.Alpha_context.Round.to_int32 block.round = 6l
| _ -> false
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let stop_on_event = stop_on_event0
end in
let module Other_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
return (block_hash, block_header, operations, [Pass; Block; Block; Block])
let on_inject_operation ~op_hash ~op =
return (op_hash, op, [Pass; Block; Block; Block])
let stop_on_event = stop_on_event0
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGaxNJcwEVJLgQXmnN8KN5skn6fhU4Awtu8zVDKViTd5gsfT51M";
delegate_selection =
[
( 1l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap1);
(3l, bootstrap2);
(4l, bootstrap1);
(5l, bootstrap2);
(6l, bootstrap1);
] );
];
round0 = 2L;
round1 = 3L;
timeout = 60;
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Other_hooks));
(1, (module Other_hooks));
(1, (module Other_hooks));
]
(*
Scenario M4
1. There are four bakers: A, B, C, and D.
2. A proposes at level 1 round 0. Its proposal reaches A, B, C, and D, but
with a delay of 0.5 seconds.
3. 3 votes are enough for consensus, because voting powers of all delegates
are equal. Preattestations propagate freely, however attestations from C
are blocked.
4. Check that at level 1 round 0 quorum is reached (from the point of view
of A). This means that D sends an attestation despite receiving
preattestations before the proposal.
*)
let test_scenario_m4 () =
let open Lwt_result_syntax in
let a_observed_qc = ref false in
let stop_on_event0 _ = !a_observed_qc in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
match (level, round) with
| 1l, 0l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap1
in
return
(block_hash, block_header, operations, [Pass; Pass; Pass; Delay 0.5])
| _ ->
failwith
"unexpected injection on the node A, level = %ld / round = %ld"
level
round
let check_mempool_after_processing ~mempool =
let predicate op_hash op =
op_is_attestation ~level:1l ~round:0l op_hash op
in
let* n = mempool_count_ops ~mempool ~predicate in
if n > 3 then
failwith "A received too many attestations, expected to see only 3"
else if n = 3 then (
a_observed_qc := true ;
return_unit)
else return_unit
let stop_on_event = stop_on_event0
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let stop_on_event = stop_on_event0
end in
let module Node_c_hooks : Hooks = struct
include Default_hooks
let on_inject_operation ~op_hash ~op =
let* is_attestation = op_is_attestation ~level:1l ~round:0l op_hash op in
return
( op_hash,
op,
if is_attestation then [Block; Block; Block; Block]
else [Pass; Pass; Pass; Pass] )
let stop_on_event = stop_on_event0
end in
let module Node_d_hooks : Hooks = struct
include Default_hooks
let stop_on_event = stop_on_event0
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGJmwLi7kPvGwV2LR3kjNQ6xamGPCZ9ooep9QcafbqRXZhYEciT";
delegate_selection =
[
( 1l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
];
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Node_b_hooks));
(1, (module Node_c_hooks));
(1, (module Node_d_hooks));
]
(*
Scenario M5
1. There are four bakers: A, B, C, and D.
2. A proposes at level 1 round 0. Its proposal reaches A, B, C, and D, but with
a delay of 1 second. There are no problems with propagation of
preattestations and attestations.
3. At the level 1 all four bakers have proposer slots, however we block possible
proposals from B and C at higher rounds.
4. Check that D proposes at the level 2 round 0, which means that it has
observed QC.
*)
let test_scenario_m5 () =
let open Lwt_result_syntax in
let stop_on_event0 = function
| Baking_state.New_head_proposal {block; _} -> block.shell.level >= 2l
| _ -> false
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
match (level, round) with
| 1l, 0l ->
let* () =
check_block_signature
~block_hash
~block_header
~public_key:Mockup_simulator.bootstrap1
in
return
(block_hash, block_header, operations, [Pass; Pass; Pass; Delay 1.0])
| _ ->
failwith
"unexpected injection on the node A, level = %ld / round = %ld"
level
round
let stop_on_event = stop_on_event0
end in
let module Other_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
return (block_hash, block_header, operations, [Block; Block; Block; Block])
let stop_on_event = stop_on_event0
end in
let module Node_d_hooks : Hooks = struct
include Default_hooks
let stop_on_event = stop_on_event0
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGJmwLi7kPvGwV2LR3kjNQ6xamGPCZ9ooep9QcafbqRXZhYEciT";
delegate_selection =
[
( 1l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
];
round0 = 3L;
round1 = 4L;
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Other_hooks));
(1, (module Other_hooks));
(1, (module Node_d_hooks));
]
(*
Scenario M6
1. There are four bakers: A, B, C, and D.
2. A proposes at level 1 round 0. Its proposal reaches all nodes, and they
observe PQC. Only A observes a QC.
3. At level 1 round 1 it is B's turn to propose. Since it has observed the
PQC, it reproposes A's proposal. A does not see it.
4. B observes PQC and QC for its proposal.
5. A proposes at level 2 round 0. No one sees the proposal.
6. B proposes at level 2 round 1. A sees B's proposal and switches its branch.
7. We wait 2 more levels before checking A's chain to verify that it has
adopted B's proposal.
*)
let test_scenario_m6 () =
let open Lwt_result_syntax in
let b_proposal_2_1 = ref None in
let stop_on_event0 = function
| Baking_state.New_head_proposal {block; _} -> block.shell.level > 4l
| _ -> false
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
let propagation_vector =
match (level, round) with
| 2l, 0l -> [Pass; Block; Block; Block]
| _ -> [Pass; Pass; Pass; Pass]
in
return (block_hash, block_header, operations, propagation_vector)
let on_inject_operation ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
return
( op_hash,
op,
if is_a10_attestation then [Pass; Block; Block; Block]
else [Pass; Pass; Pass; Pass] )
let stop_on_event = stop_on_event0
let check_chain_on_success ~chain =
match List.nth (List.rev chain) 2 with
| None -> failwith "Node A has empty chain"
| Some (block : block) ->
verify_payload_hash
~protocol_data:block.protocol_data
~original_proposal:b_proposal_2_1
~message:"A did not switch to B's proposal (level 2, round 1)"
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data =
let* propagation_vector =
match (level, round) with
| 1l, 1l -> return [Block; Delay 0.1; Delay 0.1; Delay 0.1]
| 2l, 1l ->
let* () =
save_proposal_payload ~protocol_data ~var:b_proposal_2_1
in
return [Pass; Pass; Pass; Pass]
| _ -> return [Pass; Pass; Pass; Pass]
in
return (block_hash, block_header, operations, propagation_vector)
let on_inject_operation ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
return
( op_hash,
op,
if is_a10_attestation then [Pass; Block; Block; Block]
else [Pass; Pass; Pass; Pass] )
let stop_on_event = stop_on_event0
end in
let module Other_node : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
return (block_hash, block_header, operations, [Pass; Pass; Pass; Pass])
let on_inject_operation ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
return
( op_hash,
op,
if is_a10_attestation then [Pass; Block; Block; Block]
else [Pass; Pass; Pass; Pass] )
let stop_on_event = stop_on_event0
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGnwG2gApiRzo1kdbCgQheqtZroUsAjsJzyw2RBbtg3gtTeMQ9F";
delegate_selection =
[
( 1l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
( 2l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
];
timeout = 60;
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Node_b_hooks));
(1, (module Other_node));
(1, (module Other_node));
]
(*
Scenario M7
The same as M6, but:
5. B proposes at level 2 round 0 (A does not see the proposal).
6. A proposes at 2.1. B switches to A's branch when it receives 2.1.
7. We wait 2 more levels before checking everyone's chain to verify that
A's proposal has been selected.
*)
let test_scenario_m7 () =
let open Lwt_result_syntax in
let a_proposal_2_1 = ref None in
let c_received_2_1 = ref false in
let d_received_2_1 = ref false in
let stop_on_event0 = function
| Baking_state.New_head_proposal {block; _} -> block.shell.level > 4l
| _ -> false
in
let check_chain_on_success0 node_label ~chain =
match List.nth (List.rev chain) 2 with
| None -> failwith "Node %s has empty chain" node_label
| Some (block : block) ->
verify_payload_hash
~protocol_data:block.protocol_data
~original_proposal:a_proposal_2_1
~message:
(Format.sprintf
"%s did not switch to A's proposal (level 2, round 1)"
node_label)
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data =
let* () =
match (level, round) with
| 2l, 1l -> save_proposal_payload ~protocol_data ~var:a_proposal_2_1
| _ -> return_unit
in
return (block_hash, block_header, operations, [Pass; Pass; Pass; Pass])
let on_inject_operation ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
return
( op_hash,
op,
if is_a10_attestation then [Pass; Block; Block; Block]
else [Pass; Pass; Pass; Pass] )
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "A"
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
let* propagation_vector =
match (level, round) with
| 1l, 1l -> return [Block; Delay 0.1; Delay 0.1; Delay 0.1]
| 2l, 0l -> return [Block; Pass; Pass; Pass]
| _ -> return [Pass; Pass; Pass; Pass]
in
return (block_hash, block_header, operations, propagation_vector)
let on_inject_operation ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
let* level2_preattestation = op_is_preattestation ~level:2l op_hash op in
let* level2_attestation = op_is_attestation ~level:2l op_hash op in
let propagation_vector =
match
(is_a10_attestation, level2_preattestation, level2_attestation)
with
| true, _, _ -> [Pass; Block; Block; Block]
| _, true, _ | _, _, true -> [Block; Block; Block; Block]
| _, _, _ -> [Pass; Pass; Pass; Pass]
in
return (op_hash, op, propagation_vector)
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "B"
end in
let module Node_c_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
let propagation_vector =
if !c_received_2_1 then [Pass; Pass; Pass; Pass]
else [Block; Block; Block; Block]
in
return (block_hash, block_header, operations, propagation_vector)
let check_chain_after_processing ~level ~round ~chain:_ =
match (level, round) with
| 2l, 1l ->
c_received_2_1 := true ;
return_unit
| _ -> return_unit
let on_inject_operation ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
let* level2_preattestation = op_is_preattestation ~level:2l op_hash op in
let* level2_attestation = op_is_attestation ~level:2l op_hash op in
let propagation_vector =
match
( is_a10_attestation,
!c_received_2_1,
level2_preattestation,
level2_attestation )
with
| true, _, _, _ -> [Pass; Block; Block; Block]
| _, false, true, _ | _, false, _, true -> [Block; Block; Block; Block]
| _, _, _, _ -> [Pass; Pass; Pass; Pass]
in
return (op_hash, op, propagation_vector)
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "C"
end in
let module Node_d_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
let propagation_vector =
if !d_received_2_1 then [Pass; Pass; Pass; Pass]
else [Block; Block; Block; Block]
in
return (block_hash, block_header, operations, propagation_vector)
let check_chain_after_processing ~level ~round ~chain:_ =
match (level, round) with
| 2l, 1l ->
d_received_2_1 := true ;
return_unit
| _ -> return_unit
let on_inject_operation ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
let* level2_preattestation = op_is_preattestation ~level:2l op_hash op in
let* level2_attestation = op_is_attestation ~level:2l op_hash op in
let propagation_vector =
match
( is_a10_attestation,
!d_received_2_1,
level2_preattestation,
level2_attestation )
with
| true, _, _, _ -> [Pass; Block; Block; Block]
| _, false, true, _ | _, false, _, true -> [Block; Block; Block; Block]
| _, _, _, _ -> [Pass; Pass; Pass; Pass]
in
return (op_hash, op, propagation_vector)
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "D"
end in
let config =
{
default_config with
initial_seed =
some_seed "rngGJ7ReXwsjWuzpeqCgHAjudFwJtxdYz44Genz1FnyJ8R226hoKh";
delegate_selection =
[
( 1l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
( 2l,
[
(0l, bootstrap2);
(1l, bootstrap1);
(2l, bootstrap3);
(3l, bootstrap4);
] );
];
timeout = 60;
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Node_b_hooks));
(1, (module Node_c_hooks));
(1, (module Node_d_hooks));
]
(*
Scenario M8
5. B proposes at 2.0 and observes PQC but not QC.
6. C re-proposes at 2.1 and similarly observes PQC but not QC.
7. A proposes at 2.2. B, C, and D do not switch to A's branch; moreover A
switches to their branch when it receives the next proposal (2.3). This
happens because B, C, and D have PQC despite A having a higher round (2 > 1).
8. We wait 2 more levels before checking everyone's chain to verify that
B's proposal has been selected.
*)
let test_scenario_m8 () =
let open Lwt_result_syntax in
let b_proposal_2_0 = ref None in
let stop_on_event0 = function
| Baking_state.New_head_proposal {block; _} -> block.shell.level > 4l
| _ -> false
in
let on_inject_operation0 ~op_hash ~op =
let* is_a10_attestation =
op_is_attestation ~level:1l ~round:0l op_hash op
in
let* is_b20_attestation =
op_is_attestation ~level:2l ~round:0l op_hash op
in
let* is_c21_attestation =
op_is_attestation ~level:2l ~round:1l op_hash op
in
let propagation_vector =
if is_a10_attestation then [Pass; Block; Block; Block]
else if is_b20_attestation || is_c21_attestation then
[Block; Block; Block; Block]
else [Pass; Pass; Pass; Pass]
in
return (op_hash, op, propagation_vector)
in
let check_chain_on_success0 node_label ~chain =
match List.nth (List.rev chain) 2 with
| None -> failwith "Node %s has empty chain" node_label
| Some (block : block) ->
verify_payload_hash
~protocol_data:block.protocol_data
~original_proposal:b_proposal_2_0
~message:
(Format.sprintf
"%s did not switch to B's proposal (level 2, round 0)"
node_label)
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
return (block_hash, block_header, operations, [Pass; Pass; Pass; Pass])
let on_inject_operation = on_inject_operation0
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "A"
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data =
let* propagation_vector =
match (level, round) with
| 1l, 1l -> return [Block; Delay 0.1; Delay 0.1; Delay 0.1]
| 2l, 0l ->
let* () =
save_proposal_payload ~protocol_data ~var:b_proposal_2_0
in
return [Block; Pass; Pass; Pass]
| _ -> return [Pass; Pass; Pass; Pass]
in
return (block_hash, block_header, operations, propagation_vector)
let on_inject_operation = on_inject_operation0
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "B"
end in
let module Node_c_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level ~round ~block_hash ~block_header ~operations
~protocol_data:_ =
let propagation_vector =
match (level, round) with
| 2l, 1l -> [Block; Pass; Pass; Pass]
| _ -> [Pass; Pass; Pass; Pass]
in
return (block_hash, block_header, operations, propagation_vector)
let on_inject_operation = on_inject_operation0
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "C"
end in
let module Node_d_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
return (block_hash, block_header, operations, [Pass; Pass; Pass; Pass])
let on_inject_operation = on_inject_operation0
let stop_on_event = stop_on_event0
let check_chain_on_success = check_chain_on_success0 "D"
end in
let config =
{
default_config with
initial_seed =
some_seed "rngFy2zFmgg25SXrE6aawqQVhD1kdw9eCCRxc843RLQjz5MZ6MGER";
delegate_selection =
[
( 1l,
[
(0l, bootstrap1);
(1l, bootstrap2);
(2l, bootstrap3);
(3l, bootstrap4);
] );
( 2l,
[
(0l, bootstrap2);
(1l, bootstrap3);
(2l, bootstrap1);
(3l, bootstrap4);
] );
];
timeout = 60;
}
in
run
~config
[
(1, (module Node_a_hooks));
(1, (module Node_b_hooks));
(1, (module Node_c_hooks));
(1, (module Node_d_hooks));
]
(*
Scenario M9
Two nodes: A, B
1. L1 - A proposes and reaches QC.
2. L1 - When QC is reached, observe that B emits time to forge event. Shortly after, observe
that B emits time to bake event.
3. L2 - A observes the block from B and ends.
*)
let test_scenario_m9 () =
let stop_level = Int32.of_int 2 in
let node_b_qc = ref false in
let node_b_ttf = ref false in
let node_b_level = ref Int32.zero in
let bh : Block_hash.t option ref = ref None in
let stop_on_event0 = function
| Baking_state.New_head_proposal {block; _} ->
block.shell.level >= stop_level
| _ -> false
in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let raise_error : string option ref = ref None
let block_round block_header =
let open Block_header in
match
Protocol.Alpha_context.Fitness.round_from_raw block_header.shell.fitness
with
| Error _ -> assert false
| Ok x -> x
let on_new_head ~block_hash ~block_header =
let open Block_header in
let block_round = block_round block_header in
let level = block_header.shell.level in
let is_round0 =
Protocol.Alpha_context.Round.equal
block_round
Protocol.Alpha_context.Round.zero
in
match level with
| 2l ->
raise_error :=
if
is_round0
&& not (Block_hash.equal block_hash (Stdlib.Option.get !bh))
then Some "Block hash was not equal"
else if not is_round0 then
Some "Level 2 expected to have a block at round 0"
else None ;
Lwt.return @@ Some (block_hash, block_header)
| _ -> Lwt.return @@ Some (block_hash, block_header)
let stop_on_event = stop_on_event0
let check_chain_on_success ~chain:_ =
match !raise_error with
| Some err -> Stdlib.failwith err
| _ -> return_unit
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let on_inject_block ~level:_ ~round:_ ~block_hash ~block_header ~operations
~protocol_data:_ =
bh := Some block_hash ;
return (block_hash, block_header, operations, [Pass; Pass])
let stop_on_event = function
| Baking_state.Quorum_reached _ when !node_b_level = 1l ->
node_b_qc := true ;
false
| Baking_state.Timeout timeout when !node_b_level = 1l -> (
match timeout with
| Time_to_forge_block ->
if !node_b_qc then (
node_b_ttf := true ;
false)
else
Stdlib.failwith
"time to forge emitted without observing qc event"
| Time_to_bake_next_level _ ->
if !node_b_qc && !node_b_ttf then false
else
Stdlib.failwith
"time to bake emitted without observing qc or time to forge \
event"
| End_of_round _ ->
Stdlib.failwith "End of round timeout not expected")
| Baking_state.New_valid_proposal {block; _} ->
node_b_level := block.shell.level ;
false
| event -> stop_on_event0 event
end in
let config =
{
default_config with
delegate_selection = [(1l, [(0l, bootstrap1)]); (2l, [(0l, bootstrap2)])];
round0 = 3L;
round1 = 4L;
}
in
run ~config [(1, (module Node_a_hooks)); (1, (module Node_b_hooks))]
(*
Scenario M10
Two nodes : A, B
1. Node A is the proposer at level 1, round 0, but is dead
2. Node B proposes at level 1, round 1, therefore the
Time_to_forge_block was not called.
*)
let test_scenario_m10 () =
let stop_level = 1l in
let stop_round = Protocol.Alpha_context.Round.(succ zero) in
let node_b_ttf = ref false in
let module Node_a_hooks : Hooks = struct
include Default_hooks
let stop_on_event _ = true (* Node A stops immediately. *)
end in
let module Node_b_hooks : Hooks = struct
include Default_hooks
let stop_on_event = function
| Baking_state.Timeout Time_to_forge_block ->
node_b_ttf := true ;
false
(* When we get to level = 1, round = 1, the time to forge timeout should
not have been called *)
| Baking_state.New_head_proposal {block; _} ->
let block_round = block.round in
if block.shell.level >= stop_level && block_round = stop_round then (
assert (not !node_b_ttf) ;
true)
else false
| _ -> false
end in
let config =
{
default_config with
round0 = 3L;
round1 = 4L;
delegate_selection = [(1l, [(0l, bootstrap1); (1l, bootstrap2)])];
}
in
run ~config [(1, (module Node_a_hooks)); (1, (module Node_b_hooks))]
let () =
let open Lwt_result_syntax in
(* Activate a sink to record baker's events *)
let t = lazy (Tezt_sink.activate ()) in
let proto_name =
String.lowercase_ascii Protocol.name
|> String.map (function '-' -> '_' | x -> x)
in
let register_test (title, test) =
Test.register
~__FILE__
~title
~tags:[proto_name; "baker"; "mockup"; Tag.time_sensitive]
@@ fun () ->
let*! () = Lazy.force t in
let*! r = test () in
match r with
| Ok () -> unit
| Error errs -> Test.fail ~__LOC__ "%a" pp_print_trace errs
in
List.iter
register_test
[
(Protocol.name ^ ": reaches level 5", test_level_5);
( Protocol.name ^ ": cannot progress without new head",
test_preattest_on_valid );
(Protocol.name ^ ": reset delayed pqc", test_reset_delayed_pqc);
(Protocol.name ^ ": scenario t1", test_scenario_t1);
(Protocol.name ^ ": scenario t2", test_scenario_t2);
(Protocol.name ^ ": scenario t3", test_scenario_t3);
(Protocol.name ^ ": scenario t4", test_scenario_t4);
(Protocol.name ^ ": scenario f1", test_scenario_f1);
(Protocol.name ^ ": scenario f2", test_scenario_f2);
(Protocol.name ^ ": scenario m1", test_scenario_m1);
(Protocol.name ^ ": scenario m2", test_scenario_m2);
(Protocol.name ^ ": scenario m3", test_scenario_m3);
(Protocol.name ^ ": scenario m4", test_scenario_m4);
(Protocol.name ^ ": scenario m5", test_scenario_m5);
(Protocol.name ^ ": scenario m6", test_scenario_m6);
(Protocol.name ^ ": scenario m7", test_scenario_m7);
(Protocol.name ^ ": scenario m8", test_scenario_m8);
(Protocol.name ^ ": scenario m9", test_scenario_m9);
(Protocol.name ^ ": scenario m10", test_scenario_m10);
]