Skip to content

Commit 0bcd42f

Browse files
wesbiggsWes Biggs
and
Wes Biggs
authored
Support Frequency schema names (#46)
* Deploy now uses `dsnp.*` schema names and new versions of add/propose schema extrinsics. * Adds `dsnp.getSchemaId()` and utility types/methods for registering non-standard mappings (see `README.md`). * New `find.ts` CLI script will do a forward lookup of DSNP schema names on chain. cf. frequency-chain/frequency#1784 Updated to use `@frequency-chain/[email protected]`. --------- Co-authored-by: Wes Biggs <[email protected]>
1 parent bff0997 commit 0bcd42f

File tree

6 files changed

+1146
-1272
lines changed

6 files changed

+1146
-1272
lines changed

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,43 @@ npm install @dsnp/frequency-schemas
1212
### Use Schema
1313

1414
```typescript
15-
import { dsnp } from "frequency-schemas";
15+
import { dsnp } from "@dsnp/frequency-schemas";
1616

1717
console.log(dsnp.getSchema("broadcast"));
1818
```
1919

20+
### Get Schema Id from Chain
21+
22+
```typescript
23+
import { dsnp } from "@dsnp/frequency-schemas";
24+
import { ApiPromise } from "@polkadot/api";
25+
26+
const api = ApiPromise.create(/* ... */);
27+
console.log(await dsnp.getSchemaId(api, "broadcast"));
28+
```
29+
30+
Frequency mainnet and testnet have well-known Ids defined in `dsnp/index.ts`.
31+
Other configurations default to assuming `npm run deploy` has been run on a fresh chain (which is usually the case for a localhost instance), but can be overridden:
32+
33+
```
34+
dsnp.setSchemaMapping(api.genesisHash.toString(), {
35+
// format is dsnpName: { version: schemaId, ... }
36+
"tombstone": { "1.2": 64 },
37+
"broadcast": { "1.2": 67 },
38+
// ...
39+
});
40+
41+
console.log(await dsnp.getSchemaId(api, "broadcast")); // yields 67
42+
```
43+
2044
### With Parquet Writer
2145

2246
```sh
2347
npm install @dsnp/parquetjs
2448
```
2549

2650
```typescript
27-
import { parquet } from "frequency-schemas";
51+
import { parquet } from "@dsnp/frequency-schemas";
2852
import { ParquetWriter } from "@dsnp/parquetjs";
2953

3054
const [parquetSchema, writerOptions] = parquet.fromFrequencySchema("broadcast");
@@ -150,6 +174,14 @@ There are 8 schemas on the connected chain.
150174
...
151175
```
152176

177+
## Find Frequency Schema Ids that Match DSNP Schema Versions
178+
179+
This script will look up and verify schemas in the schema registry that match the DSNP names and versions defined in `dsnp/index.ts`.
180+
181+
```sh
182+
DEPLOY_SCHEMA_ENDPOINT_URL="ws://127.0.0.1:9944" npm run find
183+
```
184+
153185
## Use with Docker
154186

155187
This repo deploys `dsnp/instant-seal-node-with-deployed-schemas` to Docker Hub.

cli/deploy.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getFrequencyAPI, getSignerAccountKeys } from "./services/connect.js";
2-
import dsnp, { SchemaName as DsnpSchemaName } from "../dsnp/index.js";
2+
import dsnp, { SchemaName as DsnpSchemaName, SchemaMapping, GENESIS_HASH_MAINNET } from "../dsnp/index.js";
33
import { EventRecord } from "@polkadot/types/interfaces";
44

55
export const deploy = async () => {
@@ -42,7 +42,8 @@ export const deploy = async () => {
4242

4343
console.log("Deploy of Schemas Starting...");
4444

45-
return await createSchemas(schemaNames);
45+
const mapping = await createSchemas(schemaNames);
46+
console.log("Generated schema mapping:\n", JSON.stringify(mapping, null, 2));
4647
};
4748

4849
// Given a list of events, a section and a method,
@@ -54,17 +55,13 @@ const eventWithSectionAndMethod = (events: EventRecord[], section: string, metho
5455

5556
// Given a list of schema names, attempt to create them with the chain.
5657
const createSchemas = async (schemaNames: string[]) => {
57-
type SchemaInfo = {
58-
schemaName: string;
59-
id?: number;
60-
};
58+
type SchemaInfo = [schemaName: DsnpSchemaName, { [version: string]: number }];
6159

6260
const promises: Promise<SchemaInfo>[] = [];
6361
const api = await getFrequencyAPI();
6462
const signerAccountKeys = getSignerAccountKeys();
6563
// Mainnet genesis hash means we should propose instead of create
66-
const shouldPropose =
67-
api.genesisHash.toHex() === "0x4a587bf17a404e3572747add7aab7bbe56e805a5479c6c436f07f36fcc8d3ae1";
64+
const shouldPropose = api.genesisHash.toHex() === GENESIS_HASH_MAINNET;
6865

6966
if (shouldPropose && schemaNames.length > 1) {
7067
console.error("Proposing to create schemas can only occur one at a time. Please try again with only one schema.");
@@ -92,55 +89,70 @@ const createSchemas = async (schemaNames: string[]) => {
9289
// Propose to create
9390
const promise = new Promise<SchemaInfo>((resolve, reject) => {
9491
api.tx.schemas
95-
.proposeToCreateSchema(
92+
.proposeToCreateSchemaV2(
9693
json_no_ws,
9794
schemaDeploy.modelType,
9895
schemaDeploy.payloadLocation,
9996
schemaDeploy.settings,
97+
"dsnp." + schemaName,
10098
)
10199
.signAndSend(signerAccountKeys, { nonce }, ({ status, events, dispatchError }) => {
102100
if (dispatchError) {
103101
console.error("ERROR: ", dispatchError.toHuman());
104102
console.log("Might already have a proposal with the same hash?");
105-
reject();
103+
reject(dispatchError.toHuman());
106104
} else if (status.isInBlock || status.isFinalized) {
107105
const evt = eventWithSectionAndMethod(events, "council", "Proposed");
108106
if (evt) {
109107
const id = evt?.data[1];
110108
const hash = evt?.data[2].toHex();
111109
console.log("SUCCESS: " + schemaName + " schema proposed with id of " + id + " and hash of " + hash);
112-
resolve({ schemaName, id: Number(id.toHuman()) });
113-
} else resolve({ schemaName });
110+
const v2n = Object.fromEntries([[schemaDeploy.dsnpVersion, Number(id.toHuman())]]);
111+
resolve([schemaName as DsnpSchemaName, v2n]);
112+
} else {
113+
const err = "Proposed event not found";
114+
console.error(`ERROR: ${err}`);
115+
reject(err);
116+
}
114117
}
115118
});
116119
});
117120
promises[idx] = promise;
118121
} else {
119122
// Create directly via sudo
120-
const tx = api.tx.schemas.createSchemaViaGovernance(
123+
const tx = api.tx.schemas.createSchemaViaGovernanceV2(
121124
signerAccountKeys.address,
122125
json_no_ws,
123126
schemaDeploy.modelType,
124127
schemaDeploy.payloadLocation,
125128
schemaDeploy.settings,
129+
"dsnp." + schemaName,
126130
);
127131
const promise = new Promise<SchemaInfo>((resolve, reject) => {
128132
api.tx.sudo.sudo(tx).signAndSend(signerAccountKeys, { nonce }, ({ status, events, dispatchError }) => {
129133
if (dispatchError) {
130134
console.error("ERROR: ", dispatchError.toHuman());
131-
reject();
135+
reject(dispatchError.toHuman());
132136
} else if (status.isInBlock || status.isFinalized) {
133137
const evt = eventWithSectionAndMethod(events, "schemas", "SchemaCreated");
134138
if (evt) {
135-
const val = evt?.data[1];
136-
console.log("SUCCESS: " + schemaName + " schema created with id of " + val);
137-
resolve({ schemaName, id: Number(val.toHuman()) });
138-
} else resolve({ schemaName });
139+
const id = evt?.data[1];
140+
console.log("SUCCESS: " + schemaName + " schema created with id of " + id);
141+
const v2n = Object.fromEntries([[schemaDeploy.dsnpVersion, Number(id.toHuman())]]);
142+
resolve([schemaName as DsnpSchemaName, v2n]);
143+
} else {
144+
const err = "SchemaCreated event not found";
145+
console.error(`ERROR: ${err}`);
146+
reject(err);
147+
}
139148
}
140149
});
141150
});
142151
promises[idx] = promise;
143152
}
144153
}
145-
return Promise.all(promises);
154+
const output = await Promise.all(promises);
155+
const mapping: { [genesisHash: string]: SchemaMapping } = {};
156+
mapping[api.genesisHash.toString()] = Object.fromEntries(output);
157+
return mapping;
146158
};

cli/find.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { getFrequencyAPI } from "./services/connect.js";
2+
import { schemas } from "../dsnp/index.js";
3+
4+
const find = async () => {
5+
const api = await getFrequencyAPI();
6+
7+
console.log("\n## DSNP Schema Information");
8+
9+
const allDsnp = (await api.rpc.schemas.getVersions("dsnp")).unwrap();
10+
for (const schemaEntry of schemas.entries()) {
11+
const schemaString = JSON.stringify(schemaEntry[1].model);
12+
const schemaVersionResult = allDsnp.filter(
13+
(versioned) => versioned.schema_name.toString() === "dsnp." + schemaEntry[0],
14+
);
15+
for (const version of schemaVersionResult) {
16+
const schemaResult = (await api.rpc.schemas.getBySchemaId(version.schema_id.toString())).unwrap();
17+
const jsonSchema = Buffer.from(schemaResult.model).toString("utf8");
18+
const { schema_id, model_type, payload_location, settings } = schemaResult;
19+
20+
// Ensure that full entry details match, otherwise it's a different version
21+
if (
22+
schemaString === jsonSchema &&
23+
model_type.toHuman() === schemaEntry[1].modelType &&
24+
payload_location.toHuman() === schemaEntry[1].payloadLocation &&
25+
JSON.stringify(settings.toHuman()) === JSON.stringify(schemaEntry[1].settings)
26+
) {
27+
console.log(`\n## Schema Id ${schema_id}`);
28+
console.table(
29+
Object.entries({
30+
schemaName: schemaEntry[0],
31+
dsnpVersion: schemaEntry[1].dsnpVersion,
32+
schemaId: schema_id.toHuman(),
33+
}).map(([key, value]) => ({ key, value })),
34+
);
35+
}
36+
}
37+
}
38+
};
39+
40+
export const main = async () => {
41+
await find();
42+
};
43+
44+
main().catch(console.error).finally(process.exit);

0 commit comments

Comments
 (0)