diff --git a/cmd/syncPriceFeeds.go b/cmd/syncPriceFeeds.go index c977f35b..fadade4a 100644 --- a/cmd/syncPriceFeeds.go +++ b/cmd/syncPriceFeeds.go @@ -34,6 +34,7 @@ import ( "github.com/vulcanize/vulcanizedb/pkg/transformers" "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pep" "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pip" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep" "github.com/vulcanize/vulcanizedb/utils" ) @@ -94,6 +95,7 @@ func syncPriceFeeds() { transformers := []transformers.Transformer{ pep.NewPepTransformer(blockChain, &db), pip.NewPipTransformer(blockChain, &db), + rep.NewRepTransformer(blockChain, &db), } go backFillPriceFeeds(blockChain, headerRepository, missingBlocksPopulated, startingBlockNumber, transformers) diff --git a/db/migrations/1532635100_create_reps_table.down.sql b/db/migrations/1532635100_create_reps_table.down.sql new file mode 100644 index 00000000..5d82e3dc --- /dev/null +++ b/db/migrations/1532635100_create_reps_table.down.sql @@ -0,0 +1 @@ +DROP TABLE maker.reps; \ No newline at end of file diff --git a/db/migrations/1532635100_create_reps_table.up.sql b/db/migrations/1532635100_create_reps_table.up.sql new file mode 100644 index 00000000..7f79e98f --- /dev/null +++ b/db/migrations/1532635100_create_reps_table.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE maker.reps ( + id SERIAL PRIMARY KEY, + block_number BIGINT NOT NULL, + header_id INTEGER NOT NULL, + usd_value NUMERIC, + CONSTRAINT headers_fk FOREIGN KEY (header_id) + REFERENCES headers (id) + ON DELETE CASCADE +); \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index 461507e3..bfa6a4ca 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -183,6 +183,38 @@ CREATE SEQUENCE maker.pips_id_seq ALTER SEQUENCE maker.pips_id_seq OWNED BY maker.pips.id; +-- +-- Name: reps; Type: TABLE; Schema: maker; Owner: - +-- + +CREATE TABLE maker.reps ( + id integer NOT NULL, + block_number bigint NOT NULL, + header_id integer NOT NULL, + usd_value numeric +); + + +-- +-- Name: reps_id_seq; Type: SEQUENCE; Schema: maker; Owner: - +-- + +CREATE SEQUENCE maker.reps_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: reps_id_seq; Type: SEQUENCE OWNED BY; Schema: maker; Owner: - +-- + +ALTER SEQUENCE maker.reps_id_seq OWNED BY maker.reps.id; + + -- -- Name: logs; Type: TABLE; Schema: public; Owner: - -- @@ -583,6 +615,13 @@ ALTER TABLE ONLY maker.peps ALTER COLUMN id SET DEFAULT nextval('maker.peps_id_s ALTER TABLE ONLY maker.pips ALTER COLUMN id SET DEFAULT nextval('maker.pips_id_seq'::regclass); +-- +-- Name: reps id; Type: DEFAULT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.reps ALTER COLUMN id SET DEFAULT nextval('maker.reps_id_seq'::regclass); + + -- -- Name: blocks id; Type: DEFAULT; Schema: public; Owner: - -- @@ -694,6 +733,14 @@ ALTER TABLE ONLY maker.pips ADD CONSTRAINT pips_pkey PRIMARY KEY (id); +-- +-- Name: reps reps_pkey; Type: CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.reps + ADD CONSTRAINT reps_pkey PRIMARY KEY (id); + + -- -- Name: blocks blocks_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -857,6 +904,14 @@ ALTER TABLE ONLY maker.pips ADD CONSTRAINT headers_fk FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; +-- +-- Name: reps headers_fk; Type: FK CONSTRAINT; Schema: maker; Owner: - +-- + +ALTER TABLE ONLY maker.reps + ADD CONSTRAINT headers_fk FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE; + + -- -- Name: transactions blocks_fk; Type: FK CONSTRAINT; Schema: public; Owner: - -- diff --git a/pkg/transformers/price_feeds/constants.go b/pkg/transformers/price_feeds/constants.go index 480cc314..07f4a674 100644 --- a/pkg/transformers/price_feeds/constants.go +++ b/pkg/transformers/price_feeds/constants.go @@ -8,12 +8,14 @@ import ( var ( ErrMultipleLogs = errors.New("multiple matching logs found in block") ErrNoMatchingLog = errors.New("no matching log") - PeekMethodName = "peek" - PepLogTopic0 = "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527" - PipLogTopic0 = "0x1817835800000000000000000000000000000000000000000000000000000000" - PepAddress = "0x99041F808D598B782D5a3e498681C2452A31da08" - PipAddress = "0x729D19f657BD0614b4985Cf1D82531c67569197B" - PipMedianizerABI = `[{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"poke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"poke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"compute","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"unset","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"indexes","outputs":[{"name":"","type":"bytes12"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"next","outputs":[{"name":"","type":"bytes12"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"read","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"peek","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes12"}],"name":"values","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"min_","type":"uint96"}],"name":"setMin","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"void","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"},{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"}],"name":"unset","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"next_","type":"bytes12"}],"name":"setNext","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"min","outputs":[{"name":"","type":"uint96"}],"payable":false,"type":"function"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}]` Ether = big.NewFloat(1e18) + PeekMethodName = "peek" + PepAddress = "0x99041F808D598B782D5a3e498681C2452A31da08" + PepLogTopic0 = "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527" + PipAddress = "0x729D19f657BD0614b4985Cf1D82531c67569197B" + PipLogTopic0 = "0x1817835800000000000000000000000000000000000000000000000000000000" + PipMedianizerABI = `[{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"","type":"bytes32"}],"name":"poke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"poke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"compute","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wat","type":"address"}],"name":"unset","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"indexes","outputs":[{"name":"","type":"bytes12"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"next","outputs":[{"name":"","type":"bytes12"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"read","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"peek","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes12"}],"name":"values","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"min_","type":"uint96"}],"name":"setMin","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"void","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"},{"name":"wat","type":"address"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"pos","type":"bytes12"}],"name":"unset","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"next_","type":"bytes12"}],"name":"setNext","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"min","outputs":[{"name":"","type":"uint96"}],"payable":false,"type":"function"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}]` Ray = big.NewFloat(1e27) + RepAddress = "0xF5f94b7F9De14D43112e713835BCef2d55b76c1C" + RepLogTopic0 = "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527" ) diff --git a/pkg/transformers/price_feeds/rep/fetcher.go b/pkg/transformers/price_feeds/rep/fetcher.go new file mode 100644 index 00000000..edb9289c --- /dev/null +++ b/pkg/transformers/price_feeds/rep/fetcher.go @@ -0,0 +1,40 @@ +package rep + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds" + "math/big" +) + +type IRepFetcher interface { + FetchRepValue(header core.Header) (string, error) +} + +type RepFetcher struct { + chain core.BlockChain +} + +func NewRepFetcher(chain core.BlockChain) RepFetcher { + return RepFetcher{ + chain: chain, + } +} + +func (fetcher RepFetcher) FetchRepValue(header core.Header) (string, error) { + blockNumber := big.NewInt(header.BlockNumber) + logs, err := fetcher.chain.GetLogs(price_feeds.RepAddress, price_feeds.RepLogTopic0, blockNumber, blockNumber) + return fetcher.getLogValue(logs, err) +} + +func (fetcher RepFetcher) getLogValue(logs []core.Log, err error) (string, error) { + if err != nil { + return "", err + } + if len(logs) < 1 { + return "", price_feeds.ErrNoMatchingLog + } + if len(logs) > 1 { + return "", price_feeds.ErrMultipleLogs + } + return logs[0].Data, nil +} diff --git a/pkg/transformers/price_feeds/rep/fetcher_test.go b/pkg/transformers/price_feeds/rep/fetcher_test.go new file mode 100644 index 00000000..ba1fac2c --- /dev/null +++ b/pkg/transformers/price_feeds/rep/fetcher_test.go @@ -0,0 +1,62 @@ +package rep_test + +import ( + . "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/price_feeds" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep" + "math/big" +) + +var _ = Describe("Rep fetcher", func() { + It("gets logs describing updated rep/usd value", func() { + mockBlockChain := fakes.NewMockBlockChain() + mockBlockChain.SetGetLogsReturnLogs([]core.Log{{}}) + fetcher := rep.NewRepFetcher(mockBlockChain) + header := core.Header{ + BlockNumber: 100, + Hash: "", + Raw: nil, + } + + _, err := fetcher.FetchRepValue(header) + + Expect(err).NotTo(HaveOccurred()) + mockBlockChain.AssertGetLogsCalledWith(price_feeds.RepAddress, price_feeds.RepLogTopic0, big.NewInt(header.BlockNumber), big.NewInt(header.BlockNumber)) + }) + + It("returns error if getting logs fails", func() { + mockBlockChain := fakes.NewMockBlockChain() + mockBlockChain.SetGetLogsReturnLogs([]core.Log{{}}) + mockBlockChain.SetGetLogsReturnErr(fakes.FakeError) + fetcher := rep.NewRepFetcher(mockBlockChain) + + _, err := fetcher.FetchRepValue(core.Header{}) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + + It("returns no matching logs error if no logs returned", func() { + mockBlockChain := fakes.NewMockBlockChain() + fetcher := rep.NewRepFetcher(mockBlockChain) + + _, err := fetcher.FetchRepValue(core.Header{}) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(price_feeds.ErrNoMatchingLog)) + }) + + It("returns error if more than one matching logs returned", func() { + mockBlockChain := fakes.NewMockBlockChain() + mockBlockChain.SetGetLogsReturnLogs([]core.Log{{}, {}}) + fetcher := rep.NewRepFetcher(mockBlockChain) + + _, err := fetcher.FetchRepValue(core.Header{}) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(price_feeds.ErrMultipleLogs)) + }) +}) diff --git a/pkg/transformers/price_feeds/rep/rep_suite_test.go b/pkg/transformers/price_feeds/rep/rep_suite_test.go new file mode 100644 index 00000000..a4860d32 --- /dev/null +++ b/pkg/transformers/price_feeds/rep/rep_suite_test.go @@ -0,0 +1,13 @@ +package rep_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestRep(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Rep Suite") +} diff --git a/pkg/transformers/price_feeds/rep/repository.go b/pkg/transformers/price_feeds/rep/repository.go new file mode 100644 index 00000000..eaed6597 --- /dev/null +++ b/pkg/transformers/price_feeds/rep/repository.go @@ -0,0 +1,25 @@ +package rep + +import ( + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds" +) + +type IRepRepository interface { + CreateRep(rep price_feeds.PriceUpdate) error +} + +type RepRepository struct { + db *postgres.DB +} + +func NewRepRepository(db *postgres.DB) RepRepository { + return RepRepository{ + db: db, + } +} + +func (repository RepRepository) CreateRep(rep price_feeds.PriceUpdate) error { + _, err := repository.db.Exec(`INSERT INTO maker.reps (block_number, header_id, usd_value) VALUES ($1, $2, $3::NUMERIC)`, rep.BlockNumber, rep.HeaderID, rep.UsdValue) + return err +} diff --git a/pkg/transformers/price_feeds/rep/repository_test.go b/pkg/transformers/price_feeds/rep/repository_test.go new file mode 100644 index 00000000..003df5b7 --- /dev/null +++ b/pkg/transformers/price_feeds/rep/repository_test.go @@ -0,0 +1,49 @@ +package rep_test + +import ( + . "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/price_feeds" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("Rep repository", func() { + It("returns header if matching header does not exist", func() { + db := test_config.NewTestDB(core.Node{}) + repository := rep.NewRepRepository(db) + pepToAdd := price_feeds.PriceUpdate{ + BlockNumber: 0, + HeaderID: 0, + UsdValue: "123.456", + } + + err := repository.CreateRep(pepToAdd) + + Expect(err).To(HaveOccurred()) + }) + + It("creates a rep when matching header exists", func() { + db := test_config.NewTestDB(core.Node{}) + repository := rep.NewRepRepository(db) + header := core.Header{BlockNumber: 12345} + headerRepository := repositories.NewHeaderRepository(db) + headerID, err := headerRepository.CreateOrUpdateHeader(header) + Expect(err).NotTo(HaveOccurred()) + pepToAdd := price_feeds.PriceUpdate{ + BlockNumber: header.BlockNumber, + HeaderID: headerID, + UsdValue: "123.456", + } + + err = repository.CreateRep(pepToAdd) + + Expect(err).NotTo(HaveOccurred()) + var dbRep price_feeds.PriceUpdate + err = db.Get(&dbRep, `SELECT block_number, header_id, usd_value FROM maker.reps WHERE header_id = $1`, pepToAdd.HeaderID) + Expect(err).NotTo(HaveOccurred()) + Expect(dbRep).To(Equal(pepToAdd)) + }) +}) diff --git a/pkg/transformers/price_feeds/rep/transformer.go b/pkg/transformers/price_feeds/rep/transformer.go new file mode 100644 index 00000000..2803855a --- /dev/null +++ b/pkg/transformers/price_feeds/rep/transformer.go @@ -0,0 +1,43 @@ +package rep + +import ( + "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds" +) + +type RepTransformer struct { + fetcher IRepFetcher + repository IRepRepository +} + +func NewRepTransformer(chain core.BlockChain, db *postgres.DB) RepTransformer { + fetcher := NewRepFetcher(chain) + repository := NewRepRepository(db) + return RepTransformer{ + fetcher: fetcher, + repository: repository, + } +} + +func (transformer RepTransformer) Execute(header core.Header, headerID int64) error { + logValue, err := transformer.fetcher.FetchRepValue(header) + if err != nil { + if err == price_feeds.ErrNoMatchingLog { + return nil + } + return err + } + rep := getRep(logValue, header, headerID) + return transformer.repository.CreateRep(rep) +} + +func getRep(logValue string, header core.Header, headerID int64) price_feeds.PriceUpdate { + valueInUSD := price_feeds.Convert("wad", logValue, 15) + rep := price_feeds.PriceUpdate{ + BlockNumber: header.BlockNumber, + HeaderID: headerID, + UsdValue: valueInUSD, + } + return rep +} diff --git a/pkg/transformers/price_feeds/rep/transformer_test.go b/pkg/transformers/price_feeds/rep/transformer_test.go new file mode 100644 index 00000000..9194b31b --- /dev/null +++ b/pkg/transformers/price_feeds/rep/transformer_test.go @@ -0,0 +1,45 @@ +package rep_test + +import ( + "github.com/ethereum/go-ethereum/common" + . "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/fakes" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds" + "github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep" + "github.com/vulcanize/vulcanizedb/test_config" +) + +var _ = Describe("Rep transformer", func() { + It("returns nil if no logs found", func() { + chain := fakes.NewMockBlockChain() + db := test_config.NewTestDB(core.Node{}) + transformer := rep.NewRepTransformer(chain, db) + + err := transformer.Execute(core.Header{}, 123) + + Expect(err).NotTo(HaveOccurred()) + }) + + It("creates rep row for found log", func() { + chain := fakes.NewMockBlockChain() + chain.SetGetLogsReturnLogs([]core.Log{{Data: common.ToHex([]byte{1, 2, 3, 4, 5})}}) + db := test_config.NewTestDB(core.Node{}) + headerRepository := repositories.NewHeaderRepository(db) + header := core.Header{BlockNumber: 12345} + headerID, err := headerRepository.CreateOrUpdateHeader(header) + Expect(err).NotTo(HaveOccurred()) + transformer := rep.NewRepTransformer(chain, db) + + err = transformer.Execute(header, headerID) + + Expect(err).NotTo(HaveOccurred()) + var dbRep price_feeds.PriceUpdate + err = db.Get(&dbRep, `SELECT block_number, header_id, usd_value FROM maker.reps WHERE header_id = $1`, headerID) + Expect(err).NotTo(HaveOccurred()) + Expect(dbRep.BlockNumber).To(Equal(header.BlockNumber)) + }) +})