Add high watermark checking for blocks and endorsements

This commit is contained in:
Benjamin Canou 2018-07-04 21:34:45 +02:00 committed by Grégoire Henry
parent 4a436c2f18
commit 96c36f1698
No known key found for this signature in database
GPG Key ID: 50D984F20BD445D2
6 changed files with 102 additions and 23 deletions

View File

@ -27,6 +27,63 @@ open Signer_logging
let log = lwt_log_notice let log = lwt_log_notice
module High_watermark = struct
let encoding =
let open Data_encoding in
let raw_hash =
conv Blake2B.to_bytes Blake2B.of_bytes_exn bytes in
conv
(List.map (fun (chain_id, marks) -> Chain_id.to_b58check chain_id, marks))
(List.map (fun (chain_id, marks) -> Chain_id.of_b58check_exn chain_id, marks)) @@
assoc @@
conv
(List.map (fun (pkh, mark) -> Signature.Public_key_hash.to_b58check pkh, mark))
(List.map (fun (pkh, mark) -> Signature.Public_key_hash.of_b58check_exn pkh, mark)) @@
assoc @@
obj2
(req "level" int32)
(req "hash" raw_hash)
let mark_if_block_or_endorsement (cctxt : #Client_context.wallet) pkh bytes =
let mark art name get_level =
let file = name ^ "_high_watermark" in
cctxt#with_lock @@ fun () ->
cctxt#load file ~default:[] encoding >>=? fun all ->
if MBytes.length bytes < 9 then
failwith "byte sequence too short to be %s %s" art name
else
let hash = Blake2B.hash_bytes [ bytes ] in
let chain_id = Chain_id.of_bytes_exn (MBytes.sub bytes 1 4) in
let level = get_level () in
begin match List.assoc_opt chain_id all with
| None -> return ()
| Some marks ->
match List.assoc_opt pkh marks with
| None -> return ()
| Some (previous_level, previous_hash) ->
if previous_level > level then
failwith "%s level %ld below high watermark %ld" name level previous_level
else if previous_level = level && previous_hash <> hash then
failwith "%s level %ld already signed with a different header" name level
else
return ()
end >>=? fun () ->
let rec update = function
| [] -> [ chain_id, [ pkh, (level, hash) ] ]
| (e_chain_id, marks) :: rest ->
if chain_id = e_chain_id then
let marks = (pkh, (level, hash)) :: List.filter (fun (pkh', _) -> pkh <> pkh') marks in
(e_chain_id, marks) :: rest
else
(e_chain_id, marks) :: update rest in
cctxt#write file (update all) encoding in
if MBytes.length bytes > 0 && MBytes.get_uint8 bytes 0 = 0x01 then
mark "a" "block" (fun () -> MBytes.get_int32 bytes 5)
else if MBytes.length bytes > 0 && MBytes.get_uint8 bytes 0 = 0x02 then
mark "an" "endorsement" (fun () -> MBytes.get_int32 bytes (MBytes.length bytes - 4))
else return ()
end
module Authorized_key = module Authorized_key =
Client_aliases.Alias (struct Client_aliases.Alias (struct
@ -50,7 +107,7 @@ let check_magic_byte magic_bytes data =
let sign let sign
(cctxt : #Client_context.wallet) (cctxt : #Client_context.wallet)
Signer_messages.Sign.Request.{ pkh ; data ; signature } Signer_messages.Sign.Request.{ pkh ; data ; signature }
?magic_bytes ~require_auth = ?magic_bytes ~check_high_watermark ~require_auth =
log Tag.DSL.(fun f -> log Tag.DSL.(fun f ->
f "Request for signing %d bytes of data for key %a, magic byte = %02X" f "Request for signing %d bytes of data for key %a, magic byte = %02X"
-% t event "request_for_signing" -% t event "request_for_signing"
@ -77,6 +134,10 @@ let sign
f "Signing data for key %s" f "Signing data for key %s"
-% t event "signing_data" -% t event "signing_data"
-% s Client_keys.Logging.tag name) >>= fun () -> -% s Client_keys.Logging.tag name) >>= fun () ->
begin if check_high_watermark then
High_watermark.mark_if_block_or_endorsement cctxt pkh data
else return ()
end >>=? fun () ->
Client_keys.sign cctxt sk_uri data >>=? fun signature -> Client_keys.sign cctxt sk_uri data >>=? fun signature ->
return signature return signature

View File

@ -26,11 +26,11 @@
let log = Signer_logging.lwt_log_notice let log = Signer_logging.lwt_log_notice
open Signer_logging open Signer_logging
let run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode = let run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~check_high_watermark ~require_auth mode =
let dir = RPC_directory.empty in let dir = RPC_directory.empty in
let dir = let dir =
RPC_directory.register1 dir Signer_services.sign begin fun pkh signature data -> RPC_directory.register1 dir Signer_services.sign begin fun pkh signature data ->
Handler.sign cctxt { pkh ; data ; signature } ?magic_bytes ~require_auth Handler.sign cctxt { pkh ; data ; signature } ?magic_bytes ~check_high_watermark ~require_auth
end in end in
let dir = let dir =
RPC_directory.register1 dir Signer_services.public_key begin fun pkh () () -> RPC_directory.register1 dir Signer_services.public_key begin fun pkh () () ->
@ -63,7 +63,7 @@ let run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode
failwith "Port already in use." failwith "Port already in use."
| exn -> Lwt.return (error_exn exn)) | exn -> Lwt.return (error_exn exn))
let run_https (cctxt : #Client_context.wallet) ~host ~port ~cert ~key ?magic_bytes ~require_auth = let run_https (cctxt : #Client_context.wallet) ~host ~port ~cert ~key ?magic_bytes ~check_high_watermark ~require_auth =
Lwt_utils_unix.getaddrinfo ~passive:true ~node:host ~service:(string_of_int port) >>= function Lwt_utils_unix.getaddrinfo ~passive:true ~node:host ~service:(string_of_int port) >>= function
| []-> | []->
failwith "Cannot resolve listening address: %S" host failwith "Cannot resolve listening address: %S" host
@ -75,9 +75,9 @@ let run_https (cctxt : #Client_context.wallet) ~host ~port ~cert ~key ?magic_byt
-% s port_number port) >>= fun () -> -% s port_number port) >>= fun () ->
let mode : Conduit_lwt_unix.server = let mode : Conduit_lwt_unix.server =
`TLS (`Crt_file_path cert, `Key_file_path key, `No_password, `Port port) in `TLS (`Crt_file_path cert, `Key_file_path key, `No_password, `Port port) in
run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~check_high_watermark ~require_auth mode
let run_http (cctxt : #Client_context.wallet) ~host ~port ?magic_bytes ~require_auth = let run_http (cctxt : #Client_context.wallet) ~host ~port ?magic_bytes ~check_high_watermark ~require_auth =
Lwt_utils_unix.getaddrinfo ~passive:true ~node:host ~service:(string_of_int port) >>= function Lwt_utils_unix.getaddrinfo ~passive:true ~node:host ~service:(string_of_int port) >>= function
| [] -> | [] ->
failwith "Cannot resolve listening address: %S" host failwith "Cannot resolve listening address: %S" host
@ -89,4 +89,4 @@ let run_http (cctxt : #Client_context.wallet) ~host ~port ?magic_bytes ~require_
-% s port_number port) >>= fun () -> -% s port_number port) >>= fun () ->
let mode : Conduit_lwt_unix.server = let mode : Conduit_lwt_unix.server =
`TCP (`Port port) in `TCP (`Port port) in
run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~check_high_watermark ~require_auth mode

View File

@ -27,6 +27,7 @@ val run_https:
#Client_context.io_wallet -> #Client_context.io_wallet ->
host:string -> port:int -> cert:string -> key:string -> host:string -> port:int -> cert:string -> key:string ->
?magic_bytes: int list -> ?magic_bytes: int list ->
check_high_watermark: bool ->
require_auth: bool -> require_auth: bool ->
'a tzresult Lwt.t 'a tzresult Lwt.t
@ -34,5 +35,6 @@ val run_http:
#Client_context.io_wallet -> #Client_context.io_wallet ->
host:string -> port:int -> host:string -> port:int ->
?magic_bytes: int list -> ?magic_bytes: int list ->
check_high_watermark: bool ->
require_auth: bool -> require_auth: bool ->
'a tzresult Lwt.t 'a tzresult Lwt.t

View File

@ -82,13 +82,25 @@ let magic_bytes_arg =
failwith "Bad format for magic bytes, a series of numbers \ failwith "Bad format for magic bytes, a series of numbers \
is expected, separated by commas.")) is expected, separated by commas."))
let high_watermark_switch =
Clic.switch
~doc: "high watermark restriction\n\
Stores the highest level signed for blocks and endorsements \
for each address, and forbids to sign a level that is \
inferior or equal afterwards, except for the exact same \
input data."
~short: 'W'
~long: "check-high-watermark"
()
let commands base_dir require_auth = let commands base_dir require_auth =
Client_keys_commands.commands None @ Client_keys_commands.commands None @
Tezos_signer_backends.Ledger.commands () @ Tezos_signer_backends.Ledger.commands () @
[ command ~group [ command ~group
~desc: "Launch a signer daemon over a TCP socket." ~desc: "Launch a signer daemon over a TCP socket."
(args3 (args4
magic_bytes_arg magic_bytes_arg
high_watermark_switch
(default_arg (default_arg
~doc: "listening address or host name" ~doc: "listening address or host name"
~short: 'a' ~short: 'a'
@ -104,16 +116,17 @@ let commands base_dir require_auth =
~default: default_tcp_port ~default: default_tcp_port
(parameter (fun _ s -> return s)))) (parameter (fun _ s -> return s))))
(prefixes [ "launch" ; "socket" ; "signer" ] @@ stop) (prefixes [ "launch" ; "socket" ; "signer" ] @@ stop)
(fun (magic_bytes, host, port) cctxt -> (fun (magic_bytes, check_high_watermark, host, port) cctxt ->
Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () ->
Socket_daemon.run Socket_daemon.run
cctxt (Tcp (host, port, [AI_SOCKTYPE SOCK_STREAM])) cctxt (Tcp (host, port, [AI_SOCKTYPE SOCK_STREAM]))
?magic_bytes ~require_auth >>=? fun _ -> ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ ->
return_unit) ; return_unit) ;
command ~group command ~group
~desc: "Launch a signer daemon over a local Unix socket." ~desc: "Launch a signer daemon over a local Unix socket."
(args2 (args3
magic_bytes_arg magic_bytes_arg
high_watermark_switch
(default_arg (default_arg
~doc: "path to the local socket file" ~doc: "path to the local socket file"
~short: 's' ~short: 's'
@ -122,15 +135,16 @@ let commands base_dir require_auth =
~default: (Filename.concat base_dir "socket") ~default: (Filename.concat base_dir "socket")
(parameter (fun _ s -> return s)))) (parameter (fun _ s -> return s))))
(prefixes [ "launch" ; "local" ; "signer" ] @@ stop) (prefixes [ "launch" ; "local" ; "signer" ] @@ stop)
(fun (magic_bytes, path) cctxt -> (fun (magic_bytes, check_high_watermark, path) cctxt ->
Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () ->
Socket_daemon.run Socket_daemon.run
cctxt (Unix path) ?magic_bytes ~require_auth >>=? fun _ -> cctxt (Unix path) ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ ->
return_unit) ; return_unit) ;
command ~group command ~group
~desc: "Launch a signer daemon over HTTP." ~desc: "Launch a signer daemon over HTTP."
(args3 (args4
magic_bytes_arg magic_bytes_arg
high_watermark_switch
(default_arg (default_arg
~doc: "listening address or host name" ~doc: "listening address or host name"
~short: 'a' ~short: 'a'
@ -149,13 +163,14 @@ let commands base_dir require_auth =
try return (int_of_string x) try return (int_of_string x)
with Failure _ -> failwith "Invalid port %s" x)))) with Failure _ -> failwith "Invalid port %s" x))))
(prefixes [ "launch" ; "http" ; "signer" ] @@ stop) (prefixes [ "launch" ; "http" ; "signer" ] @@ stop)
(fun (magic_bytes, host, port) cctxt -> (fun (magic_bytes, check_high_watermark, host, port) cctxt ->
Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () ->
Http_daemon.run_http cctxt ~host ~port ?magic_bytes ~require_auth) ; Http_daemon.run_http cctxt ~host ~port ?magic_bytes ~check_high_watermark ~require_auth) ;
command ~group command ~group
~desc: "Launch a signer daemon over HTTPS." ~desc: "Launch a signer daemon over HTTPS."
(args3 (args4
magic_bytes_arg magic_bytes_arg
high_watermark_switch
(default_arg (default_arg
~doc: "listening address or host name" ~doc: "listening address or host name"
~short: 'a' ~short: 'a'
@ -190,9 +205,9 @@ let commands base_dir require_auth =
failwith "No such TLS key file %s" s failwith "No such TLS key file %s" s
else else
return s)) @@ stop) return s)) @@ stop)
(fun (magic_bytes, host, port) cert key cctxt -> (fun (magic_bytes, check_high_watermark, host, port) cert key cctxt ->
Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () ->
Http_daemon.run_https cctxt ~host ~port ~cert ~key ?magic_bytes ~require_auth) ; Http_daemon.run_https cctxt ~host ~port ~cert ~key ?magic_bytes ~check_high_watermark ~require_auth) ;
command ~group command ~group
~desc: "Authorize a given public key to perform signing requests." ~desc: "Authorize a given public key to perform signing requests."
(args1 (args1

View File

@ -28,11 +28,11 @@ open Signer_messages
let log = lwt_log_notice let log = lwt_log_notice
let handle_client ?magic_bytes ~require_auth cctxt fd = let handle_client ?magic_bytes ~check_high_watermark ~require_auth cctxt fd =
Lwt_utils_unix.Socket.recv fd Request.encoding >>=? function Lwt_utils_unix.Socket.recv fd Request.encoding >>=? function
| Sign req -> | Sign req ->
let encoding = result_encoding Sign.Response.encoding in let encoding = result_encoding Sign.Response.encoding in
Handler.sign cctxt req ?magic_bytes ~require_auth >>= fun res -> Handler.sign cctxt req ?magic_bytes ~check_high_watermark ~require_auth >>= fun res ->
Lwt_utils_unix.Socket.send fd encoding res >>= fun _ -> Lwt_utils_unix.Socket.send fd encoding res >>= fun _ ->
Lwt_unix.close fd >>= fun () -> Lwt_unix.close fd >>= fun () ->
return_unit return_unit
@ -54,7 +54,7 @@ let handle_client ?magic_bytes ~require_auth cctxt fd =
Lwt_unix.close fd >>= fun () -> Lwt_unix.close fd >>= fun () ->
return_unit return_unit
let run (cctxt : #Client_context.wallet) path ?magic_bytes ~require_auth = let run (cctxt : #Client_context.wallet) path ?magic_bytes ~check_high_watermark ~require_auth =
let open Lwt_utils_unix.Socket in let open Lwt_utils_unix.Socket in
begin begin
match path with match path with
@ -84,7 +84,7 @@ let run (cctxt : #Client_context.wallet) path ?magic_bytes ~require_auth =
| [Exn End_of_file] -> return_unit | [Exn End_of_file] -> return_unit
| errs -> Lwt.return (Error errs)) | errs -> Lwt.return (Error errs))
(fun () -> (fun () ->
handle_client ?magic_bytes ~require_auth cctxt cfd) handle_client ?magic_bytes ~check_high_watermark ~require_auth cctxt cfd)
end ; end ;
loop fd loop fd
in in

View File

@ -27,5 +27,6 @@ val run:
#Client_context.io_wallet -> #Client_context.io_wallet ->
Lwt_utils_unix.Socket.addr -> Lwt_utils_unix.Socket.addr ->
?magic_bytes: int list -> ?magic_bytes: int list ->
check_high_watermark: bool ->
require_auth: bool -> require_auth: bool ->
'a list tzresult Lwt.t 'a list tzresult Lwt.t