(*****************************************************************************)
(* *)
(* 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. *)
(* *)
(*****************************************************************************)
(** This module can be used to craft an operation without using client
dedicated commands.
{1 Overview}
This module aims to replace the module {!module:Operation_legacy}
to provide an interface which is more extensible. In other words,
supporting a new operation should be easier using this interface.
An unsigned operation is represented by the datatype
{!type:t}. {!type:t} is a wrapper around a JSON representation of
an operation. Some meta information needs to be provided to sign
this operation. {!type:t} is not signed a priori to ease writing
tests with bad signatures.
This module also provides two functions to ease the injection of
an operation: {!val:inject} which should be called when the
injection is expected to succeed, and {!val:inject_with_error} when
the injection is expected to fail.
Anyone is free to add support for new operations.
{2 Manager operations}
Manager operations represent most of the operations used by the
tests. Those operations contain several parameters (see
{!val:Manager.make}) and can be batched. Wrapper like
{!val:Manager.inject} and {!val:Manager.inject_with_error} are
provided to ease the writing of tests.
*)
(** The abstract representation of an unsigned operation. *)
type t
type operation := t
type consensus_kind = Attestation of {with_dal : bool} | Preattestation
(** The kind is necessary because it determines the watermark of an
operation which is necessary for signing an operation. This type
aims to be extended when other kinds of operations are added into
this module. *)
type kind =
| Consensus of {kind : consensus_kind; chain_id : string}
| Anonymous
| Voting
| Manager
(** [make ~branch ?signer ~kind json client] builds the representation
of an unsigned operation. *)
val make : branch:string -> ?signer:Account.key -> kind:kind -> JSON.u -> t
(** [json t] gives the json representation of an unsigned operation. *)
val json : t -> JSON.u
(** [hex ?(protocol=None) ?(signature=None) t client] computes the binary
representation of an operation as a hexadecimal string. If [protocol] is
given, the binary representation is computed using the encoding of operation
from the [protocol]. Otherwise, a call to the [forge_operations] RPC is done
to compute the binary representation. If [signature] is given, the
hexadecimal represents the signed version of the operation. [client] is used
to construct the binary representation of [t].
@param protocol controls whether the encoding of a protocol should be used
to compute the binary representation of the operation rather than calling
the [forge_operations] RPC to compute it.
@param signature controls whether a signature should be attached
to the operation. *)
val hex :
?protocol:Protocol.t ->
?signature:Tezos_crypto.Signature.t ->
t ->
Client.t ->
Hex.t Lwt.t
(** [sign t client] signs the raw representation of operation [t] by its signer
(see {!val:make}). [client] is used to construct the binary representation
of [t]. Note that if no signer have been given to [t] the function returns
[Tezos_crypto.Signature.zero]. *)
val sign :
?protocol:Protocol.t -> t -> Client.t -> Tezos_crypto.Signature.t Lwt.t
(** [hash t client] returns the hash of the operation *)
val hash : t -> Client.t -> [`OpHash of string] Lwt.t
(** Returns the size (in bytes) of the operation.
@param protocol Allows using the operation encoding rather than
using the [forge_operations] RPC to compute the hexadecimal
representation of the operation.
@param signature Allows to manually set the signature of the
operation. When omitted, the operation is correctly signed using {!sign}. *)
val byte_size :
?protocol:Protocol.t ->
?signature:Tezos_crypto.Signature.t ->
t ->
Client.t ->
int Lwt.t
(** [inject ?(request=`Inject) ?(force=false) ?(signature=None)
?(error=None) t] injects an operation into the node. The node is
extracted from the [Client]. If a node cannot be extracted, the
injection fails. If the injection succeeds, the hash of the
operation is returned.
@param request If [`Inject], we do not wait the [prevalidator] to
classify the operation. This can create some flakyness in the test
but is needed to test corner cases. If [`Notify], the function
waits for the prevalidator to classify the operation. However, the
nodes need to activate the debug events for the prevalidator.
@param force If [true], the function succeeds even though the
operation was classified with an error and was not propagated by
the prevalidator. If [false], the call fails if the prevalidator
classified the operation with an error.
@param protocol Allow using the operation encoding rather than using the
[forge_operations] RPC to compute the hexadecimal representation of the
operations.
@param signature Allows to give manually the signature of the
operation. The operation is signed when the signature is omitted.
@param error If the injection is expecting to fail, allows to
specify the expected error. *)
val inject :
?request:[`Inject | `Notify] ->
?force:bool ->
?protocol:Protocol.t ->
?signature:Tezos_crypto.Signature.t ->
?error:rex ->
t ->
Client.t ->
[`OpHash of string] Lwt.t
(** Same as [inject], but do not wait for the process to exit. *)
val spawn_inject :
?force:bool ->
?protocol:Protocol.t ->
?signature:Tezos_crypto.Signature.t ->
t ->
Client.t ->
JSON.t Runnable.process Lwt.t
(** Run [spawn_inject] then capture two groups on stderr with [rex]. *)
val inject_and_capture2_stderr :
rex:rex ->
?force:bool ->
?protocol:Protocol.t ->
?signature:Tezos_crypto.Signature.t ->
t ->
Client.t ->
(string * string) Lwt.t
(** [inject_operations ?protocol ?request ?force ?error ?use_tmp_file ops
client] is similar as [inject] for a list of operations. This function calls
the RPC {!val:RPC.post_private_injection_operations} which is faster than
calling the RPC used by {!val:inject} several times. Note that this function
should be used mainly when the time for injecting operations matters.
@param use_tmp_file see {!val:RPC.post_private_injection_operations} for
more information. *)
val inject_operations :
?protocol:Protocol.t ->
?request:[`Inject | `Notify] ->
?force:bool ->
?error:rex ->
?use_tmp_file:bool ->
t list ->
Client.t ->
[`OpHash of string] list Lwt.t
(** Craft a json representing the full operation, in a format that is
compatible with the [run_operation] RPC
({!RPC.post_chain_block_helpers_scripts_run_operation}).
This json contains many more fields than the one produced by the
{!json} function above.
The operation is signed with {!Tezos_crypto.Signature.zero},
because the [run_operation] RPC skips signature checks anyway.
@param chain_id Allows to manually provide the [chain_id]. If
omitted, the [chain_id] is retrieved via RPC using the provided
[client].
@param client The {!Client.t} argument is used to retrieve the
[chain_id] when it is not provided. *)
val make_run_operation_input : ?chain_id:string -> t -> Client.t -> JSON.u Lwt.t
(** Craft a json representing the full operation, in a format that is
compatible with the [preapply/operations] RPC
({!RPC.post_chain_block_helpers_preapply_operations}).
This json contains many more fields than the one produced by the
{!json} function above. *)
val make_preapply_operation_input :
protocol:Protocol.t -> signature:Tezos_crypto.Signature.t -> t -> JSON.u
module Consensus : sig
(** A representation of a consensus operation. *)
type t
(** [consensus ~kind ~use_legacy_name ~level ~round ~slot ~block_payload_hash]
crafts a consensus operation with the [kind] at [level] on the [round]
with the [slot] and [block_payload_hash]. If [use_legacy_name] is set, the
[kind] field in the crafted JSON will be "(pre)endorsement" instead of
"(pre)attestation". *)
val consensus :
use_legacy_name:bool ->
kind:consensus_kind ->
slot:int ->
level:int ->
round:int ->
block_payload_hash:string ->
t
(** [preattestation ?use_legacy_name ~level ~round ~slot ~block_payload_hash]
crafts a preattestation operation at [level] on the [round] with the
[slot] and [block_payload_hash]. If [use_legacy_name] is set, the [kind]
field in the crafted JSON will be "preendorsement" instead of
"preattestation". *)
val preattestation :
use_legacy_name:bool ->
slot:int ->
level:int ->
round:int ->
block_payload_hash:string ->
t
(** [attestation ?use_legacy_name ~level ~round ~slot ~block_payload_hash
?dal_attestation ()] crafts an attestation operation at the given [level]
on the given [round] with the given [slot] and [block_payload_hash] and
optionally the given [dal_attestation]. If [use_legacy_name] is set, the
[kind] field in the crafted JSON will be "endorsement" instead of
"attestation". *)
val attestation :
use_legacy_name:bool ->
slot:int ->
level:int ->
round:int ->
block_payload_hash:string ->
?dal_attestation:bool array ->
unit ->
t
(** [kind_to_string kind ~use_legacy_name] returns the name of the
[kind]. If [use_legacy_name] is set, the name corresponding to the [kind]
will be "(pre)endorsement" instead of "(pre)attestation". *)
val kind_to_string : consensus_kind -> use_legacy_name:bool -> string
(** [operation] constructs an operation from a consensus
operation. the [client] is used to fetch the branch and the
[chain_id]. *)
val operation :
?branch:string ->
?chain_id:string ->
signer:Account.key ->
t ->
Client.t ->
operation Lwt.t
(** A wrapper for {!val:inject} with consensus operations. The client
is used to get all the data that was not provided if it can be
recovered via RPCs. Mainly those are the [branch] and the
[chain_id]. *)
val inject :
?request:[`Inject | `Notify] ->
?force:bool ->
?branch:string ->
?chain_id:string ->
?error:rex ->
signer:Account.key ->
t ->
Client.t ->
[`OpHash of string] Lwt.t
(** Retrieves the attestation slots at [level] by calling the [GET
/chains/<chain>/blocks/<block>/helpers/validators] RPC. *)
val get_slots : level:int -> Client.t -> JSON.t Lwt.t
(** Returns the first slot of the provided delegate in the
[slots_json] that describes all attestation rights at some
level.
Causes the test to fail if the delegate is not found. *)
val first_slot : slots_json:JSON.t -> Account.key -> int
(** Calls the [GET /chains/<chain>/blocks/<block>/header] RPC and
extracts the head block's payload hash from the result. *)
val get_block_payload_hash : ?block:string -> Client.t -> string Lwt.t
end
module Anonymous : sig
(** A representation of an anonymous operation. *)
type t
type double_consensus_evidence_kind =
| Double_attestation_evidence
| Double_preattestation_evidence
(** [double_consensus_evidence ~kind ~use_legacy_name op1 op2] crafts a double
consensus evidence operation with the [kind], [op1] and [op2]. Both
operations should be of the same kind and the same as the one expected by
[kind]. If [use_legacy_name] is set, the [kind] field in the crafted JSON
will be "(pre)endorsement" instead of "(pre)attestation". *)
val double_consensus_evidence :
kind:double_consensus_evidence_kind ->
use_legacy_name:bool ->
operation * Tezos_crypto.Signature.t ->
operation * Tezos_crypto.Signature.t ->
t
(** [double_attestation_evidence ~use_legacy_name op1 op2] crafts a double
attestation evidence operation with op1 and op2. Both operations should be
attestations. If [use_legacy_name] is set, the [kind] field in the crafted
JSON will be "endorsement" instead of "attestation". *)
val double_attestation_evidence :
use_legacy_name:bool ->
operation * Tezos_crypto.Signature.t ->
operation * Tezos_crypto.Signature.t ->
t
(** [double_preattestation_evidence ~use_legacy_name op1 op2] crafts a double
attestation evidence operation with op1 and op2. Both operations should be
preattestations. If [use_legacy_name] is set, the [kind] field in the
crafted JSON will be "preendorsement" instead of "preattestation". *)
val double_preattestation_evidence :
use_legacy_name:bool ->
operation * Tezos_crypto.Signature.t ->
operation * Tezos_crypto.Signature.t ->
t
(** [kind_to_string kind ~use_legacy_name] return the name of the [kind]. If
[use_legacy_name] is set, the name corresponding to the [kind] will be
"double_(pre)endorsement_evidence" instead of
"double_(pre)attestation_evidence". *)
val kind_to_string :
double_consensus_evidence_kind -> use_legacy_name:bool -> string
(** [operation] constructs an operation from an anonymous operation. the
[client] is used to fetch the branch and the [chain_id]. *)
val operation : ?branch:string -> t -> Client.t -> operation Lwt.t
(** A wrapper for {!val:inject} with anonymous operations. The client is used
to get all the data that was not provided if it can be recovered via RPCs.
Mainly those are the [branch] and the [chain_id]. *)
val inject :
?request:[`Inject | `Notify] ->
?force:bool ->
?branch:string ->
?error:rex ->
t ->
Client.t ->
[`OpHash of string] Lwt.t
(** Crafts two (pre)attestations that only differ in their block
payload hash, then a well-formed double (pre)attestation
evidence operation on them.
The denounced (pre)attestations have the specified
[misbehaviour_level] and [misbehaviour_round], the first slot of
[culprit], and two constant and distinct block payload
hashes. They are signed by [culprit]. *)
val make_double_consensus_evidence_with_distinct_bph :
kind:double_consensus_evidence_kind ->
misbehaviour_level:int ->
misbehaviour_round:int ->
culprit:Account.key ->
Client.t ->
t Lwt.t
end
(** Voting operations (validation pass [1]): [proposals] and [ballot].
Only the [proposals] operation is currently supported. Feel free to
add support for [ballot] as needed. *)
module Voting : sig
(** A representation of a voting operation. *)
type t
(** [proposals source period protocol_hashes] crafts a [proposals]
operation, that is, an operation that submits candidate protocol
hashes for voting.
@param source The account that submits the proposals.
@param period An index that identifies the targeted voting
period.
@param protocol_hashes A list of candidate protocol hashes. *)
val proposals : Account.key -> int -> string list -> t
(** Contruct a voting operation from its representation.
@param branch Allows to manually provide the branch. If omitted,
the branch it retrieved via RPC using the given client.
@param client Used to retrieve the branch when it is not
provided.
@param signer Allows to manually set the signer of the operation,
e.g. to craft an operation with a wrong signature. If omitted,
the signer is the operation's source.
@raise Invalid_argument if neither the [branch] argument nor the
[client] one is provided. *)
val operation :
?branch:string ->
?client:Client.t ->
?signer:Account.key ->
t ->
operation Lwt.t
(** A wrapper for {!inject}ing a voting operation.
See {!inject} for a description of arguments [request], [force],
[signature], and [error].
See [Voting.operation] right above for a description of arguments
[branch] and [signer]. *)
val inject :
?request:[`Inject | `Notify] ->
?force:bool ->
?signature:Tezos_crypto.Signature.t ->
?error:rex ->
?branch:string ->
?signer:Account.key ->
t ->
Client.t ->
[`OpHash of string] Lwt.t
end
module Manager : sig
(** Payload of a manager operation. This excludes generic parameters
common to all manager operations. See {!type:t}. *)
type payload
(** Build a public key revelation.
The [Account.key] argument has no default value because it will
typically be a fresh account. *)
val reveal : Account.key -> payload
(** [transfer ?(dest=Constant.bootstrap2) ~amount:1_000_000 ()]
builds a transfer operation. Note that the amount is expressed in
mutez. *)
val transfer : ?dest:Account.key -> ?amount:int -> unit -> payload
(** [origination ?(init_balance=0) ~code ~init_storage ()]
builds an origination operation. *)
val origination :
?init_balance:int -> code:JSON.u -> init_storage:JSON.u -> unit -> payload
(** [call ~dest ~amount:0 ~entrypoint ~arg ()] builds a smart contract call
operation to the [entrypoint] with the provided Michelson argument
[arg]. Note that the amount is expressed in mutez. *)
val call :
?dest:string ->
?amount:int ->
?entrypoint:string ->
?arg:JSON.u ->
unit ->
payload
(** [dal_publish_commitment ~level ~index ~header] builds an
operation for the data-availability layer that publishes a
slot. *)
val dal_publish_commitment :
index:int ->
commitment:Tezos_crypto_dal.Cryptobox.commitment ->
proof:Tezos_crypto_dal.Cryptobox.commitment_proof ->
payload
(** [delegation ?(delegate=Constant.bootstrap2) ()] builds a
delegation operation. *)
val delegation : ?delegate:Account.key -> unit -> payload
(** The sc_rollup_proof structure is complex to be written by hand during tests
and should likely be generated by a PVM. We expose it as a JSON value. *)
type sc_rollup_proof = JSON.u
(** A section of a proof is make of a state hash, if any, and of a tick. *)
type sc_rollup_dissection_chunk = {state_hash : string option; tick : int}
(** A refutation step is either a dissection or a proof. *)
type sc_rollup_game_refutation_step =
| Proof of sc_rollup_proof
| Dissection of sc_rollup_dissection_chunk list
(** An sc_rollup_refutation is the information submitted by players during a
(refutation) game. *)
type sc_rollup_refutation =
| Start of {
player_commitment_hash : string;
opponent_commitment_hash : string;
}
| Move of {
choice_tick : int;
refutation_step : sc_rollup_game_refutation_step;
}
(** [sc_rollup_refute ?refutation ~rollup ~oppenent] builds an Sc rollup
refutation manager operation payload. The refutation is [None] in case
of initialization or some game refutation step (either a dissection or
a proof) otherwise. [sc_rollup] is the Sc rollup's address and [opponent] the
public key hash of the staker who published the commitment we are about
to refute. *)
val sc_rollup_refute :
refutation:sc_rollup_refutation ->
sc_rollup:string ->
opponent:string ->
unit ->
payload
(** A representation of a manager operation. This includes generic
parameters common to all manager operations. See {!val:make}. *)
type t
(** [make] builds a manager operation from a payload and generic
parameters common to all the manager operations. The default
values of the generic parameters are set depending on the
payload. Those default values ensure that the operation can be
executed correctly. Have a look at the definition of this
function to know the default value for each operation payload. *)
val make :
?source:Account.key ->
?counter:int ->
?fee:int ->
?gas_limit:int ->
?storage_limit:int ->
payload ->
t
(** [make_batch] builds a batch of manager operations from a list of
payloads and an initial counter. This function calls {!val:make}
on all the payloads incrementing the initial [counter] for each
operation except for the first one. The function does not fail if
the list of [payload] is empty. *)
val make_batch :
?source:Account.key ->
?fee:int ->
?gas_limit:int ->
?storage_limit:int ->
counter:int ->
payload list ->
t list
(** [get_next_counter ~source client] returns the next valid counter
value for [source] expected by the protocol for a manager
operation where the source is [source]. If the [source] is not
provided, the same one as {!val:make} is used. *)
val get_next_counter : ?source:Account.key -> Client.t -> int Lwt.t
(** [json t] gives the json representation of a manager operation. *)
val json : Client.t -> t -> JSON.u Lwt.t
(** [operation ?branch t client] constructs an operation from a
manager operation. [branch] can be used to set manually the
branch. [client] can be used to get some meta information such as
the [counter] for the operation. *)
val operation :
?branch:string ->
?signer:Account.key ->
t list ->
Client.t ->
operation Lwt.t
(** A wrapper for {!val:inject} with manager operations. The client
is used to get all the data that was not provided if it can be
recovered via RPCs. Mainly those are the [branch] and the
[counter]. *)
val inject :
?request:[`Inject | `Notify] ->
?force:bool ->
?branch:string ->
?signer:Account.key ->
?error:rex ->
t list ->
Client.t ->
[`OpHash of string] Lwt.t
(** A wrapper for {!RPC.get_chain_block_hash} with an offset for the block.
[offset] defaults to [2], to pick the latested finalized branch with
Tenderbake.
*)
val get_branch : ?chain:string -> ?offset:int -> Client.t -> string Lwt.t
(** A wrapper for {!val-operation} on a list consisting in a single
{!val-transfer}. See both functions for details and default
values of arguments. *)
val mk_single_transfer :
?source:Account.key ->
?counter:int ->
?fee:int ->
?gas_limit:int ->
?storage_limit:int ->
?dest:Account.key ->
?amount:int ->
?branch:string ->
?signer:Account.key ->
Client.t ->
operation Lwt.t
(** A wrapper for {!inject}ing a batch consisting in a single
transfer. See {!transfer}, {!make_batch}, and {!inject} for the
descriptions and default values of the arguments. *)
val inject_single_transfer :
?source:Account.key ->
?counter:int ->
?fee:int ->
?gas_limit:int ->
?storage_limit:int ->
?dest:Account.key ->
?amount:int ->
?request:[< `Arrived | `Flush | `Inject | `Notify > `Inject] ->
?force:bool ->
?branch:string ->
?signer:Account.key ->
?error:rex ->
Client.t ->
[`OpHash of string] Lwt.t
end
(** Regular expressions for specific error messages.
Can be used as e.g.
- the [error] argument of {!val:inject}
- the [rex] argument of {!val:inject_and_capture2_stderr}
- the [msg] argument of {!val:Process.check_error}. *)
(** Matches the client message for the [Operation_quota_exceeded]
protocol error. *)
val gas_limit_exceeded : rex
(** Matches the message produced by
[Operation_conflict {new_hash; needed_fee_in_mutez = Some fee}]
from [src/lib_shell_services/validation_errors].
Captures [new_hash] and [fee]. *)
val conflict_error_with_needed_fee : rex
(** Matches the message produced by
[Operation_conflict {new_hash; needed_fee_in_mutez = None}]
from [src/lib_shell_services/validation_errors].
Captures [new_hash]. *)
val conflict_error_no_possible_fee : rex
(** Matches the message produced by
[Rejected_by_full_mempool {hash; needed_fee_in_mutez = Some fee}]
from [src/lib_shell_services/validation_errors].
Captures [hash] and [fee]. *)
val rejected_by_full_mempool_with_needed_fee : rex
(** Matches the message produced by
[Rejected_by_full_mempool {hash; needed_fee_in_mutez = None}]
from [src/lib_shell_services/validation_errors].
Captures [hash]. *)
val rejected_by_full_mempool_no_possible_fee : rex
(** Matches the message produced by
[Dal_data_availibility_attester_not_in_committee {attester; level; slot}]
from [src/proto_alpha/lib_protocol/dal_errors_repr].
Captures [attester], [level], and [slot]. *)
val dal_data_availibility_attester_not_in_committee : rex
(** Calls {!inject_and_capture2_stderr} and checks that the second
captured group is [expected_fee].
Intended to be used with {!conflict_error_with_needed_fee} or
{!rejected_by_full_mempool_with_needed_fee} as [rex]. *)
val inject_error_check_recommended_fee :
loc:string -> rex:rex -> expected_fee:int -> t -> Client.t -> unit Lwt.t
(** Matches the message produced by
[Already_denounced {kind; delegate; level}]
from [src/proto_xxx/lib_protocol/validate_errors].
Captures [delegate], [level], [kind]. *)
val already_denounced : rex
(** Matches the message produced by
[Outdated_denunciation {kind; level; last_cycle}]
from [src/proto_xxx/lib_protocol/validate_errors].
Captures [kind], [last_cycle], [level]. *)
val outdated_denunciation : rex