Skip to content

Commit

Permalink
[stateful storage] add a new extrinsic for PaginatedUpsertSignaturePa… (
Browse files Browse the repository at this point in the history
#1651)

# Goal
The goal of this PR is to allow generating signatures for paginated
storages without having `msa_id` in the payload.

Closes #1642 

# Discussion
- for backwards compatibility reasons instead of just removing the
msa_id from the payload I created a v2 for the payload and extrinsic
- benchmarks and stables weights are not changed at all so it's using
the old extrinsic one

# Checklist
- [x] Chain spec updated
- [x] Unit and Integration Tests added
  • Loading branch information
aramikm authored and shannonwells committed Apr 16, 2024
1 parent 31d9131 commit 0af9649
Show file tree
Hide file tree
Showing 12 changed files with 1,105 additions and 13 deletions.
51 changes: 49 additions & 2 deletions integration-tests/capacity/transactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ import {
getOrCreateParquetBroadcastSchema,
getOrCreateAvroChatMessagePaginatedSchema,
CHAIN_ENVIRONMENT,
generateItemizedSignaturePayloadV2
generateItemizedSignaturePayloadV2,
generatePaginatedUpsertSignaturePayloadV2,
generatePaginatedDeleteSignaturePayloadV2
} from "../scaffolding/helpers";

describe("Capacity Transactions", function () {
Expand Down Expand Up @@ -428,7 +430,52 @@ describe("Capacity Transactions", function () {
pageId: page_id,
});
const deletePayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStoragePaginatedDeleteSignaturePayload", deletePayload);
let remove_result = ExtrinsicHelper.removePageWithSignature(delegatorKeys, capacityKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload);
let remove_result = ExtrinsicHelper.deletePageWithSignature(delegatorKeys, capacityKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload);
const [pageRemove, chainEvents2] = await remove_result.payWithCapacity();
assertEvent(chainEvents2, "system.ExtrinsicSuccess");
assertEvent(chainEvents2, "capacity.CapacityWithdrawn");
assert.notEqual(pageRemove, undefined, "should have returned a event");

// no pages should exist
const result = await ExtrinsicHelper.getPaginatedStorage(delegatorProviderId, paginatedSchemaId);
assert.notEqual(result, undefined, "should have returned a valid response");
assert.equal(result.length, 0, "should returned no paginated pages");
});

it("successfully pays with Capacity for eligible transaction - upsertPageWithSignatureV2; deletePageWithSignatureV2", async function () {
let paginatedSchemaId: SchemaId = await getOrCreateAvroChatMessagePaginatedSchema();

// Create a MSA for the delegator
[delegatorKeys, delegatorProviderId] = await createDelegator();
assert.notEqual(delegatorKeys, undefined, "setup should populate delegator_key");
assert.notEqual(delegatorProviderId, undefined, "setup should populate msa_id");

let page_id = new u16(ExtrinsicHelper.api.registry, 1);

// Add and update actions
let target_hash = await getCurrentPaginatedHash(delegatorProviderId, paginatedSchemaId, page_id.toNumber());
const upsertPayload = await generatePaginatedUpsertSignaturePayloadV2({
targetHash: target_hash,
schemaId: paginatedSchemaId,
pageId: page_id,
payload: new Bytes(ExtrinsicHelper.api.registry, "Hello World From Frequency"),
});
const upsertPayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStoragePaginatedUpsertSignaturePayloadV2", upsertPayload);
let upsert_result = ExtrinsicHelper.upsertPageWithSignatureV2(delegatorKeys, capacityKeys, signPayloadSr25519(delegatorKeys, upsertPayloadData), upsertPayload);
const [pageUpdateEvent, chainEvents1] = await upsert_result.payWithCapacity();
assertEvent(chainEvents1, "system.ExtrinsicSuccess");
assertEvent(chainEvents1, "capacity.CapacityWithdrawn");
assert.notEqual(pageUpdateEvent, undefined, "should have returned a PalletStatefulStoragePaginatedPageUpdate event");

// Remove the page
target_hash = await getCurrentPaginatedHash(delegatorProviderId, paginatedSchemaId, page_id.toNumber());
const deletePayload = await generatePaginatedDeleteSignaturePayloadV2({
targetHash: target_hash,
schemaId: paginatedSchemaId,
pageId: page_id,
});
const deletePayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStoragePaginatedDeleteSignaturePayloadV2", deletePayload);
let remove_result = ExtrinsicHelper.deletePageWithSignatureV2(delegatorKeys, capacityKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload);
const [pageRemove, chainEvents2] = await remove_result.payWithCapacity();
assertEvent(chainEvents2, "system.ExtrinsicSuccess");
assertEvent(chainEvents2, "capacity.CapacityWithdrawn");
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions integration-tests/scaffolding/extrinsicHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export type AddProviderPayload = { authorizedMsaId?: u64; schemaIds?: u16[], exp
export type ItemizedSignaturePayload = { msaId?: u64; schemaId?: u16, targetHash?: u32, expiration?: any; actions?: any; }
export type ItemizedSignaturePayloadV2 = { schemaId?: u16, targetHash?: u32, expiration?: any; actions?: any; }
export type PaginatedUpsertSignaturePayload = { msaId?: u64; schemaId?: u16, pageId?: u16, targetHash?: u32, expiration?: any; payload?: any; }
export type PaginatedUpsertSignaturePayloadV2 = { schemaId?: u16, pageId?: u16, targetHash?: u32, expiration?: any; payload?: any; }
export type PaginatedDeleteSignaturePayload = { msaId?: u64; schemaId?: u16, pageId?: u16, targetHash?: u32, expiration?: any; }
export type PaginatedDeleteSignaturePayloadV2 = { schemaId?: u16, pageId?: u16, targetHash?: u32, expiration?: any; }

export class EventError extends Error {
name: string = '';
Expand Down Expand Up @@ -315,18 +317,26 @@ export class ExtrinsicHelper {
return new Extrinsic(() => ExtrinsicHelper.api.tx.statefulStorage.applyItemActionsWithSignature(delegatorKeys.publicKey, signature, payload), providerKeys, ExtrinsicHelper.api.events.statefulStorage.ItemizedPageUpdated);
}

public static applyItemActionsWithSignatureV2(delegatorKeys: KeyringPair, providerKeys: KeyringPair, signature: Sr25519Signature, payload: ItemizedSignaturePayloadV2): Extrinsic {
return new Extrinsic(() => ExtrinsicHelper.api.tx.statefulStorage.applyItemActionsWithSignatureV2(delegatorKeys.publicKey, signature, payload), providerKeys, ExtrinsicHelper.api.events.statefulStorage.ItemizedPageUpdated);
}
public static applyItemActionsWithSignatureV2(delegatorKeys: KeyringPair, providerKeys: KeyringPair, signature: Sr25519Signature, payload: ItemizedSignaturePayloadV2): Extrinsic {
return new Extrinsic(() => ExtrinsicHelper.api.tx.statefulStorage.applyItemActionsWithSignatureV2(delegatorKeys.publicKey, signature, payload), providerKeys, ExtrinsicHelper.api.events.statefulStorage.ItemizedPageUpdated);
}

public static removePageWithSignature(delegatorKeys: KeyringPair, providerKeys: KeyringPair, signature: Sr25519Signature, payload: PaginatedDeleteSignaturePayload): Extrinsic {
public static deletePageWithSignature(delegatorKeys: KeyringPair, providerKeys: KeyringPair, signature: Sr25519Signature, payload: PaginatedDeleteSignaturePayload): Extrinsic {
return new Extrinsic(() => ExtrinsicHelper.api.tx.statefulStorage.deletePageWithSignature(delegatorKeys.publicKey, signature, payload), providerKeys, ExtrinsicHelper.api.events.statefulStorage.PaginatedPageDeleted);
}

public static deletePageWithSignatureV2(delegatorKeys: KeyringPair, providerKeys: KeyringPair, signature: Sr25519Signature, payload: PaginatedDeleteSignaturePayloadV2): Extrinsic {
return new Extrinsic(() => ExtrinsicHelper.api.tx.statefulStorage.deletePageWithSignatureV2(delegatorKeys.publicKey, signature, payload), providerKeys, ExtrinsicHelper.api.events.statefulStorage.PaginatedPageDeleted);
}

public static upsertPageWithSignature(delegatorKeys: KeyringPair, providerKeys: KeyringPair, signature: Sr25519Signature, payload: PaginatedUpsertSignaturePayload): Extrinsic {
return new Extrinsic(() => ExtrinsicHelper.api.tx.statefulStorage.upsertPageWithSignature(delegatorKeys.publicKey, signature, payload), providerKeys, ExtrinsicHelper.api.events.statefulStorage.PaginatedPageUpdated);
}

public static upsertPageWithSignatureV2(delegatorKeys: KeyringPair, providerKeys: KeyringPair, signature: Sr25519Signature, payload: PaginatedUpsertSignaturePayloadV2): Extrinsic {
return new Extrinsic(() => ExtrinsicHelper.api.tx.statefulStorage.upsertPageWithSignatureV2(delegatorKeys.publicKey, signature, payload), providerKeys, ExtrinsicHelper.api.events.statefulStorage.PaginatedPageUpdated);
}

public static getItemizedStorage(msa_id: MessageSourceId, schemaId: any): Promise<ItemizedStoragePageResponse> {
return firstValueFrom(ExtrinsicHelper.api.rpc.statefulStorage.getItemizedStorage(msa_id, schemaId));
}
Expand Down
26 changes: 25 additions & 1 deletion integration-tests/scaffolding/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
EventMap,
ExtrinsicHelper,
ItemizedSignaturePayload, ItemizedSignaturePayloadV2, PaginatedDeleteSignaturePayload,
PaginatedUpsertSignaturePayload
PaginatedDeleteSignaturePayloadV2, PaginatedUpsertSignaturePayload, PaginatedUpsertSignaturePayloadV2
} from "./extrinsicHelpers";
import { EXISTENTIAL_DEPOSIT } from "./rootHooks";
import {HandleResponse, MessageSourceId, PageHash, SchemaGrantResponse} from "@frequency-chain/api-augment/interfaces";
Expand Down Expand Up @@ -109,6 +109,18 @@ export async function generatePaginatedUpsertSignaturePayload(payloadInputs: Pag
}
}

export async function generatePaginatedUpsertSignaturePayloadV2(payloadInputs: PaginatedUpsertSignaturePayloadV2, expirationOffset: number = 100, blockNumber?: number): Promise<PaginatedUpsertSignaturePayloadV2> {
let { expiration, ...payload } = payloadInputs;
if (!expiration) {
expiration = (blockNumber || (await getBlockNumber())) + expirationOffset;
}

return {
expiration,
...payload,
}
}

export async function generatePaginatedDeleteSignaturePayload(payloadInputs: PaginatedDeleteSignaturePayload, expirationOffset: number = 100, blockNumber?: number): Promise<PaginatedDeleteSignaturePayload> {
let { expiration, ...payload } = payloadInputs;
if (!expiration) {
Expand All @@ -121,6 +133,18 @@ export async function generatePaginatedDeleteSignaturePayload(payloadInputs: Pag
}
}

export async function generatePaginatedDeleteSignaturePayloadV2(payloadInputs: PaginatedDeleteSignaturePayloadV2, expirationOffset: number = 100, blockNumber?: number): Promise<PaginatedDeleteSignaturePayloadV2> {
let { expiration, ...payload } = payloadInputs;
if (!expiration) {
expiration = (blockNumber || (await getBlockNumber())) + expirationOffset;
}

return {
expiration,
...payload,
}
}

export function createKeys(name: string = 'first pair'): KeyringPair {
const mnemonic = mnemonicGenerate();
// create & add the pair to the keyring with the type and some additional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
createProviderKeysAndId,
generateItemizedSignaturePayload,
generateItemizedSignaturePayloadV2,
generatePaginatedDeleteSignaturePayload,
generatePaginatedUpsertSignaturePayload,
generatePaginatedDeleteSignaturePayload, generatePaginatedDeleteSignaturePayloadV2,
generatePaginatedUpsertSignaturePayload, generatePaginatedUpsertSignaturePayloadV2,
getCurrentItemizedHash,
getCurrentPaginatedHash,
signPayloadSr25519
Expand Down Expand Up @@ -151,7 +151,45 @@ describe("📗 Stateful Pallet Storage Signature Required", () => {
pageId: page_id,
});
const deletePayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStoragePaginatedDeleteSignaturePayload", deletePayload);
let remove_result = ExtrinsicHelper.removePageWithSignature(delegatorKeys, providerKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload);
let remove_result = ExtrinsicHelper.deletePageWithSignature(delegatorKeys, providerKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload);
const [pageRemove, chainEvents2] = await remove_result.fundAndSend();
assert.notEqual(chainEvents2["system.ExtrinsicSuccess"], undefined, "should have returned an ExtrinsicSuccess event");
assert.notEqual(chainEvents2["transactionPayment.TransactionFeePaid"], undefined, "should have returned a TransactionFeePaid event");
assert.notEqual(pageRemove, undefined, "should have returned a event");

// no pages should exist
const result = await ExtrinsicHelper.getPaginatedStorage(msa_id, paginatedSchemaId);
assert.notEqual(result, undefined, "should have returned a valid response");
assert.equal(result.length, 0, "should returned no paginated pages");
}).timeout(10000);

it("should be able to call upsertPageWithSignatureV2 a page and deletePageWithSignatureV2 it successfully", async function () {
let page_id = new u16(ExtrinsicHelper.api.registry, 1);

// Add and update actions
let target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber());
const upsertPayload = await generatePaginatedUpsertSignaturePayloadV2({
targetHash: target_hash,
schemaId: paginatedSchemaId,
pageId: page_id,
payload: new Bytes(ExtrinsicHelper.api.registry, "Hello World From Frequency"),
});
const upsertPayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStoragePaginatedUpsertSignaturePayloadV2", upsertPayload);
let upsert_result = ExtrinsicHelper.upsertPageWithSignatureV2(delegatorKeys, providerKeys, signPayloadSr25519(delegatorKeys, upsertPayloadData), upsertPayload);
const [pageUpdateEvent, chainEvents1] = await upsert_result.fundAndSend();
assert.notEqual(chainEvents1["system.ExtrinsicSuccess"], undefined, "should have returned an ExtrinsicSuccess event");
assert.notEqual(chainEvents1["transactionPayment.TransactionFeePaid"], undefined, "should have returned a TransactionFeePaid event");
assert.notEqual(pageUpdateEvent, undefined, "should have returned a PalletStatefulStoragePaginatedPageUpdate event");

// Remove the page
target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber());
const deletePayload = await generatePaginatedDeleteSignaturePayloadV2({
targetHash: target_hash,
schemaId: paginatedSchemaId,
pageId: page_id,
});
const deletePayloadData = ExtrinsicHelper.api.registry.createType("PalletStatefulStoragePaginatedDeleteSignaturePayloadV2", deletePayload);
let remove_result = ExtrinsicHelper.deletePageWithSignatureV2(delegatorKeys, providerKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload);
const [pageRemove, chainEvents2] = await remove_result.fundAndSend();
assert.notEqual(chainEvents2["system.ExtrinsicSuccess"], undefined, "should have returned an ExtrinsicSuccess event");
assert.notEqual(chainEvents2["transactionPayment.TransactionFeePaid"], undefined, "should have returned a TransactionFeePaid event");
Expand Down
87 changes: 87 additions & 0 deletions pallets/stateful-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ pub mod pallet {
///
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::upsert_page_with_signature(payload.payload.len() as u32))]
#[allow(deprecated)]
#[deprecated(note = "please use `upsert_page_with_signature_v2` instead")]
pub fn upsert_page_with_signature(
origin: OriginFor<T>,
delegator_key: T::AccountId,
Expand Down Expand Up @@ -421,6 +423,8 @@ pub mod pallet {
///
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::delete_page_with_signature())]
#[allow(deprecated)]
#[deprecated(note = "please use `delete_page_with_signature_v2` instead")]
pub fn delete_page_with_signature(
origin: OriginFor<T>,
delegator_key: T::AccountId,
Expand Down Expand Up @@ -495,6 +499,89 @@ pub mod pallet {
)?;
Ok(())
}

/// Creates or updates an Paginated storage with new payload that requires signature
/// since the signature of delegator is checked there is no need for delegation validation
///
/// # Events
/// * [`Event::PaginatedPageUpdated`]
///
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::upsert_page_with_signature(payload.payload.len() as u32))]
pub fn upsert_page_with_signature_v2(
origin: OriginFor<T>,
delegator_key: T::AccountId,
proof: MultiSignature,
payload: PaginatedUpsertSignaturePayloadV2<T>,
) -> DispatchResult {
ensure_signed(origin)?;
ensure!(
payload.page_id <= T::MaxPaginatedPageId::get(),
Error::<T>::PageIdExceedsMaxAllowed
);
Self::check_payload_expiration(
frame_system::Pallet::<T>::block_number(),
payload.expiration,
)?;
Self::check_signature(&proof, &delegator_key.clone(), payload.encode())?;
let state_owner_msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&delegator_key)
.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
Self::check_schema_for_write(
payload.schema_id,
PayloadLocation::Paginated,
true,
false,
)?;
Self::update_paginated(
state_owner_msa_id,
payload.schema_id,
payload.page_id,
payload.target_hash,
PaginatedPage::<T>::from(payload.payload),
)?;
Ok(())
}

/// Deletes a Paginated storage that requires signature
/// since the signature of delegator is checked there is no need for delegation validation
///
/// # Events
/// * [`Event::PaginatedPageDeleted`]
///
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::delete_page_with_signature())]
pub fn delete_page_with_signature_v2(
origin: OriginFor<T>,
delegator_key: T::AccountId,
proof: MultiSignature,
payload: PaginatedDeleteSignaturePayloadV2<T>,
) -> DispatchResult {
ensure_signed(origin)?;
ensure!(
payload.page_id <= T::MaxPaginatedPageId::get(),
Error::<T>::PageIdExceedsMaxAllowed
);
Self::check_payload_expiration(
frame_system::Pallet::<T>::block_number(),
payload.expiration,
)?;
Self::check_signature(&proof, &delegator_key.clone(), payload.encode())?;
let state_owner_msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&delegator_key)
.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
Self::check_schema_for_write(
payload.schema_id,
PayloadLocation::Paginated,
true,
true,
)?;
Self::delete_paginated(
state_owner_msa_id,
payload.schema_id,
payload.page_id,
payload.target_hash,
)?;
Ok(())
}
}
}

Expand Down
Loading

0 comments on commit 0af9649

Please sign in to comment.