2016-09-08 21:13:10 +04:00
|
|
|
(**************************************************************************)
|
|
|
|
(* *)
|
2017-11-14 03:36:14 +04:00
|
|
|
(* Copyright (c) 2014 - 2017. *)
|
2016-09-08 21:13:10 +04:00
|
|
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
|
|
(* *)
|
|
|
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
|
|
|
open Logging.Node.State
|
|
|
|
|
|
|
|
type error +=
|
2017-02-24 20:17:53 +04:00
|
|
|
| Unknown_network of Net_id.t
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-09-13 17:44:19 +04:00
|
|
|
type error += Bad_data_dir
|
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
let () =
|
|
|
|
Error_monad.register_error_kind
|
|
|
|
`Temporary
|
|
|
|
~id:"state.unknown_network"
|
|
|
|
~title:"Unknown network"
|
|
|
|
~description:"TODO"
|
2017-02-24 20:17:53 +04:00
|
|
|
~pp:(fun ppf id ->
|
|
|
|
Format.fprintf ppf "Unknown network %a" Net_id.pp id)
|
2017-03-31 15:04:05 +04:00
|
|
|
Data_encoding.(obj1 (req "net" Net_id.encoding))
|
2016-09-08 21:13:10 +04:00
|
|
|
(function Unknown_network x -> Some x | _ -> None)
|
|
|
|
(fun x -> Unknown_network x) ;
|
2017-09-13 17:44:19 +04:00
|
|
|
Error_monad.register_error_kind
|
|
|
|
`Permanent
|
|
|
|
~id:"badDataDir"
|
|
|
|
~title:"Bad data directory"
|
|
|
|
~description:"The data directory could not be read. \
|
|
|
|
This could be because it was generated with an \
|
|
|
|
old version of the tezos-node program. \
|
|
|
|
Deleting and regenerating this directory \
|
|
|
|
may fix the problem."
|
|
|
|
Data_encoding.empty
|
|
|
|
(function Bad_data_dir -> Some () | _ -> None)
|
|
|
|
(fun () -> Bad_data_dir) ;
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-11-13 19:34:00 +04:00
|
|
|
(** *)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
module Shared = struct
|
2017-02-24 20:17:53 +04:00
|
|
|
type 'a t = {
|
|
|
|
data: 'a ;
|
|
|
|
lock: Lwt_mutex.t ;
|
|
|
|
}
|
|
|
|
let create data = { data ; lock = Lwt_mutex.create () }
|
|
|
|
let use { data ; lock } f =
|
|
|
|
Lwt_mutex.with_lock lock (fun () -> f data)
|
|
|
|
end
|
|
|
|
|
|
|
|
type global_state = {
|
|
|
|
global_data: global_data Shared.t ;
|
|
|
|
protocol_store: Store.Protocol.store Shared.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
and global_data = {
|
2017-04-19 23:46:10 +04:00
|
|
|
nets: net_state Net_id.Table.t ;
|
2017-02-24 20:17:53 +04:00
|
|
|
global_store: Store.t ;
|
2017-04-19 23:46:10 +04:00
|
|
|
context_index: Context.index ;
|
2017-02-24 20:17:53 +04:00
|
|
|
}
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
and net_state = {
|
2017-09-29 20:43:13 +04:00
|
|
|
global_state: global_state ;
|
2017-04-19 23:46:10 +04:00
|
|
|
net_id: Net_id.t ;
|
2017-02-24 20:17:53 +04:00
|
|
|
genesis: genesis ;
|
2017-11-11 06:34:12 +04:00
|
|
|
faked_genesis_hash: Block_hash.t ;
|
2017-02-24 20:17:53 +04:00
|
|
|
expiration: Time.t option ;
|
2017-04-10 23:14:17 +04:00
|
|
|
allow_forked_network: bool ;
|
2017-04-19 23:46:10 +04:00
|
|
|
block_store: Store.Block.store Shared.t ;
|
|
|
|
context_index: Context.index Shared.t ;
|
2017-11-27 09:13:12 +04:00
|
|
|
block_watcher: block Lwt_watcher.input ;
|
2017-04-19 23:46:10 +04:00
|
|
|
chain_state: chain_state Shared.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
and genesis = {
|
|
|
|
time: Time.t ;
|
|
|
|
block: Block_hash.t ;
|
|
|
|
protocol: Protocol_hash.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
and chain_state = {
|
|
|
|
mutable data: chain_data ;
|
2017-02-24 20:17:53 +04:00
|
|
|
chain_store: Store.Chain.store ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
and chain_data = {
|
|
|
|
current_head: block ;
|
2017-11-13 17:33:39 +04:00
|
|
|
current_mempool: mempool ;
|
2017-11-14 04:29:19 +04:00
|
|
|
live_blocks: Block_hash.Set.t ;
|
|
|
|
live_operations: Operation_hash.Set.t ;
|
2017-12-17 22:51:06 +04:00
|
|
|
locator: Block_locator.t Lwt.t lazy_t ;
|
2017-11-13 17:33:39 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
and mempool = {
|
|
|
|
known_valid: Operation_hash.t list ;
|
|
|
|
pending: Operation_hash.Set.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
and block = {
|
|
|
|
net_state: net_state ;
|
|
|
|
hash: Block_hash.t ;
|
|
|
|
contents: Store.Block.contents ;
|
|
|
|
}
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-11-13 17:33:39 +04:00
|
|
|
let mempool_encoding =
|
|
|
|
let open Data_encoding in
|
|
|
|
conv
|
|
|
|
(fun { known_valid ; pending } -> (known_valid, pending))
|
|
|
|
(fun (known_valid, pending) -> { known_valid ; pending })
|
|
|
|
(obj2
|
|
|
|
(req "known_valid" (dynamic_size (list Operation_hash.encoding)))
|
|
|
|
(req "pending" (dynamic_size Operation_hash.Set.encoding)))
|
|
|
|
|
|
|
|
let empty_mempool = {
|
|
|
|
known_valid = [] ;
|
|
|
|
pending = Operation_hash.Set.empty ;
|
|
|
|
}
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let read_chain_store { chain_state } f =
|
|
|
|
Shared.use chain_state begin fun state ->
|
|
|
|
f state.chain_store state.data
|
2017-02-24 20:17:53 +04:00
|
|
|
end
|
2016-10-21 16:01:20 +04:00
|
|
|
|
2017-09-22 20:20:26 +04:00
|
|
|
let update_chain_store { net_id ; context_index ; chain_state } f =
|
2017-04-19 23:46:10 +04:00
|
|
|
Shared.use chain_state begin fun state ->
|
|
|
|
f state.chain_store state.data >>= fun (data, res) ->
|
2017-09-22 20:20:26 +04:00
|
|
|
Lwt_utils.may data
|
|
|
|
~f:begin fun data ->
|
|
|
|
state.data <- data ;
|
|
|
|
Shared.use context_index begin fun context_index ->
|
|
|
|
Context.set_head context_index net_id
|
|
|
|
data.current_head.contents.context
|
|
|
|
end >>= fun () ->
|
|
|
|
Lwt.return_unit
|
|
|
|
end >>= fun () ->
|
2017-04-19 23:46:10 +04:00
|
|
|
Lwt.return res
|
2017-03-30 15:16:21 +04:00
|
|
|
end
|
|
|
|
|
2017-12-17 22:51:06 +04:00
|
|
|
let compute_locator_from_hash (net : net_state) ?(size = 200) head =
|
|
|
|
Shared.use net.block_store begin fun block_store ->
|
|
|
|
Block_locator.compute block_store head size
|
|
|
|
end
|
|
|
|
|
|
|
|
let compute_locator net ?size head =
|
|
|
|
compute_locator_from_hash net ?size head.hash
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
type t = global_state
|
2017-03-30 15:16:21 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
module Locked_block = struct
|
2017-03-30 15:16:21 +04:00
|
|
|
|
2017-12-05 18:18:05 +04:00
|
|
|
let store_genesis store genesis context =
|
2017-04-19 21:21:23 +04:00
|
|
|
let shell : Block_header.shell_header = {
|
2017-04-10 15:01:22 +04:00
|
|
|
level = 0l ;
|
2017-04-12 20:22:40 +04:00
|
|
|
proto_level = 0 ;
|
2017-02-24 20:17:53 +04:00
|
|
|
predecessor = genesis.block ;
|
|
|
|
timestamp = genesis.time ;
|
|
|
|
fitness = [] ;
|
2017-09-29 20:43:13 +04:00
|
|
|
validation_passes = 0 ;
|
2017-04-10 19:06:11 +04:00
|
|
|
operations_hash = Operation_list_list_hash.empty ;
|
2017-12-05 18:19:22 +04:00
|
|
|
context ;
|
2017-02-24 20:17:53 +04:00
|
|
|
} in
|
2017-04-19 23:46:10 +04:00
|
|
|
let header : Block_header.t = { shell ; proto = MBytes.create 0 } in
|
|
|
|
Store.Block.Contents.store (store, genesis.block)
|
2017-12-05 18:18:05 +04:00
|
|
|
{ Store.Block.header ; message = Some "Genesis" ;
|
|
|
|
max_operations_ttl = 0 ; context ;
|
2017-11-19 17:38:36 +04:00
|
|
|
max_number_of_operations = [];
|
|
|
|
max_operation_data_length = 0;
|
|
|
|
} >>= fun () ->
|
2017-03-22 20:21:52 +04:00
|
|
|
Lwt.return header
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
module Net = struct
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
type nonrec genesis = genesis = {
|
|
|
|
time: Time.t ;
|
|
|
|
block: Block_hash.t ;
|
|
|
|
protocol: Protocol_hash.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
2017-04-19 23:46:10 +04:00
|
|
|
let genesis_encoding =
|
|
|
|
let open Data_encoding in
|
|
|
|
conv
|
|
|
|
(fun { time ; block ; protocol } -> (time, block, protocol))
|
|
|
|
(fun (time, block, protocol) -> { time ; block ; protocol })
|
|
|
|
(obj3
|
|
|
|
(req "timestamp" Time.encoding)
|
|
|
|
(req "block" Block_hash.encoding)
|
|
|
|
(req "protocol" Protocol_hash.encoding))
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
type t = net_state
|
|
|
|
type net_state = t
|
|
|
|
|
|
|
|
let allocate
|
2017-11-11 06:34:12 +04:00
|
|
|
~genesis ~faked_genesis_hash ~expiration ~allow_forked_network
|
2017-04-19 23:46:10 +04:00
|
|
|
~current_head
|
2017-09-29 20:43:13 +04:00
|
|
|
global_state context_index chain_store block_store =
|
2017-04-19 23:46:10 +04:00
|
|
|
Store.Block.Contents.read_exn
|
|
|
|
(block_store, current_head) >>= fun current_block ->
|
|
|
|
let rec chain_state = {
|
|
|
|
data = {
|
|
|
|
current_head = {
|
|
|
|
net_state ;
|
|
|
|
hash = current_head ;
|
|
|
|
contents = current_block ;
|
2017-09-29 20:43:13 +04:00
|
|
|
} ;
|
2017-11-13 17:33:39 +04:00
|
|
|
current_mempool = empty_mempool ;
|
2017-11-14 04:29:19 +04:00
|
|
|
live_blocks = Block_hash.Set.singleton genesis.block ;
|
|
|
|
live_operations = Operation_hash.Set.empty ;
|
2017-12-17 22:51:06 +04:00
|
|
|
locator = lazy (compute_locator_from_hash net_state current_head) ;
|
2017-04-19 23:46:10 +04:00
|
|
|
} ;
|
|
|
|
chain_store ;
|
|
|
|
}
|
|
|
|
and net_state = {
|
2017-09-29 20:43:13 +04:00
|
|
|
global_state ;
|
2017-04-19 23:46:10 +04:00
|
|
|
net_id = Net_id.of_block_hash genesis.block ;
|
|
|
|
chain_state = { Shared.data = chain_state ; lock = Lwt_mutex.create () } ;
|
2017-11-11 06:34:12 +04:00
|
|
|
genesis ; faked_genesis_hash ;
|
2017-04-19 23:46:10 +04:00
|
|
|
expiration ;
|
|
|
|
allow_forked_network ;
|
|
|
|
block_store = Shared.create block_store ;
|
|
|
|
context_index = Shared.create context_index ;
|
2017-11-27 09:13:12 +04:00
|
|
|
block_watcher = Lwt_watcher.create_input () ;
|
2017-04-19 23:46:10 +04:00
|
|
|
} in
|
|
|
|
Lwt.return net_state
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
let locked_create
|
2017-09-29 20:43:13 +04:00
|
|
|
global_state data ?expiration ?(allow_forked_network = false)
|
2017-07-17 17:59:09 +04:00
|
|
|
net_id genesis commit =
|
2017-03-31 15:04:05 +04:00
|
|
|
let net_store = Store.Net.get data.global_store net_id in
|
2017-04-19 23:46:10 +04:00
|
|
|
let block_store = Store.Block.get net_store
|
2017-02-24 20:17:53 +04:00
|
|
|
and chain_store = Store.Chain.get net_store in
|
2017-03-31 15:04:05 +04:00
|
|
|
Store.Net.Genesis_hash.store net_store genesis.block >>= fun () ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Store.Net.Genesis_time.store net_store genesis.time >>= fun () ->
|
|
|
|
Store.Net.Genesis_protocol.store net_store genesis.protocol >>= fun () ->
|
|
|
|
Store.Chain.Current_head.store chain_store genesis.block >>= fun () ->
|
|
|
|
Store.Chain.Known_heads.store chain_store genesis.block >>= fun () ->
|
|
|
|
begin
|
|
|
|
match expiration with
|
|
|
|
| None -> Lwt.return_unit
|
|
|
|
| Some time -> Store.Net.Expiration.store net_store time
|
|
|
|
end >>= fun () ->
|
2017-04-10 23:14:17 +04:00
|
|
|
begin
|
|
|
|
if allow_forked_network then
|
|
|
|
Store.Net.Allow_forked_network.store data.global_store net_id
|
|
|
|
else
|
|
|
|
Lwt.return_unit
|
|
|
|
end >>= fun () ->
|
2017-04-19 23:46:10 +04:00
|
|
|
Locked_block.store_genesis
|
2017-11-11 06:34:12 +04:00
|
|
|
block_store genesis commit >>= fun genesis_header ->
|
2017-04-19 23:46:10 +04:00
|
|
|
allocate
|
2017-02-24 20:17:53 +04:00
|
|
|
~genesis
|
2017-11-11 06:34:12 +04:00
|
|
|
~faked_genesis_hash:(Block_header.hash genesis_header)
|
2017-04-19 23:46:10 +04:00
|
|
|
~current_head:genesis.block
|
2017-02-24 20:17:53 +04:00
|
|
|
~expiration
|
2017-04-10 23:14:17 +04:00
|
|
|
~allow_forked_network
|
2017-09-29 20:43:13 +04:00
|
|
|
global_state
|
2017-04-19 23:46:10 +04:00
|
|
|
data.context_index
|
2017-02-24 20:17:53 +04:00
|
|
|
chain_store
|
2017-04-19 23:46:10 +04:00
|
|
|
block_store
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-04-10 23:14:17 +04:00
|
|
|
let create state ?allow_forked_network genesis =
|
2017-03-31 15:04:05 +04:00
|
|
|
let net_id = Net_id.of_block_hash genesis.block in
|
2017-02-24 20:17:53 +04:00
|
|
|
Shared.use state.global_data begin fun data ->
|
2017-03-31 15:04:05 +04:00
|
|
|
if Net_id.Table.mem data.nets net_id then
|
2017-02-24 20:17:53 +04:00
|
|
|
Pervasives.failwith "State.Net.create"
|
|
|
|
else
|
2017-07-17 17:59:09 +04:00
|
|
|
Context.commit_genesis
|
|
|
|
data.context_index
|
|
|
|
~net_id
|
|
|
|
~time:genesis.time
|
|
|
|
~protocol:genesis.protocol >>= fun commit ->
|
2017-04-19 23:46:10 +04:00
|
|
|
locked_create
|
2017-09-29 20:43:13 +04:00
|
|
|
state data ?allow_forked_network net_id genesis commit >>= fun net ->
|
2017-03-31 15:04:05 +04:00
|
|
|
Net_id.Table.add data.nets net_id net ;
|
2017-02-24 20:17:53 +04:00
|
|
|
Lwt.return net
|
|
|
|
end
|
|
|
|
|
2017-09-29 20:43:13 +04:00
|
|
|
let locked_read global_state data id =
|
2017-02-24 20:17:53 +04:00
|
|
|
let net_store = Store.Net.get data.global_store id in
|
2017-04-19 23:46:10 +04:00
|
|
|
let block_store = Store.Block.get net_store
|
2017-02-24 20:17:53 +04:00
|
|
|
and chain_store = Store.Chain.get net_store in
|
2017-03-31 15:04:05 +04:00
|
|
|
Store.Net.Genesis_hash.read net_store >>=? fun genesis_hash ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Store.Net.Genesis_time.read net_store >>=? fun time ->
|
|
|
|
Store.Net.Genesis_protocol.read net_store >>=? fun protocol ->
|
|
|
|
Store.Net.Expiration.read_opt net_store >>= fun expiration ->
|
2017-04-10 23:14:17 +04:00
|
|
|
Store.Net.Allow_forked_network.known
|
|
|
|
data.global_store id >>= fun allow_forked_network ->
|
2017-11-11 06:34:12 +04:00
|
|
|
Store.Block.Contents.read (block_store, genesis_hash) >>=? fun genesis_header ->
|
2017-02-24 20:17:53 +04:00
|
|
|
let genesis = { time ; protocol ; block = genesis_hash } in
|
2017-04-19 23:46:10 +04:00
|
|
|
Store.Chain.Current_head.read chain_store >>=? fun current_head ->
|
2017-09-13 17:44:19 +04:00
|
|
|
try
|
|
|
|
allocate
|
|
|
|
~genesis
|
2017-11-11 06:34:12 +04:00
|
|
|
~faked_genesis_hash:(Block_header.hash genesis_header.header)
|
2017-09-13 17:44:19 +04:00
|
|
|
~current_head
|
|
|
|
~expiration
|
|
|
|
~allow_forked_network
|
|
|
|
global_state
|
|
|
|
data.context_index
|
|
|
|
chain_store
|
|
|
|
block_store >>= return
|
|
|
|
with Not_found ->
|
|
|
|
fail Bad_data_dir
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-09-29 20:43:13 +04:00
|
|
|
let locked_read_all global_state data =
|
2017-02-24 20:17:53 +04:00
|
|
|
Store.Net.list data.global_store >>= fun ids ->
|
|
|
|
iter_p
|
|
|
|
(fun id ->
|
2017-09-29 20:43:13 +04:00
|
|
|
locked_read global_state data id >>=? fun net ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Net_id.Table.add data.nets id net ;
|
|
|
|
return ())
|
|
|
|
ids
|
|
|
|
|
|
|
|
let read_all state =
|
|
|
|
Shared.use state.global_data begin fun data ->
|
2017-09-29 20:43:13 +04:00
|
|
|
locked_read_all state data
|
2017-02-24 20:17:53 +04:00
|
|
|
end
|
|
|
|
|
|
|
|
let get state id =
|
|
|
|
Shared.use state.global_data begin fun data ->
|
|
|
|
try return (Net_id.Table.find data.nets id)
|
|
|
|
with Not_found -> fail (Unknown_network id)
|
|
|
|
end
|
|
|
|
|
|
|
|
let all state =
|
|
|
|
Shared.use state.global_data begin fun { nets } ->
|
|
|
|
Lwt.return @@
|
|
|
|
Net_id.Table.fold (fun _ net acc -> net :: acc) nets []
|
|
|
|
end
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let id { net_id } = net_id
|
2017-02-24 20:17:53 +04:00
|
|
|
let genesis { genesis } = genesis
|
2017-11-11 06:34:12 +04:00
|
|
|
let faked_genesis_hash { faked_genesis_hash } = faked_genesis_hash
|
2017-02-24 20:17:53 +04:00
|
|
|
let expiration { expiration } = expiration
|
2017-04-10 23:14:17 +04:00
|
|
|
let allow_forked_network { allow_forked_network } = allow_forked_network
|
2017-09-29 20:43:13 +04:00
|
|
|
let global_state { global_state } = global_state
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
let destroy state net =
|
|
|
|
lwt_debug "destroy %a" Net_id.pp (id net) >>= fun () ->
|
|
|
|
Shared.use state.global_data begin fun { global_store ; nets } ->
|
|
|
|
Net_id.Table.remove nets (id net) ;
|
|
|
|
Store.Net.destroy global_store (id net) >>= fun () ->
|
|
|
|
Lwt.return_unit
|
|
|
|
end
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
module Block = struct
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
type t = block = {
|
|
|
|
net_state: Net.t ;
|
|
|
|
hash: Block_hash.t ;
|
|
|
|
contents: Store.Block.contents ;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
2017-04-19 23:46:10 +04:00
|
|
|
type block = t
|
|
|
|
|
|
|
|
let compare b1 b2 = Block_hash.compare b1.hash b2.hash
|
|
|
|
let equal b1 b2 = Block_hash.equal b1.hash b2.hash
|
|
|
|
|
|
|
|
let hash { hash } = hash
|
|
|
|
let header { contents = { header } } = header
|
2017-09-29 20:43:13 +04:00
|
|
|
let net_state { net_state } = net_state
|
2017-11-14 06:14:26 +04:00
|
|
|
let net_id { net_state = { net_id } } = net_id
|
2017-04-19 23:46:10 +04:00
|
|
|
let shell_header { contents = { header = { shell } } } = shell
|
|
|
|
let timestamp b = (shell_header b).timestamp
|
|
|
|
let fitness b = (shell_header b).fitness
|
|
|
|
let level b = (shell_header b).level
|
|
|
|
let proto_level b = (shell_header b).proto_level
|
2017-09-29 20:43:13 +04:00
|
|
|
let validation_passes b = (shell_header b).validation_passes
|
2017-04-19 23:46:10 +04:00
|
|
|
let message { contents = { message } } = message
|
2017-04-20 10:49:14 +04:00
|
|
|
let max_operations_ttl { contents = { max_operations_ttl } } =
|
|
|
|
max_operations_ttl
|
2017-11-19 17:38:36 +04:00
|
|
|
let max_number_of_operations { contents = { max_number_of_operations } } =
|
|
|
|
max_number_of_operations
|
|
|
|
let max_operation_data_length { contents = { max_operation_data_length } } =
|
|
|
|
max_operation_data_length
|
2017-04-19 23:46:10 +04:00
|
|
|
|
2017-11-11 06:34:12 +04:00
|
|
|
let is_genesis b = Block_hash.equal b.hash b.net_state.genesis.block
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let known_valid net_state hash =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Contents.known (store, hash)
|
|
|
|
end
|
|
|
|
let known_invalid net_state hash =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Invalid_block.known store hash
|
|
|
|
end
|
2017-10-31 20:59:02 +04:00
|
|
|
let read_invalid net_state hash =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Invalid_block.read_opt store hash
|
|
|
|
end
|
2017-11-27 01:21:56 +04:00
|
|
|
let list_invalid net_state =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Invalid_block.fold store ~init:[]
|
|
|
|
~f:(fun hash { level ; errors } acc ->
|
|
|
|
Lwt.return ((hash, level, errors) :: acc))
|
|
|
|
end
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-09-29 20:43:13 +04:00
|
|
|
let known net_state hash =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Contents.known (store, hash) >>= fun known ->
|
|
|
|
if known then
|
|
|
|
Lwt.return_true
|
|
|
|
else
|
|
|
|
Store.Block.Invalid_block.known store hash
|
|
|
|
end
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let read net_state hash =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Contents.read (store, hash) >>=? fun contents ->
|
|
|
|
return { net_state ; hash ; contents }
|
|
|
|
end
|
|
|
|
let read_opt net_state hash =
|
|
|
|
read net_state hash >>= function
|
|
|
|
| Error _ -> Lwt.return None
|
|
|
|
| Ok v -> Lwt.return (Some v)
|
|
|
|
let read_exn net_state hash =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Contents.read_exn (store, hash) >>= fun contents ->
|
|
|
|
Lwt.return { net_state ; hash ; contents }
|
|
|
|
end
|
|
|
|
|
|
|
|
(* Quick accessor to be optimized ?? *)
|
|
|
|
let read_predecessor net_state hash =
|
|
|
|
read net_state hash >>=? fun { contents = { header } } ->
|
|
|
|
return header.shell.predecessor
|
|
|
|
let read_predecessor_opt net_state hash =
|
|
|
|
read_predecessor net_state hash >>= function
|
|
|
|
| Error _ -> Lwt.return None
|
|
|
|
| Ok v -> Lwt.return (Some v)
|
|
|
|
let read_predecessor_exn net_state hash =
|
|
|
|
read_exn net_state hash >>= fun { contents = { header } } ->
|
|
|
|
Lwt.return header.shell.predecessor
|
|
|
|
|
|
|
|
let predecessor { net_state ; contents = { header } ; hash } =
|
|
|
|
if Block_hash.equal hash header.shell.predecessor then
|
|
|
|
Lwt.return_none
|
|
|
|
else
|
|
|
|
read_exn net_state header.shell.predecessor >>= fun block ->
|
|
|
|
Lwt.return (Some block)
|
|
|
|
|
2017-12-05 18:19:25 +04:00
|
|
|
type error += Inconsistent_hash of Context_hash.t * Context_hash.t
|
|
|
|
|
|
|
|
let () =
|
|
|
|
Error_monad.register_error_kind
|
|
|
|
`Permanent
|
|
|
|
~id:"inconsistentContextHash"
|
|
|
|
~title:"Inconsistent commit hash"
|
|
|
|
~description:
|
|
|
|
"When commiting the context of a block, the announced context \
|
|
|
|
hash was not the one computed at commit time."
|
|
|
|
Data_encoding.(obj2
|
|
|
|
(req "wrong_context_hash" Context_hash.encoding)
|
|
|
|
(req "expected_context_hash" Context_hash.encoding))
|
|
|
|
(function Inconsistent_hash (got, exp) -> Some (got, exp) | _ -> None)
|
|
|
|
(fun (got, exp) -> Inconsistent_hash (got, exp))
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let store
|
|
|
|
net_state block_header operations
|
2017-12-05 18:18:05 +04:00
|
|
|
{ Updater.context ; message ; max_operations_ttl ;
|
2017-11-19 17:38:36 +04:00
|
|
|
max_number_of_operations ; max_operation_data_length } =
|
2017-04-19 23:46:10 +04:00
|
|
|
let bytes = Block_header.to_bytes block_header in
|
|
|
|
let hash = Block_header.hash_raw bytes in
|
|
|
|
(* let's the validator check the consistency... of fitness, level, ... *)
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Invalid_block.known store hash >>= fun known_invalid ->
|
|
|
|
fail_when known_invalid (failure "Known invalid") >>=? fun () ->
|
|
|
|
Store.Block.Contents.known (store, hash) >>= fun known ->
|
|
|
|
if known then
|
2017-07-17 17:59:09 +04:00
|
|
|
return None
|
2017-04-19 23:46:10 +04:00
|
|
|
else begin
|
2017-07-17 17:59:09 +04:00
|
|
|
Context.commit
|
2017-12-05 18:18:05 +04:00
|
|
|
~time:block_header.shell.timestamp ?message context >>= fun commit ->
|
2017-12-05 18:19:25 +04:00
|
|
|
fail_unless
|
|
|
|
(Context_hash.equal block_header.shell.context commit)
|
|
|
|
(Inconsistent_hash (commit, block_header.shell.context)) >>=? fun () ->
|
2017-07-17 17:59:09 +04:00
|
|
|
let contents = {
|
|
|
|
Store.Block.header = block_header ;
|
|
|
|
message ;
|
|
|
|
max_operations_ttl ;
|
2017-11-19 17:38:36 +04:00
|
|
|
max_number_of_operations ;
|
|
|
|
max_operation_data_length ;
|
2017-07-17 17:59:09 +04:00
|
|
|
context = commit ;
|
|
|
|
} in
|
2017-04-19 23:46:10 +04:00
|
|
|
Store.Block.Contents.store (store, hash) contents >>= fun () ->
|
|
|
|
let hashes = List.map (List.map Operation.hash) operations in
|
|
|
|
let list_hashes = List.map Operation_list_hash.compute hashes in
|
|
|
|
Lwt_list.iteri_p
|
|
|
|
(fun i hashes ->
|
|
|
|
let path = Operation_list_list_hash.compute_path list_hashes i in
|
|
|
|
Store.Block.Operation_hashes.store
|
|
|
|
(store, hash) i hashes >>= fun () ->
|
|
|
|
Store.Block.Operation_path.store (store, hash) i path)
|
|
|
|
hashes >>= fun () ->
|
|
|
|
Lwt_list.iteri_p
|
|
|
|
(fun i ops -> Store.Block.Operations.store (store, hash) i ops)
|
|
|
|
operations >>= fun () ->
|
2017-07-17 17:59:09 +04:00
|
|
|
(* Update the chain state. *)
|
|
|
|
Shared.use net_state.chain_state begin fun chain_state ->
|
|
|
|
let store = chain_state.chain_store in
|
|
|
|
let predecessor = block_header.shell.predecessor in
|
|
|
|
Store.Chain.Known_heads.remove store predecessor >>= fun () ->
|
|
|
|
Store.Chain.Known_heads.store store hash
|
|
|
|
end >>= fun () ->
|
|
|
|
let block = { net_state ; hash ; contents } in
|
2017-11-27 09:13:12 +04:00
|
|
|
Lwt_watcher.notify net_state.block_watcher block ;
|
2017-07-17 17:59:09 +04:00
|
|
|
return (Some block)
|
2017-04-19 23:46:10 +04:00
|
|
|
end
|
2017-07-17 17:59:09 +04:00
|
|
|
end
|
2017-04-19 23:46:10 +04:00
|
|
|
|
2017-10-31 20:59:02 +04:00
|
|
|
let store_invalid net_state block_header errors =
|
2017-04-19 23:46:10 +04:00
|
|
|
let bytes = Block_header.to_bytes block_header in
|
|
|
|
let hash = Block_header.hash_raw bytes in
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Contents.known (store, hash) >>= fun known_valid ->
|
|
|
|
fail_when known_valid (failure "Known valid") >>=? fun () ->
|
|
|
|
Store.Block.Invalid_block.known store hash >>= fun known_invalid ->
|
|
|
|
if known_invalid then
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
Store.Block.Invalid_block.store store hash
|
2017-10-31 20:59:02 +04:00
|
|
|
{ level = block_header.shell.level ; errors } >>= fun () ->
|
2017-04-19 23:46:10 +04:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
let watcher net_state =
|
2017-11-27 09:13:12 +04:00
|
|
|
Lwt_watcher.create_stream net_state.block_watcher
|
2017-04-19 23:46:10 +04:00
|
|
|
|
|
|
|
let operation_hashes { net_state ; hash ; contents } i =
|
2017-09-29 20:43:13 +04:00
|
|
|
if i < 0 || contents.header.shell.validation_passes <= i then
|
2017-04-19 23:46:10 +04:00
|
|
|
invalid_arg "State.Block.operations" ;
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Operation_hashes.read_exn (store, hash) i >>= fun hashes ->
|
|
|
|
Store.Block.Operation_path.read_exn (store, hash) i >>= fun path ->
|
|
|
|
Lwt.return (hashes, path)
|
|
|
|
end
|
|
|
|
|
|
|
|
let all_operation_hashes { net_state ; hash ; contents } =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Lwt_list.map_p
|
|
|
|
(Store.Block.Operation_hashes.read_exn (store, hash))
|
2017-09-29 20:43:13 +04:00
|
|
|
(0 -- (contents.header.shell.validation_passes - 1))
|
2017-04-19 23:46:10 +04:00
|
|
|
end
|
|
|
|
|
|
|
|
let operations { net_state ; hash ; contents } i =
|
2017-09-29 20:43:13 +04:00
|
|
|
if i < 0 || contents.header.shell.validation_passes <= i then
|
2017-04-19 23:46:10 +04:00
|
|
|
invalid_arg "State.Block.operations" ;
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Store.Block.Operation_path.read_exn (store, hash) i >>= fun path ->
|
|
|
|
Store.Block.Operations.read_exn (store, hash) i >>= fun ops ->
|
|
|
|
Lwt.return (ops, path)
|
|
|
|
end
|
|
|
|
|
|
|
|
let all_operations { net_state ; hash ; contents } =
|
|
|
|
Shared.use net_state.block_store begin fun store ->
|
|
|
|
Lwt_list.map_p
|
|
|
|
(fun i -> Store.Block.Operations.read_exn (store, hash) i)
|
2017-09-29 20:43:13 +04:00
|
|
|
(0 -- (contents.header.shell.validation_passes - 1))
|
2017-04-19 23:46:10 +04:00
|
|
|
end
|
|
|
|
|
|
|
|
let context { net_state ; hash } =
|
2017-07-17 17:59:09 +04:00
|
|
|
Shared.use net_state.block_store begin fun block_store ->
|
|
|
|
Store.Block.Contents.read_exn (block_store, hash)
|
2017-12-05 18:19:25 +04:00
|
|
|
end >>= fun { context = commit } ->
|
2017-04-19 23:46:10 +04:00
|
|
|
Shared.use net_state.context_index begin fun context_index ->
|
2017-07-17 17:59:09 +04:00
|
|
|
Context.checkout_exn context_index commit
|
2017-04-19 23:46:10 +04:00
|
|
|
end
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-07-17 17:59:09 +04:00
|
|
|
let protocol_hash block =
|
|
|
|
context block >>= fun context ->
|
|
|
|
Context.get_protocol context
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-07-17 17:59:09 +04:00
|
|
|
let test_network block =
|
|
|
|
context block >>= fun context ->
|
|
|
|
Context.get_test_network context
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let read_block { global_data } hash =
|
|
|
|
Shared.use global_data begin fun { nets } ->
|
|
|
|
Net_id.Table.fold
|
|
|
|
(fun _net_id net_state acc ->
|
|
|
|
acc >>= function
|
|
|
|
| Some _ -> acc
|
|
|
|
| None ->
|
|
|
|
Block.read_opt net_state hash >>= function
|
|
|
|
| None -> acc
|
|
|
|
| Some block -> Lwt.return (Some block))
|
|
|
|
nets
|
|
|
|
Lwt.return_none
|
|
|
|
end
|
|
|
|
|
|
|
|
let read_block_exn t hash =
|
|
|
|
read_block t hash >>= function
|
|
|
|
| None -> Lwt.fail Not_found
|
|
|
|
| Some b -> Lwt.return b
|
|
|
|
|
2017-09-29 20:43:13 +04:00
|
|
|
let fork_testnet block protocol expiration =
|
|
|
|
Shared.use block.net_state.global_state.global_data begin fun data ->
|
2017-04-19 23:46:10 +04:00
|
|
|
Block.context block >>= fun context ->
|
|
|
|
Context.set_test_network context Not_running >>= fun context ->
|
|
|
|
Context.set_protocol context protocol >>= fun context ->
|
|
|
|
Context.commit_test_network_genesis
|
2017-07-17 17:59:09 +04:00
|
|
|
data.context_index block.hash block.contents.header.shell.timestamp
|
|
|
|
context >>=? fun (net_id, genesis, commit) ->
|
2017-04-19 23:46:10 +04:00
|
|
|
let genesis = {
|
|
|
|
block = genesis ;
|
|
|
|
time = Time.add block.contents.header.shell.timestamp 1L ;
|
|
|
|
protocol ;
|
|
|
|
} in
|
2017-09-29 20:43:13 +04:00
|
|
|
Net.locked_create block.net_state.global_state data
|
2017-07-17 17:59:09 +04:00
|
|
|
net_id ~expiration genesis commit >>= fun net ->
|
2017-04-19 23:46:10 +04:00
|
|
|
return net
|
|
|
|
end
|
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
module Protocol = struct
|
|
|
|
|
2017-11-27 09:13:12 +04:00
|
|
|
include Protocol
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let known global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.Contents.known store hash
|
|
|
|
end
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let read global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.Contents.read store hash
|
|
|
|
end
|
|
|
|
let read_opt global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.Contents.read_opt store hash
|
|
|
|
end
|
|
|
|
let read_exn global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.Contents.read_exn store hash
|
|
|
|
end
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let read_raw global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.RawContents.read (store, hash)
|
|
|
|
end
|
|
|
|
let read_raw_opt global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.RawContents.read_opt (store, hash)
|
|
|
|
end
|
|
|
|
let read_raw_exn global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.RawContents.read_exn (store, hash)
|
|
|
|
end
|
|
|
|
|
|
|
|
let store global_state p =
|
|
|
|
let bytes = Protocol.to_bytes p in
|
|
|
|
let hash = Protocol.hash_raw bytes in
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.Contents.known store hash >>= fun known ->
|
|
|
|
if known then
|
|
|
|
Lwt.return None
|
|
|
|
else
|
|
|
|
Store.Protocol.RawContents.store (store, hash) bytes >>= fun () ->
|
|
|
|
Lwt.return (Some hash)
|
|
|
|
end
|
|
|
|
|
|
|
|
let remove global_state hash =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.Contents.known store hash >>= fun known ->
|
|
|
|
if known then
|
|
|
|
Lwt.return_false
|
|
|
|
else
|
|
|
|
Store.Protocol.Contents.remove store hash >>= fun () ->
|
|
|
|
Lwt.return_true
|
|
|
|
end
|
|
|
|
|
|
|
|
let list global_state =
|
|
|
|
Shared.use global_state.protocol_store begin fun store ->
|
|
|
|
Store.Protocol.Contents.fold_keys store
|
|
|
|
~init:Protocol_hash.Set.empty
|
|
|
|
~f:(fun x acc -> Lwt.return (Protocol_hash.Set.add x acc))
|
|
|
|
end
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2017-10-09 12:55:12 +04:00
|
|
|
module Registred_protocol = struct
|
|
|
|
|
|
|
|
module type T = sig
|
|
|
|
val hash: Protocol_hash.t
|
2017-10-27 20:53:07 +04:00
|
|
|
include Updater.NODE_PROTOCOL
|
2017-10-09 12:55:12 +04:00
|
|
|
val complete_b58prefix : Context.t -> string -> string list Lwt.t
|
|
|
|
end
|
|
|
|
|
2017-11-10 23:30:29 +04:00
|
|
|
type t = (module T)
|
|
|
|
|
2017-10-09 12:55:12 +04:00
|
|
|
let build_v1 hash =
|
2017-10-29 01:14:00 +04:00
|
|
|
let (module F) = Tezos_protocol_compiler.Registerer.get_exn hash in
|
2017-10-09 12:55:12 +04:00
|
|
|
let module Name = struct
|
|
|
|
let name = Protocol_hash.to_b58check hash
|
|
|
|
end in
|
|
|
|
let module Env = Tezos_protocol_environment.Make(Name)() in
|
|
|
|
(module struct
|
|
|
|
let hash = hash
|
|
|
|
module P = F(Env)
|
|
|
|
include P
|
2017-10-27 20:53:07 +04:00
|
|
|
include Updater.LiftProtocol(Name)(Env)(P)
|
2017-10-09 12:55:12 +04:00
|
|
|
let complete_b58prefix = Env.Context.complete
|
|
|
|
end : T)
|
|
|
|
|
|
|
|
module VersionTable = Protocol_hash.Table
|
|
|
|
|
|
|
|
let versions : (module T) VersionTable.t =
|
|
|
|
VersionTable.create 20
|
|
|
|
|
|
|
|
let mem hash =
|
2017-11-10 23:30:29 +04:00
|
|
|
VersionTable.mem versions hash ||
|
|
|
|
Tezos_protocol_compiler.Registerer.mem hash
|
2017-10-09 12:55:12 +04:00
|
|
|
|
|
|
|
let get_exn hash =
|
|
|
|
try VersionTable.find versions hash
|
|
|
|
with Not_found ->
|
|
|
|
let proto = build_v1 hash in
|
|
|
|
VersionTable.add versions hash proto ;
|
|
|
|
proto
|
|
|
|
|
|
|
|
let get hash =
|
|
|
|
try Some (get_exn hash)
|
|
|
|
with Not_found -> None
|
|
|
|
|
2017-10-27 20:53:07 +04:00
|
|
|
end
|
|
|
|
|
|
|
|
module Register_embedded_protocol
|
|
|
|
(Env : Updater.Node_protocol_environment_sigs.V1)
|
|
|
|
(Proto : Env.Updater.PROTOCOL)
|
|
|
|
(Source : sig
|
|
|
|
val hash: Protocol_hash.t option
|
2017-11-27 09:13:12 +04:00
|
|
|
val sources: Protocol.t
|
2017-10-27 20:53:07 +04:00
|
|
|
end) = struct
|
|
|
|
|
|
|
|
let () =
|
|
|
|
let hash =
|
|
|
|
match Source.hash with
|
2017-11-27 09:13:12 +04:00
|
|
|
| None -> Protocol.hash Source.sources
|
2017-10-27 20:53:07 +04:00
|
|
|
| Some hash -> hash in
|
|
|
|
let module Name = struct
|
|
|
|
let name = Protocol_hash.to_b58check hash
|
|
|
|
end in
|
|
|
|
(* TODO add a memory table for "embedded" sources... *)
|
|
|
|
Registred_protocol.VersionTable.add
|
|
|
|
Registred_protocol.versions hash
|
|
|
|
(module struct
|
|
|
|
let hash = hash
|
|
|
|
include Proto
|
|
|
|
include Updater.LiftProtocol(Name)(Env)(Proto)
|
|
|
|
let complete_b58prefix = Env.Context.complete
|
|
|
|
end : Registred_protocol.T)
|
|
|
|
|
2017-10-09 12:55:12 +04:00
|
|
|
end
|
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
let read
|
2017-11-13 19:34:00 +04:00
|
|
|
?patch_context
|
|
|
|
~store_root
|
|
|
|
~context_root
|
|
|
|
() =
|
2017-04-19 23:46:10 +04:00
|
|
|
Store.init store_root >>=? fun global_store ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Context.init ?patch_context ~root:context_root >>= fun context_index ->
|
|
|
|
let global_data = {
|
|
|
|
nets = Net_id.Table.create 17 ;
|
2017-04-19 23:46:10 +04:00
|
|
|
global_store ;
|
|
|
|
context_index ;
|
2017-02-24 20:17:53 +04:00
|
|
|
} in
|
|
|
|
let state = {
|
|
|
|
global_data = Shared.create global_data ;
|
2017-04-19 23:46:10 +04:00
|
|
|
protocol_store = Shared.create @@ Store.Protocol.get global_store ;
|
2017-02-24 20:17:53 +04:00
|
|
|
} in
|
|
|
|
Net.read_all state >>=? fun () ->
|
|
|
|
return state
|
2017-05-31 20:27:11 +04:00
|
|
|
|
|
|
|
let close { global_data } =
|
|
|
|
Shared.use global_data begin fun { global_store } ->
|
|
|
|
Store.close global_store ;
|
|
|
|
Lwt.return_unit
|
|
|
|
end
|