Add high watermark checking for blocks and endorsements
This commit is contained in:
parent
4a436c2f18
commit
96c36f1698
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user