Skip to content

Commit 9945270

Browse files
committed
ASN.1 schema and validation
1 parent c82e04f commit 9945270

File tree

6 files changed

+303
-4
lines changed

6 files changed

+303
-4
lines changed

codec/data/block.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
"guarantees": [
156156
{
157157
"report": {
158-
"packge_spec": {
158+
"package_spec": {
159159
"hash": "0x30466e0ae1b05dde5249872475f6beeac368fd014b5a3413ceb32d3872143284",
160160
"len": 42,
161161
"root": "0x7af11fdaa717c398e223211842b41392f18df4bbc4ea0f4cfb972f19c7a64949",

codec/data/extrinsic.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
"guarantees": [
131131
{
132132
"report": {
133-
"packge_spec": {
133+
"package_spec": {
134134
"hash": "0x30466e0ae1b05dde5249872475f6beeac368fd014b5a3413ceb32d3872143284",
135135
"len": 42,
136136
"root": "0x7af11fdaa717c398e223211842b41392f18df4bbc4ea0f4cfb972f19c7a64949",

codec/data/guarantees_extrinsic.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[
22
{
33
"report": {
4-
"packge_spec": {
4+
"package_spec": {
55
"hash": "0x30466e0ae1b05dde5249872475f6beeac368fd014b5a3413ceb32d3872143284",
66
"len": 42,
77
"root": "0x7af11fdaa717c398e223211842b41392f18df4bbc4ea0f4cfb972f19c7a64949",

codec/data/work_report.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"packge_spec": {
2+
"package_spec": {
33
"hash": "0x30466e0ae1b05dde5249872475f6beeac368fd014b5a3413ceb32d3872143284",
44
"len": 42,
55
"root": "0x7af11fdaa717c398e223211842b41392f18df4bbc4ea0f4cfb972f19c7a64949",

codec/schema.asn

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
TypesModule DEFINITIONS ::= BEGIN
2+
3+
-- Tiny Constants
4+
5+
validators-count INTEGER ::= 6
6+
epoch-length INTEGER ::= 12
7+
core-count INTEGER ::= 2
8+
9+
-- (validators-count * 2/3 + 1)
10+
validators-super-majority INTEGER ::= 5
11+
-- (core-count + 1) / 8
12+
avail-bitfield-bytes INTEGER ::= 1
13+
14+
-- Generic Primitive Types
15+
16+
U8 ::= INTEGER (0..255)
17+
U16 ::= INTEGER(0..65535)
18+
U32 ::= INTEGER (0..4294967295)
19+
I64 ::= INTEGER(-9223372036854775808..9223372036854775807)
20+
21+
ByteSequence ::= OCTET STRING
22+
ByteArray32 ::= OCTET STRING (SIZE(32))
23+
24+
-- Application Specific Primitive Types
25+
26+
OpaqueHash ::= ByteArray32
27+
28+
TimeSlot ::= U32
29+
ServiceId ::= U32
30+
Gas ::= I64
31+
ValidatorIndex ::= U16
32+
CoreIndex ::= U16
33+
34+
BandersnatchKey ::= ByteArray32
35+
Ed25519Key ::= ByteArray32
36+
37+
BandersnatchVrfSignature ::= OCTET STRING (SIZE(96))
38+
BandersnatchRingSignature ::= OCTET STRING (SIZE(784))
39+
Ed25519Signature ::= OCTET STRING (SIZE(64))
40+
41+
-- Refine Context
42+
43+
RefineContext ::= SEQUENCE {
44+
anchor OpaqueHash,
45+
state-root OpaqueHash,
46+
beefy-root OpaqueHash,
47+
lookup-anchor OpaqueHash,
48+
lookup-anchor-slot TimeSlot,
49+
prerequisite OpaqueHash OPTIONAL
50+
}
51+
52+
-- Work Package
53+
54+
ImportSpec ::= SEQUENCE {
55+
tree-root OpaqueHash,
56+
index U32
57+
}
58+
59+
ExtrinsicSpec ::= SEQUENCE {
60+
hash OpaqueHash,
61+
len U32
62+
}
63+
64+
Authorizer ::= SEQUENCE {
65+
code-hash OpaqueHash,
66+
params ByteSequence
67+
}
68+
69+
WorkItem ::= SEQUENCE {
70+
service ServiceId,
71+
code-hash OpaqueHash,
72+
payload ByteSequence,
73+
gas-limit Gas,
74+
import-segments SEQUENCE OF ImportSpec,
75+
extrinsic SEQUENCE OF ExtrinsicSpec,
76+
export-count U16
77+
}
78+
79+
WorkPackage ::= SEQUENCE {
80+
authorization ByteSequence,
81+
auth-code-host ServiceId,
82+
authorizer Authorizer,
83+
context RefineContext,
84+
items SEQUENCE OF WorkItem
85+
}
86+
87+
-- Work Report
88+
89+
WorkExecResult ::= CHOICE {
90+
ok [0] ByteSequence,
91+
out-of-gas [1] NULL,
92+
panic [2] NULL,
93+
bad-code [3] NULL,
94+
code-oversize [4] NULL
95+
}
96+
97+
WorkResult ::= SEQUENCE {
98+
service ServiceId,
99+
code-hash OpaqueHash,
100+
payload-hash OpaqueHash,
101+
gas-ratio Gas,
102+
result WorkExecResult
103+
}
104+
105+
WorkPackageSpec ::= SEQUENCE {
106+
hash OpaqueHash,
107+
len U32,
108+
root OpaqueHash,
109+
segments OpaqueHash
110+
}
111+
112+
WorkReport ::= SEQUENCE {
113+
package-spec WorkPackageSpec,
114+
context RefineContext,
115+
core-index CoreIndex,
116+
authorizer-hash OpaqueHash,
117+
auth-output ByteSequence,
118+
results SEQUENCE OF WorkResult
119+
}
120+
121+
-- Header
122+
123+
EpochMark ::= SEQUENCE {
124+
entropy OpaqueHash,
125+
validators SEQUENCE SIZE(validators-count) OF BandersnatchKey
126+
}
127+
128+
TicketBody ::= SEQUENCE {
129+
id OpaqueHash,
130+
attempt U8
131+
}
132+
133+
TicketsMark ::= SEQUENCE SIZE(epoch-length) OF TicketBody
134+
135+
Header ::= SEQUENCE {
136+
parent OpaqueHash,
137+
parent-state-root OpaqueHash,
138+
extrinsic-hash OpaqueHash,
139+
slot TimeSlot,
140+
epoch-mark EpochMark OPTIONAL,
141+
tickets-mark TicketsMark OPTIONAL,
142+
offenders-mark SEQUENCE OF Ed25519Key,
143+
author-index ValidatorIndex,
144+
entropy-source BandersnatchVrfSignature,
145+
seal BandersnatchVrfSignature
146+
}
147+
148+
-- Tickets
149+
150+
TicketEnvelope ::= SEQUENCE {
151+
attempt U8,
152+
signature BandersnatchRingSignature
153+
}
154+
155+
TicketsExtrinsic ::= SEQUENCE OF TicketEnvelope
156+
157+
-- Disputes
158+
159+
Judgement ::= SEQUENCE {
160+
vote BOOLEAN,
161+
index ValidatorIndex,
162+
signature Ed25519Signature
163+
}
164+
165+
Verdict ::= SEQUENCE {
166+
target OpaqueHash,
167+
age U32,
168+
votes SEQUENCE SIZE(validators-super-majority) OF Judgement
169+
}
170+
171+
Culprit ::= SEQUENCE {
172+
target OpaqueHash,
173+
key Ed25519Key,
174+
signature Ed25519Signature
175+
}
176+
177+
Fault ::= SEQUENCE {
178+
target OpaqueHash,
179+
vote BOOLEAN,
180+
key Ed25519Key,
181+
signature Ed25519Signature
182+
}
183+
184+
DisputesExtrinsic ::= SEQUENCE {
185+
verdicts SEQUENCE OF Verdict,
186+
culprits SEQUENCE OF Culprit,
187+
faults SEQUENCE OF Fault
188+
}
189+
190+
-- Preimages
191+
192+
Preimage ::= SEQUENCE {
193+
requester ServiceId,
194+
blob ByteSequence
195+
}
196+
197+
PreimagesExtrinsic ::= SEQUENCE OF Preimage
198+
199+
-- Assurances
200+
201+
AvailAssurance ::= SEQUENCE {
202+
anchor OpaqueHash,
203+
bitfield OCTET STRING (SIZE(avail-bitfield-bytes)),
204+
validator-index ValidatorIndex,
205+
signature Ed25519Signature
206+
}
207+
208+
AssurancesExtrinsic ::= SEQUENCE OF AvailAssurance
209+
210+
-- Guarantees
211+
212+
ValidatorSignature ::= SEQUENCE {
213+
validator-index ValidatorIndex,
214+
signature Ed25519Signature
215+
}
216+
217+
ReportGuarantee ::= SEQUENCE {
218+
report WorkReport,
219+
slot TimeSlot,
220+
signatures SEQUENCE OF ValidatorSignature
221+
}
222+
223+
GuaranteesExtrinsic ::= SEQUENCE OF ReportGuarantee
224+
225+
-- Block
226+
227+
Extrinsic ::= SEQUENCE {
228+
tickets TicketsExtrinsic,
229+
disputes DisputesExtrinsic,
230+
preimages PreimagesExtrinsic,
231+
assurances AssurancesExtrinsic,
232+
guarantees GuaranteesExtrinsic
233+
}
234+
235+
Block ::= SEQUENCE {
236+
header Header,
237+
extrinsic Extrinsic
238+
}
239+
240+
END

codec/validate.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
3+
from pathlib import Path
4+
import asn1tools
5+
import json
6+
import sys
7+
8+
import os
9+
import re
10+
11+
# - JSON uses snake case, ASN.1 requires kebab case
12+
# - JSON prefix octet strings with '0x', ASN doesn't like it
13+
def make_asn1_parsable(json_str):
14+
return json_str.replace('_', '-').replace('0x', '')
15+
16+
def path_to_struct_name(path):
17+
# Strip the directory path and extension
18+
name = os.path.splitext(os.path.basename(path))[0]
19+
# Remove "_X" if it ends with a number
20+
name = re.sub(r'_\d+$', '', name)
21+
# Convert kebab-case or snake_case to PascalCase
22+
name = re.sub(r'[-_](\w)', lambda m: m.group(1).upper(), name)
23+
# Capitalize the first character
24+
name = name[0].upper() + name[1:]
25+
return name
26+
27+
def validate_case(schema, path):
28+
print("* Validating: ", path)
29+
30+
type_name = path_to_struct_name(path)
31+
32+
# Decode from json using the schema
33+
json_bytes = open(path, "rb").read()
34+
json_str_org = json_bytes.decode('utf-8')
35+
json_str_org = make_asn1_parsable(json_str_org)
36+
37+
json_bytes = json_str_org.encode('utf-8')
38+
decoded = schema.decode(type_name, json_bytes, check_constraints=True)
39+
40+
# Encode to json using the schema
41+
encoded = schema.encode(type_name, decoded, check_constraints=True)
42+
# Original json uses snake case, asn1 requires kebab case
43+
json_str = encoded.decode('utf-8')
44+
json_obj = json.loads(json_str)
45+
# Strings are converted to arrays of characters,
46+
# map back to single string.
47+
json_str = json.dumps(json_obj, indent = 4)
48+
49+
assert (json_str.rstrip().lower() == json_str_org.rstrip().lower())
50+
51+
52+
def main():
53+
# Validate tiny
54+
schema = asn1tools.compile_files(["schema.asn"], codec="jer")
55+
for path in Path("data").iterdir():
56+
if path.is_file() and path.suffix == ".json":
57+
validate_case(schema, path)
58+
59+
main()

0 commit comments

Comments
 (0)