Skip to content

Commit

Permalink
Set root on forwarded proposals (#2252)
Browse files Browse the repository at this point in the history
  • Loading branch information
achamayou authored Mar 1, 2021
1 parent 0458b02 commit d6e05a5
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 127 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Changed

- Governance proposals can be submitted successfully against secondaries (#2247)
- `set_ca_cert`/`remove_ca_cert` proposals have been renamed `set_ca_cert_bundle`/`remove_ca_cert_bundle` and now also accept a bundle of certificates encoded as concatenated PEM string (#2221).

## [0.18.3]
Expand Down
1 change: 1 addition & 0 deletions src/node/rpc/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ namespace ccf

update_consensus();
auto tx = tables.create_tx();
set_root_on_proposals(*ctx, tx);

const auto endpoint = endpoints.find_endpoint(tx, *ctx);
if (
Expand Down
1 change: 0 additions & 1 deletion tests/e2e_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def mem_stats(network):


def run(args):

chosen_suite = []

if not args.test_suite:
Expand Down
13 changes: 7 additions & 6 deletions tests/governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,13 @@ def test_node_ids(network, args):

@reqs.description("Checking service principals proposals")
def test_service_principals(network, args):
primary, _ = network.find_nodes()
node = network.find_node_by_role()

principal_id = "0xdeadbeef"
ballot = {"ballot": {"text": "return true"}}

def read_service_principal():
with primary.client("member0") as mc:
with node.client("member0") as mc:
return mc.post(
"/gov/read",
{"table": "public:gov.service_principals", "key": principal_id},
Expand All @@ -201,8 +201,8 @@ def read_service_principal():
"data": principal_data,
},
}
proposal = network.consortium.get_any_active_member().propose(primary, proposal)
network.consortium.vote_using_majority(primary, proposal, ballot)
proposal = network.consortium.get_any_active_member().propose(node, proposal)
network.consortium.vote_using_majority(node, proposal, ballot)

# Confirm it can be read
r = read_service_principal()
Expand All @@ -219,8 +219,8 @@ def read_service_principal():
"id": principal_id,
},
}
proposal = network.consortium.get_any_active_member().propose(primary, proposal)
network.consortium.vote_using_majority(primary, proposal, ballot)
proposal = network.consortium.get_any_active_member().propose(node, proposal)
network.consortium.vote_using_majority(node, proposal, ballot)

# Confirm it is gone
r = read_service_principal()
Expand All @@ -240,6 +240,7 @@ def run(args):
args.nodes, args.binary_dir, args.debug_nodes, args.perf_nodes, pdb=args.pdb
) as network:
network.start_and_join(args)

network = test_node_ids(network, args)
network = test_member_data(network, args)
network = test_quote(network, args)
Expand Down
15 changes: 15 additions & 0 deletions tests/infra/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
COMMON_FOLDER = "common"


class NodeRole(Enum):
ANY = 0
PRIMARY = 1
BACKUP = 2


class ServiceStatus(Enum):
OPENING = 1
OPEN = 2
Expand Down Expand Up @@ -738,6 +744,15 @@ def find_backups(self, primary=None, timeout=3):
def find_any_backup(self, primary=None, timeout=3):
return random.choice(self.find_backups(primary=primary, timeout=timeout))

def find_node_by_role(self, role=NodeRole.ANY):
role_ = (
random.choice([NodeRole.PRIMARY, NodeRole.BACKUP]) if NodeRole.ANY else role
)
if role_ == NodeRole.PRIMARY:
return self.find_primary()[0]
else:
return self.find_any_backup()

def find_nodes(self, timeout=3):
primary, _ = self.find_primary(timeout=timeout)
backups = self.find_backups(primary=primary, timeout=timeout)
Expand Down
238 changes: 118 additions & 120 deletions tests/memberclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

@reqs.description("Send an unsigned request where signature is required")
def test_missing_signature_header(network, args):
primary, _ = network.find_primary()
node = network.find_node_by_role()
member = network.consortium.get_any_active_member()
with primary.client(f"member{member.member_id}") as mc:
with node.client(f"member{member.member_id}") as mc:
r = mc.post("/gov/proposals")
assert r.status_code == http.HTTPStatus.UNAUTHORIZED, r.status_code
www_auth = "www-authenticate"
Expand Down Expand Up @@ -80,18 +80,18 @@ def modified_signature(request):

@reqs.description("Send a corrupted signature where signed request is required")
def test_corrupted_signature(network, args):
primary, _ = network.find_primary()
node = network.find_node_by_role()

# Test each supported curve
for curve in infra.network.ParticipantsCurve:
LOG.info(f"Testing curve: {curve.name}")
# Add a member so we have at least one on this curve
member = network.consortium.generate_and_add_new_member(
primary,
node,
curve=curve,
)

with primary.client(*member.auth(write=True)) as mc:
with node.client(*member.auth(write=True)) as mc:
# pylint: disable=protected-access

# Cache the original auth provider
Expand All @@ -107,138 +107,136 @@ def test_corrupted_signature(network, args):
ccf.clients.RequestClient._auth_provider = original_auth

# Remove the new member once we're done with them
network.consortium.retire_member(primary, member)
network.consortium.retire_member(node, member)

return network


def run(args):
with infra.network.network(
args.nodes, args.binary_dir, args.debug_nodes, args.perf_nodes, pdb=args.pdb
) as network:
network.start_and_join(args)
primary, _ = network.find_primary()

network = test_missing_signature_header(network, args)
network = test_corrupted_signature(network, args)

LOG.info("Original members can ACK")
network.consortium.get_any_active_member().ack(primary)

LOG.info("Network cannot be opened twice")
try:
network.consortium.open_network(primary)
except infra.proposal.ProposalNotAccepted as e:
assert e.proposal.state == infra.proposal.ProposalState.Failed

LOG.info("Proposal to add a new member (with different curve)")
(
new_member_proposal,
new_member,
careful_vote,
) = network.consortium.generate_and_propose_new_member(
remote_node=primary,
curve=infra.network.ParticipantsCurve(args.participants_curve).next(),
)

LOG.info("Check proposal has been recorded in open state")
proposals = network.consortium.get_proposals(primary)
proposal_entry = next(
(p for p in proposals if p.proposal_id == new_member_proposal.proposal_id),
None,
)
assert proposal_entry
assert proposal_entry.state == ProposalState.Open
@reqs.description("Test various governance operations")
def test_governance(network, args):
node = network.find_node_by_role()
primary, _ = network.find_primary()

LOG.info("Rest of consortium accept the proposal")
network.consortium.vote_using_majority(
primary, new_member_proposal, careful_vote
)
assert new_member_proposal.state == ProposalState.Accepted
LOG.info("Original members can ACK")
network.consortium.get_any_active_member().ack(node)

LOG.info("Network cannot be opened twice")
try:
network.consortium.open_network(node)
except infra.proposal.ProposalNotAccepted as e:
assert e.proposal.state == infra.proposal.ProposalState.Failed

LOG.info("Proposal to add a new member (with different curve)")
(
new_member_proposal,
new_member,
careful_vote,
) = network.consortium.generate_and_propose_new_member(
remote_node=node,
curve=infra.network.ParticipantsCurve(args.participants_curve).next(),
)

# Manually add new member to consortium
network.consortium.members.append(new_member)
LOG.info("Check proposal has been recorded in open state")
proposals = network.consortium.get_proposals(primary)
proposal_entry = next(
(p for p in proposals if p.proposal_id == new_member_proposal.proposal_id),
None,
)
assert proposal_entry
assert proposal_entry.state == ProposalState.Open

LOG.info("Rest of consortium accept the proposal")
network.consortium.vote_using_majority(node, new_member_proposal, careful_vote)
assert new_member_proposal.state == ProposalState.Accepted

# Manually add new member to consortium
network.consortium.members.append(new_member)

LOG.debug("Further vote requests fail as the proposal has already been accepted")
params_error = http.HTTPStatus.BAD_REQUEST.value
assert (
network.consortium.get_member_by_id(0)
.vote(node, new_member_proposal, careful_vote)
.status_code
== params_error
)
assert (
network.consortium.get_member_by_id(1)
.vote(node, new_member_proposal, careful_vote)
.status_code
== params_error
)

LOG.debug(
"Further vote requests fail as the proposal has already been accepted"
)
params_error = http.HTTPStatus.BAD_REQUEST.value
assert (
network.consortium.get_member_by_id(0)
.vote(primary, new_member_proposal, careful_vote)
.status_code
== params_error
)
assert (
network.consortium.get_member_by_id(1)
.vote(primary, new_member_proposal, careful_vote)
.status_code
== params_error
)
LOG.debug("Accepted proposal cannot be withdrawn")
response = network.consortium.get_member_by_id(
new_member_proposal.proposer_id
).withdraw(node, new_member_proposal)
assert response.status_code == params_error

LOG.info("New non-active member should get insufficient rights response")
try:
proposal_trust_0, careful_vote = ccf.proposal_generator.trust_node(0)
new_member.propose(node, proposal_trust_0)
assert False, "New non-active member should get insufficient rights response"
except infra.proposal.ProposalNotCreated as e:
assert e.response.status_code == http.HTTPStatus.FORBIDDEN.value

LOG.debug("New member ACK")
new_member.ack(node)

LOG.info("New member is now active and send an accept node proposal")
trust_node_proposal_0 = new_member.propose(node, proposal_trust_0)
trust_node_proposal_0.vote_for = careful_vote

LOG.debug("Members vote to accept the accept node proposal")
network.consortium.vote_using_majority(node, trust_node_proposal_0, careful_vote)
assert trust_node_proposal_0.state == infra.proposal.ProposalState.Accepted

LOG.info("New member makes a new proposal")
proposal_trust_1, careful_vote = ccf.proposal_generator.trust_node(1)
trust_node_proposal = new_member.propose(node, proposal_trust_1)

LOG.debug("Other members (non proposer) are unable to withdraw new proposal")
response = network.consortium.get_member_by_id(1).withdraw(
node, trust_node_proposal
)
assert response.status_code == http.HTTPStatus.FORBIDDEN.value

LOG.debug("Accepted proposal cannot be withdrawn")
response = network.consortium.get_member_by_id(
new_member_proposal.proposer_id
).withdraw(primary, new_member_proposal)
assert response.status_code == params_error

LOG.info("New non-active member should get insufficient rights response")
try:
proposal_trust_0, careful_vote = ccf.proposal_generator.trust_node(0)
new_member.propose(primary, proposal_trust_0)
assert (
False
), "New non-active member should get insufficient rights response"
except infra.proposal.ProposalNotCreated as e:
assert e.response.status_code == http.HTTPStatus.FORBIDDEN.value

LOG.debug("New member ACK")
new_member.ack(primary)

LOG.info("New member is now active and send an accept node proposal")
trust_node_proposal_0 = new_member.propose(primary, proposal_trust_0)
trust_node_proposal_0.vote_for = careful_vote

LOG.debug("Members vote to accept the accept node proposal")
network.consortium.vote_using_majority(
primary, trust_node_proposal_0, careful_vote
)
assert trust_node_proposal_0.state == infra.proposal.ProposalState.Accepted
LOG.debug("Proposer withdraws their proposal")
response = new_member.withdraw(node, trust_node_proposal)
assert response.status_code == http.HTTPStatus.OK.value
assert trust_node_proposal.state == infra.proposal.ProposalState.Withdrawn

LOG.info("New member makes a new proposal")
proposal_trust_1, careful_vote = ccf.proposal_generator.trust_node(1)
trust_node_proposal = new_member.propose(primary, proposal_trust_1)
proposals = network.consortium.get_proposals(primary)
proposal_entry = next(
(p for p in proposals if p.proposal_id == trust_node_proposal.proposal_id),
None,
)
assert proposal_entry
assert proposal_entry.state == ProposalState.Withdrawn

LOG.debug("Other members (non proposer) are unable to withdraw new proposal")
response = network.consortium.get_member_by_id(1).withdraw(
primary, trust_node_proposal
)
assert response.status_code == http.HTTPStatus.FORBIDDEN.value
LOG.debug("Further withdraw proposals fail")
response = new_member.withdraw(node, trust_node_proposal)
assert response.status_code == params_error

LOG.debug("Proposer withdraws their proposal")
response = new_member.withdraw(primary, trust_node_proposal)
assert response.status_code == http.HTTPStatus.OK.value
assert trust_node_proposal.state == infra.proposal.ProposalState.Withdrawn
LOG.debug("Further votes fail")
response = new_member.vote(node, trust_node_proposal, careful_vote)
assert response.status_code == params_error

proposals = network.consortium.get_proposals(primary)
proposal_entry = next(
(p for p in proposals if p.proposal_id == trust_node_proposal.proposal_id),
None,
)
assert proposal_entry
assert proposal_entry.state == ProposalState.Withdrawn

LOG.debug("Further withdraw proposals fail")
response = new_member.withdraw(primary, trust_node_proposal)
assert response.status_code == params_error
def run(args):
with infra.network.network(
args.nodes, args.binary_dir, args.debug_nodes, args.perf_nodes, pdb=args.pdb
) as network:
network.start_and_join(args)

LOG.debug("Further votes fail")
response = new_member.vote(primary, trust_node_proposal, careful_vote)
assert response.status_code == params_error
network = test_missing_signature_header(network, args)
network = test_corrupted_signature(network, args)
network = test_governance(network, args)


if __name__ == "__main__":
args = infra.e2e_args.cli_args()
args.package = "liblogging"
args.nodes = infra.e2e_args.min_nodes(args, f=0)
args.nodes = infra.e2e_args.min_nodes(args, f=1)
run(args)

0 comments on commit d6e05a5

Please sign in to comment.