6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Symbol ブロックチェーン 履歴 取得 timestamp [検索] \ (´・ω・`)

Last updated at Posted at 2024-06-13

はじめに

  • timestamp(日付、2024/06/13 etc...)で指定した期間のトランザクション履歴だけ取得したいのにブロック高での絞り込みしかできない...(´・ω・`)
  • ブロック高の計算めんど...(´・ω・`)

というお困りごと解消備忘録です。

待望のアップデート

2024年4月に公開されたアップデートにより、ブロック情報取得時にtimestampを利用してフィルタリングできるようになりました。ブロック高計算からの卒業。

image.png

ソースコードを追うとfromTimestamp, toTimestamp を指定してやることで抽出できるようです。早速やっていきましょう。

image.png

参考

一連の流れ

  1. 日付をUNIX時間に変換する(timestamp)
  2. timestampでブロック高を取得する
  3. ブロック高で指定期間内の履歴を抽出する

やってみる

日付をUNIX時間に変換する + timestampでブロック高を取得する

まずは履歴の検索開始ブロック高(fromBlockHeight)と検索終了ブロック高(toBlockHeight)を取得するため、日付をtimestampに変換します。

ただしこのtimestamp、変換の際に少し工夫する必要がありまして。

image.png

ネメシスブロック(Nemesis Block)
1番目のブロック、他チェーンでジェネシスブロックと呼ばれるもの
epochAdjustment
ネメシスブロックのtimestamp(秒)
 ・Symbolメインネット: 1615853185
 ・Symbolテストネット: 1667250467

つまり、指定したUNIX時間①からepochAdjustment分②を差し引いてやることでブロック情報の取得に用いるtimestamp③を取得することができます。詳細はこちらから↓

実際にtimestampを使って得られた値

https://sym-main-03.opening-line.jp:3001/blocks?fromTimestamp=98636015000&toTimestamp=98636075000

{
  "meta":{
    "hash":"4E6718A70BF60920BF25D948B1A611247A4896A4DC4E17A4C2691BDD5543641C",
    "generationHash":"FF64D231C2CB153B159CFF140DBCF05063BB17AE2D8586E55BF0253EA5FC8866",
    "totalFee":"31525",
    "totalTransactionsCount":4,
    "stateHashSubCacheMerkleRoots":[
      "95E4666A459371F3669A4FF676B99B929B85CA6540A3E206B75437E30B561820",
      "84E4E37EF1FC37A2CC23B8E56B8A2E46268A2F5A0C97B51FE274547E2BA0260D",
      ...,
    ],
    "transactionsCount":4,
    "statementsCount":1
  },
  "block":{
    "size":1639,
    "signature":"BC7351C34DE14B4216B27C3CE21E4982AF272864C56D16B3A45E469E193A2678C0C33336D5E7CFD59504D2E478EA32581E217208140AFDA97F437B4AEEA74E0C",
    "signerPublicKey":"F862AB32216DE74697E939748755C519C5B461DF7F5AFFB96A5D07585879C1E9",
    "version":1,
    "network":104,
    "type":33091,
    "height":"3282027",
    "timestamp":"98636031099",★★★
    "difficulty":"131198718152991",
    "proofGamma":"6E154716018C8FBBA15DD10CB1CB084388AB141BB13FFC5F7043A4406844019E",
    "proofVerificationHash":"D01B3A7B133C8082C99509375D772674",
    "proofScalar":"8300669A7238E49B872EACDD3CD80593D5DF9204A17D1495A9AFF2C417F09700",
    "previousBlockHash":"E2B8E0873C51853AC2CA73DF5D7A9905AF6BC5F50B8F83DEFFD07AFFE3C25013",
    "transactionsHash":"C8BF28A9E874650F93DC4BD8240506A559745404BEB221BF0E57FC1C4DF329DE",
    "receiptsHash":"651520E50FBE75F8977E1A31BFF9F80E1482E1507DD9490D1D46AAE7973F54F5",
    "stateHash":"FB23B6D6E5EDCCA6E44FD8A522B69FCDED4C85A950B0D80F2EEF636C464F22EB",
    "beneficiaryAddress":"68645FF6B18FEE134E9997D5C81DDA2F31855D710EF08566",
    "feeMultiplier":25
  },
  "id":"663107A4B8D2F084D206AA8F"
}

Symbolブロックチェーンでは約30秒ごとに1ブロック生成されるため、検索開始日の最初のブロックと検索終了日の最後のブロックを以下のように取得します。

ブロック情報を取得するAPIはこちら↓

import dayjs from 'dayjs';

// ブロック情報を取得する関数
async function fetchBlockHeight(
  baseUrl: string,
  startTimestamp: number,
  endTimestamp: number,
) {
  const url = `${baseUrl}/blocks?fromTimestamp=${startTimestamp}&toTimestamp=${endTimestamp}`;
  const response = await fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    throw new Error('Network response error.');
  }

  const data = await response.json();
  return data.data[0]?.block?.height || 0;
}

// -----------------------
async function main() {
  const baseUrl = 'https://sym-main-03.opening-line.jp:3001';
  const fromDate = '2024/05/01';
  const toDate = '2024/05/31';
  const nemesisBlockTimestamp = 1615853185000; // epochAdjustment * 1000(ミリ秒に変換)

  // 検索開始日の最初のブロックを取得するためのtimestamp
  const fromStartTimestamp = dayjs(fromTimestamp, 'YYYY/MM/DD').unix() * 1000 - nemesisBlockTimestamp;
  const fromEndTimestamp = dayjs(fromTimestamp, 'YYYY/MM/DD').add(1, 'minute').unix() * 1000 - nemesisBlockTimestamp;
  // 検索終了日の最後のブロックを取得するためのtimestamp
  const toStartTimestamp = dayjs(toTimestamp, 'YYYY/MM/DD').add(1, 'day').unix() * 1000 - nemesisBlockTimestamp;
  const toEndTimestamp = dayjs(toTimestamp, 'YYYY/MM/DD').add(1, 'day').add(1, 'minute').unix() * 1000 - nemesisBlockTimestamp;

  try {
    // 各ブロック高を取得する
    const [rawFromBlockHeight, rawToBlockHeight] = await Promise.all([
      fetchBlockHeight(baseUrl, fromStartTimestamp, fromEndTimestamp),
      fetchBlockHeight(baseUrl, toStartTimestamp, toEndTimestamp),
    ]);

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

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

main();

やや冗長な気もしますが整理すると以下のようになります。
今回の場合は2024/05/31の最終ブロック高 3371218 を利用します。

image.png

ブロック高で指定期間内の履歴を抽出する

あとは得られたブロック高を利用して指定期間内のトランザクション履歴を取得するだけです。

// トランザクション履歴を取得する関数
async function fetchTransactions(
  baseUrl: string,
  recipientAddress: string,
  fromBlockHeight: number,
  toBlockHeight: number,
) {
  let allTransactions = [];

  async function fetchPage(pageNumber: number) {
    const url = `${baseUrl}/transactions/confirmed?recipientAddress=${recipientAddress}&fromHeight=${fromBlockHeight}&toHeight=${toBlockHeight}&embedded=true&order=asc&pageNumber=${pageNumber}&pageSize=100`;
    const response = await fetch(url, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      throw new Error('Network response error.');
    }

    const data = await response.json();
    allTransactions = allTransactions.concat(data.data);

    if (data.data.length === 100) {
      await fetchPage(pageNumber + 1);
    }
  }
  
  await fetchPage(1);
  return allTransactions;
}

// -----------------------
async function main() {
  const baseUrl = 'https://sym-main-03.opening-line.jp:3001';
  const recipientAddress = 'Your wallet address';
  const fromBlockHeight = 3282027;
  const toBlockHeight = 3371219;
  const transactions = await fetchTransactions(baseUrl, recipientAddress, fromBlockHeight, toBlockHeight);
}

main();

ソースコード総括

import dayjs from 'dayjs';

// ブロック情報を取得する関数
async function fetchBlockHeight(
  baseUrl: string,
  startTimestamp: number,
  endTimestamp: number,
) {
  const url = `${baseUrl}/blocks?fromTimestamp=${startTimestamp}&toTimestamp=${endTimestamp}`;
  const response = await fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    throw new Error('Network response error.');
  }

  const data = await response.json();
  return data.data[0]?.block?.height || 0;
}

// トランザクション履歴を取得する関数
async function fetchTransactions(
  baseUrl: string,
  recipientAddress: string,
  fromBlockHeight: number,
  toBlockHeight: number,
) {
  let allTransactions = [];

  async function fetchPage(pageNumber: number) {
    const url = `${baseUrl}/transactions/confirmed?recipientAddress=${recipientAddress}&fromHeight=${fromBlockHeight}&toHeight=${toBlockHeight}&embedded=true&order=asc&pageNumber=${pageNumber}&pageSize=100`;
    const response = await fetch(url, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      throw new Error('Network response error.');
    }

    const data = await response.json();
    allTransactions = allTransactions.concat(data.data);

    if (data.data.length === 100) {
      await fetchPage(pageNumber + 1);
    }
  }
  
  await fetchPage(1);
  return allTransactions;
}

// -----------------------
async function main() {
  const baseUrl = 'https://sym-main-03.opening-line.jp:3001';
  const recipientAddress = 'Your wallet address';
  const fromDate = '2024/05/01';
  const toDate = '2024/05/31';
  const nemesisBlockTimestamp = 1615853185000; // epochAdjustment * 1000(ミリ秒に変換)

  // 検索開始日の最初のブロックを取得するためのtimestamp
  const fromStartTimestamp = dayjs(fromTimestamp, 'YYYY/MM/DD').unix() * 1000 - nemesisBlockTimestamp;
  const fromEndTimestamp = dayjs(fromTimestamp, 'YYYY/MM/DD').add(1, 'minute').unix() * 1000 - nemesisBlockTimestamp;
  // 検索終了日の最後のブロックを取得するためのtimestamp
  const toStartTimestamp = dayjs(toTimestamp, 'YYYY/MM/DD').add(1, 'day').unix() * 1000 - nemesisBlockTimestamp;
  const toEndTimestamp = dayjs(toTimestamp, 'YYYY/MM/DD').add(1, 'day').add(1, 'minute').unix() * 1000 - nemesisBlockTimestamp;

  try {
    // 各ブロック高を取得する
    const [rawFromBlockHeight, rawToBlockHeight] = await Promise.all([
      fetchBlockHeight(baseUrl, fromStartTimestamp, fromEndTimestamp),
      fetchBlockHeight(baseUrl, toStartTimestamp, toEndTimestamp),
    ]);

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

    const fromBlockHeight = rawFromBlockHeight; // 検索開始ブロックは調整不要
    const toBlockHeight = rawToBlockHeight - 1; // 欲しい検索終了ブロック高は1つ前のブロックなので調整

    // トランザクション履歴を取得する
    const transactions = await fetchTransactions(baseUrl, recipientAddress, fromBlockHeight, toBlockHeight);
  
  } catch (e) {
    console.error('Unexpected error:', e);
  }
};

main();

参考

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?