2018-06-29 16:08:08 +04:00
|
|
|
(*****************************************************************************)
|
|
|
|
(* *)
|
|
|
|
(* Open Source License *)
|
|
|
|
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
|
|
(* *)
|
|
|
|
(* Permission is hereby granted, free of charge, to any person obtaining a *)
|
|
|
|
(* copy of this software and associated documentation files (the "Software"),*)
|
|
|
|
(* to deal in the Software without restriction, including without limitation *)
|
|
|
|
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
|
|
|
|
(* and/or sell copies of the Software, and to permit persons to whom the *)
|
|
|
|
(* Software is furnished to do so, subject to the following conditions: *)
|
|
|
|
(* *)
|
|
|
|
(* The above copyright notice and this permission notice shall be included *)
|
|
|
|
(* in all copies or substantial portions of the Software. *)
|
|
|
|
(* *)
|
|
|
|
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
|
|
|
|
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
|
|
|
|
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
|
|
|
|
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
|
|
|
|
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
|
|
|
|
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
|
|
|
|
(* DEALINGS IN THE SOFTWARE. *)
|
|
|
|
(* *)
|
|
|
|
(*****************************************************************************)
|
2016-10-20 20:54:16 +04:00
|
|
|
|
2017-11-27 09:13:12 +04:00
|
|
|
open Preapply_result
|
2018-02-13 17:12:09 +04:00
|
|
|
open Validation_errors
|
2016-10-20 20:54:16 +04:00
|
|
|
|
2017-11-19 18:15:03 +04:00
|
|
|
let rec apply_operations apply_operation state r max_ops ~sort ops =
|
2016-10-20 20:54:16 +04:00
|
|
|
Lwt_list.fold_left_s
|
2017-11-19 18:15:03 +04:00
|
|
|
(fun (state, max_ops, r) (hash, op, parsed_op) ->
|
|
|
|
apply_operation state max_ops op parsed_op >>= function
|
2018-04-16 02:44:20 +04:00
|
|
|
| Ok (state, _metadata) ->
|
2017-11-14 02:27:19 +04:00
|
|
|
let applied = (hash, op) :: r.applied in
|
2017-11-19 18:15:03 +04:00
|
|
|
Lwt.return (state, max_ops - 1, { r with applied })
|
2016-10-20 20:54:16 +04:00
|
|
|
| Error errors ->
|
|
|
|
match classify_errors errors with
|
|
|
|
| `Branch ->
|
|
|
|
let branch_refused =
|
2017-11-14 02:27:19 +04:00
|
|
|
Operation_hash.Map.add hash (op, errors) r.branch_refused in
|
2017-11-19 18:15:03 +04:00
|
|
|
Lwt.return (state, max_ops, { r with branch_refused })
|
2016-10-20 20:54:16 +04:00
|
|
|
| `Permanent ->
|
|
|
|
let refused =
|
2017-11-14 02:27:19 +04:00
|
|
|
Operation_hash.Map.add hash (op, errors) r.refused in
|
2017-11-19 18:15:03 +04:00
|
|
|
Lwt.return (state, max_ops, { r with refused })
|
2016-10-20 20:54:16 +04:00
|
|
|
| `Temporary ->
|
|
|
|
let branch_delayed =
|
2017-11-14 02:27:19 +04:00
|
|
|
Operation_hash.Map.add hash (op, errors) r.branch_delayed in
|
2017-11-19 18:15:03 +04:00
|
|
|
Lwt.return (state, max_ops, { r with branch_delayed }))
|
|
|
|
(state, max_ops, r)
|
|
|
|
ops >>= fun (state, max_ops, r) ->
|
2016-10-20 20:54:16 +04:00
|
|
|
match r.applied with
|
|
|
|
| _ :: _ when sort ->
|
|
|
|
let rechecked_operations =
|
|
|
|
List.filter
|
2017-11-14 02:27:19 +04:00
|
|
|
(fun (hash, _, _) -> Operation_hash.Map.mem hash r.branch_delayed)
|
2016-10-20 20:54:16 +04:00
|
|
|
ops in
|
2017-04-27 03:01:05 +04:00
|
|
|
let remaining = List.length rechecked_operations in
|
|
|
|
if remaining = 0 || remaining = List.length ops then
|
2017-11-19 18:15:03 +04:00
|
|
|
Lwt.return (state, max_ops, r)
|
2017-04-27 03:01:05 +04:00
|
|
|
else
|
2017-11-19 18:15:03 +04:00
|
|
|
apply_operations apply_operation state r max_ops ~sort rechecked_operations
|
2016-10-20 20:54:16 +04:00
|
|
|
| _ ->
|
2017-11-19 18:15:03 +04:00
|
|
|
Lwt.return (state, max_ops, r)
|
2016-10-20 20:54:16 +04:00
|
|
|
|
|
|
|
type prevalidation_state =
|
2018-08-16 20:03:10 +04:00
|
|
|
State : { proto : ('state, 'operation_data) proto ; state : 'state ;
|
|
|
|
max_number_of_operations : int ;
|
|
|
|
new_operation_input : ([ `Applied | `Refused | `Branch_refused | `Branch_delayed ] *
|
|
|
|
Operation.shell_header * 'operation_data) Lwt_watcher.input ;
|
|
|
|
}
|
2016-10-20 20:54:16 +04:00
|
|
|
-> prevalidation_state
|
|
|
|
|
2018-08-16 20:03:10 +04:00
|
|
|
and ('state, 'operation_data) proto =
|
|
|
|
(module Registered_protocol.T
|
|
|
|
with type P.validation_state = 'state
|
|
|
|
and type P.operation_data = 'operation_data )
|
2016-10-20 20:54:16 +04:00
|
|
|
|
2017-11-19 18:15:03 +04:00
|
|
|
let start_prevalidation
|
2018-02-16 17:05:46 +04:00
|
|
|
?protocol_data
|
2017-11-19 18:15:03 +04:00
|
|
|
~predecessor ~timestamp () =
|
2017-04-19 23:46:10 +04:00
|
|
|
let { Block_header.shell =
|
|
|
|
{ fitness = predecessor_fitness ;
|
|
|
|
timestamp = predecessor_timestamp ;
|
|
|
|
level = predecessor_level } } =
|
|
|
|
State.Block.header predecessor in
|
|
|
|
State.Block.context predecessor >>= fun predecessor_context ->
|
|
|
|
Context.get_protocol predecessor_context >>= fun protocol ->
|
2018-06-27 19:00:24 +04:00
|
|
|
let predecessor_hash = State.Block.hash predecessor in
|
2017-10-27 21:41:47 +04:00
|
|
|
begin
|
2018-03-15 18:26:52 +04:00
|
|
|
match Registered_protocol.get protocol with
|
2017-10-27 21:41:47 +04:00
|
|
|
| None ->
|
|
|
|
(* FIXME. *)
|
|
|
|
(* This should not happen: it should be handled in the validator. *)
|
|
|
|
failwith "Prevalidation: missing protocol '%a' for the current block."
|
|
|
|
Protocol_hash.pp_short protocol
|
|
|
|
| Some protocol ->
|
|
|
|
return protocol
|
|
|
|
end >>=? fun (module Proto) ->
|
2018-02-16 04:26:24 +04:00
|
|
|
Context.reset_test_chain
|
2018-06-27 19:00:24 +04:00
|
|
|
predecessor_context predecessor_hash
|
2017-04-10 23:14:17 +04:00
|
|
|
timestamp >>= fun predecessor_context ->
|
2018-04-21 01:04:33 +04:00
|
|
|
begin
|
|
|
|
match protocol_data with
|
2018-06-27 06:05:42 +04:00
|
|
|
| None -> return_none
|
2018-04-21 01:04:33 +04:00
|
|
|
| Some protocol_data ->
|
|
|
|
match
|
|
|
|
Data_encoding.Binary.of_bytes
|
|
|
|
Proto.block_header_data_encoding
|
|
|
|
protocol_data
|
|
|
|
with
|
|
|
|
| None -> failwith "Invalid block header"
|
2018-06-27 06:05:42 +04:00
|
|
|
| Some protocol_data -> return_some protocol_data
|
2018-04-21 01:04:33 +04:00
|
|
|
end >>=? fun protocol_data ->
|
2016-10-20 20:54:16 +04:00
|
|
|
Proto.begin_construction
|
2018-06-27 19:00:24 +04:00
|
|
|
~chain_id: (State.Block.chain_id predecessor)
|
2016-10-20 20:54:16 +04:00
|
|
|
~predecessor_context
|
|
|
|
~predecessor_timestamp
|
2017-04-10 14:14:11 +04:00
|
|
|
~predecessor_fitness
|
2017-04-10 15:01:22 +04:00
|
|
|
~predecessor_level
|
2018-06-27 19:00:24 +04:00
|
|
|
~predecessor: predecessor_hash
|
2017-04-10 14:14:11 +04:00
|
|
|
~timestamp
|
2018-02-16 17:05:46 +04:00
|
|
|
?protocol_data
|
2017-04-26 17:01:39 +04:00
|
|
|
()
|
2017-04-10 14:14:11 +04:00
|
|
|
>>=? fun state ->
|
2018-01-31 13:04:35 +04:00
|
|
|
(* FIXME arbitrary value, to be customisable *)
|
|
|
|
let max_number_of_operations = 1000 in
|
2018-08-16 20:03:10 +04:00
|
|
|
let new_operation_input = Lwt_watcher.create_input () in
|
2017-11-19 18:15:03 +04:00
|
|
|
return (State { proto = (module Proto) ; state ;
|
2018-08-16 20:03:10 +04:00
|
|
|
max_number_of_operations ;
|
|
|
|
new_operation_input ;
|
|
|
|
})
|
2017-04-27 03:01:05 +04:00
|
|
|
|
2016-10-20 20:54:16 +04:00
|
|
|
let prevalidate
|
2017-11-19 18:15:03 +04:00
|
|
|
(State { proto = (module Proto) ; state ;
|
2018-08-16 20:03:10 +04:00
|
|
|
max_number_of_operations ; new_operation_input })
|
|
|
|
~sort (ops : (Operation_hash.t * Operation.t) list) =
|
2016-10-20 20:54:16 +04:00
|
|
|
let ops =
|
2017-04-27 03:01:05 +04:00
|
|
|
List.map
|
|
|
|
(fun (h, op) ->
|
2018-04-21 01:04:33 +04:00
|
|
|
let parsed_op =
|
|
|
|
match Data_encoding.Binary.of_bytes
|
|
|
|
Proto.operation_data_encoding
|
|
|
|
op.Operation.proto with
|
|
|
|
| None -> error Parse_error
|
2018-04-30 21:06:06 +04:00
|
|
|
| Some protocol_data ->
|
|
|
|
Ok ({ shell = op.shell ; protocol_data } : Proto.operation) in
|
2018-04-21 01:04:33 +04:00
|
|
|
(h, op, parsed_op))
|
2017-04-27 03:01:05 +04:00
|
|
|
ops in
|
|
|
|
let invalid_ops =
|
2017-11-27 09:13:12 +04:00
|
|
|
List.filter_map
|
2017-11-14 02:27:19 +04:00
|
|
|
(fun (h, op, parsed_op) -> match parsed_op with
|
2017-04-27 03:01:05 +04:00
|
|
|
| Ok _ -> None
|
2017-11-14 02:27:19 +04:00
|
|
|
| Error err -> Some (h, op, err)) ops
|
2017-04-27 03:01:05 +04:00
|
|
|
and parsed_ops =
|
2017-11-27 09:13:12 +04:00
|
|
|
List.filter_map
|
2017-11-14 02:27:19 +04:00
|
|
|
(fun (h, op, parsed_op) -> match parsed_op with
|
|
|
|
| Ok parsed_op -> Some (h, op, parsed_op)
|
2017-04-27 03:01:05 +04:00
|
|
|
| Error _ -> None) ops in
|
|
|
|
let sorted_ops =
|
2016-10-20 20:54:16 +04:00
|
|
|
if sort then
|
2017-11-14 02:27:19 +04:00
|
|
|
let compare (_, _, op1) (_, _, op2) = Proto.compare_operations op1 op2 in
|
2017-04-27 03:01:05 +04:00
|
|
|
List.sort compare parsed_ops
|
|
|
|
else parsed_ops in
|
2018-04-30 21:06:06 +04:00
|
|
|
let apply_operation state max_ops op (parse_op) =
|
2017-11-19 18:15:03 +04:00
|
|
|
let size = Data_encoding.Binary.length Operation.encoding op in
|
|
|
|
if max_ops <= 0 then
|
|
|
|
fail Too_many_operations
|
2018-06-02 16:58:08 +04:00
|
|
|
else if size > Proto.max_operation_data_length then
|
|
|
|
fail (Oversized_operation { size ; max = Proto.max_operation_data_length })
|
2017-11-19 18:15:03 +04:00
|
|
|
else
|
2018-04-30 21:06:06 +04:00
|
|
|
Proto.apply_operation state parse_op >>=? fun (state, receipt) ->
|
|
|
|
return (state, receipt) in
|
2017-04-27 03:01:05 +04:00
|
|
|
apply_operations
|
2017-11-19 18:15:03 +04:00
|
|
|
apply_operation
|
2017-11-27 09:13:12 +04:00
|
|
|
state Preapply_result.empty max_number_of_operations
|
2017-11-19 18:15:03 +04:00
|
|
|
~sort sorted_ops >>= fun (state, max_number_of_operations, r) ->
|
2017-04-27 03:01:05 +04:00
|
|
|
let r =
|
|
|
|
{ r with
|
|
|
|
applied = List.rev r.applied ;
|
|
|
|
branch_refused =
|
|
|
|
List.fold_left
|
2017-11-14 02:27:19 +04:00
|
|
|
(fun map (h, op, err) -> Operation_hash.Map.add h (op, err) map)
|
2017-04-27 03:01:05 +04:00
|
|
|
r.branch_refused invalid_ops } in
|
2017-11-19 18:15:03 +04:00
|
|
|
Lwt.return (State { proto = (module Proto) ; state ;
|
2018-08-16 20:03:10 +04:00
|
|
|
max_number_of_operations ; new_operation_input },
|
2017-11-19 18:15:03 +04:00
|
|
|
r)
|
2016-10-20 20:54:16 +04:00
|
|
|
|
|
|
|
let end_prevalidation (State { proto = (module Proto) ; state }) =
|
2018-04-16 02:44:20 +04:00
|
|
|
Proto.finalize_block state >>=? fun (result, _metadata) ->
|
|
|
|
return result
|
2018-04-16 02:44:24 +04:00
|
|
|
|
|
|
|
let preapply ~predecessor ~timestamp ~protocol_data ~sort_operations:sort ops =
|
|
|
|
start_prevalidation
|
|
|
|
~protocol_data ~predecessor ~timestamp () >>=? fun validation_state ->
|
|
|
|
let ops = List.map (List.map (fun x -> Operation.hash x, x)) ops in
|
|
|
|
Lwt_list.fold_left_s
|
|
|
|
(fun (validation_state, rs) ops ->
|
|
|
|
prevalidate
|
|
|
|
validation_state ~sort ops >>= fun (validation_state, r) ->
|
|
|
|
Lwt.return (validation_state, rs @ [r]))
|
|
|
|
(validation_state, []) ops >>= fun (validation_state, rs) ->
|
|
|
|
let operations_hash =
|
|
|
|
Operation_list_list_hash.compute
|
|
|
|
(List.map
|
|
|
|
(fun r ->
|
|
|
|
Operation_list_hash.compute
|
|
|
|
(List.map fst r.Preapply_result.applied))
|
|
|
|
rs) in
|
2018-06-02 15:45:41 +04:00
|
|
|
end_prevalidation validation_state >>=? fun validation_result ->
|
2018-04-16 02:44:24 +04:00
|
|
|
let pred_shell_header = State.Block.shell_header predecessor in
|
2018-06-02 15:45:41 +04:00
|
|
|
let level = Int32.succ pred_shell_header.level in
|
|
|
|
Block_validator.may_patch_protocol
|
|
|
|
~level validation_result >>=? fun { fitness ; context ; message } ->
|
2018-04-16 02:44:24 +04:00
|
|
|
State.Block.protocol_hash predecessor >>= fun pred_protocol ->
|
|
|
|
Context.get_protocol context >>= fun protocol ->
|
|
|
|
let proto_level =
|
|
|
|
if Protocol_hash.equal protocol pred_protocol then
|
|
|
|
pred_shell_header.proto_level
|
|
|
|
else
|
|
|
|
((pred_shell_header.proto_level + 1) mod 256) in
|
|
|
|
let shell_header : Block_header.shell_header = {
|
2018-06-02 15:45:41 +04:00
|
|
|
level ;
|
2018-04-16 02:44:24 +04:00
|
|
|
proto_level ;
|
|
|
|
predecessor = State.Block.hash predecessor ;
|
|
|
|
timestamp ;
|
|
|
|
validation_passes = List.length rs ;
|
|
|
|
operations_hash ;
|
|
|
|
fitness ;
|
|
|
|
context = Context_hash.zero ; (* place holder *)
|
|
|
|
} in
|
|
|
|
begin
|
|
|
|
if Protocol_hash.equal protocol pred_protocol then
|
|
|
|
return (context, message)
|
|
|
|
else
|
|
|
|
match Registered_protocol.get protocol with
|
|
|
|
| None ->
|
|
|
|
fail (Block_validator_errors.Unavailable_protocol
|
|
|
|
{ block = State.Block.hash predecessor ; protocol })
|
|
|
|
| Some (module NewProto) ->
|
|
|
|
NewProto.init context shell_header >>=? fun { context ; message ; _ } ->
|
|
|
|
return (context, message)
|
|
|
|
end >>=? fun (context, message) ->
|
2018-08-28 19:46:48 +04:00
|
|
|
Context.hash ?message ~time:timestamp context >>= fun context ->
|
2018-04-16 02:44:24 +04:00
|
|
|
return ({ shell_header with context }, rs)
|
2018-08-16 20:03:10 +04:00
|
|
|
|
|
|
|
let notify_operation (State { proto = (module Proto) ; new_operation_input ; }) result =
|
|
|
|
let { applied ; refused ; branch_refused ; branch_delayed } = result in
|
|
|
|
(* Notify new opperations *)
|
|
|
|
let map_op kind { Operation.shell ; proto } =
|
|
|
|
let protocol_data =
|
|
|
|
Data_encoding.Binary.of_bytes_exn
|
|
|
|
Proto.operation_data_encoding
|
|
|
|
proto in
|
|
|
|
kind, shell, protocol_data in
|
|
|
|
let fold_op kind _k (op, _error) acc = map_op kind op :: acc in
|
|
|
|
let applied = List.map (map_op `Applied) (List.map snd applied) in
|
|
|
|
let refused = Operation_hash.Map.fold (fold_op `Refused) refused [] in
|
|
|
|
let branch_refused = Operation_hash.Map.fold (fold_op `Branch_refused) branch_refused [] in
|
|
|
|
let branch_delayed = Operation_hash.Map.fold (fold_op `Branch_delayed) branch_delayed [] in
|
|
|
|
let ops = List.concat [ applied ; refused ; branch_refused ; branch_delayed ] in
|
|
|
|
List.iter (Lwt_watcher.notify new_operation_input) ops
|
|
|
|
|
|
|
|
let shutdown_operation_input (State { new_operation_input }) =
|
|
|
|
Lwt_watcher.shutdown_input new_operation_input
|
|
|
|
|
|
|
|
let build_rpc_directory protocol =
|
|
|
|
begin
|
|
|
|
match Registered_protocol.get protocol with
|
|
|
|
| None ->
|
|
|
|
(* FIXME. *)
|
|
|
|
(* This should not happen: it should be handled in the validator. *)
|
|
|
|
failwith "Prevalidation: missing protocol '%a' for the current block."
|
|
|
|
Protocol_hash.pp_short protocol
|
|
|
|
| Some protocol ->
|
|
|
|
return protocol
|
|
|
|
end >>=? fun (module Proto) ->
|
|
|
|
let module Proto_services = Block_services.Make(Proto)(Proto) in
|
|
|
|
|
|
|
|
let dir : (prevalidation_state * Error_monad.error Preapply_result.t) RPC_directory.t ref =
|
|
|
|
ref RPC_directory.empty in
|
|
|
|
|
|
|
|
let gen_register s f =
|
|
|
|
dir := RPC_directory.gen_register !dir s f in
|
|
|
|
|
|
|
|
gen_register
|
|
|
|
(Proto_services.S.Mempool.monitor_operations RPC_path.open_root)
|
|
|
|
begin fun ((State { new_operation_input ; proto = (module Next_proto) }), current_mempool) params () ->
|
|
|
|
let operation_stream, stopper =
|
|
|
|
Lwt_watcher.create_stream new_operation_input in
|
|
|
|
(* Convert ops *)
|
|
|
|
let map_op op =
|
|
|
|
let protocol_data =
|
|
|
|
Data_encoding.Binary.of_bytes_exn
|
|
|
|
Proto.operation_data_encoding
|
|
|
|
op.Operation.proto in
|
|
|
|
Proto.{ shell = op.shell ; protocol_data } in
|
|
|
|
let fold_op _k (op, _error) acc = map_op op :: acc in
|
|
|
|
(* First call : retrieve the current set of op from the mempool *)
|
|
|
|
let { applied ; refused ; branch_refused ; branch_delayed } = current_mempool in
|
|
|
|
let applied = if params#applied then List.map map_op (List.map snd applied) else [] in
|
|
|
|
let refused = if params#refused then
|
|
|
|
Operation_hash.Map.fold fold_op refused [] else [] in
|
|
|
|
let branch_refused = if params#branch_refused then
|
|
|
|
Operation_hash.Map.fold fold_op branch_refused [] else [] in
|
|
|
|
let branch_delayed = if params#branch_delayed then
|
|
|
|
Operation_hash.Map.fold fold_op branch_delayed [] else [] in
|
|
|
|
let current_mempool = List.concat [ applied ; refused ; branch_refused ; branch_delayed ] in
|
|
|
|
let current_mempool = ref (Some current_mempool) in
|
|
|
|
let filter_result = function
|
|
|
|
| `Applied -> params#applied
|
|
|
|
| `Refused -> params#branch_refused
|
|
|
|
| `Branch_refused -> params#refused
|
|
|
|
| `Branch_delayed -> params#branch_delayed
|
|
|
|
in
|
|
|
|
let next () =
|
|
|
|
match !current_mempool with
|
|
|
|
| Some mempool -> begin
|
|
|
|
current_mempool := None ;
|
|
|
|
Lwt.return_some mempool
|
|
|
|
end
|
|
|
|
| None -> begin
|
|
|
|
Lwt_stream.get operation_stream >>= function
|
|
|
|
| Some (kind, shell, protocol_data) when filter_result kind ->
|
|
|
|
let bytes = Data_encoding.Binary.to_bytes_exn
|
|
|
|
Next_proto.operation_data_encoding
|
|
|
|
protocol_data in
|
|
|
|
let protocol_data = Data_encoding.Binary.of_bytes_exn
|
|
|
|
Proto.operation_data_encoding
|
|
|
|
bytes in
|
|
|
|
Lwt.return_some [ { Proto.shell ; protocol_data } ]
|
|
|
|
| _ -> Lwt.return_none
|
|
|
|
end
|
|
|
|
in
|
|
|
|
let shutdown () = Lwt_watcher.shutdown stopper in
|
|
|
|
RPC_answer.return_stream { next ; shutdown }
|
|
|
|
end ;
|
|
|
|
|
|
|
|
return !dir
|