def test_vote_failure_reporting(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.post("/gov/proposals", always_accept_with_one_vote) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Open", r.body.json() proposal_id = r.body.json()["proposal_id"] ballot = vote('throw new Error("Sample error")') r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Open", r.body.json() with node.client(None, "member1") as c: ballot = ballot_yes r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() rj = r.body.json() assert rj["state"] == "Accepted", r.body.json() assert len(rj["vote_failures"]) == 1, rj["vote_failures"] member_id = network.consortium.get_member_by_local_id( "member0").service_id assert rj["vote_failures"][member_id][ "reason"] == "Error: Sample error", rj["vote_failures"] return network
def test_proposal_storage(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.get("/gov/proposals/42") assert r.status_code == 404, r.body.text() r = c.get("/gov/proposals/42/actions") assert r.status_code == 404, r.body.text() for prop in (valid_set_recovery_threshold, valid_set_recovery_threshold_twice): r = c.post("/gov/proposals", prop) assert r.status_code == 200, r.body.text() proposal_id = r.body.json()["proposal_id"] r = c.get(f"/gov/proposals/{proposal_id}") assert r.status_code == 200, r.body.text() expected = { "proposer_id": network.consortium.get_member_by_local_id( "member0").service_id, "state": "Open", "ballots": {}, } assert r.body.json() == expected, r.body.json() r = c.get(f"/gov/proposals/{proposal_id}/actions") assert r.status_code == 200, r.body.text() assert r.body.json() == prop, r.body.json() return network
def test_proposals_with_votes(network, args): node = network.find_random_node() with node.client(None, "member0") as c: for prop, state, direction in [ (always_accept_with_one_vote, "Accepted", "true"), (always_reject_with_one_vote, "Rejected", "false"), ]: r = c.post("/gov/proposals", prop) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Open", r.body.json() proposal_id = r.body.json()["proposal_id"] ballot = vote(f"return {direction}") r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == state, r.body.json() r = c.post("/gov/proposals", prop) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Open", r.body.json() proposal_id = r.body.json()["proposal_id"] member_id = network.consortium.get_member_by_local_id( "member0").service_id ballot = vote( f'if (proposer_id == "{member_id}") {{ return {direction} }} else {{ return {opposite(direction) } }}' ) r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == state, r.body.json() with node.client(None, "member0") as c: for prop, state, ballot in [ (always_accept_with_two_votes, "Accepted", ballot_yes), (always_reject_with_two_votes, "Rejected", ballot_no), ]: r = c.post("/gov/proposals", prop) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Open", r.body.json() proposal_id = r.body.json()["proposal_id"] r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Open", r.body.json() with node.client(None, "member1") as oc: r = oc.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == state, r.body.json() return network
def test_apply(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.post( "/gov/proposals", proposal(action("always_throw_in_apply")), ) assert r.status_code == 500, r.body.text() assert r.body.json()["error"]["code"] == "InternalError", r.body.json() assert (r.body.json()["error"]["message"].split("\n")[0] == "Failed to apply(): Error: Error message"), r.body.json() with node.client(None, "member0") as c: pprint.pprint( proposal(action("always_accept_noop"), action("always_throw_in_apply"))) r = c.post( "/gov/proposals", proposal(action("always_accept_noop"), action("always_throw_in_apply")), ) assert r.status_code == 200, r.body().text() proposal_id = r.body.json()["proposal_id"] r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot_yes) assert r.status_code == 200, r.body().text() with node.client(None, "member1") as c: r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot_yes) assert r.body.json( )["error"]["code"] == "InternalError", r.body.json() assert ("Failed to apply():" in r.body.json()["error"]["message"]), r.body.json() assert ("Error: Error message" in r.body.json()["error"]["message"]), r.body.json() with node.client(None, "member0") as c: r = c.post( "/gov/proposals", proposal(action("always_throw_in_resolve")), ) assert r.status_code == 500, r.body.text() assert r.body.json()["error"]["code"] == "InternalError", r.body.json() assert ("Failed to resolve():" in r.body.json()["error"]["message"]), r.body.json() assert ("Error: Resolve message" in r.body.json()["error"]["message"]), r.body.json() return network
def test_apply(network, args): node = network.find_random_node() user_to_remove = network.users[-1].service_id with node.client(None, "member0") as c: r = c.post( "/gov/proposals.js", proposal(action("remove_user", user_id=user_to_remove)), ) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Accepted", r.body.json() with node.client(network.users[-1].local_id) as c: r = c.get("/app/log/private") assert r.status_code == 401, r.body.text() return network
def test_proposal_generator(network, args): restore_js_proposals = prop_gen.GENERATE_JS_PROPOSALS prop_gen.GENERATE_JS_PROPOSALS = True node = network.find_random_node() with node.client(None, "member0") as c: proposal, ballot = prop_gen.build_proposal("set_recovery_threshold", {"threshold": 5}) r = c.post("/gov/proposals.js", proposal) assert r.status_code == 200, r.body.text() proposal_id = r.body.json()["proposal_id"] r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() prop_gen.GENERATE_JS_PROPOSALS = restore_js_proposals return network
def test_proposal_withdrawal(network, args): node = network.find_random_node() with node.client(None, "member0") as c: for prop in (valid_set_recovery_threshold, valid_set_recovery_threshold_twice): r = c.post("/gov/proposals.js/42/withdraw") assert r.status_code == 400, r.body.text() r = c.post("/gov/proposals.js", prop) assert r.status_code == 200, r.body.text() proposal_id = r.body.json()["proposal_id"] with node.client(None, "member1") as oc: r = oc.post(f"/gov/proposals.js/{proposal_id}/withdraw") assert r.status_code == 403, r.body.text() r = c.get(f"/gov/proposals.js/{proposal_id}") assert r.status_code == 200, r.body.text() expected = { "proposer_id": network.consortium.get_member_by_local_id( "member0").service_id, "state": "Open", "ballots": [], } assert r.body.json() == expected, r.body.json() r = c.post(f"/gov/proposals.js/{proposal_id}/withdraw") assert r.status_code == 200, r.body.text() expected = { "proposer_id": network.consortium.get_member_by_local_id( "member0").service_id, "state": "Withdrawn", "ballots": [], } assert r.body.json() == expected, r.body.json() r = c.post(f"/gov/proposals.js/{proposal_id}/withdraw") assert r.status_code == 400, r.body.text() return network
def test_operator_proposals_and_votes(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.post("/gov/proposals", always_accept_if_voted_by_operator) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Open", r.body.json() proposal_id = r.body.json()["proposal_id"] ballot = ballot_yes r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Accepted", r.body.json() with node.client(None, "member0") as c: r = c.post("/gov/proposals", always_accept_if_proposed_by_operator) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Accepted", r.body.json() proposal_id = r.body.json()["proposal_id"] return network
def test_pure_proposals(network, args): node = network.find_random_node() with node.client(None, "member0") as c: for prop, state in [ (always_accept_noop, "Accepted"), (always_reject_noop, "Rejected"), ]: r = c.post("/gov/proposals", prop) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == state, r.body.json() proposal_id = r.body.json()["proposal_id"] ballot = ballot_yes r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 400, r.body.text() r = c.post(f"/gov/proposals/{proposal_id}/withdraw") assert r.status_code == 400, r.body.text() return network
def test_ballot_storage(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.post("/gov/proposals.js", valid_set_recovery_threshold) assert r.status_code == 200, r.body.text() proposal_id = r.body.json()["proposal_id"] r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", {}) assert r.status_code == 400, r.body.text() ballot = { "ballot": "export function vote (proposal, proposer_id) { return true }" } r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() member_id = network.consortium.get_member_by_local_id( "member0").service_id r = c.get(f"/gov/proposals.js/{proposal_id}/ballots/{member_id}") assert r.status_code == 200, r.body.text() assert r.body.json() == ballot, r.body.json() with node.client(None, "member1") as c: ballot = { "ballot": "export function vote (proposal, proposer_id) { return false }" } r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() member_id = network.consortium.get_member_by_local_id( "member1").service_id r = c.get(f"/gov/proposals.js/{proposal_id}/ballots/{member_id}") assert r.status_code == 200, r.body.text() assert r.body.json() == ballot return network
def test_proposal_validation(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.post("/gov/proposals.js", valid_set_recovery_threshold) assert r.status_code == 200, r.body.text() r = c.post("/gov/proposals.js", valid_set_recovery_threshold_twice) assert r.status_code == 200, r.body.text() r = c.post("/gov/proposals.js", no_args_set_recovery_threshold) assert (r.status_code == 400 and r.body.json()["error"]["code"] == "ProposalFailedToValidate"), r.body.text() r = c.post( "/gov/proposals.js", merge(no_args_set_recovery_threshold, bad_arg_set_recovery_threshold), ) assert (r.status_code == 400 and r.body.json()["error"]["code"] == "ProposalFailedToValidate"), r.body.text() return network
def test_actions(network, args): node = network.find_random_node() with node.client(None, "member0") as c: valid_set_member_data = proposal( action( "set_member_data", member_id= f"{network.consortium.get_member_by_local_id('member0').service_id}", member_data={"is_admin": True}, )) r = c.post("/gov/proposals.js", valid_set_member_data) assert r.status_code == 200, r.body.text() valid_rekey_ledger = proposal(action("rekey_ledger")) r = c.post("/gov/proposals.js", valid_rekey_ledger) assert r.status_code == 200, r.body.text() valid_service_open = proposal(action("transition_service_to_open")) r = c.post("/gov/proposals.js", valid_service_open) assert r.status_code == 200, r.body.text() new_user_local_id = "js_user" new_user = network.create_user(new_user_local_id, args.participants_curve) LOG.info(f"Adding new user {new_user.service_id}") with open( os.path.join(network.common_dir, f"{new_user_local_id}_cert.pem"), "r") as cert: valid_new_user = proposal( action("set_user", cert=cert.read(), user_data={"is_admin": True})) r = c.post("/gov/proposals.js", valid_new_user) assert r.status_code == 200, r.body.text() return network
def test_ballot_storage(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.post("/gov/proposals", valid_set_recovery_threshold) assert r.status_code == 200, r.body.text() proposal_id = r.body.json()["proposal_id"] r = c.post(f"/gov/proposals/{proposal_id}/ballots", {}) assert r.status_code == 400, r.body.text() ballot = ballot_yes r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 400, r.body.text() assert r.body.json( )["error"]["code"] == "VoteAlreadyExists", r.body.json() member_id = network.consortium.get_member_by_local_id( "member0").service_id r = c.get(f"/gov/proposals/{proposal_id}/ballots/{member_id}") assert r.status_code == 200, r.body.text() assert r.body.json() == ballot, r.body.json() with node.client(None, "member1") as c: ballot = ballot_no r = c.post(f"/gov/proposals/{proposal_id}/ballots", ballot) assert r.status_code == 200, r.body.text() member_id = network.consortium.get_member_by_local_id( "member1").service_id r = c.get(f"/gov/proposals/{proposal_id}/ballots/{member_id}") assert r.status_code == 200, r.body.text() assert r.body.json() == ballot return network
def test_apply(network, args): node = network.find_random_node() with node.client(None, "member0") as c: r = c.post( "/gov/proposals", proposal(action("always_throw_in_apply")), ) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Failed", r.body.json() assert (r.body.json()["failure"]["reason"] == "Failed to apply(): Error: Error message"), r.body.json() with node.client(None, "member0") as c: r = c.post( "/gov/proposals", proposal(action("always_throw_in_resolve")), ) assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Failed", r.body.json() assert (r.body.json()["failure"]["reason"] == "Failed to resolve(): Error: Resolve message"), r.body.json() return network
def test_set_constitution(network, args): node = network.find_random_node() # Create some open proposals pending_proposals = [] with node.client(None, "member0") as c: r = c.post( "/gov/proposals", valid_set_recovery_threshold, ) assert r.status_code == 200, r.body.text() body = r.body.json() assert body["state"] == "Open", body pending_proposals.append(body["proposal_id"]) r = c.post( "/gov/proposals", always_accept_with_one_vote, ) assert r.status_code == 200, r.body.text() body = r.body.json() assert body["state"] == "Open", body pending_proposals.append(body["proposal_id"]) # Create a set_constitution proposal, with test proposals removed, and pass it original_constitution = args.constitution modified_constitution = [ path for path in original_constitution if "test_actions.js" not in path ] network.consortium.set_constitution(node, modified_constitution) with node.client(None, "member0") as c: # Check all other proposals were dropped for proposal_id in pending_proposals: r = c.get(f"/gov/proposals/{proposal_id}") assert r.status_code == 200, r.body.text() assert r.body.json()["state"] == "Dropped", r.body.json() # Confirm constitution has changed by proposing test actions which are no longer present r = c.post( "/gov/proposals", always_accept_noop, ) assert (r.status_code == 400 and r.body.json()["error"]["code"] == "ProposalFailedToValidate"), r.body.text() r = c.post( "/gov/proposals", always_reject_noop, ) assert (r.status_code == 400 and r.body.json()["error"]["code"] == "ProposalFailedToValidate"), r.body.text() # Confirm modified constitution can still accept valid proposals r = c.post( "/gov/proposals", valid_set_recovery_threshold, ) assert r.status_code == 200, r.body.text() body = r.body.json() assert body["state"] == "Open", body # Restore original constitution network.consortium.set_constitution(node, original_constitution) # Confirm original constitution was restored r = c.post( "/gov/proposals", always_accept_noop, ) assert r.status_code == 200, r.body.text() body = r.body.json() assert body["state"] == "Accepted", body return network
def test_proposal_validation(network, args): node = network.find_random_node() def assert_invalid_proposal(r): assert (r.status_code == 400 and r.body.json()["error"]["code"] == "ProposalFailedToValidate"), r.body.text() with node.client(None, "member0") as c: r = c.post( "/gov/proposals", proposal(action("valid_pem", pem="That's not a PEM")), ) assert_invalid_proposal(r) with open(os.path.join(network.common_dir, "networkcert.pem"), "r") as cert: valid_pem = cert.read() r = c.post( "/gov/proposals", proposal(action("valid_pem", pem=valid_pem)), ) assert r.status_code == 200 # Arg missing r = c.post( "/gov/proposals", proposal(action("remove_user")), ) assert_invalid_proposal(r) # Not a string r = c.post( "/gov/proposals", proposal(action("remove_user", user_id=42)), ) assert_invalid_proposal(r) # Too short r = c.post( "/gov/proposals", proposal(action("remove_user", user_id="deadbeef")), ) assert_invalid_proposal(r) # Too long r = c.post( "/gov/proposals", proposal( action( "remove_user", user_id= "0deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", )), ) assert_invalid_proposal(r) # Not hex r = c.post( "/gov/proposals", proposal( action( "remove_user", user_id= "totboeuftotboeuftotboeuftotboeuftotboeuftotboeuftotboeuftotboeuf", )), ) assert_invalid_proposal(r) # Just right # NB: It validates (structurally correct type), but does nothing because this user doesn't exist r = c.post( "/gov/proposals", proposal( action( "remove_user", user_id= "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", )), ) assert r.status_code == 200 return network
def test_actions(network, args): node = network.find_random_node() # Rekey ledger network.consortium.trigger_ledger_rekey(node) # Add new user twice (with and without user data) new_user_local_id = "js_user" new_user = network.create_user(new_user_local_id, args.participants_curve) LOG.info(f"Adding new user {new_user.service_id}") user_data = None network.consortium.add_user(node, new_user.local_id, user_data) user_data = {"foo": "bar"} network.consortium.add_user(node, new_user.local_id, user_data) with node.client(new_user.local_id) as c: r = c.post("/app/log/private", {"id": 0, "msg": "JS"}) assert r.status_code == 200, r.body.text() # Set user data network.consortium.set_user_data(node, new_user.service_id, user_data={"user": "******"}) network.consortium.set_user_data(node, new_user.service_id, user_data=None) # Remove user network.consortium.remove_user(node, new_user.service_id) with node.client(new_user.local_id) as c: r = c.get("/app/log/private") assert r.status_code == 401, r.body.text() # Set member data network.consortium.set_member_data( node, network.consortium.get_member_by_local_id("member0").service_id, member_data={ "is_operator": True, "is_admin": True }, ) # Set recovery threshold try: network.consortium.set_recovery_threshold(node, recovery_threshold=0) assert False, "Recovery threshold cannot be set to zero" except infra.proposal.ProposalNotCreated as e: assert (e.response.status_code == 400 and e.response.body.json()["error"]["code"] == "ProposalFailedToValidate"), e.response.body.text() try: network.consortium.set_recovery_threshold(node, recovery_threshold=256) assert False, "Recovery threshold cannot be set to > 255" except infra.proposal.ProposalNotCreated as e: assert (e.response.status_code == 400 and e.response.body.json()["error"]["code"] == "ProposalFailedToValidate"), e.response.body.text() try: network.consortium.set_recovery_threshold(node, recovery_threshold=None) assert False, "Recovery threshold value must be passed as proposal argument" except infra.proposal.ProposalNotCreated as e: assert (e.response.status_code == 400 and e.response.body.json()["error"]["code"] == "ProposalFailedToValidate"), e.response.body.text() try: network.consortium.set_recovery_threshold( node, recovery_threshold=len( network.consortium.get_active_recovery_members()) + 1, ) assert ( False ), "Recovery threshold cannot be greater than the number of active recovery members" except infra.proposal.ProposalNotAccepted: pass network.consortium.set_recovery_threshold( node, recovery_threshold=network.consortium.recovery_threshold - 1) # Refresh recovery shares network.consortium.trigger_recovery_shares_refresh(node) # Set member new_member = network.consortium.generate_and_add_new_member( node, args.participants_curve) member_data = {"foo": "bar"} new_member = network.consortium.generate_and_add_new_member( node, args.participants_curve, member_data=member_data) # Remove member network.consortium.remove_member(node, new_member) network.consortium.remove_member(node, new_member) return network