Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix historical TX requests for previous epochs during recovery #6507

Merged
22 changes: 22 additions & 0 deletions src/node/historical_queries_adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,28 @@ namespace ccf::historical
}
}

// If recovery in progress, prohibit any historical queries for previous
// epochs.
achamayou marked this conversation as resolved.
Show resolved Hide resolved
auto service = args.tx.template ro<ccf::Service>(Tables::SERVICE);
auto active_service = service->get();
if (active_service && active_service->status != ServiceStatus::OPEN)
{
if (
active_service->current_service_create_txid &&
target_tx_id.view < active_service->current_service_create_txid->view)
{
auto reason = fmt::format(
"Historical transaction {} is not signed by the current service "
"identity key and can't be retrieved until recovery is complete.",
maxtropets marked this conversation as resolved.
Show resolved Hide resolved
target_tx_id.to_str());
ehandler(
HistoricalQueryErrorCode::TransactionInvalid,
std::move(reason),
args);
return;
}
}

// We need a handle to determine whether this request is the 'same' as a
// previous one. For simplicity we use target_tx_id.seqno. This means we
// keep a lot of state around for old requests! It should be cleaned up
Expand Down
13 changes: 11 additions & 2 deletions tests/e2e_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ def verify_endorsements_openssl(service_cert, receipt):


def verify_receipt(
receipt, service_cert, claims=None, generic=True, skip_endorsement_check=False
receipt,
service_cert,
claims=None,
generic=True,
skip_endorsement_check=False,
is_signature_tx=False,
):
"""
Raises an exception on failure
Expand Down Expand Up @@ -115,7 +120,7 @@ def verify_receipt(
.digest()
.hex()
)
else:
elif not is_signature_tx:
assert "leaf_components" in receipt, receipt
assert "write_set_digest" in receipt["leaf_components"]
write_set_digest = bytes.fromhex(receipt["leaf_components"]["write_set_digest"])
Expand All @@ -133,6 +138,10 @@ def verify_receipt(
.digest()
.hex()
)
else:
assert is_signature_tx
leaf = receipt["leaf"]

root = ccf.receipt.root(leaf, receipt["proof"])
ccf.receipt.verify(root, receipt["signature"], node_cert)

Expand Down
26 changes: 25 additions & 1 deletion tests/recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ def test_recover_service_with_wrong_identity(network, args):
network.save_service_identity(args)
first_service_identity_file = args.previous_service_identity_file

with old_primary.client() as c:
last_view, last_seq = (
c.get("/node/commit").body.json()["transaction_id"].split(".")
)

network.stop_all_nodes()

current_ledger_dir, committed_ledger_dirs = old_primary.get_ledger()
Expand Down Expand Up @@ -254,8 +259,27 @@ def test_recover_service_with_wrong_identity(network, args):
snapshots_dir=snapshots_dir,
)

recovered_network.recover(args)
# Must fail with a dedicated error message if requesting a receipt for a TX
# from past epochs, as soon as ledger secrets are not yet available,
achamayou marked this conversation as resolved.
Show resolved Hide resolved
# therefore no proof can't be generated.
maxtropets marked this conversation as resolved.
Show resolved Hide resolved
primary, _ = recovered_network.find_primary()
with primary.client() as cli:
curr_view, curr_seq = (
maxtropets marked this conversation as resolved.
Show resolved Hide resolved
cli.get("/node/commit").body.json()["transaction_id"].split(".")
)
response = cli.get(f"/node/receipt?transaction_id={last_view}.{last_seq}")
assert response.status_code == http.HTTPStatus.NOT_FOUND, response
assert (
"not signed by the current servic"
maxtropets marked this conversation as resolved.
Show resolved Hide resolved
in response.body.json()["error"]["message"]
), response

# TX from the current epoch though can be verified, as soon as the caller
# trusts the current service identity.
receipt = primary.get_receipt(curr_view, curr_seq).json()
verify_receipt(receipt, recovered_network.cert, is_signature_tx=True)
maxtropets marked this conversation as resolved.
Show resolved Hide resolved

recovered_network.recover(args)
return recovered_network


Expand Down