From 6c77f369d9d95a8dc260c81172945f5e2887da46 Mon Sep 17 00:00:00 2001 From: Elizabeth Date: Wed, 10 Oct 2018 11:56:06 -0500 Subject: [PATCH] Vat heal (#56) * VatHeal Converter * Add VatHeal repository * Add VatHeal transformer * Add VatHeal to continuousLogSync command * Mark vat_init_checked as true when creating vat init records * Update urn and v converting * Return error if Repository.MarkCheckedHeader fails * Add deleting vat heal from test cleanup method --- cmd/continuousLogSync.go | 1 + .../1538671228_create_vat_heal.down.sql | 3 + .../1538671228_create_vat_heal.up.sql | 13 + .../1539112897_create_urn_view.down.sql | 4 + .../1539112897_create_urn_view.up.sql | 172 +++++++++++++ db/schema.sql | 67 ++++++ pkg/transformers/shared/constants.go | 2 + .../shared/event_signature_generator_test.go | 7 + pkg/transformers/shared/log_fetcher.go | 2 +- .../test_data/mocks/vat_heal/converter.go | 35 +++ .../test_data/mocks/vat_heal/repository.go | 65 +++++ pkg/transformers/test_data/vat_heal.go | 49 ++++ pkg/transformers/transformers.go | 3 + pkg/transformers/vat_heal/config.go | 25 ++ pkg/transformers/vat_heal/converter.go | 70 ++++++ pkg/transformers/vat_heal/converter_test.go | 53 +++++ pkg/transformers/vat_heal/model.go | 23 ++ pkg/transformers/vat_heal/repository.go | 84 +++++++ pkg/transformers/vat_heal/repository_test.go | 225 ++++++++++++++++++ pkg/transformers/vat_heal/transformer.go | 80 +++++++ pkg/transformers/vat_heal/transformer_test.go | 163 +++++++++++++ .../vat_heal/vat_heal_suite_test.go | 13 + pkg/transformers/vat_init/repository.go | 8 + pkg/transformers/vat_init/repository_test.go | 74 +++--- test_config/test_config.go | 1 + 25 files changed, 1195 insertions(+), 47 deletions(-) create mode 100644 db/migrations/1538671228_create_vat_heal.down.sql create mode 100644 db/migrations/1538671228_create_vat_heal.up.sql create mode 100644 db/migrations/1539112897_create_urn_view.down.sql create mode 100644 db/migrations/1539112897_create_urn_view.up.sql create mode 100644 pkg/transformers/test_data/mocks/vat_heal/converter.go create mode 100644 pkg/transformers/test_data/mocks/vat_heal/repository.go create mode 100644 pkg/transformers/test_data/vat_heal.go create mode 100644 pkg/transformers/vat_heal/config.go create mode 100644 pkg/transformers/vat_heal/converter.go create mode 100644 pkg/transformers/vat_heal/converter_test.go create mode 100644 pkg/transformers/vat_heal/model.go create mode 100644 pkg/transformers/vat_heal/repository.go create mode 100644 pkg/transformers/vat_heal/repository_test.go create mode 100644 pkg/transformers/vat_heal/transformer.go create mode 100644 pkg/transformers/vat_heal/transformer_test.go create mode 100644 pkg/transformers/vat_heal/vat_heal_suite_test.go diff --git a/cmd/continuousLogSync.go b/cmd/continuousLogSync.go index f89fdb9a..6b4a3138 100644 --- a/cmd/continuousLogSync.go +++ b/cmd/continuousLogSync.go @@ -106,6 +106,7 @@ func buildTransformerInitializerMap() map[string]shared2.TransformerInitializer transformerInitializerMap["tend"] = transformers.TendTransformerInitializer transformerInitializerMap["vatGrab"] = transformers.VatGrabTransformerInitializer transformerInitializerMap["vatInit"] = transformers.VatInitTransformerInitializer + transformerInitializerMap["vatHeal"] = transformers.VatHealTransformerInitializer transformerInitializerMap["vatFold"] = transformers.VatFoldTransformerInitializer transformerInitializerMap["vatToll"] = transformers.VatTollTransformerInitializer transformerInitializerMap["vatTune"] = transformers.VatTuneTransformerInitializer diff --git a/db/migrations/1538671228_create_vat_heal.down.sql b/db/migrations/1538671228_create_vat_heal.down.sql new file mode 100644 index 00000000..abb1fb32 --- /dev/null +++ b/db/migrations/1538671228_create_vat_heal.down.sql @@ -0,0 +1,3 @@ +DROP TABLE maker.vat_heal; +ALTER TABLE public.checked_headers + DROP COLUMN vat_heal_checked BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/db/migrations/1538671228_create_vat_heal.up.sql b/db/migrations/1538671228_create_vat_heal.up.sql new file mode 100644 index 00000000..e052c9c7 --- /dev/null +++ b/db/migrations/1538671228_create_vat_heal.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE maker.vat_heal ( + id SERIAL PRIMARY KEY, + header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, + urn varchar, + v varchar, + rad int, + tx_idx INTEGER NOT NULL, + raw_log JSONB, + UNIQUE (header_id, tx_idx) +); + +ALTER TABLE public.checked_headers + ADD COLUMN vat_heal_checked BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/db/migrations/1539112897_create_urn_view.down.sql b/db/migrations/1539112897_create_urn_view.down.sql new file mode 100644 index 00000000..66cc0eb9 --- /dev/null +++ b/db/migrations/1539112897_create_urn_view.down.sql @@ -0,0 +1,4 @@ +DROP TYPE ilkObject CASCADE; +DROP TYPE frobObject CASCADE; +DROP TYPE biteObject CASCADE; +DROP TYPE customUrn CASCADE; diff --git a/db/migrations/1539112897_create_urn_view.up.sql b/db/migrations/1539112897_create_urn_view.up.sql new file mode 100644 index 00000000..65662c82 --- /dev/null +++ b/db/migrations/1539112897_create_urn_view.up.sql @@ -0,0 +1,172 @@ +-- ALTER TABLE maker.bite +-- ALTER COLUMN ink SET DATA TYPE numeric USING ink::numeric, +-- ALTER COLUMN art SET DATA TYPE numeric USING art::numeric; +-- +-- CREATE VIEW maker.urn AS ( +-- WITH urn_state AS ( +-- SELECT +-- ilk, --text +-- urn, -- text +-- ink, -- numeric +-- art, -- numeric` +-- dink, -- numeric +-- dart, -- numeric +-- tx_idx +-- FROM maker.frob +-- UNION ALL +-- SELECT +-- ilk, -- text +-- urn, -- text +-- ink, -- varchar +-- art, -- varchar +-- 0 AS dink, +-- 0 AS dart, +-- tx_idx +-- FROM maker.bite +-- ORDER BY tx_idx DESC +-- ) +-- SELECT DISTINCT ON (urn, ilk) +-- urn_state.ilk, +-- -- us.ilk, --SELECT the ilk record FROM the ilk view +-- urn_state.art, +-- urn_state.ink, +-- urn_state.dink, +-- urn_state.dart, +-- urn_state.urn, +-- urn_state.tx_idx +-- -- urn_state.frobs, +-- -- urn_state.bites, +-- +-- FROM urn_state +-- -- ORDER BY id, block DESC, TIME DESC +-- ); +-- +-- ------------------------ +-- +-- +-- --creating ilk view, and records to populate urns and ilk +-- +-- create view maker.ilk as ( +-- select distinct on (id) +-- maker.vat_init.ilk as id, +-- maker.cat_file_chop_lump.data as chop, +-- maker.cat_file_chop_lump.data as lump, +-- maker.cat_file_flip.flip, +-- maker.drip_file_ilk.vow, +-- maker.drip_file_ilk.tax +-- from maker.vat_init +-- +-- join maker.cat_file_chop_lump on maker.cat_file_chop_lump.ilk = maker.vat_init.ilk +-- join maker.cat_file_flip on maker.cat_file_flip.ilk = maker.vat_init.ilk +-- join maker.drip_file_ilk on maker.drip_file_ilk.ilk = maker.vat_init.ilk +-- ); +-- +-- insert into headers (block_number) values(1); +-- insert into maker.cat_file_chop_lump (header_id, ilk, what, data, tx_idx) values(1, 'fake ilk', 'lump', 1, 0); +-- insert into maker.vat_init (header_id, ilk, tx_idx) values(1, 'fake ilk', 1); +-- insert into maker.vat_init (header_id, ilk, tx_idx) values(1, 'another fake ilk', 2); +-- insert into maker.frob (header_id, ilk, urn, dink, dart, ink, art, iart, tx_idx) values(1, 'fake ilk', 'urn1', 1, 2, 3, 4, 5, 2); +-- insert into maker.frob (header_id, ilk, urn, dink, dart, ink, art, iart, tx_idx) values(1, 'another fake ilk', 'urn1', 1, 2, 3, 4, 5, 3); +-- insert into maker.bite (header_id, ilk, urn, ink, art, iart, tab, flip, tx_idx) values(1, 'another fake ilk', 'urn1', 1, 2, 3, 4, '5', 4); +-- insert into maker.bite (header_id, ilk, urn, ink, art, iart, tab, flip, tx_idx) values(1, 'fake ilk', 'urn1', 1, 2, 3, 4, '5', 8); +-- +-- insert into maker.cat_file_chop_lump (header_id, ilk, what, data, tx_idx) values (1, 'fake ilk', 'lump', 1, 5); +-- insert into maker.cat_file_flip (header_id, ilk, flip, tx_idx) values (1, 'fake ilk', 3, 6); +-- insert into maker.drip_file_ilk (header_id, ilk, vow, tax, tx_idx) values (1, 'fake ilk', 5, 6, 7); +-- ----------------------------------- +-- CREATE TYPE ilkObject AS ( --this will need to change depending on what the actual Ilk view looks like +-- ilkId text, +-- chop numeric, +-- lump numeric, +-- tax numeric +-- ); +-- +-- CREATE OR REPLACE FUNCTION get_ilk () +-- RETURNS ilkObject AS +-- $$ +-- SELECT +-- id AS ilkId, +-- chop, +-- lump, +-- tax +-- FROM +-- maker.ilk +-- LIMIT 1; +-- $$ +-- LANGUAGE SQL STABLE; +-- +-- ----------------------------------- +-- CREATE TYPE frobObject AS ( +-- dink numeric +-- ); +-- +-- ----------------------------------- +-- CREATE OR REPLACE FUNCTION get_frobs() RETURNS SETOF frobObject AS $$ +-- SELECT +-- dink +-- FROM +-- maker.frob +-- $$ +-- LANGUAGE SQL STABLE; +-- +-- ----------------------------------- +-- CREATE TYPE biteObject AS ( +-- id integer, +-- header_id integer, +-- ilk text, +-- ink numeric +-- ); +-- +-- CREATE OR REPLACE FUNCTION get_bites() RETURNS SETOF biteObject AS $$ +-- SELECT +-- id, +-- header_id, +-- ilk, +-- ink +-- FROM +-- maker.bite +-- $$ +-- LANGUAGE SQL STABLE; +-- +-- -- ----------------------------------- +-- -- CREATE TYPE customUrn AS ( +-- -- ilkName text, +-- -- art numeric, +-- -- ink numeric, +-- -- urn text, +-- -- ilkObj ilkObject, +-- -- frobs frobObject, +-- -- bites biteObject +-- -- ); +-- -- +-- -- ----------------------------------- +-- -- CREATE FUNCTION get_urn () RETURNS customUrn AS $$ +-- -- DECLARE result customUrn; +-- -- BEGIN +-- -- WITH u AS ( +-- -- SELECT * FROM maker.urn +-- -- ), +-- -- i AS ( +-- -- SELECT * FROM get_ilk() LIMIT 1 +-- -- ), +-- -- f AS ( +-- -- SELECT * FROM get_frobs() +-- -- ), +-- -- b AS ( +-- -- SELECT * from get_bites() +-- -- ) +-- -- +-- -- SELECT +-- -- u.ilk::text AS ilkName, +-- -- u.art::numeric AS art, +-- -- u.ink::numeric AS ink, +-- -- u.urn::text AS urn, +-- -- i AS ilkObj, +-- -- f AS frobs, +-- -- b AS bites +-- -- FROM u, i, f, b +-- -- into result; +-- -- +-- -- RETURN result; +-- -- END; +-- -- $$ LANGUAGE plpgsql STABLE; \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index d5b08e38..fdd64650 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -770,6 +770,41 @@ CREATE SEQUENCE maker.vat_grab_id_seq ALTER SEQUENCE maker.vat_grab_id_seq OWNED BY maker.vat_grab.id; +-- +-- Name: vat_heal; Type: TABLE; Schema: maker; Owner: - +-- + +CREATE TABLE maker.vat_heal ( + id integer NOT NULL, + header_id integer NOT NULL, + urn character varying, + v character varying, + rad integer, + tx_idx integer NOT NULL, + raw_log jsonb +); + + +-- +-- Name: vat_heal_id_seq; Type: SEQUENCE; Schema: maker; Owner: - +-- + +CREATE SEQUENCE maker.vat_heal_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: vat_heal_id_seq; Type: SEQUENCE OWNED BY; Schema: maker; Owner: - +-- + +ALTER SEQUENCE maker.vat_heal_id_seq OWNED BY maker.vat_heal.id; + + -- -- Name: vat_init; Type: TABLE; Schema: maker; Owner: - -- @@ -978,6 +1013,7 @@ CREATE TABLE public.checked_headers ( pit_file_stability_fee_checked boolean DEFAULT false NOT NULL, vat_init_checked boolean DEFAULT false NOT NULL, vat_fold_checked boolean DEFAULT false NOT NULL, + vat_heal_checked boolean DEFAULT false NOT NULL, vat_toll_checked boolean DEFAULT false NOT NULL, vat_tune_checked boolean DEFAULT false NOT NULL, vat_grab_checked boolean DEFAULT false NOT NULL @@ -1441,6 +1477,13 @@ ALTER TABLE ONLY maker.vat_fold ALTER COLUMN id SET DEFAULT nextval('maker.vat_f ALTER TABLE ONLY maker.vat_grab ALTER COLUMN id SET DEFAULT nextval('maker.vat_grab_id_seq'::regclass); +-- +-- Name: vat_heal id; Type: DEFAULT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.vat_heal ALTER COLUMN id SET DEFAULT nextval('maker.vat_heal_id_seq'::regclass); + + -- -- Name: vat_init id; Type: DEFAULT; Schema: maker; Owner: - -- @@ -1852,6 +1895,22 @@ ALTER TABLE ONLY maker.vat_grab ADD CONSTRAINT vat_grab_pkey PRIMARY KEY (id); +-- +-- Name: vat_heal vat_heal_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.vat_heal + ADD CONSTRAINT vat_heal_header_id_tx_idx_key UNIQUE (header_id, tx_idx); + + +-- +-- Name: vat_heal vat_heal_pkey; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.vat_heal + ADD CONSTRAINT vat_heal_pkey PRIMARY KEY (id); + + -- -- Name: vat_init vat_init_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: - -- @@ -2214,6 +2273,14 @@ ALTER TABLE ONLY maker.vat_grab ADD CONSTRAINT vat_grab_header_id_fkey FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; +-- +-- Name: vat_heal vat_heal_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.vat_heal + ADD CONSTRAINT vat_heal_header_id_fkey FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; + + -- -- Name: vat_init vat_init_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - -- diff --git a/pkg/transformers/shared/constants.go b/pkg/transformers/shared/constants.go index c7647eb1..82b82834 100644 --- a/pkg/transformers/shared/constants.go +++ b/pkg/transformers/shared/constants.go @@ -55,6 +55,7 @@ var ( pitFileIlkMethod = "file(bytes32,bytes32,uint256)" pitFileStabilityFeeMethod = GetSolidityMethodSignature(PitABI, "file") tendMethod = GetSolidityMethodSignature(FlipperABI, "tend") + vatHealMethod = GetSolidityMethodSignature(VatABI, "heal") vatGrabMethod = GetSolidityMethodSignature(VatABI, "grab") vatInitMethod = GetSolidityMethodSignature(VatABI, "init") vatFoldMethod = GetSolidityMethodSignature(VatABI, "fold") @@ -79,6 +80,7 @@ var ( PitFileIlkSignature = GetLogNoteSignature(pitFileIlkMethod) PitFileStabilityFeeSignature = GetLogNoteSignature(pitFileStabilityFeeMethod) TendFunctionSignature = GetLogNoteSignature(tendMethod) + VatHealSignature = GetLogNoteSignature(vatHealMethod) VatGrabSignature = GetLogNoteSignature(vatGrabMethod) VatInitSignature = GetLogNoteSignature(vatInitMethod) VatFoldSignature = GetLogNoteSignature(vatFoldMethod) diff --git a/pkg/transformers/shared/event_signature_generator_test.go b/pkg/transformers/shared/event_signature_generator_test.go index 0ccec73d..c040ebdf 100644 --- a/pkg/transformers/shared/event_signature_generator_test.go +++ b/pkg/transformers/shared/event_signature_generator_test.go @@ -119,6 +119,13 @@ var _ = Describe("Event signature generator", func() { Expect(expected).To(Equal(actual)) }) + It("gets the vat heal method signature", func() { + expected := "heal(bytes32,bytes32,int256)" + actual := shared.GetSolidityMethodSignature(shared.VatABI, "heal") + + Expect(expected).To(Equal(actual)) + }) + It("gets the vat init method signature", func() { expected := "fold(bytes32,bytes32,int256)" actual := shared.GetSolidityMethodSignature(shared.VatABI, "fold") diff --git a/pkg/transformers/shared/log_fetcher.go b/pkg/transformers/shared/log_fetcher.go index a42a18ff..c1fc3015 100644 --- a/pkg/transformers/shared/log_fetcher.go +++ b/pkg/transformers/shared/log_fetcher.go @@ -58,4 +58,4 @@ func hexStringsToAddresses(hexStrings []string) []common.Address { } return addresses -} +} \ No newline at end of file diff --git a/pkg/transformers/test_data/mocks/vat_heal/converter.go b/pkg/transformers/test_data/mocks/vat_heal/converter.go new file mode 100644 index 00000000..387832ea --- /dev/null +++ b/pkg/transformers/test_data/mocks/vat_heal/converter.go @@ -0,0 +1,35 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal + +import ( + "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_heal" +) + +type MockVatHealConverter struct { + converterErr error + PassedLogs []types.Log +} + +func (converter *MockVatHealConverter) ToModels(ethLogs []types.Log) ([]vat_heal.VatHealModel, error) { + converter.PassedLogs = ethLogs + return []vat_heal.VatHealModel{test_data.VatHealModel}, converter.converterErr +} + +func (converter *MockVatHealConverter) SetConverterError(e error) { + converter.converterErr = e +} diff --git a/pkg/transformers/test_data/mocks/vat_heal/repository.go b/pkg/transformers/test_data/mocks/vat_heal/repository.go new file mode 100644 index 00000000..e29afd36 --- /dev/null +++ b/pkg/transformers/test_data/mocks/vat_heal/repository.go @@ -0,0 +1,65 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_heal" +) + +type MockVatHealRepository struct { + createErr error + markHeaderCheckedErr error + MarkHeaderCheckedPassedHeaderID int64 + missingHeaders []core.Header + missingHeadersErr error + PassedStartingBlockNumber int64 + PassedEndingBlockNumber int64 + PassedHeaderID int64 + PassedModels []vat_heal.VatHealModel +} + +func (repository *MockVatHealRepository) MarkCheckedHeader(headerId int64) error { + repository.MarkHeaderCheckedPassedHeaderID = headerId + return repository.markHeaderCheckedErr +} + +func (repository *MockVatHealRepository) Create(headerID int64, models []vat_heal.VatHealModel) error { + repository.PassedHeaderID = headerID + repository.PassedModels = models + return repository.createErr +} + +func (repository *MockVatHealRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) { + repository.PassedStartingBlockNumber = startingBlockNumber + repository.PassedEndingBlockNumber = endingBlockNumber + return repository.missingHeaders, repository.missingHeadersErr +} + +func (repository *MockVatHealRepository) SetMarkHeaderCheckedErr(e error) { + repository.markHeaderCheckedErr = e +} + +func (repository *MockVatHealRepository) SetMissingHeadersErr(e error) { + repository.missingHeadersErr = e +} + +func (repository *MockVatHealRepository) SetMissingHeaders(headers []core.Header) { + repository.missingHeaders = headers +} + +func (repository *MockVatHealRepository) SetCreateError(e error) { + repository.createErr = e +} diff --git a/pkg/transformers/test_data/vat_heal.go b/pkg/transformers/test_data/vat_heal.go new file mode 100644 index 00000000..871be81a --- /dev/null +++ b/pkg/transformers/test_data/vat_heal.go @@ -0,0 +1,49 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test_data + +import ( + "encoding/json" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_heal" +) + +var VatHealLog = types.Log{ + Address: common.HexToAddress("0xa970ed54e41d9db6d91db5e7ff7a9451dad98993"), + Topics: []common.Hash{ + common.HexToHash("0x990a5f6300000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0x7d7bee5fcfd8028cf7b00876c5b1421c800561a6000000000000000000000000"), + common.HexToHash("0x7340e006f4135ba6970d43bf43d88dcad4e7a8ca000000000000000000000000"), + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000078"), + }, + Data: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000064990a5f637d7bee5fcfd8028cf7b00876c5b1421c800561a600000000000000000000000074686520762076616c75650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078"), + BlockNumber: 10, + TxHash: common.HexToHash("0x991b8079b1333024000dcaf2b00c24c5db0315e112a4ac4d912aa96a602e12b9"), + TxIndex: 2, + BlockHash: common.HexToHash("0x0c54bdadb149691a103cfa5ad57329bda1e0fe55f1ba1ce5dd49dc1ecfe03daa"), + Index: 0, + Removed: false, +} + +var rawHealLog, _ = json.Marshal(VatHealLog) +var VatHealModel = vat_heal.VatHealModel{ + Urn: "0x7d7bEe5fCfD8028cf7b00876C5b1421c800561A6", + V: "0x7340e006f4135BA6970D43bf43d88DCAD4e7a8CA", + Rad: 120, + TransactionIndex: 2, + Raw: rawHealLog, +} diff --git a/pkg/transformers/transformers.go b/pkg/transformers/transformers.go index de007146..d8a2058c 100644 --- a/pkg/transformers/transformers.go +++ b/pkg/transformers/transformers.go @@ -39,6 +39,7 @@ import ( "github.com/vulcanize/vulcanizedb/pkg/transformers/tend" "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_fold" "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_grab" + "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_heal" "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_init" "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_toll" "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_tune" @@ -68,6 +69,7 @@ var ( TendTransformerInitializer = tend.TendTransformerInitializer{Config: tend.TendConfig}.NewTendTransformer VatGrabTransformerInitializer = vat_grab.VatGrabTransformerInitializer{Config: vat_grab.VatGrabConfig}.NewVatGrabTransformer VatInitTransformerInitializer = vat_init.VatInitTransformerInitializer{Config: vat_init.VatInitConfig}.NewVatInitTransformer + VatHealTransformerInitializer = vat_heal.VatHealTransformerInitializer{Config: vat_heal.VatHealConfig}.NewVatHealTransformer VatFoldTransformerInitializer = vat_fold.VatFoldTransformerInitializer{Config: vat_fold.VatFoldConfig}.NewVatFoldTransformer VatTollTransformerInitializer = vat_toll.VatTollTransformerInitializer{Config: vat_toll.VatTollConfig}.NewVatTollTransformer VatTuneTransformerInitializer = vat_tune.VatTuneTransformerInitializer{Config: vat_tune.VatTuneConfig}.NewVatTuneTransformer @@ -95,6 +97,7 @@ func TransformerInitializers() []shared.TransformerInitializer { TendTransformerInitializer, VatGrabTransformerInitializer, VatInitTransformerInitializer, + VatHealTransformerInitializer, VatFoldTransformerInitializer, VatTollTransformerInitializer, VatTuneTransformerInitializer, diff --git a/pkg/transformers/vat_heal/config.go b/pkg/transformers/vat_heal/config.go new file mode 100644 index 00000000..1cc28524 --- /dev/null +++ b/pkg/transformers/vat_heal/config.go @@ -0,0 +1,25 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal + +import "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + +var VatHealConfig = shared.TransformerConfig{ + ContractAddresses: []string{shared.VatContractAddress}, + ContractAbi: shared.VatABI, + Topics: []string{shared.VatHealSignature}, + StartingBlockNumber: 0, + EndingBlockNumber: 10000000, +} diff --git a/pkg/transformers/vat_heal/converter.go b/pkg/transformers/vat_heal/converter.go new file mode 100644 index 00000000..eda40eac --- /dev/null +++ b/pkg/transformers/vat_heal/converter.go @@ -0,0 +1,70 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal + +import ( + "encoding/json" + "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "strconv" +) + +type Converter interface { + ToModels(ethLogs []types.Log) ([]VatHealModel, error) +} + +type VatHealConverter struct{} + +func (VatHealConverter) ToModels(ethLogs []types.Log) ([]VatHealModel, error) { + var models []VatHealModel + for _, ethLog := range ethLogs { + err := verifyLog(ethLog) + if err != nil { + return nil, err + } + + urn := common.BytesToAddress(ethLog.Topics[1].Bytes()[:common.AddressLength]) + v := common.BytesToAddress(ethLog.Topics[2].Bytes()[:common.AddressLength]) + radInt, err := strconv.ParseInt(ethLog.Topics[3].Hex(), 0, 64) + if err != nil { + return nil, err + } + + rawLogJson, err := json.Marshal(ethLog) + if err != nil { + return nil, err + } + + model := VatHealModel{ + Urn: urn.String(), + V: v.String(), + Rad: int(radInt), + TransactionIndex: ethLog.TxIndex, + Raw: rawLogJson, + } + + models = append(models, model) + } + + return models, nil +} + +func verifyLog(log types.Log) error { + if len(log.Topics) < 4 { + return errors.New("log missing topics") + } + return nil +} diff --git a/pkg/transformers/vat_heal/converter_test.go b/pkg/transformers/vat_heal/converter_test.go new file mode 100644 index 00000000..a32579ea --- /dev/null +++ b/pkg/transformers/vat_heal/converter_test.go @@ -0,0 +1,53 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal_test + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_heal" +) + +var _ = Describe("VatHeal converter", func() { + It("Converts logs to models", func() { + converter := vat_heal.VatHealConverter{} + models, err := converter.ToModels([]types.Log{test_data.VatHealLog}) + + Expect(err).NotTo(HaveOccurred()) + Expect(models[0].Urn).To(Equal(test_data.VatHealModel.Urn)) + Expect(models[0].V).To(Equal(test_data.VatHealModel.V)) + Expect(models[0].Rad).To(Equal(test_data.VatHealModel.Rad)) + Expect(models[0].TransactionIndex).To(Equal(test_data.VatHealModel.TransactionIndex)) + Expect(models[0].Raw).To(Equal(test_data.VatHealModel.Raw)) + }) + + It("Returns an error there are missing topics", func() { + converter := vat_heal.VatHealConverter{} + badLog := types.Log{ + Topics: []common.Hash{ + common.HexToHash("0x"), + common.HexToHash("0x"), + common.HexToHash("0x"), + }, + } + _, err := converter.ToModels([]types.Log{badLog}) + + Expect(err).To(HaveOccurred()) + }) +}) diff --git a/pkg/transformers/vat_heal/model.go b/pkg/transformers/vat_heal/model.go new file mode 100644 index 00000000..304a7224 --- /dev/null +++ b/pkg/transformers/vat_heal/model.go @@ -0,0 +1,23 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal + +type VatHealModel struct { + Urn string + V string + Rad int + TransactionIndex uint `db:"tx_idx"` + Raw []byte `db:"raw_log"` +} diff --git a/pkg/transformers/vat_heal/repository.go b/pkg/transformers/vat_heal/repository.go new file mode 100644 index 00000000..55e862d9 --- /dev/null +++ b/pkg/transformers/vat_heal/repository.go @@ -0,0 +1,84 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +type Repository interface { + Create(headerId int64, models []VatHealModel) error + MissingHeaders(startingBlock, endingBlock int64) ([]core.Header, error) + MarkCheckedHeader(headerId int64) error +} + +type VatHealRepository struct { + DB *postgres.DB +} + +func NewVatHealRepository(db *postgres.DB) VatHealRepository { + return VatHealRepository{DB: db} +} + +func (repository VatHealRepository) Create(headerId int64, models []VatHealModel) error { + tx, err := repository.DB.Begin() + if err != nil { + return err + } + + for _, model := range models { + _, err := tx.Exec(`INSERT INTO maker.vat_heal (header_id, urn, v, rad, tx_idx, raw_log) + VALUES($1, $2, $3, $4, $5, $6)`, + headerId, model.Urn, model.V, model.Rad, model.TransactionIndex, model.Raw) + if err != nil { + tx.Rollback() + return err + } + } + + _, err = tx.Exec(`INSERT INTO public.checked_headers (header_id, vat_heal_checked) + VALUES($1, $2) + ON CONFLICT (header_id) DO + UPDATE SET vat_heal_checked = $2`, headerId, true) + if err != nil { + tx.Rollback() + return err + } + return tx.Commit() +} + +func (repository VatHealRepository) MissingHeaders(startingBlock, endingBlock int64) ([]core.Header, error) { + var headers []core.Header + err := repository.DB.Select(&headers, + `SELECT headers.id, block_number from headers + LEFT JOIN checked_headers on headers.id = header_id + WHERE (header_id ISNULL OR vat_heal_checked IS FALSE) + AND headers.block_number >= $1 + AND headers.block_number <= $2 + AND headers.eth_node_fingerprint = $3`, + startingBlock, endingBlock, repository.DB.Node.ID) + + return headers, err +} + +func (repository VatHealRepository) MarkCheckedHeader(headerId int64) error { + _, err := repository.DB.Exec(`INSERT INTO public.checked_headers (header_id, vat_heal_checked) + VALUES($1, $2) + ON CONFLICT (header_id) DO + UPDATE SET vat_heal_checked = $2`, headerId, true) + + return err +} diff --git a/pkg/transformers/vat_heal/repository_test.go b/pkg/transformers/vat_heal/repository_test.go new file mode 100644 index 00000000..4d05df2c --- /dev/null +++ b/pkg/transformers/vat_heal/repository_test.go @@ -0,0 +1,225 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_heal" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("VatHeal Repository", func() { + var db *postgres.DB + var repository vat_heal.VatHealRepository + var headerRepository repositories.HeaderRepository + var headerId int64 + var err error + + BeforeEach(func() { + node := test_config.NewTestNode() + db = test_config.NewTestDB(node) + test_config.CleanTestDB(db) + repository = vat_heal.VatHealRepository{DB: db} + headerRepository = repositories.NewHeaderRepository(db) + headerId, err = headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + }) + + type VatHealDBResult struct { + vat_heal.VatHealModel + Id int + HeaderId int64 `db:"header_id"` + } + + type CheckedHeaderResult struct { + VatHealChecked bool `db:"vat_heal_checked"` + } + + Describe("Create", func() { + It("persists vat heal records", func() { + anotherVatHeal := test_data.VatHealModel + anotherVatHeal.TransactionIndex = test_data.VatHealModel.TransactionIndex + 1 + err = repository.Create(headerId, []vat_heal.VatHealModel{test_data.VatHealModel, anotherVatHeal}) + + var dbResult []VatHealDBResult + err = db.Select(&dbResult, `SELECT * from maker.vat_heal where header_id = $1`, headerId) + Expect(err).NotTo(HaveOccurred()) + Expect(len(dbResult)).To(Equal(2)) + Expect(dbResult[0].Urn).To(Equal(test_data.VatHealModel.Urn)) + Expect(dbResult[0].V).To(Equal(test_data.VatHealModel.V)) + Expect(dbResult[0].Rad).To(Equal(test_data.VatHealModel.Rad)) + Expect(dbResult[0].TransactionIndex).To(Equal(test_data.VatHealModel.TransactionIndex)) + Expect(dbResult[1].TransactionIndex).To(Equal(test_data.VatHealModel.TransactionIndex + 1)) + Expect(dbResult[0].Raw).To(MatchJSON(test_data.VatHealModel.Raw)) + Expect(dbResult[0].HeaderId).To(Equal(headerId)) + }) + + It("returns an error if the insertion fails", func() { + err = repository.Create(headerId, []vat_heal.VatHealModel{test_data.VatHealModel}) + Expect(err).NotTo(HaveOccurred()) + err = repository.Create(headerId, []vat_heal.VatHealModel{test_data.VatHealModel}) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("pq: duplicate key value violates unique constraint")) + }) + + It("marks the header as checked for vat heal logs", func() { + err = repository.Create(headerId, []vat_heal.VatHealModel{test_data.VatHealModel}) + Expect(err).NotTo(HaveOccurred()) + + var headerChecked bool + err = db.Get(&headerChecked, `SELECT vat_heal_checked FROM public.checked_headers WHERE header_id = $1`, headerId) + Expect(err).NotTo(HaveOccurred()) + Expect(headerChecked).To(BeTrue()) + }) + + It("removes vat heal if corresponding header is deleted", func() { + err = repository.Create(headerId, []vat_heal.VatHealModel{test_data.VatHealModel}) + Expect(err).NotTo(HaveOccurred()) + + _, err = db.Exec(`DELETE FROM headers WHERE id = $1`, headerId) + + Expect(err).NotTo(HaveOccurred()) + var count int + err = db.QueryRow(`SELECT count(*) from maker.vat_heal`).Scan(&count) + Expect(err).NotTo(HaveOccurred()) + Expect(count).To(Equal(0)) + }) + + It("wraps create in a transaction", func() { + err = repository.Create(headerId, []vat_heal.VatHealModel{test_data.VatHealModel, test_data.VatHealModel}) + Expect(err).To(HaveOccurred()) + var count int + err = repository.DB.QueryRowx(`SELECT count(*) FROM maker.vat_heal`).Scan(&count) + Expect(count).To(Equal(0)) + }) + }) + + Describe("MissingHeaders", func() { + It("returns headers that haven't been checked", func() { + startingBlock := GinkgoRandomSeed() + vatHealBlock := startingBlock + 1 + endingBlock := startingBlock + 2 + outsideRangeBlock := startingBlock + 3 + + var headerIds []int64 + blockNumbers := []int64{startingBlock, vatHealBlock, endingBlock, outsideRangeBlock} + for _, n := range blockNumbers { + headerId, err := headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: n}) + Expect(err).NotTo(HaveOccurred()) + headerIds = append(headerIds, headerId) + } + + err = repository.MarkCheckedHeader(headerIds[0]) + Expect(err).NotTo(HaveOccurred()) + + headers, err := repository.MissingHeaders(startingBlock, endingBlock) + Expect(err).NotTo(HaveOccurred()) + Expect(headers[0].Id).To(Or(Equal(headerIds[1]), Equal(headerIds[2]))) + Expect(headers[1].Id).To(Or(Equal(headerIds[1]), Equal(headerIds[2]))) + Expect(len(headers)).To(Equal(2)) + }) + + It("returns header ids when checked_headers.vat_heal is false", func() { + startingBlock := GinkgoRandomSeed() + vatHealBlock := startingBlock + 1 + endingBlock := startingBlock + 2 + outsideRangeBlock := startingBlock + 3 + + var headerIds []int64 + blockNumbers := []int64{startingBlock, vatHealBlock, endingBlock, outsideRangeBlock} + for _, n := range blockNumbers { + headerId, err := headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: n}) + Expect(err).NotTo(HaveOccurred()) + headerIds = append(headerIds, headerId) + } + + err = repository.MarkCheckedHeader(headerIds[0]) + _, err = repository.DB.Exec(`INSERT INTO checked_headers (header_id) VALUES ($1)`, headerIds[1]) + Expect(err).NotTo(HaveOccurred()) + + headers, err := repository.MissingHeaders(startingBlock, endingBlock) + Expect(err).NotTo(HaveOccurred()) + Expect(headers[0].Id).To(Or(Equal(headerIds[1]), Equal(headerIds[2]))) + Expect(headers[1].Id).To(Or(Equal(headerIds[1]), Equal(headerIds[2]))) + Expect(len(headers)).To(Equal(2)) + }) + + It("only returns header ids for the current node", func() { + startingBlock := GinkgoRandomSeed() + vatHealBlock := startingBlock + 1 + endingBlock := startingBlock + 2 + outsideRangeBlock := startingBlock + 3 + db2 := test_config.NewTestDB(core.Node{ID: "second node"}) + headerRepository2 := repositories.NewHeaderRepository(db2) + repository2 := vat_heal.NewVatHealRepository(db2) + + var headerIds []int64 + blockNumbers := []int64{startingBlock, vatHealBlock, endingBlock, outsideRangeBlock} + for _, n := range blockNumbers { + headerId, err := headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: n}) + Expect(err).NotTo(HaveOccurred()) + headerIds = append(headerIds, headerId) + + _, err = headerRepository2.CreateOrUpdateHeader(core.Header{BlockNumber: n}) + Expect(err).NotTo(HaveOccurred()) + } + + err = repository.MarkCheckedHeader(headerIds[0]) + Expect(err).NotTo(HaveOccurred()) + + nodeOneMissingHeaders, err := repository.MissingHeaders(startingBlock, endingBlock) + Expect(err).NotTo(HaveOccurred()) + Expect(len(nodeOneMissingHeaders)).To(Equal(2)) + + nodeTwoMissingHeaders, err := repository2.MissingHeaders(startingBlock, endingBlock) + Expect(err).NotTo(HaveOccurred()) + Expect(len(nodeTwoMissingHeaders)).To(Equal(3)) + }) + }) + + Describe("MarkCheckedHeader", func() { + It("creates a new checked_header record", func() { + err := repository.MarkCheckedHeader(headerId) + Expect(err).NotTo(HaveOccurred()) + + var checkedHeaderResult = CheckedHeaderResult{} + err = db.Get(&checkedHeaderResult, `SELECT vat_heal_checked FROM checked_headers WHERE header_id = $1`, headerId) + Expect(err).NotTo(HaveOccurred()) + Expect(checkedHeaderResult.VatHealChecked).To(BeTrue()) + }) + + It("updates an existing checked header", func() { + _, err := repository.DB.Exec(`INSERT INTO checked_headers (header_id) VALUES($1)`, headerId) + Expect(err).NotTo(HaveOccurred()) + + var checkedHeaderResult CheckedHeaderResult + err = db.Get(&checkedHeaderResult, `SELECT vat_heal_checked FROM checked_headers WHERE header_id = $1`, headerId) + Expect(err).NotTo(HaveOccurred()) + Expect(checkedHeaderResult.VatHealChecked).To(BeFalse()) + + err = repository.MarkCheckedHeader(headerId) + Expect(err).NotTo(HaveOccurred()) + + err = db.Get(&checkedHeaderResult, `SELECT vat_heal_checked FROM checked_headers WHERE header_id = $1`, headerId) + Expect(err).NotTo(HaveOccurred()) + Expect(checkedHeaderResult.VatHealChecked).To(BeTrue()) + }) + }) +}) diff --git a/pkg/transformers/vat_heal/transformer.go b/pkg/transformers/vat_heal/transformer.go new file mode 100644 index 00000000..70ba78d2 --- /dev/null +++ b/pkg/transformers/vat_heal/transformer.go @@ -0,0 +1,80 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" +) + +type VatHealTransformer struct { + Config shared.TransformerConfig + Converter Converter + Fetcher shared.LogFetcher + Repository Repository +} + +type VatHealTransformerInitializer struct { + Config shared.TransformerConfig +} + +func (i VatHealTransformerInitializer) NewVatHealTransformer(db *postgres.DB, blockChain core.BlockChain) shared.Transformer { + fetcher := shared.NewFetcher(blockChain) + repository := NewVatHealRepository(db) + transformer := VatHealTransformer{ + Fetcher: fetcher, + Repository: repository, + Converter: VatHealConverter{}, + Config: i.Config, + } + + return transformer +} + +func (transformer VatHealTransformer) Execute() error { + config := transformer.Config + topics := [][]common.Hash{{common.HexToHash(config.Topics[0])}} + headers, err := transformer.Repository.MissingHeaders(config.StartingBlockNumber, config.EndingBlockNumber) + if err != nil { + return err + } + + for _, header := range headers { + logs, err := transformer.Fetcher.FetchLogs(config.ContractAddresses, topics, header.BlockNumber) + if err != nil { + return err + } + + if len(logs) < 1 { + err = transformer.Repository.MarkCheckedHeader(header.Id) + } + if err != nil { + return err + } + + models, err := transformer.Converter.ToModels(logs) + if err != nil { + return err + } + + err = transformer.Repository.Create(header.Id, models) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/transformers/vat_heal/transformer_test.go b/pkg/transformers/vat_heal/transformer_test.go new file mode 100644 index 00000000..69a9eded --- /dev/null +++ b/pkg/transformers/vat_heal/transformer_test.go @@ -0,0 +1,163 @@ +// Copyright 2018 Vulcanize +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vat_heal_test + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/fakes" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks" + mock_vat_heal "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks/vat_heal" + "github.com/vulcanize/vulcanizedb/pkg/transformers/vat_heal" +) + +var _ = Describe("VatHeal Transformer", func() { + var mockRepository mock_vat_heal.MockVatHealRepository + var transformer vat_heal.VatHealTransformer + var fetcher mocks.MockLogFetcher + var converter mock_vat_heal.MockVatHealConverter + + BeforeEach(func() { + mockRepository = mock_vat_heal.MockVatHealRepository{} + fetcher = mocks.MockLogFetcher{} + converter = mock_vat_heal.MockVatHealConverter{} + transformer = vat_heal.VatHealTransformer{ + Repository: &mockRepository, + Config: vat_heal.VatHealConfig, + Fetcher: &fetcher, + Converter: &converter, + } + }) + + It("gets all of the missing header ids", func() { + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(mockRepository.PassedStartingBlockNumber).To(Equal(vat_heal.VatHealConfig.StartingBlockNumber)) + Expect(mockRepository.PassedEndingBlockNumber).To(Equal(vat_heal.VatHealConfig.EndingBlockNumber)) + }) + + It("returns and error if getting the missing headers fails", func() { + mockRepository.SetMissingHeadersErr(fakes.FakeError) + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("fetches vat heal logs for the headers", func() { + header := core.Header{BlockNumber: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(fetcher.FetchedContractAddresses[0]).To(Equal(vat_heal.VatHealConfig.ContractAddresses)) + Expect(fetcher.FetchedTopics).To(Equal([][]common.Hash{{common.HexToHash(shared.VatHealSignature)}})) + Expect(fetcher.FetchedBlocks).To(Equal([]int64{header.BlockNumber})) + }) + + It("returns and error if fetching the logs fails", func() { + header := core.Header{BlockNumber: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + fetcher.SetFetcherError(fakes.FakeError) + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("converts the logs to models", func() { + header := core.Header{BlockNumber: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + fetcher.SetFetchedLogs([]types.Log{test_data.VatHealLog}) + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(converter.PassedLogs).To(Equal([]types.Log{test_data.VatHealLog})) + }) + + It("returns an error if converting fails", func() { + header := core.Header{BlockNumber: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + fetcher.SetFetchedLogs([]types.Log{test_data.VatHealLog}) + converter.SetConverterError(fakes.FakeError) + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("persists the vat heal models", func() { + header := core.Header{Id: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + fetcher.SetFetchedLogs([]types.Log{test_data.VatHealLog}) + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(mockRepository.PassedModels).To(ContainElement(test_data.VatHealModel)) + Expect(mockRepository.PassedHeaderID).To(Equal(header.Id)) + }) + + It("returns an error if persisting the vat heal models fails", func() { + header := core.Header{Id: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + fetcher.SetFetchedLogs([]types.Log{test_data.VatHealLog}) + mockRepository.SetCreateError(fakes.FakeError) + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("marks the header as checked when there are no logs", func() { + header := core.Header{Id: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(mockRepository.MarkHeaderCheckedPassedHeaderID).To(Equal(header.Id)) + }) + + It("doesn't call MarkCheckedHeader when there are logs", func() { + header := core.Header{Id: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + fetcher.SetFetchedLogs([]types.Log{test_data.VatHealLog}) + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(mockRepository.MarkHeaderCheckedPassedHeaderID).To(Equal(int64(0))) + }) + + It("returns an error if MarkCheckedHeader fails", func() { + header := core.Header{Id: GinkgoRandomSeed()} + mockRepository.SetMissingHeaders([]core.Header{header}) + mockRepository.SetMissingHeadersErr(fakes.FakeError) + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) +}) diff --git a/pkg/transformers/vat_heal/vat_heal_suite_test.go b/pkg/transformers/vat_heal/vat_heal_suite_test.go new file mode 100644 index 00000000..aa5cb799 --- /dev/null +++ b/pkg/transformers/vat_heal/vat_heal_suite_test.go @@ -0,0 +1,13 @@ +package vat_heal_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestVatHeal(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "VatHeal Suite") +} diff --git a/pkg/transformers/vat_init/repository.go b/pkg/transformers/vat_init/repository.go index 3fc3d56b..f17d91c5 100644 --- a/pkg/transformers/vat_init/repository.go +++ b/pkg/transformers/vat_init/repository.go @@ -51,6 +51,14 @@ func (repository VatInitRepository) Create(headerID int64, models []VatInitModel return err } } + _, err = tx.Exec(`INSERT INTO public.checked_headers (header_id, vat_init_checked) + VALUES($1, $2) + ON CONFLICT (header_id) DO + UPDATE SET vat_heal_checked = $2`, headerID, true) + if err != nil { + tx.Rollback() + return err + } return tx.Commit() } diff --git a/pkg/transformers/vat_init/repository_test.go b/pkg/transformers/vat_init/repository_test.go index dce264c8..08337943 100644 --- a/pkg/transformers/vat_init/repository_test.go +++ b/pkg/transformers/vat_init/repository_test.go @@ -29,15 +29,25 @@ import ( ) var _ = Describe("Vat init repository", func() { + var ( + db *postgres.DB + vatInitRepository vat_init.Repository + headerRepository repositories.HeaderRepository + err error + headerID int64 + ) + + BeforeEach(func() { + db = test_config.NewTestDB(core.Node{}) + test_config.CleanTestDB(db) + vatInitRepository = vat_init.NewVatInitRepository(db) + headerRepository = repositories.NewHeaderRepository(db) + headerID, err = headerRepository.CreateOrUpdateHeader(core.Header{}) + Expect(err).NotTo(HaveOccurred()) + }) + Describe("Create", func() { It("adds a vat event", func() { - db := test_config.NewTestDB(core.Node{}) - test_config.CleanTestDB(db) - headerRepository := repositories.NewHeaderRepository(db) - headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) - Expect(err).NotTo(HaveOccurred()) - vatInitRepository := vat_init.NewVatInitRepository(db) - err = vatInitRepository.Create(headerID, []vat_init.VatInitModel{test_data.VatInitModel}) Expect(err).NotTo(HaveOccurred()) @@ -50,12 +60,6 @@ var _ = Describe("Vat init repository", func() { }) It("does not duplicate vat events", func() { - db := test_config.NewTestDB(core.Node{}) - test_config.CleanTestDB(db) - headerRepository := repositories.NewHeaderRepository(db) - headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) - Expect(err).NotTo(HaveOccurred()) - vatInitRepository := vat_init.NewVatInitRepository(db) err = vatInitRepository.Create(headerID, []vat_init.VatInitModel{test_data.VatInitModel}) Expect(err).NotTo(HaveOccurred()) @@ -66,12 +70,6 @@ var _ = Describe("Vat init repository", func() { }) It("removes vat if corresponding header is deleted", func() { - db := test_config.NewTestDB(core.Node{}) - test_config.CleanTestDB(db) - headerRepository := repositories.NewHeaderRepository(db) - headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{}) - Expect(err).NotTo(HaveOccurred()) - vatInitRepository := vat_init.NewVatInitRepository(db) err = vatInitRepository.Create(headerID, []vat_init.VatInitModel{test_data.VatInitModel}) Expect(err).NotTo(HaveOccurred()) @@ -83,25 +81,19 @@ var _ = Describe("Vat init repository", func() { Expect(err).To(HaveOccurred()) Expect(err).To(MatchError(sql.ErrNoRows)) }) + + It("marks the header as checked for vat init logs", func() { + err = vatInitRepository.Create(headerID, []vat_init.VatInitModel{test_data.VatInitModel}) + Expect(err).NotTo(HaveOccurred()) + + var headerChecked bool + err = db.Get(&headerChecked, `SELECT vat_init_checked FROM public.checked_headers WHERE header_id = $1`, headerID) + Expect(err).NotTo(HaveOccurred()) + Expect(headerChecked).To(BeTrue()) + }) }) Describe("MarkHeaderChecked", func() { - var ( - db *postgres.DB - vatInitRepository vat_init.Repository - err error - headerID int64 - ) - - BeforeEach(func() { - db = test_config.NewTestDB(core.Node{}) - test_config.CleanTestDB(db) - headerRepository := repositories.NewHeaderRepository(db) - headerID, err = headerRepository.CreateOrUpdateHeader(core.Header{}) - Expect(err).NotTo(HaveOccurred()) - vatInitRepository = vat_init.NewVatInitRepository(db) - }) - It("creates a row for a new headerID", func() { err = vatInitRepository.MarkHeaderChecked(headerID) @@ -127,9 +119,6 @@ var _ = Describe("Vat init repository", func() { Describe("MissingHeaders", func() { It("returns headers that haven't been checked", func() { - db := test_config.NewTestDB(core.Node{}) - test_config.CleanTestDB(db) - headerRepository := repositories.NewHeaderRepository(db) startingBlockNumber := int64(1) vatInitBlockNumber := int64(2) endingBlockNumber := int64(3) @@ -153,8 +142,6 @@ var _ = Describe("Vat init repository", func() { }) It("only treats headers as checked if drip drip logs have been checked", func() { - db := test_config.NewTestDB(core.Node{}) - test_config.CleanTestDB(db) headerRepository := repositories.NewHeaderRepository(db) startingBlockNumber := int64(1) vatInitBlockNumber := int64(2) @@ -166,11 +153,10 @@ var _ = Describe("Vat init repository", func() { headerIDs = append(headerIDs, headerID) Expect(err).NotTo(HaveOccurred()) } - VatInitRepository := vat_init.NewVatInitRepository(db) _, err := db.Exec(`INSERT INTO public.checked_headers (header_id) VALUES ($1)`, headerIDs[1]) Expect(err).NotTo(HaveOccurred()) - headers, err := VatInitRepository.MissingHeaders(startingBlockNumber, endingBlockNumber) + headers, err := vatInitRepository.MissingHeaders(startingBlockNumber, endingBlockNumber) Expect(err).NotTo(HaveOccurred()) Expect(len(headers)).To(Equal(3)) @@ -180,10 +166,7 @@ var _ = Describe("Vat init repository", func() { }) It("only returns headers associated with the current node", func() { - db := test_config.NewTestDB(core.Node{}) - test_config.CleanTestDB(db) blockNumbers := []int64{1, 2, 3} - headerRepository := repositories.NewHeaderRepository(db) dbTwo := test_config.NewTestDB(core.Node{ID: "second"}) headerRepositoryTwo := repositories.NewHeaderRepository(dbTwo) var headerIDs []int64 @@ -194,7 +177,6 @@ var _ = Describe("Vat init repository", func() { _, err = headerRepositoryTwo.CreateOrUpdateHeader(core.Header{BlockNumber: n}) Expect(err).NotTo(HaveOccurred()) } - vatInitRepository := vat_init.NewVatInitRepository(db) vatInitRepositoryTwo := vat_init.NewVatInitRepository(dbTwo) err := vatInitRepository.MarkHeaderChecked(headerIDs[0]) Expect(err).NotTo(HaveOccurred()) diff --git a/test_config/test_config.go b/test_config/test_config.go index 49ff186c..09e86234 100644 --- a/test_config/test_config.go +++ b/test_config/test_config.go @@ -97,6 +97,7 @@ func CleanTestDB(db *postgres.DB) { db.MustExec("DELETE FROM maker.price_feeds") db.MustExec("DELETE FROM maker.tend") db.MustExec("DELETE FROM maker.vat_grab") + db.MustExec("DELETE FROM maker.vat_heal") db.MustExec("DELETE FROM maker.vat_init") db.MustExec("DELETE FROM maker.vat_fold") db.MustExec("DELETE FROM maker.vat_toll")