Separate schema and data migration for EIP-6

Fixes #1109

Use `NOT VALID` on constraint and don't put data updates in schema
migration.  Instead, migrate data using script based on @amandasposito's
scripts.
pull/1111/head
Luke Imhoff 6 years ago
parent d61a06605d
commit 39b6eccfda
  1. 19
      apps/explorer/lib/explorer/chain/internal_transaction/type.ex
  2. 31
      apps/explorer/priv/repo/migrations/20181107164103_eip6.exs
  3. 64
      apps/explorer/priv/repo/migrations/scripts/20181107164103_eip6.sql

@ -38,6 +38,13 @@ defmodule Explorer.Chain.InternalTransaction.Type do
iex> Explorer.Chain.InternalTransaction.Type.cast("selfdestruct") iex> Explorer.Chain.InternalTransaction.Type.cast("selfdestruct")
{:ok, :selfdestruct} {:ok, :selfdestruct}
Deprecated values are not allowed for incoming data.
iex> Explorer.Chain.InternalTransaction.Type.cast(:suicide)
:error
iex> Explorer.Chain.InternalTransaction.Type.cast("suicide")
:error
Unsupported `String.t` return an `:error`. Unsupported `String.t` return an `:error`.
iex> Explorer.Chain.InternalTransaction.Type.cast("hard-fork") iex> Explorer.Chain.InternalTransaction.Type.cast("hard-fork")
@ -65,6 +72,11 @@ defmodule Explorer.Chain.InternalTransaction.Type do
iex> Explorer.Chain.InternalTransaction.Type.dump(:selfdestruct) iex> Explorer.Chain.InternalTransaction.Type.dump(:selfdestruct)
{:ok, "selfdestruct"} {:ok, "selfdestruct"}
Deprecated values are not allowed to be dumped to the database as old values should only be read, not written.
iex> Explorer.Chain.InternalTransaction.Type.dump(:suicide)
:error
Other atoms return an error Other atoms return an error
iex> Explorer.Chain.InternalTransaction.Type.dump(:other) iex> Explorer.Chain.InternalTransaction.Type.dump(:other)
@ -91,6 +103,11 @@ defmodule Explorer.Chain.InternalTransaction.Type do
iex> Explorer.Chain.InternalTransaction.Type.load("selfdestruct") iex> Explorer.Chain.InternalTransaction.Type.load("selfdestruct")
{:ok, :selfdestruct} {:ok, :selfdestruct}
Converts deprecated value on load to the corresponding `t:t/0`.
iex> Explorer.Chain.InternalTransaction.Type.load("suicide")
{:ok, :selfdestruct}
Other `t:String.t/0` return `:error` Other `t:String.t/0` return `:error`
iex> Explorer.Chain.InternalTransaction.Type.load("other") iex> Explorer.Chain.InternalTransaction.Type.load("other")
@ -103,6 +120,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do
def load("create"), do: {:ok, :create} def load("create"), do: {:ok, :create}
def load("reward"), do: {:ok, :reward} def load("reward"), do: {:ok, :reward}
def load("selfdestruct"), do: {:ok, :selfdestruct} def load("selfdestruct"), do: {:ok, :selfdestruct}
# deprecated
def load("suicide"), do: {:ok, :selfdestruct}
def load(_), do: :error def load(_), do: :error
@doc """ @doc """

@ -1,28 +1,31 @@
defmodule Explorer.Repo.Migrations.EIP6 do defmodule Explorer.Repo.Migrations.EIP6 do
@moduledoc """
Use `priv/repo/migrations/scripts/20181107164103_eip6.sql` to migrate data and validate constraint.
```sh
mix ecto.migrate
psql -d $DATABASE -a -f priv/repo/migrations/scripts/20181107164103_eip6.sql
```
"""
use Ecto.Migration use Ecto.Migration
def up do def up do
execute("ALTER TABLE internal_transactions DROP CONSTRAINT suicide_has_from_and_to_address_hashes") execute("ALTER TABLE internal_transactions DROP CONSTRAINT suicide_has_from_and_to_address_hashes")
execute("UPDATE internal_transactions SET type = 'selfdestruct' WHERE type = 'suicide'") # `NOT VALID` skips checking pre-existing rows. Use `priv/repo/migrations/scripts/20181107164103_eip6.sql` to
# migrate data and validate constraints
create( execute("""
constraint( ALTER TABLE internal_transactions
:internal_transactions, ADD CONSTRAINT selfdestruct_has_from_and_to_address
:selfdestruct_has_from_and_to_address_hashes, CHECK (type != 'selfdestruct' OR (from_address_hash IS NOT NULL AND gas IS NULL AND to_address_hash IS NOT NULL))
check: """ NOT VALID
type != 'selfdestruct' OR """)
(from_address_hash IS NOT NULL AND gas IS NULL AND to_address_hash IS NOT NULL)
"""
)
)
end end
def down do def down do
execute("ALTER TABLE internal_transactions DROP CONSTRAINT selfdestruct_has_from_and_to_address_hashes") execute("ALTER TABLE internal_transactions DROP CONSTRAINT selfdestruct_has_from_and_to_address_hashes")
execute("UPDATE internal_transactions SET type = 'suicide' WHERE type = 'selfdestruct'")
create( create(
constraint( constraint(
:internal_transactions, :internal_transactions,

@ -0,0 +1,64 @@
DO $$
DECLARE
row_count integer := 1;
batch_size integer := 50000; -- HOW MANY ITEMS WILL BE UPDATED AT TIME
iterator integer := batch_size;
max_row_number integer;
next_iterator integer;
updated_row_count integer;
deleted_row_count integer;
BEGIN
DROP TABLE IF EXISTS current_suicide_internal_transactions_temp;
-- CREATES TEMP TABLE TO STORE DATA TO BE UPDATED
CREATE TEMP TABLE current_suicide_internal_transactions_temp(
transaction_hash bytea NOT NULL,
index bigint NOT NULL,
row_number integer
);
INSERT INTO current_suicide_internal_transactions_temp
SELECT DISTINCT ON (transaction_hash, index)
transaction_hash,
index,
ROW_NUMBER () OVER ()
FROM internal_transactions
WHERE type = 'suicide'
ORDER BY transaction_hash, index DESC;
max_row_number := (SELECT MAX(row_number) FROM current_suicide_internal_transactions_temp);
RAISE NOTICE '% items to be updated', max_row_number + 1;
-- ITERATES THROUGH THE ITEMS UNTIL THE TEMP TABLE IS EMPTY
WHILE iterator <= max_row_number LOOP
next_iterator := iterator + batch_size;
RAISE NOTICE '-> suicide internal transactions % to % to be updated', iterator, next_iterator - 1;
UPDATE internal_transactions
SET type = 'selfdestruct'
FROM current_suicide_internal_transactions_temp
WHERE internal_transactions.transaction_hash = current_suicide_internal_transactions_temp.transaction_hash AND
internal_transactions.index = current_suicide_internal_transactions_temp.index AND
current_suicide_internal_transactions_temp.row_number < next_iterator;
GET DIAGNOSTICS updated_row_count = ROW_COUNT;
RAISE NOTICE '-> % internal transactions updated from suicide to selfdesruct', updated_row_count;
DELETE FROM current_suicide_internal_transactions_temp
WHERE row_number < next_iterator;
GET DIAGNOSTICS deleted_row_count = ROW_COUNT;
ASSERT updated_row_count = deleted_row_count;
-- COMMITS THE BATCH UPDATES
CHECKPOINT;
-- UPDATES THE COUNTER SO IT DOESN'T TURN INTO AN INFINITE LOOP
iterator := next_iterator;
END LOOP;
RAISE NOTICE 'All suicide type internal transactions updated to selfdestruct. Validating constraint.';
ALTER TABLE internal_transactions VALIDATE CONSTRAINT selfdestruct_has_from_and_to_address;
END $$;
Loading…
Cancel
Save