Add Rep price feed

This commit is contained in:
Rob Mulholand 2018-07-26 15:51:49 -05:00
parent 2949996d22
commit 6e68dc4a92
12 changed files with 352 additions and 6 deletions

View File

@ -34,6 +34,7 @@ import (
"github.com/vulcanize/vulcanizedb/pkg/transformers" "github.com/vulcanize/vulcanizedb/pkg/transformers"
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/pep" "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/pip"
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds/rep"
"github.com/vulcanize/vulcanizedb/utils" "github.com/vulcanize/vulcanizedb/utils"
) )
@ -94,6 +95,7 @@ func syncPriceFeeds() {
transformers := []transformers.Transformer{ transformers := []transformers.Transformer{
pep.NewPepTransformer(blockChain, &db), pep.NewPepTransformer(blockChain, &db),
pip.NewPipTransformer(blockChain, &db), pip.NewPipTransformer(blockChain, &db),
rep.NewRepTransformer(blockChain, &db),
} }
go backFillPriceFeeds(blockChain, headerRepository, missingBlocksPopulated, startingBlockNumber, transformers) go backFillPriceFeeds(blockChain, headerRepository, missingBlocksPopulated, startingBlockNumber, transformers)

View File

@ -0,0 +1 @@
DROP TABLE maker.reps;

View File

@ -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
);

View File

@ -183,6 +183,38 @@ CREATE SEQUENCE maker.pips_id_seq
ALTER SEQUENCE maker.pips_id_seq OWNED BY maker.pips.id; 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: - -- 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); 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: - -- Name: blocks id; Type: DEFAULT; Schema: public; Owner: -
-- --
@ -694,6 +733,14 @@ ALTER TABLE ONLY maker.pips
ADD CONSTRAINT pips_pkey PRIMARY KEY (id); 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: - -- 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; 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: - -- Name: transactions blocks_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --

View File

@ -8,12 +8,14 @@ import (
var ( var (
ErrMultipleLogs = errors.New("multiple matching logs found in block") ErrMultipleLogs = errors.New("multiple matching logs found in block")
ErrNoMatchingLog = errors.New("no matching log") 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) 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) Ray = big.NewFloat(1e27)
RepAddress = "0xF5f94b7F9De14D43112e713835BCef2d55b76c1C"
RepLogTopic0 = "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527"
) )

View File

@ -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
}

View File

@ -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))
})
})

View File

@ -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")
}

View File

@ -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
}

View File

@ -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))
})
})

View File

@ -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
}

View File

@ -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))
})
})