Skip to content

Combine view and seqno into single HTTP header #2257

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

Merged
merged 9 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed

- Historical query system now supports range queries.
- `x-ccf-tx-view` and `x-ccf-tx-seqno` response headers have been removed, and replaced with `x-ms-ccf-transaction-id`. This includes both original fields, separated by a single `.`. Historical queries using `ccf::historical::adapter` should also pass a single combined `x-ms-ccf-transaction-id` header.

## [0.18.4]

Expand Down
59 changes: 59 additions & 0 deletions include/ccf/tx_id.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once

#include <charconv>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>

namespace ccf
{
using View = uint64_t;
using SeqNo = uint64_t;

struct TxID
{
View view;
SeqNo seqno;

std::string to_str() const
{
return std::to_string(view) + "." + std::to_string(seqno);
}

static std::optional<TxID> from_str(const std::string_view& sv)
{
const auto separator_idx = sv.find(".");
if (separator_idx == std::string_view::npos)
{
return std::nullopt;
}

TxID tx_id;

{
const auto view_sv = sv.substr(0, separator_idx);
const auto [p, ec] =
std::from_chars(view_sv.begin(), view_sv.end(), tx_id.view);
if (ec != std::errc() || p != view_sv.end())
{
return std::nullopt;
}
}

{
const auto seqno_sv = sv.substr(separator_idx + 1);
const auto [p, ec] =
std::from_chars(seqno_sv.begin(), seqno_sv.end(), tx_id.seqno);
if (ec != std::errc() || p != seqno_sv.end())
{
return std::nullopt;
}
}

return tx_id;
}
};
}
23 changes: 15 additions & 8 deletions python/ccf/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ def truncate(string: str, max_len: int = 128):
return string


CCF_TX_SEQNO_HEADER = "x-ccf-tx-seqno"
CCF_TX_VIEW_HEADER = "x-ccf-tx-view"
CCF_TX_ID_HEADER = "x-ms-ccf-transaction-id"

DEFAULT_CONNECTION_TIMEOUT_SEC = 3
DEFAULT_REQUEST_TIMEOUT_SEC = 10
Expand Down Expand Up @@ -91,8 +90,14 @@ class Identity:
description: str


def int_or_none(v):
return int(v) if v is not None else None
def parse_tx_id(s: Optional[str]):
try:
if s is not None:
view_s, seqno_s = s.split(".")
return int(view_s), int(seqno_s)
except (AttributeError, ValueError):
pass
return None, None


class FakeSocket:
Expand Down Expand Up @@ -190,11 +195,12 @@ def __str__(self):

@staticmethod
def from_requests_response(rr):
view, seqno = parse_tx_id(rr.headers.get(CCF_TX_ID_HEADER))
return Response(
status_code=rr.status_code,
body=RequestsResponseBody(rr),
seqno=int_or_none(rr.headers.get(CCF_TX_SEQNO_HEADER)),
view=int_or_none(rr.headers.get(CCF_TX_VIEW_HEADER)),
seqno=seqno,
view=view,
headers=rr.headers,
)

Expand All @@ -215,11 +221,12 @@ def from_raw(raw):

raw_body = response.read()

view, seqno = parse_tx_id(response.getheader(CCF_TX_ID_HEADER))
return Response(
response.status,
body=RawResponseBody(raw_body),
seqno=int_or_none(response.getheader(CCF_TX_SEQNO_HEADER)),
view=int_or_none(response.getheader(CCF_TX_VIEW_HEADER)),
seqno=seqno,
view=view,
headers=response.headers,
)

Expand Down
4 changes: 2 additions & 2 deletions src/enclave/rpc_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "node/client_signatures.h"
#include "node/entities.h"
#include "node/rpc/error.h"
#include "tx_id.h"

#include <llhttp/llhttp.h>
#include <variant>
Expand Down Expand Up @@ -182,8 +183,7 @@ namespace enclave
virtual void set_response_status(int status) = 0;
virtual int get_response_status() const = 0;

virtual void set_seqno(kv::Version) = 0;
virtual void set_view(kv::Consensus::View) = 0;
virtual void set_tx_id(const ccf::TxID& tx_id) = 0;

virtual void set_response_header(
const std::string_view& name, const std::string_view& value) = 0;
Expand Down
3 changes: 1 addition & 2 deletions src/http/http_consts.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ namespace http
static constexpr auto RETRY_AFTER = "retry-after";
static constexpr auto WWW_AUTHENTICATE = "www-authenticate";

static constexpr auto CCF_TX_SEQNO = "x-ccf-tx-seqno";
static constexpr auto CCF_TX_VIEW = "x-ccf-tx-view";
static constexpr auto CCF_TX_ID = "x-ms-ccf-transaction-id";
}

namespace headervalues
Expand Down
9 changes: 2 additions & 7 deletions src/http/http_rpc_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,9 @@ namespace http
return request_index;
}

virtual void set_seqno(kv::Version sn) override
virtual void set_tx_id(const ccf::TxID& tx_id) override
{
set_response_header(http::headers::CCF_TX_SEQNO, fmt::format("{}", sn));
}

virtual void set_view(kv::Consensus::View v) override
{
set_response_header(http::headers::CCF_TX_VIEW, fmt::format("{}", v));
set_response_header(http::headers::CCF_TX_ID, tx_id.to_str());
}

virtual const std::vector<uint8_t>& get_request_body() const override
Expand Down
8 changes: 6 additions & 2 deletions src/http/ws_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "enclave/tls_endpoint.h"
#include "http_proc.h"
#include "tx_id.h"
#include "ws_consts.h"

#include <algorithm>
Expand Down Expand Up @@ -116,12 +117,15 @@ namespace ws
auto seqno = serialized::read<size_t>(data, s);
auto view = serialized::read<size_t>(data, s);

ccf::TxID tx_id;
tx_id.view = view;
tx_id.seqno = seqno;

std::vector<uint8_t> body(data, data + s);

proc.handle_response(
(http_status)status,
{{http::headers::CCF_TX_SEQNO, fmt::format("{}", seqno)},
{http::headers::CCF_TX_VIEW, fmt::format("{}", view)}},
{{http::headers::CCF_TX_ID, tx_id.to_str()}},
std::move(body));
state = INIT;
return INITIAL_READ;
Expand Down
10 changes: 3 additions & 7 deletions src/http/ws_rpc_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,10 @@ namespace ws
const std::string_view&, const std::string_view&) override
{}

virtual void set_seqno(kv::Version sn) override
virtual void set_tx_id(const ccf::TxID& tx_id) override
{
seqno = sn;
}

virtual void set_view(kv::Consensus::View t) override
{
view = t;
view = tx_id.view;
seqno = tx_id.seqno;
}

virtual void set_apply_writes(bool apply) override
Expand Down
73 changes: 22 additions & 51 deletions src/node/historical_queries_adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "historical_queries_interface.h"
#include "node/rpc/endpoint_registry.h"
#include "tx_id.h"

namespace ccf::historical
{
Expand All @@ -26,72 +27,43 @@ namespace ccf::historical
{
return [f, &state_cache, available](EndpointContext& args) {
// Extract the requested transaction ID
kv::Consensus::View target_view;
kv::SeqNo target_seqno;

ccf::TxID target_tx_id;
{
const auto target_view_opt =
args.rpc_ctx->get_request_header(http::headers::CCF_TX_VIEW);
if (!target_view_opt.has_value())
const auto tx_id_header =
args.rpc_ctx->get_request_header(http::headers::CCF_TX_ID);
if (!tx_id_header.has_value())
{
args.rpc_ctx->set_error(
HTTP_STATUS_BAD_REQUEST,
ccf::errors::MissingRequiredHeader,
fmt::format(
"Historical query is missing '{}' header.",
http::headers::CCF_TX_VIEW));
http::headers::CCF_TX_ID));
return;
}

target_view =
std::strtoul(target_view_opt.value().c_str(), nullptr, 10);
if (target_view == 0)
const auto tx_id_opt = ccf::TxID::from_str(tx_id_header.value());
if (!tx_id_opt.has_value())
{
args.rpc_ctx->set_error(
HTTP_STATUS_BAD_REQUEST,
ccf::errors::InvalidHeaderValue,
fmt::format(
"The value '{}' in header '{}' could not be converted to a valid "
"view.",
target_view_opt.value(),
http::headers::CCF_TX_VIEW));
return;
}

const auto target_seqno_opt =
args.rpc_ctx->get_request_header(http::headers::CCF_TX_SEQNO);
if (!target_seqno_opt.has_value())
{
args.rpc_ctx->set_error(
HTTP_STATUS_BAD_REQUEST,
ccf::errors::MissingRequiredHeader,
fmt::format(
"Historical query is missing '{}' header.",
http::headers::CCF_TX_SEQNO));
"Tx ID.",
tx_id_header.value(),
http::headers::CCF_TX_ID));
return;
}

target_seqno =
std::strtoul(target_seqno_opt.value().c_str(), nullptr, 10);
if (target_view == 0)
{
args.rpc_ctx->set_error(
HTTP_STATUS_BAD_REQUEST,
ccf::errors::InvalidHeaderValue,
fmt::format(
"The value '{}' in header '{}' could not be converted to a valid "
"seqno.",
target_seqno_opt.value(),
http::headers::CCF_TX_SEQNO));
return;
}
target_tx_id = tx_id_opt.value();
}

// Check that the requested transaction ID is available
{
auto error_reason = fmt::format(
"Transaction {}.{} is not available.", target_view, target_seqno);
if (!available(target_view, target_seqno, error_reason))
"Transaction {} is not available.", target_tx_id.to_str());
if (!available(target_tx_id.view, target_tx_id.seqno, error_reason))
{
args.rpc_ctx->set_error(
HTTP_STATUS_BAD_REQUEST,
Expand All @@ -102,13 +74,14 @@ namespace ccf::historical
}

// We need a handle to determine whether this request is the 'same' as a
// previous one. For simplicity we use target_seqno. This means we keep a
// lot of state around for old requests! It should be cleaned up manually
const auto historic_request_handle = target_seqno;
// 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
// manually
const auto historic_request_handle = target_tx_id.seqno;

// Get a store at the target version from the cache, if it is present
auto historical_store =
state_cache.get_store_at(historic_request_handle, target_seqno);
state_cache.get_store_at(historic_request_handle, target_tx_id.seqno);
if (historical_store == nullptr)
{
args.rpc_ctx->set_response_status(HTTP_STATUS_ACCEPTED);
Expand All @@ -118,15 +91,13 @@ namespace ccf::historical
args.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE, http::headervalues::contenttype::TEXT);
args.rpc_ctx->set_response_body(fmt::format(
"Historical transaction at seqno {} in view {} is not currently "
"available.",
target_seqno,
target_view));
"Historical transaction {} is not currently available.",
target_tx_id.to_str()));
return;
}

// Call the provided handler
f(args, historical_store, target_view, target_seqno);
f(args, historical_store, target_tx_id.view, target_tx_id.seqno);
};
}
#pragma clang diagnostic pop
Expand Down
6 changes: 4 additions & 2 deletions src/node/rpc/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,10 @@ namespace ccf
{
if (cv != kv::NoVersion)
{
ctx->set_seqno(cv);
ctx->set_view(tx.commit_term());
ccf::TxID tx_id;
tx_id.view = tx.commit_term();
tx_id.seqno = cv;
ctx->set_tx_id(tx_id);
}

if (history != nullptr && consensus->is_primary())
Expand Down
4 changes: 2 additions & 2 deletions src/perf_client/perf_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ namespace client
PreparedTxs prepared_txs;

timing::ResponseTimes response_times;
timing::TransactionID last_response_tx_id = {0, 0};
ccf::TxID last_response_tx_id = {0, 0};

std::chrono::high_resolution_clock::time_point last_write_time;
std::chrono::nanoseconds write_delay_ns = std::chrono::nanoseconds::zero();
Expand Down Expand Up @@ -711,7 +711,7 @@ namespace client
}
}

void wait_for_global_commit(const timing::TransactionID& target)
void wait_for_global_commit(const ccf::TxID& target)
{
response_times.wait_for_global_commit(target);
}
Expand Down
Loading