2019-09-05 17:21:01 +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. *)
|
|
|
|
(* *)
|
|
|
|
(*****************************************************************************)
|
|
|
|
|
|
|
|
(* Tezos Protocol Implementation - Protocol Signature Instance *)
|
|
|
|
|
|
|
|
type block_header_data = Alpha_context.Block_header.protocol_data
|
2020-02-12 20:40:17 +04:00
|
|
|
|
2019-09-05 17:21:01 +04:00
|
|
|
type block_header = Alpha_context.Block_header.t = {
|
2020-02-12 20:40:17 +04:00
|
|
|
shell : Block_header.shell_header;
|
|
|
|
protocol_data : block_header_data;
|
2019-09-05 17:21:01 +04:00
|
|
|
}
|
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let block_header_data_encoding =
|
|
|
|
Alpha_context.Block_header.protocol_data_encoding
|
2019-09-05 17:21:01 +04:00
|
|
|
|
|
|
|
type block_header_metadata = Apply_results.block_metadata
|
2020-02-12 20:40:17 +04:00
|
|
|
|
2019-09-05 17:21:01 +04:00
|
|
|
let block_header_metadata_encoding = Apply_results.block_metadata_encoding
|
|
|
|
|
|
|
|
type operation_data = Alpha_context.packed_protocol_data =
|
2020-02-12 20:40:17 +04:00
|
|
|
| Operation_data :
|
|
|
|
'kind Alpha_context.Operation.protocol_data
|
|
|
|
-> operation_data
|
|
|
|
|
2019-09-05 17:21:01 +04:00
|
|
|
let operation_data_encoding = Alpha_context.Operation.protocol_data_encoding
|
|
|
|
|
|
|
|
type operation_receipt = Apply_results.packed_operation_metadata =
|
2020-02-12 20:40:17 +04:00
|
|
|
| Operation_metadata :
|
|
|
|
'kind Apply_results.operation_metadata
|
|
|
|
-> operation_receipt
|
|
|
|
| No_operation_metadata : operation_receipt
|
|
|
|
|
|
|
|
let operation_receipt_encoding = Apply_results.operation_metadata_encoding
|
2019-09-05 17:21:01 +04:00
|
|
|
|
|
|
|
let operation_data_and_receipt_encoding =
|
|
|
|
Apply_results.operation_data_and_metadata_encoding
|
|
|
|
|
|
|
|
type operation = Alpha_context.packed_operation = {
|
2020-02-12 20:40:17 +04:00
|
|
|
shell : Operation.shell_header;
|
|
|
|
protocol_data : operation_data;
|
2019-09-05 17:21:01 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
let acceptable_passes = Alpha_context.Operation.acceptable_passes
|
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let max_block_length = Alpha_context.Block_header.max_header_length
|
2019-09-05 17:21:01 +04:00
|
|
|
|
|
|
|
let max_operation_data_length =
|
|
|
|
Alpha_context.Constants.max_operation_data_length
|
|
|
|
|
|
|
|
let validation_passes =
|
|
|
|
let max_anonymous_operations =
|
2020-02-12 20:40:17 +04:00
|
|
|
Alpha_context.Constants.max_revelations_per_block
|
|
|
|
+ (* allow 100 wallet activations or denunciations per block *) 100
|
|
|
|
in
|
|
|
|
Updater.
|
|
|
|
[ {max_size = 32 * 1024; max_op = Some 32};
|
|
|
|
(* 32 endorsements *)
|
|
|
|
{max_size = 32 * 1024; max_op = None};
|
|
|
|
(* 32k of voting operations *)
|
|
|
|
{
|
|
|
|
max_size = max_anonymous_operations * 1024;
|
|
|
|
max_op = Some max_anonymous_operations;
|
|
|
|
};
|
|
|
|
{max_size = 512 * 1024; max_op = None} ]
|
|
|
|
|
|
|
|
(* 512kB *)
|
2019-09-05 17:21:01 +04:00
|
|
|
|
|
|
|
let rpc_services =
|
|
|
|
Alpha_services.register () ;
|
|
|
|
Services_registration.get_rpc_services ()
|
|
|
|
|
|
|
|
type validation_mode =
|
|
|
|
| Application of {
|
2020-02-12 20:40:17 +04:00
|
|
|
block_header : Alpha_context.Block_header.t;
|
|
|
|
baker : Alpha_context.public_key_hash;
|
|
|
|
block_delay : Alpha_context.Period.t;
|
2019-09-05 17:21:01 +04:00
|
|
|
}
|
|
|
|
| Partial_application of {
|
2020-02-12 20:40:17 +04:00
|
|
|
block_header : Alpha_context.Block_header.t;
|
|
|
|
baker : Alpha_context.public_key_hash;
|
|
|
|
block_delay : Alpha_context.Period.t;
|
2019-09-05 17:21:01 +04:00
|
|
|
}
|
2020-02-12 20:40:17 +04:00
|
|
|
| Partial_construction of {predecessor : Block_hash.t}
|
2019-09-05 17:21:01 +04:00
|
|
|
| Full_construction of {
|
2020-02-12 20:40:17 +04:00
|
|
|
predecessor : Block_hash.t;
|
|
|
|
protocol_data : Alpha_context.Block_header.contents;
|
|
|
|
baker : Alpha_context.public_key_hash;
|
|
|
|
block_delay : Alpha_context.Period.t;
|
2019-09-05 17:21:01 +04:00
|
|
|
}
|
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
type validation_state = {
|
|
|
|
mode : validation_mode;
|
|
|
|
chain_id : Chain_id.t;
|
|
|
|
ctxt : Alpha_context.t;
|
|
|
|
op_count : int;
|
|
|
|
}
|
2019-09-05 17:21:01 +04:00
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let current_context {ctxt; _} = return (Alpha_context.finalize ctxt).context
|
2019-09-05 17:21:01 +04:00
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let begin_partial_application ~chain_id ~ancestor_context:ctxt
|
|
|
|
~predecessor_timestamp ~predecessor_fitness
|
2019-09-05 17:21:01 +04:00
|
|
|
(block_header : Alpha_context.Block_header.t) =
|
|
|
|
let level = block_header.shell.level in
|
|
|
|
let fitness = predecessor_fitness in
|
|
|
|
let timestamp = block_header.shell.timestamp in
|
2020-02-12 20:40:17 +04:00
|
|
|
Alpha_context.prepare ~level ~predecessor_timestamp ~timestamp ~fitness ctxt
|
|
|
|
>>=? fun ctxt ->
|
|
|
|
Apply.begin_application ctxt chain_id block_header predecessor_timestamp
|
|
|
|
>>=? fun (ctxt, baker, block_delay) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
let mode =
|
|
|
|
Partial_application
|
2020-02-12 20:40:17 +04:00
|
|
|
{block_header; baker = Signature.Public_key.hash baker; block_delay}
|
|
|
|
in
|
|
|
|
return {mode; chain_id; ctxt; op_count = 0}
|
2019-09-05 17:21:01 +04:00
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let begin_application ~chain_id ~predecessor_context:ctxt
|
|
|
|
~predecessor_timestamp ~predecessor_fitness
|
2019-09-05 17:21:01 +04:00
|
|
|
(block_header : Alpha_context.Block_header.t) =
|
|
|
|
let level = block_header.shell.level in
|
|
|
|
let fitness = predecessor_fitness in
|
|
|
|
let timestamp = block_header.shell.timestamp in
|
2020-02-12 20:40:17 +04:00
|
|
|
Alpha_context.prepare ~level ~predecessor_timestamp ~timestamp ~fitness ctxt
|
|
|
|
>>=? fun ctxt ->
|
|
|
|
Apply.begin_application ctxt chain_id block_header predecessor_timestamp
|
|
|
|
>>=? fun (ctxt, baker, block_delay) ->
|
2019-10-17 13:45:27 +04:00
|
|
|
let mode =
|
2020-02-12 20:40:17 +04:00
|
|
|
Application
|
|
|
|
{block_header; baker = Signature.Public_key.hash baker; block_delay}
|
|
|
|
in
|
|
|
|
return {mode; chain_id; ctxt; op_count = 0}
|
2019-09-05 17:21:01 +04:00
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let begin_construction ~chain_id ~predecessor_context:ctxt
|
|
|
|
~predecessor_timestamp ~predecessor_level:pred_level
|
|
|
|
~predecessor_fitness:pred_fitness ~predecessor ~timestamp
|
|
|
|
?(protocol_data : block_header_data option) () =
|
2019-09-05 17:21:01 +04:00
|
|
|
let level = Int32.succ pred_level in
|
|
|
|
let fitness = pred_fitness in
|
2020-02-12 20:40:17 +04:00
|
|
|
Alpha_context.prepare ~level ~predecessor_timestamp ~timestamp ~fitness ctxt
|
|
|
|
>>=? fun ctxt ->
|
|
|
|
( match protocol_data with
|
|
|
|
| None ->
|
|
|
|
Apply.begin_partial_construction ctxt
|
|
|
|
>>=? fun ctxt ->
|
|
|
|
let mode = Partial_construction {predecessor} in
|
|
|
|
return (mode, ctxt)
|
|
|
|
| Some proto_header ->
|
|
|
|
Apply.begin_full_construction
|
|
|
|
ctxt
|
|
|
|
predecessor_timestamp
|
|
|
|
proto_header.contents
|
|
|
|
>>=? fun (ctxt, protocol_data, baker, block_delay) ->
|
|
|
|
let mode =
|
|
|
|
let baker = Signature.Public_key.hash baker in
|
|
|
|
Full_construction {predecessor; baker; protocol_data; block_delay}
|
|
|
|
in
|
|
|
|
return (mode, ctxt) )
|
|
|
|
>>=? fun (mode, ctxt) -> return {mode; chain_id; ctxt; op_count = 0}
|
2019-09-05 17:21:01 +04:00
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let apply_operation ({mode; chain_id; ctxt; op_count; _} as data)
|
2019-09-05 17:21:01 +04:00
|
|
|
(operation : Alpha_context.packed_operation) =
|
|
|
|
match mode with
|
2020-02-12 20:40:17 +04:00
|
|
|
| Partial_application _
|
|
|
|
when not
|
|
|
|
(List.exists
|
|
|
|
(Compare.Int.equal 0)
|
|
|
|
(Alpha_context.Operation.acceptable_passes operation)) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
(* Multipass validation only considers operations in pass 0. *)
|
|
|
|
let op_count = op_count + 1 in
|
2020-02-12 20:40:17 +04:00
|
|
|
return ({data with ctxt; op_count}, No_operation_metadata)
|
2019-09-05 17:21:01 +04:00
|
|
|
| _ ->
|
2020-02-12 20:40:17 +04:00
|
|
|
let {shell; protocol_data = Operation_data protocol_data} = operation in
|
|
|
|
let operation : _ Alpha_context.operation = {shell; protocol_data} in
|
|
|
|
let (predecessor, baker) =
|
2019-09-05 17:21:01 +04:00
|
|
|
match mode with
|
|
|
|
| Partial_application
|
2020-02-12 20:40:17 +04:00
|
|
|
{block_header = {shell = {predecessor; _}; _}; baker}
|
|
|
|
| Application {block_header = {shell = {predecessor; _}; _}; baker}
|
|
|
|
| Full_construction {predecessor; baker; _} ->
|
|
|
|
(predecessor, baker)
|
|
|
|
| Partial_construction {predecessor} ->
|
|
|
|
(predecessor, Signature.Public_key_hash.zero)
|
2019-09-05 17:21:01 +04:00
|
|
|
in
|
2020-02-12 20:40:17 +04:00
|
|
|
Apply.apply_operation
|
|
|
|
ctxt
|
|
|
|
chain_id
|
|
|
|
Optimized
|
|
|
|
predecessor
|
|
|
|
baker
|
2019-09-05 17:21:01 +04:00
|
|
|
(Alpha_context.Operation.hash operation)
|
2020-02-12 20:40:17 +04:00
|
|
|
operation
|
|
|
|
>>=? fun (ctxt, result) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
let op_count = op_count + 1 in
|
2020-02-12 20:40:17 +04:00
|
|
|
return ({data with ctxt; op_count}, Operation_metadata result)
|
2019-09-05 17:21:01 +04:00
|
|
|
|
2020-02-12 20:40:17 +04:00
|
|
|
let finalize_block {mode; ctxt; op_count} =
|
2019-09-05 17:21:01 +04:00
|
|
|
match mode with
|
|
|
|
| Partial_construction _ ->
|
|
|
|
let level = Alpha_context.Level.current ctxt in
|
2020-02-12 20:40:17 +04:00
|
|
|
Alpha_context.Vote.get_current_period_kind ctxt
|
|
|
|
>>=? fun voting_period_kind ->
|
2019-09-05 17:21:01 +04:00
|
|
|
let baker = Signature.Public_key_hash.zero in
|
|
|
|
Signature.Public_key_hash.Map.fold
|
|
|
|
(fun delegate deposit ctxt ->
|
2020-02-12 20:40:17 +04:00
|
|
|
ctxt
|
|
|
|
>>=? fun ctxt ->
|
|
|
|
Alpha_context.Delegate.freeze_deposit ctxt delegate deposit)
|
2019-09-05 17:21:01 +04:00
|
|
|
(Alpha_context.get_deposits ctxt)
|
2020-02-12 20:40:17 +04:00
|
|
|
(return ctxt)
|
|
|
|
>>=? fun ctxt ->
|
2019-09-05 17:21:01 +04:00
|
|
|
let ctxt = Alpha_context.finalize ctxt in
|
2020-02-12 20:40:17 +04:00
|
|
|
return
|
|
|
|
( ctxt,
|
|
|
|
Apply_results.
|
|
|
|
{
|
|
|
|
baker;
|
|
|
|
level;
|
|
|
|
voting_period_kind;
|
|
|
|
nonce_hash = None;
|
|
|
|
consumed_gas = Z.zero;
|
|
|
|
deactivated = [];
|
|
|
|
balance_updates = [];
|
|
|
|
} )
|
|
|
|
| Partial_application {block_header; baker; block_delay} ->
|
2019-10-17 13:45:27 +04:00
|
|
|
let level = Alpha_context.Level.current ctxt in
|
|
|
|
let included_endorsements = Alpha_context.included_endorsements ctxt in
|
2020-02-12 20:40:17 +04:00
|
|
|
Apply.check_minimum_endorsements
|
|
|
|
ctxt
|
2019-10-17 13:45:27 +04:00
|
|
|
block_header.protocol_data.contents
|
2020-02-12 20:40:17 +04:00
|
|
|
block_delay
|
|
|
|
included_endorsements
|
|
|
|
>>=? fun () ->
|
|
|
|
Alpha_context.Vote.get_current_period_kind ctxt
|
|
|
|
>>=? fun voting_period_kind ->
|
2019-09-05 17:21:01 +04:00
|
|
|
let ctxt = Alpha_context.finalize ctxt in
|
2020-02-12 20:40:17 +04:00
|
|
|
return
|
|
|
|
( ctxt,
|
|
|
|
Apply_results.
|
|
|
|
{
|
|
|
|
baker;
|
|
|
|
level;
|
|
|
|
voting_period_kind;
|
|
|
|
nonce_hash = None;
|
|
|
|
consumed_gas = Z.zero;
|
|
|
|
deactivated = [];
|
|
|
|
balance_updates = [];
|
|
|
|
} )
|
2019-09-05 17:21:01 +04:00
|
|
|
| Application
|
2020-02-12 20:40:17 +04:00
|
|
|
{ baker;
|
|
|
|
block_delay;
|
|
|
|
block_header = {protocol_data = {contents = protocol_data; _}; _} }
|
|
|
|
| Full_construction {protocol_data; baker; block_delay; _} ->
|
|
|
|
Apply.finalize_application ctxt protocol_data baker ~block_delay
|
|
|
|
>>=? fun (ctxt, receipt) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
let level = Alpha_context.Level.current ctxt in
|
|
|
|
let priority = protocol_data.priority in
|
|
|
|
let raw_level = Alpha_context.Raw_level.to_int32 level.level in
|
|
|
|
let fitness = Alpha_context.Fitness.current ctxt in
|
|
|
|
let commit_message =
|
|
|
|
Format.asprintf
|
2019-10-17 13:45:27 +04:00
|
|
|
"lvl %ld, fit 1:%Ld, prio %d, %d ops"
|
2020-02-12 20:40:17 +04:00
|
|
|
raw_level
|
|
|
|
fitness
|
|
|
|
priority
|
|
|
|
op_count
|
|
|
|
in
|
2019-09-05 17:21:01 +04:00
|
|
|
let ctxt = Alpha_context.finalize ~commit_message ctxt in
|
|
|
|
return (ctxt, receipt)
|
|
|
|
|
|
|
|
let compare_operations op1 op2 =
|
|
|
|
let open Alpha_context in
|
2020-02-12 20:40:17 +04:00
|
|
|
let (Operation_data op1) = op1.protocol_data in
|
|
|
|
let (Operation_data op2) = op2.protocol_data in
|
|
|
|
match (op1.contents, op2.contents) with
|
|
|
|
| (Single (Endorsement _), Single (Endorsement _)) ->
|
|
|
|
0
|
|
|
|
| (_, Single (Endorsement _)) ->
|
|
|
|
1
|
|
|
|
| (Single (Endorsement _), _) ->
|
|
|
|
-1
|
|
|
|
| (Single (Seed_nonce_revelation _), Single (Seed_nonce_revelation _)) ->
|
|
|
|
0
|
|
|
|
| (_, Single (Seed_nonce_revelation _)) ->
|
|
|
|
1
|
|
|
|
| (Single (Seed_nonce_revelation _), _) ->
|
|
|
|
-1
|
|
|
|
| ( Single (Double_endorsement_evidence _),
|
|
|
|
Single (Double_endorsement_evidence _) ) ->
|
|
|
|
0
|
|
|
|
| (_, Single (Double_endorsement_evidence _)) ->
|
|
|
|
1
|
|
|
|
| (Single (Double_endorsement_evidence _), _) ->
|
|
|
|
-1
|
|
|
|
| (Single (Double_baking_evidence _), Single (Double_baking_evidence _)) ->
|
|
|
|
0
|
|
|
|
| (_, Single (Double_baking_evidence _)) ->
|
|
|
|
1
|
|
|
|
| (Single (Double_baking_evidence _), _) ->
|
|
|
|
-1
|
|
|
|
| (Single (Activate_account _), Single (Activate_account _)) ->
|
|
|
|
0
|
|
|
|
| (_, Single (Activate_account _)) ->
|
|
|
|
1
|
|
|
|
| (Single (Activate_account _), _) ->
|
|
|
|
-1
|
|
|
|
| (Single (Proposals _), Single (Proposals _)) ->
|
|
|
|
0
|
|
|
|
| (_, Single (Proposals _)) ->
|
|
|
|
1
|
|
|
|
| (Single (Proposals _), _) ->
|
|
|
|
-1
|
|
|
|
| (Single (Ballot _), Single (Ballot _)) ->
|
|
|
|
0
|
|
|
|
| (_, Single (Ballot _)) ->
|
|
|
|
1
|
|
|
|
| (Single (Ballot _), _) ->
|
|
|
|
-1
|
2019-09-05 17:21:01 +04:00
|
|
|
(* Manager operations with smaller counter are pre-validated first. *)
|
2020-02-12 20:40:17 +04:00
|
|
|
| (Single (Manager_operation op1), Single (Manager_operation op2)) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
Z.compare op1.counter op2.counter
|
2020-02-12 20:40:17 +04:00
|
|
|
| (Cons (Manager_operation op1, _), Single (Manager_operation op2)) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
Z.compare op1.counter op2.counter
|
2020-02-12 20:40:17 +04:00
|
|
|
| (Single (Manager_operation op1), Cons (Manager_operation op2, _)) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
Z.compare op1.counter op2.counter
|
2020-02-12 20:40:17 +04:00
|
|
|
| (Cons (Manager_operation op1, _), Cons (Manager_operation op2, _)) ->
|
2019-09-05 17:21:01 +04:00
|
|
|
Z.compare op1.counter op2.counter
|
|
|
|
|
|
|
|
let init ctxt block_header =
|
|
|
|
let level = block_header.Block_header.level in
|
|
|
|
let fitness = block_header.fitness in
|
|
|
|
let timestamp = block_header.timestamp in
|
2020-02-12 20:40:17 +04:00
|
|
|
let typecheck (ctxt : Alpha_context.context)
|
|
|
|
(script : Alpha_context.Script.t) =
|
|
|
|
Script_ir_translator.parse_script ctxt ~legacy:false script
|
|
|
|
>>=? fun (Ex_script parsed_script, ctxt) ->
|
|
|
|
Script_ir_translator.extract_big_map_diff
|
|
|
|
ctxt
|
|
|
|
Optimized
|
|
|
|
parsed_script.storage_type
|
|
|
|
parsed_script.storage
|
|
|
|
~to_duplicate:Script_ir_translator.no_big_map_id
|
|
|
|
~to_update:Script_ir_translator.no_big_map_id
|
|
|
|
~temporary:false
|
|
|
|
>>=? fun (storage, big_map_diff, ctxt) ->
|
|
|
|
Script_ir_translator.unparse_data
|
|
|
|
ctxt
|
|
|
|
Optimized
|
|
|
|
parsed_script.storage_type
|
|
|
|
storage
|
|
|
|
>>=? fun (storage, ctxt) ->
|
|
|
|
let storage =
|
|
|
|
Alpha_context.Script.lazy_expr (Micheline.strip_locations storage)
|
|
|
|
in
|
|
|
|
return (({script with storage}, big_map_diff), ctxt)
|
2019-09-05 17:21:01 +04:00
|
|
|
in
|
2020-02-12 20:40:17 +04:00
|
|
|
Alpha_context.prepare_first_block ~typecheck ~level ~timestamp ~fitness ctxt
|
|
|
|
>>=? fun ctxt -> return (Alpha_context.finalize ctxt)
|
|
|
|
|
|
|
|
(* Vanity nonce: 0050006865723388 *)
|