2016-09-08 21:13:10 +04:00
|
|
|
(**************************************************************************)
|
|
|
|
(* *)
|
|
|
|
(* Copyright (c) 2014 - 2016. *)
|
|
|
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
|
|
(* *)
|
|
|
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
open Lwt.Infix
|
2016-09-08 21:13:10 +04:00
|
|
|
open Logging.Node.Worker
|
|
|
|
|
|
|
|
let inject_operation validator ?force bytes =
|
|
|
|
let t =
|
2017-04-19 21:21:23 +04:00
|
|
|
match Data_encoding.Binary.of_bytes Operation.encoding bytes with
|
2016-09-08 21:13:10 +04:00
|
|
|
| None -> failwith "Can't parse the operation"
|
|
|
|
| Some operation ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Validator.get
|
|
|
|
validator operation.shell.net_id >>=? fun net_validator ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let pv = Validator.prevalidator net_validator in
|
|
|
|
Prevalidator.inject_operation pv ?force operation in
|
|
|
|
let hash = Operation_hash.hash_bytes [bytes] in
|
|
|
|
Lwt.return (hash, t)
|
|
|
|
|
2016-10-21 16:01:20 +04:00
|
|
|
let inject_protocol state ?force:_ proto =
|
2017-02-24 20:17:53 +04:00
|
|
|
let proto_bytes =
|
2017-04-19 21:21:23 +04:00
|
|
|
Data_encoding.Binary.to_bytes Protocol.encoding proto in
|
2016-10-21 16:01:20 +04:00
|
|
|
let hash = Protocol_hash.hash_bytes [proto_bytes] in
|
2017-02-24 20:17:53 +04:00
|
|
|
let validation =
|
|
|
|
Updater.compile hash proto >>= function
|
|
|
|
| false ->
|
|
|
|
failwith
|
|
|
|
"Compilation failed (%a)"
|
|
|
|
Protocol_hash.pp_short hash
|
2016-10-24 16:10:17 +04:00
|
|
|
| true ->
|
2017-04-19 23:46:10 +04:00
|
|
|
State.Protocol.store state proto >>= function
|
|
|
|
| None ->
|
2017-02-24 20:17:53 +04:00
|
|
|
failwith
|
|
|
|
"Previously registred protocol (%a)"
|
|
|
|
Protocol_hash.pp_short hash
|
2017-04-19 23:46:10 +04:00
|
|
|
| Some _ -> return ()
|
2016-10-24 16:10:17 +04:00
|
|
|
in
|
|
|
|
Lwt.return (hash, validation)
|
2016-10-21 16:01:20 +04:00
|
|
|
|
2017-03-30 15:16:21 +04:00
|
|
|
let inject_block validator ?force bytes operations =
|
|
|
|
Validator.inject_block
|
|
|
|
validator ?force
|
|
|
|
bytes operations >>=? fun (hash, block) ->
|
2017-02-24 20:17:53 +04:00
|
|
|
return (hash, (block >>=? fun _ -> return ()))
|
2016-10-21 16:01:20 +04:00
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
type t = {
|
|
|
|
state: State.t ;
|
2017-02-24 20:17:53 +04:00
|
|
|
distributed_db: Distributed_db.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
validator: Validator.worker ;
|
2017-04-19 23:46:10 +04:00
|
|
|
mainnet_db: Distributed_db.net_db ;
|
2017-02-28 03:48:22 +04:00
|
|
|
mainnet_net: State.Net.t ;
|
|
|
|
mainnet_validator: Validator.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
inject_block:
|
2017-03-30 15:16:21 +04:00
|
|
|
?force:bool ->
|
2017-04-19 23:46:10 +04:00
|
|
|
MBytes.t -> Distributed_db.operation list list ->
|
2017-02-24 20:17:53 +04:00
|
|
|
(Block_hash.t * unit tzresult Lwt.t) tzresult Lwt.t ;
|
2016-09-08 21:13:10 +04:00
|
|
|
inject_operation:
|
2017-02-24 20:17:53 +04:00
|
|
|
?force:bool -> MBytes.t ->
|
|
|
|
(Operation_hash.t * unit tzresult Lwt.t) Lwt.t ;
|
2016-10-21 16:01:20 +04:00
|
|
|
inject_protocol:
|
2017-04-19 21:21:23 +04:00
|
|
|
?force:bool -> Protocol.t ->
|
2017-02-24 20:17:53 +04:00
|
|
|
(Protocol_hash.t * unit tzresult Lwt.t) Lwt.t ;
|
|
|
|
p2p: Distributed_db.p2p ; (* For P2P RPCs *)
|
2016-09-08 21:13:10 +04:00
|
|
|
shutdown: unit -> unit Lwt.t ;
|
|
|
|
}
|
|
|
|
|
|
|
|
let init_p2p net_params =
|
|
|
|
match net_params with
|
|
|
|
| None ->
|
|
|
|
lwt_log_notice "P2P layer is disabled" >>= fun () ->
|
2017-04-18 20:13:46 +04:00
|
|
|
Error_monad.return (P2p.faked_network Distributed_db_metadata.cfg)
|
2016-09-08 21:13:10 +04:00
|
|
|
| Some (config, limits) ->
|
|
|
|
lwt_log_notice "bootstraping network..." >>= fun () ->
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.create
|
|
|
|
~config ~limits
|
|
|
|
Distributed_db_metadata.cfg
|
2017-04-18 20:13:46 +04:00
|
|
|
Distributed_db_message.cfg >>=? fun p2p ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Lwt.async (fun () -> P2p.maintain p2p) ;
|
2017-04-18 20:13:46 +04:00
|
|
|
Error_monad.return p2p
|
2017-01-23 14:10:02 +04:00
|
|
|
|
2017-01-23 14:10:07 +04:00
|
|
|
type config = {
|
2017-02-24 20:17:53 +04:00
|
|
|
genesis: State.Net.genesis ;
|
2017-01-23 14:10:07 +04:00
|
|
|
store_root: string ;
|
|
|
|
context_root: string ;
|
|
|
|
patch_context: (Context.t -> Context.t Lwt.t) option ;
|
|
|
|
p2p: (P2p.config * P2p.limits) option ;
|
2017-04-10 23:14:17 +04:00
|
|
|
test_network_max_tll: int option ;
|
2017-01-23 14:10:07 +04:00
|
|
|
}
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-10 23:14:17 +04:00
|
|
|
let may_create_net state genesis =
|
2017-03-31 15:04:05 +04:00
|
|
|
State.Net.get state (Net_id.of_block_hash genesis.State.Net.block) >>= function
|
2017-03-02 18:08:42 +04:00
|
|
|
| Ok net -> Lwt.return net
|
|
|
|
| Error _ ->
|
2017-04-10 23:14:17 +04:00
|
|
|
State.Net.create state genesis
|
2017-03-02 18:08:42 +04:00
|
|
|
|
|
|
|
|
2017-01-23 14:10:07 +04:00
|
|
|
let create { genesis ; store_root ; context_root ;
|
2017-04-10 23:14:17 +04:00
|
|
|
patch_context ; p2p = net_params ;
|
|
|
|
test_network_max_tll = max_ttl } =
|
2017-04-18 20:13:46 +04:00
|
|
|
init_p2p net_params >>=? fun p2p ->
|
2016-09-08 21:13:10 +04:00
|
|
|
State.read
|
2017-02-24 20:17:53 +04:00
|
|
|
~store_root ~context_root ?patch_context () >>=? fun state ->
|
|
|
|
let distributed_db = Distributed_db.create state p2p in
|
2017-04-10 23:14:17 +04:00
|
|
|
let validator =
|
|
|
|
Validator.create_worker ?max_ttl state distributed_db in
|
|
|
|
may_create_net state genesis >>= fun mainnet_net ->
|
2017-02-28 03:48:22 +04:00
|
|
|
Validator.activate validator mainnet_net >>= fun mainnet_validator ->
|
|
|
|
let mainnet_db = Validator.net_db mainnet_validator in
|
2016-09-08 21:13:10 +04:00
|
|
|
let shutdown () =
|
2017-05-31 20:27:11 +04:00
|
|
|
State.close state >>= fun () ->
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.shutdown p2p >>= fun () ->
|
|
|
|
Validator.shutdown validator >>= fun () ->
|
|
|
|
Lwt.return_unit
|
2016-09-08 21:13:10 +04:00
|
|
|
in
|
|
|
|
return {
|
|
|
|
state ;
|
2017-02-24 20:17:53 +04:00
|
|
|
distributed_db ;
|
2016-09-08 21:13:10 +04:00
|
|
|
validator ;
|
2017-02-28 03:48:22 +04:00
|
|
|
mainnet_db ;
|
|
|
|
mainnet_net ;
|
|
|
|
mainnet_validator ;
|
2017-02-24 20:17:53 +04:00
|
|
|
inject_block = inject_block validator ;
|
2016-09-08 21:13:10 +04:00
|
|
|
inject_operation = inject_operation validator ;
|
2016-10-21 16:01:20 +04:00
|
|
|
inject_protocol = inject_protocol state ;
|
2017-02-17 22:12:06 +04:00
|
|
|
p2p ;
|
2016-09-08 21:13:10 +04:00
|
|
|
shutdown ;
|
|
|
|
}
|
|
|
|
|
|
|
|
let shutdown node = node.shutdown ()
|
|
|
|
|
|
|
|
module RPC = struct
|
|
|
|
|
|
|
|
type block = Node_rpc_services.Blocks.block
|
|
|
|
type block_info = Node_rpc_services.Blocks.block_info = {
|
|
|
|
hash: Block_hash.t ;
|
2017-04-10 19:06:11 +04:00
|
|
|
net_id: Net_id.t ;
|
2017-04-10 15:01:22 +04:00
|
|
|
level: Int32.t ;
|
2017-04-12 20:22:40 +04:00
|
|
|
proto_level: int ; (* uint8 *)
|
2016-09-08 21:13:10 +04:00
|
|
|
predecessor: Block_hash.t ;
|
|
|
|
timestamp: Time.t ;
|
2017-03-30 15:16:21 +04:00
|
|
|
operations_hash: Operation_list_list_hash.t ;
|
2017-04-10 19:06:11 +04:00
|
|
|
fitness: MBytes.t list ;
|
|
|
|
data: MBytes.t ;
|
2017-03-30 15:16:21 +04:00
|
|
|
operations: Operation_hash.t list list option ;
|
2017-04-10 19:06:11 +04:00
|
|
|
protocol: Protocol_hash.t ;
|
2017-04-10 23:14:17 +04:00
|
|
|
test_network: Context.test_network;
|
2016-09-08 21:13:10 +04:00
|
|
|
}
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let convert (block: State.Block.t) =
|
|
|
|
let hash = State.Block.hash block in
|
|
|
|
let header = State.Block.header block in
|
|
|
|
State.Block.all_operation_hashes block >>= fun operations ->
|
|
|
|
State.Block.context block >>= fun context ->
|
|
|
|
Context.get_protocol context >>= fun protocol ->
|
|
|
|
Context.get_test_network context >>= fun test_network ->
|
2017-04-14 02:05:36 +04:00
|
|
|
Lwt.return {
|
2017-04-19 23:46:10 +04:00
|
|
|
hash ;
|
|
|
|
net_id = header.shell.net_id ;
|
|
|
|
level = header.shell.level ;
|
|
|
|
proto_level = header.shell.proto_level ;
|
|
|
|
predecessor = header.shell.predecessor ;
|
|
|
|
timestamp = header.shell.timestamp ;
|
|
|
|
operations_hash = header.shell.operations_hash ;
|
|
|
|
fitness = header.shell.fitness ;
|
|
|
|
data = header.proto ;
|
2017-04-14 02:05:36 +04:00
|
|
|
operations = Some operations ;
|
2017-04-19 23:46:10 +04:00
|
|
|
protocol ;
|
|
|
|
test_network ;
|
2017-04-14 02:05:36 +04:00
|
|
|
}
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let inject_block node = node.inject_block
|
|
|
|
let inject_operation node = node.inject_operation
|
2016-10-21 16:01:20 +04:00
|
|
|
let inject_protocol node = node.inject_protocol
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let raw_block_info node hash =
|
2017-04-19 23:46:10 +04:00
|
|
|
State.read_block node.state hash >>= function
|
|
|
|
| Some block ->
|
2017-04-14 02:05:36 +04:00
|
|
|
convert block
|
2017-02-24 20:17:53 +04:00
|
|
|
| None ->
|
|
|
|
Lwt.fail Not_found
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let prevalidation_hash =
|
2017-04-05 11:54:21 +04:00
|
|
|
Block_hash.of_b58check_exn
|
2017-02-19 21:22:32 +04:00
|
|
|
"BLockPrevaLidationPrevaLidationPrevaLidationPrZ4mr6"
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let get_net node = function
|
2017-02-24 20:17:53 +04:00
|
|
|
| `Genesis | `Head _ | `Prevalidation ->
|
2017-02-28 03:48:22 +04:00
|
|
|
node.mainnet_validator, node.mainnet_db
|
2016-09-08 21:13:10 +04:00
|
|
|
| `Test_head _ | `Test_prevalidation ->
|
2017-02-28 03:48:22 +04:00
|
|
|
match Validator.test_validator node.mainnet_validator with
|
2016-09-08 21:13:10 +04:00
|
|
|
| None -> raise Not_found
|
|
|
|
| Some v -> v
|
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
let get_validator node = function
|
2017-02-28 03:48:22 +04:00
|
|
|
| `Genesis | `Head _ | `Prevalidation -> node.mainnet_validator
|
2017-02-24 20:17:53 +04:00
|
|
|
| `Test_head _ | `Test_prevalidation ->
|
2017-02-28 03:48:22 +04:00
|
|
|
match Validator.test_validator node.mainnet_validator with
|
2017-02-24 20:17:53 +04:00
|
|
|
| None -> raise Not_found
|
|
|
|
| Some (v, _) -> v
|
|
|
|
|
|
|
|
let get_validator_per_hash node hash =
|
2017-04-19 23:46:10 +04:00
|
|
|
State.read_block_exn node.state hash >>= fun block ->
|
|
|
|
let header = State.Block.header block in
|
2017-03-31 15:04:05 +04:00
|
|
|
if Net_id.equal
|
2017-02-28 03:48:22 +04:00
|
|
|
(State.Net.id node.mainnet_net)
|
2017-04-19 23:46:10 +04:00
|
|
|
header.shell.net_id then
|
2017-02-28 03:48:22 +04:00
|
|
|
Lwt.return (Some (node.mainnet_validator, node.mainnet_db))
|
2017-02-24 20:17:53 +04:00
|
|
|
else
|
2017-02-28 03:48:22 +04:00
|
|
|
match Validator.test_validator node.mainnet_validator with
|
2017-02-24 20:17:53 +04:00
|
|
|
| Some (test_validator, net_db)
|
2017-03-31 15:04:05 +04:00
|
|
|
when Net_id.equal
|
2017-02-24 20:17:53 +04:00
|
|
|
(State.Net.id (Validator.net_state test_validator))
|
2017-04-19 23:46:10 +04:00
|
|
|
header.shell.net_id ->
|
2017-02-28 03:48:22 +04:00
|
|
|
Lwt.return (Some (node.mainnet_validator, net_db))
|
2017-02-24 20:17:53 +04:00
|
|
|
| _ -> Lwt.return_none
|
|
|
|
|
|
|
|
let read_valid_block node h =
|
2017-04-19 23:46:10 +04:00
|
|
|
State.read_block node.state h
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
let read_valid_block_exn node h =
|
2017-04-19 23:46:10 +04:00
|
|
|
State.read_block_exn node.state h
|
|
|
|
|
|
|
|
let rec predecessor net_db n v =
|
2017-02-24 20:17:53 +04:00
|
|
|
if n <= 0 then
|
|
|
|
Lwt.return v
|
|
|
|
else
|
2017-04-19 23:46:10 +04:00
|
|
|
State.Block.predecessor v >>= function
|
|
|
|
| None -> Lwt.fail Not_found
|
|
|
|
| Some v -> predecessor net_db (n-1) v
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
let block_info node (block: block) =
|
|
|
|
match block with
|
2017-02-24 20:17:53 +04:00
|
|
|
| `Genesis ->
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.genesis node.mainnet_net >>= convert
|
2016-09-08 21:13:10 +04:00
|
|
|
| ( `Head n | `Test_head n ) as block ->
|
2017-02-24 20:17:53 +04:00
|
|
|
let validator = get_validator node block in
|
|
|
|
let net_db = Validator.net_db validator in
|
|
|
|
let net_state = Validator.net_state validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
predecessor net_db n head >>= convert
|
2017-02-24 20:17:53 +04:00
|
|
|
| `Hash h ->
|
2017-04-14 02:05:36 +04:00
|
|
|
read_valid_block_exn node h >>= convert
|
2016-09-08 21:13:10 +04:00
|
|
|
| ( `Prevalidation | `Test_prevalidation ) as block ->
|
2017-02-24 20:17:53 +04:00
|
|
|
let validator = get_validator node block in
|
2016-09-08 21:13:10 +04:00
|
|
|
let pv = Validator.prevalidator validator in
|
2017-02-24 20:17:53 +04:00
|
|
|
let net_state = Validator.net_state validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
let head_header = State.Block.header head in
|
|
|
|
let head_hash = State.Block.hash head in
|
|
|
|
State.Block.context head >>= fun head_context ->
|
|
|
|
Context.get_protocol head_context >>= fun head_protocol ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Prevalidator.context pv >>= function
|
|
|
|
| Error _ -> Lwt.fail Not_found
|
2017-04-10 14:14:11 +04:00
|
|
|
| Ok { context ; fitness } ->
|
|
|
|
Context.get_protocol context >>= fun protocol ->
|
2017-04-10 19:06:11 +04:00
|
|
|
Context.get_test_network context >>= fun test_network ->
|
2017-04-12 20:22:40 +04:00
|
|
|
let proto_level =
|
2017-04-19 23:46:10 +04:00
|
|
|
if Protocol_hash.equal protocol head_protocol then
|
|
|
|
head_header.shell.proto_level
|
2017-04-12 20:22:40 +04:00
|
|
|
else
|
2017-04-19 23:46:10 +04:00
|
|
|
((head_header.shell.proto_level + 1) mod 256) in
|
2016-10-20 20:54:16 +04:00
|
|
|
let operations =
|
|
|
|
let pv_result, _ = Prevalidator.operations pv in
|
2017-04-10 15:01:22 +04:00
|
|
|
[ pv_result.applied ] in
|
2016-10-20 20:54:16 +04:00
|
|
|
Lwt.return
|
2017-04-10 15:01:22 +04:00
|
|
|
{ hash = prevalidation_hash ;
|
2017-04-19 23:46:10 +04:00
|
|
|
level = Int32.succ head_header.shell.level ;
|
2017-04-12 20:22:40 +04:00
|
|
|
proto_level ;
|
2017-04-19 23:46:10 +04:00
|
|
|
predecessor = head_hash ;
|
2017-04-10 15:01:22 +04:00
|
|
|
fitness ;
|
|
|
|
timestamp = Prevalidator.timestamp pv ;
|
2017-04-10 19:06:11 +04:00
|
|
|
protocol ;
|
2017-04-10 15:01:22 +04:00
|
|
|
operations_hash =
|
|
|
|
Operation_list_list_hash.compute
|
|
|
|
(List.map Operation_list_hash.compute operations) ;
|
|
|
|
operations = Some operations ;
|
2017-04-10 19:06:11 +04:00
|
|
|
data = MBytes.of_string "" ;
|
2017-04-19 23:46:10 +04:00
|
|
|
net_id = head_header.shell.net_id ;
|
2017-04-10 19:06:11 +04:00
|
|
|
test_network ;
|
2017-04-10 15:01:22 +04:00
|
|
|
}
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let rpc_context block : Updater.rpc_context Lwt.t =
|
|
|
|
let block_hash = State.Block.hash block in
|
|
|
|
let block_header = State.Block.header block in
|
|
|
|
State.Block.context block >|= fun context ->
|
|
|
|
{ Updater.block_hash ;
|
|
|
|
block_header ;
|
|
|
|
operation_hashes = (fun () -> State.Block.all_operation_hashes block) ;
|
|
|
|
operations = (fun () -> State.Block.all_operations block) ;
|
|
|
|
context ;
|
2017-04-14 02:07:01 +04:00
|
|
|
}
|
2017-04-10 14:14:11 +04:00
|
|
|
|
|
|
|
let get_rpc_context node block =
|
2016-09-08 21:13:10 +04:00
|
|
|
match block with
|
|
|
|
| `Genesis ->
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.genesis node.mainnet_net >>= fun block ->
|
|
|
|
rpc_context block >>= fun ctxt ->
|
|
|
|
Lwt.return (Some ctxt)
|
2017-02-24 20:17:53 +04:00
|
|
|
| ( `Head n | `Test_head n ) as block ->
|
|
|
|
let validator = get_validator node block in
|
|
|
|
let net_state = Validator.net_state validator in
|
|
|
|
let net_db = Validator.net_db validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
predecessor net_db n head >>= fun block ->
|
|
|
|
rpc_context block >>= fun ctxt ->
|
|
|
|
Lwt.return (Some ctxt)
|
2016-09-08 21:13:10 +04:00
|
|
|
| `Hash hash-> begin
|
2017-04-19 23:46:10 +04:00
|
|
|
read_valid_block node hash >>= function
|
|
|
|
| None ->
|
|
|
|
Lwt.return_none
|
|
|
|
| Some block ->
|
|
|
|
rpc_context block >>= fun ctxt ->
|
|
|
|
Lwt.return (Some ctxt)
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|
|
|
|
| ( `Prevalidation | `Test_prevalidation ) as block ->
|
2017-04-14 02:07:01 +04:00
|
|
|
let validator, net_db = get_net node block in
|
2016-09-08 21:13:10 +04:00
|
|
|
let pv = Validator.prevalidator validator in
|
2017-04-14 02:07:01 +04:00
|
|
|
let net_state = Validator.net_state validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
let head_header = State.Block.header head in
|
|
|
|
let head_hash = State.Block.hash head in
|
|
|
|
State.Block.context head >>= fun head_context ->
|
|
|
|
Context.get_protocol head_context >>= fun head_protocol ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Prevalidator.context pv >>= function
|
|
|
|
| Error _ -> Lwt.fail Not_found
|
2017-04-10 14:14:11 +04:00
|
|
|
| Ok { context ; fitness } ->
|
2017-04-14 02:07:01 +04:00
|
|
|
Context.get_protocol context >>= fun protocol ->
|
|
|
|
let proto_level =
|
2017-04-19 23:46:10 +04:00
|
|
|
if Protocol_hash.equal protocol head_protocol then
|
|
|
|
head_header.shell.proto_level
|
2017-04-14 02:07:01 +04:00
|
|
|
else
|
2017-04-19 23:46:10 +04:00
|
|
|
((head_header.shell.proto_level + 1) mod 256) in
|
2017-04-14 02:07:01 +04:00
|
|
|
let operation_hashes =
|
|
|
|
let pv_result, _ = Prevalidator.operations pv in
|
|
|
|
[ pv_result.applied ] in
|
|
|
|
let operations_hash =
|
|
|
|
Operation_list_list_hash.compute
|
|
|
|
(List.map Operation_list_hash.compute operation_hashes) in
|
|
|
|
Lwt.return (Some {
|
|
|
|
Updater.block_hash = prevalidation_hash ;
|
|
|
|
block_header = {
|
|
|
|
shell = {
|
2017-04-19 23:46:10 +04:00
|
|
|
net_id = head_header.shell.net_id ;
|
|
|
|
level = Int32.succ head_header.shell.level ;
|
2017-04-14 02:07:01 +04:00
|
|
|
proto_level ;
|
2017-04-19 23:46:10 +04:00
|
|
|
predecessor = head_hash ;
|
2017-04-14 02:07:01 +04:00
|
|
|
timestamp = Prevalidator.timestamp pv ;
|
|
|
|
operations_hash ;
|
|
|
|
fitness ;
|
|
|
|
} ;
|
|
|
|
proto = MBytes.create 0 ;
|
|
|
|
} ;
|
|
|
|
operation_hashes = (fun () -> Lwt.return operation_hashes) ;
|
|
|
|
operations = begin fun () ->
|
|
|
|
Lwt_list.map_p
|
|
|
|
(Lwt_list.map_p
|
|
|
|
(Distributed_db.Operation.read_exn net_db))
|
|
|
|
operation_hashes
|
|
|
|
end ;
|
|
|
|
context ;
|
|
|
|
})
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let operation_hashes node block =
|
2016-09-08 21:13:10 +04:00
|
|
|
match block with
|
2017-04-19 23:46:10 +04:00
|
|
|
| `Genesis -> Lwt.return []
|
2016-09-08 21:13:10 +04:00
|
|
|
| ( `Head n | `Test_head n ) as block ->
|
2017-02-24 20:17:53 +04:00
|
|
|
let validator = get_validator node block in
|
|
|
|
let net_state = Validator.net_state validator in
|
|
|
|
let net_db = Validator.net_db validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
predecessor net_db n head >>= fun block ->
|
|
|
|
State.Block.all_operation_hashes block
|
2016-09-08 21:13:10 +04:00
|
|
|
| (`Prevalidation | `Test_prevalidation) as block ->
|
|
|
|
let validator, _net = get_net node block in
|
|
|
|
let pv = Validator.prevalidator validator in
|
2016-10-20 20:54:16 +04:00
|
|
|
let { Prevalidation.applied }, _ = Prevalidator.operations pv in
|
2017-03-30 15:16:21 +04:00
|
|
|
Lwt.return [applied]
|
2016-10-20 20:54:16 +04:00
|
|
|
| `Hash hash ->
|
2017-04-14 02:05:36 +04:00
|
|
|
read_valid_block node hash >>= function
|
|
|
|
| None -> Lwt.return_nil
|
2017-04-19 23:46:10 +04:00
|
|
|
| Some block ->
|
|
|
|
State.Block.all_operation_hashes block
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let operations node block =
|
|
|
|
match block with
|
|
|
|
| `Genesis -> Lwt.return []
|
|
|
|
| ( `Head n | `Test_head n ) as block ->
|
|
|
|
let validator = get_validator node block in
|
|
|
|
let net_state = Validator.net_state validator in
|
|
|
|
let net_db = Validator.net_db validator in
|
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
predecessor net_db n head >>= fun block ->
|
|
|
|
State.Block.all_operations block
|
|
|
|
| (`Prevalidation | `Test_prevalidation) as block ->
|
|
|
|
let validator, net_db = get_net node block in
|
|
|
|
let pv = Validator.prevalidator validator in
|
|
|
|
let { Prevalidation.applied }, _ = Prevalidator.operations pv in
|
|
|
|
Lwt_list.map_p
|
|
|
|
(Distributed_db.Operation.read_exn net_db) applied >>= fun applied ->
|
|
|
|
Lwt.return [applied]
|
|
|
|
| `Hash hash ->
|
|
|
|
read_valid_block node hash >>= function
|
|
|
|
| None -> Lwt.return_nil
|
|
|
|
| Some block ->
|
|
|
|
State.Block.all_operations block
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
let pending_operations node (block: block) =
|
2016-09-08 21:13:10 +04:00
|
|
|
match block with
|
|
|
|
| ( `Head 0 | `Prevalidation
|
|
|
|
| `Test_head 0 | `Test_prevalidation ) as block ->
|
|
|
|
let validator, _net = get_net node block in
|
|
|
|
let pv = Validator.prevalidator validator in
|
|
|
|
Lwt.return (Prevalidator.operations pv)
|
|
|
|
| ( `Head n | `Test_head n ) as block ->
|
2017-02-24 20:17:53 +04:00
|
|
|
let validator = get_validator node block in
|
|
|
|
let prevalidator = Validator.prevalidator validator in
|
|
|
|
let net_state = Validator.net_state validator in
|
|
|
|
let net_db = Validator.net_db validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
predecessor net_db n head >>= fun b ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Prevalidator.pending ~block:b prevalidator >|= fun ops ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Prevalidation.empty_result, ops
|
2016-09-08 21:13:10 +04:00
|
|
|
| `Genesis ->
|
2017-02-28 03:48:22 +04:00
|
|
|
let net = node.mainnet_net in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.genesis net >>= fun b ->
|
2017-02-24 20:17:53 +04:00
|
|
|
let validator = get_validator node `Genesis in
|
|
|
|
let prevalidator = Validator.prevalidator validator in
|
|
|
|
Prevalidator.pending ~block:b prevalidator >|= fun ops ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Prevalidation.empty_result, ops
|
2017-02-24 20:17:53 +04:00
|
|
|
| `Hash h -> begin
|
|
|
|
get_validator_per_hash node h >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| None ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Lwt.return (Prevalidation.empty_result, Operation_hash.Set.empty)
|
2017-02-24 20:17:53 +04:00
|
|
|
| Some (validator, net_db) ->
|
|
|
|
let net_state = Distributed_db.state net_db in
|
|
|
|
let prevalidator = Validator.prevalidator validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
State.Block.read_exn net_state h >>= fun block ->
|
2017-02-24 20:17:53 +04:00
|
|
|
Prevalidator.pending ~block prevalidator >|= fun ops ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Prevalidation.empty_result, ops
|
2017-02-24 20:17:53 +04:00
|
|
|
end
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
let protocols { state } =
|
|
|
|
State.Protocol.list state >>= fun set ->
|
|
|
|
Lwt.return (Protocol_hash.Set.elements set)
|
2016-10-21 16:01:20 +04:00
|
|
|
|
|
|
|
let protocol_content node hash =
|
|
|
|
State.Protocol.read node.state hash
|
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
let preapply node block ~timestamp ~sort ops =
|
|
|
|
begin
|
|
|
|
match block with
|
|
|
|
| `Genesis ->
|
2017-02-28 03:48:22 +04:00
|
|
|
let net = node.mainnet_net in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.genesis net >>= return
|
2016-09-08 21:13:10 +04:00
|
|
|
| ( `Head 0 | `Prevalidation
|
|
|
|
| `Test_head 0 | `Test_prevalidation ) as block ->
|
2017-02-24 20:17:53 +04:00
|
|
|
let validator = get_validator node block in
|
|
|
|
let net_state = Validator.net_state validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= return
|
2016-09-08 21:13:10 +04:00
|
|
|
| `Head n | `Test_head n as block -> begin
|
2017-02-24 20:17:53 +04:00
|
|
|
let validator = get_validator node block in
|
|
|
|
let net_state = Validator.net_state validator in
|
|
|
|
let net_db = Validator.net_db validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head net_state >>= fun head ->
|
|
|
|
predecessor net_db n head >>= return
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|
2017-02-24 20:17:53 +04:00
|
|
|
| `Hash hash ->
|
|
|
|
read_valid_block node hash >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| None -> Lwt.return (error_exn Not_found)
|
2017-02-24 20:17:53 +04:00
|
|
|
| Some data -> return data
|
2016-10-20 20:54:16 +04:00
|
|
|
end >>=? fun predecessor ->
|
2017-02-28 03:48:22 +04:00
|
|
|
let net_db = Validator.net_db node.mainnet_validator in
|
2017-04-19 23:46:10 +04:00
|
|
|
map_p (Distributed_db.resolve_operation net_db) ops >>=? fun rops ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Prevalidation.start_prevalidation
|
|
|
|
~predecessor ~timestamp >>=? fun validation_state ->
|
|
|
|
Prevalidation.prevalidate
|
|
|
|
validation_state ~sort rops >>=? fun (validation_state, r) ->
|
2017-04-10 14:14:11 +04:00
|
|
|
Prevalidation.end_prevalidation validation_state >>=? fun { fitness } ->
|
2016-10-20 20:54:16 +04:00
|
|
|
return (fitness, { r with applied = List.rev r.applied })
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2016-11-14 19:26:34 +04:00
|
|
|
let complete node ?block str =
|
|
|
|
match block with
|
|
|
|
| None ->
|
2017-02-19 21:22:32 +04:00
|
|
|
Base58.complete str
|
2016-11-14 19:26:34 +04:00
|
|
|
| Some block ->
|
2017-04-10 14:14:11 +04:00
|
|
|
get_rpc_context node block >>= function
|
2016-11-14 19:26:34 +04:00
|
|
|
| None -> Lwt.fail Not_found
|
2017-04-10 14:14:11 +04:00
|
|
|
| Some { context = ctxt } ->
|
2016-11-14 19:26:34 +04:00
|
|
|
Context.get_protocol ctxt >>= fun protocol_hash ->
|
|
|
|
let (module Proto) = Updater.get_exn protocol_hash in
|
2017-02-19 21:22:32 +04:00
|
|
|
Base58.complete str >>= fun l1 ->
|
|
|
|
Proto.complete_b58prefix ctxt str >>= fun l2 ->
|
2016-11-14 19:26:34 +04:00
|
|
|
Lwt.return (l1 @ l2)
|
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
let context_dir node block =
|
2017-04-10 14:14:11 +04:00
|
|
|
get_rpc_context node block >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| None -> Lwt.return None
|
2017-04-10 14:14:11 +04:00
|
|
|
| Some rpc_context ->
|
|
|
|
Context.get_protocol rpc_context.context >>= fun protocol_hash ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let (module Proto) = Updater.get_exn protocol_hash in
|
2017-04-10 14:14:11 +04:00
|
|
|
let dir = RPC.map (fun () -> rpc_context) Proto.rpc_services in
|
2016-09-08 21:13:10 +04:00
|
|
|
Lwt.return (Some (RPC.map (fun _ -> ()) dir))
|
|
|
|
|
|
|
|
let heads node =
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.known_heads node.mainnet_net >>= fun heads ->
|
2017-02-24 20:17:53 +04:00
|
|
|
begin
|
2017-02-28 03:48:22 +04:00
|
|
|
match Validator.test_validator node.mainnet_validator with
|
2017-02-24 20:17:53 +04:00
|
|
|
| None -> Lwt.return_nil
|
|
|
|
| Some (_, net_db) ->
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.known_heads (Distributed_db.state net_db)
|
2017-02-24 20:17:53 +04:00
|
|
|
end >>= fun test_heads ->
|
2017-04-14 02:05:36 +04:00
|
|
|
Lwt_list.fold_left_s
|
|
|
|
(fun map block ->
|
|
|
|
convert block >|= fun bi ->
|
|
|
|
Block_hash.Map.add
|
2017-04-19 23:46:10 +04:00
|
|
|
(State.Block.hash block) bi map)
|
2017-04-14 02:05:36 +04:00
|
|
|
Block_hash.Map.empty (test_heads @ heads)
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-02-15 23:38:00 +04:00
|
|
|
let predecessors node len head =
|
2017-04-19 23:46:10 +04:00
|
|
|
let rec loop acc len block =
|
|
|
|
if len = 0 then
|
2017-02-15 23:38:00 +04:00
|
|
|
Lwt.return (List.rev acc)
|
2017-04-19 23:46:10 +04:00
|
|
|
else
|
|
|
|
State.Block.predecessor block >>= function
|
|
|
|
| None -> Lwt.return (List.rev acc)
|
|
|
|
| Some block ->
|
|
|
|
loop (State.Block.hash block :: acc) (len-1) block
|
|
|
|
in
|
2017-02-15 23:38:00 +04:00
|
|
|
try
|
2017-04-19 23:46:10 +04:00
|
|
|
State.read_block_exn node.state head >>= fun block ->
|
|
|
|
loop [] len block
|
2017-02-15 23:38:00 +04:00
|
|
|
with Not_found -> Lwt.return_nil
|
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let predecessors_bi ignored len head =
|
2016-09-08 21:13:10 +04:00
|
|
|
try
|
2017-04-19 23:46:10 +04:00
|
|
|
let rec loop acc len block =
|
2017-04-14 02:05:36 +04:00
|
|
|
convert block >>= fun bi ->
|
2017-04-19 23:46:10 +04:00
|
|
|
State.Block.predecessor block >>= function
|
|
|
|
| None ->
|
|
|
|
Lwt.return (List.rev (bi :: acc))
|
|
|
|
| Some pred ->
|
|
|
|
if len = 0 ||
|
|
|
|
Block_hash.Set.mem (State.Block.hash block) ignored then
|
|
|
|
Lwt.return (List.rev acc)
|
|
|
|
else
|
|
|
|
loop (bi :: acc) (len-1) pred
|
|
|
|
in
|
2016-09-08 21:13:10 +04:00
|
|
|
loop [] len head
|
|
|
|
with Not_found -> Lwt.return_nil
|
|
|
|
|
|
|
|
let list node len heads =
|
|
|
|
Lwt_list.fold_left_s
|
|
|
|
(fun (ignored, acc) head ->
|
2017-04-19 23:46:10 +04:00
|
|
|
State.read_block_exn node.state head >>= fun block ->
|
|
|
|
predecessors_bi ignored len block >>= fun predecessors ->
|
2016-09-08 21:13:10 +04:00
|
|
|
let ignored =
|
|
|
|
List.fold_right
|
2017-02-24 20:17:53 +04:00
|
|
|
(fun x s -> Block_hash.Set.add x.hash s)
|
2016-09-08 21:13:10 +04:00
|
|
|
predecessors ignored in
|
2017-04-14 02:05:36 +04:00
|
|
|
Lwt.return (ignored, predecessors :: acc)
|
2016-09-08 21:13:10 +04:00
|
|
|
)
|
2017-02-24 20:17:53 +04:00
|
|
|
(Block_hash.Set.empty, [])
|
2017-02-15 23:38:00 +04:00
|
|
|
heads >>= fun (_, blocks) ->
|
|
|
|
Lwt.return (List.rev blocks)
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let block_header_watcher node =
|
|
|
|
Distributed_db.watch_block_header node.distributed_db
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-04-19 23:46:10 +04:00
|
|
|
let block_watcher node =
|
2017-02-28 03:48:22 +04:00
|
|
|
let stream, shutdown = Validator.global_watcher node.validator in
|
2017-04-14 02:05:36 +04:00
|
|
|
Lwt_stream.map_s (fun block -> convert block) stream,
|
2016-09-08 21:13:10 +04:00
|
|
|
shutdown
|
|
|
|
|
|
|
|
let operation_watcher node =
|
2017-02-24 20:17:53 +04:00
|
|
|
Distributed_db.watch_operation node.distributed_db
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2016-10-21 16:01:20 +04:00
|
|
|
let protocol_watcher node =
|
2017-02-24 20:17:53 +04:00
|
|
|
Distributed_db.watch_protocol node.distributed_db
|
2016-10-21 16:01:20 +04:00
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
let validate node net_id block =
|
|
|
|
Validator.get node.validator net_id >>=? fun net_v ->
|
|
|
|
Validator.fetch_block net_v block >>=? fun _ ->
|
|
|
|
return ()
|
|
|
|
|
2017-02-28 03:48:22 +04:00
|
|
|
let bootstrapped node =
|
|
|
|
let block_stream, stopper =
|
|
|
|
Validator.new_head_watcher node.mainnet_validator in
|
|
|
|
let first_run = ref true in
|
|
|
|
let rec next () =
|
|
|
|
if !first_run then begin
|
|
|
|
first_run := false ;
|
2017-04-19 23:46:10 +04:00
|
|
|
Chain.head node.mainnet_net >>= fun head ->
|
|
|
|
let head_hash = State.Block.hash head in
|
|
|
|
let head_header = State.Block.header head in
|
|
|
|
Lwt.return (Some (head_hash, head_header.shell.timestamp))
|
2017-02-28 03:48:22 +04:00
|
|
|
end else begin
|
|
|
|
Lwt.pick [
|
|
|
|
( Lwt_stream.get block_stream >|=
|
2017-04-19 23:46:10 +04:00
|
|
|
map_option ~f:(fun b ->
|
|
|
|
(State.Block.hash b, (State.Block.header b).shell.timestamp)) ) ;
|
2017-02-28 03:48:22 +04:00
|
|
|
(Validator.bootstrapped node.mainnet_validator >|= fun () -> None) ;
|
|
|
|
]
|
|
|
|
end in
|
|
|
|
let shutdown () = Watcher.shutdown stopper in
|
|
|
|
RPC.Answer.{ next ; shutdown }
|
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
module Network = struct
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
let stat (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.stat node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let watch (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.watch node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let connect (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.connect node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
module Connection = struct
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
let info (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Connection.info node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let kick (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Connection.kick node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let list (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Connection.list node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let count (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Connection.count node.p2p
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
end
|
|
|
|
|
|
|
|
module Point = struct
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
let info (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Point.info node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
2017-03-02 18:39:36 +04:00
|
|
|
let list (node : t) restrict =
|
|
|
|
P2p.RPC.Point.list ~restrict node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let events (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Point.events node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let watch (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Point.watch node.p2p
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
end
|
|
|
|
|
2017-02-24 06:50:33 +04:00
|
|
|
module Peer_id = struct
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
let info (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Peer_id.info node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
2017-03-02 18:39:36 +04:00
|
|
|
let list (node : t) restrict =
|
|
|
|
P2p.RPC.Peer_id.list ~restrict node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let events (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Peer_id.events node.p2p
|
2017-02-17 22:12:06 +04:00
|
|
|
|
|
|
|
let watch (node : t) =
|
2017-02-24 20:17:53 +04:00
|
|
|
P2p.RPC.Peer_id.watch node.p2p
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
end
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2017-02-17 22:12:06 +04:00
|
|
|
end
|
2017-02-28 03:48:22 +04:00
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|