add protocol store + rpcs
This commit is contained in:
parent
d11e44dead
commit
488373551b
@ -148,6 +148,8 @@ let inject_block ?(wait = true) ?force block =
|
|||||||
call_service0 Services.inject_block (block, wait, force)
|
call_service0 Services.inject_block (block, wait, force)
|
||||||
let inject_operation ?(wait = true) ?force operation =
|
let inject_operation ?(wait = true) ?force operation =
|
||||||
call_service0 Services.inject_operation (operation, wait, force)
|
call_service0 Services.inject_operation (operation, wait, force)
|
||||||
|
let inject_protocol ?(wait = true) ?force protocol =
|
||||||
|
call_service0 Services.inject_protocol (protocol, wait, force)
|
||||||
let describe ?recurse path =
|
let describe ?recurse path =
|
||||||
let prefix, arg = RPC.forge_request Services.describe () recurse in
|
let prefix, arg = RPC.forge_request Services.describe () recurse in
|
||||||
get_json (prefix @ path) arg >>=
|
get_json (prefix @ path) arg >>=
|
||||||
|
@ -25,6 +25,8 @@ val inject_block:
|
|||||||
Block_hash.t tzresult Lwt.t
|
Block_hash.t tzresult Lwt.t
|
||||||
val inject_operation:
|
val inject_operation:
|
||||||
?wait:bool -> ?force:bool -> MBytes.t -> Operation_hash.t tzresult Lwt.t
|
?wait:bool -> ?force:bool -> MBytes.t -> Operation_hash.t tzresult Lwt.t
|
||||||
|
val inject_protocol:
|
||||||
|
?wait:bool -> ?force:bool -> Store.protocol -> Protocol_hash.t tzresult Lwt.t
|
||||||
|
|
||||||
module Blocks : sig
|
module Blocks : sig
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ let remove_rec (module View : VIEW) key =
|
|||||||
GitStore.FunView.remove_rec View.v (data_key key) >>= fun v ->
|
GitStore.FunView.remove_rec View.v (data_key key) >>= fun v ->
|
||||||
Lwt.return (pack (module GitStore) View.s v)
|
Lwt.return (pack (module GitStore) View.s v)
|
||||||
|
|
||||||
|
let keys (module View : VIEW) = Store.undefined_key_fn
|
||||||
|
|
||||||
(*-- Initialisation ----------------------------------------------------------*)
|
(*-- Initialisation ----------------------------------------------------------*)
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ module type DISTRIBUTED_DB = sig
|
|||||||
val update: t -> key -> value -> bool Lwt.t
|
val update: t -> key -> value -> bool Lwt.t
|
||||||
val remove: t -> key -> bool Lwt.t
|
val remove: t -> key -> bool Lwt.t
|
||||||
val shutdown: t -> unit Lwt.t
|
val shutdown: t -> unit Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
type operation_state = {
|
type operation_state = {
|
||||||
@ -106,3 +108,42 @@ module Block =
|
|||||||
Persist.MakeImperativeProxy
|
Persist.MakeImperativeProxy
|
||||||
(Store.Faked_functional_block)
|
(Store.Faked_functional_block)
|
||||||
(Block_hash_table) (Block_scheduler)
|
(Block_hash_table) (Block_scheduler)
|
||||||
|
|
||||||
|
type protocol_state = {
|
||||||
|
request_protocols: Protocol_hash.t list -> unit ;
|
||||||
|
}
|
||||||
|
|
||||||
|
module Protocol_scheduler = struct
|
||||||
|
let name = "protocol_scheduler"
|
||||||
|
type rdata = Store.net_id
|
||||||
|
type data = float ref
|
||||||
|
type state = protocol_state
|
||||||
|
let init_request _ _ = Lwt.return (ref 0.0)
|
||||||
|
let request net ~get:_ ~set:_ pendings =
|
||||||
|
let current_time = Unix.gettimeofday () in
|
||||||
|
let time = current_time -. (3. +. Random.float 8.) in
|
||||||
|
let protocols =
|
||||||
|
List.fold_left
|
||||||
|
(fun acc (hash, last_request, Store.Net net_id) ->
|
||||||
|
if !last_request < time then begin
|
||||||
|
last_request := current_time ;
|
||||||
|
let prev =
|
||||||
|
try Block_hash_map.find net_id acc
|
||||||
|
with Not_found -> [] in
|
||||||
|
Block_hash_map.add net_id (hash :: prev) acc
|
||||||
|
end else
|
||||||
|
acc)
|
||||||
|
Block_hash_map.empty
|
||||||
|
pendings in
|
||||||
|
if Block_hash_map.is_empty protocols then
|
||||||
|
0.
|
||||||
|
else begin
|
||||||
|
Block_hash_map.iter (fun _net_id -> net.request_protocols) protocols ;
|
||||||
|
1. +. Random.float 4.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Protocol =
|
||||||
|
Persist.MakeImperativeProxy
|
||||||
|
(Store.Faked_functional_protocol)
|
||||||
|
(Protocol_hash_table) (Protocol_scheduler)
|
||||||
|
@ -23,6 +23,8 @@ module type DISTRIBUTED_DB = sig
|
|||||||
val update: t -> key -> value -> bool Lwt.t
|
val update: t -> key -> value -> bool Lwt.t
|
||||||
val remove: t -> key -> bool Lwt.t
|
val remove: t -> key -> bool Lwt.t
|
||||||
val shutdown: t -> unit Lwt.t
|
val shutdown: t -> unit Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
type operation_state = {
|
type operation_state = {
|
||||||
@ -44,3 +46,13 @@ module Block :
|
|||||||
and type key := Store.Block.key
|
and type key := Store.Block.key
|
||||||
and type value := Store.Block.value
|
and type value := Store.Block.value
|
||||||
and type state := block_state
|
and type state := block_state
|
||||||
|
|
||||||
|
|
||||||
|
type protocol_state = {
|
||||||
|
request_protocols: Protocol_hash.t list -> unit ;
|
||||||
|
}
|
||||||
|
module Protocol :
|
||||||
|
DISTRIBUTED_DB with type store := Store.Protocol.t
|
||||||
|
and type key := Store.Protocol.key
|
||||||
|
and type value := Store.Protocol.value
|
||||||
|
and type state := protocol_state
|
||||||
|
@ -24,6 +24,8 @@ module type STORE = sig
|
|||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
val list: t -> key list -> key list Lwt.t
|
val list: t -> key list -> key list Lwt.t
|
||||||
val remove_rec: t -> key -> t Lwt.t
|
val remove_rec: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys : t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module type BYTES_STORE = sig
|
module type BYTES_STORE = sig
|
||||||
@ -35,6 +37,8 @@ module type BYTES_STORE = sig
|
|||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
val list: t -> key list -> key list Lwt.t
|
val list: t -> key list -> key list Lwt.t
|
||||||
val remove_rec: t -> key -> t Lwt.t
|
val remove_rec: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys : t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module type TYPED_STORE = sig
|
module type TYPED_STORE = sig
|
||||||
@ -45,6 +49,8 @@ module type TYPED_STORE = sig
|
|||||||
val get: t -> key -> value option Lwt.t
|
val get: t -> key -> value option Lwt.t
|
||||||
val set: t -> key -> value -> t Lwt.t
|
val set: t -> key -> value -> t Lwt.t
|
||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module type KEY = sig
|
module type KEY = sig
|
||||||
@ -146,6 +152,7 @@ module MakeBytesStore
|
|||||||
let remove_rec s k =
|
let remove_rec s k =
|
||||||
S.remove_rec s (to_path k)
|
S.remove_rec s (to_path k)
|
||||||
|
|
||||||
|
let keys s = S.keys s >|= List.map of_path
|
||||||
end
|
end
|
||||||
|
|
||||||
module MakeTypedStore
|
module MakeTypedStore
|
||||||
@ -167,6 +174,7 @@ module MakeTypedStore
|
|||||||
|
|
||||||
let raw_get = S.get
|
let raw_get = S.get
|
||||||
|
|
||||||
|
let keys = S.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
module RawKey = struct
|
module RawKey = struct
|
||||||
@ -369,6 +377,8 @@ module type IMPERATIVE_PROXY = sig
|
|||||||
val fetch: t -> rdata -> Store.key -> Store.value Lwt.t
|
val fetch: t -> rdata -> Store.key -> Store.value Lwt.t
|
||||||
val pending: t -> Store.key -> bool
|
val pending: t -> Store.key -> bool
|
||||||
val shutdown: t -> unit Lwt.t
|
val shutdown: t -> unit Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> Store.key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module type IMPERATIVE_PROXY_SCHEDULER = sig
|
module type IMPERATIVE_PROXY_SCHEDULER = sig
|
||||||
@ -457,6 +467,8 @@ module MakeImperativeProxy
|
|||||||
let known { store } hash =
|
let known { store } hash =
|
||||||
use store (fun store -> Store.mem store hash)
|
use store (fun store -> Store.mem store hash)
|
||||||
|
|
||||||
|
let keys { store } = use store Store.keys
|
||||||
|
|
||||||
let read { store } hash =
|
let read { store } hash =
|
||||||
use store (fun store -> Store.get store hash)
|
use store (fun store -> Store.get store hash)
|
||||||
|
|
||||||
@ -528,6 +540,8 @@ module MakeImperativeProxy
|
|||||||
let shutdown { cancel ; worker } =
|
let shutdown { cancel ; worker } =
|
||||||
cancel () >>= fun () -> worker
|
cancel () >>= fun () -> worker
|
||||||
|
|
||||||
|
let keys { store } =
|
||||||
|
use store (fun store -> Store.keys store)
|
||||||
end
|
end
|
||||||
|
|
||||||
(*-- Predefined Instances ----------------------------------------------------*)
|
(*-- Predefined Instances ----------------------------------------------------*)
|
||||||
|
@ -27,6 +27,8 @@ module type STORE = sig
|
|||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
val list: t -> key list -> key list Lwt.t
|
val list: t -> key list -> key list Lwt.t
|
||||||
val remove_rec: t -> key -> t Lwt.t
|
val remove_rec: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys : t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
(** Projection of OCaml keys of some abstract type to concrete storage
|
(** Projection of OCaml keys of some abstract type to concrete storage
|
||||||
@ -55,6 +57,8 @@ module type BYTES_STORE = sig
|
|||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
val list: t -> key list -> key list Lwt.t
|
val list: t -> key list -> key list Lwt.t
|
||||||
val remove_rec: t -> key -> t Lwt.t
|
val remove_rec: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys : t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module MakeBytesStore (S : STORE) (K : KEY) :
|
module MakeBytesStore (S : STORE) (K : KEY) :
|
||||||
@ -82,6 +86,8 @@ module type TYPED_STORE = sig
|
|||||||
val get: t -> key -> value option Lwt.t
|
val get: t -> key -> value option Lwt.t
|
||||||
val set: t -> key -> value -> t Lwt.t
|
val set: t -> key -> value -> t Lwt.t
|
||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t (** Not always relevant, BEWARE! *)
|
||||||
end
|
end
|
||||||
|
|
||||||
(** Gives a typed view of a store (values of a given type stored under
|
(** Gives a typed view of a store (values of a given type stored under
|
||||||
@ -91,6 +97,7 @@ end
|
|||||||
module MakeTypedStore (S : STORE) (K : KEY) (C : VALUE) :
|
module MakeTypedStore (S : STORE) (K : KEY) (C : VALUE) :
|
||||||
TYPED_STORE with type t = S.t and type key = K.t and type value = C.t
|
TYPED_STORE with type t = S.t and type key = K.t and type value = C.t
|
||||||
|
|
||||||
|
|
||||||
(** {2 Persistent Sets} ******************************************************)
|
(** {2 Persistent Sets} ******************************************************)
|
||||||
|
|
||||||
(** Signature of a set as returned by {!MakePersistentSet} *)
|
(** Signature of a set as returned by {!MakePersistentSet} *)
|
||||||
@ -194,6 +201,8 @@ module type IMPERATIVE_PROXY = sig
|
|||||||
val fetch: t -> rdata -> Store.key -> Store.value Lwt.t
|
val fetch: t -> rdata -> Store.key -> Store.value Lwt.t
|
||||||
val pending: t -> Store.key -> bool
|
val pending: t -> Store.key -> bool
|
||||||
val shutdown: t -> unit Lwt.t
|
val shutdown: t -> unit Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> Store.key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module type IMPERATIVE_PROXY_SCHEDULER = sig
|
module type IMPERATIVE_PROXY_SCHEDULER = sig
|
||||||
|
@ -92,11 +92,13 @@ type generic_store = FS.t
|
|||||||
type block_store = FS.t
|
type block_store = FS.t
|
||||||
type blockchain_store = FS.t
|
type blockchain_store = FS.t
|
||||||
type operation_store = FS.t
|
type operation_store = FS.t
|
||||||
|
type protocol_store = FS.t
|
||||||
|
|
||||||
type store = {
|
type store = {
|
||||||
block: block_store Persist.shared_ref ;
|
block: block_store Persist.shared_ref ;
|
||||||
blockchain: blockchain_store Persist.shared_ref ;
|
blockchain: blockchain_store Persist.shared_ref ;
|
||||||
operation: operation_store Persist.shared_ref ;
|
operation: operation_store Persist.shared_ref ;
|
||||||
|
protocol: protocol_store Persist.shared_ref ;
|
||||||
global_store: generic_store Persist.shared_ref ;
|
global_store: generic_store Persist.shared_ref ;
|
||||||
net_init: ?expiration:Time.t -> genesis -> net_store Lwt.t ;
|
net_init: ?expiration:Time.t -> genesis -> net_store Lwt.t ;
|
||||||
net_read: net_id -> net_store tzresult Lwt.t ;
|
net_read: net_id -> net_store tzresult Lwt.t ;
|
||||||
@ -126,6 +128,8 @@ module type TYPED_IMPERATIVE_STORE = sig
|
|||||||
val get_exn: t -> key -> value Lwt.t
|
val get_exn: t -> key -> value Lwt.t
|
||||||
val set: t -> key -> value -> unit Lwt.t
|
val set: t -> key -> value -> unit Lwt.t
|
||||||
val del: t -> key -> unit Lwt.t
|
val del: t -> key -> unit Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module type IMPERATIVE_STORE = sig
|
module type IMPERATIVE_STORE = sig
|
||||||
@ -146,6 +150,14 @@ module type KEY = sig
|
|||||||
val to_path: t -> string list
|
val to_path: t -> string list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module type HASHKEY = sig
|
||||||
|
type t
|
||||||
|
val to_path: t -> string list
|
||||||
|
val of_path: string list -> t
|
||||||
|
val prefix : string list
|
||||||
|
val length : int
|
||||||
|
end
|
||||||
|
|
||||||
module Raw_key = struct
|
module Raw_key = struct
|
||||||
type t = string list
|
type t = string list
|
||||||
let to_path x = x
|
let to_path x = x
|
||||||
@ -187,6 +199,7 @@ module Errors_value = struct
|
|||||||
let of_bytes b = Data_encoding.(Binary.of_bytes (list (error_encoding ()))) b
|
let of_bytes b = Data_encoding.(Binary.of_bytes (list (error_encoding ()))) b
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let undefined_key_fn = Lwt.fail_invalid_arg "function keys cannot be implemented in this module"
|
||||||
|
|
||||||
module Make (K : KEY) (V : Persist.VALUE) = struct
|
module Make (K : KEY) (V : Persist.VALUE) = struct
|
||||||
type t = FS.t
|
type t = FS.t
|
||||||
@ -205,6 +218,8 @@ module Make (K : KEY) (V : Persist.VALUE) = struct
|
|||||||
let del t k = FS.del t (K.to_path k)
|
let del t k = FS.del t (K.to_path k)
|
||||||
let list t ks = FS.list t (List.map K.to_path ks)
|
let list t ks = FS.list t (List.map K.to_path ks)
|
||||||
let remove_rec t k = FS.remove_rec t (K.to_path k)
|
let remove_rec t k = FS.remove_rec t (K.to_path k)
|
||||||
|
|
||||||
|
let keys _t = undefined_key_fn
|
||||||
end
|
end
|
||||||
|
|
||||||
module Data_store : IMPERATIVE_STORE with type t = FS.t =
|
module Data_store : IMPERATIVE_STORE with type t = FS.t =
|
||||||
@ -212,6 +227,7 @@ module Data_store : IMPERATIVE_STORE with type t = FS.t =
|
|||||||
|
|
||||||
include Data_store
|
include Data_store
|
||||||
|
|
||||||
|
|
||||||
(*-- Typed block store under "blocks/" ---------------------------------------*)
|
(*-- Typed block store under "blocks/" ---------------------------------------*)
|
||||||
|
|
||||||
type shell_block = {
|
type shell_block = {
|
||||||
@ -350,6 +366,7 @@ module Block = struct
|
|||||||
|
|
||||||
let raw_get t k = Raw_block.get t k
|
let raw_get t k = Raw_block.get t k
|
||||||
|
|
||||||
|
let keys _t = undefined_key_fn (** We never list keys here *)
|
||||||
end
|
end
|
||||||
|
|
||||||
module Blockchain_succ_key = struct
|
module Blockchain_succ_key = struct
|
||||||
@ -484,9 +501,111 @@ module Operation = struct
|
|||||||
let to_bytes = Raw_operation_value.to_bytes
|
let to_bytes = Raw_operation_value.to_bytes
|
||||||
let hash op = Operation_hash.hash_bytes [to_bytes op]
|
let hash op = Operation_hash.hash_bytes [to_bytes op]
|
||||||
let raw_get t k = Raw_operation_data.get t k
|
let raw_get t k = Raw_operation_data.get t k
|
||||||
|
|
||||||
|
let keys _t = undefined_key_fn (** We never list keys here *)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
(*-- Typed operation store under "protocols/" -------------------------------*)
|
||||||
|
|
||||||
|
type component = {
|
||||||
|
name : string ;
|
||||||
|
interface : string option ;
|
||||||
|
implementation : string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let component_encoding =
|
||||||
|
let open Data_encoding in
|
||||||
|
conv
|
||||||
|
(fun { name ; interface; implementation } -> (name, interface, implementation))
|
||||||
|
(fun (name, interface, implementation) -> { name ; interface ; implementation })
|
||||||
|
(obj3
|
||||||
|
(req "name" string)
|
||||||
|
(opt "interface" string)
|
||||||
|
(req "implementation" string))
|
||||||
|
|
||||||
|
type protocol = component list
|
||||||
|
let protocol_encoding = Data_encoding.list component_encoding
|
||||||
|
|
||||||
|
module Raw_protocol_value = struct
|
||||||
|
type t = protocol
|
||||||
|
let to_bytes v = Data_encoding.Binary.to_bytes protocol_encoding v
|
||||||
|
let of_bytes b = Data_encoding.Binary.of_bytes protocol_encoding b
|
||||||
|
end
|
||||||
|
|
||||||
|
module Raw_protocol_key = struct
|
||||||
|
type t = Protocol_hash.t
|
||||||
|
let to_path p = "protocols" :: Protocol_hash.to_path p @ [ "contents" ]
|
||||||
|
end
|
||||||
|
|
||||||
|
module Protocol_data = Make (Raw_protocol_key) (Raw_protocol_value)
|
||||||
|
module Raw_protocol_data = Make (Raw_protocol_key) (Raw_value)
|
||||||
|
|
||||||
|
module Protocol_time_key = struct
|
||||||
|
type t = Protocol_hash.t
|
||||||
|
let to_path p = "protocols" :: Protocol_hash.to_path p @ [ "discovery_time" ]
|
||||||
|
end
|
||||||
|
module Protocol_time = Make (Protocol_time_key) (Time_value)
|
||||||
|
|
||||||
|
module Protocol_errors_key = struct
|
||||||
|
type t = Protocol_hash.t
|
||||||
|
let to_path p = "protocols" :: Protocol_hash.to_path p @ [ "errors" ]
|
||||||
|
end
|
||||||
|
module Protocol_errors = Make (Protocol_errors_key) (Errors_value)
|
||||||
|
|
||||||
|
module Protocol = struct
|
||||||
|
type t = FS.t
|
||||||
|
type key = Protocol_hash.t
|
||||||
|
type value = protocol tzresult Time.timed_data
|
||||||
|
let mem = Protocol_data.mem
|
||||||
|
let get s k =
|
||||||
|
Protocol_time.get s k >>= function
|
||||||
|
| None -> Lwt.return_none
|
||||||
|
| Some time ->
|
||||||
|
Protocol_errors.get s k >>= function
|
||||||
|
| Some exns -> Lwt.return (Some { Time.data = Error exns ; time })
|
||||||
|
| None ->
|
||||||
|
Protocol_data.get s k >>= function
|
||||||
|
| None -> Lwt.return_none
|
||||||
|
| Some bytes -> Lwt.return (Some { Time.data = Ok bytes ; time })
|
||||||
|
let get_exn s k =
|
||||||
|
get s k >>= function
|
||||||
|
| None -> Lwt.fail Not_found
|
||||||
|
| Some x -> Lwt.return x
|
||||||
|
let set s k { Time.data ; time } =
|
||||||
|
Protocol_time.set s k time >>= fun () ->
|
||||||
|
match data with
|
||||||
|
| Ok bytes ->
|
||||||
|
Protocol_data.set s k bytes >>= fun () ->
|
||||||
|
Protocol_errors.del s k
|
||||||
|
| Error exns ->
|
||||||
|
Protocol_errors.set s k exns >>= fun () ->
|
||||||
|
Protocol_data.del s k
|
||||||
|
let del s k =
|
||||||
|
Protocol_time.del s k >>= fun () ->
|
||||||
|
Protocol_data.del s k >>= fun () ->
|
||||||
|
Protocol_errors.del s k
|
||||||
|
let of_bytes = Raw_protocol_value.of_bytes
|
||||||
|
let to_bytes = Raw_protocol_value.to_bytes
|
||||||
|
let hash proto = Protocol_hash.hash_bytes [to_bytes proto]
|
||||||
|
let compare p1 p2 =
|
||||||
|
Protocol_hash.(compare (hash_bytes [to_bytes p1]) (hash_bytes [to_bytes p2]))
|
||||||
|
let equal b1 b2 = compare b1 b2 = 0
|
||||||
|
let raw_get t k = Raw_protocol_data.get t k
|
||||||
|
|
||||||
|
let fold s x ~f =
|
||||||
|
let rec dig i root acc =
|
||||||
|
if i <= 0 then
|
||||||
|
f (Protocol_hash.of_path @@ List.tl root) acc
|
||||||
|
else
|
||||||
|
FS.list s [root] >>= fun roots ->
|
||||||
|
Lwt_list.fold_right_s (dig (i - 1)) roots acc
|
||||||
|
in
|
||||||
|
dig Protocol_hash.path_len ["protocols"] x
|
||||||
|
|
||||||
|
let keys s = fold s [] ~f:(fun k a -> Lwt.return @@ k :: a)
|
||||||
|
end
|
||||||
|
|
||||||
(*- Genesis and initialization -----------------------------------------------*)
|
(*- Genesis and initialization -----------------------------------------------*)
|
||||||
|
|
||||||
let genesis_encoding =
|
let genesis_encoding =
|
||||||
@ -620,6 +739,7 @@ let init root =
|
|||||||
{ block = Persist.share t ;
|
{ block = Persist.share t ;
|
||||||
blockchain = Persist.share t ;
|
blockchain = Persist.share t ;
|
||||||
operation = Persist.share t ;
|
operation = Persist.share t ;
|
||||||
|
protocol = Persist.share t ;
|
||||||
global_store = Persist.share t ;
|
global_store = Persist.share t ;
|
||||||
net_init = net_init ~root ;
|
net_init = net_init ~root ;
|
||||||
net_read = net_read ~root ;
|
net_read = net_read ~root ;
|
||||||
@ -638,6 +758,7 @@ end
|
|||||||
|
|
||||||
module Faked_functional_operation = Faked_functional_typed_store (Operation)
|
module Faked_functional_operation = Faked_functional_typed_store (Operation)
|
||||||
module Faked_functional_block = Faked_functional_typed_store (Block)
|
module Faked_functional_block = Faked_functional_typed_store (Block)
|
||||||
|
module Faked_functional_protocol = Faked_functional_typed_store (Protocol)
|
||||||
|
|
||||||
module Faked_functional_store : Persist.STORE with type t = t
|
module Faked_functional_store : Persist.STORE with type t = t
|
||||||
= struct
|
= struct
|
||||||
@ -645,4 +766,6 @@ module Faked_functional_store : Persist.STORE with type t = t
|
|||||||
let set s k v = Data_store.set s k v >>= fun () -> Lwt.return s
|
let set s k v = Data_store.set s k v >>= fun () -> Lwt.return s
|
||||||
let del s k = Data_store.del s k >>= fun () -> Lwt.return s
|
let del s k = Data_store.del s k >>= fun () -> Lwt.return s
|
||||||
let remove_rec s k = Data_store.remove_rec s k >>= fun () -> Lwt.return s
|
let remove_rec s k = Data_store.remove_rec s k >>= fun () -> Lwt.return s
|
||||||
|
|
||||||
|
let keys _s = invalid_arg "function keys not implementable here" (** We never use list here. *)
|
||||||
end
|
end
|
||||||
|
@ -21,6 +21,7 @@ module type TYPED_IMPERATIVE_STORE = sig
|
|||||||
val get_exn: t -> key -> value Lwt.t
|
val get_exn: t -> key -> value Lwt.t
|
||||||
val set: t -> key -> value -> unit Lwt.t
|
val set: t -> key -> value -> unit Lwt.t
|
||||||
val del: t -> key -> unit Lwt.t
|
val del: t -> key -> unit Lwt.t
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module type IMPERATIVE_STORE = sig
|
module type IMPERATIVE_STORE = sig
|
||||||
@ -39,11 +40,13 @@ type generic_store
|
|||||||
type block_store
|
type block_store
|
||||||
type blockchain_store
|
type blockchain_store
|
||||||
type operation_store
|
type operation_store
|
||||||
|
type protocol_store
|
||||||
|
|
||||||
type store = private {
|
type store = private {
|
||||||
block: block_store Persist.shared_ref ;
|
block: block_store Persist.shared_ref ;
|
||||||
blockchain: blockchain_store Persist.shared_ref ;
|
blockchain: blockchain_store Persist.shared_ref ;
|
||||||
operation: operation_store Persist.shared_ref ;
|
operation: operation_store Persist.shared_ref ;
|
||||||
|
protocol: protocol_store Persist.shared_ref ;
|
||||||
global_store: generic_store Persist.shared_ref ;
|
global_store: generic_store Persist.shared_ref ;
|
||||||
net_init: ?expiration:Time.t -> genesis -> net_store Lwt.t ;
|
net_init: ?expiration:Time.t -> genesis -> net_store Lwt.t ;
|
||||||
net_read: net_id -> net_store tzresult Lwt.t ;
|
net_read: net_id -> net_store tzresult Lwt.t ;
|
||||||
@ -70,6 +73,9 @@ val pp_net_id: Format.formatter -> net_id -> unit
|
|||||||
(** Open or initialize a store at a given path. *)
|
(** Open or initialize a store at a given path. *)
|
||||||
val init: string -> store Lwt.t
|
val init: string -> store Lwt.t
|
||||||
|
|
||||||
|
(** Lwt exn returned when function keys is not implemented *)
|
||||||
|
val undefined_key_fn : 'a Lwt.t
|
||||||
|
|
||||||
(** {2 Generic interface} ****************************************************)
|
(** {2 Generic interface} ****************************************************)
|
||||||
|
|
||||||
(** The generic primitives do work on the direct root, but in a
|
(** The generic primitives do work on the direct root, but in a
|
||||||
@ -107,6 +113,16 @@ type block = {
|
|||||||
val shell_block_encoding: shell_block Data_encoding.t
|
val shell_block_encoding: shell_block Data_encoding.t
|
||||||
val block_encoding: block Data_encoding.t
|
val block_encoding: block Data_encoding.t
|
||||||
|
|
||||||
|
(** Protocol *)
|
||||||
|
type component = {
|
||||||
|
name : string ;
|
||||||
|
interface : string option ;
|
||||||
|
implementation : string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
type protocol = component list
|
||||||
|
val protocol_encoding : protocol Data_encoding.t
|
||||||
|
|
||||||
(** {2 Block and operations store} ********************************************)
|
(** {2 Block and operations store} ********************************************)
|
||||||
|
|
||||||
module Block : sig
|
module Block : sig
|
||||||
@ -177,6 +193,19 @@ module Operation : sig
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Protocol : sig
|
||||||
|
val of_bytes: MBytes.t -> protocol option
|
||||||
|
val to_bytes: protocol -> MBytes.t
|
||||||
|
val hash: protocol -> Protocol_hash.t
|
||||||
|
|
||||||
|
include TYPED_IMPERATIVE_STORE
|
||||||
|
with type t = protocol_store
|
||||||
|
and type key = Protocol_hash.t
|
||||||
|
and type value = protocol tzresult Time.timed_data
|
||||||
|
|
||||||
|
val raw_get: t -> Protocol_hash.t -> MBytes.t option Lwt.t
|
||||||
|
end
|
||||||
|
|
||||||
(**/**) (* For testing only *)
|
(**/**) (* For testing only *)
|
||||||
|
|
||||||
(* module LwtUnixStore : sig *)
|
(* module LwtUnixStore : sig *)
|
||||||
@ -198,4 +227,9 @@ module Faked_functional_block :
|
|||||||
and type value = Block.value
|
and type value = Block.value
|
||||||
and type key = Block.key
|
and type key = Block.key
|
||||||
|
|
||||||
|
module Faked_functional_protocol :
|
||||||
|
Persist.TYPED_STORE with type t = Protocol.t
|
||||||
|
and type value = Protocol.value
|
||||||
|
and type key = Protocol.key
|
||||||
|
|
||||||
module Faked_functional_store : Persist.STORE with type t = t
|
module Faked_functional_store : Persist.STORE with type t = t
|
||||||
|
@ -25,8 +25,8 @@ type message =
|
|||||||
| Get_operations of Operation_hash.t list
|
| Get_operations of Operation_hash.t list
|
||||||
| Operation of MBytes.t
|
| Operation of MBytes.t
|
||||||
|
|
||||||
| Current_protocol of net_id
|
| Get_protocols of Protocol_hash.t list
|
||||||
| Protocol_inventory of Protocol_hash.t
|
| Protocol of MBytes.t
|
||||||
|
|
||||||
|
|
||||||
let to_frame msg =
|
let to_frame msg =
|
||||||
@ -54,10 +54,10 @@ let to_frame msg =
|
|||||||
| Operation b ->
|
| Operation b ->
|
||||||
[ S 2703 ; B b ]
|
[ S 2703 ; B b ]
|
||||||
|
|
||||||
| Current_protocol (Net net_id) ->
|
| Get_protocols protos ->
|
||||||
[ S 2800 ; bh net_id ]
|
[ S 2800 ; F (List.map ph protos) ]
|
||||||
| Protocol_inventory p ->
|
| Protocol p ->
|
||||||
[ S 2801 ; ph p ]
|
[ S 2801 ; B p ]
|
||||||
|
|
||||||
let from_frame msg =
|
let from_frame msg =
|
||||||
|
|
||||||
@ -82,9 +82,9 @@ let from_frame msg =
|
|||||||
Some (Get_operations (List.map oph ops))
|
Some (Get_operations (List.map oph ops))
|
||||||
| [ S 2703 ; B contents ] -> Some (Operation contents)
|
| [ S 2703 ; B contents ] -> Some (Operation contents)
|
||||||
|
|
||||||
| [ S 2800 ; B netid ] -> Some (Current_protocol (net netid))
|
| [ S 2800 ; F protos ] -> Some (Get_protocols (List.map ph protos))
|
||||||
|
|
||||||
| [ S 2801 ; p ] -> Some (Protocol_inventory (ph p))
|
| [ S 2801 ; B contents ] -> Some (Protocol contents)
|
||||||
|
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
|
@ -22,8 +22,9 @@ type message =
|
|||||||
| Get_operations of Operation_hash.t list
|
| Get_operations of Operation_hash.t list
|
||||||
| Operation of MBytes.t
|
| Operation of MBytes.t
|
||||||
|
|
||||||
| Current_protocol of Store.net_id
|
| Get_protocols of Protocol_hash.t list
|
||||||
| Protocol_inventory of Protocol_hash.t
|
| Protocol of MBytes.t
|
||||||
|
|
||||||
|
|
||||||
(** Converts a high level message to a network frame *)
|
(** Converts a high level message to a network frame *)
|
||||||
val to_frame: message -> Netbits.frame
|
val to_frame: message -> Netbits.frame
|
||||||
|
@ -24,6 +24,13 @@ let inject_operation validator ?force bytes =
|
|||||||
let hash = Operation_hash.hash_bytes [bytes] in
|
let hash = Operation_hash.hash_bytes [bytes] in
|
||||||
Lwt.return (hash, t)
|
Lwt.return (hash, t)
|
||||||
|
|
||||||
|
let inject_protocol state ?force:_ proto =
|
||||||
|
(* TODO: Validate the protocol *)
|
||||||
|
let proto_bytes = Store.Protocol.to_bytes proto in
|
||||||
|
let hash = Protocol_hash.hash_bytes [proto_bytes] in
|
||||||
|
let t = State.Protocol.store state proto_bytes >>|? ignore in
|
||||||
|
Lwt.return (hash, t)
|
||||||
|
|
||||||
let process_operation state validator bytes =
|
let process_operation state validator bytes =
|
||||||
State.Operation.store state bytes >>= function
|
State.Operation.store state bytes >>= function
|
||||||
| Error _ | Ok None -> Lwt.return_unit
|
| Error _ | Ok None -> Lwt.return_unit
|
||||||
@ -38,6 +45,13 @@ let process_operation state validator bytes =
|
|||||||
Prevalidator.register_operation prevalidator hash ;
|
Prevalidator.register_operation prevalidator hash ;
|
||||||
Lwt.return_unit
|
Lwt.return_unit
|
||||||
|
|
||||||
|
let process_protocol state _validator bytes =
|
||||||
|
State.Protocol.store state bytes >>= function
|
||||||
|
| Error _ | Ok None -> Lwt.return_unit
|
||||||
|
| Ok (Some (hash, _proto)) ->
|
||||||
|
(* TODO: Store only pending protocols... *)
|
||||||
|
lwt_log_info "process Protocol %a" Protocol_hash.pp_short hash
|
||||||
|
|
||||||
let process_block state validator bytes =
|
let process_block state validator bytes =
|
||||||
State.Block.store state bytes >>= function
|
State.Block.store state bytes >>= function
|
||||||
| Error _ | Ok None -> Lwt.return_unit
|
| Error _ | Ok None -> Lwt.return_unit
|
||||||
@ -144,23 +158,20 @@ let process state validator msg =
|
|||||||
process_operation state validator content >>= fun () ->
|
process_operation state validator content >>= fun () ->
|
||||||
Lwt.return_nil
|
Lwt.return_nil
|
||||||
|
|
||||||
| Current_protocol net_id ->
|
| Get_protocols protos ->
|
||||||
lwt_log_info "process Current_protocol" >>= fun () ->
|
lwt_log_info "process Get_protocols" >>= fun () ->
|
||||||
if not (State.Net.is_active state net_id) then
|
Lwt_list.map_p (State.Protocol.raw_read state) protos >>= fun protos ->
|
||||||
Lwt.return_nil
|
let cons_protocol acc = function
|
||||||
else begin
|
| Some proto -> Protocol proto :: acc
|
||||||
match State.Net.get state net_id with
|
| None -> acc in
|
||||||
| Error _ -> Lwt.return_nil
|
Lwt.return (List.fold_left cons_protocol [] protos)
|
||||||
| Ok net ->
|
|
||||||
State.Net.Blockchain.head net >>= fun head ->
|
|
||||||
Lwt.return [Protocol_inventory head.protocol_hash]
|
|
||||||
end
|
|
||||||
|
|
||||||
| Protocol_inventory _ ->
|
| Protocol content ->
|
||||||
lwt_log_info "process Protocol_inventory" >>= fun () ->
|
lwt_log_info "process Protocol" >>= fun () ->
|
||||||
(* TODO... *)
|
process_protocol state validator content >>= fun () ->
|
||||||
Lwt.return_nil
|
Lwt.return_nil
|
||||||
|
|
||||||
|
|
||||||
type t = {
|
type t = {
|
||||||
state: State.t ;
|
state: State.t ;
|
||||||
validator: Validator.worker ;
|
validator: Validator.worker ;
|
||||||
@ -170,6 +181,8 @@ type t = {
|
|||||||
?force:bool -> MBytes.t -> (Block_hash.t * unit tzresult Lwt.t) Lwt.t ;
|
?force:bool -> MBytes.t -> (Block_hash.t * unit tzresult Lwt.t) Lwt.t ;
|
||||||
inject_operation:
|
inject_operation:
|
||||||
?force:bool -> MBytes.t -> (Operation_hash.t * unit tzresult Lwt.t) Lwt.t ;
|
?force:bool -> MBytes.t -> (Operation_hash.t * unit tzresult Lwt.t) Lwt.t ;
|
||||||
|
inject_protocol:
|
||||||
|
?force:bool -> Store.protocol -> (Protocol_hash.t * unit tzresult Lwt.t) Lwt.t ;
|
||||||
shutdown: unit -> unit Lwt.t ;
|
shutdown: unit -> unit Lwt.t ;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +197,11 @@ let request_blocks net _net_id blocks =
|
|||||||
For now simply broadcast the request to all our neighbours. *)
|
For now simply broadcast the request to all our neighbours. *)
|
||||||
P2p.broadcast (Messages.(to_frame (Get_blocks blocks))) net
|
P2p.broadcast (Messages.(to_frame (Get_blocks blocks))) net
|
||||||
|
|
||||||
|
let request_protocols net protocols =
|
||||||
|
(* TODO improve the lookup strategy.
|
||||||
|
For now simply broadcast the request to all our neighbours. *)
|
||||||
|
P2p.broadcast (Messages.(to_frame (Get_protocols protocols))) net
|
||||||
|
|
||||||
let init_p2p net_params =
|
let init_p2p net_params =
|
||||||
match net_params with
|
match net_params with
|
||||||
| None ->
|
| None ->
|
||||||
@ -200,8 +218,9 @@ let create
|
|||||||
lwt_log_info "reading state..." >>= fun () ->
|
lwt_log_info "reading state..." >>= fun () ->
|
||||||
let request_operations = request_operations p2p in
|
let request_operations = request_operations p2p in
|
||||||
let request_blocks = request_blocks p2p in
|
let request_blocks = request_blocks p2p in
|
||||||
|
let request_protocols = request_protocols p2p in
|
||||||
State.read
|
State.read
|
||||||
~request_operations ~request_blocks
|
~request_operations ~request_blocks ~request_protocols
|
||||||
~store_root ~context_root ~ttl:(48 * 3600) (* 2 days *)
|
~store_root ~context_root ~ttl:(48 * 3600) (* 2 days *)
|
||||||
?patch_context () >>= fun state ->
|
?patch_context () >>= fun state ->
|
||||||
let validator = Validator.create_worker p2p state in
|
let validator = Validator.create_worker p2p state in
|
||||||
@ -264,6 +283,7 @@ let create
|
|||||||
global_validator ;
|
global_validator ;
|
||||||
inject_block = inject_block state validator ;
|
inject_block = inject_block state validator ;
|
||||||
inject_operation = inject_operation validator ;
|
inject_operation = inject_operation validator ;
|
||||||
|
inject_protocol = inject_protocol state ;
|
||||||
shutdown ;
|
shutdown ;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,6 +330,7 @@ module RPC = struct
|
|||||||
|
|
||||||
let inject_block node = node.inject_block
|
let inject_block node = node.inject_block
|
||||||
let inject_operation node = node.inject_operation
|
let inject_operation node = node.inject_operation
|
||||||
|
let inject_protocol node = node.inject_protocol
|
||||||
|
|
||||||
let raw_block_info node hash =
|
let raw_block_info node hash =
|
||||||
State.Valid_block.read_exn node.state hash >|= convert
|
State.Valid_block.read_exn node.state hash >|= convert
|
||||||
@ -449,6 +470,11 @@ module RPC = struct
|
|||||||
State.Net.Mempool.for_block net b >|= fun ops ->
|
State.Net.Mempool.for_block net b >|= fun ops ->
|
||||||
Updater.empty_result, ops
|
Updater.empty_result, ops
|
||||||
|
|
||||||
|
let protocols { state } = State.Protocol.keys state
|
||||||
|
|
||||||
|
let protocol_content node hash =
|
||||||
|
State.Protocol.read node.state hash
|
||||||
|
|
||||||
let preapply node block ~timestamp ~sort ops =
|
let preapply node block ~timestamp ~sort ops =
|
||||||
begin
|
begin
|
||||||
match block with
|
match block with
|
||||||
@ -539,6 +565,9 @@ module RPC = struct
|
|||||||
let operation_watcher node =
|
let operation_watcher node =
|
||||||
State.Operation.create_watcher node.state
|
State.Operation.create_watcher node.state
|
||||||
|
|
||||||
|
let protocol_watcher node =
|
||||||
|
State.Protocol.create_watcher node.state
|
||||||
|
|
||||||
let validate node net_id block =
|
let validate node net_id block =
|
||||||
Validator.get node.validator net_id >>=? fun net_v ->
|
Validator.get node.validator net_id >>=? fun net_v ->
|
||||||
Validator.fetch_block net_v block >>=? fun _ ->
|
Validator.fetch_block net_v block >>=? fun _ ->
|
||||||
|
@ -29,6 +29,8 @@ module RPC : sig
|
|||||||
t -> ?force:bool -> MBytes.t -> (Block_hash.t * unit tzresult Lwt.t) Lwt.t
|
t -> ?force:bool -> MBytes.t -> (Block_hash.t * unit tzresult Lwt.t) Lwt.t
|
||||||
val inject_operation:
|
val inject_operation:
|
||||||
t -> ?force:bool -> MBytes.t -> (Operation_hash.t * unit tzresult Lwt.t) Lwt.t
|
t -> ?force:bool -> MBytes.t -> (Operation_hash.t * unit tzresult Lwt.t) Lwt.t
|
||||||
|
val inject_protocol:
|
||||||
|
t -> ?force:bool -> Store.protocol -> (Protocol_hash.t * unit tzresult Lwt.t) Lwt.t
|
||||||
|
|
||||||
val raw_block_info:
|
val raw_block_info:
|
||||||
t -> Block_hash.t -> block_info Lwt.t
|
t -> Block_hash.t -> block_info Lwt.t
|
||||||
@ -54,6 +56,13 @@ module RPC : sig
|
|||||||
val pending_operations:
|
val pending_operations:
|
||||||
t -> block -> (error Updater.preapply_result * Operation_hash_set.t) Lwt.t
|
t -> block -> (error Updater.preapply_result * Operation_hash_set.t) Lwt.t
|
||||||
|
|
||||||
|
val protocols:
|
||||||
|
t -> Protocol_hash.t list Lwt.t
|
||||||
|
val protocol_content:
|
||||||
|
t -> Protocol_hash.t -> Store.protocol tzresult Time.timed_data option Lwt.t
|
||||||
|
val protocol_watcher:
|
||||||
|
t -> (Protocol_hash.t * Store.protocol) Lwt_stream.t * (unit -> unit)
|
||||||
|
|
||||||
val context_dir:
|
val context_dir:
|
||||||
t -> block -> 'a RPC.directory option Lwt.t
|
t -> block -> 'a RPC.directory option Lwt.t
|
||||||
|
|
||||||
|
@ -332,6 +332,42 @@ let get_operations node hash () =
|
|||||||
| Some bytes -> RPC.Answer.return bytes
|
| Some bytes -> RPC.Answer.return bytes
|
||||||
| None -> raise Not_found
|
| None -> raise Not_found
|
||||||
|
|
||||||
|
let list_protocols node {Services.Protocols.monitor; contents} =
|
||||||
|
let monitor = match monitor with None -> false | Some x -> x in
|
||||||
|
let include_contents = match contents with None -> false | Some x -> x in
|
||||||
|
Node.RPC.protocols node >>= fun protocols ->
|
||||||
|
Lwt_list.map_p
|
||||||
|
(fun hash ->
|
||||||
|
if include_contents then
|
||||||
|
Node.RPC.protocol_content node hash >>= function
|
||||||
|
| None | Some { Time.data = Error _ } -> Lwt.return (hash, None)
|
||||||
|
| Some { Time.data = Ok bytes }->
|
||||||
|
Lwt.return (hash, Some bytes)
|
||||||
|
else
|
||||||
|
Lwt.return (hash, None))
|
||||||
|
protocols >>= fun protocols ->
|
||||||
|
if not monitor then
|
||||||
|
RPC.Answer.return protocols
|
||||||
|
else
|
||||||
|
let stream, shutdown = Node.RPC.protocol_watcher node in
|
||||||
|
let first_request = ref true in
|
||||||
|
let next () =
|
||||||
|
if not !first_request then
|
||||||
|
Lwt_stream.get stream >>= function
|
||||||
|
| None -> Lwt.return_none
|
||||||
|
| Some (h, op) when include_contents -> Lwt.return (Some [h, Some op])
|
||||||
|
| Some (h, _) -> Lwt.return (Some [h, None])
|
||||||
|
else begin
|
||||||
|
first_request := false ;
|
||||||
|
Lwt.return (Some protocols)
|
||||||
|
end in
|
||||||
|
RPC.Answer.return_stream { next ; shutdown }
|
||||||
|
|
||||||
|
let get_protocols node hash () =
|
||||||
|
Node.RPC.protocol_content node hash >>= function
|
||||||
|
| Some bytes -> RPC.Answer.return bytes
|
||||||
|
| None -> raise Not_found
|
||||||
|
|
||||||
let build_rpc_directory node =
|
let build_rpc_directory node =
|
||||||
let dir = RPC.empty in
|
let dir = RPC.empty in
|
||||||
let dir = RPC.register0 dir Services.Blocks.list (list_blocks node) in
|
let dir = RPC.register0 dir Services.Blocks.list (list_blocks node) in
|
||||||
@ -351,6 +387,10 @@ let build_rpc_directory node =
|
|||||||
RPC.register0 dir Services.Operations.list (list_operations node) in
|
RPC.register0 dir Services.Operations.list (list_operations node) in
|
||||||
let dir =
|
let dir =
|
||||||
RPC.register1 dir Services.Operations.bytes (get_operations node) in
|
RPC.register1 dir Services.Operations.bytes (get_operations node) in
|
||||||
|
let dir =
|
||||||
|
RPC.register0 dir Services.Protocols.list (list_protocols node) in
|
||||||
|
let dir =
|
||||||
|
RPC.register1 dir Services.Protocols.bytes (get_protocols node) in
|
||||||
let dir =
|
let dir =
|
||||||
let implementation (net_id, pred, time, fitness, operations, header) =
|
let implementation (net_id, pred, time, fitness, operations, header) =
|
||||||
Node.RPC.block_info node (`Head 0) >>= fun bi ->
|
Node.RPC.block_info node (`Head 0) >>= fun bi ->
|
||||||
@ -383,6 +423,13 @@ let build_rpc_directory node =
|
|||||||
(if blocking then wait else return ()) >>=? fun () -> return hash
|
(if blocking then wait else return ()) >>=? fun () -> return hash
|
||||||
end >>= RPC.Answer.return in
|
end >>= RPC.Answer.return in
|
||||||
RPC.register0 dir Services.inject_operation implementation in
|
RPC.register0 dir Services.inject_operation implementation in
|
||||||
|
let dir =
|
||||||
|
let implementation (proto, blocking, force) =
|
||||||
|
Node.RPC.inject_protocol ?force node proto >>= fun (hash, wait) ->
|
||||||
|
begin
|
||||||
|
(if blocking then wait else return ()) >>=? fun () -> return hash
|
||||||
|
end >>= RPC.Answer.return in
|
||||||
|
RPC.register0 dir Services.inject_protocol implementation in
|
||||||
let dir =
|
let dir =
|
||||||
let implementation () =
|
let implementation () =
|
||||||
RPC.Answer.return Data_encoding.Json.(schema (Error_monad.error_encoding ())) in
|
RPC.Answer.return Data_encoding.Json.(schema (Error_monad.error_encoding ())) in
|
||||||
|
@ -383,6 +383,56 @@ module Operations = struct
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Protocols = struct
|
||||||
|
let protocols_arg =
|
||||||
|
let name = "protocol_id" in
|
||||||
|
let descr =
|
||||||
|
"A protocol identifier in hexadecimal." in
|
||||||
|
let construct = Protocol_hash.to_b48check in
|
||||||
|
let destruct h =
|
||||||
|
try Ok (Protocol_hash.of_b48check h)
|
||||||
|
with _ -> Error "Can't parse hash" in
|
||||||
|
RPC.Arg.make ~name ~descr ~construct ~destruct ()
|
||||||
|
|
||||||
|
let bytes =
|
||||||
|
RPC.service
|
||||||
|
~input: empty
|
||||||
|
~output:
|
||||||
|
(obj1 (req "data"
|
||||||
|
(describe ~title: "Tezos protocol"
|
||||||
|
(Time.timed_encoding @@
|
||||||
|
RPC.Error.wrap @@
|
||||||
|
Store.protocol_encoding))))
|
||||||
|
RPC.Path.(root / "protocols" /: protocols_arg)
|
||||||
|
|
||||||
|
type list_param = {
|
||||||
|
contents: bool option ;
|
||||||
|
monitor: bool option ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list_param_encoding =
|
||||||
|
conv
|
||||||
|
(fun {contents; monitor} -> (contents, monitor))
|
||||||
|
(fun (contents, monitor) -> {contents; monitor})
|
||||||
|
(obj2
|
||||||
|
(opt "contents" bool)
|
||||||
|
(opt "monitor" bool))
|
||||||
|
|
||||||
|
let list =
|
||||||
|
RPC.service
|
||||||
|
~input: list_param_encoding
|
||||||
|
~output:
|
||||||
|
(obj1
|
||||||
|
(req "protocols"
|
||||||
|
(list
|
||||||
|
(obj2
|
||||||
|
(req "hash" Protocol_hash.encoding)
|
||||||
|
(opt "contents"
|
||||||
|
(dynamic_size Store.protocol_encoding)))
|
||||||
|
)))
|
||||||
|
RPC.Path.(root / "protocols")
|
||||||
|
end
|
||||||
|
|
||||||
let forge_block =
|
let forge_block =
|
||||||
RPC.service
|
RPC.service
|
||||||
~description: "Forge a block header"
|
~description: "Forge a block header"
|
||||||
@ -480,6 +530,59 @@ let inject_operation =
|
|||||||
(obj1 (req "injectedOperation" Operation_hash.encoding)))
|
(obj1 (req "injectedOperation" Operation_hash.encoding)))
|
||||||
RPC.Path.(root / "inject_operation")
|
RPC.Path.(root / "inject_operation")
|
||||||
|
|
||||||
|
let inject_protocol =
|
||||||
|
let proto =
|
||||||
|
(list
|
||||||
|
(obj3
|
||||||
|
(req "name"
|
||||||
|
(describe ~title:"OCaml module name"
|
||||||
|
string))
|
||||||
|
(opt "interface"
|
||||||
|
(describe
|
||||||
|
~description:"Content of the .mli file"
|
||||||
|
string))
|
||||||
|
(req "implementation"
|
||||||
|
(describe
|
||||||
|
~description:"Content of the .ml file"
|
||||||
|
string))))
|
||||||
|
in
|
||||||
|
let proto_of_rpc =
|
||||||
|
List.map (fun (name, interface, implementation) ->
|
||||||
|
{ Store.name; interface; implementation })
|
||||||
|
in
|
||||||
|
let rpc_of_proto =
|
||||||
|
List.map (fun { Store.name; interface; implementation } ->
|
||||||
|
(name, interface, implementation))
|
||||||
|
in
|
||||||
|
RPC.service
|
||||||
|
~description:
|
||||||
|
"Inject a protocol in node. Returns the ID of the protocol."
|
||||||
|
~input:
|
||||||
|
(conv
|
||||||
|
(fun (proto, blocking, force) -> (rpc_of_proto proto, Some blocking, force))
|
||||||
|
(fun (proto, blocking, force) -> (proto_of_rpc proto, unopt true blocking, force))
|
||||||
|
(obj3
|
||||||
|
(req "protocol"
|
||||||
|
(describe ~title: "Tezos protocol"
|
||||||
|
proto))
|
||||||
|
(opt "blocking"
|
||||||
|
(describe
|
||||||
|
~description:
|
||||||
|
"Should the RPC wait for the protocol to be \
|
||||||
|
validated before to answer. (default: true)"
|
||||||
|
bool))
|
||||||
|
(opt "force"
|
||||||
|
(describe
|
||||||
|
~description:
|
||||||
|
"Should we inject protocol that is invalid. (default: false)"
|
||||||
|
bool))))
|
||||||
|
~output:
|
||||||
|
(RPC.Error.wrap @@
|
||||||
|
describe
|
||||||
|
~title: "Hash of the injected protocol" @@
|
||||||
|
(obj1 (req "injectedProtocol" Protocol_hash.encoding)))
|
||||||
|
RPC.Path.(root / "inject_protocol")
|
||||||
|
|
||||||
let describe =
|
let describe =
|
||||||
RPC.Description.service
|
RPC.Description.service
|
||||||
~description: "RPCs documentation and input/output schema"
|
~description: "RPCs documentation and input/output schema"
|
||||||
|
@ -97,6 +97,19 @@ module Operations : sig
|
|||||||
list_param, (Operation_hash.t * Store.operation option) list) RPC.service
|
list_param, (Operation_hash.t * Store.operation option) list) RPC.service
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Protocols : sig
|
||||||
|
val bytes:
|
||||||
|
(unit, unit * Protocol_hash.t, unit,
|
||||||
|
Store.protocol tzresult Time.timed_data) RPC.service
|
||||||
|
type list_param = {
|
||||||
|
contents: bool option ;
|
||||||
|
monitor: bool option ;
|
||||||
|
}
|
||||||
|
val list:
|
||||||
|
(unit, unit,
|
||||||
|
list_param, (Protocol_hash.t * Store.protocol option) list) RPC.service
|
||||||
|
end
|
||||||
|
|
||||||
val forge_block:
|
val forge_block:
|
||||||
(unit, unit,
|
(unit, unit,
|
||||||
Updater.net_id option * Block_hash.t option * Time.t option *
|
Updater.net_id option * Block_hash.t option * Time.t option *
|
||||||
@ -115,5 +128,9 @@ val inject_operation:
|
|||||||
(unit, unit,
|
(unit, unit,
|
||||||
(MBytes.t * bool * bool option), Operation_hash.t tzresult) RPC.service
|
(MBytes.t * bool * bool option), Operation_hash.t tzresult) RPC.service
|
||||||
|
|
||||||
|
val inject_protocol:
|
||||||
|
(unit, unit,
|
||||||
|
(Store.protocol * bool * bool option), Protocol_hash.t tzresult) RPC.service
|
||||||
|
|
||||||
val describe:
|
val describe:
|
||||||
(unit, unit, bool option, RPC.Description.directory_descr) RPC.service
|
(unit, unit, bool option, RPC.Description.directory_descr) RPC.service
|
||||||
|
@ -82,6 +82,9 @@ type t = {
|
|||||||
operation_db: Db_proxy.Operation.t ;
|
operation_db: Db_proxy.Operation.t ;
|
||||||
operation_watchers:
|
operation_watchers:
|
||||||
(Operation_hash.t * Store.operation) Watcher.t list ref ;
|
(Operation_hash.t * Store.operation) Watcher.t list ref ;
|
||||||
|
protocol_db: Db_proxy.Protocol.t ;
|
||||||
|
protocol_watchers:
|
||||||
|
(Protocol_hash.t * Store.protocol) Watcher.t list ref ;
|
||||||
valid_block_state: valid_block_state Persist.shared_ref ;
|
valid_block_state: valid_block_state Persist.shared_ref ;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +157,15 @@ module InvalidOperations =
|
|||||||
Persist.MakeBufferedPersistentSet
|
Persist.MakeBufferedPersistentSet
|
||||||
(Store.Faked_functional_store) (InvalidOperations_key) (Operation_hash_set)
|
(Store.Faked_functional_store) (InvalidOperations_key) (Operation_hash_set)
|
||||||
|
|
||||||
|
module InvalidProtocols_key = struct
|
||||||
|
include Protocol_hash
|
||||||
|
let prefix = ["state"; "invalid_protocols"]
|
||||||
|
let length = path_len
|
||||||
|
end
|
||||||
|
module InvalidProtocols =
|
||||||
|
Persist.MakeBufferedPersistentSet
|
||||||
|
(Store.Faked_functional_store) (InvalidProtocols_key) (Protocol_hash_set)
|
||||||
|
|
||||||
module InvalidBlocks_key = struct
|
module InvalidBlocks_key = struct
|
||||||
include Block_hash
|
include Block_hash
|
||||||
let prefix = ["state"; "invalid_blocks"]
|
let prefix = ["state"; "invalid_blocks"]
|
||||||
@ -236,6 +248,66 @@ module Operation = struct
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Protocol = struct
|
||||||
|
type key = Store.Protocol.key
|
||||||
|
|
||||||
|
type component = Store.component = {
|
||||||
|
name: string;
|
||||||
|
interface: string option;
|
||||||
|
implementation: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type t = Store.protocol
|
||||||
|
|
||||||
|
type protocol = t
|
||||||
|
exception Invalid of key * error list
|
||||||
|
let of_bytes = Store.Protocol.of_bytes
|
||||||
|
let to_bytes = Store.Protocol.to_bytes
|
||||||
|
let known t k = Db_proxy.Protocol.known t.protocol_db k
|
||||||
|
let read t k = Db_proxy.Protocol.read t.protocol_db k
|
||||||
|
let read_exn t k =
|
||||||
|
Db_proxy.Protocol.read t.protocol_db k >>= function
|
||||||
|
| None -> Lwt.fail Not_found
|
||||||
|
| Some { data = Error e } -> Lwt.fail (Invalid (k, e))
|
||||||
|
| Some { data = Ok data ; time } -> Lwt.return { Time.data ; time }
|
||||||
|
let hash = Store.Protocol.hash
|
||||||
|
let raw_read t k =
|
||||||
|
Persist.use t.store.Store.protocol
|
||||||
|
(fun store -> Store.Protocol.raw_get store k)
|
||||||
|
let prefetch t net_id ks =
|
||||||
|
List.iter (Db_proxy.Protocol.prefetch t.protocol_db net_id) ks
|
||||||
|
let fetch t net_id k = Db_proxy.Protocol.fetch t.protocol_db net_id k
|
||||||
|
let store t bytes =
|
||||||
|
match of_bytes bytes with
|
||||||
|
| None -> fail Cannot_parse
|
||||||
|
| Some proto ->
|
||||||
|
let h = hash proto in
|
||||||
|
Db_proxy.Protocol.store t.protocol_db h (Time.make_timed (Ok proto))
|
||||||
|
>>= function
|
||||||
|
| true ->
|
||||||
|
Watcher.notify !(t.protocol_watchers) (h, proto) ;
|
||||||
|
return (Some (h, proto))
|
||||||
|
| false ->
|
||||||
|
return None
|
||||||
|
let mark_invalid t k e =
|
||||||
|
Db_proxy.Protocol.update t.protocol_db k (Time.make_timed (Error e))
|
||||||
|
>>= function
|
||||||
|
| true ->
|
||||||
|
Persist.update t.store.global_store (fun store ->
|
||||||
|
InvalidProtocols.set store k >>= fun store ->
|
||||||
|
Lwt.return (Some store)) >>= fun _ ->
|
||||||
|
Lwt.return true
|
||||||
|
| false -> Lwt.return false
|
||||||
|
|
||||||
|
let invalid state =
|
||||||
|
Persist.use state.store.global_store InvalidProtocols.read
|
||||||
|
|
||||||
|
let create_watcher t = Watcher.create_stream t.protocol_watchers ()
|
||||||
|
|
||||||
|
let keys { protocol_db } = Db_proxy.Protocol.keys protocol_db
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
let iter_predecessors
|
let iter_predecessors
|
||||||
(type t)
|
(type t)
|
||||||
(compare: t -> t -> int)
|
(compare: t -> t -> int)
|
||||||
@ -458,7 +530,7 @@ module Valid_block = struct
|
|||||||
hash: Block_hash.t ;
|
hash: Block_hash.t ;
|
||||||
pred: Block_hash.t ;
|
pred: Block_hash.t ;
|
||||||
timestamp: Time.t ;
|
timestamp: Time.t ;
|
||||||
fitness: Protocol.fitness ;
|
fitness: Fitness.fitness ;
|
||||||
operations: Operation_hash.t list ;
|
operations: Operation_hash.t list ;
|
||||||
discovery_time: Time.t ;
|
discovery_time: Time.t ;
|
||||||
protocol_hash: Protocol_hash.t ;
|
protocol_hash: Protocol_hash.t ;
|
||||||
@ -785,6 +857,8 @@ module Valid_block = struct
|
|||||||
| Error exns ->
|
| Error exns ->
|
||||||
locked_store_invalid vstate hash exns >>= fun _changed ->
|
locked_store_invalid vstate hash exns >>= fun _changed ->
|
||||||
Lwt.return vstate
|
Lwt.return vstate
|
||||||
|
|
||||||
|
let keys _ = Store.undefined_key_fn
|
||||||
end
|
end
|
||||||
|
|
||||||
let iter_predecessors =
|
let iter_predecessors =
|
||||||
@ -1216,12 +1290,14 @@ let () =
|
|||||||
(** Whole protocol state : read and store. *)
|
(** Whole protocol state : read and store. *)
|
||||||
|
|
||||||
let read
|
let read
|
||||||
~request_operations ~request_blocks
|
~request_operations ~request_blocks ~request_protocols
|
||||||
~store_root ~context_root ~ttl ?patch_context () =
|
~store_root ~context_root ~ttl ?patch_context () =
|
||||||
Store.init store_root >>= fun store ->
|
Store.init store_root >>= fun store ->
|
||||||
lwt_log_info "Initialising the distributed database..." >>= fun () ->
|
lwt_log_info "Initialising the distributed database..." >>= fun () ->
|
||||||
let operation_db =
|
let operation_db =
|
||||||
Db_proxy.Operation.create { request_operations } store.operation in
|
Db_proxy.Operation.create { request_operations } store.operation in
|
||||||
|
let protocol_db =
|
||||||
|
Db_proxy.Protocol.create { request_protocols } store.protocol in
|
||||||
let block_db =
|
let block_db =
|
||||||
Db_proxy.Block.create { request_blocks } store.block in
|
Db_proxy.Block.create { request_blocks } store.block in
|
||||||
Valid_block.create
|
Valid_block.create
|
||||||
@ -1233,6 +1309,8 @@ let read
|
|||||||
nets = Block_hash_table.create 7 ;
|
nets = Block_hash_table.create 7 ;
|
||||||
operation_db ;
|
operation_db ;
|
||||||
operation_watchers = ref [] ;
|
operation_watchers = ref [] ;
|
||||||
|
protocol_db ;
|
||||||
|
protocol_watchers = ref [] ;
|
||||||
block_db ; block_watchers = ref [] ;
|
block_db ; block_watchers = ref [] ;
|
||||||
valid_block_state ;
|
valid_block_state ;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ type error +=
|
|||||||
val read:
|
val read:
|
||||||
request_operations: (net_id -> Operation_hash.t list -> unit) ->
|
request_operations: (net_id -> Operation_hash.t list -> unit) ->
|
||||||
request_blocks: (net_id -> Block_hash.t list -> unit) ->
|
request_blocks: (net_id -> Block_hash.t list -> unit) ->
|
||||||
|
request_protocols: (Protocol_hash.t list -> unit) ->
|
||||||
store_root:string ->
|
store_root:string ->
|
||||||
context_root:string ->
|
context_root:string ->
|
||||||
ttl:int ->
|
ttl:int ->
|
||||||
@ -342,6 +343,78 @@ module Valid_block : sig
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
(** {2 Protocol database} ****************************************************)
|
||||||
|
|
||||||
|
(** The local and distributed database of protocols. *)
|
||||||
|
module Protocol : sig
|
||||||
|
|
||||||
|
type key = Protocol_hash.t
|
||||||
|
|
||||||
|
type component = Store.component = {
|
||||||
|
name : string ;
|
||||||
|
interface : string option ;
|
||||||
|
implementation : string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
type t = Store.protocol
|
||||||
|
|
||||||
|
type protocol = t
|
||||||
|
|
||||||
|
(** Is a protocol stored in the local database ? *)
|
||||||
|
val known: state -> key -> bool Lwt.t
|
||||||
|
|
||||||
|
(** Read a protocol in the local database. This returns [None]
|
||||||
|
when the protocol does not exist in the local database; this returns
|
||||||
|
[Some (Error _)] when [mark_invalid] was used. This also returns
|
||||||
|
the time when the protocol was stored on the local database. *)
|
||||||
|
val read:
|
||||||
|
state -> key -> protocol tzresult Time.timed_data option Lwt.t
|
||||||
|
|
||||||
|
(** Read a protocol in the local database. This throws [Not_found]
|
||||||
|
when the protocol does not exist in the local database or when
|
||||||
|
[mark_invalid] was used. *)
|
||||||
|
val read_exn:
|
||||||
|
state -> key -> protocol Time.timed_data Lwt.t
|
||||||
|
exception Invalid of key * error list
|
||||||
|
|
||||||
|
(** Read an operation in the local database (without parsing). *)
|
||||||
|
val raw_read: state -> key -> MBytes.t option Lwt.t
|
||||||
|
|
||||||
|
(** Read a protocol from the distributed database. This may block
|
||||||
|
while the block is fetched from the P2P network. *)
|
||||||
|
val fetch:
|
||||||
|
state -> Store.net_id -> key -> protocol tzresult Time.timed_data Lwt.t
|
||||||
|
|
||||||
|
(** Request protocols on the P2P network without waiting for answers. *)
|
||||||
|
val prefetch: state -> Store.net_id -> key list -> unit
|
||||||
|
|
||||||
|
(** Add a protocol to the local database. This returns [Ok None]
|
||||||
|
if the protocol was already stored in the database, or returns
|
||||||
|
the parsed operation if not. It may also fails when the shell
|
||||||
|
part of the operation cannot be parsed or when the operation
|
||||||
|
does not belong to an active "network". For a given sequence of
|
||||||
|
bytes, it is guaranted that at most one call to [store] returns
|
||||||
|
[Some _]. *)
|
||||||
|
val store:
|
||||||
|
state -> MBytes.t -> (Protocol_hash.t * protocol) option tzresult Lwt.t
|
||||||
|
|
||||||
|
(** Mark a protocol as invalid in the local database. This returns
|
||||||
|
[false] if the protocol was previously stored in the local
|
||||||
|
database. The protocol is not removed from the local database,
|
||||||
|
but its content is replaced by a list of errors. *)
|
||||||
|
val mark_invalid: state -> key -> error list -> bool Lwt.t
|
||||||
|
|
||||||
|
(** Returns the list known-invalid procols. *)
|
||||||
|
val invalid: state -> Protocol_hash_set.t Lwt.t
|
||||||
|
|
||||||
|
(** Create a stream of all the newly locally-stored protocols.
|
||||||
|
The returned function allows to terminate the stream. *)
|
||||||
|
val create_watcher:
|
||||||
|
state -> (key * protocol) Lwt_stream.t * (unit -> unit)
|
||||||
|
|
||||||
|
val keys: state -> key list Lwt.t
|
||||||
|
end
|
||||||
|
|
||||||
(** {2 Network} ****************************************************************)
|
(** {2 Network} ****************************************************************)
|
||||||
|
|
||||||
(** Data specific to a given network. *)
|
(** Data specific to a given network. *)
|
||||||
|
@ -131,7 +131,7 @@ let get_basedir () =
|
|||||||
let init dir =
|
let init dir =
|
||||||
basedir := Some dir
|
basedir := Some dir
|
||||||
|
|
||||||
type component = {
|
type component = Store.component = {
|
||||||
name : string ;
|
name : string ;
|
||||||
interface : string option ;
|
interface : string option ;
|
||||||
implementation : string ;
|
implementation : string ;
|
||||||
|
@ -18,6 +18,8 @@ module type STORE = sig
|
|||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
val list: t -> key list -> key list Lwt.t
|
val list: t -> key list -> key list Lwt.t
|
||||||
val remove_rec: t -> key -> t Lwt.t
|
val remove_rec: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
(** Projection of OCaml keys of some abstract type to concrete storage
|
(** Projection of OCaml keys of some abstract type to concrete storage
|
||||||
@ -57,6 +59,8 @@ module type BYTES_STORE = sig
|
|||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
val list: t -> key list -> key list Lwt.t
|
val list: t -> key list -> key list Lwt.t
|
||||||
val remove_rec: t -> key -> t Lwt.t
|
val remove_rec: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
module MakeBytesStore (S : STORE) (K : KEY) :
|
module MakeBytesStore (S : STORE) (K : KEY) :
|
||||||
@ -73,6 +77,8 @@ module type TYPED_STORE = sig
|
|||||||
val get: t -> key -> value option Lwt.t
|
val get: t -> key -> value option Lwt.t
|
||||||
val set: t -> key -> value -> t Lwt.t
|
val set: t -> key -> value -> t Lwt.t
|
||||||
val del: t -> key -> t Lwt.t
|
val del: t -> key -> t Lwt.t
|
||||||
|
|
||||||
|
val keys: t -> key list Lwt.t
|
||||||
end
|
end
|
||||||
|
|
||||||
(** Gives a typed view of a store (values of a given type stored under
|
(** Gives a typed view of a store (values of a given type stored under
|
||||||
|
Loading…
Reference in New Issue
Block a user