Merge pull request #1657 from poanetwork/gs-fix-consensus-loss

Force consensus loss for parent block if its hash mismatches parent_hash
pull/1663/head
Victor Baranov 6 years ago committed by GitHub
commit 54387983eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 50
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  3. 6
      apps/explorer/priv/repo/migrations/scripts/20190326202921_lose_consensus_for_invalid_blocks.sql
  4. 31
      apps/explorer/test/explorer/chain/import/runner/blocks_test.exs

@ -16,6 +16,7 @@
- [#1643](https://github.com/poanetwork/blockscout/pull/1643) - Set internal_transactions_indexed_at for empty blocks - [#1643](https://github.com/poanetwork/blockscout/pull/1643) - Set internal_transactions_indexed_at for empty blocks
- [#1647](https://github.com/poanetwork/blockscout/pull/1647) - Fix typo in view - [#1647](https://github.com/poanetwork/blockscout/pull/1647) - Fix typo in view
- [#1650](https://github.com/poanetwork/blockscout/pull/1650) - Add petersburg evm version to smart contract verifier - [#1650](https://github.com/poanetwork/blockscout/pull/1650) - Add petersburg evm version to smart contract verifier
- [#1657](https://github.com/poanetwork/blockscout/pull/1657) - Force consensus loss for parent block if its hash mismatches parent_hash
### Chore ### Chore

@ -46,6 +46,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
|> Map.put(:timestamps, timestamps) |> Map.put(:timestamps, timestamps)
ordered_consensus_block_numbers = ordered_consensus_block_numbers(changes_list) ordered_consensus_block_numbers = ordered_consensus_block_numbers(changes_list)
where_invalid_parent = where_invalid_parent(changes_list)
where_forked = where_forked(changes_list) where_forked = where_forked(changes_list)
multi multi
@ -69,6 +70,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
|> Multi.run(:lose_consensus, fn repo, _ -> |> Multi.run(:lose_consensus, fn repo, _ ->
lose_consensus(repo, ordered_consensus_block_numbers, insert_options) lose_consensus(repo, ordered_consensus_block_numbers, insert_options)
end) end)
|> Multi.run(:lose_invalid_parent_consensus, fn repo, _ ->
lose_invalid_parent_consensus(repo, where_invalid_parent, insert_options)
end)
|> Multi.run(:delete_address_token_balances, fn repo, _ -> |> Multi.run(:delete_address_token_balances, fn repo, _ ->
delete_address_token_balances(repo, ordered_consensus_block_numbers, insert_options) delete_address_token_balances(repo, ordered_consensus_block_numbers, insert_options)
end) end)
@ -312,6 +316,32 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
end end
end end
defp lose_invalid_parent_consensus(repo, where_invalid_parent, %{
timeout: timeout,
timestamps: %{updated_at: updated_at}
}) do
query =
from(
block in where_invalid_parent,
update: [
set: [
consensus: false,
updated_at: ^updated_at
]
],
select: [:hash, :number]
)
try do
{_, result} = repo.update_all(query, [], timeout: timeout)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, where_invalid_parent: where_invalid_parent}}
end
end
defp delete_address_token_balances(_, [], _), do: {:ok, []} defp delete_address_token_balances(_, [], _), do: {:ok, []}
defp delete_address_token_balances(repo, ordered_consensus_block_numbers, %{timeout: timeout}) do defp delete_address_token_balances(repo, ordered_consensus_block_numbers, %{timeout: timeout}) do
@ -543,12 +573,22 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
initial = from(t in Transaction, where: false) initial = from(t in Transaction, where: false)
Enum.reduce(blocks_changes, initial, fn %{consensus: consensus, hash: hash, number: number}, acc -> Enum.reduce(blocks_changes, initial, fn %{consensus: consensus, hash: hash, number: number}, acc ->
case consensus do if consensus do
false -> from(transaction in acc, or_where: transaction.block_hash != ^hash and transaction.block_number == ^number)
from(transaction in acc, or_where: transaction.block_hash == ^hash and transaction.block_number == ^number) else
from(transaction in acc, or_where: transaction.block_hash == ^hash and transaction.block_number == ^number)
end
end)
end
defp where_invalid_parent(blocks_changes) when is_list(blocks_changes) do
initial = from(b in Block, where: false)
true -> Enum.reduce(blocks_changes, initial, fn %{consensus: consensus, parent_hash: parent_hash, number: number}, acc ->
from(transaction in acc, or_where: transaction.block_hash != ^hash and transaction.block_number == ^number) if consensus do
from(block in acc, or_where: block.number == ^(number - 1) and block.hash != ^parent_hash)
else
acc
end end
end) end)
end end

@ -0,0 +1,6 @@
UPDATE blocks SET consensus = FALSE, updated_at = NOW()
WHERE consensus AND number IN (
SELECT b0.number - 1 FROM "blocks" AS b0
LEFT JOIN "blocks" AS b1 ON (b0."parent_hash" = b1."hash") AND b1."consensus"
WHERE b0."number" > 0 AND b0."consensus" AND b1."hash" IS NULL
);

@ -8,6 +8,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
alias Ecto.Multi alias Ecto.Multi
alias Explorer.Chain.Import.Runner.{Blocks, Transaction} alias Explorer.Chain.Import.Runner.{Blocks, Transaction}
alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain
alias Explorer.Repo alias Explorer.Repo
describe "run/1" do describe "run/1" do
@ -258,6 +259,36 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
blocks_update_token_holder_counts: [] blocks_update_token_holder_counts: []
}} = run_block_consensus_change(block, true, options) }} = run_block_consensus_change(block, true, options)
end end
# Regression test for https://github.com/poanetwork/blockscout/issues/1644
test "discards parent block if it isn't related to the current one because of reorg",
%{consensus_block: %Block{number: block_number, hash: block_hash, miner_hash: miner_hash}, options: options} do
old_block = insert(:block, parent_hash: block_hash, number: block_number + 1)
insert(:block, parent_hash: old_block.hash, number: old_block.number + 1)
new_block1 = params_for(:block, parent_hash: block_hash, number: block_number + 1, miner_hash: miner_hash)
new_block2 =
params_for(:block, parent_hash: new_block1.hash, number: new_block1.number + 1, miner_hash: miner_hash)
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block2)
changes_list = [block_changes]
Multi.new()
|> Blocks.run(changes_list, options)
|> Repo.transaction()
assert Chain.missing_block_number_ranges(block_number..new_block2.number) == [old_block.number..old_block.number]
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block1)
changes_list = [block_changes]
Multi.new()
|> Blocks.run(changes_list, options)
|> Repo.transaction()
assert Chain.missing_block_number_ranges(block_number..new_block2.number) == []
end
end end
defp count(schema) do defp count(schema) do

Loading…
Cancel
Save