diff --git a/.dockerignore b/.dockerignore index ee1f34894..e05b8e48e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -53,6 +53,7 @@ test/shell/test-state test/shell/test-context test/proto_alpha/test-transaction test/proto_alpha/test-origination +test/proto_alpha/test-endorsement **/*~ **/\#*\# diff --git a/.gitignore b/.gitignore index c15b99eb7..c82dfd4b4 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ /test/shell/test-context /test/proto_alpha/test-transaction /test/proto_alpha/test-origination +/test/proto_alpha/test-endorsement *~ \#*\# diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 391bc6724..6d2f2a570 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -123,6 +123,15 @@ test:proto_alpha:origination: dependencies: - build +test:proto_alpha:endorsement: + stage: test + tags: + - tezos_builder + script: + - make -C test/proto_alpha run-test-endorsement + dependencies: + - build + test:basic.sh: stage: test tags: diff --git a/test/proto_alpha/Makefile b/test/proto_alpha/Makefile index a71b35317..dadfab0ba 100644 --- a/test/proto_alpha/Makefile +++ b/test/proto_alpha/Makefile @@ -68,3 +68,23 @@ test-origination: ${LIB} ${TEST_CONNECTION_IMPLS:.ml=.cmx} clean:: rm -f test-origination +############################################################################ +## Endorsement + +.PHONY:run-test-endorsement +run-test-endorsement: + @echo + ./test-endorsement + +TEST_CONNECTION_IMPLS := \ + proto_alpha_helpers.mli \ + proto_alpha_helpers.ml \ + test_endorsement.ml + +test-endorsement: ${LIB} ${TEST_CONNECTION_IMPLS:.ml=.cmx} + @echo COMPILE $(notdir $@) + @${OCAMLOPT} -linkall -linkpkg ${OCAMLFLAGS} -o $@ $^ + +clean:: + rm -f test-endorsement + diff --git a/test/proto_alpha/test_endorsement.ml b/test/proto_alpha/test_endorsement.ml new file mode 100644 index 000000000..6c8195406 --- /dev/null +++ b/test/proto_alpha/test_endorsement.ml @@ -0,0 +1,229 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2016. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +open Client_embedded_proto_alpha +open Tezos_context + +module Helpers = Proto_alpha_helpers +module Assert = Helpers.Assert + +let test_double_endorsement contract block = + + (* Double endorsement for the same level *) + Helpers.Mining.mine contract block >>=? fun b1 -> + + (* branch root *) + Helpers.Mining.mine contract (`Hash b1) >>=? fun b2 -> + (* changing branch *) + Helpers.Mining.mine contract (`Hash b1) >>=? fun b2' -> + + (* branch root *) + Helpers.Endorse.endorse ~force:true contract (`Hash b2) >>=? fun ops -> + Helpers.Mining.mine ~operations:[ ops ] contract (`Hash b2) >>=? fun _b3 -> + + Helpers.Endorse.endorse ~force:true contract (`Hash b2') >>=? fun ops -> + Helpers.Mining.mine ~operations:[ ops ] contract (`Hash b2') >>=? fun b3' -> + + Helpers.Endorse.endorse ~force:true contract (`Hash b3') >>=? fun ops -> + Helpers.Mining.mine ~operations:[ ops ] contract (`Hash b3') >>=? fun b4' -> + + (* TODO: Inject double endorsement op ! *) + Helpers.Mining.mine contract (`Hash b4') + +(* FIXME: Mining.Invalid_signature is unclassified *) +let test_invalid_signature block = + let public_key = + Environment.Ed25519.Public_key.of_b58check_exn + "edpkv8EUUH68jmo3f7Um5PezmfGrRF24gnfLpH3sVNwJnV5bVCxL2n" in + let secret_key = + Environment.Ed25519.Secret_key.of_b58check_exn + "edskRuR1azSfboG86YPTyxrQgosh5zChf5bVDmptqLTb5EuXAm9rsn\ + DYfTKhq7rDQujdn5WWzwUMeV3agaZ6J2vPQT58jJAJPi" in + let account = + Helpers.Account.create ~keys:(secret_key, public_key) "WRONG SIGNATURE" in + Helpers.Mining.mine account block >>= fun res -> + Assert.generic_economic_error ~msg:__LOC__ res ; + return () + +let contain_tzerror ?(msg="") ~f t = + t >>= function + | Ok _ -> failwith "%s: Expected error found success" msg + | Error error when not (List.exists f error) -> + failwith "@[Unexpected error@ %a@]" pp_print_error error + | _ -> return () + +let test_wrong_delegate contract block = + contain_tzerror ~msg:__LOC__ ~f:begin Assert.ecoproto_error (function + | Mining.Wrong_delegate _ -> true + | _ -> false) + end + (Helpers.Endorse.endorse ~slot:1 ~force:true contract block >>=? fun _ -> + Helpers.Endorse.endorse ~slot:2 ~force:true contract block >>=? fun _ -> + Helpers.Endorse.endorse ~slot:3 ~force:true contract block >>=? fun _ -> + Helpers.Endorse.endorse ~slot:4 ~force:true contract block >>=? fun _ -> + Helpers.Endorse.endorse ~slot:5 ~force:true contract block) + +let test_invalid_endorsement_slot contract block = + Helpers.Endorse.endorse ~slot:~-1 ~force:true contract block >>= fun res -> + Assert.invalid_endorsement_slot ~msg:__LOC__ res ; + Helpers.Endorse.endorse ~slot:16 ~force:true contract block >>= fun res -> + Assert.invalid_endorsement_slot ~msg:__LOC__ res ; + return () + +let test_endorsement_rewards + block ({ Helpers.Account.b1 ; _ } as baccounts) = + let get_endorser_except_b1 accounts = + let account, cpt = ref accounts.(0), ref 0 in + while !account = b1 do + incr cpt ; + account := accounts.(!cpt) + done ; + return (!account, !cpt) in + + let bond = Tez.to_cents Constants.endorsement_bond_cost in + + (* Endorsement Rights *) + (* #1 endorse & inject in a block *) + Helpers.Endorse.endorsers_list block baccounts >>=? fun accounts -> + get_endorser_except_b1 accounts >>=? fun (account0, slot0) -> + Helpers.Account.balance account0 >>=? fun balance0 -> + Helpers.Endorse.endorse ~slot:slot0 ~force:true account0 block >>=? fun ops -> + Helpers.Mining.mine ~operations:[ ops ] b1 block >>=? fun head0 -> + Assert.balance_equal ~msg:__LOC__ account0 + (Int64.sub (Tez.to_cents balance0) bond) >>=? fun () -> + + (* #2 endorse & inject in a block *) + let block0 = `Hash head0 in + Helpers.Endorse.endorsers_list block0 baccounts >>=? fun accounts -> + get_endorser_except_b1 accounts >>=? fun (account1, slot1) -> + Helpers.Account.balance account1 >>=? fun balance1 -> + Helpers.Endorse.endorse ~slot:slot1 ~force:true account1 block0 >>=? fun ops -> + Helpers.Mining.mine ~operations:[ ops ] b1 block0 >>=? fun head1 -> + Assert.balance_equal ~msg:__LOC__ account1 + (Int64.sub (Tez.to_cents balance1) bond) >>=? fun () -> + + (* #3 endorse but the operation is not included in a block, so no reward *) + let block1 = `Hash head1 in + Helpers.Endorse.endorsers_list block1 baccounts >>=? fun accounts -> + get_endorser_except_b1 accounts >>=? fun (account2, slot2) -> + Helpers.Account.balance account2 >>=? fun balance2 -> + Helpers.Endorse.endorse ~slot:slot2 ~force:true account2 block1 >>=? fun _ops -> + Assert.balance_equal ~msg:__LOC__ account2 + (Int64.sub (Tez.to_cents balance2) bond) >>=? fun () -> + + Helpers.Mining.mine b1 (`Hash head1) >>=? fun head2 -> + Helpers.Mining.mine b1 (`Hash head2) >>=? fun head3 -> + + (* Check rewards after one cycle for account0 *) + Helpers.Mining.endorsement_reward b1 block0 >>=? fun rw0 -> + Assert.balance_equal ~msg:__LOC__ account0 + (Int64.add (Tez.to_cents balance0) rw0) >>=? fun () -> + + (* Check rewards after one cycle for account1 *) + Helpers.Mining.endorsement_reward b1 block1 >>=? fun rw1 -> + Assert.balance_equal ~msg:__LOC__ account1 + (Int64.add (Tez.to_cents balance1) rw1) >>=? fun () -> + + (* Check no rewards after one cycle for account2 *) + Assert.balance_equal + ~msg:__LOC__ account2 (Tez.to_cents balance2) >>=? fun () -> + + (* #2 endorse and check reward only on the good chain *) + Helpers.Mining.mine b1 (`Hash head3) >>=? fun head -> + Helpers.Mining.mine b1 (`Hash head3) >>=? fun fork -> + + (* working on head *) + Helpers.Endorse.endorsers_list (`Hash head) baccounts >>=? fun accounts -> + get_endorser_except_b1 accounts >>=? fun (account3, slot3) -> + Helpers.Account.balance account3 >>=? fun balance3 -> + Helpers.Endorse.endorse + ~slot:slot3 ~force:true account3 (`Hash head) >>=? fun ops -> + Helpers.Mining.mine ~operations:[ ops ] b1 (`Hash head) >>=? fun new_head -> + + (* working on fork *) + Helpers.Endorse.endorsers_list (`Hash fork) baccounts >>=? fun accounts -> + get_endorser_except_b1 accounts >>=? fun (account4, slot4) -> + Helpers.Account.balance account4 >>=? fun _balance4 -> + Helpers.Endorse.endorse ~slot:slot4 ~force:true account4 (`Hash fork) >>=? fun ops -> + Helpers.Mining.mine ~operations:[ ops ] b1 (`Hash fork) >>=? fun _new_fork -> + Helpers.Account.balance account4 >>=? fun balance4 -> + + Helpers.Mining.mine b1 (`Hash new_head) >>=? fun head -> + Helpers.Mining.mine b1 (`Hash head) >>=? fun head -> + + (* Check rewards after one cycle *) + Helpers.Mining.endorsement_reward b1 (`Hash new_head) >>=? fun reward -> + Assert.balance_equal ~msg:__LOC__ account3 + (Int64.add (Tez.to_cents balance3) reward) >>=? fun () -> + + (* Check no reward for the fork *) + begin + if account3 = account4 then return () + (* if account4 is different from account3, we need to check that there + is no reward for him since the endorsement was in the fork branch *) + else Assert.balance_equal ~msg:__LOC__ account4 (Tez.to_cents balance4) + end >>=? fun () -> return head + +let test_endorsement_rights contract block = + Helpers.Endorse.endorsement_rights contract block >>|? fun possibilities -> + possibilities <> [] + +let run _blkid (({ b1 ; b2 ; b3 ; b4 ; b5 } : Helpers.Account.bootstrap_accounts) as baccounts) = + + (* Helpers.Mining.mine b1 blkid >>=? fun n -> *) + let head = `Head 0 in + let default_account = Helpers.Account.create "default_account" in + + test_endorsement_rights default_account head >>=? fun has_right_to_endorse -> + Assert.equal_bool ~msg:__LOC__ has_right_to_endorse false ; + test_endorsement_rights b1 head >>=? fun has_right_to_endorse -> + Assert.equal_bool ~msg:__LOC__ has_right_to_endorse true ; + test_endorsement_rights b1 head >>=? fun has_right_to_endorse -> + Assert.equal_bool ~msg:__LOC__ has_right_to_endorse true ; + + Assert.balance_equal ~msg:__LOC__ b1 2_000_000_00L >>=? fun () -> + Assert.balance_equal ~msg:__LOC__ b2 2_000_000_00L >>=? fun () -> + Assert.balance_equal ~msg:__LOC__ b3 2_000_000_00L >>=? fun () -> + Assert.balance_equal ~msg:__LOC__ b4 2_000_000_00L >>=? fun () -> + Assert.balance_equal ~msg:__LOC__ b5 2_000_000_00L >>=? fun () -> + + (* Check Rewards *) + test_endorsement_rewards head baccounts >>=? fun head -> + + (* Endorse with a contract with wrong delegate: + - contract with no endorsement rights + - contract which signs at every available slots *) + test_wrong_delegate default_account (`Hash head) >>=? fun () -> + test_wrong_delegate b5 (`Hash head) >>=? fun () -> + + (* Endorse with a wrong slot : -1 and max (16) *) + test_invalid_endorsement_slot b3 (`Hash head) >>=? fun () -> + + (* FIXME: Mining.Invalid_signature is still unclassified *) + test_invalid_signature (`Hash head) >>=? fun _ -> + + (* FIXME: cannot inject double endorsement operation yet, but the + code is still here + Double endorsement *) + test_double_endorsement b5 (`Hash head) >>=? fun new_head -> + + return new_head + +let main () = + Helpers.init () >>=? fun (_node_pid, hash) -> + run (`Hash hash) Helpers.Account.bootstrap_accounts >>=? fun _blkh -> + return () + + +let tests = [ + "main", (fun _ -> main ()) ; +] + +let () = + Test.run "endorsement." tests diff --git a/test/proto_alpha/test_endorsement.mli b/test/proto_alpha/test_endorsement.mli new file mode 100644 index 000000000..76a0bb6b7 --- /dev/null +++ b/test/proto_alpha/test_endorsement.mli @@ -0,0 +1,8 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2016. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************)