Repository: paritytech/polkadot-sdk
Pull request #8104: Stabilize V2 archive RPC methods
Author: login: jsdw
Labels: name: T3-RPC_API
description: This PR/Issue is related to RPC APIs.
name: R1-breaking_change
description: This PR introduces a breaking change and should be highlighted in the upcoming release.
Created: 2025-03-31T10:54:37Z | Merged: 2025-03-31T14:56:34Z

# Description

This PR Stabilizes the V2 archive RPCs, and should merge following the [correpsonding PR](https://github.com/paritytech/json-rpc-interface-spec/pull/167) in the spec repository merging. 

## Integration

Downstream projects can now rely on the `archive_*` RPC methods on being stable. Anybody using the prior `archive_unstable_*` RPCs in recent times should be able to upgrade by simply renaming `archive_unstable` to `archive_v1` for any RPC calls, given that the actual interface hasn't changed since December 2024. 

<!--

* [ ] My PR follows the [labeling requirements](
https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process
) of this project (at minimum one label for `T` required)
    * External contributors: ask maintainers to put the right label on your PR.
* [ ] I have made corresponding changes to the documentation (if applicable)
* [ ] I have added tests that prove my fix is effective or that my feature works (if applicable)

You can remove the "Checklist" section once all have been checked. Thank you for your contribution!

-->



Diff:
diff --git a/prdoc/pr_8104.prdoc b/prdoc/pr_8104.prdoc
new file mode 100644
index 0000000000000..083bebae0b423
--- /dev/null
+++ b/prdoc/pr_8104.prdoc
@@ -0,0 +1,14 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: Stabilize RPC `archive` methods to V1
+
+doc:
+  - audience: Node Dev
+    description: |
+      This PR renames the V2 `archive_unstable_*` RPC calls to be `archive_v1_*`, signalling 
+      that they have been stabilized.
+
+crates:
+  - name: sc-rpc-spec-v2
+    bump: major
diff --git a/substrate/client/rpc-spec-v2/src/archive/api.rs b/substrate/client/rpc-spec-v2/src/archive/api.rs
index a205d0502c936..4ce0a9a4f5d64 100644
--- a/substrate/client/rpc-spec-v2/src/archive/api.rs
+++ b/substrate/client/rpc-spec-v2/src/archive/api.rs
@@ -36,8 +36,8 @@ pub trait ArchiveApi<Hash> {
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_body")]
-	fn archive_unstable_body(&self, hash: Hash) -> RpcResult<Option<Vec<String>>>;
+	#[method(name = "archive_v1_body")]
+	fn archive_v1_body(&self, hash: Hash) -> RpcResult<Option<Vec<String>>>;
 
 	/// Get the chain's genesis hash.
 	///
@@ -46,8 +46,8 @@ pub trait ArchiveApi<Hash> {
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_genesisHash")]
-	fn archive_unstable_genesis_hash(&self) -> RpcResult<String>;
+	#[method(name = "archive_v1_genesisHash")]
+	fn archive_v1_genesis_hash(&self) -> RpcResult<String>;
 
 	/// Get the block's header.
 	///
@@ -57,8 +57,8 @@ pub trait ArchiveApi<Hash> {
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_header")]
-	fn archive_unstable_header(&self, hash: Hash) -> RpcResult<Option<String>>;
+	#[method(name = "archive_v1_header")]
+	fn archive_v1_header(&self, hash: Hash) -> RpcResult<Option<String>>;
 
 	/// Get the height of the current finalized block.
 	///
@@ -67,8 +67,8 @@ pub trait ArchiveApi<Hash> {
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_finalizedHeight")]
-	fn archive_unstable_finalized_height(&self) -> RpcResult<u64>;
+	#[method(name = "archive_v1_finalizedHeight")]
+	fn archive_v1_finalized_height(&self) -> RpcResult<u64>;
 
 	/// Get the hashes of blocks from the given height.
 	///
@@ -78,16 +78,16 @@ pub trait ArchiveApi<Hash> {
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_hashByHeight")]
-	fn archive_unstable_hash_by_height(&self, height: u64) -> RpcResult<Vec<String>>;
+	#[method(name = "archive_v1_hashByHeight")]
+	fn archive_v1_hash_by_height(&self, height: u64) -> RpcResult<Vec<String>>;
 
 	/// Call into the Runtime API at a specified block's state.
 	///
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_call")]
-	fn archive_unstable_call(
+	#[method(name = "archive_v1_call")]
+	fn archive_v1_call(
 		&self,
 		hash: Hash,
 		function: String,
@@ -100,11 +100,11 @@ pub trait ArchiveApi<Hash> {
 	///
 	/// This method is unstable and subject to change in the future.
 	#[subscription(
-		name = "archive_unstable_storage" => "archive_unstable_storageEvent",
-		unsubscribe = "archive_unstable_stopStorage",
+		name = "archive_v1_storage" => "archive_v1_storageEvent",
+		unsubscribe = "archive_v1_stopStorage",
 		item = ArchiveStorageEvent,
 	)]
-	fn archive_unstable_storage(
+	fn archive_v1_storage(
 		&self,
 		hash: Hash,
 		items: Vec<StorageQuery<String>>,
@@ -117,11 +117,11 @@ pub trait ArchiveApi<Hash> {
 	///
 	/// This method is unstable and can change in minor or patch releases.
 	#[subscription(
-		name = "archive_unstable_storageDiff" => "archive_unstable_storageDiffEvent",
-		unsubscribe = "archive_unstable_storageDiff_stopStorageDiff",
+		name = "archive_v1_storageDiff" => "archive_v1_storageDiffEvent",
+		unsubscribe = "archive_v1_storageDiff_stopStorageDiff",
 		item = ArchiveStorageDiffEvent,
 	)]
-	fn archive_unstable_storage_diff(
+	fn archive_v1_storage_diff(
 		&self,
 		hash: Hash,
 		items: Vec<ArchiveStorageDiffItem<String>>,
diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs
index 62e44a0162419..ca89dbe3a7c77 100644
--- a/substrate/client/rpc-spec-v2/src/archive/archive.rs
+++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs
@@ -117,7 +117,7 @@ where
 		+ StorageProvider<Block, BE>
 		+ 'static,
 {
-	fn archive_unstable_body(&self, hash: Block::Hash) -> RpcResult<Option<Vec<String>>> {
+	fn archive_v1_body(&self, hash: Block::Hash) -> RpcResult<Option<Vec<String>>> {
 		let Ok(Some(signed_block)) = self.client.block(hash) else { return Ok(None) };
 
 		let extrinsics = signed_block
@@ -130,21 +130,21 @@ where
 		Ok(Some(extrinsics))
 	}
 
-	fn archive_unstable_genesis_hash(&self) -> RpcResult<String> {
+	fn archive_v1_genesis_hash(&self) -> RpcResult<String> {
 		Ok(self.genesis_hash.clone())
 	}
 
-	fn archive_unstable_header(&self, hash: Block::Hash) -> RpcResult<Option<String>> {
+	fn archive_v1_header(&self, hash: Block::Hash) -> RpcResult<Option<String>> {
 		let Ok(Some(header)) = self.client.header(hash) else { return Ok(None) };
 
 		Ok(Some(hex_string(&header.encode())))
 	}
 
-	fn archive_unstable_finalized_height(&self) -> RpcResult<u64> {
+	fn archive_v1_finalized_height(&self) -> RpcResult<u64> {
 		Ok(self.client.info().finalized_number.saturated_into())
 	}
 
-	fn archive_unstable_hash_by_height(&self, height: u64) -> RpcResult<Vec<String>> {
+	fn archive_v1_hash_by_height(&self, height: u64) -> RpcResult<Vec<String>> {
 		let height: NumberFor<Block> = U256::from(height)
 			.try_into()
 			.map_err(|_| ArchiveError::InvalidParam(format!("Invalid block height: {}", height)))?;
@@ -195,7 +195,7 @@ where
 		Ok(result)
 	}
 
-	fn archive_unstable_call(
+	fn archive_v1_call(
 		&self,
 		hash: Block::Hash,
 		function: String,
@@ -214,7 +214,7 @@ where
 		})
 	}
 
-	fn archive_unstable_storage(
+	fn archive_v1_storage(
 		&self,
 		pending: PendingSubscriptionSink,
 		hash: Block::Hash,
@@ -265,7 +265,7 @@ where
 		self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
 	}
 
-	fn archive_unstable_storage_diff(
+	fn archive_v1_storage_diff(
 		&self,
 		pending: PendingSubscriptionSink,
 		hash: Block::Hash,
diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs
index 48cbbaa4934aa..a10cc2ba04a61 100644
--- a/substrate/client/rpc-spec-v2/src/archive/tests.rs
+++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs
@@ -94,8 +94,7 @@ async fn get_next_event<T: serde::de::DeserializeOwned>(sub: &mut RpcSubscriptio
 async fn archive_genesis() {
 	let (_client, api) = setup_api();
 
-	let genesis: String =
-		api.call("archive_unstable_genesisHash", EmptyParams::new()).await.unwrap();
+	let genesis: String = api.call("archive_v1_genesisHash", EmptyParams::new()).await.unwrap();
 	assert_eq!(genesis, hex_string(&CHAIN_GENESIS));
 }
 
@@ -105,7 +104,7 @@ async fn archive_body() {
 
 	// Invalid block hash.
 	let invalid_hash = hex_string(&INVALID_HASH);
-	let res: Option<Vec<String>> = api.call("archive_unstable_body", [invalid_hash]).await.unwrap();
+	let res: Option<Vec<String>> = api.call("archive_v1_body", [invalid_hash]).await.unwrap();
 	assert!(res.is_none());
 
 	// Import a new block with an extrinsic.
@@ -129,7 +128,7 @@ async fn archive_body() {
 
 	let expected_tx = hex_string(&block.extrinsics[0].encode());
 
-	let body: Vec<String> = api.call("archive_unstable_body", [block_hash]).await.unwrap();
+	let body: Vec<String> = api.call("archive_v1_body", [block_hash]).await.unwrap();
 	assert_eq!(vec![expected_tx], body);
 }
 
@@ -139,7 +138,7 @@ async fn archive_header() {
 
 	// Invalid block hash.
 	let invalid_hash = hex_string(&INVALID_HASH);
-	let res: Option<String> = api.call("archive_unstable_header", [invalid_hash]).await.unwrap();
+	let res: Option<String> = api.call("archive_v1_header", [invalid_hash]).await.unwrap();
 	assert!(res.is_none());
 
 	// Import a new block with an extrinsic.
@@ -161,7 +160,7 @@ async fn archive_header() {
 	let block_hash = format!("{:?}", block.header.hash());
 	client.import(BlockOrigin::Own, block.clone()).await.unwrap();
 
-	let header: String = api.call("archive_unstable_header", [block_hash]).await.unwrap();
+	let header: String = api.call("archive_v1_header", [block_hash]).await.unwrap();
 	let bytes = array_bytes::hex2bytes(&header).unwrap();
 	let header: Header = Decode::decode(&mut &bytes[..]).unwrap();
 	assert_eq!(header, block.header);
@@ -173,8 +172,7 @@ async fn archive_finalized_height() {
 
 	let client_height: u32 = client.info().finalized_number.saturated_into();
 
-	let height: u32 =
-		api.call("archive_unstable_finalizedHeight", EmptyParams::new()).await.unwrap();
+	let height: u32 = api.call("archive_v1_finalizedHeight", EmptyParams::new()).await.unwrap();
 
 	assert_eq!(client_height, height);
 }
@@ -184,7 +182,7 @@ async fn archive_hash_by_height() {
 	let (client, api) = setup_api();
 
 	// Genesis height.
-	let hashes: Vec<String> = api.call("archive_unstable_hashByHeight", [0]).await.unwrap();
+	let hashes: Vec<String> = api.call("archive_v1_hashByHeight", [0]).await.unwrap();
 	assert_eq!(hashes, vec![format!("{:?}", client.genesis_hash())]);
 
 	// Block tree:
@@ -260,28 +258,28 @@ async fn archive_hash_by_height() {
 	client.import(BlockOrigin::Own, block_4.clone()).await.unwrap();
 
 	// Check finalized height.
-	let hashes: Vec<String> = api.call("archive_unstable_hashByHeight", [1]).await.unwrap();
+	let hashes: Vec<String> = api.call("archive_v1_hashByHeight", [1]).await.unwrap();
 	assert_eq!(hashes, vec![format!("{:?}", finalized_hash)]);
 
 	// Test nonfinalized heights.
 	// Height N must include block 1.
 	let mut height = block_1.header.number;
-	let hashes: Vec<String> = api.call("archive_unstable_hashByHeight", [height]).await.unwrap();
+	let hashes: Vec<String> = api.call("archive_v1_hashByHeight", [height]).await.unwrap();
 	assert_eq!(hashes, vec![format!("{:?}", block_1_hash)]);
 
 	// Height (N + 1) must include block 2 and 4.
 	height += 1;
-	let hashes: Vec<String> = api.call("archive_unstable_hashByHeight", [height]).await.unwrap();
+	let hashes: Vec<String> = api.call("archive_v1_hashByHeight", [height]).await.unwrap();
 	assert_eq!(hashes, vec![format!("{:?}", block_4_hash), format!("{:?}", block_2_hash)]);
 
 	// Height (N + 2) must include block 3.
 	height += 1;
-	let hashes: Vec<String> = api.call("archive_unstable_hashByHeight", [height]).await.unwrap();
+	let hashes: Vec<String> = api.call("archive_v1_hashByHeight", [height]).await.unwrap();
 	assert_eq!(hashes, vec![format!("{:?}", block_3_hash)]);
 
 	// Height (N + 3) has no blocks.
 	height += 1;
-	let hashes: Vec<String> = api.call("archive_unstable_hashByHeight", [height]).await.unwrap();
+	let hashes: Vec<String> = api.call("archive_v1_hashByHeight", [height]).await.unwrap();
 	assert!(hashes.is_empty());
 }
 
@@ -293,7 +291,7 @@ async fn archive_call() {
 	// Invalid parameter (non-hex).
 	let err = api
 		.call::<_, serde_json::Value>(
-			"archive_unstable_call",
+			"archive_v1_call",
 			[&invalid_hash, "BabeApi_current_epoch", "0x00X"],
 		)
 		.await
@@ -303,7 +301,7 @@ async fn archive_call() {
 	// Pass an invalid parameters that cannot be decode.
 	let err = api
 		.call::<_, serde_json::Value>(
-			"archive_unstable_call",
+			"archive_v1_call",
 			// 0x0 is invalid.
 			[&invalid_hash, "BabeApi_current_epoch", "0x0"],
 		)
@@ -313,7 +311,7 @@ async fn archive_call() {
 
 	// Invalid hash.
 	let result: MethodResult = api
-		.call("archive_unstable_call", [&invalid_hash, "BabeApi_current_epoch", "0x00"])
+		.call("archive_v1_call", [&invalid_hash, "BabeApi_current_epoch", "0x00"])
 		.await
 		.unwrap();
 	assert_matches!(result, MethodResult::Err(_));
@@ -335,7 +333,7 @@ async fn archive_call() {
 	let call_parameters = hex_string(&alice_id.encode());
 	let result: MethodResult = api
 		.call(
-			"archive_unstable_call",
+			"archive_v1_call",
 			[&format!("{:?}", block_1_hash), "AccountNonceApi_account_nonce", &call_parameters],
 		)
 		.await
@@ -368,7 +366,7 @@ async fn archive_storage_hashes_values() {
 	];
 
 	let mut sub = api
-		.subscribe_unbounded("archive_unstable_storage", rpc_params![&block_hash, items.clone()])
+		.subscribe_unbounded("archive_v1_storage", rpc_params![&block_hash, items.clone()])
 		.await
 		.unwrap();
 
@@ -393,7 +391,7 @@ async fn archive_storage_hashes_values() {
 	let expected_value = hex_string(&VALUE);
 
 	let mut sub = api
-		.subscribe_unbounded("archive_unstable_storage", rpc_params![&block_hash, items])
+		.subscribe_unbounded("archive_v1_storage", rpc_params![&block_hash, items])
 		.await
 		.unwrap();
 
@@ -455,10 +453,7 @@ async fn archive_storage_hashes_values_child_trie() {
 		StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues },
 	];
 	let mut sub = api
-		.subscribe_unbounded(
-			"archive_unstable_storage",
-			rpc_params![&genesis_hash, items, &child_info],
-		)
+		.subscribe_unbounded("archive_v1_storage", rpc_params![&genesis_hash, items, &child_info])
 		.await
 		.unwrap();
 
@@ -502,7 +497,7 @@ async fn archive_storage_closest_merkle_value() {
 	) -> HashMap<String, String> {
 		let mut sub = api
 			.subscribe_unbounded(
-				"archive_unstable_storage",
+				"archive_v1_storage",
 				rpc_params![
 					&block_hash,
 					vec![
@@ -664,7 +659,7 @@ async fn archive_storage_iterations() {
 	let invalid_hash = hex_string(&INVALID_HASH);
 	let mut sub = api
 		.subscribe_unbounded(
-			"archive_unstable_storage",
+			"archive_v1_storage",
 			rpc_params![
 				&invalid_hash,
 				vec![StorageQuery {
@@ -684,7 +679,7 @@ async fn archive_storage_iterations() {
 	// Valid call with storage at the key.
 	let mut sub = api
 		.subscribe_unbounded(
-			"archive_unstable_storage",
+			"archive_v1_storage",
 			rpc_params![
 				&block_hash,
 				vec![StorageQuery {
@@ -791,7 +786,7 @@ async fn archive_storage_diff_main_trie() {
 	];
 	let mut sub = api
 		.subscribe_unbounded(
-			"archive_unstable_storageDiff",
+			"archive_v1_storageDiff",
 			rpc_params![&block_hash, items.clone(), &prev_hash],
 		)
 		.await
@@ -894,7 +889,7 @@ async fn archive_storage_diff_no_changes() {
 	}];
 	let mut sub = api
 		.subscribe_unbounded(
-			"archive_unstable_storageDiff",
+			"archive_v1_storageDiff",
 			rpc_params![&block_hash, items.clone(), &prev_hash],
 		)
 		.await
@@ -949,7 +944,7 @@ async fn archive_storage_diff_deleted_changes() {
 
 	let mut sub = api
 		.subscribe_unbounded(
-			"archive_unstable_storageDiff",
+			"archive_v1_storageDiff",
 			rpc_params![&block_hash, items.clone(), &prev_hash],
 		)
 		.await
@@ -979,7 +974,7 @@ async fn archive_storage_diff_invalid_params() {
 	let items: Vec<ArchiveStorageDiffItem<String>> = Vec::new();
 	let err = api
 		.subscribe_unbounded(
-			"archive_unstable_storageDiff",
+			"archive_v1_storageDiff",
 			rpc_params!["123", items.clone(), &invalid_hash],
 		)
 		.await
@@ -992,7 +987,7 @@ async fn archive_storage_diff_invalid_params() {
 	let items: Vec<ArchiveStorageDiffItem<String>> = Vec::new();
 	let mut sub = api
 		.subscribe_unbounded(
-			"archive_unstable_storageDiff",
+			"archive_v1_storageDiff",
 			rpc_params![&invalid_hash, items.clone(), &invalid_hash],
 		)
 		.await
diff --git a/substrate/client/rpc-spec-v2/src/chain_head/api.rs b/substrate/client/rpc-spec-v2/src/chain_head/api.rs
index 128d803521f6b..6b60c393a9326 100644
--- a/substrate/client/rpc-spec-v2/src/chain_head/api.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_head/api.rs
@@ -49,7 +49,7 @@ pub trait ChainHeadApi<Hash> {
 	/// allowing the JSON-RPC client to retrieve more information about a block
 	/// that has been reported.
 	///
-	/// Use `archive_unstable_body` if instead you want to retrieve the body of an arbitrary block.
+	/// Use `archive_v1_body` if instead you want to retrieve the body of an arbitrary block.
 	///
 	/// # Unstable
 	///
@@ -67,7 +67,7 @@ pub trait ChainHeadApi<Hash> {
 	/// allowing the JSON-RPC client to retrieve more information about a block
 	/// that has been reported.
 	///
-	/// Use `archive_unstable_header` if instead you want to retrieve the header of an arbitrary
+	/// Use `archive_v1_header` if instead you want to retrieve the header of an arbitrary
 	/// block.
 	///
 	/// # Unstable


Reviews:
user: login: niklasad1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T13:25:28Z
user: login: lexnv
body: LGTM! Thanks 🙏 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T13:31:23Z
user: login: niklasad1
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T13:44:19Z
user: login: niklasad1
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T13:45:59Z

Review comments:
diff_hunk: @@ -36,8 +36,8 @@ pub trait ArchiveApi<Hash> {
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_body")]
-	fn archive_unstable_body(&self, hash: Hash) -> RpcResult<Option<Vec<String>>>;
+	#[method(name = "archive_v1_body")]
+	fn archive_v1_body(&self, hash: Hash) -> RpcResult<Option<Vec<String>>>;
path: substrate/client/rpc-spec-v2/src/archive/api.rs
user: login: niklasad1
body: We shouldn't return RpcResult from this API IMHO, which means that one can return any JSON-RPC error object.

Really, it should be either a concrete error enum which possible states or just `Option<Vec<String>>` if infallible the same applies to all other rpc impls below.

Ah, we are missing an impl for Option<T> for IntoResponse and probably all these infallible.

However, before stabilization it would be good clean that up
created_at: 2025-03-31T13:25:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,8 +36,8 @@ pub trait ArchiveApi<Hash> {
 	/// # Unstable
 	///
 	/// This method is unstable and subject to change in the future.
-	#[method(name = "archive_unstable_body")]
-	fn archive_unstable_body(&self, hash: Hash) -> RpcResult<Option<Vec<String>>>;
+	#[method(name = "archive_v1_body")]
+	fn archive_v1_body(&self, hash: Hash) -> RpcResult<Option<Vec<String>>>;
path: substrate/client/rpc-spec-v2/src/archive/api.rs
user: login: niklasad1
body: ah damn, it doesn't work when the client stuff is enabled in rpc macro never mind then
created_at: 2025-03-31T13:44:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: paritytech-workflow-stopper[bot]
created_at: 2025-03-31T11:15:01Z
body: 
All GitHub workflows were cancelled due to failure one of the required jobs.
**Failed workflow url**: https://github.com/paritytech/polkadot-sdk/actions/runs/14170178100
**Failed job name**: fmt

author_association: NONE
user: login: Polkadot-Forum
created_at: 2025-04-01T10:21:59Z
body: This pull request has been mentioned on **Polkadot Forum**. There might be relevant details there:

https://forum.polkadot.network/t/stabilizing-the-archive-rpc-v2-methods/12058/2

author_association: NONE