Symbolブロックチェーンのアカウント数推移を調べる

# はじめに
2021年3月17日のローンチから早や4年を迎えようとしているSymbolブロックチェーン。
ふとこれまでに発行されたアカウント総数や各月ごとにどれくらいのアカウントが発行されているのか気になったので、今回はその調査結果および調査方法を備忘録として書き記していきます。


# 調査結果
**アカウント発行総数：286,091**（～2024年12月17日、ブロック高3946672）

<details><summary>各月ごとの詳細</summary>

|集計期間|生成されたアカウント数|アカウント総数|
|-----------|------------|------------|
|2021/03/17 ~ 2021/03/31|74,545|74,545|
|2021/04/01 ~ 2021/04/30|16,167|90,712|
|2021/05/01 ~ 2021/05/31|21,650|112,362|
|2021/06/01 ~ 2021/06/30|8,589|120,951|
|2021/07/01 ~ 2021/07/31|4,232|125,183|
|2021/08/01 ~ 2021/08/31|5,150|130,333|
|2021/09/01 ~ 2021/09/30|4,027|134,360|
|2021/10/01 ~ 2021/10/31|3,134|165,703|
|2021/11/01 ~ 2021/11/30|7,714|173,417|
|2021/12/01 ~ 2021/12/31|9,552|182,969|
|2022/01/01 ~ 2022/01/31|5,712|188,681|
|2022/02/01 ~ 2022/02/28|5,530|194,211|
|2022/03/01 ~ 2022/03/31|5,050|199,261|
|2022/04/01 ~ 2022/04/30|4,273|203,534|
|2022/05/01 ~ 2022/05/31|3,304|206,838|
|2022/06/01 ~ 2022/06/30|1,885|208,723|
|2022/07/01 ~ 2022/07/31|1,538|210,261|
|2022/08/01 ~ 2022/08/31|1,443|211,704|
|2022/09/01 ~ 2022/09/30|1,512|213,216|
|2022/10/01 ~ 2022/10/31|2,345|215,561|
|2022/11/01 ~ 2022/11/30|1,775|217,336|
|2022/12/01 ~ 2022/12/31|1,291|218,627|
|2023/01/01 ~ 2023/01/31|1,827|220,454|
|2023/02/01 ~ 2023/02/28|2,588|223,042|
|2023/03/01 ~ 2023/03/31|1,800|224,842|
|2023/04/01 ~ 2023/04/30|1,742|226,584|
|2023/05/01 ~ 2023/05/31|1,139|227,723|
|2023/06/01 ~ 2023/06/30|1,368|229,091|
|2023/07/01 ~ 2023/07/31|1,289|230,380|
|2023/08/01 ~ 2023/08/31|2,811|233,191|
|2023/09/01 ~ 2023/09/30|1,730|234,921|
|2023/10/01 ~ 2023/10/31|1,507|236,428|
|2023/11/01 ~ 2023/11/30|2,597|239,025|
|2023/12/01 ~ 2023/12/31|1,633|240,658|
|2024/01/01 ~ 2024/01/31|4,466|245,124|
|2024/02/01 ~ 2024/02/29|5,324|250,448|
|2024/03/01 ~ 2024/03/31|8,879|259,327|
|2024/04/01 ~ 2024/04/30|19,035|278,362|
|2024/05/01 ~ 2024/05/31|1,514|279,876|
|2024/06/01 ~ 2024/06/30|988|280,864|
|2024/07/01 ~ 2024/07/31|785|281,649|
|2024/08/01 ~ 2024/08/31|699|282,348|
|2024/09/01 ~ 2024/09/30|1,131|283,479|
|2024/10/01 ~ 2024/10/31|720|284,199|
|2024/11/01 ~ 2024/11/30|1,182|285,381|
|2024/12/01 ~ 2024/12/17|710|286,091|

</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2239577/4a178dc3-45b1-8a43-c311-5dc68b52e923.png)

参考までにEthereumの推移も載せておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2239577/fa3c9bc4-f39d-daa3-57cb-e2dd9ad23c81.png)

https://www.theblock.co/data/on-chain-metrics/ethereum/number-of-new-addresses-on-the-ethereum-network-monthly


# 調査方法
### 各月における先頭/末尾のブロック高を取得する
まずは各月における先頭/末尾のブロック高を取得します。

:::note warn
Symbolブロックチェーンでは約30秒に1ブロック生成されるため1日の理論ブロック高は2880ですが、実際は少なからず誤差があり安直に2880ブロックずつ加算していくとズレが生じるので注意しましょう。
:::

例
2024/01/01: [2933873](https://symbol.fyi/blocks/2933873) ～ [2936748](https://symbol.fyi/blocks/2936748)（2875ブロック）

2024/01/02: [2936749](https://symbol.fyi/blocks/2936749) ～ [2939625](https://symbol.fyi/blocks/2939625)（2876ブロック）

https://symbol.github.io/symbol-openapi/v1.0.3/#tag/Block-routes/operation/searchBlocks

```typescript
import dayjs from 'dayjs';

const NODE_URL = 'https://sym-main-03.opening-line.jp:3001';
const NEMESIS_TIMESTAMP = 1615853185000; // mainnet

type ResponseSearchBlock = {
  data: {
    id: string;
    meta: {
      hash: string;
      totalFee: string;
      generationHash: string;
      stateHashSubCacheMerkleRoots: any[];
      totalTransactionsCount: number;
      transactionsCount: number;
      statementsCount: number;
    };
    block: {
      size: number;
      signature: string;
      signerPublicKey: string;
      version: number;
      network: number;
      type: number;
      height: number;
      timestamp: number;
      difficulty: number;
      proofGamma: string;
      proofVerificationHash: string;
      proofScalar: string;
      previousBlockHash: string;
      transactionsHash: string;
      receiptsHash: string;
      stateHash: string;
      beneficiaryAddress: string;
      feeMultiplier: number;
    };
  }[];
  pagination: {
    pageNumber: number;
    pageSize: number;
  };
};

const fetchBlockHeight = async (fromTimestamp: string, toTimestamp: string) => {
  const query = new URLSearchParams({
    fromTimestamp: fromTimestamp,
    toTimestamp: toTimestamp,
  });

  const blockResponse: ResponseSearchBlock = await fetch(
    new URL(`/blocks?${query}`, NODE_URL),
    {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
    },
  ).then((res) => res.json());

  if (blockResponse.data.length === 0) {
    throw new Error('Block not found');
  }

  return blockResponse.data[0]?.block.height || 0;
};

const getBlockHeights = async (fromTimestamp: string, toTimestamp: string) => {
  try {
    // 検索開始日の最初のブロック高を取得
    const fromStartTimestamp = dayjs(fromTimestamp, 'YYYY/MM/DD').unix() * 1000 - NEMESIS_TIMESTAMP;
    const fromEndTimestamp = dayjs(fromTimestamp, 'YYYY/MM/DD').add(1, 'minute').unix() * 1000 - NEMESIS_TIMESTAMP;
    // 検索終了日の最終ブロック高を取得
    const toStartTimestamp = dayjs(toTimestamp, 'YYYY/MM/DD').add(1, 'day').unix() * 1000 - NEMESIS_TIMESTAMP;
    const toEndTimestamp = dayjs(toTimestamp, 'YYYY/MM/DD').add(1, 'day').add(1, 'minute').unix() * 1000 - NEMESIS_TIMESTAMP;

    const [fromBlockHeight, rawToBlockHeight] = await Promise.all([
      fetchBlockHeight(fromStartTimestamp.toString(), fromEndTimestamp.toString()),
      fetchBlockHeight(toStartTimestamp.toString(), toEndTimestamp.toString()),
    ]);

    if (fromBlockHeight === 0 || rawToBlockHeight === 0) {
      throw new Error('Block not found');
    }

    // 欲しい検索終了ブロック高は1つ前のブロックなので調整
    const toBlockHeight = rawToBlockHeight - 1;
    return { fromBlockHeight, toBlockHeight };
  } catch (e) {
    console.error(e);
    return { fromBlockHeight: 0, toBlockHeight: 0 };
  }
};
```


### 各月に発行されたアカウント数を取得する
アカウントが発行されたブロック高は`addressHeight`から確認できます。
今回は上記で取得した先頭/末尾ブロック高の間に含まれる`addressHeight`を持つアカウントを抽出、カウントしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2239577/1fb5c1f2-c275-47c7-cf80-6893c6a3d886.png)

:::note warn
抽出の際にページ番号（pageNumber）を指定しないと毎度途方もない数のリクエストを送ることになり無限に時間が溶けます。もしお試し実行される際には以下の一覧表を参考にページ番号を指定いただくと幸せになれるかもしれません。
:::

<details><summary>開始日/終了日とページ番号一覧</summary>

|開始日|終了日|開始日の最初のブロック高|終了日の最後のブロック高|開始位置のページ番号|
|-----------|------------|------------|-----------|------------|
|2021-03-17|2021-03-31|0|42176|0|
|2021-04-01|2021-04-30|42177|128505|745|
|2021-05-01|2021-05-31|128506|217699|907|
|2021-06-01|2021-06-30|217700|304026|1123|
|2021-07-01|2021-07-31|304027|393233|1209|
|2021-08-01|2021-08-31|393234|482429|1251|
|2021-09-01|2021-09-30|482430|568750|1303|
|2021-10-01|2021-10-31|568751|657942|1343|
|2021-11-01|2021-11-30|657943|744258|1657|
|2021-12-01|2021-12-31|744259|833462|1734|
|2022-01-01|2022-01-31|833463|922652|1829|
|2022-02-01|2022-02-28|922653|1003214|1886|
|2022-03-01|2022-03-31|1003215|1092417|1942|
|2022-04-01|2022-04-30|1092418|1178732|1992|
|2022-05-01|2022-05-31|1178733|1267932|2035|
|2022-06-01|2022-06-30|1267933|1354250|2068|
|2022-07-01|2022-07-31|1354251|1443445|2087|
|2022-08-01|2022-08-31|1443446|1532643|2102|
|2022-09-01|2022-09-30|1532644|1618958|2117|
|2022-10-01|2022-10-31|1618959|1708151|2132|
|2022-11-01|2022-11-30|1708152|1794472|2155|
|2022-12-01|2022-12-31|1794473|1883664|2173|
|2023-01-01|2023-01-31|1883665|1972860|2186|
|2023-02-01|2023-02-28|1972861|2053424|2204|
|2023-03-01|2023-03-31|2053425|2142621|2230|
|2023-04-01|2023-04-30|2142622|2228938|2248|
|2023-05-01|2023-05-31|2228939|2318134|2265|
|2023-06-01|2023-06-30|2318135|2404452|2277|
|2023-07-01|2023-07-31|2404453|2493646|2291|
|2023-08-01|2023-08-31|2493647|2582844|2303|
|2023-09-01|2023-09-30|2582845|2669160|2332|
|2023-10-01|2023-10-31|2669161|2758360|2349|
|2023-11-01|2023-11-30|2758361|2844675|2364|
|2023-12-01|2023-12-31|2844676|2933872|2390|
|2024-01-01|2024-01-31|2933873|3023063|2406|
|2024-02-01|2024-02-29|3023064|3106509|2451|
|2024-03-01|2024-03-31|3106510|3195694|2504|
|2024-04-01|2024-04-30|3195695|3282026|2593|
|2024-05-01|2024-05-31|3282027|3371218|2783|
|2024-06-01|2024-06-30|3371219|3457532|2798|
|2024-07-01|2024-07-31|3457533|3546730|2808|
|2024-08-01|2024-08-31|3546731|3635930|2816|
|2024-09-01|2024-09-30|3635931|3722250|2823|
|2024-10-01|2024-10-31|3722251|3811439|2834|
|2024-11-01|2024-11-30|3811440|3897755|2842|
|2024-12-01|2024-12-17|3897756|3946672|2853|

</details>


https://symbol.github.io/symbol-openapi/v1.0.3/#tag/Account-routes/operation/searchAccounts

```typescript
const PAGE_SIZE = '100';

type ResponseSearchAccounts = {
  data: {
    id: string;
    account: {
      version: number;
      address: string;
      addressHeight: string;
      publicKey: string;
      publicKeyHeight: string;
      accountType: number;
      SupplementalPublicKeys: {
        linked: {
          publicKey: string;
        };
        node: {
          publicKey: string;
        };
        vrf: {
          publicKey: string;
        };
        voting: {
          publicKey: string;
        };
      };
      activityBuckets: {
        startHeight: string;
        totalFeesPaid: string;
        beneficiaryCount: string;
        rawScore: string;
      }[];
      mosaics: {
        id: string;
        amount: string;
      }[];
      importance: string;
      importanceHeight: string;
    };
  }[];
  pagination: {
    pageNumber: number;
    pageSize: number;
  };
};

const getCreatedAccountForMonth = async () => {
  try {
    const fromTimestamp = '2024-01-01'; // 開始日を指定
    const toTimestamp = '2024-01-31'; // 終了日を指定
    const pageNumber = 2406; // ページ番号を指定

    // 日時からブロック高を取得
    const { fromBlockHeight, toBlockHeight } = await getBlockHeights(
      fromTimestamp,
      toTimestamp,
    );

    // アカウント情報を取得
    const fetchAccounts = async (pageNumber: number, count: number = 0): Promise<{ totalCount: number; lastPageNumber: number }> => {
      const query = new URLSearchParams({
        pageSize: PAGE_SIZE,
        pageNumber: pageNumber.toString(),
      });

      const accounts: ResponseSearchAccounts = await fetch(
        new URL(`/accounts?${query}`, NODE_URL),
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        },
      ).then((res) => res.json());

      // fromBlockHeight以上かつtoBlockHeight以下のアカウントをカウント
      const validAccountsCount = accounts.data.filter((account) => {
        const addressHeight = Number(account.account.addressHeight);
        return (
          addressHeight >= fromBlockHeight && addressHeight <= toBlockHeight
        );
      }).length;
      const totalCount = count + validAccountsCount;

      if (
        accounts.data.length > 0 &&
        Number(accounts.data[accounts.data.length - 1].account.addressHeight) <=
          toBlockHeight
      ) {
        await new Promise((resolve) => setTimeout(resolve, 1000));
        return fetchAccounts(pageNumber + 1, totalCount);
      }

      return { totalCount, lastPageNumber: pageNumber };
    };

    const { totalCount, lastPageNumber } = await fetchAccounts(pageNumber);
    return {
      totalCount: totalCount,
      fromTimestamp: fromTimestamp,
      toTimestamp: toTimestamp,
      fromBlockHeight: fromBlockHeight,
      toBlockHeight: toBlockHeight,
      lastPageNumber: lastPageNumber,
    };
  } catch (error) {
    console.error('getCreatedAccountForMonth error', error);
  }
};
```

# おわりに
全体通して見ると現状相場との相関がやや色濃く見られるな、という印象でした。
また2022年下期以降は低調に推移しつつもコンスタントに毎月1000アカウント近く発行されているのが少し意外でした（個人的にはもっと少ない予想だったので用途が気になっています）。

実際にどれだけ利用されているかという観点だと「各アカウントがどの程度アクティブなのか」「各月のトランザクション数推移との相関はどうなのか」なども併せて多角的に分析・考察する必要があると考えていますが、やるとなったらかなり膨大な作業になりそうだな...と天井を見上げつつ今回はここでキーを打つ手を止めたいと思います。

KEEP BUILDING.