diff --git a/db/migrations/1536710319_create_drip_drip_table.down.sql b/db/migrations/1536710319_create_drip_drip_table.down.sql new file mode 100644 index 00000000..2c9b7a28 --- /dev/null +++ b/db/migrations/1536710319_create_drip_drip_table.down.sql @@ -0,0 +1 @@ +DROP TABLE maker.drip_drip; \ No newline at end of file diff --git a/db/migrations/1536710319_create_drip_drip_table.up.sql b/db/migrations/1536710319_create_drip_drip_table.up.sql new file mode 100644 index 00000000..58c26273 --- /dev/null +++ b/db/migrations/1536710319_create_drip_drip_table.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE maker.drip_drip ( + id SERIAL PRIMARY KEY, + header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, + ilk TEXT, + tx_idx INTEGER NOT NUll, + raw_log JSONB, + UNIQUE (header_id, tx_idx) +); \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index b19de60b..2b4a786f 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -149,6 +149,39 @@ CREATE SEQUENCE maker.dent_db_id_seq ALTER SEQUENCE maker.dent_db_id_seq OWNED BY maker.dent.db_id; +-- +-- Name: drip_drip; Type: TABLE; Schema: maker; Owner: - +-- + +CREATE TABLE maker.drip_drip ( + id integer NOT NULL, + header_id integer NOT NULL, + ilk text, + tx_idx integer NOT NULL, + raw_log jsonb +); + + +-- +-- Name: drip_drip_id_seq; Type: SEQUENCE; Schema: maker; Owner: - +-- + +CREATE SEQUENCE maker.drip_drip_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: drip_drip_id_seq; Type: SEQUENCE OWNED BY; Schema: maker; Owner: - +-- + +ALTER SEQUENCE maker.drip_drip_id_seq OWNED BY maker.drip_drip.id; + + -- -- Name: drip_file_ilk; Type: TABLE; Schema: maker; Owner: - -- @@ -931,6 +964,13 @@ ALTER TABLE ONLY maker.deal ALTER COLUMN id SET DEFAULT nextval('maker.deal_id_s ALTER TABLE ONLY maker.dent ALTER COLUMN db_id SET DEFAULT nextval('maker.dent_db_id_seq'::regclass); +-- +-- Name: drip_drip id; Type: DEFAULT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.drip_drip ALTER COLUMN id SET DEFAULT nextval('maker.drip_drip_id_seq'::regclass); + + -- -- Name: drip_file_ilk id; Type: DEFAULT; Schema: maker; Owner: - -- @@ -1119,6 +1159,22 @@ ALTER TABLE ONLY maker.dent ADD CONSTRAINT dent_pkey PRIMARY KEY (db_id); +-- +-- Name: drip_drip drip_drip_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.drip_drip + ADD CONSTRAINT drip_drip_header_id_tx_idx_key UNIQUE (header_id, tx_idx); + + +-- +-- Name: drip_drip drip_drip_pkey; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.drip_drip + ADD CONSTRAINT drip_drip_pkey PRIMARY KEY (id); + + -- -- Name: drip_file_ilk drip_file_ilk_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: - -- @@ -1450,6 +1506,14 @@ ALTER TABLE ONLY maker.dent ADD CONSTRAINT dent_header_id_fkey FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; +-- +-- Name: drip_drip drip_drip_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.drip_drip + ADD CONSTRAINT drip_drip_header_id_fkey FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; + + -- -- Name: drip_file_ilk drip_file_ilk_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: - -- diff --git a/pkg/transformers/drip_drip/config.go b/pkg/transformers/drip_drip/config.go new file mode 100644 index 00000000..83a519ab --- /dev/null +++ b/pkg/transformers/drip_drip/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 drip_drip + +import "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + +var DripDripConfig = shared.TransformerConfig{ + ContractAddress: shared.DripContractAddress, + ContractAbi: shared.DripABI, + Topics: []string{shared.DripDripSignature}, + StartingBlockNumber: 0, + EndingBlockNumber: 100, +} diff --git a/pkg/transformers/drip_drip/converter.go b/pkg/transformers/drip_drip/converter.go new file mode 100644 index 00000000..99e58199 --- /dev/null +++ b/pkg/transformers/drip_drip/converter.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 drip_drip + +import ( + "bytes" + "encoding/json" + "errors" + "github.com/ethereum/go-ethereum/core/types" +) + +type Converter interface { + ToModel(ethLog types.Log) (DripDripModel, error) +} + +type DripDripConverter struct{} + +func (DripDripConverter) ToModel(ethLog types.Log) (DripDripModel, error) { + err := verifyLog(ethLog) + if err != nil { + return DripDripModel{}, err + } + ilk := string(bytes.Trim(ethLog.Topics[2].Bytes(), "\x00")) + raw, err := json.Marshal(ethLog) + return DripDripModel{ + Ilk: ilk, + TransactionIndex: ethLog.TxIndex, + Raw: raw, + }, err +} + +func verifyLog(log types.Log) error { + if len(log.Topics) < 3 { + return errors.New("log missing topics") + } + return nil +} diff --git a/pkg/transformers/drip_drip/converter_test.go b/pkg/transformers/drip_drip/converter_test.go new file mode 100644 index 00000000..454a3409 --- /dev/null +++ b/pkg/transformers/drip_drip/converter_test.go @@ -0,0 +1,44 @@ +// 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 drip_drip_test + +import ( + "github.com/ethereum/go-ethereum/core/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_drip" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" +) + +var _ = Describe("Drip drip converter", func() { + It("returns err if log is missing topics", func() { + converter := drip_drip.DripDripConverter{} + badLog := types.Log{} + + _, err := converter.ToModel(badLog) + + Expect(err).To(HaveOccurred()) + }) + + It("converts a log to an model", func() { + converter := drip_drip.DripDripConverter{} + + model, err := converter.ToModel(test_data.EthDripDripLog) + + Expect(err).NotTo(HaveOccurred()) + Expect(model).To(Equal(test_data.DripDripModel)) + }) +}) diff --git a/pkg/transformers/drip_drip/drip_drip_suite_test.go b/pkg/transformers/drip_drip/drip_drip_suite_test.go new file mode 100644 index 00000000..458c4562 --- /dev/null +++ b/pkg/transformers/drip_drip/drip_drip_suite_test.go @@ -0,0 +1,27 @@ +// 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 drip_drip_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestDripDrip(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "DripDrip Suite") +} diff --git a/pkg/transformers/drip_drip/model.go b/pkg/transformers/drip_drip/model.go new file mode 100644 index 00000000..6461a036 --- /dev/null +++ b/pkg/transformers/drip_drip/model.go @@ -0,0 +1,21 @@ +// 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 drip_drip + +type DripDripModel struct { + Ilk string + TransactionIndex uint `db:"tx_idx"` + Raw []byte `db:"raw_log"` +} diff --git a/pkg/transformers/drip_drip/repository.go b/pkg/transformers/drip_drip/repository.go new file mode 100644 index 00000000..0221a4cf --- /dev/null +++ b/pkg/transformers/drip_drip/repository.go @@ -0,0 +1,59 @@ +// 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 drip_drip + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" +) + +type Repository interface { + Create(headerID int64, model DripDripModel) error + MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) +} + +type DripDripRepository struct { + db *postgres.DB +} + +func NewDripDripRepository(db *postgres.DB) DripDripRepository { + return DripDripRepository{db: db} +} + +func (repository DripDripRepository) Create(headerID int64, model DripDripModel) error { + _, err := repository.db.Exec( + `INSERT into maker.drip_drip (header_id, ilk, tx_idx, raw_log) + VALUES($1, $2, $3, $4)`, + headerID, model.Ilk, model.TransactionIndex, model.Raw, + ) + return err +} + +func (repository DripDripRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) { + var result []core.Header + err := repository.db.Select( + &result, + `SELECT headers.id, headers.block_number FROM headers + LEFT JOIN maker.drip_drip on headers.id = header_id + WHERE header_id ISNULL + AND headers.block_number >= $1 + AND headers.block_number <= $2 + AND headers.eth_node_fingerprint = $3`, + startingBlockNumber, + endingBlockNumber, + repository.db.Node.ID, + ) + return result, err +} diff --git a/pkg/transformers/drip_drip/repository_test.go b/pkg/transformers/drip_drip/repository_test.go new file mode 100644 index 00000000..f4ff5094 --- /dev/null +++ b/pkg/transformers/drip_drip/repository_test.go @@ -0,0 +1,143 @@ +// 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 drip_drip_test + +import ( + "database/sql" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" + "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_drip" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("Drip drip repository", func() { + Describe("Create", func() { + It("adds a drip drip 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()) + dripDripRepository := drip_drip.NewDripDripRepository(db) + + err = dripDripRepository.Create(headerID, test_data.DripDripModel) + + Expect(err).NotTo(HaveOccurred()) + var dbPitFile drip_drip.DripDripModel + err = db.Get(&dbPitFile, `SELECT ilk, tx_idx, raw_log FROM maker.drip_drip WHERE header_id = $1`, headerID) + Expect(err).NotTo(HaveOccurred()) + Expect(dbPitFile.Ilk).To(Equal(test_data.DripDripModel.Ilk)) + Expect(dbPitFile.TransactionIndex).To(Equal(test_data.DripDripModel.TransactionIndex)) + Expect(dbPitFile.Raw).To(MatchJSON(test_data.DripDripModel.Raw)) + }) + + It("does not duplicate drip drip 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()) + dripDripRepository := drip_drip.NewDripDripRepository(db) + err = dripDripRepository.Create(headerID, test_data.DripDripModel) + Expect(err).NotTo(HaveOccurred()) + + err = dripDripRepository.Create(headerID, test_data.DripDripModel) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("pq: duplicate key value violates unique constraint")) + }) + + It("removes drip drip 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()) + dripDripRepository := drip_drip.NewDripDripRepository(db) + err = dripDripRepository.Create(headerID, test_data.DripDripModel) + Expect(err).NotTo(HaveOccurred()) + + _, err = db.Exec(`DELETE FROM headers WHERE id = $1`, headerID) + + Expect(err).NotTo(HaveOccurred()) + var dbPitFile drip_drip.DripDripModel + err = db.Get(&dbPitFile, `SELECT ilk, tx_idx, raw_log FROM maker.drip_drip WHERE header_id = $1`, headerID) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(sql.ErrNoRows)) + }) + }) + + Describe("MissingHeaders", func() { + It("returns headers with no associated drip drip event", func() { + db := test_config.NewTestDB(core.Node{}) + test_config.CleanTestDB(db) + headerRepository := repositories.NewHeaderRepository(db) + startingBlockNumber := int64(1) + dripDripBlockNumber := int64(2) + endingBlockNumber := int64(3) + blockNumbers := []int64{startingBlockNumber, dripDripBlockNumber, endingBlockNumber, endingBlockNumber + 1} + var headerIDs []int64 + for _, n := range blockNumbers { + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: n}) + headerIDs = append(headerIDs, headerID) + Expect(err).NotTo(HaveOccurred()) + } + dripDripRepository := drip_drip.NewDripDripRepository(db) + err := dripDripRepository.Create(headerIDs[1], test_data.DripDripModel) + Expect(err).NotTo(HaveOccurred()) + + headers, err := dripDripRepository.MissingHeaders(startingBlockNumber, endingBlockNumber) + + Expect(err).NotTo(HaveOccurred()) + Expect(len(headers)).To(Equal(2)) + Expect(headers[0].BlockNumber).To(Or(Equal(startingBlockNumber), Equal(endingBlockNumber))) + Expect(headers[1].BlockNumber).To(Or(Equal(startingBlockNumber), Equal(endingBlockNumber))) + }) + + 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 + for _, n := range blockNumbers { + headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: n}) + Expect(err).NotTo(HaveOccurred()) + headerIDs = append(headerIDs, headerID) + _, err = headerRepositoryTwo.CreateOrUpdateHeader(core.Header{BlockNumber: n}) + Expect(err).NotTo(HaveOccurred()) + } + dripDripRepository := drip_drip.NewDripDripRepository(db) + dripDripRepositoryTwo := drip_drip.NewDripDripRepository(dbTwo) + err := dripDripRepository.Create(headerIDs[0], test_data.DripDripModel) + Expect(err).NotTo(HaveOccurred()) + + nodeOneMissingHeaders, err := dripDripRepository.MissingHeaders(blockNumbers[0], blockNumbers[len(blockNumbers)-1]) + Expect(err).NotTo(HaveOccurred()) + Expect(len(nodeOneMissingHeaders)).To(Equal(len(blockNumbers) - 1)) + + nodeTwoMissingHeaders, err := dripDripRepositoryTwo.MissingHeaders(blockNumbers[0], blockNumbers[len(blockNumbers)-1]) + Expect(err).NotTo(HaveOccurred()) + Expect(len(nodeTwoMissingHeaders)).To(Equal(len(blockNumbers))) + }) + }) +}) diff --git a/pkg/transformers/drip_drip/transformer.go b/pkg/transformers/drip_drip/transformer.go new file mode 100644 index 00000000..d1dcded6 --- /dev/null +++ b/pkg/transformers/drip_drip/transformer.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 drip_drip + +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 DripDripTransformerInitializer struct { + Config shared.TransformerConfig +} + +func (initializer DripDripTransformerInitializer) NewDripDripTransformer(db *postgres.DB, blockChain core.BlockChain) shared.Transformer { + converter := DripDripConverter{} + fetcher := shared.NewFetcher(blockChain) + repository := NewDripDripRepository(db) + return DripDripTransformer{ + Config: initializer.Config, + Converter: converter, + Fetcher: fetcher, + Repository: repository, + } +} + +type DripDripTransformer struct { + Config shared.TransformerConfig + Converter Converter + Fetcher shared.LogFetcher + Repository Repository +} + +func (transformer DripDripTransformer) Execute() error { + missingHeaders, err := transformer.Repository.MissingHeaders(transformer.Config.StartingBlockNumber, transformer.Config.EndingBlockNumber) + if err != nil { + return err + } + for _, header := range missingHeaders { + topics := [][]common.Hash{{common.HexToHash(shared.DripDripSignature)}} + matchingLogs, err := transformer.Fetcher.FetchLogs(transformer.Config.ContractAddress, topics, header.BlockNumber) + if err != nil { + return err + } + for _, log := range matchingLogs { + model, err := transformer.Converter.ToModel(log) + if err != nil { + return err + } + err = transformer.Repository.Create(header.Id, model) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/transformers/drip_drip/transformer_test.go b/pkg/transformers/drip_drip/transformer_test.go new file mode 100644 index 00000000..a0b31321 --- /dev/null +++ b/pkg/transformers/drip_drip/transformer_test.go @@ -0,0 +1,175 @@ +// 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 drip_drip_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/drip_drip" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks" + drip_drip_mocks "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks/drip_drip" +) + +var _ = Describe("Drip drip transformer", func() { + It("gets missing headers for block numbers specified in config", func() { + repository := &drip_drip_mocks.MockDripDripRepository{} + transformer := drip_drip.DripDripTransformer{ + Config: drip_drip.DripDripConfig, + Fetcher: &mocks.MockLogFetcher{}, + Converter: &drip_drip_mocks.MockDripDripConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(repository.PassedStartingBlockNumber).To(Equal(drip_drip.DripDripConfig.StartingBlockNumber)) + Expect(repository.PassedEndingBlockNumber).To(Equal(drip_drip.DripDripConfig.EndingBlockNumber)) + }) + + It("returns error if repository returns error for missing headers", func() { + repository := &drip_drip_mocks.MockDripDripRepository{} + repository.SetMissingHeadersErr(fakes.FakeError) + transformer := drip_drip.DripDripTransformer{ + Fetcher: &mocks.MockLogFetcher{}, + Converter: &drip_drip_mocks.MockDripDripConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("fetches logs for missing headers", func() { + fetcher := &mocks.MockLogFetcher{} + repository := &drip_drip_mocks.MockDripDripRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}, {BlockNumber: 2}}) + transformer := drip_drip.DripDripTransformer{ + Config: drip_drip.DripDripConfig, + Fetcher: fetcher, + Converter: &drip_drip_mocks.MockDripDripConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(fetcher.FetchedBlocks).To(Equal([]int64{1, 2})) + Expect(fetcher.FetchedContractAddress).To(Equal(drip_drip.DripDripConfig.ContractAddress)) + Expect(fetcher.FetchedTopics).To(Equal([][]common.Hash{{common.HexToHash(shared.DripDripSignature)}})) + }) + + It("returns error if fetcher returns error", func() { + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetcherError(fakes.FakeError) + repository := &drip_drip_mocks.MockDripDripRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + transformer := drip_drip.DripDripTransformer{ + Fetcher: fetcher, + Converter: &drip_drip_mocks.MockDripDripConverter{}, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("converts matching logs", func() { + converter := &drip_drip_mocks.MockDripDripConverter{} + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthDripDripLog}) + repository := &drip_drip_mocks.MockDripDripRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + transformer := drip_drip.DripDripTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(converter.PassedLog).To(Equal(test_data.EthDripDripLog)) + }) + + It("returns error if converter returns error", func() { + converter := &drip_drip_mocks.MockDripDripConverter{} + converter.SetConverterError(fakes.FakeError) + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthDripDripLog}) + repository := &drip_drip_mocks.MockDripDripRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}}) + transformer := drip_drip.DripDripTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("persists drip drip model", func() { + converter := &drip_drip_mocks.MockDripDripConverter{} + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthDripDripLog}) + repository := &drip_drip_mocks.MockDripDripRepository{} + fakeHeader := core.Header{BlockNumber: 1, Id: 2} + repository.SetMissingHeaders([]core.Header{fakeHeader}) + transformer := drip_drip.DripDripTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).NotTo(HaveOccurred()) + Expect(repository.PassedHeaderID).To(Equal(fakeHeader.Id)) + Expect(repository.PassedModel).To(Equal(test_data.DripDripModel)) + }) + + It("returns error if repository returns error for create", func() { + converter := &drip_drip_mocks.MockDripDripConverter{} + fetcher := &mocks.MockLogFetcher{} + fetcher.SetFetchedLogs([]types.Log{test_data.EthDripDripLog}) + repository := &drip_drip_mocks.MockDripDripRepository{} + repository.SetMissingHeaders([]core.Header{{BlockNumber: 1, Id: 2}}) + repository.SetCreateError(fakes.FakeError) + transformer := drip_drip.DripDripTransformer{ + Fetcher: fetcher, + Converter: converter, + Repository: repository, + } + + err := transformer.Execute() + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) +}) diff --git a/pkg/transformers/shared/constants.go b/pkg/transformers/shared/constants.go index 7f0fba57..e7b60dee 100644 --- a/pkg/transformers/shared/constants.go +++ b/pkg/transformers/shared/constants.go @@ -25,7 +25,7 @@ var ( VatABI = `[{"constant":true,"inputs":[],"name":"debt","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x0dca59c1"},{"constant":true,"inputs":[{"name":"","type":"bytes32"},{"name":"","type":"bytes32"}],"name":"urns","outputs":[{"name":"ink","type":"uint256"},{"name":"art","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x26e27482"},{"constant":true,"inputs":[],"name":"vice","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x2d61a355"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"sin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xa60f1d3e"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"wards","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xbf353dbb"},{"constant":true,"inputs":[{"name":"","type":"bytes32"},{"name":"","type":"bytes32"}],"name":"gem","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xc0912683"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"ilks","outputs":[{"name":"take","type":"uint256"},{"name":"rate","type":"uint256"},{"name":"Ink","type":"uint256"},{"name":"Art","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xd9638d36"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"dai","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xf53e4e69"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor","signature":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":true,"name":"too","type":"bytes32"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"Note","type":"event","signature":"0x8c2dbbc2b33ffaa77c104b777e574a8a4ff79829dfee8b66f4dc63e3f8067152"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x65fae35e"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x9c52a7f1"},{"constant":false,"inputs":[{"name":"ilk","type":"bytes32"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x3b663195"},{"constant":false,"inputs":[{"name":"ilk","type":"bytes32"},{"name":"guy","type":"bytes32"},{"name":"rad","type":"int256"}],"name":"slip","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x42066cbb"},{"constant":false,"inputs":[{"name":"ilk","type":"bytes32"},{"name":"src","type":"bytes32"},{"name":"dst","type":"bytes32"},{"name":"rad","type":"int256"}],"name":"flux","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xa6e41821"},{"constant":false,"inputs":[{"name":"src","type":"bytes32"},{"name":"dst","type":"bytes32"},{"name":"rad","type":"int256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x78f19470"},{"constant":false,"inputs":[{"name":"i","type":"bytes32"},{"name":"u","type":"bytes32"},{"name":"v","type":"bytes32"},{"name":"w","type":"bytes32"},{"name":"dink","type":"int256"},{"name":"dart","type":"int256"}],"name":"tune","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x5dd6471a"},{"constant":false,"inputs":[{"name":"i","type":"bytes32"},{"name":"u","type":"bytes32"},{"name":"v","type":"bytes32"},{"name":"w","type":"bytes32"},{"name":"dink","type":"int256"},{"name":"dart","type":"int256"}],"name":"grab","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x3690ae4c"},{"constant":false,"inputs":[{"name":"u","type":"bytes32"},{"name":"v","type":"bytes32"},{"name":"rad","type":"int256"}],"name":"heal","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x990a5f63"},{"constant":false,"inputs":[{"name":"i","type":"bytes32"},{"name":"u","type":"bytes32"},{"name":"rate","type":"int256"}],"name":"fold","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xe6a6a64d"},{"constant":false,"inputs":[{"name":"i","type":"bytes32"},{"name":"u","type":"bytes32"},{"name":"take","type":"int256"}],"name":"toll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x09b7a0b5"}]` // temporary addresses from local Ganache deployment - CatContractAddress = "0xe0f0fa6982c59d8aa4ae0134bfe048327bd788cacf758b643ca41f055ffce76c" + CatContractAddress = "0xF56D79323fa7F82ACf0A4F88331093a77486dA7e" DripContractAddress = "0xD4ae7420FCF54ab7cd27ab9c24DCC876eF6b64C3" FlipperContractAddress = "0x6b59c42097e2fff7cad96cb08ceefd601081ad9c" PepContractAddress = "0x99041F808D598B782D5a3e498681C2452A31da08" @@ -38,21 +38,23 @@ var ( biteMethod = GetSolidityMethodSignature(CatABI, "Bite") dealMethod = GetSolidityMethodSignature(FlipperABI, "deal") dentMethod = GetSolidityMethodSignature(FlipperABI, "dent") + dripDripMethod = GetSolidityMethodSignature(DripABI, "drip") dripFileIlkMethod = "file(bytes32,bytes32,uint256)" dripFileRepoMethod = GetSolidityMethodSignature(DripABI, "file") dripFileVowMethod = "file(bytes32,bytes32)" flipKickMethod = GetSolidityMethodSignature(FlipperABI, "Kick") frobMethod = GetSolidityMethodSignature(PitABI, "Frob") + logValueMethod = GetSolidityMethodSignature(MedianizerABI, "LogValue") pitFileDebtCeilingMethod = "file(bytes32,uint256)" pitFileIlkMethod = "file(bytes32,bytes32,uint256)" pitFileStabilityFeeMethod = GetSolidityMethodSignature(PitABI, "file") tendMethod = GetSolidityMethodSignature(FlipperABI, "tend") - logValueMethod = GetSolidityMethodSignature(MedianizerABI, "LogValue") vatInitMethod = GetSolidityMethodSignature(VatABI, "init") BiteSignature = GetEventSignature(biteMethod) DealSignature = GetLogNoteSignature(dealMethod) DentFunctionSignature = GetLogNoteSignature(dentMethod) + DripDripSignature = GetLogNoteSignature(dripDripMethod) DripFileIlkSignature = GetLogNoteSignature(dripFileIlkMethod) DripFileRepoSignature = GetLogNoteSignature(dripFileRepoMethod) DripFileVowSignature = GetLogNoteSignature(dripFileVowMethod) diff --git a/pkg/transformers/test_data/drip_drip.go b/pkg/transformers/test_data/drip_drip.go new file mode 100644 index 00000000..20b45739 --- /dev/null +++ b/pkg/transformers/test_data/drip_drip.go @@ -0,0 +1,48 @@ +// 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/drip_drip" + "github.com/vulcanize/vulcanizedb/pkg/transformers/shared" +) + +var EthDripDripLog = types.Log{ + Address: common.HexToAddress(shared.DripContractAddress), + Topics: []common.Hash{ + common.HexToHash("0x44e2a5a800000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0x00000000000000000000000064d922894153be9eef7b7218dc565d1d0ce2a092"), + common.HexToHash("0x66616b6520696c6b000000000000000000000000000000000000000000000000"), + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + }, + Data: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002444e2a5a866616b6520696c6b000000000000000000000000000000000000000000000000"), + BlockNumber: 62, + TxHash: common.HexToHash("0xa34fd5cfcb125ebfc81d33633495701b531753669712092bdb8aa6159a240040"), + TxIndex: 10, + BlockHash: common.HexToHash("0x7a2aa72468986774d90dc8c80d436956a5b6a7e5acea430fb8a79f9217ef00a3"), + Index: 0, + Removed: false, +} + +var rawDripDripLog, _ = json.Marshal(EthDripDripLog) +var DripDripModel = drip_drip.DripDripModel{ + Ilk: "fake ilk", + TransactionIndex: EthDripDripLog.TxIndex, + Raw: rawDripDripLog, +} diff --git a/pkg/transformers/test_data/mocks/drip_drip/converter.go b/pkg/transformers/test_data/mocks/drip_drip/converter.go new file mode 100644 index 00000000..11c66a48 --- /dev/null +++ b/pkg/transformers/test_data/mocks/drip_drip/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 drip_drip + +import ( + "github.com/ethereum/go-ethereum/core/types" + "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_drip" + "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data" +) + +type MockDripDripConverter struct { + Err error + PassedLog types.Log +} + +func (converter *MockDripDripConverter) ToModel(ethLog types.Log) (drip_drip.DripDripModel, error) { + converter.PassedLog = ethLog + return test_data.DripDripModel, converter.Err +} + +func (converter *MockDripDripConverter) SetConverterError(e error) { + converter.Err = e +} diff --git a/pkg/transformers/test_data/mocks/drip_drip/repository.go b/pkg/transformers/test_data/mocks/drip_drip/repository.go new file mode 100644 index 00000000..c836061b --- /dev/null +++ b/pkg/transformers/test_data/mocks/drip_drip/repository.go @@ -0,0 +1,54 @@ +// 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 drip_drip + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_drip" +) + +type MockDripDripRepository struct { + createErr error + missingHeaders []core.Header + missingHeadersErr error + PassedStartingBlockNumber int64 + PassedEndingBlockNumber int64 + PassedHeaderID int64 + PassedModel drip_drip.DripDripModel +} + +func (repository *MockDripDripRepository) Create(headerID int64, model drip_drip.DripDripModel) error { + repository.PassedHeaderID = headerID + repository.PassedModel = model + return repository.createErr +} + +func (repository *MockDripDripRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) { + repository.PassedStartingBlockNumber = startingBlockNumber + repository.PassedEndingBlockNumber = endingBlockNumber + return repository.missingHeaders, repository.missingHeadersErr +} + +func (repository *MockDripDripRepository) SetMissingHeadersErr(e error) { + repository.missingHeadersErr = e +} + +func (repository *MockDripDripRepository) SetMissingHeaders(headers []core.Header) { + repository.missingHeaders = headers +} + +func (repository *MockDripDripRepository) SetCreateError(e error) { + repository.createErr = e +} diff --git a/pkg/transformers/transformers.go b/pkg/transformers/transformers.go index 96d1df83..3a04590a 100644 --- a/pkg/transformers/transformers.go +++ b/pkg/transformers/transformers.go @@ -18,6 +18,7 @@ import ( "github.com/vulcanize/vulcanizedb/pkg/transformers/bite" "github.com/vulcanize/vulcanizedb/pkg/transformers/deal" "github.com/vulcanize/vulcanizedb/pkg/transformers/dent" + "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_drip" "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_file" ilk2 "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_file/ilk" "github.com/vulcanize/vulcanizedb/pkg/transformers/drip_file/repo" @@ -37,6 +38,7 @@ func TransformerInitializers() []shared.TransformerInitializer { biteTransformerInitializer := bite.BiteTransformerInitializer{Config: bite.BiteConfig} dealTransformerInitializer := deal.DealTransformerInitializer{Config: deal.Config} dentTransformerInitializer := dent.DentTransformerInitializer{Config: dent.DentConfig} + dripDripTransformerInitializer := drip_drip.DripDripTransformerInitializer{Config: drip_drip.DripDripConfig} flipKickTransformerInitializer := flip_kick.FlipKickTransformerInitializer{Config: flip_kick.FlipKickConfig} frobTransformerInitializer := frob.FrobTransformerInitializer{Config: frob.FrobConfig} dripFileConfig := drip_file.DripFileConfig @@ -56,6 +58,7 @@ func TransformerInitializers() []shared.TransformerInitializer { dentTransformerInitializer.NewDentTransformer, dripFileIlkTransformerInitializer.NewDripFileIlkTransformer, dripFileRepoTransformerInitializer.NewDripFileRepoTransformer, + dripDripTransformerInitializer.NewDripDripTransformer, flipKickTransformerInitializer.NewFlipKickTransformer, frobTransformerInitializer.NewFrobTransformer, pitFileDebtCeilingTransformerInitializer.NewPitFileDebtCeilingTransformer,