Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
24 changes: 20 additions & 4 deletions contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,19 @@ namespace sysio {
checksum256 envelope_hash;
opp::types::EnvelopeStatus status;
std::vector<char> raw_envelope;
/// The `message_id` of the last (today: only) message in this envelope — the message
/// stream tip for this outpost. The next `buildenv` for the outpost carries it in its
/// header's `previous_message_id` and continues the big-endian sequence number embedded
/// in its first 8 bytes (see opp_canonical_codec.hpp `derive_message_id`).
checksum256 last_message_id;

uint64_t by_outpost() const { return chain_code; }
uint128_t by_outpost_epoch() const {
return opp::outpost_epoch_key(chain_code, epoch_index);
}

SYSLIB_SERIALIZE(outbound_envelope,
(id)(chain_code)(epoch_index)(envelope_hash)(status)(raw_envelope))
(id)(chain_code)(epoch_index)(envelope_hash)(status)(raw_envelope)(last_message_id))
};

using outenvelopes_t = sysio::kv::table<"outenvelopes"_n, id_key, outbound_envelope,
Expand All @@ -243,21 +248,32 @@ namespace sysio {
/// classify each operator's delivery: a matching checksum is a hit, a non-matching delivered
/// checksum is slashed. Zero until a winner exists for the current epoch.
///
/// `envelope_digest` is the inbound chain tip: the canonical epoch digest (keccak256 over
/// the canonical field-complete encoding with `envelope_hash` blanked; see
/// `envelope_digest` is the inbound ENVELOPE chain tip: the canonical epoch digest (keccak256
/// over the canonical field-complete encoding with `envelope_hash` blanked; see
/// opp_canonical_codec.hpp) of the last ACCEPTED envelope from this outpost. The next
/// accepted envelope's `previous_envelope_hash` must continue from it. Zero until the first
/// envelope from this outpost is accepted. Unlike `epoch_index`/`winning_checksum` it is a
/// running tip, not per-epoch state.
///
/// `message_tip` is the inbound MESSAGE chain tip: the `message_id` of the last ACCEPTED
/// message from this outpost. The next accepted message's `previous_message_id` must equal
/// it, which — with the per-message sequence splice validated in `semantic_headers_ok` —
/// makes the message stream strictly monotonic and non-replayable. The envelope chain orders
/// envelopes but does not by itself bind the messages inside them, so without this a
/// correctly envelope-chained successor could replay an earlier valid `Message` verbatim and
/// re-dispatch its attestations. Zero until the first message from this outpost is accepted
/// (which may lag `envelope_digest` if the first accepted envelopes are empty-message acks).
struct [[sysio::table("outpcons")]] outpost_consensus_entry {
uint64_t chain_code;
uint32_t epoch_index;
bool consensus_reached;
checksum256 winning_checksum;
checksum256 envelope_digest;
checksum256 message_tip;

SYSLIB_SERIALIZE(outpost_consensus_entry,
(chain_code)(epoch_index)(consensus_reached)(winning_checksum)(envelope_digest))
(chain_code)(epoch_index)(consensus_reached)(winning_checksum)(envelope_digest)
(message_tip))
};

using outpost_consensus_t =
Expand Down
356 changes: 285 additions & 71 deletions contracts/sysio.msgch/src/sysio.msgch.cpp

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions contracts/sysio.msgch/sysio.msgch.abi
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@
{
"name": "raw_envelope",
"type": "bytes"
},
{
"name": "last_message_id",
"type": "checksum256"
}
]
},
Expand All @@ -327,6 +331,10 @@
{
"name": "envelope_digest",
"type": "checksum256"
},
{
"name": "message_tip",
"type": "checksum256"
}
]
},
Expand Down
Binary file modified contracts/sysio.msgch/sysio.msgch.wasm
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <magic_enum/magic_enum.hpp>

#include <cstdint>
#include <optional>
#include <vector>

namespace sysio::opp::canonical {
Expand Down Expand Up @@ -84,7 +85,7 @@ namespace field {
constexpr uint32_t endpoints = 1;
constexpr uint32_t message_id = 2;
constexpr uint32_t previous_message_id = 3;
constexpr uint32_t encoding_flags = 4;
// Slot 4 was `encoding_flags` — removed from opp.proto; reserved, do not reuse.
constexpr uint32_t payload_size = 5;
constexpr uint32_t payload_checksum = 6;
constexpr uint32_t timestamp = 7;
Expand All @@ -99,11 +100,6 @@ namespace field {
constexpr uint32_t data_size = 2;
constexpr uint32_t data = 3;
} // namespace attestation_entry
namespace encoding_flags {
constexpr uint32_t endianness = 1;
constexpr uint32_t hash_algorithm = 2;
constexpr uint32_t length_encoding = 3;
} // namespace encoding_flags
} // namespace field

namespace detail {
Expand Down Expand Up @@ -199,21 +195,6 @@ inline void encode_into(std::vector<char>& out, const types::ChainId& m) {
}
/// @}

/// @{ sysio.opp.types.EncodingFlags
inline size_t encoded_size(const types::EncodingFlags& m) {
return detail::varint_field_size(field::encoding_flags::endianness, detail::enum_wire_value(m.endianness)) +
detail::varint_field_size(field::encoding_flags::hash_algorithm,
detail::enum_wire_value(m.hash_algorithm)) +
detail::varint_field_size(field::encoding_flags::length_encoding,
detail::enum_wire_value(m.length_encoding));
}
inline void encode_into(std::vector<char>& out, const types::EncodingFlags& m) {
detail::put_varint_field(out, field::encoding_flags::endianness, detail::enum_wire_value(m.endianness));
detail::put_varint_field(out, field::encoding_flags::hash_algorithm, detail::enum_wire_value(m.hash_algorithm));
detail::put_varint_field(out, field::encoding_flags::length_encoding, detail::enum_wire_value(m.length_encoding));
}
/// @}

/// @{ sysio.opp.Endpoints
inline size_t encoded_size(const Endpoints& m) {
return detail::submessage_field_size(field::endpoints::start, encoded_size(m.start)) +
Expand All @@ -232,7 +213,6 @@ inline size_t encoded_size(const MessageHeader& m) {
return detail::submessage_field_size(field::message_header::endpoints, encoded_size(m.endpoints)) +
detail::bytes_field_size(field::message_header::message_id, m.message_id) +
detail::bytes_field_size(field::message_header::previous_message_id, m.previous_message_id) +
detail::submessage_field_size(field::message_header::encoding_flags, encoded_size(m.encoding_flags)) +
detail::varint_field_size(field::message_header::payload_size, static_cast<uint32_t>(m.payload_size)) +
detail::bytes_field_size(field::message_header::payload_checksum, m.payload_checksum) +
detail::varint_field_size(field::message_header::timestamp, static_cast<uint64_t>(m.timestamp)) +
Expand All @@ -243,8 +223,6 @@ inline void encode_into(std::vector<char>& out, const MessageHeader& m) {
encode_into(out, m.endpoints);
detail::put_bytes_field(out, field::message_header::message_id, m.message_id);
detail::put_bytes_field(out, field::message_header::previous_message_id, m.previous_message_id);
detail::put_submessage_prefix(out, field::message_header::encoding_flags, encoded_size(m.encoding_flags));
encode_into(out, m.encoding_flags);
detail::put_varint_field(out, field::message_header::payload_size, static_cast<uint32_t>(m.payload_size));
detail::put_bytes_field(out, field::message_header::payload_checksum, m.payload_checksum);
detail::put_varint_field(out, field::message_header::timestamp, static_cast<uint64_t>(m.timestamp));
Expand Down Expand Up @@ -364,6 +342,69 @@ inline sysio::checksum256 epoch_digest(const Envelope& env) {
return sysio::keccak(preimage.data(), preimage.size());
}

/// Canonical field-complete bytes of a standalone MessagePayload — the sub-message bytes exactly
/// as embedded inside an Envelope, minus the enclosing field tag + length prefix. Feeds
/// `MessageHeader.payload_size` and `MessageHeader.payload_checksum`.
inline std::vector<char> encode(const MessagePayload& payload) {
std::vector<char> out;
out.reserve(encoded_size(payload));
encode_into(out, payload);
return out;
}

/// Canonical field-complete bytes of a standalone MessageHeader (same sub-message form as
/// `encode(MessagePayload)`).
inline std::vector<char> encode(const MessageHeader& header) {
std::vector<char> out;
out.reserve(encoded_size(header));
encode_into(out, header);
return out;
}

/// The `MessageHeader.header_checksum` value: keccak256 over the canonical encoding of `header`
/// with `message_id` and `header_checksum` blanked. Takes a copy so callers can pass the header
/// they are about to finalize without pre-blanking either field. Matches the generated Solidity
/// `OPPCommon.headerChecksum`.
inline sysio::checksum256 header_digest(MessageHeader header) {
header.message_id.clear();
header.header_checksum.clear();
const std::vector<char> preimage = encode(header);
return sysio::keccak(preimage.data(), preimage.size());
}

/// The `MessageHeader.message_id` value: `header_checksum` with its first 8 bytes replaced by the
/// message's big-endian sequence number (the previous message's sequence number + 1; a stream's
/// first message is sequence number 1). Mirrors the Solidity `OPPCommon.getMessageID` /
/// `setMessageNumber` splice.
inline sysio::checksum256 derive_message_id(const sysio::checksum256& header_checksum,
uint64_t sequence) {
auto bytes = header_checksum.extract_as_byte_array();
for (size_t i = 0; i < 8; ++i) {
bytes[i] = static_cast<uint8_t>(sequence >> (8 * (7 - i)));
}
return sysio::checksum256{bytes};
}

/// The big-endian sequence number carried in the first 8 bytes of a wire `message_id`, accepting
/// only the two canonical forms: an EMPTY id (stream genesis) reads as 0, so the successor
/// message's sequence number is 1, and a 32-byte id yields its first 8 bytes. Any other length
/// is non-canonical and yields std::nullopt — verifiers treat it as a mismatch, never as genesis
/// or as a sequence source (1-7 bytes would otherwise alias genesis, and any other length would
/// alias a truncated or padded id).
inline std::optional<uint64_t> message_sequence(const std::vector<char>& message_id) {
if (message_id.empty()) {
return 0;
}
if (message_id.size() != 32) {
return std::nullopt;
}
uint64_t sequence = 0;
for (size_t i = 0; i < 8; ++i) {
sequence = (sequence << 8) | static_cast<uint8_t>(message_id[i]);
}
return sequence;
}

// -------------------------------------------------------------------------------------------------
// Drift guards: fail the build when a regenerated pb header changes a member count, forcing this
// encoder (and the pinned field numbers above) to be revisited. Field renumbering without a count
Expand All @@ -375,15 +416,13 @@ static_assert(zpp::bits::access::number_of_members<Endpoints>() == 2,
"opp.proto Endpoints changed; update opp_canonical_codec.hpp to match");
static_assert(zpp::bits::access::number_of_members<Message>() == 2,
"opp.proto Message changed; update opp_canonical_codec.hpp to match");
static_assert(zpp::bits::access::number_of_members<MessageHeader>() == 8,
static_assert(zpp::bits::access::number_of_members<MessageHeader>() == 7,
"opp.proto MessageHeader changed; update opp_canonical_codec.hpp to match");
static_assert(zpp::bits::access::number_of_members<MessagePayload>() == 2,
"opp.proto MessagePayload changed; update opp_canonical_codec.hpp to match");
static_assert(zpp::bits::access::number_of_members<AttestationEntry>() == 3,
"opp.proto AttestationEntry changed; update opp_canonical_codec.hpp to match");
static_assert(zpp::bits::access::number_of_members<types::ChainId>() == 2,
"types.proto ChainId changed; update opp_canonical_codec.hpp to match");
static_assert(zpp::bits::access::number_of_members<types::EncodingFlags>() == 3,
"types.proto EncodingFlags changed; update opp_canonical_codec.hpp to match");

} // namespace sysio::opp::canonical
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,6 @@ DataStream& operator>>(DataStream& ds, WirePermission& t) {
return ds >> t.account >> t.permission;
}

// EncodingFlags: { Endianness; HashAlgorithm; LengthEncoding; }
template <typename DataStream>
DataStream& operator<<(DataStream& ds, const EncodingFlags& t) {
return ds << t.endianness << t.hash_algorithm << t.length_encoding;
}
template <typename DataStream>
DataStream& operator>>(DataStream& ds, EncodingFlags& t) {
return ds >> t.endianness >> t.hash_algorithm >> t.length_encoding;
}

// ---------------------------------------------------------------------------
// v6 registry-entity messages (Chain / Token / ChainToken / Reserve / ReserveAmount)
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -242,17 +232,17 @@ DataStream& operator>>(DataStream& ds, MessagePayload& t) {
return ds >> t.version >> t.attestations;
}

// MessageHeader: all 8 fields
// MessageHeader: all 7 fields
template <typename DataStream>
DataStream& operator<<(DataStream& ds, const MessageHeader& t) {
return ds << t.endpoints << t.message_id << t.previous_message_id
<< t.encoding_flags << t.payload_size << t.payload_checksum
<< t.payload_size << t.payload_checksum
<< t.timestamp << t.header_checksum;
}
template <typename DataStream>
DataStream& operator>>(DataStream& ds, MessageHeader& t) {
return ds >> t.endpoints >> t.message_id >> t.previous_message_id
>> t.encoding_flags >> t.payload_size >> t.payload_checksum
>> t.payload_size >> t.payload_checksum
>> t.timestamp >> t.header_checksum;
}

Expand Down
Loading
Loading