# [ACP31] Subnet所有権移転の仕組みを理解しよう！

> ACP31は、現在のSubnet（サブネット）の所有者が、新しい所有者へと所有権を移転できるようにする仕組みを導入する提案です。

- URL: https://cardene.dev/articles/8c3bf2fcf450c2e1feb6
- 公開日: 2025-09-12
- 更新日: 2025-09-12
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、Subnetの所有者を新しいアドレスに移転できるようにする仕組みを提案している**ACP31**についてまとめていきます！

:::message
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/31-enable-subnet-ownership-transfer/README.md

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ACP31**は、現在のSubnet（サブネット）の所有者が、新しい所有者へと所有権を移転できるようにする仕組みを導入する提案です。
AvalancheのP-Chain上でSubnetを作成すると、そのSubnetには`Owner`（所有者）が設定されますが、現行仕様では一度設定された所有者を変更することはできません。
**ACP31**によって、所有権の移転を可能にし、Subnet運営の柔軟性を高めることを目的としています。

## 動機

AvalancheのSubnetは、P-Chain上の`CreateSubnetTx`（Subnet作成トランザクション）によって生成されます。
このときに設定される所有者情報は固定され、現状では変更ができません。
そのため、Subnetのライフサイクルにおいて所有者を変更したい場合に対応できないという制約があります。

Subnetの所有者を変更できるようにする必要性は、以下のような状況から生まれます。

* **鍵のローテーション**
    セキュリティ上の観点から、暗号鍵を一定期間ごとに入れ替える（ローテーションする）ことが推奨されています。
    しかし、所有者を固定したままでは古い鍵を使い続ける必要があり、セキュリティリスクが高まります。

* **運営権限の移譲**
    Subnetを運営している組織や個人が交代する場合や、外部に運営を委託する場合に、所有権を新しい主体に移す必要があります。
    現行仕様ではこの移譲ができないため、運営の柔軟性に欠けます。

* **長期的な運用管理**
    プロジェクトやコミュニティベースのSubnetでは、所有者が永続的に同じである保証はありません。
    時間が経つにつれて所有者の変更を求める場面は必ず発生します。
    そのときに対応できる仕組みをあらかじめ用意しておくことが重要です。

これらの理由から、Subnetの所有者を移転可能にする機能は、セキュリティと運営の持続性の両面において必要不可欠であると考えられます。

## 仕様

**ACP31**は、Subnetの現在の所有者が、新しい所有者に所有権を移すための新しいトランザクション型`TransferSubnetOwnershipTx`を定義します。
入力として対象の`Subnet`を受け取り、`CreateSubnetTx`に記録された元の`Owner`と照合して、提出者の`SubnetAuth`（Subnetに対する操作権限を証明する署名等）が正当であることを検証して新しい`Owner`を設定します。
これにより、これまで固定だった所有者情報を安全に更新できるようにします。

### トランザクション形式

トランザクションの形式は以下のような構造体になっています。

```golang
type TransferSubnetOwnershipTx struct {
	// Metadata, inputs and outputs
	BaseTx `serialize:"true"`
	// ID of the subnet this tx is modifying
	Subnet ids.ID `serialize:"true" json:"subnetID"`
	// Proves that the issuer has the right to remove the node from the subnet.
	SubnetAuth verify.Verifiable `serialize:"true" json:"subnetAuthorization"`
	// Who is now authorized to manage this subnet
	Owner fx.Owner `serialize:"true" json:"newOwner"`
}
```

| フィールド名 | 型 | 説明 | 備考 |
| ---------- | -- | --- | --- |
| `BaseTx` | `BaseTx` | 共通のメタデータ、インプット、アウトプットを保持します。   | 他のP-ChainTxと同様のベース部分です。 |
| `Subnet` | `ids.ID` | 所有者変更の対象となるSubnetの識別子です。 | `subnetID`としてシリアライズされます。 |
| `SubnetAuth` | `verify.Verifiable` | 提出者が当該Subnetに対して権限を持つことを証明します。 | コメント文言は「ノード削除権限」に触れていますが、本Txでは`CreateSubnetTx`の`Owner`に対する権限検証に用います。 |
| `Owner` | `fx.Owner` | 更新後の新しい所有者情報です。 | `newOwner`としてシリアライズされます。 |

### 検証フローと状態更新

このトランザクションが有効とみなされ、ステートに反映されるまでの流れは以下になります。

1. トランザクション受領
    ノードは`TransferSubnetOwnershipTx`を受け取り、`BaseTx`の形式整合性や手数料支払の前提条件を確認します。
2. 対象Subnetの特定
    `Subnet`フィールドには`ids.ID`型が格納されます。
    この値は、対象となるSubnetを一意に識別するためのIDです。
    ノードは、この`SubnetID`を使って内部のステートデータ（P-Chain上で管理されているSubnet情報）から該当するSubnetを参照します。
3. 既存Ownerの取得
    対象Subnetを生成した`CreateSubnetTx`に格納されている`Owner`を確認します。
4. 権限の検証（SubnetAuthの照合）
    `SubnetAuth`（verify.Verifiableを満たす認証データ）を、手順3で得た`Owner`に基づいて検証します。
    ここで「**この提出者は現Owner権限を保持しているか**」を判断します。
    検証に失敗すればTxは無効です。
5. 新しいOwnerの反映
    検証に成功した場合、`Owner`フィールドの内容をSubnetの新しい所有者としてステートに反映します。
6. 手数料の処理と確定
    規定の手数料を消費し、Txがブロックに取り込まれて確定します。

## 図：処理フローの可視化

```mermaid
sequenceDiagram
    autonumber
    participant Client as クライアント
    participant Node as ノード(P-Chain)
    participant State as ステート

    Client->>Node: TransferSubnetOwnershipTx提出
    Node->>State: SubnetIDから対象Subnet検索
    State-->>Node: CreateSubnetTxのOwner取得
    Node->>Node: SubnetAuthをOwnerに対して検証
    alt 検証成功
        Node->>State: OwnerをnewOwnerに更新
        Node->>Node: 手数料0.001AVAX処理
        Node-->>Client: 成功（所有者更新確定）
    else 検証失敗
        Node-->>Client: 失敗（権限不正）
    end
```

### 型IDとcodecバージョン

このトランザクション型のtypeIDは`0x21`で、codecバージョンは`0x00`です。
typeIDは「**どの種類のTxか**」を識別するための定数で、codecは「**どのシリアライズ/デシリアライズ規則でエンコードするか**」を示します。

### 手数料

手数料は`0.001AVAX`です。
これはSubnetValidatorやDelegatorを追加する場合と同等の水準に設定されています。
ネットワークリソース消費と経済的なシビル耐性のバランスを取るための値です。

### 重要な注意点

**ACP31**で必ず守るべき要点は以下です。

1. `Subnet`は所有者更新対象の唯一の識別子です。
2. 検証は必ず`CreateSubnetTx`内の`Owner`を基準に`SubnetAuth`を照合します。
3. 検証成功後にのみ、`Owner`を新しい値へ更新します。
                           |

## 互換性

`TransferSubnetOwnershipTx`という新しいトランザクション型を導入することは、Avalancheネットワークの実行仕様に変更を加える必要があります。
つまり、この機能を有効にするためには、必ず全体のネットワークアップグレードが必要になります。

アップグレードが有効化される前にこの新しいトランザクションを処理してしまうと、未対応ノードとの間で認識が食い違い、コンセンサス（合意形成）が正しく維持できなくなる危険性があります。
そのため、実装者はアップグレードが有効になるまでは必ずこのトランザクションを拒否するように設計しなければなりません。

**ACP31**では、`TransferSubnetOwnershipTx`の仕様だけを定義しています。
したがって、互換性を保ちながら導入するには、実際のアップグレードが行われるまではトランザクションが無効であることを保証する必要があります。

## 参考実装

この新しいトランザクションの実装は、AvalancheGoのリポジトリ内で [プルリクエスト2178](https://github.com/ava-labs/avalanchego/pull/2178) として作成され、その後マージされています。

しかし、現在はまだ「D」アップグレードが有効化されていないため、このトランザクションをAvalancheGoに提出しても拒否されます。

## 最後に

今回は「Subnetの所有者を新しいアドレスに移転できるようにする仕組みを提案している**ACP31**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
