Skip to content

Commit

Permalink
Moonbeam change tracking improv
Browse files Browse the repository at this point in the history
  • Loading branch information
dominicletz committed May 9, 2024
1 parent c979dec commit 714c0dc
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 40 deletions.
3 changes: 2 additions & 1 deletion lib/remote_chain/node_proxy.ex
Expand Up @@ -8,6 +8,7 @@ defmodule RemoteChain.NodeProxy do
use GenServer, restart: :permanent
alias RemoteChain.NodeProxy
require Logger
@default_timeout 25_000

defstruct [:chain, connections: %{}, req: 100, requests: %{}, lastblocks: %{}]

Expand All @@ -21,7 +22,7 @@ defmodule RemoteChain.NodeProxy do
end

def rpc(chain, method, params) do
GenServer.call(name(chain), {:rpc, method, params}, 15_000)
GenServer.call(name(chain), {:rpc, method, params}, @default_timeout)
end

@impl true
Expand Down
79 changes: 40 additions & 39 deletions lib/remote_chain/rpc_cache.ex
Expand Up @@ -45,11 +45,29 @@ defmodule RemoteChain.RPCCache do

def get_storage_at(chain, address, slot, block \\ "latest") do
block = resolve_block(chain, block)

# for storage requests we use the last change block as the base
# any contract not using change tracking will suffer 240 blocks (one hour) of caching
block =
case get_last_change(chain, address, block) do
0 -> block - rem(block, 240)
num -> min(num, block)
end

rpc!(chain, "eth_getStorageAt", [address, slot, block])
end

def get_storage_many(chain, address, slots, block \\ "latest") do
block = resolve_block(chain, block)

# for storage requests we use the last change block as the base
# any contract not using change tracking will suffer 240 blocks (one hour) of caching
block =
case get_last_change(chain, address, block) do
0 -> block - rem(block, 240)
num -> min(num, block)
end

calls = Enum.map(slots, fn slot -> {:rpc, "eth_getStorageAt", [address, slot, block]} end)

RemoteChain.Util.batch_call(name(chain), calls, @default_timeout)
Expand Down Expand Up @@ -82,51 +100,34 @@ defmodule RemoteChain.RPCCache do
rpc!(chain, "eth_getBalance", [address, block])
end

def get_last_change(chain, address, block \\ "latest") do
block = resolve_block(chain, block)

# `ChangeTracker.sol` slot for signaling: 0x1e4717b2dc5dfd7f487f2043bfe9999372d693bf4d9c51b5b84f1377939cd487
rpc!(chain, "eth_getStorageAt", [
address,
"0x1e4717b2dc5dfd7f487f2043bfe9999372d693bf4d9c51b5b84f1377939cd487",
block
])
|> Base16.decode_int()
end

def get_account_root(chain, address, block \\ "latest")
when chain in [Chains.MoonbaseAlpha, Chains.Moonbeam, Chains.Moonriver] do
block = resolve_block(chain, block)

# this code is specific to Moonbeam (EVM on Substrate) simulating the account_root
# we're now using the `ChangeTracker.sol` slot for signaling: 0x1e4717b2dc5dfd7f487f2043bfe9999372d693bf4d9c51b5b84f1377939cd487

# this is modulname and storage name hashed and concatenated
# from this document: https://www.shawntabrizi.com/blog/substrate/querying-substrate-storage-via-rpc/#storage-keys
# Constant prefix for '?.?'
# prefix = Base16.decode("0x26AA394EEA5630E07C48AE0C9558CEF7B99D880EC681799C0CF30E8886371DA9")
# Constant prefix for 'evm.accountStorages'
prefix = Base.decode16!("1DA53B775B270400E7E61ED5CBC5A146AB1160471B1418779239BA8E2B847E42")
bin_address = Base16.decode(address)
{:ok, storage_item_key} = :eblake2.blake2b(16, bin_address)
storage_key = Base16.encode(prefix <> storage_item_key <> bin_address)

# fetching the polkadot relay hash for the corresponding block
# `block` is always a block number (not a block hash)
# and we're assuming that polkadot relay chain and evm chain are in sync
block_hash = rpc!(chain, "chain_getBlockHash", [block])

# To emulate a storage root that only changes when
# any of the slots has changed we do this:
# 1) Fetch all account keys
# => because there can be many account keys and fetching them all can be slow
# => we're guessing here on change for more than 20 keys
keys =
rpc!(chain, "state_getKeys", [storage_key, block_hash])
|> Enum.map(fn key -> "0x" <> binary_part(key, byte_size(key) - 40, 40) end)
|> Enum.sort()

if length(keys) < 40 do
values = get_storage_many(chain, address, keys, block)

Enum.zip(keys, values)
|> Enum.map(fn {key, value} -> key <> value end)
|> Enum.join()
|> Hash.keccak_256()
else
# since this is a heuristic, it can be wrong and might miss some changes
# so we force fresh every week
base = div(block + Base16.decode_int(address), 50_000) |> Base16.encode(false)
last_change = get_last_change(chain, address, block)

Enum.join([base | keys])
|> Hash.keccak_256()
if last_change > 0 do
Hash.keccak_256(address <> "#{last_change}")
else
# there is no change tracking yet?
# in this case we're just not updating more often than once an hour
blocks_per_hour = div(3600, 15)
base = div(block + Base16.decode_int(address), blocks_per_hour)
Hash.keccak_256(address <> "#{last_change}:#{base}")
end
|> Base16.encode()
end
Expand Down
11 changes: 11 additions & 0 deletions notes.exs
@@ -1,3 +1,14 @@
# 9th May 2024

node = :global.whereis_name({RemoteChain.NodeProxy, Chains.Moonbeam})
Process.info(node)
:sys.get_state(node)

cache = :global.whereis_name({RemoteChain.RPCCache, Chains.Moonbeam})
Process.info(cache)
Lru.size(:sys.get_state(cache).lru)


# 16 Apr 2024

dom = "0x5849ea89593cf65e13110690d9339c121801a45c"
Expand Down

0 comments on commit 714c0dc

Please sign in to comment.