Skip to content

Commit 9ec3033

Browse files
authored
Change serialisation format for service maps (#2301)
1 parent 92f9fe2 commit 9ec3033

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1161
-670
lines changed

CHANGELOG.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [0.19.2]
99

10+
### Added
11+
12+
- New `get_user_data_v1` and `get_member_data_v1` C++ API calls have been added to retrieve the data associated with users/members. The user/member data is no longer included in the `AuthnIdentity` caller struct (#2301).
13+
- New `get_user_cert_v1` and `get_member_cert_v1` C++ API calls have been added to retrieve the PEM certificate of the users/members. The user/member certificate is no longer included in the `AuthnIdentity` caller struct (#2301).
14+
1015
### Changed
1116

12-
- String values in query parameters no longer need to be quoted. For instance, you should now call `/network/nodes?host=127.0.0.1` rather than `/network/nodes?host="127.0.0.1"`.
13-
- Schema documentation for query parameters should now be added with `add_query_parameter`, rather than `set_auto_schema`. The `In` type of `set_auto_schema` should only be used to describe the request body.
14-
- `json_adapter` will no longer try to convert query parameters to a JSON object. The JSON passed as an argument to these handlers will now be populated only by the request body. The query string should be parsed separately, and `http::parse_query(s)` is added as a starting point. This means strings in query parameters no longer need to be quoted.
17+
- String values in query parameters no longer need to be quoted. For instance, you should now call `/network/nodes?host=127.0.0.1` rather than `/network/nodes?host="127.0.0.1"` (#2309).
18+
- Schema documentation for query parameters should now be added with `add_query_parameter`, rather than `set_auto_schema`. The `In` type of `set_auto_schema` should only be used to describe the request body (#2309).
19+
- `json_adapter` will no longer try to convert query parameters to a JSON object. The JSON passed as an argument to these handlers will now be populated only by the request body. The query string should be parsed separately, and `http::parse_query(s)` is added as a starting point. This means strings in query parameters no longer need to be quoted (#2309).
1520
- Enum values returned by built-in REST API endpoints are now PascalCase. Lua governance scripts that use enum values need to be updated as well, for example, `"ACTIVE"` becomes `"Active"` for member info. The same applies when using the `/gov/query` endpoint (#2152).
21+
- Most service tables (e.g. for nodes and signatures) are now serialised as JSON instead of msgpack. Some tables (e.g. user and member certificates) are serialised as raw bytes for performance reasons (#2301).
22+
- The users and members tables have been split into `public:ccf.gov.users.certs`/`public:ccf.gov.users.info` and `public:ccf.gov.members.certs`/`public:ccf.gov.members.encryption_public_keys`/`public:ccf.gov.members.info` respectively (#2301).
1623

1724
## [0.19.1]
1825

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ if(BUILD_TESTS)
269269
${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/hash.cpp
270270
${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/thread_messaging.cpp
271271
${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/lru.cpp
272+
${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/hex.cpp
272273
)
273274
target_link_libraries(ds_test PRIVATE ${CMAKE_THREAD_LIBS_INIT})
274275

doc/schemas/app_openapi.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"type": "object"
5757
},
5858
"EntityId": {
59+
"format": "hex",
5960
"pattern": "^[a-f0-9]{64}$",
6061
"type": "string"
6162
},

doc/schemas/gov_openapi.json

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"type": "object"
5757
},
5858
"EntityId": {
59+
"format": "hex",
5960
"pattern": "^[a-f0-9]{64}$",
6061
"type": "string"
6162
},
@@ -286,7 +287,7 @@
286287
"Script": {
287288
"properties": {
288289
"bytecode": {
289-
"$ref": "#/components/schemas/uint8_array"
290+
"$ref": "#/components/schemas/base64string"
290291
},
291292
"text": {
292293
"$ref": "#/components/schemas/string"
@@ -351,6 +352,10 @@
351352
],
352353
"type": "object"
353354
},
355+
"base64string": {
356+
"format": "base64",
357+
"type": "string"
358+
},
354359
"int64": {
355360
"maximum": 9223372036854775807,
356361
"minimum": -9223372036854775808,
@@ -364,17 +369,6 @@
364369
"maximum": 18446744073709551615,
365370
"minimum": 0,
366371
"type": "integer"
367-
},
368-
"uint8": {
369-
"maximum": 255,
370-
"minimum": 0,
371-
"type": "integer"
372-
},
373-
"uint8_array": {
374-
"items": {
375-
"$ref": "#/components/schemas/uint8"
376-
},
377-
"type": "array"
378372
}
379373
},
380374
"securitySchemes": {

doc/schemas/node_openapi.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"type": "object"
5757
},
5858
"EntityId": {
59+
"format": "hex",
5960
"pattern": "^[a-f0-9]{64}$",
6061
"type": "string"
6162
},
@@ -306,7 +307,7 @@
306307
"Quote": {
307308
"properties": {
308309
"endorsements": {
309-
"$ref": "#/components/schemas/string"
310+
"$ref": "#/components/schemas/base64string"
310311
},
311312
"format": {
312313
"$ref": "#/components/schemas/QuoteFormat"
@@ -318,7 +319,7 @@
318319
"$ref": "#/components/schemas/EntityId"
319320
},
320321
"raw": {
321-
"$ref": "#/components/schemas/string"
322+
"$ref": "#/components/schemas/base64string"
322323
}
323324
},
324325
"required": [
@@ -363,6 +364,10 @@
363364
],
364365
"type": "string"
365366
},
367+
"base64string": {
368+
"format": "base64",
369+
"type": "string"
370+
},
366371
"boolean": {
367372
"type": "boolean"
368373
},

getting_started/setup_vm/roles/ccf_build/vars/common.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ debs:
2222
- sudo
2323
- curl
2424
- shellcheck
25-
- xxd
2625

2726
mbedtls_ver: "2.16.8"
2827
mbedtls_dir: "mbedtls-{{ mbedtls_ver }}"

python/ccf/clients.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def escape_loguru_tags(s):
3535
return loguru_tag_regex.sub(lambda match: f"\\{match[0]}", s)
3636

3737

38-
def truncate(string: str, max_len: int = 128):
38+
def truncate(string: str, max_len: int = 256):
3939
if len(string) > max_len:
4040
return f"{string[: max_len]} + {len(string) - max_len} chars"
4141
else:

python/ccf/ledger.py

Lines changed: 21 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import struct
66
import os
77

8-
from typing import BinaryIO, NamedTuple, Optional, Set
9-
from enum import IntEnum
8+
from typing import BinaryIO, NamedTuple, Optional
109

1110
# Default implementation has buggy interaction between read_bytes and tell, so use fallback
1211
import msgpack.fallback as msgpack # type: ignore
12+
import json
13+
import base64
1314

1415
from loguru import logger as LOG # type: ignore
1516
from cryptography.x509 import load_pem_x509_certificate
@@ -73,7 +74,6 @@ class PublicDomain:
7374
_version: int
7475
_max_conflict_version: int
7576
_tables: dict
76-
_msgpacked_tables: Set[str]
7777

7878
def __init__(self, buffer: io.BytesIO):
7979
self._buffer = buffer
@@ -83,12 +83,6 @@ def __init__(self, buffer: io.BytesIO):
8383
self._version = self._read_next()
8484
self._max_conflict_version = self._read_next()
8585
self._tables = {}
86-
# Keys and Values may have custom serialisers.
87-
# Store most as raw bytes, only decode a few which we know are msgpack and are required for ledger verification.
88-
self._msgpacked_tables = {
89-
SIGNATURE_TX_TABLE_NAME,
90-
NODES_TABLE_NAME,
91-
}
9286
self._read()
9387

9488
def _read_next(self):
@@ -124,19 +118,12 @@ def _read(self):
124118
for _ in range(write_count):
125119
k = self._read_next_entry()
126120
val = self._read_next_entry()
127-
if map_name in self._msgpacked_tables:
128-
k = msgpack.unpackb(k, **UNPACK_ARGS)
129-
val = msgpack.unpackb(val, **UNPACK_ARGS)
130-
if map_name == NODES_TABLE_NAME:
131-
k = str(k)
132121
records[k] = val
133122

134123
remove_count = self._read_next()
135124
if remove_count:
136125
for _ in range(remove_count):
137126
k = self._read_next_entry()
138-
if map_name in self._msgpacked_tables:
139-
k = msgpack.unpackb(k, **UNPACK_ARGS)
140127
records[k] = None
141128

142129
LOG.trace(
@@ -162,14 +149,6 @@ def _byte_read_safe(file, num_of_bytes):
162149
return ret
163150

164151

165-
class NodeStatus(IntEnum):
166-
"""These are the corresponding status meanings from the ccf.nodes table"""
167-
168-
PENDING = 0
169-
TRUSTED = 1
170-
RETIRED = 2
171-
172-
173152
class TxBundleInfo(NamedTuple):
174153
"""Bundle for transaction information required for validation """
175154

@@ -178,7 +157,7 @@ class TxBundleInfo(NamedTuple):
178157
node_cert: bytes
179158
signature: bytes
180159
node_activity: dict
181-
signing_node: bytes
160+
signing_node: str
182161

183162

184163
class LedgerValidator:
@@ -187,27 +166,11 @@ class LedgerValidator:
187166
It has the ability to take transactions and it maintains a MerkleTree data structure similar to CCF.
188167
189168
Ledger is valid and hasn't been tampered with if following conditions are met:
190-
1) The merkle proof is signed by a TRUSTED node in the given network
169+
1) The merkle proof is signed by a Trusted node in the given network
191170
2) The merkle root and signature are verified with the node cert
192171
3) The merkle proof is correct for each set of transactions
193172
"""
194173

195-
# The node that is expected to sign the signature transaction
196-
# The certificate used to sign the signature transaction
197-
# https://github.com/microsoft/CCF/blob/main/src/node/nodes.h
198-
EXPECTED_NODE_CERT_INDEX = 1
199-
# The current network trust status of the Node at the time of the current transaction
200-
EXPECTED_NODE_STATUS_INDEX = 4
201-
202-
# Signature table contains PrimarySignature which extends NodeSignature. NodeId should be at index 1 in the serialized Node
203-
# https://github.com/microsoft/CCF/blob/main/src/node/signatures.h
204-
EXPECTED_NODE_SIGNATURE_INDEX = 0
205-
EXPECTED_NODE_SEQNO_INDEX = 1
206-
EXPECTED_NODE_VIEW_INDEX = 2
207-
EXPECTED_ROOT_INDEX = 5
208-
# https://github.com/microsoft/CCF/blob/main/src/node/node_signature.h
209-
EXPECTED_SIGNING_NODE_ID_INDEX = 1
210-
EXPECTED_SIGNATURE_INDEX = 0
211174
# Constant for the size of a hashed transaction
212175
SHA_256_HASH_SIZE = 32
213176

@@ -240,34 +203,29 @@ def add_transaction(self, transaction):
240203
# Add contributing nodes certs and update nodes network trust status for verification
241204
if NODES_TABLE_NAME in tables:
242205
node_table = tables[NODES_TABLE_NAME]
243-
for nodeid, values in node_table.items():
206+
for node_id, node_info in node_table.items():
207+
node_id = node_id.decode()
208+
node_info = json.loads(node_info)
244209
# Add the nodes certificate
245-
self.node_certificates[nodeid] = values[self.EXPECTED_NODE_CERT_INDEX]
210+
self.node_certificates[node_id] = node_info["cert"].encode()
246211
# Update node trust status
247-
self.node_activity_status[nodeid] = NodeStatus(
248-
values[self.EXPECTED_NODE_STATUS_INDEX]
249-
)
212+
self.node_activity_status[node_id] = node_info["status"]
250213

251214
# This is a merkle root/signature tx if the table exists
252215
if SIGNATURE_TX_TABLE_NAME in tables:
253216
self.signature_count += 1
254217
signature_table = tables[SIGNATURE_TX_TABLE_NAME]
255218

256-
for nodeid, values in signature_table.items():
257-
current_seqno = values[self.EXPECTED_NODE_SEQNO_INDEX]
258-
current_view = values[self.EXPECTED_NODE_VIEW_INDEX]
259-
signing_node = str(
260-
values[self.EXPECTED_NODE_SIGNATURE_INDEX][
261-
self.EXPECTED_SIGNING_NODE_ID_INDEX
262-
]
263-
)
219+
for _, signature in signature_table.items():
220+
signature = json.loads(signature)
221+
current_seqno = signature["seqno"]
222+
current_view = signature["view"]
223+
signing_node = signature["node"]
264224

265225
# Get binary representations for the cert, existing root, and signature
266-
cert = b"".join(self.node_certificates[signing_node])
267-
existing_root = b"".join(values[self.EXPECTED_ROOT_INDEX])
268-
signature = values[self.EXPECTED_NODE_SIGNATURE_INDEX][
269-
self.EXPECTED_SIGNATURE_INDEX
270-
]
226+
cert = self.node_certificates[signing_node]
227+
existing_root = bytes.fromhex(signature["root"])
228+
signature = base64.b64decode(signature["sig"])
271229

272230
tx_info = TxBundleInfo(
273231
self.merkle,
@@ -295,7 +253,7 @@ def _verify_tx_set(self, tx_info: TxBundleInfo):
295253
"""
296254
Verify items 1, 2, and 3 for all the transactions up until a signature.
297255
"""
298-
# 1) The merkle root is signed by a TRUSTED node in the given network, else throws
256+
# 1) The merkle root is signed by a Trusted node in the given network, else throws
299257
self._verify_node_status(tx_info)
300258
# 2) The merkle root and signature are verified with the node cert, else throws
301259
self._verify_root_signature(tx_info)
@@ -304,8 +262,8 @@ def _verify_tx_set(self, tx_info: TxBundleInfo):
304262

305263
@staticmethod
306264
def _verify_node_status(tx_info: TxBundleInfo):
307-
"""Verify item 1, The merkle root is signed by a TRUSTED node in the given network"""
308-
if tx_info.node_activity[tx_info.signing_node] != NodeStatus.TRUSTED:
265+
"""Verify item 1, The merkle root is signed by a Trusted node in the given network"""
266+
if tx_info.node_activity[tx_info.signing_node] != "Trusted":
309267
LOG.error(
310268
f"The signing node {tx_info.signing_node!r} is not trusted by the network"
311269
)
@@ -535,10 +493,6 @@ def __iter__(self):
535493
return self
536494

537495

538-
def extract_msgpacked_data(data: bytes):
539-
return msgpack.unpackb(data, **UNPACK_ARGS)
540-
541-
542496
class InvalidRootException(Exception):
543497
"""MerkleTree root doesn't match with the root reported in the signature's table"""
544498

python/ccf/proposal_generator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ def set_module(module_name: str, module_path: str, **kwargs):
347347
if module_name_.suffix == ".js":
348348
with open(module_path) as f:
349349
js = f.read()
350-
proposal_args = {"name": module_name, "module": {"js": js}}
350+
proposal_args = {"name": module_name, "module": js}
351351
else:
352352
raise ValueError("module name must end with .js")
353353
return build_proposal("set_module", proposal_args, **kwargs)
@@ -367,7 +367,7 @@ def read_modules(modules_path: str) -> List[dict]:
367367
rel_module_name = rel_module_name.replace("\\", "/") # Windows support
368368
with open(path) as f:
369369
js = f.read()
370-
modules.append({"name": rel_module_name, "module": {"js": js}})
370+
modules.append({"name": rel_module_name, "module": js})
371371
return modules
372372

373373

python/ledger_tutorial.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ccf.ledger
55
import sys
66
from loguru import logger as LOG
7+
import json
78

89
# Note: It is safer to run the ledger tutorial when the service has stopped
910
# as all ledger files will have been written to.
@@ -30,8 +31,6 @@
3031
target_table = "public:ccf.gov.nodes.info"
3132

3233
# SNIPPET_START: iterate_over_ledger
33-
target_table_changes = 0 # Simple counter
34-
3534
for chunk in ledger:
3635
for transaction in chunk:
3736
# Retrieve all public tables changed in transaction
@@ -41,10 +40,8 @@
4140
if target_table in public_tables:
4241
# Ledger verification is happening implicitly in ccf.ledger.Ledger()
4342
for key, value in public_tables[target_table].items():
44-
target_table_changes += 1 # A key was changed
45-
# Log the key and value for the transaction on the target table
46-
# The target_table: 'public:ccf.gov.nodes.info' has already been decoded in ledger.py
47-
# For other tables knowledge of serialization scheme used is important.
48-
# If the table was using msgpack, use ccf.ledger.extract_msgpacked_data(data)
49-
LOG.info(f"{key} : {value}")
43+
# Note: `key` and `value` are raw bytes here.
44+
# This code needs to have knowledge of the serialisation format for each table.
45+
# In this case, the target table 'public:ccf.gov.nodes.info' is raw bytes to JSON.
46+
LOG.info(f"{key.decode()} : {json.loads(value)}")
5047
# SNIPPET_END: iterate_over_ledger

python/utils/verify_quote.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ trap cleanup EXIT
6565

6666
curl_output=$(curl -sS --fail -X GET "${node_address}"/node/quotes/self "${@}")
6767

68-
echo "${curl_output}" | jq -r .raw | xxd -r -p > "${tmp_dir}/${quote_file_name}"
69-
echo "${curl_output}" | jq -r .endorsements | xxd -r -p > "${tmp_dir}/${endorsements_file_name}"
68+
echo "${curl_output}" | jq -r .raw | base64 --decode > "${tmp_dir}/${quote_file_name}"
69+
echo "${curl_output}" | jq -r .endorsements | base64 --decode > "${tmp_dir}/${endorsements_file_name}"
7070

7171
if [ ! -s "${tmp_dir}/${quote_file_name}" ]; then
7272
echo "Error: Node quote is empty. Virtual mode does not support SGX quotes."

0 commit comments

Comments
 (0)