dal.ml
(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com> *)
(* *)
(* Permission is hereby granted, free of charge, to any person obtaining a *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
(* and/or sell copies of the Software, and to permit persons to whom the *)
(* Software is furnished to do so, subject to the following conditions: *)
(* *)
(* The above copyright notice and this permission notice shall be included *)
(* in all copies or substantial portions of the Software. *)
(* *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
(* DEALINGS IN THE SOFTWARE. *)
(* *)
(*****************************************************************************)
(* Testing
-------
Component: DAL
Invocation: See docstring of the individual tests for instructions on how to execute them.
Subject: Test getting informaton about the DAL distribution.
*)
module Dal = Dal_common
module Helpers = struct
include Dal.Helpers
(* We override store slot so that it uses a DAL node in this file. *)
let store_slot dal_node ?with_proof slot =
store_slot (Either.Left dal_node) ?with_proof slot
end
module Dal_RPC = struct
include Dal.RPC
(* We override call_xx RPCs in Dal.RPC to use a DAL node in this file. *)
include Dal.RPC.Local
end
let dal_parameters () =
Test.register
~__FILE__
~title:"Check the validity of DAL parameters"
~tags:[Tag.tezos2; "dal"; "parameters"]
@@ fun () ->
let open Dal.Cryptobox in
let number_of_shards = Cli.get_int "number_of_shards" in
let slot_size = Cli.get_int "slot_size" in
let redundancy_factor = Cli.get_int "redundancy_factor" in
let page_size = Cli.get_int "page_size" in
let parameters =
{number_of_shards; redundancy_factor; page_size; slot_size}
in
match Internal_for_tests.ensure_validity_without_srs parameters with
| Ok _ ->
Internal_for_tests.parameters_initialisation ()
|> Internal_for_tests.load_parameters ;
let _ = make parameters in
unit
| Error (`Fail s) ->
Test.fail "The set of parameters is invalid. Reason:@.%s@." s
(** Start a layer 1 node on the given network, with the given data-dir and
rpc-port if any. *)
let start_layer_1_node ~network ?data_dir ?net_addr ?rpc_addr ?metrics_addr
?net_port ?rpc_port ?metrics_port () =
let arguments =
[Node.Network network; Synchronisation_threshold 1; Expected_pow 26]
in
let node =
Node.create
?data_dir
?rpc_host:rpc_addr
?rpc_port
?net_port
?net_addr
?metrics_addr
?metrics_port
arguments
in
let* () = Node.config_reset node arguments in
let* () = Node.run node arguments in
let* () = Node.wait_for_ready node in
return node
(** Start a DAL node with the given information and wait until it's ready. *)
let start_dal_node ~peers ?data_dir ?net_addr ?net_port ?rpc_addr ?rpc_port
?metrics_addr ?metrics_port ?public_ip_addr ?producer_profiles
?attester_profiles ?bootstrap_profile node =
let listen_addr =
match (net_addr, net_port) with
| None, None -> None
| Some addr, None -> Some (sf "%s:%d" addr @@ Port.fresh ())
| None, Some port -> Some (sf "%s:%d" Constant.default_host port)
| Some addr, Some port -> Some (sf "%s:%d" addr port)
in
let public_addr =
Option.map
(fun ip -> Option.fold net_port ~none:ip ~some:(sf "%s:%d" ip))
public_ip_addr
in
let metrics_addr =
match (metrics_addr, metrics_port) with
| None, None -> None
| Some addr, None -> Some (sf "%s:%d" addr @@ Port.fresh ())
| None, Some port -> Some (sf "%s:%d" Constant.default_host port)
| Some addr, Some port -> Some (sf "%s:%d" addr port)
in
let dal_node =
Dal_node.create
?data_dir
?rpc_port
?rpc_host:rpc_addr
?listen_addr
?public_addr
?metrics_addr
~node
()
in
let* () =
Dal_node.init_config
~expected_pow:26.
~peers
?producer_profiles
?attester_profiles
?bootstrap_profile
dal_node
in
let* () = Dal_node.run ~wait_ready:true dal_node in
return dal_node
(** This function determines the teztnet network date depending on the current
time. *)
let teztnet_network_target =
let dailynet () = Ptime_clock.now () |> Ptime.to_date in
let weeklynet () =
let t = Ptime_clock.now () in
let days_since_wednesday = (Ptime.weekday_num t + 4) mod 7 in
let span = Ptime.Span.of_int_s @@ (days_since_wednesday * 3600 * 24) in
match Ptime.sub_span t span with
| Some t -> t |> Ptime.to_date
| _ -> assert false (* Unreachable*)
in
fun ~network ~teztnet_network_day ->
Option.value
teztnet_network_day
~default:
(let year, month, day =
match network with
| "dailynet" -> dailynet ()
| "weeklynet" -> weeklynet ()
| s -> Test.fail "Unknown network %s@." s
in
Printf.sprintf "%04d-%02d-%02d" year month day)
(** This function allows to run a [main_scenario] function on a teztnet network
after initializing an L1 and DAL node and bootstrapping them. *)
let scenario_on_teztnet =
let ( //> ) =
let ( // ) = Filename.concat in
fun a_opt b ->
Option.map
(fun a ->
let dir = a // b in
ignore @@ Sys.command (sf "mkdir -p %s" dir) ;
dir)
a_opt
in
fun ~network
?(dal_bootstrap_peers = [])
?producer_profiles
?attester_profiles
~main_scenario
() ->
(* Parse CLI arguments *)
(* The working-dir option is not mandatory. It allows providing a fix
directly for nodes and wallet data/base dir. An advantage of this is the
ability to stop & restart the infra without being obliged to resync
dailynet or weeklynet from genesis. In fact, Tezt chooses a different
(random) directory for binaries (usually in /tmp) if they are not
explicitly specified. *)
let working_dir = Cli.get_string_opt "working-dir" in
let public_ip_addr = Cli.get_string_opt "public-ip-addr" in
let teztnet_network_day = Cli.get_string_opt "teztnet-network-day" in
(* L1 ports *)
let net_port = Cli.get_int_opt "net-port" in
let rpc_port = Cli.get_int_opt "rpc-port" in
let metrics_port = Cli.get_int_opt "metrics-port" in
(* DAL ports *)
let dal_net_port = Cli.get_int_opt "dal-net-port" in
let dal_rpc_port = Cli.get_int_opt "dal-rpc-port" in
let dal_metrics_port = Cli.get_int_opt "dal-metrics-port" in
(* L1 addresses *)
let net_addr = Cli.get_string_opt "net-addr" in
let rpc_addr = Cli.get_string_opt "rpc-addr" in
let metrics_addr = Cli.get_string_opt "metrics-addr" in
(* DAL addresses *)
let dal_net_addr = Cli.get_string_opt "dal-net-addr" in
let dal_rpc_addr = Cli.get_string_opt "dal-rpc-addr" in
let dal_metrics_addr = Cli.get_string_opt "dal-metrics-addr" in
(* Determine the right network day *)
let teztnet_network_day =
teztnet_network_target ~network ~teztnet_network_day
in
(* prepare directories *)
let working_dir = working_dir //> network //> teztnet_network_day in
let data_l1 = working_dir //> "layer1-data" in
let dal_producer = working_dir //> "dal-data" in
let wallet = working_dir //> "wallet" in
(* Start L1 node and wait until it's bootstrapped *)
let* l1_node =
let network =
sf "https://teztnets.com/%s-%s" network teztnet_network_day
in
start_layer_1_node
~network
?data_dir:data_l1
?net_addr
?rpc_addr
?metrics_addr
?net_port
?rpc_port
?metrics_port
()
in
let client =
Client.create
?base_dir:wallet
~endpoint:(Node l1_node)
~media_type:Json
()
in
let* () = Client.bootstrapped client in
(* Start DAL node *)
let peer = sf "dal.%s-%s.teztnets.com:11732" network teztnet_network_day in
let* dal_node =
start_dal_node
~peers:(peer :: dal_bootstrap_peers)
?data_dir:dal_producer
?producer_profiles
?attester_profiles
?public_ip_addr
?net_addr:dal_net_addr
?net_port:dal_net_port
?rpc_addr:dal_rpc_addr
?rpc_port:dal_rpc_port
?metrics_addr:dal_metrics_addr
?metrics_port:dal_metrics_port
l1_node
in
(* Prepare airdropper account. Secret key of
tz1PEhbjTyVvjQ2Zz8g4bYU2XPTbhvG8JMFh, a funded key on periodic testnets. *)
let airdropper_sk =
"edsk3AWajGUgzzGi3UrQiNWeRZR1YMRYVxfe642AFSKBTFXaoJp5hu"
in
let airdropper_alias = "airdropper" in
let* () =
Client.import_secret_key
client
(Account.Unencrypted airdropper_sk)
~alias:airdropper_alias
~force:true
in
main_scenario ~airdropper_alias client dal_node l1_node
(** This function allows to injects DAL slots in the given network. *)
let slots_injector_scenario ?publisher_sk ~airdropper_alias client dal_node
l1_node ~slot_index =
let open Runnable.Syntax in
let alias = "publisher" in
let* () =
match publisher_sk with
| None ->
let* _alias = Client.gen_keys ~alias client ~force:true in
unit
| Some account_sk ->
Client.import_secret_key
client
(Account.Unencrypted account_sk)
~alias
~force:true
in
(* Airdrop publisher if needed *)
let* balance_publisher = Client.get_balance_for ~account:alias client in
let* () =
if Tez.to_mutez balance_publisher > 5_000_000 then unit
else
Client.transfer
~amount:(Tez.of_mutez_int 50_000_000)
~giver:airdropper_alias
~receiver:alias
~burn_cap:(Tez.of_mutez_int 70_000)
~wait:"1"
client
in
(* Fetch slots size from protocol parameters *)
let* proto_parameters =
Client.RPC.call client @@ RPC.get_chain_block_context_constants ()
in
let dal_parameters =
Dal.Parameters.from_protocol_parameters proto_parameters
in
let slot_size = dal_parameters.cryptobox.slot_size in
(* Endless loop that injects slots *)
let rec loop level =
let slot =
sf "slot=%d/payload=%d" slot_index level |> Helpers.make_slot ~slot_size
in
let* commitment, proof =
Helpers.store_slot dal_node ~with_proof:true slot
in
let* level = Node.wait_for_level l1_node (level + 1) in
let*! () =
Client.publish_dal_commitment
~src:alias
~slot_index
~commitment
~proof
client
in
loop level
in
let* level = Client.level client in
loop level
let _stake_or_unstake_half_balance client ~baker_alias =
let* baker = Client.show_address ~alias:baker_alias client in
let* full_balance =
Client.RPC.call client
@@ RPC.get_chain_block_context_delegate_full_balance
baker.Account.public_key_hash
in
let* available_balance = Client.get_balance_for ~account:baker_alias client in
(* Stake half of the baker's balance *)
let frozen_balance = Tez.(full_balance - available_balance) in
let entrypoint, amount =
if Tez.to_mutez available_balance > Tez.to_mutez frozen_balance then
("stake", Tez.((available_balance - frozen_balance) /! 2L))
else ("unstake", Tez.((frozen_balance - available_balance) /! 2L))
in
if Tez.to_mutez amount = 0 then unit
else
Client.transfer
~amount
~giver:baker_alias
~receiver:baker_alias
~burn_cap:(Tez.of_mutez_int 140_000)
~entrypoint
~wait:"1"
client
(** This function allows to start a baker and attests slots DAL slots in the
given network. *)
let baker_scenario ?baker_sk ~airdropper_alias client dal_node l1_node =
(* We'll not airdrop baker account to avoid emptying airdropper. The user
should either provide a baker secret key with enough tez, or we use
airdropper as a baker directly. *)
let* baker_alias =
match baker_sk with
| None -> return airdropper_alias
| Some account_sk ->
let alias = "baker" in
let* () =
Client.import_secret_key
client
(Account.Unencrypted account_sk)
~alias
~force:true
in
return alias
in
(* No need to check if baker_alias is already delegate. Re-registering an
already registered delegate doesn't fail. *)
let* _s = Client.register_delegate ~delegate:baker_alias client in
(* TODO: manual staking has been disabled in Oxford-2 (after being enabled in
rejected Oxford-1) in favor of automatic staking. So, this command
currently fails. But, it might be reactivated in protocol P.
let* () = _stake_or_unstake_half_balance client ~baker_alias in
*)
let baker = Baker.create ~protocol:Protocol.Alpha ~dal_node l1_node client in
let* () = Baker.run baker in
Lwt_unix.sleep Float.max_float
(** A slots injector test parameterized by a network. Typically, to run a slot
injector on dailynet for slot index 10, one could run:
dune exec tezt/manual_tests/main.exe -- --file dal.ml \
--title "Join dailynet and inject slots" --verbose \
-a public-ip-addr="<pub-addr>" \
-a slot-index=0 \
-a publisher-sk="edsk4Vi86eAcBVXYLnBawg2p9FaDEKeDxXPHo26UfhsJteVY7P7guq" \
-a working-dir=./dal-e2e \
-a rpc-port=30000 \
-a dal-rpc-port=30001
Replace dailynet with weeklynet to rather join weeklynet.
*)
let slots_injector_test ~network =
Test.register
~__FILE__
~title:(sf "Join %s and inject slots" network)
~tags:[Tag.tezos2; "dal"; "slot"; "producer"; network]
@@ fun () ->
let slot_index = Cli.get_int "slot-index" in
let publisher_sk = Cli.get_string_opt "publisher-sk" in
let dal_bootstrap_peers =
Cli.get_string_opt "dal-bootstrap-peers"
|> Option.map (String.split_on_char ',')
in
scenario_on_teztnet
?dal_bootstrap_peers
~network
~main_scenario:(slots_injector_scenario ~slot_index ?publisher_sk)
~producer_profiles:[slot_index]
()
(** A baker test parameterized by a network. Typically, to run a baker that
operates on dailynet, one could run:
dune exec tezt/manual_tests/main.exe -- --file dal.ml \
--title "Join dailynet and bake" --verbose \
-a public-ip-addr="<pub-addr>" \
-a working-dir=./dal-e2e \
-a rpc-port=10000 \
-a baker-sk="edpkuSZ6gD6reoGEuJyPiPk67gp94V7xXtP1EZ83H46fNW9YQBUUdg" \
-a dal-rpc-port=10001
Replace dailynet with weeklynet to rather join weeklynet.
*)
let baker_test ~network =
Test.register
~__FILE__
~title:(sf "Join %s and bake" network)
~tags:[Tag.tezos2; "dal"; "baker"; network]
~uses:[Protocol.baker Alpha]
@@ fun () ->
let baker_sk = Cli.get_string_opt "baker-sk" in
let dal_bootstrap_peers =
Cli.get_string_opt "dal-bootstrap-peers"
|> Option.map (String.split_on_char ',')
in
scenario_on_teztnet
?dal_bootstrap_peers
~network
~main_scenario:(baker_scenario ?baker_sk)
()
let register () =
dal_parameters () ;
List.iter
(fun network ->
slots_injector_test ~network ;
baker_test ~network)
["dailynet"; "weeklynet"] ;
()