Skip to content

Commit

Permalink
Support Frequency schema names (#46)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
wesbiggs and Wes Biggs authored Jan 9, 2024
1 parent bff0997 commit 0bcd42f
Show file tree
Hide file tree
Showing 6 changed files with 1,146 additions and 1,272 deletions.
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,43 @@ npm install @dsnp/frequency-schemas
### Use Schema

```typescript
import { dsnp } from "frequency-schemas";
import { dsnp } from "@dsnp/frequency-schemas";

console.log(dsnp.getSchema("broadcast"));
```

### Get Schema Id from Chain

```typescript
import { dsnp } from "@dsnp/frequency-schemas";
import { ApiPromise } from "@polkadot/api";

const api = ApiPromise.create(/* ... */);
console.log(await dsnp.getSchemaId(api, "broadcast"));
```

Frequency mainnet and testnet have well-known Ids defined in `dsnp/index.ts`.
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:

```
dsnp.setSchemaMapping(api.genesisHash.toString(), {
// format is dsnpName: { version: schemaId, ... }
"tombstone": { "1.2": 64 },
"broadcast": { "1.2": 67 },
// ...
});
console.log(await dsnp.getSchemaId(api, "broadcast")); // yields 67
```

### With Parquet Writer

```sh
npm install @dsnp/parquetjs
```

```typescript
import { parquet } from "frequency-schemas";
import { parquet } from "@dsnp/frequency-schemas";
import { ParquetWriter } from "@dsnp/parquetjs";

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

## Find Frequency Schema Ids that Match DSNP Schema Versions

This script will look up and verify schemas in the schema registry that match the DSNP names and versions defined in `dsnp/index.ts`.

```sh
DEPLOY_SCHEMA_ENDPOINT_URL="ws://127.0.0.1:9944" npm run find
```

## Use with Docker

This repo deploys `dsnp/instant-seal-node-with-deployed-schemas` to Docker Hub.
Expand Down
50 changes: 31 additions & 19 deletions cli/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFrequencyAPI, getSignerAccountKeys } from "./services/connect.js";
import dsnp, { SchemaName as DsnpSchemaName } from "../dsnp/index.js";
import dsnp, { SchemaName as DsnpSchemaName, SchemaMapping, GENESIS_HASH_MAINNET } from "../dsnp/index.js";
import { EventRecord } from "@polkadot/types/interfaces";

export const deploy = async () => {
Expand Down Expand Up @@ -42,7 +42,8 @@ export const deploy = async () => {

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

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

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

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

const promises: Promise<SchemaInfo>[] = [];
const api = await getFrequencyAPI();
const signerAccountKeys = getSignerAccountKeys();
// Mainnet genesis hash means we should propose instead of create
const shouldPropose =
api.genesisHash.toHex() === "0x4a587bf17a404e3572747add7aab7bbe56e805a5479c6c436f07f36fcc8d3ae1";
const shouldPropose = api.genesisHash.toHex() === GENESIS_HASH_MAINNET;

if (shouldPropose && schemaNames.length > 1) {
console.error("Proposing to create schemas can only occur one at a time. Please try again with only one schema.");
Expand Down Expand Up @@ -92,55 +89,70 @@ const createSchemas = async (schemaNames: string[]) => {
// Propose to create
const promise = new Promise<SchemaInfo>((resolve, reject) => {
api.tx.schemas
.proposeToCreateSchema(
.proposeToCreateSchemaV2(
json_no_ws,
schemaDeploy.modelType,
schemaDeploy.payloadLocation,
schemaDeploy.settings,
"dsnp." + schemaName,
)
.signAndSend(signerAccountKeys, { nonce }, ({ status, events, dispatchError }) => {
if (dispatchError) {
console.error("ERROR: ", dispatchError.toHuman());
console.log("Might already have a proposal with the same hash?");
reject();
reject(dispatchError.toHuman());
} else if (status.isInBlock || status.isFinalized) {
const evt = eventWithSectionAndMethod(events, "council", "Proposed");
if (evt) {
const id = evt?.data[1];
const hash = evt?.data[2].toHex();
console.log("SUCCESS: " + schemaName + " schema proposed with id of " + id + " and hash of " + hash);
resolve({ schemaName, id: Number(id.toHuman()) });
} else resolve({ schemaName });
const v2n = Object.fromEntries([[schemaDeploy.dsnpVersion, Number(id.toHuman())]]);
resolve([schemaName as DsnpSchemaName, v2n]);
} else {
const err = "Proposed event not found";
console.error(`ERROR: ${err}`);
reject(err);
}
}
});
});
promises[idx] = promise;
} else {
// Create directly via sudo
const tx = api.tx.schemas.createSchemaViaGovernance(
const tx = api.tx.schemas.createSchemaViaGovernanceV2(
signerAccountKeys.address,
json_no_ws,
schemaDeploy.modelType,
schemaDeploy.payloadLocation,
schemaDeploy.settings,
"dsnp." + schemaName,
);
const promise = new Promise<SchemaInfo>((resolve, reject) => {
api.tx.sudo.sudo(tx).signAndSend(signerAccountKeys, { nonce }, ({ status, events, dispatchError }) => {
if (dispatchError) {
console.error("ERROR: ", dispatchError.toHuman());
reject();
reject(dispatchError.toHuman());
} else if (status.isInBlock || status.isFinalized) {
const evt = eventWithSectionAndMethod(events, "schemas", "SchemaCreated");
if (evt) {
const val = evt?.data[1];
console.log("SUCCESS: " + schemaName + " schema created with id of " + val);
resolve({ schemaName, id: Number(val.toHuman()) });
} else resolve({ schemaName });
const id = evt?.data[1];
console.log("SUCCESS: " + schemaName + " schema created with id of " + id);
const v2n = Object.fromEntries([[schemaDeploy.dsnpVersion, Number(id.toHuman())]]);
resolve([schemaName as DsnpSchemaName, v2n]);
} else {
const err = "SchemaCreated event not found";
console.error(`ERROR: ${err}`);
reject(err);
}
}
});
});
promises[idx] = promise;
}
}
return Promise.all(promises);
const output = await Promise.all(promises);
const mapping: { [genesisHash: string]: SchemaMapping } = {};
mapping[api.genesisHash.toString()] = Object.fromEntries(output);
return mapping;
};
44 changes: 44 additions & 0 deletions cli/find.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getFrequencyAPI } from "./services/connect.js";
import { schemas } from "../dsnp/index.js";

const find = async () => {
const api = await getFrequencyAPI();

console.log("\n## DSNP Schema Information");

const allDsnp = (await api.rpc.schemas.getVersions("dsnp")).unwrap();
for (const schemaEntry of schemas.entries()) {
const schemaString = JSON.stringify(schemaEntry[1].model);
const schemaVersionResult = allDsnp.filter(
(versioned) => versioned.schema_name.toString() === "dsnp." + schemaEntry[0],
);
for (const version of schemaVersionResult) {
const schemaResult = (await api.rpc.schemas.getBySchemaId(version.schema_id.toString())).unwrap();
const jsonSchema = Buffer.from(schemaResult.model).toString("utf8");
const { schema_id, model_type, payload_location, settings } = schemaResult;

// Ensure that full entry details match, otherwise it's a different version
if (
schemaString === jsonSchema &&
model_type.toHuman() === schemaEntry[1].modelType &&
payload_location.toHuman() === schemaEntry[1].payloadLocation &&
JSON.stringify(settings.toHuman()) === JSON.stringify(schemaEntry[1].settings)
) {
console.log(`\n## Schema Id ${schema_id}`);
console.table(
Object.entries({
schemaName: schemaEntry[0],
dsnpVersion: schemaEntry[1].dsnpVersion,
schemaId: schema_id.toHuman(),
}).map(([key, value]) => ({ key, value })),
);
}
}
}
};

export const main = async () => {
await find();
};

main().catch(console.error).finally(process.exit);
Loading

0 comments on commit 0bcd42f

Please sign in to comment.