Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8e3e520
Removed deprecated ValidateUnsigned
antkve Jan 22, 2026
aa5574a
Fixed name
antkve Jan 22, 2026
0c7c14b
Removed auth contexts and simplified
antkve Jan 23, 2026
1c022bd
Fixed preimage test to reflect consume-at-dispatch
antkve Jan 23, 2026
8f2e817
Format
antkve Jan 23, 2026
ca6dd6e
Clippy fix
antkve Jan 23, 2026
42d021f
Added preimage test
antkve Jan 26, 2026
e1cd9f5
Moved preimage test to subxt
antkve Feb 4, 2026
5de03b6
Fixed merge
antkve Feb 4, 2026
a785122
Fixed merge again
antkve Feb 4, 2026
6ac2154
Format
antkve Feb 4, 2026
27d797d
Fixed duplicate just
antkve Feb 4, 2026
a6549f4
Fix test + verify preimage data
antkve Feb 4, 2026
6e5751d
Fix compilation
antkve Feb 4, 2026
c5edd6f
Format yet again
antkve Feb 4, 2026
d889e16
Test fix
antkve Feb 4, 2026
8272287
Fixed compilation
antkve Feb 11, 2026
15c767d
fix: feeless_if
franciscoaguirre Feb 13, 2026
df3382f
Merge branch 'main' into ak-validate-unsigned
franciscoaguirre Feb 13, 2026
a328ebd
Fixed integration test
antkve Feb 13, 2026
18970e2
Benchmark fix?
antkve Feb 13, 2026
7a3348d
Use GitHub Actions matrix for integration tests (#238)
x3c41a Feb 13, 2026
45f7951
Format
antkve Feb 16, 2026
1de7bb1
Merge branch 'main' into ak-validate-unsigned-2
antkve Feb 16, 2026
304758d
Merge branch 'main' into ak-validate-unsigned
antkve Mar 24, 2026
12644e5
Cleaned up and removed providecidconfig from tests
antkve Mar 25, 2026
a92f6e1
General tx with papi
antkve Mar 25, 2026
655bc31
Fixed test
antkve Mar 25, 2026
36e437c
Merge branch 'main' into ak-validate-unsigned
bkontur Mar 26, 2026
723db18
Updated general TX script to recommended generalSigner syntax
antkve Mar 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git
[workspace]
resolver = "2"
members = [

"node",
"pallets/common",
"pallets/relayer-set",
Expand Down
20 changes: 11 additions & 9 deletions examples/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assert from 'assert';
import { cidFromBytes } from "./cid_dag_metadata.js";
import { Binary, Enum } from '@polkadot-api/substrate-bindings';
import { CHUNK_SIZE, toHex, toHashingEnum } from './common.js';
import { createGeneralSigner } from './general_tx.js';

// Convert data to Binary for PAPI (handles string, Uint8Array, and array-like types)
function toBinary(data) {
Expand Down Expand Up @@ -138,6 +139,9 @@ export const TX_MODE_IN_POOL = "in-tx-pool";

const DEFAULT_TX_TIMEOUT_MS = 180_000; // 180 seconds or 30 blocks

// Singleton general signer for unsigned transactions (reusable across calls)
const generalSigner = createGeneralSigner();

const TX_MODE_CONFIG = {
[TX_MODE_IN_BLOCK]: {
match: (ev) => ev.type === "txBestBlocksState" && ev.found,
Expand All @@ -159,17 +163,15 @@ export async function waitForTransaction(tx, signer = null, txName, txMode = TX_
throw new Error(`Unhandled txMode: ${txMode}`);
}

// Get the observable - either signed or unsigned
// Get the observable - either signed or unsigned (general)
let observable;
if (signer === null) {
console.log(`⬆️ Submitting unsigned ${txName}`);
// TODO: https://github.com/polkadot-api/polkadot-api/issues/760
// const bareTx = await tx.getBareTx(txOpts);
if (Object.keys(txOpts).length > 0) {
throw new Error(`txOpts not supported for unsigned transactions (getBareTx doesn't accept options). See: https://github.com/polkadot-api/polkadot-api/issues/760`);
}
const bareTx = await tx.getBareTx();
observable = client.submitAndWatch(bareTx);
console.log(`⬆️ Submitting unsigned ${txName} as general transaction`);
// With #[pallet::authorize], unsigned transactions must be submitted as "general"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antkve very good job, here, I am trying to push this for PAPI: polkadot-api/polkadot-api#760 (comment)

// extrinsics (with extension pipeline) rather than "bare" extrinsics (which bypass
// all extensions including AuthorizeCall).
// See: https://github.com/polkadot-api/polkadot-api/issues/760
observable = tx.signSubmitAndWatch(generalSigner, txOpts);
} else {
observable = tx.signSubmitAndWatch(signer, txOpts);
}
Expand Down
65 changes: 65 additions & 0 deletions examples/general_tx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* General transaction signer for #[pallet::authorize] calls.
*
* With #[pallet::authorize] replacing ValidateUnsigned, unsigned transactions must be
* submitted as "general" extrinsics (Preamble::General) rather than "bare" extrinsics
* (Preamble::Bare). General extrinsics include the transaction extension pipeline but
* no signature, allowing AuthorizeCall to process the call's authorization logic.
*
* This uses a custom PolkadotSigner that constructs v5 general transactions using the
* runtime metadata to properly encode extension data, rather than hardcoding defaults.
*
* See: https://github.com/polkadot-api/polkadot-api/issues/760
*/

import {
compact,
decAnyMetadata,
extrinsicFormat,
unifyMetadata,
} from "@polkadot-api/substrate-bindings"
import { mergeUint8 } from "polkadot-api/utils"

const EXTENSION_VERSION = 0;

/**
* Create a PolkadotSigner that produces v5 general transactions (unsigned with extensions).
*
* Usage:
* const signer = createGeneralSigner();
* await tx.signSubmitAndWatch(signer).subscribe(...);
*
* @returns {import("polkadot-api").PolkadotSigner}
*/
export function createGeneralSigner() {
return {
publicKey: new Uint8Array(32),
signBytes() {
throw new Error("Unsupported: generalSigner does not support signBytes")
},
async signTx(callData, signedExtensions, metadata) {
const decMeta = unifyMetadata(decAnyMetadata(metadata))

const extra = decMeta.extrinsic.signedExtensions[EXTENSION_VERSION].map(
({ identifier }) => {
const signedExtension = signedExtensions[identifier]
if (!signedExtension)
throw new Error(`Missing ${identifier} signed extension`)
return signedExtension.value
},
)

const preResult = mergeUint8([
extrinsicFormat.enc({
version: 5,
type: "general",
}),
new Uint8Array([EXTENSION_VERSION]),
...extra,
callData,
])

return mergeUint8([compact.enc(preResult.length), preResult])
},
}
}
33 changes: 29 additions & 4 deletions pallets/transaction-storage/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ pub fn run_to_block<T: Config>(n: frame_system::pallet_prelude::BlockNumberFor<T
mod benchmarks {
use super::*;

fn authorize_for_store<T: Config>(
who: &T::AccountId,
transactions: u32,
bytes: u64,
) -> Result<(), BenchmarkError> {
let origin = T::Authorizer::try_successful_origin()
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
TransactionStorage::<T>::authorize_account(
origin as T::RuntimeOrigin,
who.clone(),
transactions,
bytes,
)
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;
Ok(())
}

#[benchmark]
fn store(l: Linear<{ 1 }, { T::MaxTransactionSize::get() }>) -> Result<(), BenchmarkError> {
let data = vec![0u8; l as usize];
Expand All @@ -145,9 +162,11 @@ mod benchmarks {
)
.unwrap()
.to_bytes();
let caller: T::AccountId = whitelisted_caller();
authorize_for_store::<T>(&caller, 1, l as u64)?;

#[extrinsic_call]
_(RawOrigin::None, data);
_(RawOrigin::Signed(caller), data);

assert!(!BlockTransactions::<T>::get().is_empty());
assert_last_event::<T>(Event::Stored { index: 0, content_hash, cid }.into());
Expand All @@ -158,11 +177,13 @@ mod benchmarks {
fn renew() -> Result<(), BenchmarkError> {
let data = vec![0u8; T::MaxTransactionSize::get() as usize];
let content_hash = sp_io::hashing::blake2_256(&data);
TransactionStorage::<T>::store(RawOrigin::None.into(), data)?;
let caller: T::AccountId = whitelisted_caller();
authorize_for_store::<T>(&caller, 2, T::MaxTransactionSize::get() as u64)?;
TransactionStorage::<T>::store(RawOrigin::Signed(caller.clone()).into(), data)?;
run_to_block::<T>(1u32.into());

#[extrinsic_call]
_(RawOrigin::None, BlockNumberFor::<T>::zero(), 0);
_(RawOrigin::Signed(caller), BlockNumberFor::<T>::zero(), 0);

assert_last_event::<T>(Event::Renewed { index: 0, content_hash }.into());
Ok(())
Expand All @@ -171,9 +192,13 @@ mod benchmarks {
#[benchmark]
fn check_proof() -> Result<(), BenchmarkError> {
run_to_block::<T>(1u32.into());
let caller: T::AccountId = whitelisted_caller();
let tx_count = T::MaxBlockTransactions::get();
let max_size = T::MaxTransactionSize::get() as u64;
authorize_for_store::<T>(&caller, tx_count, max_size.saturating_mul(tx_count as u64))?;
for _ in 0..T::MaxBlockTransactions::get() {
TransactionStorage::<T>::store(
RawOrigin::None.into(),
RawOrigin::Signed(caller.clone()).into(),
vec![0u8; T::MaxTransactionSize::get() as usize],
)?;
}
Expand Down
Loading
Loading