commit
d3b57d6b8a
@ -0,0 +1,31 @@ |
|||||||
|
defmodule Indexer.Block.Transform do |
||||||
|
@moduledoc """ |
||||||
|
Protocol for transforming blocks. |
||||||
|
""" |
||||||
|
|
||||||
|
@type block :: map() |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Transforms a block. |
||||||
|
""" |
||||||
|
@callback transform(block :: block()) :: block() |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Runs a list of blocks through the configured block transformer. |
||||||
|
""" |
||||||
|
def transform_blocks(blocks) when is_list(blocks) do |
||||||
|
transformer = Application.get_env(:indexer, :block_transformer) |
||||||
|
|
||||||
|
unless transformer do |
||||||
|
raise ArgumentError, |
||||||
|
""" |
||||||
|
No block transformer defined. Set a blocker transformer." |
||||||
|
|
||||||
|
config :indexer, |
||||||
|
block_transformer: Indexer.Block.Transform.Base |
||||||
|
""" |
||||||
|
end |
||||||
|
|
||||||
|
Enum.map(blocks, &transformer.transform/1) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,14 @@ |
|||||||
|
defmodule Indexer.Block.Transform.Base do |
||||||
|
@moduledoc """ |
||||||
|
Default block transformer to be used. |
||||||
|
""" |
||||||
|
|
||||||
|
alias Indexer.Block.Transform |
||||||
|
|
||||||
|
@behaviour Transform |
||||||
|
|
||||||
|
@impl Transform |
||||||
|
def transform(block) when is_map(block) do |
||||||
|
block |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,16 @@ |
|||||||
|
defmodule Indexer.Block.Transform.Clique do |
||||||
|
@moduledoc """ |
||||||
|
Handles block transforms for Clique chain. |
||||||
|
""" |
||||||
|
|
||||||
|
alias Indexer.Block.{Transform, Util} |
||||||
|
|
||||||
|
@behaviour Transform |
||||||
|
|
||||||
|
@impl Transform |
||||||
|
def transform(block) when is_map(block) do |
||||||
|
miner_address = Util.signer(block) |
||||||
|
|
||||||
|
%{block | miner_hash: miner_address} |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,75 @@ |
|||||||
|
defmodule Indexer.Block.Util do |
||||||
|
@moduledoc """ |
||||||
|
Helper functions for parsing block information. |
||||||
|
""" |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Calculates the signer's address by recovering the ECDSA public key. |
||||||
|
|
||||||
|
https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm |
||||||
|
""" |
||||||
|
def signer(block) when is_map(block) do |
||||||
|
# Last 65 bytes is the signature. Multiply by two since we haven't transformed to raw bytes |
||||||
|
{extra_data, signature} = String.split_at(trim_prefix(block.extra_data), -130) |
||||||
|
|
||||||
|
block = %{block | extra_data: extra_data} |
||||||
|
|
||||||
|
signature_hash = signature_hash(block) |
||||||
|
|
||||||
|
recover_pub_key(signature_hash, decode(signature)) |
||||||
|
end |
||||||
|
|
||||||
|
# Signature hash calculated from the block header. |
||||||
|
# Needed for PoA-based chains |
||||||
|
defp signature_hash(block) do |
||||||
|
header_data = [ |
||||||
|
decode(block.parent_hash), |
||||||
|
decode(block.sha3_uncles), |
||||||
|
decode(block.miner_hash), |
||||||
|
decode(block.state_root), |
||||||
|
decode(block.transactions_root), |
||||||
|
decode(block.receipts_root), |
||||||
|
decode(block.logs_bloom), |
||||||
|
block.difficulty, |
||||||
|
block.number, |
||||||
|
block.gas_limit, |
||||||
|
block.gas_used, |
||||||
|
DateTime.to_unix(block.timestamp), |
||||||
|
decode(block.extra_data), |
||||||
|
decode(block.mix_hash), |
||||||
|
decode(block.nonce) |
||||||
|
] |
||||||
|
|
||||||
|
:keccakf1600.hash(:sha3_256, ExRLP.encode(header_data)) |
||||||
|
end |
||||||
|
|
||||||
|
defp trim_prefix("0x" <> rest), do: rest |
||||||
|
|
||||||
|
defp decode("0x" <> rest) do |
||||||
|
decode(rest) |
||||||
|
end |
||||||
|
|
||||||
|
defp decode(data) do |
||||||
|
Base.decode16!(data, case: :mixed) |
||||||
|
end |
||||||
|
|
||||||
|
# Recovers the key from the signature hash and signature |
||||||
|
defp recover_pub_key(signature_hash, signature) do |
||||||
|
<< |
||||||
|
r::bytes-size(32), |
||||||
|
s::bytes-size(32), |
||||||
|
v::integer-size(8) |
||||||
|
>> = signature |
||||||
|
|
||||||
|
# First byte represents compression which can be ignored |
||||||
|
# Private key is the last 64 bytes |
||||||
|
{:ok, <<_compression::bytes-size(1), private_key::binary>>} = |
||||||
|
:libsecp256k1.ecdsa_recover_compact(signature_hash, r <> s, :uncompressed, v) |
||||||
|
|
||||||
|
# Public key comes from the last 20 bytes |
||||||
|
<<_::bytes-size(12), public_key::binary>> = :keccakf1600.hash(:sha3_256, private_key) |
||||||
|
|
||||||
|
miner_address = Base.encode16(public_key, case: :lower) |
||||||
|
"0x" <> miner_address |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,42 @@ |
|||||||
|
defmodule Indexer.Block.Transform.BaseTest do |
||||||
|
use ExUnit.Case |
||||||
|
|
||||||
|
alias Indexer.Block.Transform.Base |
||||||
|
|
||||||
|
@block %{ |
||||||
|
difficulty: 1, |
||||||
|
extra_data: |
||||||
|
"0xd68301080d846765746886676f312e3130856c696e7578000000000000000000773ab2ca8f47904a14739ad80a75b71d9d29b9fff8b7ecdcb73efffa6f74122f17d304b5dc8e6e5f256c9474dd115c8d4dae31b7a3d409e5c3270f8fde41cd8c00", |
||||||
|
gas_limit: 7_753_377, |
||||||
|
gas_used: 1_810_195, |
||||||
|
hash: "0x7004c895e812c55b0c2be8a46d72ca300a683dc27d1d7917ee7742d4d0359c1f", |
||||||
|
logs_bloom: |
||||||
|
"0x00000000000000020000000000002000000400000000000000000000000000000000000000000000040000080004000020000010000000000000000000000000000000000000000008000008000000000000000000200000000000000000000000000000020000000000000000000800000000000000804000000010080000000800000000000000000000000000000000000000000000800000000000080000000008000400000000404000000000000000000000000200000000000000000000000002000000000000001002000000000000002000000008000000000020000000000000000000000000000000000000000000000000400000800000000000", |
||||||
|
miner: "0x0000000000000000000000000000000000000000", |
||||||
|
mix_hash: "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
nonce: "0x0000000000000000", |
||||||
|
number: 2_848_394, |
||||||
|
parent_hash: "0x20350fc367e19d3865be1ea7da72ab81f8f9941c43ac6bb24a34a0a7caa2f3df", |
||||||
|
receipts_root: "0x6ade4ac1079ea50cfadcce2b75ffbe4f9b14bf69b4607bbf1739463076ca6246", |
||||||
|
sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
size: 6437, |
||||||
|
state_root: "0x23f63347851bcd109059d007d71e19c4f5e73b7f0862bebcd04458333a004d92", |
||||||
|
timestamp: DateTime.from_unix!(1_534_796_040), |
||||||
|
total_difficulty: 5_353_647, |
||||||
|
transactions: [ |
||||||
|
"0x7e3bb851fc74a436826d2af6b96e4db9484431811ef0d9c9e78370488d33d4e5", |
||||||
|
"0x3976fd1e3d2a715c3cfcfde9bd3210798c26c017b8edb841d319227ecb3322fb", |
||||||
|
"0xd8db124005bb8b6fda7b71fd56ac782552a66af58fe843ba3c4930423b87d1d2", |
||||||
|
"0x10c1a1ca4d9f4b2bd5b89f7bbcbbc2d69e166fe23662b8db4f6beae0f50ac9fd", |
||||||
|
"0xaa58a6545677c796a56b8bc874174c8cfd31a6c6e6ca3a87e086d4f66d52858a" |
||||||
|
], |
||||||
|
transactions_root: "0xde8d25c0b9b54310128a21601331094b43f910f9f96102869c2e2dca94884bf4", |
||||||
|
uncles: [] |
||||||
|
} |
||||||
|
|
||||||
|
describe "transform/1" do |
||||||
|
test "passes the block through unchanged" do |
||||||
|
assert Base.transform(@block) == @block |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,43 @@ |
|||||||
|
defmodule Indexer.Block.Transform.CliqueTest do |
||||||
|
use ExUnit.Case |
||||||
|
|
||||||
|
alias Indexer.Block.Transform.Clique |
||||||
|
|
||||||
|
@block %{ |
||||||
|
difficulty: 1, |
||||||
|
extra_data: |
||||||
|
"0xd68301080d846765746886676f312e3130856c696e7578000000000000000000773ab2ca8f47904a14739ad80a75b71d9d29b9fff8b7ecdcb73efffa6f74122f17d304b5dc8e6e5f256c9474dd115c8d4dae31b7a3d409e5c3270f8fde41cd8c00", |
||||||
|
gas_limit: 7_753_377, |
||||||
|
gas_used: 1_810_195, |
||||||
|
hash: "0x7004c895e812c55b0c2be8a46d72ca300a683dc27d1d7917ee7742d4d0359c1f", |
||||||
|
logs_bloom: |
||||||
|
"0x00000000000000020000000000002000000400000000000000000000000000000000000000000000040000080004000020000010000000000000000000000000000000000000000008000008000000000000000000200000000000000000000000000000020000000000000000000800000000000000804000000010080000000800000000000000000000000000000000000000000000800000000000080000000008000400000000404000000000000000000000000200000000000000000000000002000000000000001002000000000000002000000008000000000020000000000000000000000000000000000000000000000000400000800000000000", |
||||||
|
miner_hash: "0x0000000000000000000000000000000000000000", |
||||||
|
mix_hash: "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
nonce: "0x0000000000000000", |
||||||
|
number: 2_848_394, |
||||||
|
parent_hash: "0x20350fc367e19d3865be1ea7da72ab81f8f9941c43ac6bb24a34a0a7caa2f3df", |
||||||
|
receipts_root: "0x6ade4ac1079ea50cfadcce2b75ffbe4f9b14bf69b4607bbf1739463076ca6246", |
||||||
|
sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
size: 6437, |
||||||
|
state_root: "0x23f63347851bcd109059d007d71e19c4f5e73b7f0862bebcd04458333a004d92", |
||||||
|
timestamp: DateTime.from_unix!(1_534_796_040), |
||||||
|
total_difficulty: 5_353_647, |
||||||
|
transactions: [ |
||||||
|
"0x7e3bb851fc74a436826d2af6b96e4db9484431811ef0d9c9e78370488d33d4e5", |
||||||
|
"0x3976fd1e3d2a715c3cfcfde9bd3210798c26c017b8edb841d319227ecb3322fb", |
||||||
|
"0xd8db124005bb8b6fda7b71fd56ac782552a66af58fe843ba3c4930423b87d1d2", |
||||||
|
"0x10c1a1ca4d9f4b2bd5b89f7bbcbbc2d69e166fe23662b8db4f6beae0f50ac9fd", |
||||||
|
"0xaa58a6545677c796a56b8bc874174c8cfd31a6c6e6ca3a87e086d4f66d52858a" |
||||||
|
], |
||||||
|
transactions_root: "0xde8d25c0b9b54310128a21601331094b43f910f9f96102869c2e2dca94884bf4", |
||||||
|
uncles: [] |
||||||
|
} |
||||||
|
|
||||||
|
describe "transform/1" do |
||||||
|
test "updates the miner hash with signer address" do |
||||||
|
expected = %{@block | miner_hash: "0xfc18cbc391de84dbd87db83b20935d3e89f5dd91"} |
||||||
|
assert Clique.transform(@block) == expected |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,56 @@ |
|||||||
|
defmodule Indexer.Block.TransformTest do |
||||||
|
use ExUnit.Case |
||||||
|
|
||||||
|
alias Indexer.Block.Transform |
||||||
|
|
||||||
|
@block %{ |
||||||
|
difficulty: 1, |
||||||
|
extra_data: |
||||||
|
"0xd68301080d846765746886676f312e3130856c696e7578000000000000000000773ab2ca8f47904a14739ad80a75b71d9d29b9fff8b7ecdcb73efffa6f74122f17d304b5dc8e6e5f256c9474dd115c8d4dae31b7a3d409e5c3270f8fde41cd8c00", |
||||||
|
gas_limit: 7_753_377, |
||||||
|
gas_used: 1_810_195, |
||||||
|
hash: "0x7004c895e812c55b0c2be8a46d72ca300a683dc27d1d7917ee7742d4d0359c1f", |
||||||
|
logs_bloom: |
||||||
|
"0x00000000000000020000000000002000000400000000000000000000000000000000000000000000040000080004000020000010000000000000000000000000000000000000000008000008000000000000000000200000000000000000000000000000020000000000000000000800000000000000804000000010080000000800000000000000000000000000000000000000000000800000000000080000000008000400000000404000000000000000000000000200000000000000000000000002000000000000001002000000000000002000000008000000000020000000000000000000000000000000000000000000000000400000800000000000", |
||||||
|
miner_hash: "0x0000000000000000000000000000000000000000", |
||||||
|
mix_hash: "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
nonce: "0x0000000000000000", |
||||||
|
number: 2_848_394, |
||||||
|
parent_hash: "0x20350fc367e19d3865be1ea7da72ab81f8f9941c43ac6bb24a34a0a7caa2f3df", |
||||||
|
receipts_root: "0x6ade4ac1079ea50cfadcce2b75ffbe4f9b14bf69b4607bbf1739463076ca6246", |
||||||
|
sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
size: 6437, |
||||||
|
state_root: "0x23f63347851bcd109059d007d71e19c4f5e73b7f0862bebcd04458333a004d92", |
||||||
|
timestamp: DateTime.from_unix!(1_534_796_040), |
||||||
|
total_difficulty: 5_353_647, |
||||||
|
transactions: [ |
||||||
|
"0x7e3bb851fc74a436826d2af6b96e4db9484431811ef0d9c9e78370488d33d4e5", |
||||||
|
"0x3976fd1e3d2a715c3cfcfde9bd3210798c26c017b8edb841d319227ecb3322fb", |
||||||
|
"0xd8db124005bb8b6fda7b71fd56ac782552a66af58fe843ba3c4930423b87d1d2", |
||||||
|
"0x10c1a1ca4d9f4b2bd5b89f7bbcbbc2d69e166fe23662b8db4f6beae0f50ac9fd", |
||||||
|
"0xaa58a6545677c796a56b8bc874174c8cfd31a6c6e6ca3a87e086d4f66d52858a" |
||||||
|
], |
||||||
|
transactions_root: "0xde8d25c0b9b54310128a21601331094b43f910f9f96102869c2e2dca94884bf4", |
||||||
|
uncles: [] |
||||||
|
} |
||||||
|
|
||||||
|
@blocks [@block, @block] |
||||||
|
|
||||||
|
describe "transform_blocks/1" do |
||||||
|
setup do |
||||||
|
original = Application.get_env(:indexer, :block_transformer) |
||||||
|
|
||||||
|
on_exit(fn -> Application.put_env(:indexer, :block_transformer, original) end) |
||||||
|
end |
||||||
|
|
||||||
|
test "transforms a list of blocks" do |
||||||
|
assert Transform.transform_blocks(@blocks) |
||||||
|
end |
||||||
|
|
||||||
|
test "raises when no transformer is configured" do |
||||||
|
Application.put_env(:indexer, :block_transformer, nil) |
||||||
|
|
||||||
|
assert_raise ArgumentError, fn -> Transform.transform_blocks(@blocks) end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,40 @@ |
|||||||
|
defmodule Indexer.Block.UtilTest do |
||||||
|
use ExUnit.Case |
||||||
|
|
||||||
|
alias Indexer.Block.Util |
||||||
|
|
||||||
|
test "signer/1" do |
||||||
|
data = %{ |
||||||
|
difficulty: 1, |
||||||
|
extra_data: |
||||||
|
"0xd68301080d846765746886676f312e3130856c696e7578000000000000000000773ab2ca8f47904a14739ad80a75b71d9d29b9fff8b7ecdcb73efffa6f74122f17d304b5dc8e6e5f256c9474dd115c8d4dae31b7a3d409e5c3270f8fde41cd8c00", |
||||||
|
gas_limit: 7_753_377, |
||||||
|
gas_used: 1_810_195, |
||||||
|
hash: "0x7004c895e812c55b0c2be8a46d72ca300a683dc27d1d7917ee7742d4d0359c1f", |
||||||
|
logs_bloom: |
||||||
|
"0x00000000000000020000000000002000000400000000000000000000000000000000000000000000040000080004000020000010000000000000000000000000000000000000000008000008000000000000000000200000000000000000000000000000020000000000000000000800000000000000804000000010080000000800000000000000000000000000000000000000000000800000000000080000000008000400000000404000000000000000000000000200000000000000000000000002000000000000001002000000000000002000000008000000000020000000000000000000000000000000000000000000000000400000800000000000", |
||||||
|
miner_hash: "0x0000000000000000000000000000000000000000", |
||||||
|
mix_hash: "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
nonce: "0x0000000000000000", |
||||||
|
number: 2_848_394, |
||||||
|
parent_hash: "0x20350fc367e19d3865be1ea7da72ab81f8f9941c43ac6bb24a34a0a7caa2f3df", |
||||||
|
receipts_root: "0x6ade4ac1079ea50cfadcce2b75ffbe4f9b14bf69b4607bbf1739463076ca6246", |
||||||
|
sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||||
|
size: 6437, |
||||||
|
state_root: "0x23f63347851bcd109059d007d71e19c4f5e73b7f0862bebcd04458333a004d92", |
||||||
|
timestamp: DateTime.from_unix!(1_534_796_040), |
||||||
|
total_difficulty: 5_353_647, |
||||||
|
transactions: [ |
||||||
|
"0x7e3bb851fc74a436826d2af6b96e4db9484431811ef0d9c9e78370488d33d4e5", |
||||||
|
"0x3976fd1e3d2a715c3cfcfde9bd3210798c26c017b8edb841d319227ecb3322fb", |
||||||
|
"0xd8db124005bb8b6fda7b71fd56ac782552a66af58fe843ba3c4930423b87d1d2", |
||||||
|
"0x10c1a1ca4d9f4b2bd5b89f7bbcbbc2d69e166fe23662b8db4f6beae0f50ac9fd", |
||||||
|
"0xaa58a6545677c796a56b8bc874174c8cfd31a6c6e6ca3a87e086d4f66d52858a" |
||||||
|
], |
||||||
|
transactions_root: "0xde8d25c0b9b54310128a21601331094b43f910f9f96102869c2e2dca94884bf4", |
||||||
|
uncles: [] |
||||||
|
} |
||||||
|
|
||||||
|
assert Util.signer(data) == "0xfc18cbc391de84dbd87db83b20935d3e89f5dd91" |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue