🧐

Ethereumのコントラクトのステートを上書きした場合に過去に遡って掘り起こせるのか?

2021/12/09に公開

先日EthereumでTwitterっぽいものを作ってみるという記事を書いた。

その中で何の気なしに「ブロックチェーンに保存されているのでツイートは消せない」と書いたが、よく考えてみると「コントラクトのステート自体は簡単に上書きできるし本当に消せないのか?」という疑問が湧いてきたので調べてみた。

まずは公式DocsということでTRANSACTIONSBLOCKSEthereum Virtual Machineを読んでみたが現状のステートの保持に関しては理解できるが過去のある時点の上書きされた状態を参照できるのか?はわからなかった。

そこでこの疑問を和組というWeb3関連の情報をやりとりできる日本のコミュニティ(Discord)で聞いてみた。すると有益な回答を得られたのでQ&Aを簡潔ながら記しておく↓

Q

storageに保存されたデータは永続化される。ではそのデータの書き換えの履歴まで残るのか?例えば下記のようなコントラクトでchangeMessageを呼び出すとmessageのstateは書き換わる。書き変わった結果Googbye, World!になるが、その前のHello, World!というstateはブロックチェーン履歴(Etherscan等)で参照することは可能なのか?

   contract Greeter {
     string private message;
   
     constructor() {
       message = "Hello, World!";
     }
   
     function changeMessage() public {
       message = "Goodbye, World!";
     }
   }

A

答えとしては、参照可能 。上書きされてしまった状態のブロックはすぐFull Nodeからは消えてしまう(最新の128block)。しかしArchive Nodeを参照したい時点のブロックID指定でローカルに持ってくるとその時点のステートが得られるので結果的に参照可能となる。

実際に頂いたアンサー抜粋↓

トランザクションによってストレージのステートが更新されていきますが、過去のステート(n番目のblockのstate)は履歴として残っている(*)ので参照することが可能です。これを利用して、Hardhatなどでmainnetのn番目のblockの全ステートをforkしてローカルでテストしたりすることができます。
Mainnet forking | Hardhat | Ethereum development environment for professionals by Nomic Labs

genesis blockからの全ての履歴を持っているのはArchive nodeだけなので注意が必要です。full nodeだと最新128 blockのステートしか持っていません。

ブロックチェーンはブロックをタイムスタンプにして積み重ねることでとにかく記録をとどめていく発想なので、全部ひっくり返せば力技でなんとかなったりはしますw

Full Node, Archive Nodeとは

EthhubのRunning an Ethereum Nodeによると、Full Nodeは全てのステートとトランザクションを保持しており、Archive Nodeはさらに全ての過去の履歴まで保持しているNodeとのこと。

Archive nodes are only necessary if you want to check the state of an account at any given block height. For example, if you wanted to know the Ether balance an account had at block #4,000,000, you would need to run and query an archive node.

Archive Nodesのセクションにも書いてある通り、Archive Nodesを使ってBlockNumberを指定してクエリを投げれば過去のある時点のステートをチェックできるらしい。

実際に試してみる

Mainnet forking | Hardhat | Ethereum development environment for professionals by Nomic LabsによるとHardhatのMainnet Forkingの仕組みを使えば過去の特定のブロックのArchive Nodeをローカルに取得してその時点のステートにアクセスできるらしいので実際にやってみる。

題材としてはこの記事の中で作ったTwitterっぽいアプリ。このアプリの中のツイートをある時点の状態とその次の時点の状態でそれぞれブロックID指定でForkしてみることでステートの変化を確認してみる。

準備としてはhardhat.config.jsに下記を書き加えるのみ。

module.exports = {
  solidity: "0.8.4",
  networks: {
    hardhat: {
      forking: {
        url: "https://eth-mainnet.alchemyapi.io/v2/<key>",
        blockNumber: 11574797,
      },
    },
  },
}

Archive NodeにアクセスするにはAlchemyを使った方が良いとのこと。

blockNumberRopsten Etherscanで当該コントラクトIDを調べてトランザクション履歴を見る。するとBlockという項目があるのでそれを使う。

そして普通にhardhatのnodeを起動。

npx hardhat node --network hardhat

この状態でローカル環境でアプリを立ち上げてみる。

まずはコントラクトを作成した直後にツイートした状態。Hello!というツイートのみが表示されるはず。

予想通りHello!のみ表示された。

次にその次のツイートのブロックID(11574821)を指定する。

そしてhardhatのnodeを再起動。

またアプリにアクセスしてみる。Hello!とともに次のツイートがひとつ表示されるはず。

想定通りの挙動になった。

これでForkするかもしくはArchive Nodeを全て取得してblockNumberを指定すれば過去のある時点のステートにアクセスできることがわかった。つまり一度コントラクトでステートに書き込まれた情報は掘り返せるということになる。

当初の疑問だった「ブロックチェーンに保存されているのでツイートは消せない」という表現は概ね間違いではなかったようだ。

その他リンク

Discussion