Merge pull request #12 from 8thlight/bite-event

bite event transformer
This commit is contained in:
Takayuki Goto 2018-09-04 09:58:51 -05:00 committed by GitHub
commit 807b7accd6
21 changed files with 1080 additions and 0 deletions

View File

@ -27,6 +27,9 @@ Vulcanize DB is a set of tools that make it easier for developers to write appli
* See below for configuring additional environments
## Create a migration file (up and down)
1. ./script/create_migrate create_bite_table
## Configuration
- To use a local Ethereum node, copy `environments/public.toml.example` to
`environments/public.toml` and update the `ipcPath` and `levelDbPath`.

View File

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

View File

@ -0,0 +1,14 @@
CREATE TABLE maker.bite (
id SERIAL PRIMARY KEY,
header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE,
ilk bytea,
lad bytea,
ink VARCHAR,
art VARCHAR,
iArt VARCHAR,
tab NUMERIC,
flip VARCHAR,
tx_idx INTEGER NOT NUll,
raw_log JSONB,
UNIQUE (header_id, tx_idx)
)

View File

@ -40,6 +40,45 @@ SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: bite; Type: TABLE; Schema: maker; Owner: -
--
CREATE TABLE maker.bite (
id integer NOT NULL,
header_id integer NOT NULL,
ilk bytea,
lad bytea,
ink character varying,
art character varying,
iart character varying,
tab numeric,
flip character varying,
tx_idx integer NOT NULL,
raw_log jsonb
);
--
-- Name: bite_id_seq; Type: SEQUENCE; Schema: maker; Owner: -
--
CREATE SEQUENCE maker.bite_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: bite_id_seq; Type: SEQUENCE OWNED BY; Schema: maker; Owner: -
--
ALTER SEQUENCE maker.bite_id_seq OWNED BY maker.bite.id;
--
-- Name: flip_kick; Type: TABLE; Schema: maker; Owner: -
--
@ -564,6 +603,13 @@ CREATE VIEW public.watched_event_logs AS
WHERE ((((log_filters.topic0)::text = (logs.topic0)::text) OR (log_filters.topic0 IS NULL)) AND (((log_filters.topic1)::text = (logs.topic1)::text) OR (log_filters.topic1 IS NULL)) AND (((log_filters.topic2)::text = (logs.topic2)::text) OR (log_filters.topic2 IS NULL)) AND (((log_filters.topic3)::text = (logs.topic3)::text) OR (log_filters.topic3 IS NULL)));
--
-- Name: bite id; Type: DEFAULT; Schema: maker; Owner: -
--
ALTER TABLE ONLY maker.bite ALTER COLUMN id SET DEFAULT nextval('maker.bite_id_seq'::regclass);
--
-- Name: flip_kick db_id; Type: DEFAULT; Schema: maker; Owner: -
--
@ -655,6 +701,22 @@ ALTER TABLE ONLY public.transactions ALTER COLUMN id SET DEFAULT nextval('public
ALTER TABLE ONLY public.watched_contracts ALTER COLUMN contract_id SET DEFAULT nextval('public.watched_contracts_contract_id_seq'::regclass);
--
-- Name: bite bite_header_id_tx_idx_key; Type: CONSTRAINT; Schema: maker; Owner: -
--
ALTER TABLE ONLY maker.bite
ADD CONSTRAINT bite_header_id_tx_idx_key UNIQUE (header_id, tx_idx);
--
-- Name: bite bite_pkey; Type: CONSTRAINT; Schema: maker; Owner: -
--
ALTER TABLE ONLY maker.bite
ADD CONSTRAINT bite_pkey PRIMARY KEY (id);
--
-- Name: flip_kick flip_kick_id_key; Type: CONSTRAINT; Schema: maker; Owner: -
--
@ -850,6 +912,14 @@ CREATE INDEX tx_from_index ON public.transactions USING btree (tx_from);
CREATE INDEX tx_to_index ON public.transactions USING btree (tx_to);
--
-- Name: bite bite_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: -
--
ALTER TABLE ONLY maker.bite
ADD CONSTRAINT bite_header_id_fkey FOREIGN KEY (header_id) REFERENCES public.headers(id) ON DELETE CASCADE;
--
-- Name: flip_kick flip_kick_header_id_fkey; Type: FK CONSTRAINT; Schema: maker; Owner: -
--

View File

@ -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 bite_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestBite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Bite Suite")
}

View File

@ -0,0 +1,29 @@
/*
* 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 bite
import (
"github.com/vulcanize/vulcanizedb/pkg/transformers/shared"
)
var BiteConfig = shared.TransformerConfig{
ContractAddresses: "0xe0f0fa6982c59d8aa4ae0134bfe048327bd788cacf758b643ca41f055ffce76c", //this is a temporary address deployed locally
ContractAbi: BiteABI,
Topics: []string{BiteSignature},
StartingBlockNumber: 0,
EndingBlockNumber: 100,
}

View File

@ -0,0 +1,22 @@
/*
* 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 bite
var (
BiteABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"vat\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"vow\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"flips\",\"outputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"lad\",\"type\":\"bytes32\"},{\"name\":\"ink\",\"type\":\"uint256\"},{\"name\":\"tab\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"nflip\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"live\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"wards\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"ilks\",\"outputs\":[{\"name\":\"flip\",\"type\":\"address\"},{\"name\":\"chop\",\"type\":\"uint256\"},{\"name\":\"lump\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"pit\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"vat_\",\"type\":\"address\"},{\"name\":\"pit_\",\"type\":\"address\"},{\"name\":\"vow_\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"ilk\",\"type\":\"bytes32\"},{\"indexed\":true,\"name\":\"lad\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"ink\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"art\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"tab\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"flip\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"iArt\",\"type\":\"uint256\"}],\"name\":\"Bite\",\"type\":\"event\"},{\"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\"},{\"constant\":false,\"inputs\":[{\"name\":\"guy\",\"type\":\"address\"}],\"name\":\"rely\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"guy\",\"type\":\"address\"}],\"name\":\"deny\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"what\",\"type\":\"bytes32\"},{\"name\":\"data\",\"type\":\"uint256\"}],\"name\":\"file\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"what\",\"type\":\"bytes32\"},{\"name\":\"flip\",\"type\":\"address\"}],\"name\":\"file\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"ilk\",\"type\":\"bytes32\"},{\"name\":\"lad\",\"type\":\"bytes32\"}],\"name\":\"bite\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"n\",\"type\":\"uint256\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"flip\",\"outputs\":[{\"name\":\"id\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
BiteSignature = "0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"
)

View File

@ -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 bite
import (
"encoding/json"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/geth"
"github.com/vulcanize/vulcanizedb/pkg/transformers/utilities"
)
type Converter interface {
ToEntity(contractAddress string, contractAbi string, ethLog types.Log) (BiteEntity, error)
ToModel(flipKick BiteEntity) (BiteModel, error)
}
type BiteConverter struct{}
func (BiteConverter) ToEntity(contractAddress string, contractAbi string, ethLog types.Log) (BiteEntity, error) {
entity := BiteEntity{}
address := common.HexToAddress(contractAddress)
abi, err := geth.ParseAbi(contractAbi)
if err != nil {
return entity, err
}
contract := bind.NewBoundContract(address, abi, nil, nil, nil)
err = contract.UnpackLog(&entity, "Bite", ethLog)
if err != nil {
return entity, err
}
entity.Raw = ethLog
entity.TransactionIndex = ethLog.TxIndex
return entity, nil
}
func (converter BiteConverter) ToModel(entity BiteEntity) (BiteModel, error) {
id := entity.Id.String()
ilk := entity.Ilk[:]
lad := entity.Lad[:]
ink := entity.Ink.String()
art := entity.Art.String()
iArt := entity.IArt.String()
tab := entity.Tab.String()
flip := entity.Flip.String()
txIdx := entity.TransactionIndex
rawLogJson, err := json.Marshal(entity.Raw)
rawLogString := string(rawLogJson)
if err != nil {
return BiteModel{}, err
}
return BiteModel{
Id: utilities.ConvertNilToEmptyString(id),
Ilk: ilk,
Lad: lad,
Ink: utilities.ConvertNilToEmptyString(ink),
Art: utilities.ConvertNilToEmptyString(art),
IArt: utilities.ConvertNilToEmptyString(iArt),
Tab: utilities.ConvertNilToEmptyString(tab),
Flip: utilities.ConvertNilToEmptyString(flip),
TransactionIndex: txIdx,
Raw: rawLogString,
}, nil
}

View File

@ -0,0 +1,93 @@
/*
* 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 bite_test
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/transformers/bite"
"github.com/vulcanize/vulcanizedb/pkg/transformers/test_data"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Bite Converter", func() {
var converter = bite.BiteConverter{}
Describe("ToEntity", func() {
It("converts an eth log to a bite entity", func() {
entity, err := converter.ToEntity(test_data.TemporaryBiteAddress, bite.BiteABI, test_data.EthBiteLog)
Expect(err).NotTo(HaveOccurred())
Expect(entity.Ilk).To(Equal(test_data.BiteEntity.Ilk))
Expect(entity.Lad).To(Equal(test_data.BiteEntity.Lad))
Expect(entity.Ink).To(Equal(test_data.BiteEntity.Ink))
Expect(entity.Art).To(Equal(test_data.BiteEntity.Art))
Expect(entity.Tab).To(Equal(test_data.BiteEntity.Tab))
Expect(entity.Flip).To(Equal(test_data.BiteEntity.Flip))
Expect(entity.IArt).To(Equal(test_data.BiteEntity.IArt))
Expect(entity.TransactionIndex).To(Equal(test_data.BiteEntity.TransactionIndex))
Expect(entity.Raw).To(Equal(test_data.BiteEntity.Raw))
})
It("returns an error if converting log to entity fails", func() {
_, err := converter.ToEntity(test_data.TemporaryBiteAddress, "error abi", test_data.EthBiteLog)
Expect(err).To(HaveOccurred())
})
})
Describe("ToModel", func() {
var emptyEntity = bite.BiteEntity{}
BeforeEach(func() {
emptyEntity.Id = big.NewInt(1)
})
It("converts an Entity to a Model", func() {
model, err := converter.ToModel(test_data.BiteEntity)
Expect(err).NotTo(HaveOccurred())
Expect(model).To(Equal(test_data.BiteModel))
Expect(model.TransactionIndex).To(Equal(test_data.BiteModel.TransactionIndex))
})
It("handles nil values", func() {
emptyLog, err := json.Marshal(types.Log{})
Expect(err).NotTo(HaveOccurred())
expectedModel := bite.BiteModel{
Id: "1",
Ilk: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Lad: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Ink: "",
Art: "",
IArt: "",
Tab: "",
Flip: "",
TransactionIndex: 0,
Raw: string(emptyLog),
}
model, err := converter.ToModel(emptyEntity)
Expect(err).NotTo(HaveOccurred())
Expect(model).To(Equal(expectedModel))
})
})
})

View File

@ -0,0 +1,33 @@
// 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 bite
import (
"github.com/ethereum/go-ethereum/core/types"
"math/big"
)
type BiteEntity struct {
Id *big.Int
Ilk [32]byte
Lad [32]byte
Ink *big.Int
Art *big.Int
Tab *big.Int
Flip *big.Int
IArt *big.Int
TransactionIndex uint
Raw types.Log
}

View File

@ -0,0 +1,83 @@
/*
* 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 bite_test
import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/ethereum/go-ethereum/rpc"
"github.com/vulcanize/vulcanizedb/pkg/geth"
"github.com/vulcanize/vulcanizedb/pkg/geth/client"
rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc"
"github.com/vulcanize/vulcanizedb/pkg/geth/node"
"github.com/vulcanize/vulcanizedb/pkg/transformers/bite"
"github.com/vulcanize/vulcanizedb/pkg/transformers/shared"
"github.com/vulcanize/vulcanizedb/pkg/transformers/test_data"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Integration tests", func() {
XIt("Fetches bite event logs from a local test chain", func() {
ipcPath := test_config.TestClient.IPCPath
rawRpcClient, err := rpc.Dial(ipcPath)
Expect(err).NotTo(HaveOccurred())
rpcClient := client.NewRpcClient(rawRpcClient, ipcPath)
ethClient := ethclient.NewClient(rawRpcClient)
blockChainClient := client.NewEthClient(ethClient)
realNode := node.MakeNode(rpcClient)
transactionConverter := rpc2.NewRpcTransactionConverter(ethClient)
realBlockChain := geth.NewBlockChain(blockChainClient, realNode, transactionConverter)
realFetcher := shared.NewFetcher(realBlockChain)
topic0 := common.HexToHash(bite.BiteSignature)
topics := [][]common.Hash{{topic0}}
result, err := realFetcher.FetchLogs(test_data.TemporaryBiteAddress, topics, int64(26))
Expect(err).NotTo(HaveOccurred())
Expect(len(result) > 0).To(BeTrue())
Expect(result[0].Address).To(Equal(common.HexToAddress(test_data.TemporaryBiteAddress)))
Expect(result[0].TxHash).To(Equal(test_data.EthBiteLog.TxHash))
Expect(result[0].BlockNumber).To(Equal(test_data.EthBiteLog.BlockNumber))
Expect(result[0].Topics).To(Equal(test_data.EthBiteLog.Topics))
Expect(result[0].Index).To(Equal(test_data.EthBiteLog.Index))
})
It("unpacks an event log", func() {
address := common.HexToAddress(test_data.TemporaryBiteAddress)
abi, err := geth.ParseAbi(bite.BiteABI)
Expect(err).NotTo(HaveOccurred())
contract := bind.NewBoundContract(address, abi, nil, nil, nil)
entity := &bite.BiteEntity{}
var eventLog = test_data.EthBiteLog
err = contract.UnpackLog(entity, "Bite", eventLog)
Expect(err).NotTo(HaveOccurred())
expectedEntity := test_data.BiteEntity
Expect(entity.Art).To(Equal(expectedEntity.Art))
Expect(entity.Ilk).To(Equal(expectedEntity.Ilk))
Expect(entity.Ink).To(Equal(expectedEntity.Ink))
})
})

View File

@ -0,0 +1,28 @@
// 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 bite
type BiteModel struct {
Id string
Ilk []byte
Lad []byte
Ink string
Art string
IArt string
Tab string
Flip string
TransactionIndex uint `db:"tx_idx"`
Raw string `db:"raw_log"`
}

View File

@ -0,0 +1,49 @@
package bite
import (
"github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
)
type Repository interface {
Create(headerID int64, model BiteModel) error
MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error)
}
type BiteRepository struct {
db *postgres.DB
}
func NewBiteRepository(db *postgres.DB) Repository {
return BiteRepository{db: db}
}
func (repository BiteRepository) Create(headerID int64, model BiteModel) error {
_, err := repository.db.Exec(
`INSERT into maker.bite (header_id, id, ilk, lad, ink, art, iart, tab, flip, tx_idx, raw_log)
VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
headerID, model.Id, model.Ilk, model.Lad, model.Ink, model.Art, model.IArt, model.Tab, model.Flip, model.TransactionIndex, model.Raw,
)
if err != nil {
return err
}
return nil
}
func (repository BiteRepository) MissingHeaders(startingBlockNumber int64, 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.bite 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
}

View File

@ -0,0 +1,135 @@
// 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 bite_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"database/sql"
"github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/transformers/bite"
"github.com/vulcanize/vulcanizedb/pkg/transformers/test_data"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Bite repository", func() {
Describe("Create", func() {
It("persists a bite record", func() {
node := core.Node{}
db := test_config.NewTestDB(node)
test_config.CleanTestDB(db)
headerRepository := repositories.NewHeaderRepository(db)
headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{})
Expect(err).NotTo(HaveOccurred())
biteRepository := bite.NewBiteRepository(db)
err = biteRepository.Create(headerID, test_data.BiteModel)
Expect(err).NotTo(HaveOccurred())
var dbBite bite.BiteModel
err = db.Get(&dbBite, `SELECT id, ilk, lad, ink, art, tab, flip, tx_idx, raw_log FROM maker.bite WHERE header_id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
Expect(dbBite.Id).To(Equal(test_data.BiteModel.Id))
Expect(dbBite.Ilk).To(Equal(test_data.BiteModel.Ilk))
Expect(dbBite.Lad).To(Equal(test_data.BiteModel.Lad))
Expect(dbBite.Art).To(Equal(test_data.BiteModel.Art))
Expect(dbBite.Tab).To(Equal(test_data.BiteModel.Tab))
Expect(dbBite.Flip).To(Equal(test_data.BiteModel.Flip))
Expect(dbBite.TransactionIndex).To(Equal(test_data.BiteModel.TransactionIndex))
Expect(dbBite.Raw).To(MatchJSON(test_data.BiteModel.Raw))
})
It("does not duplicate bite events", func() {
node := core.Node{}
db := test_config.NewTestDB(node)
test_config.CleanTestDB(db)
headerRepository := repositories.NewHeaderRepository(db)
headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{})
Expect(err).NotTo(HaveOccurred())
biteRepository := bite.NewBiteRepository(db)
err = biteRepository.Create(headerID, test_data.BiteModel)
Expect(err).NotTo(HaveOccurred())
var anotherBiteModel = bite.BiteModel{
Id: "11",
Ilk: test_data.BiteModel.Ilk,
Lad: test_data.BiteModel.Lad,
Ink: test_data.BiteModel.Ink,
Art: test_data.BiteModel.Art,
Tab: test_data.BiteModel.Tab,
Flip: test_data.BiteModel.Flip,
IArt: test_data.BiteModel.IArt,
TransactionIndex: test_data.BiteModel.TransactionIndex,
Raw: test_data.BiteModel.Raw,
}
err = biteRepository.Create(headerID, anotherBiteModel)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("pq: duplicate key value violates unique constraint"))
})
It("removes bite if corresponding header is deleted", func() {
node := core.Node{}
db := test_config.NewTestDB(node)
test_config.CleanTestDB(db)
headerRepository := repositories.NewHeaderRepository(db)
headerID, err := headerRepository.CreateOrUpdateHeader(core.Header{})
Expect(err).NotTo(HaveOccurred())
biteRepository := bite.NewBiteRepository(db)
err = biteRepository.Create(headerID, test_data.BiteModel)
Expect(err).NotTo(HaveOccurred())
_, err = db.Exec(`DELETE FROM headers WHERE id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
var dbBite bite.BiteModel
err = db.Get(&dbBite, `SELECT id, ilk, lad, ink, art, tab, flip, tx_idx, raw_log FROM maker.bite WHERE header_id = $1`, headerID)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(sql.ErrNoRows))
})
})
Describe("MissingHeaders", func() {
It("returns headers with no associated bite event", func() {
node := core.Node{}
db := test_config.NewTestDB(node)
test_config.CleanTestDB(db)
headerRepository := repositories.NewHeaderRepository(db)
startingBlockNumber := int64(1)
biteBlockNumber := int64(2)
endingBlockNumber := int64(3)
blockNumbers := []int64{startingBlockNumber, biteBlockNumber, 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())
}
biteRepository := bite.NewBiteRepository(db)
err := biteRepository.Create(headerIDs[1], test_data.BiteModel)
Expect(err).NotTo(HaveOccurred())
headers, err := biteRepository.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)))
})
})
})

View File

@ -0,0 +1,88 @@
/*
* 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 bite
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"
"log"
)
type BiteTransformer struct {
Repository Repository
Fetcher shared.LogFetcher
Converter Converter
Config shared.TransformerConfig
}
type BiteTransformerInitializer struct {
Config shared.TransformerConfig
}
func (i BiteTransformerInitializer) NewBiteTransformer(db *postgres.DB, blockChain core.BlockChain) shared.Transformer {
fetcher := shared.NewFetcher(blockChain)
repository := NewBiteRepository(db)
transformer := BiteTransformer{
Fetcher: fetcher,
Repository: repository,
Converter: BiteConverter{},
Config: i.Config,
}
return transformer
}
func (b BiteTransformer) Execute() error {
config := b.Config
topics := [][]common.Hash{{common.HexToHash(shared.BiteSignature)}}
missingHeaders, err := b.Repository.MissingHeaders(config.StartingBlockNumber, config.EndingBlockNumber)
if err != nil {
log.Println("Error fetching missing headers:", err)
return err
}
for _, header := range missingHeaders {
ethLogs, err := b.Fetcher.FetchLogs(config.ContractAddresses, topics, header.BlockNumber)
if err != nil {
log.Println("Error fetching matching logs:", err)
return err
}
for _, ethLog := range ethLogs {
entity, err := b.Converter.ToEntity(config.ContractAddresses, config.ContractAbi, ethLog)
model, err := b.Converter.ToModel(entity)
if err != nil {
log.Println("Error converting logs:", err)
return err
}
err = b.Repository.Create(header.Id, model)
if err != nil {
log.Println("Error persisting bite record:", err)
return err
}
}
}
return nil
}
func (b BiteTransformer) SetConfig(config shared.TransformerConfig) {
b.Config = config
}

View File

@ -0,0 +1,144 @@
/*
* 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 bite_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/bite"
"github.com/vulcanize/vulcanizedb/pkg/transformers/shared"
"github.com/vulcanize/vulcanizedb/pkg/transformers/test_data"
"github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks"
bite_mocks "github.com/vulcanize/vulcanizedb/pkg/transformers/test_data/mocks/bite"
"math/rand"
)
var _ = Describe("Bite Transformer", func() {
var repository bite_mocks.MockBiteRepository
var fetcher mocks.MockLogFetcher
var converter bite_mocks.MockBiteConverter
var transformer bite.BiteTransformer
var blockNumber1 = rand.Int63()
var blockNumber2 = rand.Int63()
var testConfig shared.TransformerConfig
BeforeEach(func() {
repository = bite_mocks.MockBiteRepository{}
fetcher = mocks.MockLogFetcher{}
converter = bite_mocks.MockBiteConverter{}
transformer = bite.BiteTransformer{
Repository: &repository,
Fetcher: &fetcher,
Converter: &converter,
Config: bite.BiteConfig,
}
testConfig = shared.TransformerConfig{
ContractAddresses: "0x12345",
ContractAbi: "test abi",
Topics: []string{shared.BiteSignature},
StartingBlockNumber: blockNumber1,
EndingBlockNumber: blockNumber2,
}
transformer.SetConfig(testConfig)
})
It("gets missing headers for blocks in the configured range", func() {
err := transformer.Execute()
Expect(err).NotTo(HaveOccurred())
Expect(repository.PassedStartingBlockNumber).To(Equal(bite.BiteConfig.StartingBlockNumber))
Expect(repository.PassedEndingBlockNumber).To(Equal(bite.BiteConfig.EndingBlockNumber))
})
It("returns an error if it fails to get missing headers", func() {
repository.SetMissingHeadersErr(fakes.FakeError)
err := transformer.Execute()
Expect(err).To(HaveOccurred())
})
It("fetches eth logs for each missing header", func() {
repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1}, {BlockNumber: blockNumber2}})
expectedTopics := [][]common.Hash{{common.HexToHash(shared.BiteSignature)}}
err := transformer.Execute()
Expect(err).NotTo(HaveOccurred())
Expect(fetcher.FetchedBlocks).To(Equal([]int64{blockNumber1, blockNumber2}))
Expect(fetcher.FetchedTopics).To(Equal(expectedTopics))
Expect(fetcher.FetchedContractAddress).To(Equal(bite.BiteConfig.ContractAddresses))
})
It("returns an error if fetching logs fails", func() {
repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}})
fetcher.SetFetcherError(fakes.FakeError)
err := transformer.Execute()
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
It("converts an eth log to an Entity", func() {
repository.SetMissingHeaders([]core.Header{{BlockNumber: 1}})
fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog})
err := transformer.Execute()
Expect(err).NotTo(HaveOccurred())
Expect(converter.ConverterContract).To(Equal(bite.BiteConfig.ContractAddresses))
Expect(converter.ConverterAbi).To(Equal(bite.BiteConfig.ContractAbi))
Expect(converter.LogsToConvert).To(Equal([]types.Log{test_data.EthBiteLog}))
})
It("returns an error if converter fails", func() {
headerId := int64(1)
repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1, Id: headerId}})
fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog})
converter.SetConverterError(fakes.FakeError)
err := transformer.Execute()
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
It("persists the bite record", func() {
headerId := int64(1)
repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1, Id: headerId}})
fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog})
err := transformer.Execute()
Expect(err).NotTo(HaveOccurred())
Expect(repository.PassedHeaderID).To(Equal(headerId))
Expect(repository.PassedBiteModel).To(Equal(test_data.BiteModel))
})
It("returns error if persisting bite record fails", func() {
repository.SetMissingHeaders([]core.Header{{BlockNumber: blockNumber1}})
fetcher.SetFetchedLogs([]types.Log{test_data.EthBiteLog})
repository.SetCreateError(fakes.FakeError)
err := transformer.Execute()
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
})

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,74 @@
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/bite"
"math/big"
"strconv"
)
var (
TemporaryBiteAddress = "0x4ac9588a53dc6008058c86eed71a5c91da793a07"
TemporaryBiteBlockHash = common.HexToHash("0xd130caaccc9203ca63eb149faeb013aed21f0317ce23489c0486da2f9adcd0eb")
TemporaryBiteBlockNumber = int64(26)
TemporaryBiteData = "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005"
TemporaryBiteTransaction = "0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"
)
var (
biteInk = big.NewInt(1)
biteArt = big.NewInt(2)
biteTab = big.NewInt(3)
biteFlip = big.NewInt(4)
biteIArt = big.NewInt(5)
biteRawJson, _ = json.Marshal(EthBiteLog)
biteRawString = string(biteRawJson)
biteIlk = [32]byte{102, 97, 107, 101, 32, 105, 108, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
biteLad = [32]byte{102, 97, 107, 101, 32, 108, 97, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
biteId = int64(1)
)
var EthBiteLog = types.Log{
Address: common.HexToAddress(TemporaryBiteAddress),
Topics: []common.Hash{
common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
common.HexToHash("0x66616b6520696c6b000000000000000000000000000000000000000000000000"),
common.HexToHash("0x66616b65206c6164000000000000000000000000000000000000000000000000"),
},
Data: hexutil.MustDecode(TemporaryBiteData),
BlockNumber: uint64(TemporaryBiteBlockNumber),
TxHash: common.HexToHash(TemporaryBiteTransaction),
TxIndex: 111,
BlockHash: TemporaryBiteBlockHash,
Index: 0,
Removed: false,
}
var BiteEntity = bite.BiteEntity{
Id: big.NewInt(biteId),
Ilk: biteIlk,
Lad: biteLad,
Ink: biteInk,
Art: biteArt,
Tab: biteTab,
Flip: biteFlip,
IArt: biteIArt,
TransactionIndex: EthBiteLog.TxIndex,
Raw: EthBiteLog,
}
var BiteModel = bite.BiteModel{
Id: strconv.FormatInt(biteId, 10),
Ilk: biteIlk[:],
Lad: biteLad[:],
Ink: biteInk.String(),
Art: biteArt.String(),
Tab: biteTab.String(),
Flip: biteFlip.String(),
IArt: biteIArt.String(),
TransactionIndex: EthBiteLog.TxIndex,
Raw: biteRawString,
}

View File

@ -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 bite
import (
"github.com/ethereum/go-ethereum/core/types"
. "github.com/vulcanize/vulcanizedb/pkg/transformers/bite"
"github.com/vulcanize/vulcanizedb/pkg/transformers/test_data"
)
type MockBiteConverter struct {
ConverterContract string
ConverterAbi string
LogsToConvert []types.Log
EntitiesToConvert []BiteEntity
ConverterError error
}
func (mbc *MockBiteConverter) ToEntity(contractAddress string, contractAbi string, ethLog types.Log) (BiteEntity, error) {
mbc.ConverterContract = contractAddress
mbc.ConverterAbi = contractAbi
mbc.LogsToConvert = append(mbc.LogsToConvert, ethLog)
return test_data.BiteEntity, mbc.ConverterError
}
func (mbc *MockBiteConverter) ToModel(entity BiteEntity) (BiteModel, error) {
mbc.EntitiesToConvert = append(mbc.EntitiesToConvert, entity)
return test_data.BiteModel, mbc.ConverterError
}
func (mbc *MockBiteConverter) SetConverterError(err error) {
mbc.ConverterError = err
}

View File

@ -0,0 +1,55 @@
// 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 bite
import (
"github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/transformers/bite"
)
type MockBiteRepository struct {
createError error
PassedEndingBlockNumber int64
PassedBiteModel bite.BiteModel
PassedHeaderID int64
PassedStartingBlockNumber int64
PassedTransactionIndex uint
missingHeaders []core.Header
missingHeadersErr error
}
func (repository *MockBiteRepository) SetCreateError(err error) {
repository.createError = err
}
func (repository *MockBiteRepository) SetMissingHeadersErr(err error) {
repository.missingHeadersErr = err
}
func (repository *MockBiteRepository) SetMissingHeaders(headers []core.Header) {
repository.missingHeaders = headers
}
func (repository *MockBiteRepository) Create(headerID int64, model bite.BiteModel) error {
repository.PassedHeaderID = headerID
repository.PassedBiteModel = model
return repository.createError
}
func (repository *MockBiteRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64) ([]core.Header, error) {
repository.PassedStartingBlockNumber = startingBlockNumber
repository.PassedEndingBlockNumber = endingBlockNumber
return repository.missingHeaders, repository.missingHeadersErr
}

View File

@ -15,6 +15,7 @@
package transformers
import (
"github.com/vulcanize/vulcanizedb/pkg/transformers/bite"
"github.com/vulcanize/vulcanizedb/pkg/transformers/flip_kick"
"github.com/vulcanize/vulcanizedb/pkg/transformers/frob"
"github.com/vulcanize/vulcanizedb/pkg/transformers/price_feeds"
@ -31,8 +32,10 @@ func TransformerInitializers() []shared.TransformerInitializer {
priceFeedTransformerInitializer := price_feeds.PriceFeedTransformerInitializer{Config: priceFeedConfig}
tendConfig := tend.TendConfig
tendTransformerInitializer := tend.TendTransformerInitializer{Config: tendConfig}
biteTransformerInitializer := bite.BiteTransformerInitializer{Config: bite.BiteConfig}
return []shared.TransformerInitializer{
biteTransformerInitializer.NewBiteTransformer,
flipKickTransformerInitializer.NewFlipKickTransformer,
frobTransformerInitializer.NewFrobTransformer,
priceFeedTransformerInitializer.NewPriceFeedTransformer,