BlockFetcher.Catchup and BlockFetcher.Realtime get their own structs, which use BlockFetcher struct. This better emphasizes that Catchup and Realtime are both block fetchers. The BlockFetcher.Catchup and BlockFetcher.Realtime structs move into Supervisor's struct.pull/489/head
parent
2333cc6cf5
commit
11735b1289
@ -0,0 +1,349 @@ |
||||
defmodule Indexer.BlockFetcher.SupervisorTest do |
||||
# `async: false` due to use of named GenServer |
||||
use EthereumJSONRPC.Case, async: false |
||||
use Explorer.DataCase |
||||
|
||||
import Mox |
||||
import EthereumJSONRPC, only: [integer_to_quantity: 1] |
||||
|
||||
alias Explorer.Chain.Block |
||||
alias Indexer.{AddressBalanceFetcherCase, BlockFetcher, BoundInterval, InternalTransactionFetcherCase} |
||||
alias Indexer.BlockFetcher.Catchup |
||||
|
||||
@moduletag capture_log: true |
||||
|
||||
# MUST use global mode because we aren't guaranteed to get `start_supervised`'s pid back fast enough to `allow` it to |
||||
# use expectations and stubs from test's pid. |
||||
setup :set_mox_global |
||||
|
||||
setup :verify_on_exit! |
||||
|
||||
describe "start_link/1" do |
||||
test "starts fetching blocks from latest and goes down", %{json_rpc_named_arguments: json_rpc_named_arguments} do |
||||
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do |
||||
case Keyword.fetch!(json_rpc_named_arguments, :variant) do |
||||
EthereumJSONRPC.Parity -> |
||||
block_number = 3_416_888 |
||||
block_quantity = integer_to_quantity(block_number) |
||||
|
||||
EthereumJSONRPC.Mox |
||||
|> stub(:json_rpc, fn |
||||
# latest block number to seed starting block number for genesis and realtime tasks |
||||
%{method: "eth_getBlockByNumber", params: ["latest", false]}, _options -> |
||||
{:ok, |
||||
%{ |
||||
"author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", |
||||
"difficulty" => "0xfffffffffffffffffffffffffffffffe", |
||||
"extraData" => "0xd583010a068650617269747986312e32362e32826c69", |
||||
"gasLimit" => "0x7a1200", |
||||
"gasUsed" => "0x0", |
||||
"hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e", |
||||
"logsBloom" => |
||||
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", |
||||
"number" => block_quantity, |
||||
"parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c", |
||||
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"sealFields" => [ |
||||
"0x841240b30d", |
||||
"0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01" |
||||
], |
||||
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"signature" => |
||||
"58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01", |
||||
"size" => "0x243", |
||||
"stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7", |
||||
"step" => "306230029", |
||||
"timestamp" => "0x5b437f41", |
||||
"totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb", |
||||
"transactions" => [], |
||||
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"uncles" => [] |
||||
}} |
||||
|
||||
[%{method: "eth_getBlockByNumber", params: [_, true]} | _] = requests, _options -> |
||||
{:ok, |
||||
Enum.map(requests, fn %{id: id, params: [block_quantity, true]} -> |
||||
%{ |
||||
id: id, |
||||
jsonrpc: "2.0", |
||||
result: %{ |
||||
"author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", |
||||
"difficulty" => "0xfffffffffffffffffffffffffffffffe", |
||||
"extraData" => "0xd583010a068650617269747986312e32362e32826c69", |
||||
"gasLimit" => "0x7a1200", |
||||
"gasUsed" => "0x0", |
||||
"hash" => |
||||
Explorer.Factory.block_hash() |
||||
|> to_string(), |
||||
"logsBloom" => |
||||
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", |
||||
"number" => block_quantity, |
||||
"parentHash" => |
||||
Explorer.Factory.block_hash() |
||||
|> to_string(), |
||||
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"sealFields" => [ |
||||
"0x841240b30d", |
||||
"0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01" |
||||
], |
||||
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"signature" => |
||||
"58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01", |
||||
"size" => "0x243", |
||||
"stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7", |
||||
"step" => "306230029", |
||||
"timestamp" => "0x5b437f41", |
||||
"totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb", |
||||
"transactions" => [], |
||||
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"uncles" => [] |
||||
} |
||||
} |
||||
end)} |
||||
|
||||
[%{method: "eth_getBalance"} | _] = requests, _options -> |
||||
{:ok, Enum.map(requests, fn %{id: id} -> %{id: id, jsonrpc: "2.0", result: "0x0"} end)} |
||||
end) |
||||
|
||||
EthereumJSONRPC.Geth -> |
||||
block_number = 5_950_901 |
||||
block_quantity = integer_to_quantity(block_number) |
||||
|
||||
EthereumJSONRPC.Mox |
||||
|> stub(:json_rpc, fn |
||||
%{method: "eth_getBlockByNumber", params: ["latest", false]}, _options -> |
||||
{:ok, |
||||
%{ |
||||
"difficulty" => "0xc2550dc5bfc5d", |
||||
"extraData" => "0x65746865726d696e652d657538", |
||||
"gasLimit" => "0x7a121d", |
||||
"gasUsed" => "0x6cc04b", |
||||
"hash" => "0x71f484056fec687fd469989426c94c469ff08a28eae9a1865359d64557bb99f6", |
||||
"logsBloom" => |
||||
"0x900840000041000850020000002800020800840900200210041006005028810880231200c1a0800001003a00011813005102000020800207080210000020014c00888640001040300c180008000084001000010018010040001118181400a06000280428024010081100015008080814141000644404040a8021101010040001001022000000000880420004008000180004000a01002080890010000a0601001a0000410244421002c0000100920100020004000020c10402004080008000203001000200c4001a000002000c0000000100200410090bc52e080900108230000110010082120200000004e01002000500001009e14001002051000040830080", |
||||
"miner" => "0xea674fdde714fd979de3edf0f56aa9716b898ec8", |
||||
"mixHash" => "0x555275cd0ab4c3b2fe3936843ee25bb67da05ef7dcf17216bc0e382d21d139a0", |
||||
"nonce" => "0xa49e42a024600113", |
||||
"number" => block_quantity, |
||||
"parentHash" => "0xb4357733c59cc6f785542d072a205f4e195f7198f544ea5e01c1b90ef0f914a5", |
||||
"receiptsRoot" => "0x17baf8de366fecc1be494bff245be6357ac60a5fe786099dba89983778c8421e", |
||||
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"size" => "0x6c7b", |
||||
"stateRoot" => "0x79345c692a0bf363e95c37750336c534309b3f3fe8b59712ac1527118070f488", |
||||
"timestamp" => "0x5b475377", |
||||
"totalDifficulty" => "0x120258e22c69502fc88", |
||||
"transactions" => ["0xa4b58d1d1473f4891d9ff91f624dba73611bf1f6e9a60d3ca2dcfc75d2ab185c"], |
||||
"transactionsRoot" => "0x5972b7988f667d7e86679322641117e503ea2c1bc5a27822a8a8120fe53f2c8b", |
||||
"uncles" => [] |
||||
}} |
||||
|
||||
[%{method: "eth_getBlockByNumber", params: [_, true]} | _] = requests, _options -> |
||||
{:ok, |
||||
Enum.map(requests, fn %{id: id, params: [block_quantity, true]} -> |
||||
%{ |
||||
id: id, |
||||
jsonrpc: "2.0", |
||||
result: %{ |
||||
"difficulty" => "0xc22479024e55f", |
||||
"extraData" => "0x73656f3130", |
||||
"gasLimit" => "0x7a121d", |
||||
"gasUsed" => "0x7a0527", |
||||
"hash" => |
||||
Explorer.Factory.block_hash() |
||||
|> to_string(), |
||||
"logsBloom" => |
||||
"0x006a044c050a6759208088200009808898246808402123144ac15801c09a2672990130000042500000cc6090b063f195352095a88018194112101a02640000a0109c03c40568440b853a800a60044408604bb49d1d604c802008000884520208496608a520992e0f4b41a94188088920c1995107db4696c03839a911500084001009884100605084c4542953b08101103080254c34c802a00042a62f811340400d22080d000c0e39927ca481800c8024048425462000150850500205a224810041904023a80c00dc01040203000086020111210403081096822008c12500a2060a54834800400851210122c481a04a24b5284e9900a08110c180011001c03100", |
||||
"miner" => "0xb2930b35844a230f00e51431acae96fe543a0347", |
||||
"mixHash" => "0x5e07a58028d2cee7ddbefe245e6d7b5232d997b66cc906b18ad9ad51535ced24", |
||||
"nonce" => "0x3d88ebe8031aadf6", |
||||
"number" => block_quantity, |
||||
"parentHash" => |
||||
Explorer.Factory.block_hash() |
||||
|> to_string(), |
||||
"receiptsRoot" => "0x5294a8b56be40c0c198aa443664e801bb926d49878f96151849f3ddd0cb5e76d", |
||||
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"size" => "0x4796", |
||||
"stateRoot" => "0x3755d4b5c9ae3cd58d7a856a46fbe8fb69f0ba93d81e831cd68feb8b61bc3009", |
||||
"timestamp" => "0x5b475393", |
||||
"totalDifficulty" => "0x120259a450e2527e1e7", |
||||
"transactions" => [], |
||||
"transactionsRoot" => "0xa71969ed649cd1f21846ab7b4029e79662941cc34cd473aa4590e666920ad2f4", |
||||
"uncles" => [] |
||||
} |
||||
} |
||||
end)} |
||||
|
||||
[%{method: "eth_getBalance"} | _] = requests, _options -> |
||||
{:ok, Enum.map(requests, fn %{id: id} -> %{id: id, jsonrpc: "2.0", result: "0x0"} end)} |
||||
end) |
||||
|
||||
variant_name -> |
||||
raise ArgumentError, "Unsupported variant name (#{variant_name})" |
||||
end |
||||
end |
||||
|
||||
{:ok, latest_block_number} = EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) |
||||
|
||||
default_blocks_batch_size = BlockFetcher.default_blocks_batch_size() |
||||
|
||||
assert latest_block_number > default_blocks_batch_size |
||||
|
||||
assert Repo.aggregate(Block, :count, :hash) == 0 |
||||
|
||||
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) |
||||
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
start_supervised!({BlockFetcher.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments], []]}) |
||||
|
||||
first_catchup_block_number = latest_block_number - 1 |
||||
|
||||
wait_for_results(fn -> |
||||
Repo.one!(from(block in Block, where: block.number == ^first_catchup_block_number)) |
||||
end) |
||||
|
||||
assert Repo.aggregate(Block, :count, :hash) >= 1 |
||||
|
||||
previous_batch_block_number = first_catchup_block_number - default_blocks_batch_size |
||||
|
||||
wait_for_results(fn -> |
||||
Repo.one!(from(block in Block, where: block.number == ^previous_batch_block_number)) |
||||
end) |
||||
|
||||
assert Repo.aggregate(Block, :count, :hash) >= default_blocks_batch_size |
||||
end |
||||
end |
||||
|
||||
describe "handle_info(:catchup_index, state)" do |
||||
setup context do |
||||
# force to use `Mox`, so we can manipulate `lastest_block_number` |
||||
put_in(context.json_rpc_named_arguments[:transport], EthereumJSONRPC.Mox) |
||||
end |
||||
|
||||
setup :state |
||||
|
||||
test "increases catchup_bound_interval if no blocks missing", %{ |
||||
json_rpc_named_arguments: json_rpc_named_arguments, |
||||
state: state |
||||
} do |
||||
insert(:block, number: 0) |
||||
insert(:block, number: 1) |
||||
|
||||
EthereumJSONRPC.Mox |
||||
|> expect(:json_rpc, fn %{method: "eth_getBlockByNumber", params: ["latest", false]}, _options -> |
||||
{:ok, %{"number" => "0x1"}} |
||||
end) |
||||
|
||||
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) |
||||
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
|
||||
# from `setup :state` |
||||
assert_received :catchup_index |
||||
|
||||
assert {:noreply, |
||||
%BlockFetcher.Supervisor{catchup: %Catchup{task: %Task{pid: pid, ref: ref}}} = catchup_index_state} = |
||||
BlockFetcher.Supervisor.handle_info(:catchup_index, state) |
||||
|
||||
assert_receive {^ref, %{first_block_number: 0, missing_block_count: 0}} = message |
||||
|
||||
# DOWN is not flushed |
||||
assert {:messages, [{:DOWN, ^ref, :process, ^pid, :normal}]} = Process.info(self(), :messages) |
||||
|
||||
assert {:noreply, message_state} = BlockFetcher.Supervisor.handle_info(message, catchup_index_state) |
||||
|
||||
# DOWN is flushed |
||||
assert {:messages, []} = Process.info(self(), :messages) |
||||
|
||||
assert message_state.catchup.bound_interval.current > catchup_index_state.catchup.bound_interval.current |
||||
end |
||||
|
||||
test "decreases catchup_bound_interval if blocks missing", %{ |
||||
json_rpc_named_arguments: json_rpc_named_arguments, |
||||
state: state |
||||
} do |
||||
EthereumJSONRPC.Mox |
||||
|> expect(:json_rpc, fn %{method: "eth_getBlockByNumber", params: ["latest", false]}, _options -> |
||||
{:ok, %{"number" => "0x1"}} |
||||
end) |
||||
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBlockByNumber", params: ["0x0", true]}], _options -> |
||||
{:ok, |
||||
[ |
||||
%{ |
||||
id: id, |
||||
jsonrpc: "2.0", |
||||
result: %{ |
||||
"difficulty" => "0x0", |
||||
"gasLimit" => "0x0", |
||||
"gasUsed" => "0x0", |
||||
"hash" => |
||||
Explorer.Factory.block_hash() |
||||
|> to_string(), |
||||
"miner" => "0xb2930b35844a230f00e51431acae96fe543a0347", |
||||
"number" => "0x0", |
||||
"parentHash" => |
||||
Explorer.Factory.block_hash() |
||||
|> to_string(), |
||||
"size" => "0x0", |
||||
"timestamp" => "0x0", |
||||
"totalDifficulty" => "0x0", |
||||
"transactions" => [] |
||||
} |
||||
} |
||||
]} |
||||
end) |
||||
|> stub(:json_rpc, fn [ |
||||
%{ |
||||
id: id, |
||||
method: "eth_getBalance", |
||||
params: ["0xb2930b35844a230f00e51431acae96fe543a0347", "0x0"] |
||||
} |
||||
], |
||||
_options -> |
||||
{:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]} |
||||
end) |
||||
|
||||
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) |
||||
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
|
||||
# from `setup :state` |
||||
assert_received :catchup_index |
||||
|
||||
assert {:noreply, |
||||
%BlockFetcher.Supervisor{catchup: %Catchup{task: %Task{pid: pid, ref: ref}}} = catchup_index_state} = |
||||
BlockFetcher.Supervisor.handle_info(:catchup_index, state) |
||||
|
||||
# 2 blocks are missing, but latest is assumed to be handled by realtime_index, so only 1 is missing for |
||||
# catchup_index |
||||
assert_receive {^ref, %{first_block_number: 0, missing_block_count: 1}} = message |
||||
|
||||
# DOWN is not flushed |
||||
assert {:messages, [{:DOWN, ^ref, :process, ^pid, :normal}]} = Process.info(self(), :messages) |
||||
|
||||
assert {:noreply, message_state} = BlockFetcher.Supervisor.handle_info(message, catchup_index_state) |
||||
|
||||
# DOWN is flushed |
||||
assert {:messages, []} = Process.info(self(), :messages) |
||||
|
||||
assert message_state.catchup.bound_interval.current == message_state.catchup.bound_interval.minimum |
||||
|
||||
# When not at minimum it is decreased |
||||
|
||||
above_minimum_state = update_in(catchup_index_state.catchup.bound_interval, &BoundInterval.increase/1) |
||||
|
||||
assert above_minimum_state.catchup.bound_interval.current > message_state.catchup.bound_interval.minimum |
||||
assert {:noreply, above_minimum_message_state} = BlockFetcher.Supervisor.handle_info(message, above_minimum_state) |
||||
|
||||
assert above_minimum_message_state.catchup.bound_interval.current < |
||||
above_minimum_state.catchup.bound_interval.current |
||||
end |
||||
end |
||||
|
||||
defp state(%{json_rpc_named_arguments: json_rpc_named_arguments}) do |
||||
{:ok, state} = BlockFetcher.Supervisor.init(json_rpc_named_arguments: json_rpc_named_arguments) |
||||
|
||||
%{state: state} |
||||
end |
||||
end |
Loading…
Reference in new issue