Skip to content

Commit

Permalink
feat: async backing support (#9)
Browse files Browse the repository at this point in the history
* Fix relaychain anchoring issue

* wip

* It compiles

* Bump timeout to 24s

* wip

* Update SDK to 0.35.1

* Add DIP log command for when it will be supported

* Clean up test cases based on new logic

* Last cleanups

* Yarn version
  • Loading branch information
ntn-x2 authored May 3, 2024
1 parent 994437d commit 2ae7774
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 257 deletions.
2 changes: 2 additions & 0 deletions .yarn/versions/29623abf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declined:
- "@kiltprotocol/dip-sdk"
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"bugs": "https://github.com/KILTprotocol/dip-sdk/issues",
"description": "An SDK to help integration of the KILT Decentralized Identity Provider (DIP) protocol using KILT as an Identity Provider.",
"dependencies": {
"@kiltprotocol/did": "0.35.1-rc.2",
"@kiltprotocol/types": "0.35.1-rc.2"
"@kiltprotocol/did": "0.35.1",
"@kiltprotocol/types": "0.35.1"
},
"devDependencies": {
"@kiltprotocol/sdk-js": "0.35.1-rc.2",
"@kiltprotocol/sdk-js": "0.35.1",
"@types/node": "^20.9.4",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
Expand Down
33 changes: 12 additions & 21 deletions src/sibling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,9 @@ import type { Call } from "@polkadot/types/interfaces"
const defaultValues = {
includeWeb3Name: async () => false,
linkedAccounts: async () => [],
providerBlockHeight: async (providerApi: ApiPromise) => {
const providerLastFinalizedBlockHash =
await providerApi.rpc.chain.getFinalizedHead()
return providerApi.rpc.chain
.getHeader(providerLastFinalizedBlockHash)
.then((h) => h.number.toBn())
},
relayBlockHeight: async (relayApi: ApiPromise) => {
return relayApi.derive.chain.bestNumberFinalized().then((n) => n.toBn())
}
}

/** The DIP proof params. */
Expand All @@ -44,8 +40,8 @@ export type DipSiblingBaseProofInput = {
providerApi: ApiPromise
/** The `ApiPromise` instance for the parent relay chain. */
relayApi: ApiPromise
/** The block number of the provider to use for the generation of the DIP proof. If not provided, the latest finalized block number is used. */
providerBlockHeight?: BN
/** The block number of the relay chain to use for the generation of the DIP proof. If not provided, the last finalized block is used. */
relayBlockHeight?: BN
/** Flag indicating whether the generated DIP proof should include the web3name of the DID subject. If not provided, the web3name is not revealed. */
includeWeb3Name?: boolean
/** The list of linked accounts to reveal in the generated DIP proof. If not provided, no account is revealed. */
Expand Down Expand Up @@ -77,31 +73,26 @@ export async function generateDipSiblingBaseProof({
proofVersion,
providerApi,
relayApi,
providerBlockHeight,
relayBlockHeight,
includeWeb3Name,
linkedAccounts,
}: DipSiblingBaseProofInput): Promise<DipSiblingBaseProofRes> {
const actualProviderBlockHeight =
providerBlockHeight ??
(await defaultValues.providerBlockHeight(providerApi))
const actualRelayBlockHeight =
relayBlockHeight ??
(await defaultValues.relayBlockHeight(relayApi))

const providerHeadProof = await generateProviderStateRootProof({
relayApi,
providerApi,
providerBlockHeight: actualProviderBlockHeight,
relayBlockHeight: actualRelayBlockHeight,
proofVersion,
})

// Proof of commitment must be generated with the state root at the block before the last one finalized.
const dipRootProofBlockHash = await providerApi.rpc.chain.getBlockHash(
actualProviderBlockHeight.subn(1),
)
const dipCommitmentProof = await generateDipCommitmentProof({
didUri,
providerApi,
providerBlockHash: dipRootProofBlockHash,
providerBlockHash: providerHeadProof.providerBlockHash,
version: proofVersion,
})

const dipProof = await generateDipIdentityProof({
didUri,
providerApi,
Expand Down
61 changes: 42 additions & 19 deletions src/stateProof/providerStateRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import { BN } from "@polkadot/util"

import type { ApiPromise } from "@polkadot/api"
import type { ReadProof } from "@polkadot/types/interfaces"
import type { ReadProof, Hash } from "@polkadot/types/interfaces"
import type { Option, Bytes } from "@polkadot/types-codec"

/**
* The options object provided when generating a proof for the provider state.
Expand All @@ -21,8 +22,8 @@ export type ProviderStateRootProofOpts = {
providerApi: ApiPromise
/** The `ApiPromise` instance for the relay chain. */
relayApi: ApiPromise
/** The block number on the provider chain to use for the proof. If not provided, the latest finalized block number for the provider is used. */
providerBlockHeight: BN
/** The block number on the relaychain to use for the proof. */
relayBlockHeight: BN
/** The version of the parachain state proof to generate. */
proofVersion: number
}
Expand All @@ -32,12 +33,21 @@ export type ProviderStateRootProofOpts = {
export type ProviderStateRootProofRes = {
/** The raw state proof for the provider state. */
proof: ReadProof
/** The block number of the relaychain which the proof is anchored to. */
relayBlockHeight: BN
/** The hash of the relay block which the proof is anchored to. */
relayBlockHash: Hash
/** The number of the relay block which the proof is anchored to. */
relayBlockHeight: BN,
/** The hash of the parachain block which the proof is calculated from. */
providerBlockHash: Hash
/** The number of the parachain block which the proof is calculated from. */
providerBlockHeight: BN
}
/**
* Generate a proof for the state root of the provider.
*
* Given the relay block height, its `paras::heads` storage is queried to fetch information about the provider parent block.
* Then, the next provider block is fetched and use as the basis of the proof, since its state (root) is finalized in the specified relay block.
*
* The value and type of the proof depends on the version specified.
* For more details about what each `proofVersion` provides, please refer to our docs.
*
Expand All @@ -48,28 +58,41 @@ export type ProviderStateRootProofRes = {
export async function generateProviderStateRootProof({
providerApi,
relayApi,
providerBlockHeight, // `proofVersion` is not used, for now, but it's added to avoid introducing unnecessary breaking changes
relayBlockHeight, // `proofVersion` is not used, for now, but it's added to avoid introducing unnecessary breaking changes
// proofVersion,
}: ProviderStateRootProofOpts): Promise<ProviderStateRootProofRes> {
const providerBlockHash =
await providerApi.rpc.chain.getBlockHash(providerBlockHeight)
const providerApiAtBlock = await providerApi.at(providerBlockHash)
const providerParaId =
await providerApiAtBlock.query.parachainInfo.parachainId()
const relayParentBlockNumber =
await providerApiAtBlock.query.parachainSystem.lastRelayChainBlockNumber()
// This refers to the previously finalized block, we need the current one.
const relayParentBlockHash = await relayApi.rpc.chain.getBlockHash(
relayParentBlockNumber,
)
const providerParaId = await providerApi.query.parachainInfo.parachainId()

const relayBlockHash = await (async () => {
const { block: { header } } = await relayApi.derive.chain.getBlockByNumber(relayBlockHeight)
return header.hash
})()
// This uses the `paras::heads` storage entry to fetch info about the finalized parent header, and then adds 1 to fetch the next provider block, whose state root is included in the fetched `paras::heads` entry.
const providerStoredHeader = await (async () => {
const relayApiAtBlock = await relayApi.at(relayBlockHash)
// Contains (provider_parent, provider_current_extrinsic_root, provider_current_state_root)
const providerHeadData = await relayApiAtBlock.query.paras.heads<Option<Bytes>>(providerParaId)
const providerBlockNumber = await (async () => {
// First 32 bytes of the `HeadData` is the parent block hash on which the current state is built.
const providerParentBlockHash = providerHeadData.unwrap().slice(0, 32)
// Since we need to prove the state of the current block, we add +1 to the retrieved block number of the parent block.
const { block: { header: { number } } } = await providerApi.rpc.chain.getBlock(providerParentBlockHash)
return number.toBn().addn(1)
})()
const { block: { header: providerHeader } } = await providerApi.derive.chain.getBlockByNumber(providerBlockNumber)
return providerHeader
})()

const proof = await relayApi.rpc.state.getReadProof(
[relayApi.query.paras.heads.key(providerParaId)],
relayParentBlockHash,
relayBlockHash,
)

return {
proof,
relayBlockHeight: relayParentBlockNumber.toBn(),
relayBlockHash,
relayBlockHeight,
providerBlockHash: providerStoredHeader.hash,
providerBlockHeight: providerStoredHeader.number.toBn()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ enable_tracing = false
provider = "kubernetes"
# 18000 seconds -> 300 minutes -> 5 hours
timeout = 18000
node_verifier = "None"

# Env variables:
# * RELAY_IMAGE: Docker image for relaychain nodes
Expand Down Expand Up @@ -31,6 +32,7 @@ name = "relay-charlie"
id = 2000

[parachains.collator]
args = ["-ldip=trace"]
command = "node-executable"
name = "provider-alice"
image = "{{PROVIDER_IMAGE}}"
Expand All @@ -40,6 +42,7 @@ rpc_port = "{{PROVIDER_ALICE_RPC}}"
id = 2001

[parachains.collator]
args = ["-ldip=trace"]
command = "node-executable"
name = "consumer-alice"
image = "{{CONSUMER_IMAGE}}"
Expand Down
81 changes: 13 additions & 68 deletions tests/dip-provider-template-dip-consumer-template/develop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ import type {
KiltAddress,
VerificationKeyType,
} from "@kiltprotocol/types"
import type { Option } from "@polkadot/types/codec"
import type { Option, Vec } from "@polkadot/types/codec"
import type { Call } from "@polkadot/types/interfaces"
import type { Codec } from "@polkadot/types/types"
import type { u32 } from '@polkadot/types-codec'

import { signAndSubmitTx, withCrossModuleSystemImport } from "../utils.js"

Expand Down Expand Up @@ -90,7 +91,6 @@ describe("V0", () => {
let did: DidDocument
let web3Name: Web3Name
let didKeypair: Kilt.KeyringPair
let lastTestSetupProviderBlockNumber: BN
let testConfig: typeof v0Config &
Pick<
DipSiblingBaseProofInput,
Expand Down Expand Up @@ -166,11 +166,8 @@ describe("V0", () => {
await Kilt.Blockchain.signAndSubmitTx(batchedTx, newSubmitterKeypair, {
resolveOn: Kilt.Blockchain.IS_FINALIZED,
})
// FIXME: Timeout needed since it seems `.getFinalizedHead()` still returns the previous block number as the latest finalized, even if we wait for finalization above. This results in invalid storage proofs.
// Await another 12s for the next block to be finalized, before starting with the proof generation
await setTimeout(12_000)
lastTestSetupProviderBlockNumber = (
await providerApi.query.system.number()
).toBn()
const newFullDid = (await Kilt.Did.resolve(newFullDidUri))
?.document as DidDocument
submitterKeypair = newSubmitterKeypair
Expand All @@ -195,71 +192,20 @@ describe("V0", () => {
withCrossModuleSystemImport<typeof import("@kiltprotocol/dip-sdk")>(
"..",
async (DipSdk) => {
it("Successful posts on the consumer's PostIt pallet using by default the latest provider finalized block", async () => {
it("Successful posts on the consumer's PostIt pallet using the latest relaychain block stored on the consumer chain", async () => {
const { consumerApi } = testConfig
const postText = "Hello, world!"
const call = consumerApi.tx.postIt.post(postText).method as Call
const lastStoredRelayBlockNumber = await (async () => {
const latestFinalizedConsumerBlock = await consumerApi.rpc.chain.getFinalizedHead()
const consumerApiAtLatestFinalizedBlock = await consumerApi.at(latestFinalizedConsumerBlock)
const latestRelayBlocksStoredOnConsumer = await consumerApiAtLatestFinalizedBlock.query.relayStore.latestBlockHeights<Vec<u32>>()
const lastBlock = latestRelayBlocksStoredOnConsumer.toArray().pop()!
return lastBlock
})()
const config: DipSiblingBaseProofInput & TimeBoundDidSignatureOpts = {
...testConfig,
provider: testConfig,
consumer: { ...testConfig, api: consumerApi, call },
}
const baseDipProof = await DipSdk.generateDipSiblingBaseProof(config)
const crossChainDidSignature =
await DipSdk.dipProof.extensions.timeBoundDidSignature.generateDidSignature(
config,
)

const dipSubmittable = DipSdk.generateDipSubmittableExtrinsic({
additionalProofElements:
DipSdk.dipProof.extensions.timeBoundDidSignature.toChain(
crossChainDidSignature,
),
api: consumerApi,
baseDipProof,
call,
didUri: did.uri,
})

const { status } = await signAndSubmitTx(
consumerApi,
dipSubmittable,
submitterKeypair,
)
expect(
status.isInBlock,
"Status of submitted tx should be in block.",
).toBe(true)
const blockHash = status.asInBlock
const blockNumber = (await consumerApi.rpc.chain.getHeader(blockHash))
.number
// The example PostIt pallet generates the storage key for a post by hashing (block number, submitter's username, content of the post).
const postKey = blake2AsHex(
consumerApi
.createType(
`(${
config.consumer.blockNumberRuntimeType as string
}, ${web3NameRuntimeType}, Bytes)`,
[blockNumber, web3Name, postText],
)
.toHex(),
)
const postEntry =
await consumerApi.query.postIt.posts<Option<Codec>>(postKey)
expect(
postEntry.isSome,
"Post should successfully be stored on the chain",
).toBe(true)
})

it("Successful posts on the consumer's PostIt pallet using the same block as before", async () => {
const { consumerApi } = testConfig
const postText = "Hello, world!"
const call = consumerApi.tx.postIt.post(postText).method as Call
const config: DipSiblingBaseProofInput & TimeBoundDidSignatureOpts = {
...testConfig,
// Set explicit block number for the DIP proof
providerBlockHeight: lastTestSetupProviderBlockNumber,
relayBlockHeight: lastStoredRelayBlockNumber,
provider: testConfig,
consumer: { ...testConfig, api: consumerApi, call },
}
Expand Down Expand Up @@ -297,8 +243,7 @@ describe("V0", () => {
const postKey = blake2AsHex(
consumerApi
.createType(
`(${
config.consumer.blockNumberRuntimeType as string
`(${config.consumer.blockNumberRuntimeType as string
}, ${web3NameRuntimeType}, Bytes)`,
[blockNumber, web3Name, postText],
)
Expand Down
2 changes: 2 additions & 0 deletions tests/peregrine-dip-consumer-template/develop-zombienet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ name = "relay-charlie"
id = 2000

[parachains.collator]
args = ["-ldip=trace"]
command = "node-executable"
name = "peregrine-alice"
image = "{{PROVIDER_IMAGE}}"
Expand All @@ -41,6 +42,7 @@ rpc_port = "{{PROVIDER_ALICE_RPC}}"
id = 2001

[parachains.collator]
args = ["-ldip=trace"]
command = "node-executable"
name = "consumer-alice"
image = "{{CONSUMER_IMAGE}}"
Expand Down
Loading

0 comments on commit 2ae7774

Please sign in to comment.