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