reorg pkg/ to prepare to support chains other than ethereumm

This commit is contained in:
Ian Norden
2020-02-20 16:14:16 -06:00
parent 1412f5a7f5
commit da844b0b83
240 changed files with 440 additions and 440 deletions
+19
View File
@@ -0,0 +1,19 @@
package datastore
import "fmt"
func ErrBlockDoesNotExist(blockNumber int64) error {
return fmt.Errorf("Block number %d does not exist", blockNumber)
}
func ErrContractDoesNotExist(contractHash string) error {
return fmt.Errorf("Contract %v does not exist", contractHash)
}
func ErrFilterDoesNotExist(name string) error {
return fmt.Errorf("filter %s does not exist", name)
}
func ErrReceiptDoesNotExist(txHash string) error {
return fmt.Errorf("Receipt for tx: %v does not exist", txHash)
}
+35
View File
@@ -0,0 +1,35 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package ethereum
type DatabaseType int
const (
Level DatabaseType = iota
)
type DatabaseConfig struct {
Type DatabaseType
Path string
}
func CreateDatabaseConfig(dbType DatabaseType, path string) DatabaseConfig {
return DatabaseConfig{
Type: dbType,
Path: path,
}
}
+50
View File
@@ -0,0 +1,50 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package ethereum
import (
"fmt"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/sirupsen/logrus"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/ethereum/level"
)
type Database interface {
GetBlock(hash []byte, blockNumber int64) *types.Block
GetBlockHash(blockNumber int64) []byte
GetBlockReceipts(blockHash []byte, blockNumber int64) types.Receipts
GetHeadBlockNumber() int64
}
func CreateDatabase(config DatabaseConfig) (Database, error) {
switch config.Type {
case Level:
levelDBConnection, err := rawdb.NewLevelDBDatabase(config.Path, 128, 1024, "vdb")
if err != nil {
logrus.Error("CreateDatabase: error connecting to new LDBD: ", err)
return nil, err
}
levelDBReader := level.NewLevelDatabaseReader(levelDBConnection)
levelDB := level.NewLevelDatabase(levelDBReader)
return levelDB, nil
default:
return nil, fmt.Errorf("Unknown ethereum database: %s", config.Path)
}
}
@@ -0,0 +1,56 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package level
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type LevelDatabase struct {
reader Reader
}
func NewLevelDatabase(ldbReader Reader) *LevelDatabase {
return &LevelDatabase{
reader: ldbReader,
}
}
func (l LevelDatabase) GetBlock(blockHash []byte, blockNumber int64) *types.Block {
n := uint64(blockNumber)
h := common.BytesToHash(blockHash)
return l.reader.GetBlock(h, n)
}
func (l LevelDatabase) GetBlockHash(blockNumber int64) []byte {
n := uint64(blockNumber)
h := l.reader.GetCanonicalHash(n)
return h.Bytes()
}
func (l LevelDatabase) GetBlockReceipts(blockHash []byte, blockNumber int64) types.Receipts {
n := uint64(blockNumber)
h := common.BytesToHash(blockHash)
return l.reader.GetBlockReceipts(h, n)
}
func (l LevelDatabase) GetHeadBlockNumber() int64 {
h := l.reader.GetHeadBlockHash()
n := l.reader.GetBlockNumber(h)
return int64(*n)
}
@@ -0,0 +1,61 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package level
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
)
type Reader interface {
GetBlock(hash common.Hash, number uint64) *types.Block
GetBlockNumber(hash common.Hash) *uint64
GetBlockReceipts(hash common.Hash, number uint64) types.Receipts
GetCanonicalHash(number uint64) common.Hash
GetHeadBlockHash() common.Hash
}
type LevelDatabaseReader struct {
reader ethdb.Reader
}
func NewLevelDatabaseReader(reader ethdb.Reader) *LevelDatabaseReader {
return &LevelDatabaseReader{reader: reader}
}
func (ldbr *LevelDatabaseReader) GetBlock(hash common.Hash, number uint64) *types.Block {
return rawdb.ReadBlock(ldbr.reader, hash, number)
}
func (ldbr *LevelDatabaseReader) GetBlockNumber(hash common.Hash) *uint64 {
return rawdb.ReadHeaderNumber(ldbr.reader, hash)
}
func (ldbr *LevelDatabaseReader) GetBlockReceipts(hash common.Hash, number uint64) types.Receipts {
return rawdb.ReadReceipts(ldbr.reader, hash, number, &params.ChainConfig{})
}
func (ldbr *LevelDatabaseReader) GetCanonicalHash(number uint64) common.Hash {
return rawdb.ReadCanonicalHash(ldbr.reader, number)
}
func (ldbr *LevelDatabaseReader) GetHeadBlockHash() common.Hash {
return rawdb.ReadHeadBlockHash(ldbr.reader)
}
@@ -0,0 +1,87 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package level_test
import (
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/ethereum/level"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
)
var _ = Describe("Level database", func() {
Describe("Getting a block", func() {
It("converts block number to uint64 and hash to common.Hash to fetch block from reader", func() {
mockReader := fakes.NewMockLevelDatabaseReader()
ldb := level.NewLevelDatabase(mockReader)
blockHash := []byte{5, 4, 3, 2, 1}
blockNumber := int64(12345)
ldb.GetBlock(blockHash, blockNumber)
expectedBlockHash := common.BytesToHash(blockHash)
expectedBlockNumber := uint64(blockNumber)
mockReader.AssertGetBlockCalledWith(expectedBlockHash, expectedBlockNumber)
})
})
Describe("Getting a block hash", func() {
It("converts block number to uint64 to fetch hash from reader", func() {
mockReader := fakes.NewMockLevelDatabaseReader()
ldb := level.NewLevelDatabase(mockReader)
blockNumber := int64(12345)
ldb.GetBlockHash(blockNumber)
expectedBlockNumber := uint64(blockNumber)
mockReader.AssertGetCanonicalHashCalledWith(expectedBlockNumber)
})
})
Describe("Getting a block's receipts", func() {
It("converts block number to uint64 and hash to common.Hash to fetch receipts from reader", func() {
mockReader := fakes.NewMockLevelDatabaseReader()
ldb := level.NewLevelDatabase(mockReader)
blockHash := []byte{5, 4, 3, 2, 1}
blockNumber := int64(12345)
ldb.GetBlockReceipts(blockHash, blockNumber)
expectedBlockHash := common.BytesToHash(blockHash)
expectedBlockNumber := uint64(blockNumber)
mockReader.AssertGetBlockReceiptsCalledWith(expectedBlockHash, expectedBlockNumber)
})
})
Describe("Getting the latest block number", func() {
It("invokes the database reader to get the latest block number by hash and converts result to int64", func() {
mockReader := fakes.NewMockLevelDatabaseReader()
fakeHash := common.BytesToHash([]byte{1, 2, 3, 4, 5})
mockReader.SetHeadBlockHashReturnHash(fakeHash)
fakeBlockNumber := uint64(123456789)
mockReader.SetReturnBlockNumber(fakeBlockNumber)
ldb := level.NewLevelDatabase(mockReader)
result := ldb.GetHeadBlockNumber()
mockReader.AssertGetHeadBlockHashCalled()
mockReader.AssertGetBlockNumberCalledWith(fakeHash)
Expect(result).To(Equal(int64(fakeBlockNumber)))
})
})
})
@@ -0,0 +1,29 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package level_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestLevel(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Level Suite")
}
+37
View File
@@ -0,0 +1,37 @@
package postgres
import (
"fmt"
)
const (
BeginTransactionFailedMsg = "failed to begin transaction"
DbConnectionFailedMsg = "db connection failed"
DeleteQueryFailedMsg = "delete query failed"
InsertQueryFailedMsg = "insert query failed"
SettingNodeFailedMsg = "unable to set db node"
)
func ErrBeginTransactionFailed(beginErr error) error {
return formatError(BeginTransactionFailedMsg, beginErr.Error())
}
func ErrDBConnectionFailed(connectErr error) error {
return formatError(DbConnectionFailedMsg, connectErr.Error())
}
func ErrDBDeleteFailed(deleteErr error) error {
return formatError(DeleteQueryFailedMsg, deleteErr.Error())
}
func ErrDBInsertFailed(insertErr error) error {
return formatError(InsertQueryFailedMsg, insertErr.Error())
}
func ErrUnableToSetNode(setErr error) error {
return formatError(SettingNodeFailedMsg, setErr.Error())
}
func formatError(msg, err string) error {
return fmt.Errorf("%s: %s", msg, err)
}
+64
View File
@@ -0,0 +1,64 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package postgres
import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" //postgres driver
"github.com/vulcanize/vulcanizedb/pkg/config"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
type DB struct {
*sqlx.DB
Node core.Node
NodeID int64
}
func NewDB(databaseConfig config.Database, node core.Node) (*DB, error) {
connectString := config.DbConnectionString(databaseConfig)
db, connectErr := sqlx.Connect("postgres", connectString)
if connectErr != nil {
return &DB{}, ErrDBConnectionFailed(connectErr)
}
pg := DB{DB: db, Node: node}
nodeErr := pg.CreateNode(&node)
if nodeErr != nil {
return &DB{}, ErrUnableToSetNode(nodeErr)
}
return &pg, nil
}
func (db *DB) CreateNode(node *core.Node) error {
var nodeID int64
err := db.QueryRow(
`INSERT INTO eth_nodes (genesis_block, network_id, eth_node_id, client_name)
VALUES ($1, $2, $3, $4)
ON CONFLICT (genesis_block, network_id, eth_node_id)
DO UPDATE
SET genesis_block = $1,
network_id = $2,
eth_node_id = $3,
client_name = $4
RETURNING id`,
node.GenesisBlock, node.NetworkID, node.ID, node.ClientName).Scan(&nodeID)
if err != nil {
return ErrUnableToSetNode(err)
}
db.NodeID = nodeID
return nil
}
@@ -0,0 +1,36 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package postgres_test
import (
"io/ioutil"
"testing"
log "github.com/sirupsen/logrus"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func init() {
log.SetOutput(ioutil.Discard)
}
func TestPostgres(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Postgres Suite")
}
+165
View File
@@ -0,0 +1,165 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package postgres_test
import (
"fmt"
"strings"
"math/big"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/config"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Postgres DB", func() {
var sqlxdb *sqlx.DB
It("connects to the database", func() {
var err error
pgConfig := config.DbConnectionString(test_config.DBConfig)
sqlxdb, err = sqlx.Connect("postgres", pgConfig)
Expect(err).Should(BeNil())
Expect(sqlxdb).ShouldNot(BeNil())
})
It("serializes big.Int to db", func() {
// postgres driver doesn't support go big.Int type
// various casts in golang uint64, int64, overflow for
// transaction value (in wei) even though
// postgres numeric can handle an arbitrary
// sized int, so use string representation of big.Int
// and cast on insert
pgConnectString := config.DbConnectionString(test_config.DBConfig)
db, err := sqlx.Connect("postgres", pgConnectString)
Expect(err).NotTo(HaveOccurred())
bi := new(big.Int)
bi.SetString("34940183920000000000", 10)
Expect(bi.String()).To(Equal("34940183920000000000"))
defer db.Exec(`DROP TABLE IF EXISTS example`)
_, err = db.Exec("CREATE TABLE example ( id INTEGER, data NUMERIC )")
Expect(err).ToNot(HaveOccurred())
sqlStatement := `
INSERT INTO example (id, data)
VALUES (1, cast($1 AS NUMERIC))`
_, err = db.Exec(sqlStatement, bi.String())
Expect(err).ToNot(HaveOccurred())
var data string
err = db.QueryRow(`SELECT data FROM example WHERE id = 1`).Scan(&data)
Expect(err).ToNot(HaveOccurred())
Expect(bi.String()).To(Equal(data))
actual := new(big.Int)
actual.SetString(data, 10)
Expect(actual).To(Equal(bi))
})
It("does not commit block if block is invalid", func() {
//badNonce violates db Nonce field length
badNonce := fmt.Sprintf("x %s", strings.Repeat("1", 100))
badBlock := core.Block{
Number: 123,
Nonce: badNonce,
Transactions: []core.TransactionModel{},
}
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
db := test_config.NewTestDB(node)
test_config.CleanTestDB(db)
blocksRepository := repositories.NewBlockRepository(db)
_, err1 := blocksRepository.CreateOrUpdateBlock(badBlock)
Expect(err1).To(HaveOccurred())
savedBlock, err2 := blocksRepository.GetBlock(123)
Expect(err2).To(HaveOccurred())
Expect(savedBlock).To(BeZero())
})
It("throws error when can't connect to the database", func() {
invalidDatabase := config.Database{}
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
_, err := postgres.NewDB(invalidDatabase, node)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(postgres.DbConnectionFailedMsg))
})
It("throws error when can't create node", func() {
badHash := fmt.Sprintf("x %s", strings.Repeat("1", 100))
node := core.Node{GenesisBlock: badHash, NetworkID: 1, ID: "x123", ClientName: "geth"}
_, err := postgres.NewDB(test_config.DBConfig, node)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(postgres.SettingNodeFailedMsg))
})
It("does not commit log if log is invalid", func() {
//badTxHash violates db tx_hash field length
badTxHash := fmt.Sprintf("x %s", strings.Repeat("1", 100))
badLog := core.FullSyncLog{
Address: "x123",
BlockNumber: 1,
TxHash: badTxHash,
}
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
db, _ := postgres.NewDB(test_config.DBConfig, node)
logRepository := repositories.FullSyncLogRepository{DB: db}
err := logRepository.CreateLogs([]core.FullSyncLog{badLog}, 123)
Expect(err).ToNot(BeNil())
savedBlock, err := logRepository.GetLogs("x123", 1)
Expect(savedBlock).To(BeNil())
Expect(err).To(Not(HaveOccurred()))
})
It("does not commit block or transactions if transaction is invalid", func() {
//badHash violates db To field length
badHash := fmt.Sprintf("x %s", strings.Repeat("1", 100))
badTransaction := core.TransactionModel{To: badHash}
block := core.Block{
Number: 123,
Transactions: []core.TransactionModel{badTransaction},
}
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
db, _ := postgres.NewDB(test_config.DBConfig, node)
blockRepository := repositories.NewBlockRepository(db)
_, err1 := blockRepository.CreateOrUpdateBlock(block)
Expect(err1).To(HaveOccurred())
savedBlock, err2 := blockRepository.GetBlock(123)
Expect(err2).To(HaveOccurred())
Expect(savedBlock).To(BeZero())
})
})
@@ -0,0 +1,344 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
const (
blocksFromHeadBeforeFinal = 20
)
var ErrBlockExists = errors.New("Won't add block that already exists.")
type BlockRepository struct {
database *postgres.DB
}
func NewBlockRepository(database *postgres.DB) *BlockRepository {
return &BlockRepository{database: database}
}
func (blockRepository BlockRepository) SetBlocksStatus(chainHead int64) error {
cutoff := chainHead - blocksFromHeadBeforeFinal
_, err := blockRepository.database.Exec(`
UPDATE eth_blocks SET is_final = TRUE
WHERE is_final = FALSE AND number < $1`,
cutoff)
return err
}
func (blockRepository BlockRepository) CreateOrUpdateBlock(block core.Block) (int64, error) {
var err error
var blockID int64
retrievedBlockHash, ok := blockRepository.getBlockHash(block)
if !ok {
return blockRepository.insertBlock(block)
}
if ok && retrievedBlockHash != block.Hash {
err = blockRepository.removeBlock(block.Number)
if err != nil {
return 0, err
}
return blockRepository.insertBlock(block)
}
return blockID, ErrBlockExists
}
func (blockRepository BlockRepository) MissingBlockNumbers(startingBlockNumber int64, highestBlockNumber int64, nodeID string) []int64 {
numbers := make([]int64, 0)
err := blockRepository.database.Select(&numbers,
`SELECT all_block_numbers
FROM (
SELECT generate_series($1::INT, $2::INT) AS all_block_numbers) series
WHERE all_block_numbers NOT IN (
SELECT number FROM eth_blocks WHERE eth_node_fingerprint = $3
) `,
startingBlockNumber,
highestBlockNumber, nodeID)
if err != nil {
logrus.Error("MissingBlockNumbers: error getting blocks: ", err)
}
return numbers
}
func (blockRepository BlockRepository) GetBlock(blockNumber int64) (core.Block, error) {
blockRows := blockRepository.database.QueryRowx(
`SELECT id,
number,
gas_limit,
gas_used,
time,
difficulty,
hash,
nonce,
parent_hash,
size,
uncle_hash,
is_final,
miner,
extra_data,
reward,
uncles_reward
FROM eth_blocks
WHERE eth_node_id = $1 AND number = $2`, blockRepository.database.NodeID, blockNumber)
savedBlock, err := blockRepository.loadBlock(blockRows)
if err != nil {
switch err {
case sql.ErrNoRows:
return core.Block{}, datastore.ErrBlockDoesNotExist(blockNumber)
default:
logrus.Error("GetBlock: error loading blocks: ", err)
return savedBlock, err
}
}
return savedBlock, nil
}
func (blockRepository BlockRepository) insertBlock(block core.Block) (int64, error) {
var blockID int64
tx, beginErr := blockRepository.database.Beginx()
if beginErr != nil {
return 0, postgres.ErrBeginTransactionFailed(beginErr)
}
insertBlockErr := tx.QueryRow(
`INSERT INTO eth_blocks
(eth_node_id, number, gas_limit, gas_used, time, difficulty, hash, nonce, parent_hash, size, uncle_hash, is_final, miner, extra_data, reward, uncles_reward, eth_node_fingerprint)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
RETURNING id `,
blockRepository.database.NodeID,
block.Number,
block.GasLimit,
block.GasUsed,
block.Time,
block.Difficulty,
block.Hash,
block.Nonce,
block.ParentHash,
block.Size,
block.UncleHash,
block.IsFinal,
block.Miner,
block.ExtraData,
nullStringToZero(block.Reward),
nullStringToZero(block.UnclesReward),
blockRepository.database.Node.ID).
Scan(&blockID)
if insertBlockErr != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Error("failed to rollback transaction: ", rollbackErr)
}
return 0, postgres.ErrDBInsertFailed(insertBlockErr)
}
if len(block.Uncles) > 0 {
insertUncleErr := blockRepository.createUncles(tx, blockID, block.Hash, block.Uncles)
if insertUncleErr != nil {
tx.Rollback()
return 0, postgres.ErrDBInsertFailed(insertUncleErr)
}
}
if len(block.Transactions) > 0 {
insertTxErr := blockRepository.createTransactions(tx, blockID, block.Transactions)
if insertTxErr != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Warn("failed to rollback transaction: ", rollbackErr)
}
return 0, postgres.ErrDBInsertFailed(insertTxErr)
}
}
commitErr := tx.Commit()
if commitErr != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Warn("failed to rollback transaction: ", rollbackErr)
}
return 0, commitErr
}
return blockID, nil
}
func (blockRepository BlockRepository) createUncles(tx *sqlx.Tx, blockID int64, blockHash string, uncles []core.Uncle) error {
for _, uncle := range uncles {
err := blockRepository.createUncle(tx, blockID, uncle)
if err != nil {
return err
}
}
return nil
}
func (blockRepository BlockRepository) createUncle(tx *sqlx.Tx, blockID int64, uncle core.Uncle) error {
_, err := tx.Exec(
`INSERT INTO uncles
(hash, block_id, reward, miner, raw, block_timestamp, eth_node_id, eth_node_fingerprint)
VALUES ($1, $2, $3, $4, $5, $6, $7::NUMERIC, $8)
RETURNING id`,
uncle.Hash, blockID, nullStringToZero(uncle.Reward), uncle.Miner, uncle.Raw, uncle.Timestamp, blockRepository.database.NodeID, blockRepository.database.Node.ID)
return err
}
func (blockRepository BlockRepository) createTransactions(tx *sqlx.Tx, blockID int64, transactions []core.TransactionModel) error {
for _, transaction := range transactions {
err := blockRepository.createTransaction(tx, blockID, transaction)
if err != nil {
return err
}
}
return nil
}
//Fields like value lose precision if converted to
//int64 so convert to string instead. But nil
//big.Int -> string = "" so convert to "0"
func nullStringToZero(s string) string {
if s == "" {
return "0"
}
return s
}
func (blockRepository BlockRepository) createTransaction(tx *sqlx.Tx, blockID int64, transaction core.TransactionModel) error {
_, err := tx.Exec(
`INSERT INTO full_sync_transactions
(block_id, gas_limit, gas_price, hash, input_data, nonce, raw, tx_from, tx_index, tx_to, "value")
VALUES ($1, $2::NUMERIC, $3::NUMERIC, $4, $5, $6::NUMERIC, $7, $8, $9::NUMERIC, $10, $11::NUMERIC)
RETURNING id`, blockID, transaction.GasLimit, transaction.GasPrice, transaction.Hash, transaction.Data,
transaction.Nonce, transaction.Raw, transaction.From, transaction.TxIndex, transaction.To, nullStringToZero(transaction.Value))
if err != nil {
return err
}
if hasReceipt(transaction) {
receiptRepo := FullSyncReceiptRepository{}
receiptID, err := receiptRepo.CreateFullSyncReceiptInTx(blockID, transaction.Receipt, tx)
if err != nil {
return err
}
if hasLogs(transaction) {
err = blockRepository.createLogs(tx, transaction.Receipt.Logs, receiptID)
if err != nil {
return err
}
}
}
return nil
}
func hasLogs(transaction core.TransactionModel) bool {
return len(transaction.Receipt.Logs) > 0
}
func hasReceipt(transaction core.TransactionModel) bool {
return transaction.Receipt.TxHash != ""
}
func (blockRepository BlockRepository) getBlockHash(block core.Block) (string, bool) {
var retrievedBlockHash string
// TODO: handle possible error
blockRepository.database.Get(&retrievedBlockHash,
`SELECT hash
FROM eth_blocks
WHERE number = $1 AND eth_node_id = $2`,
block.Number, blockRepository.database.NodeID)
return retrievedBlockHash, blockExists(retrievedBlockHash)
}
func (blockRepository BlockRepository) createLogs(tx *sqlx.Tx, logs []core.FullSyncLog, receiptID int64) error {
for _, tlog := range logs {
_, err := tx.Exec(
`INSERT INTO full_sync_logs (block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data, receipt_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`,
tlog.BlockNumber, tlog.Address, tlog.TxHash, tlog.Index, tlog.Topics[0], tlog.Topics[1], tlog.Topics[2], tlog.Topics[3], tlog.Data, receiptID,
)
if err != nil {
return postgres.ErrDBInsertFailed(err)
}
}
return nil
}
func blockExists(retrievedBlockHash string) bool {
return retrievedBlockHash != ""
}
func (blockRepository BlockRepository) removeBlock(blockNumber int64) error {
_, err := blockRepository.database.Exec(
`DELETE FROM eth_blocks WHERE number=$1 AND eth_node_id=$2`,
blockNumber, blockRepository.database.NodeID)
if err != nil {
return postgres.ErrDBDeleteFailed(err)
}
return nil
}
func (blockRepository BlockRepository) loadBlock(blockRows *sqlx.Row) (core.Block, error) {
type b struct {
ID int
core.Block
}
var block b
err := blockRows.StructScan(&block)
if err != nil {
logrus.Error("loadBlock: error loading block: ", err)
return core.Block{}, err
}
transactionRows, err := blockRepository.database.Queryx(`
SELECT hash,
gas_limit,
gas_price,
input_data,
nonce,
raw,
tx_from,
tx_index,
tx_to,
value
FROM full_sync_transactions
WHERE block_id = $1
ORDER BY hash`, block.ID)
if err != nil {
logrus.Error("loadBlock: error fetting transactions: ", err)
return core.Block{}, err
}
block.Transactions = blockRepository.LoadTransactions(transactionRows)
return block.Block, nil
}
func (blockRepository BlockRepository) LoadTransactions(transactionRows *sqlx.Rows) []core.TransactionModel {
var transactions []core.TransactionModel
for transactionRows.Next() {
var transaction core.TransactionModel
err := transactionRows.StructScan(&transaction)
if err != nil {
logrus.Fatal(err)
}
transactions = append(transactions, transaction)
}
return transactions
}
@@ -0,0 +1,468 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"bytes"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Saving blocks", func() {
var db *postgres.DB
var node core.Node
var blockRepository datastore.BlockRepository
BeforeEach(func() {
node = core.Node{
GenesisBlock: "GENESIS",
NetworkID: 1,
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
}
db = test_config.NewTestDB(node)
test_config.CleanTestDB(db)
blockRepository = repositories.NewBlockRepository(db)
})
It("associates blocks to a node", func() {
block := core.Block{
Number: 123,
}
_, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
nodeTwo := core.Node{
GenesisBlock: "0x456",
NetworkID: 1,
ID: "x123456",
ClientName: "Geth",
}
dbTwo := test_config.NewTestDB(nodeTwo)
test_config.CleanTestDB(dbTwo)
repositoryTwo := repositories.NewBlockRepository(dbTwo)
_, err := repositoryTwo.GetBlock(123)
Expect(err).To(HaveOccurred())
})
It("saves the attributes of the block", func() {
blockNumber := int64(123)
gasLimit := uint64(1000000)
gasUsed := uint64(10)
blockHash := "x123"
blockParentHash := "x456"
blockNonce := "0x881db2ca900682e9a9"
miner := "x123"
extraData := "xextraData"
blockTime := uint64(1508981640)
uncleHash := "x789"
blockSize := string("1000")
difficulty := int64(10)
blockReward := "5132000000000000000"
unclesReward := "3580000000000000000"
block := core.Block{
Reward: blockReward,
Difficulty: difficulty,
GasLimit: gasLimit,
GasUsed: gasUsed,
Hash: blockHash,
ExtraData: extraData,
Nonce: blockNonce,
Miner: miner,
Number: blockNumber,
ParentHash: blockParentHash,
Size: blockSize,
Time: uint64(blockTime),
UncleHash: uncleHash,
UnclesReward: unclesReward,
}
_, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, err := blockRepository.GetBlock(blockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(savedBlock.Reward).To(Equal(blockReward))
Expect(savedBlock.Difficulty).To(Equal(difficulty))
Expect(savedBlock.GasLimit).To(Equal(gasLimit))
Expect(savedBlock.GasUsed).To(Equal(gasUsed))
Expect(savedBlock.Hash).To(Equal(blockHash))
Expect(savedBlock.Nonce).To(Equal(blockNonce))
Expect(savedBlock.Miner).To(Equal(miner))
Expect(savedBlock.ExtraData).To(Equal(extraData))
Expect(savedBlock.Number).To(Equal(blockNumber))
Expect(savedBlock.ParentHash).To(Equal(blockParentHash))
Expect(savedBlock.Size).To(Equal(blockSize))
Expect(savedBlock.Time).To(Equal(blockTime))
Expect(savedBlock.UncleHash).To(Equal(uncleHash))
Expect(savedBlock.UnclesReward).To(Equal(unclesReward))
})
It("does not find a block when searching for a number that does not exist", func() {
_, err := blockRepository.GetBlock(111)
Expect(err).To(HaveOccurred())
})
It("saves one transaction associated to the block", func() {
block := core.Block{
Number: 123,
Transactions: []core.TransactionModel{fakes.FakeTransaction},
}
_, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, getErr := blockRepository.GetBlock(123)
Expect(getErr).NotTo(HaveOccurred())
Expect(len(savedBlock.Transactions)).To(Equal(1))
})
It("saves two transactions associated to the block", func() {
block := core.Block{
Number: 123,
Transactions: []core.TransactionModel{fakes.FakeTransaction, fakes.FakeTransaction},
}
_, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, getErr := blockRepository.GetBlock(123)
Expect(getErr).NotTo(HaveOccurred())
Expect(len(savedBlock.Transactions)).To(Equal(2))
})
It("saves one uncle associated to the block", func() {
fakeUncle := fakes.GetFakeUncle(common.BytesToHash([]byte{1, 2, 3}).String(), "100000")
block := core.Block{
Hash: fakes.FakeHash.String(),
Number: 123,
Transactions: []core.TransactionModel{fakes.FakeTransaction},
Uncles: []core.Uncle{fakeUncle},
UnclesReward: "156250000000000000",
}
id, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, getErr := blockRepository.GetBlock(123)
Expect(getErr).NotTo(HaveOccurred())
Expect(len(savedBlock.Transactions)).To(Equal(1))
Expect(savedBlock.UnclesReward).To(Equal(big.NewInt(0).Div(big.NewInt(5000000000000000000), big.NewInt(32)).String()))
var uncleModel core.Uncle
err := db.Get(&uncleModel, `SELECT hash, reward, miner, raw, block_timestamp FROM uncles
WHERE block_id = $1 AND hash = $2`, id, common.BytesToHash([]byte{1, 2, 3}).Hex())
Expect(err).ToNot(HaveOccurred())
Expect(uncleModel.Hash).To(Equal(fakeUncle.Hash))
Expect(uncleModel.Reward).To(Equal(fakeUncle.Reward))
Expect(uncleModel.Miner).To(Equal(fakeUncle.Miner))
Expect(uncleModel.Timestamp).To(Equal(fakeUncle.Timestamp))
})
It("saves two uncles associated to the block", func() {
fakeUncleOne := fakes.GetFakeUncle(common.BytesToHash([]byte{1, 2, 3}).String(), "100000")
fakeUncleTwo := fakes.GetFakeUncle(common.BytesToHash([]byte{3, 2, 1}).String(), "90000")
block := core.Block{
Hash: fakes.FakeHash.String(),
Number: 123,
Transactions: []core.TransactionModel{fakes.FakeTransaction},
Uncles: []core.Uncle{fakeUncleOne, fakeUncleTwo},
UnclesReward: "312500000000000000",
}
id, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, getErr := blockRepository.GetBlock(123)
Expect(getErr).NotTo(HaveOccurred())
Expect(len(savedBlock.Transactions)).To(Equal(1))
b := new(big.Int)
b.SetString("10000000000000000000", 10)
Expect(savedBlock.UnclesReward).To(Equal(big.NewInt(0).Div(b, big.NewInt(32)).String()))
var uncleModel core.Uncle
err := db.Get(&uncleModel, `SELECT hash, reward, miner, raw, block_timestamp FROM uncles
WHERE block_id = $1 AND hash = $2`, id, common.BytesToHash([]byte{1, 2, 3}).Hex())
Expect(err).ToNot(HaveOccurred())
Expect(uncleModel.Hash).To(Equal(fakeUncleOne.Hash))
Expect(uncleModel.Reward).To(Equal(fakeUncleOne.Reward))
Expect(uncleModel.Miner).To(Equal(fakeUncleOne.Miner))
Expect(uncleModel.Timestamp).To(Equal(fakeUncleOne.Timestamp))
err = db.Get(&uncleModel, `SELECT hash, reward, miner, raw, block_timestamp FROM uncles
WHERE block_id = $1 AND hash = $2`, id, common.BytesToHash([]byte{3, 2, 1}).Hex())
Expect(err).ToNot(HaveOccurred())
Expect(uncleModel.Hash).To(Equal(fakeUncleTwo.Hash))
Expect(uncleModel.Reward).To(Equal(fakeUncleTwo.Reward))
Expect(uncleModel.Miner).To(Equal(fakeUncleTwo.Miner))
Expect(uncleModel.Timestamp).To(Equal(fakeUncleTwo.Timestamp))
})
It(`replaces blocks and transactions associated to the block
when a more new block is in conflict (same block number + nodeid)`, func() {
blockOne := core.Block{
Number: 123,
Hash: "xabc",
Transactions: []core.TransactionModel{
fakes.GetFakeTransaction("x123", core.Receipt{}),
fakes.GetFakeTransaction("x345", core.Receipt{}),
},
}
blockTwo := core.Block{
Number: 123,
Hash: "xdef",
Transactions: []core.TransactionModel{
fakes.GetFakeTransaction("x678", core.Receipt{}),
fakes.GetFakeTransaction("x9ab", core.Receipt{}),
},
}
_, insertErrOne := blockRepository.CreateOrUpdateBlock(blockOne)
Expect(insertErrOne).NotTo(HaveOccurred())
_, insertErrTwo := blockRepository.CreateOrUpdateBlock(blockTwo)
Expect(insertErrTwo).NotTo(HaveOccurred())
savedBlock, _ := blockRepository.GetBlock(123)
Expect(len(savedBlock.Transactions)).To(Equal(2))
Expect(savedBlock.Transactions[0].Hash).To(Equal("x678"))
Expect(savedBlock.Transactions[1].Hash).To(Equal("x9ab"))
})
It(`does not replace blocks when block number is not unique
but block number + node id is`, func() {
blockOne := core.Block{
Number: 123,
Transactions: []core.TransactionModel{
fakes.GetFakeTransaction("x123", core.Receipt{}),
fakes.GetFakeTransaction("x345", core.Receipt{}),
},
}
blockTwo := core.Block{
Number: 123,
Transactions: []core.TransactionModel{
fakes.GetFakeTransaction("x678", core.Receipt{}),
fakes.GetFakeTransaction("x9ab", core.Receipt{}),
},
}
_, insertErrOne := blockRepository.CreateOrUpdateBlock(blockOne)
Expect(insertErrOne).NotTo(HaveOccurred())
nodeTwo := core.Node{
GenesisBlock: "0x456",
NetworkID: 1,
}
dbTwo := test_config.NewTestDB(nodeTwo)
test_config.CleanTestDB(dbTwo)
repositoryTwo := repositories.NewBlockRepository(dbTwo)
_, insertErrTwo := blockRepository.CreateOrUpdateBlock(blockOne)
Expect(insertErrTwo).NotTo(HaveOccurred())
_, insertErrThree := repositoryTwo.CreateOrUpdateBlock(blockTwo)
Expect(insertErrThree).NotTo(HaveOccurred())
retrievedBlockOne, getErrOne := blockRepository.GetBlock(123)
Expect(getErrOne).NotTo(HaveOccurred())
retrievedBlockTwo, getErrTwo := repositoryTwo.GetBlock(123)
Expect(getErrTwo).NotTo(HaveOccurred())
Expect(retrievedBlockOne.Transactions[0].Hash).To(Equal("x123"))
Expect(retrievedBlockTwo.Transactions[0].Hash).To(Equal("x678"))
})
It("returns 'block exists' error if attempting to add duplicate block", func() {
block := core.Block{
Number: 12345,
Hash: "0x12345",
}
_, err := blockRepository.CreateOrUpdateBlock(block)
Expect(err).NotTo(HaveOccurred())
_, err = blockRepository.CreateOrUpdateBlock(block)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(repositories.ErrBlockExists))
})
It("saves the attributes associated to a transaction", func() {
gasLimit := uint64(5000)
gasPrice := int64(3)
nonce := uint64(10000)
to := "1234567890"
from := "0987654321"
var value = new(big.Int)
value.SetString("34940183920000000000", 10)
inputData := "0xf7d8c8830000000000000000000000000000000000000000000000000000000000037788000000000000000000000000000000000000000000000000000000000003bd14"
gethTransaction := types.NewTransaction(nonce, common.HexToAddress(to), value, gasLimit, big.NewInt(gasPrice), common.FromHex(inputData))
var raw bytes.Buffer
rlpErr := gethTransaction.EncodeRLP(&raw)
Expect(rlpErr).NotTo(HaveOccurred())
transaction := core.TransactionModel{
Data: common.Hex2Bytes(inputData),
From: from,
GasLimit: gasLimit,
GasPrice: gasPrice,
Hash: "x1234",
Nonce: nonce,
Receipt: core.Receipt{},
To: to,
TxIndex: 2,
Value: value.String(),
Raw: []byte{},
}
block := core.Block{
Number: 123,
Transactions: []core.TransactionModel{transaction},
}
_, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, err := blockRepository.GetBlock(123)
Expect(err).NotTo(HaveOccurred())
Expect(len(savedBlock.Transactions)).To(Equal(1))
savedTransaction := savedBlock.Transactions[0]
Expect(savedTransaction).To(Equal(transaction))
})
Describe("The missing block numbers", func() {
It("is empty the starting block number is the highest known block number", func() {
_, insertErr := blockRepository.CreateOrUpdateBlock(core.Block{Number: 1})
Expect(insertErr).NotTo(HaveOccurred())
Expect(len(blockRepository.MissingBlockNumbers(1, 1, node.ID))).To(Equal(0))
})
It("is empty if copies of block exist from both current node and another", func() {
_, insertErrOne := blockRepository.CreateOrUpdateBlock(core.Block{Number: 0})
Expect(insertErrOne).NotTo(HaveOccurred())
_, insertErrTwo := blockRepository.CreateOrUpdateBlock(core.Block{Number: 1})
Expect(insertErrTwo).NotTo(HaveOccurred())
nodeTwo := core.Node{
GenesisBlock: "0x456",
NetworkID: 1,
}
dbTwo, err := postgres.NewDB(test_config.DBConfig, nodeTwo)
Expect(err).NotTo(HaveOccurred())
repositoryTwo := repositories.NewBlockRepository(dbTwo)
_, insertErrThree := repositoryTwo.CreateOrUpdateBlock(core.Block{Number: 0})
Expect(insertErrThree).NotTo(HaveOccurred())
missing := blockRepository.MissingBlockNumbers(0, 1, node.ID)
Expect(len(missing)).To(BeZero())
})
It("is the only missing block number", func() {
_, insertErr := blockRepository.CreateOrUpdateBlock(core.Block{Number: 2})
Expect(insertErr).NotTo(HaveOccurred())
Expect(blockRepository.MissingBlockNumbers(1, 2, node.ID)).To(Equal([]int64{1}))
})
It("is both missing block numbers", func() {
_, insertErr := blockRepository.CreateOrUpdateBlock(core.Block{Number: 3})
Expect(insertErr).NotTo(HaveOccurred())
Expect(blockRepository.MissingBlockNumbers(1, 3, node.ID)).To(Equal([]int64{1, 2}))
})
It("goes back to the starting block number", func() {
_, insertErr := blockRepository.CreateOrUpdateBlock(core.Block{Number: 6})
Expect(insertErr).NotTo(HaveOccurred())
Expect(blockRepository.MissingBlockNumbers(4, 6, node.ID)).To(Equal([]int64{4, 5}))
})
It("only includes missing block numbers", func() {
_, insertErrOne := blockRepository.CreateOrUpdateBlock(core.Block{Number: 4})
Expect(insertErrOne).NotTo(HaveOccurred())
_, insertErrTwo := blockRepository.CreateOrUpdateBlock(core.Block{Number: 6})
Expect(insertErrTwo).NotTo(HaveOccurred())
Expect(blockRepository.MissingBlockNumbers(4, 6, node.ID)).To(Equal([]int64{5}))
})
It("includes blocks created by a different node", func() {
_, insertErrOne := blockRepository.CreateOrUpdateBlock(core.Block{Number: 4})
Expect(insertErrOne).NotTo(HaveOccurred())
_, insertErrTwo := blockRepository.CreateOrUpdateBlock(core.Block{Number: 6})
Expect(insertErrTwo).NotTo(HaveOccurred())
Expect(blockRepository.MissingBlockNumbers(4, 6, "Different node id")).To(Equal([]int64{4, 5, 6}))
})
It("is a list with multiple gaps", func() {
_, insertErrOne := blockRepository.CreateOrUpdateBlock(core.Block{Number: 4})
Expect(insertErrOne).NotTo(HaveOccurred())
_, insertErrTwo := blockRepository.CreateOrUpdateBlock(core.Block{Number: 5})
Expect(insertErrTwo).NotTo(HaveOccurred())
_, insertErrThree := blockRepository.CreateOrUpdateBlock(core.Block{Number: 8})
Expect(insertErrThree).NotTo(HaveOccurred())
_, insertErrFour := blockRepository.CreateOrUpdateBlock(core.Block{Number: 10})
Expect(insertErrFour).NotTo(HaveOccurred())
Expect(blockRepository.MissingBlockNumbers(3, 10, node.ID)).To(Equal([]int64{3, 6, 7, 9}))
})
It("returns empty array when lower bound exceeds upper bound", func() {
Expect(blockRepository.MissingBlockNumbers(10000, 1, node.ID)).To(Equal([]int64{}))
})
It("only returns requested range even when other gaps exist", func() {
_, insertErrOne := blockRepository.CreateOrUpdateBlock(core.Block{Number: 3})
Expect(insertErrOne).NotTo(HaveOccurred())
_, insertErrTwo := blockRepository.CreateOrUpdateBlock(core.Block{Number: 8})
Expect(insertErrTwo).NotTo(HaveOccurred())
Expect(blockRepository.MissingBlockNumbers(1, 5, node.ID)).To(Equal([]int64{1, 2, 4, 5}))
})
})
Describe("The block status", func() {
It("sets the status of blocks within n-20 of chain HEAD as final", func() {
blockNumberOfChainHead := 25
for i := 0; i < blockNumberOfChainHead; i++ {
_, err := blockRepository.CreateOrUpdateBlock(core.Block{Number: int64(i), Hash: strconv.Itoa(i)})
Expect(err).NotTo(HaveOccurred())
}
setErr := blockRepository.SetBlocksStatus(int64(blockNumberOfChainHead))
Expect(setErr).NotTo(HaveOccurred())
blockOne, err := blockRepository.GetBlock(1)
Expect(err).ToNot(HaveOccurred())
Expect(blockOne.IsFinal).To(Equal(true))
blockTwo, err := blockRepository.GetBlock(24)
Expect(err).ToNot(HaveOccurred())
Expect(blockTwo.IsFinal).To(BeFalse())
})
})
})
@@ -0,0 +1,72 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
const (
insertCheckedHeaderQuery = `UPDATE public.headers SET check_count = (SELECT check_count WHERE id = $1) + 1 WHERE id = $1`
)
type CheckedHeadersRepository struct {
db *postgres.DB
}
func NewCheckedHeadersRepository(db *postgres.DB) CheckedHeadersRepository {
return CheckedHeadersRepository{db: db}
}
// Increment check_count for header
func (repo CheckedHeadersRepository) MarkHeaderChecked(headerID int64) error {
_, err := repo.db.Exec(insertCheckedHeaderQuery, headerID)
return err
}
// Zero out check count for headers with block number >= startingBlockNumber
func (repo CheckedHeadersRepository) MarkHeadersUnchecked(startingBlockNumber int64) error {
_, err := repo.db.Exec(`UPDATE public.headers SET check_count = 0 WHERE block_number >= $1`, startingBlockNumber)
return err
}
// Return header if check_count < passed checkCount
func (repo CheckedHeadersRepository) UncheckedHeaders(startingBlockNumber, endingBlockNumber, checkCount int64) ([]core.Header, error) {
var result []core.Header
var query string
var err error
if endingBlockNumber == -1 {
query = `SELECT id, block_number, hash
FROM headers
WHERE check_count < $2
AND block_number >= $1
AND eth_node_fingerprint = $3`
err = repo.db.Select(&result, query, startingBlockNumber, checkCount, repo.db.Node.ID)
} else {
query = `SELECT id, block_number, hash
FROM headers
WHERE check_count < $3
AND block_number >= $1
AND block_number <= $2
AND eth_node_fingerprint = $4`
err = repo.db.Select(&result, query, startingBlockNumber, endingBlockNumber, checkCount, repo.db.Node.ID)
}
return result, err
}
@@ -0,0 +1,272 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"math/rand"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Checked Headers repository", func() {
var (
db *postgres.DB
repo datastore.CheckedHeadersRepository
)
BeforeEach(func() {
db = test_config.NewTestDB(test_config.NewTestNode())
test_config.CleanTestDB(db)
repo = repositories.NewCheckedHeadersRepository(db)
})
AfterEach(func() {
closeErr := db.Close()
Expect(closeErr).NotTo(HaveOccurred())
})
Describe("MarkHeaderChecked", func() {
It("marks passed header as checked on insert", func() {
headerRepository := repositories.NewHeaderRepository(db)
headerID, headerErr := headerRepository.CreateOrUpdateHeader(fakes.FakeHeader)
Expect(headerErr).NotTo(HaveOccurred())
err := repo.MarkHeaderChecked(headerID)
Expect(err).NotTo(HaveOccurred())
var checkedCount int
fetchErr := db.Get(&checkedCount, `SELECT check_count FROM public.headers WHERE id = $1`, headerID)
Expect(fetchErr).NotTo(HaveOccurred())
Expect(checkedCount).To(Equal(1))
})
It("increments check count on update", func() {
headerRepository := repositories.NewHeaderRepository(db)
headerID, headerErr := headerRepository.CreateOrUpdateHeader(fakes.FakeHeader)
Expect(headerErr).NotTo(HaveOccurred())
insertErr := repo.MarkHeaderChecked(headerID)
Expect(insertErr).NotTo(HaveOccurred())
updateErr := repo.MarkHeaderChecked(headerID)
Expect(updateErr).NotTo(HaveOccurred())
var checkedCount int
fetchErr := db.Get(&checkedCount, `SELECT check_count FROM public.headers WHERE id = $1`, headerID)
Expect(fetchErr).NotTo(HaveOccurred())
Expect(checkedCount).To(Equal(2))
})
})
Describe("MarkHeadersUnchecked", func() {
It("removes rows for headers <= starting block number", func() {
blockNumberOne := rand.Int63()
blockNumberTwo := blockNumberOne + 1
blockNumberThree := blockNumberOne + 2
fakeHeaderOne := fakes.GetFakeHeader(blockNumberOne)
fakeHeaderTwo := fakes.GetFakeHeader(blockNumberTwo)
fakeHeaderThree := fakes.GetFakeHeader(blockNumberThree)
headerRepository := repositories.NewHeaderRepository(db)
// insert three headers with incrementing block number
headerIdOne, insertHeaderOneErr := headerRepository.CreateOrUpdateHeader(fakeHeaderOne)
Expect(insertHeaderOneErr).NotTo(HaveOccurred())
headerIdTwo, insertHeaderTwoErr := headerRepository.CreateOrUpdateHeader(fakeHeaderTwo)
Expect(insertHeaderTwoErr).NotTo(HaveOccurred())
headerIdThree, insertHeaderThreeErr := headerRepository.CreateOrUpdateHeader(fakeHeaderThree)
Expect(insertHeaderThreeErr).NotTo(HaveOccurred())
// mark all headers checked
markHeaderOneCheckedErr := repo.MarkHeaderChecked(headerIdOne)
Expect(markHeaderOneCheckedErr).NotTo(HaveOccurred())
markHeaderTwoCheckedErr := repo.MarkHeaderChecked(headerIdTwo)
Expect(markHeaderTwoCheckedErr).NotTo(HaveOccurred())
markHeaderThreeCheckedErr := repo.MarkHeaderChecked(headerIdThree)
Expect(markHeaderThreeCheckedErr).NotTo(HaveOccurred())
// mark headers unchecked since blockNumberTwo
err := repo.MarkHeadersUnchecked(blockNumberTwo)
Expect(err).NotTo(HaveOccurred())
var headerOneCheckCount, headerTwoCheckCount, headerThreeCheckCount int
getHeaderOneErr := db.Get(&headerOneCheckCount, `SELECT check_count FROM public.headers WHERE id = $1`, headerIdOne)
Expect(getHeaderOneErr).NotTo(HaveOccurred())
Expect(headerOneCheckCount).To(Equal(1))
getHeaderTwoErr := db.Get(&headerTwoCheckCount, `SELECT check_count FROM public.headers WHERE id = $1`, headerIdTwo)
Expect(getHeaderTwoErr).NotTo(HaveOccurred())
Expect(headerTwoCheckCount).To(BeZero())
getHeaderThreeErr := db.Get(&headerThreeCheckCount, `SELECT check_count FROM public.headers WHERE id = $1`, headerIdThree)
Expect(getHeaderThreeErr).NotTo(HaveOccurred())
Expect(headerThreeCheckCount).To(BeZero())
})
})
Describe("UncheckedHeaders", func() {
var (
headerRepository datastore.HeaderRepository
startingBlockNumber int64
endingBlockNumber int64
middleBlockNumber int64
outOfRangeBlockNumber int64
blockNumbers []int64
headerIDs []int64
err error
uncheckedCheckCount = int64(1)
recheckCheckCount = int64(2)
)
BeforeEach(func() {
headerRepository = repositories.NewHeaderRepository(db)
startingBlockNumber = rand.Int63()
middleBlockNumber = startingBlockNumber + 1
endingBlockNumber = startingBlockNumber + 2
outOfRangeBlockNumber = endingBlockNumber + 1
blockNumbers = []int64{startingBlockNumber, middleBlockNumber, endingBlockNumber, outOfRangeBlockNumber}
headerIDs = []int64{}
for _, n := range blockNumbers {
headerID, err := headerRepository.CreateOrUpdateHeader(fakes.GetFakeHeader(n))
headerIDs = append(headerIDs, headerID)
Expect(err).NotTo(HaveOccurred())
}
})
Describe("when ending block is specified", func() {
It("excludes headers that are out of range", func() {
headers, err := repo.UncheckedHeaders(startingBlockNumber, endingBlockNumber, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
headerBlockNumbers := getBlockNumbers(headers)
Expect(headerBlockNumbers).To(ConsistOf(startingBlockNumber, middleBlockNumber, endingBlockNumber))
Expect(headerBlockNumbers).NotTo(ContainElement(outOfRangeBlockNumber))
})
It("excludes headers that have been checked more than the check count", func() {
_, err = db.Exec(`UPDATE public.headers SET check_count = 1 WHERE id = $1`, headerIDs[1])
Expect(err).NotTo(HaveOccurred())
headers, err := repo.UncheckedHeaders(startingBlockNumber, endingBlockNumber, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
headerBlockNumbers := getBlockNumbers(headers)
Expect(headerBlockNumbers).To(ConsistOf(startingBlockNumber, endingBlockNumber))
Expect(headerBlockNumbers).NotTo(ContainElement(middleBlockNumber))
})
It("does not exclude headers that have been checked less than the check count", func() {
_, err = db.Exec(`UPDATE public.headers SET check_count = 1 WHERE id = $1`, headerIDs[1])
Expect(err).NotTo(HaveOccurred())
headers, err := repo.UncheckedHeaders(startingBlockNumber, endingBlockNumber, recheckCheckCount)
Expect(err).NotTo(HaveOccurred())
headerBlockNumbers := getBlockNumbers(headers)
Expect(headerBlockNumbers).To(ConsistOf(startingBlockNumber, middleBlockNumber, endingBlockNumber))
})
It("only returns headers associated with the current node", func() {
dbTwo := test_config.NewTestDB(core.Node{ID: "second"})
headerRepositoryTwo := repositories.NewHeaderRepository(dbTwo)
repoTwo := repositories.NewCheckedHeadersRepository(dbTwo)
for _, n := range blockNumbers {
_, err = headerRepositoryTwo.CreateOrUpdateHeader(fakes.GetFakeHeader(n + 10))
Expect(err).NotTo(HaveOccurred())
}
nodeOneMissingHeaders, err := repo.UncheckedHeaders(startingBlockNumber, endingBlockNumber, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
nodeOneHeaderBlockNumbers := getBlockNumbers(nodeOneMissingHeaders)
Expect(nodeOneHeaderBlockNumbers).To(ConsistOf(startingBlockNumber, middleBlockNumber, endingBlockNumber))
nodeTwoMissingHeaders, err := repoTwo.UncheckedHeaders(startingBlockNumber, endingBlockNumber+10, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
nodeTwoHeaderBlockNumbers := getBlockNumbers(nodeTwoMissingHeaders)
Expect(nodeTwoHeaderBlockNumbers).To(ConsistOf(startingBlockNumber+10, middleBlockNumber+10, endingBlockNumber+10))
})
})
Describe("when ending block is -1", func() {
var endingBlock = int64(-1)
It("includes all non-checked headers when ending block is -1 ", func() {
headers, err := repo.UncheckedHeaders(startingBlockNumber, endingBlock, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
headerBlockNumbers := getBlockNumbers(headers)
Expect(headerBlockNumbers).To(ConsistOf(startingBlockNumber, middleBlockNumber, endingBlockNumber, outOfRangeBlockNumber))
})
It("excludes headers that have been checked more than the check count", func() {
_, err = db.Exec(`UPDATE public.headers SET check_count = 1 WHERE id = $1`, headerIDs[1])
Expect(err).NotTo(HaveOccurred())
headers, err := repo.UncheckedHeaders(startingBlockNumber, endingBlock, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
headerBlockNumbers := getBlockNumbers(headers)
Expect(headerBlockNumbers).To(ConsistOf(startingBlockNumber, endingBlockNumber, outOfRangeBlockNumber))
Expect(headerBlockNumbers).NotTo(ContainElement(middleBlockNumber))
})
It("does not exclude headers that have been checked less than the check count", func() {
_, err = db.Exec(`UPDATE public.headers SET check_count = 1 WHERE id = $1`, headerIDs[1])
Expect(err).NotTo(HaveOccurred())
headers, err := repo.UncheckedHeaders(startingBlockNumber, endingBlock, recheckCheckCount)
Expect(err).NotTo(HaveOccurred())
headerBlockNumbers := getBlockNumbers(headers)
Expect(headerBlockNumbers).To(ConsistOf(startingBlockNumber, middleBlockNumber, endingBlockNumber, outOfRangeBlockNumber))
})
It("only returns headers associated with the current node", func() {
dbTwo := test_config.NewTestDB(core.Node{ID: "second"})
headerRepositoryTwo := repositories.NewHeaderRepository(dbTwo)
repoTwo := repositories.NewCheckedHeadersRepository(dbTwo)
for _, n := range blockNumbers {
_, err = headerRepositoryTwo.CreateOrUpdateHeader(fakes.GetFakeHeader(n + 10))
Expect(err).NotTo(HaveOccurred())
}
nodeOneMissingHeaders, err := repo.UncheckedHeaders(startingBlockNumber, endingBlock, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
nodeOneBlockNumbers := getBlockNumbers(nodeOneMissingHeaders)
Expect(nodeOneBlockNumbers).To(ConsistOf(startingBlockNumber, middleBlockNumber, endingBlockNumber, outOfRangeBlockNumber))
nodeTwoMissingHeaders, err := repoTwo.UncheckedHeaders(startingBlockNumber, endingBlock, uncheckedCheckCount)
Expect(err).NotTo(HaveOccurred())
nodeTwoBlockNumbers := getBlockNumbers(nodeTwoMissingHeaders)
Expect(nodeTwoBlockNumbers).To(ConsistOf(startingBlockNumber+10, middleBlockNumber+10, endingBlockNumber+10, outOfRangeBlockNumber+10))
})
})
})
})
func getBlockNumbers(headers []core.Header) []int64 {
var headerBlockNumbers []int64
for _, header := range headers {
headerBlockNumbers = append(headerBlockNumbers, header.BlockNumber)
}
return headerBlockNumbers
}
@@ -0,0 +1,69 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
type CheckedLogsRepository struct {
db *postgres.DB
}
func NewCheckedLogsRepository(db *postgres.DB) CheckedLogsRepository {
return CheckedLogsRepository{db: db}
}
// Return whether a given address + topic0 has been fetched on a previous run of vDB
func (repository CheckedLogsRepository) AlreadyWatchingLog(addresses []string, topic0 string) (bool, error) {
for _, address := range addresses {
var addressExists bool
getAddressExistsErr := repository.db.Get(&addressExists, `SELECT EXISTS(SELECT 1 FROM public.watched_logs WHERE contract_address = $1)`, address)
if getAddressExistsErr != nil {
return false, getAddressExistsErr
}
if !addressExists {
return false, nil
}
}
var topicZeroExists bool
getTopicZeroExistsErr := repository.db.Get(&topicZeroExists, `SELECT EXISTS(SELECT 1 FROM public.watched_logs WHERE topic_zero = $1)`, topic0)
if getTopicZeroExistsErr != nil {
return false, getTopicZeroExistsErr
}
return topicZeroExists, nil
}
// Persist that a given address + topic0 has is being fetched on this run of vDB
func (repository CheckedLogsRepository) MarkLogWatched(addresses []string, topic0 string) error {
tx, txErr := repository.db.Beginx()
if txErr != nil {
return txErr
}
for _, address := range addresses {
_, insertErr := tx.Exec(`INSERT INTO public.watched_logs (contract_address, topic_zero) VALUES ($1, $2)`, address, topic0)
if insertErr != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Errorf("error rolling back transaction inserting checked logs: %s", rollbackErr.Error())
}
return insertErr
}
}
return tx.Commit()
}
@@ -0,0 +1,115 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Checked logs repository", func() {
var (
db *postgres.DB
fakeAddress = fakes.FakeAddress.Hex()
fakeAddresses = []string{fakeAddress}
fakeTopicZero = fakes.FakeHash.Hex()
repository datastore.CheckedLogsRepository
)
BeforeEach(func() {
db = test_config.NewTestDB(test_config.NewTestNode())
test_config.CleanTestDB(db)
repository = repositories.NewCheckedLogsRepository(db)
})
AfterEach(func() {
closeErr := db.Close()
Expect(closeErr).NotTo(HaveOccurred())
})
Describe("AlreadyWatchingLog", func() {
It("returns true if all addresses and the topic0 are already present in the db", func() {
_, insertErr := db.Exec(`INSERT INTO public.watched_logs (contract_address, topic_zero) VALUES ($1, $2)`, fakeAddress, fakeTopicZero)
Expect(insertErr).NotTo(HaveOccurred())
hasBeenChecked, err := repository.AlreadyWatchingLog(fakeAddresses, fakeTopicZero)
Expect(err).NotTo(HaveOccurred())
Expect(hasBeenChecked).To(BeTrue())
})
It("returns true if addresses and topic0 were fetched because of a combination of other transformers", func() {
anotherFakeAddress := common.HexToAddress("0x" + fakes.RandomString(40)).Hex()
anotherFakeTopicZero := common.HexToHash("0x" + fakes.RandomString(64)).Hex()
// insert row with matching address but different topic0
_, insertOneErr := db.Exec(`INSERT INTO public.watched_logs (contract_address, topic_zero) VALUES ($1, $2)`, fakeAddress, anotherFakeTopicZero)
Expect(insertOneErr).NotTo(HaveOccurred())
// insert row with matching topic0 but different address
_, insertTwoErr := db.Exec(`INSERT INTO public.watched_logs (contract_address, topic_zero) VALUES ($1, $2)`, anotherFakeAddress, fakeTopicZero)
Expect(insertTwoErr).NotTo(HaveOccurred())
hasBeenChecked, err := repository.AlreadyWatchingLog(fakeAddresses, fakeTopicZero)
Expect(err).NotTo(HaveOccurred())
Expect(hasBeenChecked).To(BeTrue())
})
It("returns false if any address has not been checked", func() {
anotherFakeAddress := common.HexToAddress("0x" + fakes.RandomString(40)).Hex()
_, insertErr := db.Exec(`INSERT INTO public.watched_logs (contract_address, topic_zero) VALUES ($1, $2)`, fakeAddress, fakeTopicZero)
Expect(insertErr).NotTo(HaveOccurred())
hasBeenChecked, err := repository.AlreadyWatchingLog(append(fakeAddresses, anotherFakeAddress), fakeTopicZero)
Expect(err).NotTo(HaveOccurred())
Expect(hasBeenChecked).To(BeFalse())
})
It("returns false if topic0 has not been checked", func() {
anotherFakeTopicZero := common.HexToHash("0x" + fakes.RandomString(64)).Hex()
_, insertErr := db.Exec(`INSERT INTO public.watched_logs (contract_address, topic_zero) VALUES ($1, $2)`, fakeAddress, anotherFakeTopicZero)
Expect(insertErr).NotTo(HaveOccurred())
hasBeenChecked, err := repository.AlreadyWatchingLog(fakeAddresses, fakeTopicZero)
Expect(err).NotTo(HaveOccurred())
Expect(hasBeenChecked).To(BeFalse())
})
})
Describe("MarkLogWatched", func() {
It("adds a row for all of transformer's addresses + topic0", func() {
anotherFakeAddress := common.HexToAddress("0x" + fakes.RandomString(40)).Hex()
err := repository.MarkLogWatched(append(fakeAddresses, anotherFakeAddress), fakeTopicZero)
Expect(err).NotTo(HaveOccurred())
var comboOneExists, comboTwoExists bool
getComboOneErr := db.Get(&comboOneExists, `SELECT EXISTS(SELECT 1 FROM public.watched_logs WHERE contract_address = $1 AND topic_zero = $2)`, fakeAddress, fakeTopicZero)
Expect(getComboOneErr).NotTo(HaveOccurred())
Expect(comboOneExists).To(BeTrue())
getComboTwoErr := db.Get(&comboTwoExists, `SELECT EXISTS(SELECT 1 FROM public.watched_logs WHERE contract_address = $1 AND topic_zero = $2)`, anotherFakeAddress, fakeTopicZero)
Expect(getComboTwoErr).NotTo(HaveOccurred())
Expect(comboTwoExists).To(BeTrue())
})
})
})
@@ -0,0 +1,99 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"database/sql"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
type ContractRepository struct {
*postgres.DB
}
func (contractRepository ContractRepository) CreateContract(contract core.Contract) error {
abi := contract.Abi
var abiToInsert *string
if abi != "" {
abiToInsert = &abi
}
_, err := contractRepository.DB.Exec(
`INSERT INTO watched_contracts (contract_hash, contract_abi)
VALUES ($1, $2)
ON CONFLICT (contract_hash)
DO UPDATE
SET contract_hash = $1, contract_abi = $2
`, contract.Hash, abiToInsert)
if err != nil {
return postgres.ErrDBInsertFailed(err)
}
return nil
}
func (contractRepository ContractRepository) ContractExists(contractHash string) (bool, error) {
var exists bool
err := contractRepository.DB.QueryRow(
`SELECT exists(
SELECT 1
FROM watched_contracts
WHERE contract_hash = $1)`, contractHash).Scan(&exists)
if err != nil {
return false, err
}
return exists, nil
}
func (contractRepository ContractRepository) GetContract(contractHash string) (core.Contract, error) {
var hash string
var abi string
contract := contractRepository.DB.QueryRow(
`SELECT contract_hash, contract_abi FROM watched_contracts WHERE contract_hash=$1`, contractHash)
err := contract.Scan(&hash, &abi)
if err == sql.ErrNoRows {
return core.Contract{}, datastore.ErrContractDoesNotExist(contractHash)
}
savedContract, err := contractRepository.addTransactions(core.Contract{Hash: hash, Abi: abi})
if err != nil {
return core.Contract{}, err
}
return savedContract, nil
}
func (contractRepository ContractRepository) addTransactions(contract core.Contract) (core.Contract, error) {
transactionRows, err := contractRepository.DB.Queryx(`
SELECT hash,
nonce,
tx_to,
tx_from,
gas_limit,
gas_price,
value,
input_data
FROM full_sync_transactions
WHERE tx_to = $1
ORDER BY block_id DESC`, contract.Hash)
if err != nil {
return core.Contract{}, err
}
blockRepository := &BlockRepository{contractRepository.DB}
transactions := blockRepository.LoadTransactions(transactionRows)
savedContract := core.Contract{Hash: contract.Hash, Transactions: transactions, Abi: contract.Abi}
return savedContract, nil
}
@@ -0,0 +1,122 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"sort"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Creating contracts", func() {
var db *postgres.DB
var contractRepository datastore.ContractRepository
var node core.Node
BeforeEach(func() {
node = core.Node{
GenesisBlock: "GENESIS",
NetworkID: 1,
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
}
db = test_config.NewTestDB(node)
test_config.CleanTestDB(db)
contractRepository = repositories.ContractRepository{DB: db}
})
It("returns the contract when it exists", func() {
contractRepository.CreateContract(core.Contract{Hash: "x123"})
contract, err := contractRepository.GetContract("x123")
Expect(err).NotTo(HaveOccurred())
Expect(contract.Hash).To(Equal("x123"))
Expect(contractRepository.ContractExists("x123")).To(BeTrue())
Expect(contractRepository.ContractExists("x456")).To(BeFalse())
})
It("returns err if contract does not exist", func() {
_, err := contractRepository.GetContract("x123")
Expect(err).To(HaveOccurred())
})
It("returns empty array when no transactions 'To' a contract", func() {
contractRepository.CreateContract(core.Contract{Hash: "x123"})
contract, err := contractRepository.GetContract("x123")
Expect(err).ToNot(HaveOccurred())
Expect(contract.Transactions).To(BeEmpty())
})
It("returns transactions 'To' a contract", func() {
var blockRepository datastore.BlockRepository
blockRepository = repositories.NewBlockRepository(db)
block := core.Block{
Number: 123,
Transactions: []core.TransactionModel{
{Hash: "TRANSACTION1", To: "x123", Value: "0"},
{Hash: "TRANSACTION2", To: "x345", Value: "0"},
{Hash: "TRANSACTION3", To: "x123", Value: "0"},
},
}
_, insertBlockErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertBlockErr).NotTo(HaveOccurred())
insertContractErr := contractRepository.CreateContract(core.Contract{Hash: "x123"})
Expect(insertContractErr).NotTo(HaveOccurred())
contract, err := contractRepository.GetContract("x123")
Expect(err).ToNot(HaveOccurred())
sort.Slice(contract.Transactions, func(i, j int) bool {
return contract.Transactions[i].Hash < contract.Transactions[j].Hash
})
Expect(contract.Transactions).To(
Equal([]core.TransactionModel{
{Data: []byte{}, Hash: "TRANSACTION1", To: "x123", Value: "0"},
{Data: []byte{}, Hash: "TRANSACTION3", To: "x123", Value: "0"},
}))
})
It("stores the ABI of the contract", func() {
contractRepository.CreateContract(core.Contract{
Abi: "{\"some\": \"json\"}",
Hash: "x123",
})
contract, err := contractRepository.GetContract("x123")
Expect(err).ToNot(HaveOccurred())
Expect(contract.Abi).To(Equal("{\"some\": \"json\"}"))
})
It("updates the ABI of the contract if hash already present", func() {
contractRepository.CreateContract(core.Contract{
Abi: "{\"some\": \"json\"}",
Hash: "x123",
})
contractRepository.CreateContract(core.Contract{
Abi: "{\"some\": \"different json\"}",
Hash: "x123",
})
contract, err := contractRepository.GetContract("x123")
Expect(err).ToNot(HaveOccurred())
Expect(contract.Abi).To(Equal("{\"some\": \"different json\"}"))
})
})
@@ -0,0 +1,106 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"database/sql"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
type FullSyncLogRepository struct {
*postgres.DB
}
func (repository FullSyncLogRepository) CreateLogs(lgs []core.FullSyncLog, receiptID int64) error {
tx, _ := repository.DB.Beginx()
for _, tlog := range lgs {
_, insertLogErr := tx.Exec(
`INSERT INTO full_sync_logs (block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data, receipt_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`,
tlog.BlockNumber, tlog.Address, tlog.TxHash, tlog.Index, tlog.Topics[0], tlog.Topics[1], tlog.Topics[2], tlog.Topics[3], tlog.Data, receiptID,
)
if insertLogErr != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Error("CreateLogs: could not perform rollback: ", rollbackErr)
}
return postgres.ErrDBInsertFailed(insertLogErr)
}
}
err := tx.Commit()
if err != nil {
err = tx.Rollback()
if err != nil {
logrus.Error("CreateLogs: could not perform rollback: ", err)
}
return postgres.ErrDBInsertFailed(err)
}
return nil
}
func (repository FullSyncLogRepository) GetLogs(address string, blockNumber int64) ([]core.FullSyncLog, error) {
logRows, err := repository.DB.Query(
`SELECT block_number,
address,
tx_hash,
index,
topic0,
topic1,
topic2,
topic3,
data
FROM full_sync_logs
WHERE address = $1 AND block_number = $2
ORDER BY block_number DESC`, address, blockNumber)
if err != nil {
return []core.FullSyncLog{}, err
}
return repository.loadLogs(logRows)
}
func (repository FullSyncLogRepository) loadLogs(logsRows *sql.Rows) ([]core.FullSyncLog, error) {
var lgs []core.FullSyncLog
for logsRows.Next() {
var blockNumber int64
var address string
var txHash string
var index int64
var data string
var topics core.Topics
err := logsRows.Scan(&blockNumber, &address, &txHash, &index, &topics[0], &topics[1], &topics[2], &topics[3], &data)
if err != nil {
logrus.Error("loadLogs: Error scanning a row in logRows: ", err)
return []core.FullSyncLog{}, err
}
lg := core.FullSyncLog{
BlockNumber: blockNumber,
TxHash: txHash,
Address: address,
Index: index,
Data: data,
}
for i, topic := range topics {
lg.Topics[i] = topic
}
lgs = append(lgs, lg)
}
return lgs, nil
}
@@ -0,0 +1,221 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"sort"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Full sync log Repository", func() {
Describe("Saving logs", func() {
var db *postgres.DB
var blockRepository datastore.BlockRepository
var logsRepository datastore.FullSyncLogRepository
var receiptRepository datastore.FullSyncReceiptRepository
var node core.Node
BeforeEach(func() {
node = core.Node{
GenesisBlock: "GENESIS",
NetworkID: 1,
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
}
db = test_config.NewTestDB(node)
test_config.CleanTestDB(db)
blockRepository = repositories.NewBlockRepository(db)
logsRepository = repositories.FullSyncLogRepository{DB: db}
receiptRepository = repositories.FullSyncReceiptRepository{DB: db}
})
It("returns the log when it exists", func() {
blockNumber := int64(12345)
blockId, err := blockRepository.CreateOrUpdateBlock(core.Block{Number: blockNumber})
Expect(err).NotTo(HaveOccurred())
tx, _ := db.Beginx()
receiptId, err := receiptRepository.CreateFullSyncReceiptInTx(blockId, core.Receipt{}, tx)
tx.Commit()
Expect(err).NotTo(HaveOccurred())
err = logsRepository.CreateLogs([]core.FullSyncLog{{
BlockNumber: blockNumber,
Index: 0,
Address: "x123",
TxHash: "x456",
Topics: core.Topics{0: "x777", 1: "x888", 2: "x999"},
Data: "xabc",
}}, receiptId)
Expect(err).NotTo(HaveOccurred())
log, err := logsRepository.GetLogs("x123", blockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(log).NotTo(BeNil())
Expect(log[0].BlockNumber).To(Equal(blockNumber))
Expect(log[0].Address).To(Equal("x123"))
Expect(log[0].Index).To(Equal(int64(0)))
Expect(log[0].TxHash).To(Equal("x456"))
Expect(log[0].Topics[0]).To(Equal("x777"))
Expect(log[0].Topics[1]).To(Equal("x888"))
Expect(log[0].Topics[2]).To(Equal("x999"))
Expect(log[0].Data).To(Equal("xabc"))
})
It("returns nil if log does not exist", func() {
log, err := logsRepository.GetLogs("x123", 1)
Expect(err).NotTo(HaveOccurred())
Expect(log).To(BeNil())
})
It("filters to the correct block number and address", func() {
blockNumber := int64(12345)
blockId, err := blockRepository.CreateOrUpdateBlock(core.Block{Number: blockNumber})
Expect(err).NotTo(HaveOccurred())
tx, _ := db.Beginx()
receiptId, err := receiptRepository.CreateFullSyncReceiptInTx(blockId, core.Receipt{}, tx)
tx.Commit()
Expect(err).NotTo(HaveOccurred())
err = logsRepository.CreateLogs([]core.FullSyncLog{{
BlockNumber: blockNumber,
Index: 0,
Address: "x123",
TxHash: "x456",
Topics: core.Topics{0: "x777", 1: "x888", 2: "x999"},
Data: "xabc",
}}, receiptId)
Expect(err).NotTo(HaveOccurred())
err = logsRepository.CreateLogs([]core.FullSyncLog{{
BlockNumber: blockNumber,
Index: 1,
Address: "x123",
TxHash: "x789",
Topics: core.Topics{0: "x111", 1: "x222", 2: "x333"},
Data: "xdef",
}}, receiptId)
Expect(err).NotTo(HaveOccurred())
err = logsRepository.CreateLogs([]core.FullSyncLog{{
BlockNumber: 2,
Index: 0,
Address: "x123",
TxHash: "x456",
Topics: core.Topics{0: "x777", 1: "x888", 2: "x999"},
Data: "xabc",
}}, receiptId)
Expect(err).NotTo(HaveOccurred())
log, err := logsRepository.GetLogs("x123", blockNumber)
Expect(err).NotTo(HaveOccurred())
type logIndex struct {
blockNumber int64
Index int64
}
var uniqueBlockNumbers []logIndex
for _, log := range log {
uniqueBlockNumbers = append(uniqueBlockNumbers,
logIndex{log.BlockNumber, log.Index})
}
sort.Slice(uniqueBlockNumbers, func(i, j int) bool {
if uniqueBlockNumbers[i].blockNumber < uniqueBlockNumbers[j].blockNumber {
return true
}
if uniqueBlockNumbers[i].blockNumber > uniqueBlockNumbers[j].blockNumber {
return false
}
return uniqueBlockNumbers[i].Index < uniqueBlockNumbers[j].Index
})
Expect(log).NotTo(BeNil())
Expect(len(log)).To(Equal(2))
Expect(uniqueBlockNumbers).To(Equal(
[]logIndex{
{blockNumber: blockNumber, Index: 0},
{blockNumber: blockNumber, Index: 1}},
))
})
It("saves the logs attached to a receipt", func() {
logs := []core.FullSyncLog{{
Address: "0x8a4774fe82c63484afef97ca8d89a6ea5e21f973",
BlockNumber: 4745407,
Data: "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000645a68669900000000000000000000000000000000000000000000003397684ab5869b0000000000000000000000000000000000000000000000000000000000005a36053200000000000000000000000099041f808d598b782d5a3e498681c2452a31da08",
Index: 86,
Topics: core.Topics{
0: "0x5a68669900000000000000000000000000000000000000000000000000000000",
1: "0x000000000000000000000000d0148dad63f73ce6f1b6c607e3413dcf1ff5f030",
2: "0x00000000000000000000000000000000000000000000003397684ab5869b0000",
3: "0x000000000000000000000000000000000000000000000000000000005a360532",
},
TxHash: "0x002c4799161d809b23f67884eb6598c9df5894929fe1a9ead97ca175d360f547",
}, {
Address: "0x99041f808d598b782d5a3e498681c2452a31da08",
BlockNumber: 4745407,
Data: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000418178358",
Index: 87,
Topics: core.Topics{
0: "0x1817835800000000000000000000000000000000000000000000000000000000",
1: "0x0000000000000000000000008a4774fe82c63484afef97ca8d89a6ea5e21f973",
2: "0x0000000000000000000000000000000000000000000000000000000000000000",
3: "0x0000000000000000000000000000000000000000000000000000000000000000",
},
TxHash: "0x002c4799161d809b23f67884eb6598c9df5894929fe1a9ead97ca175d360f547",
}, {
Address: "0x99041f808d598b782d5a3e498681c2452a31da08",
BlockNumber: 4745407,
Data: "0x00000000000000000000000000000000000000000000003338f64c8423af4000",
Index: 88,
Topics: core.Topics{
0: "0x296ba4ca62c6c21c95e828080cb8aec7481b71390585605300a8a76f9e95b527",
},
TxHash: "0x002c4799161d809b23f67884eb6598c9df5894929fe1a9ead97ca175d360f547",
},
}
receipt := core.Receipt{
ContractAddress: "",
CumulativeGasUsed: 7481414,
GasUsed: 60711,
Logs: logs,
Bloom: "0x00000800000000000000001000000000000000400000000080000000000000000000400000010000000000000000000000000000040000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800004000000000000001000000000000000000000000000002000000480000000000000002000000000000000020000000000000000000000000000000000000000080000000000180000c00000000000000002000002000000040000000000000000000000000000010000000000020000000000000000000002000000000000000000000000400800000000000000000",
Status: 1,
TxHash: "0x002c4799161d809b23f67884eb6598c9df5894929fe1a9ead97ca175d360f547",
}
transaction := fakes.GetFakeTransaction(receipt.TxHash, receipt)
block := core.Block{Transactions: []core.TransactionModel{transaction}}
_, err := blockRepository.CreateOrUpdateBlock(block)
Expect(err).To(Not(HaveOccurred()))
retrievedLogs, err := logsRepository.GetLogs("0x99041f808d598b782d5a3e498681c2452a31da08", 4745407)
Expect(err).NotTo(HaveOccurred())
expected := logs[1:]
Expect(retrievedLogs).To(ConsistOf(expected))
})
})
})
@@ -0,0 +1,147 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"database/sql"
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/libraries/shared/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
type FullSyncReceiptRepository struct {
*postgres.DB
}
func (receiptRepository FullSyncReceiptRepository) CreateReceiptsAndLogs(blockID int64, receipts []core.Receipt) error {
tx, err := receiptRepository.DB.Beginx()
if err != nil {
return err
}
for _, receipt := range receipts {
receiptID, err := receiptRepository.CreateFullSyncReceiptInTx(blockID, receipt, tx)
if err != nil {
tx.Rollback()
return err
}
if len(receipt.Logs) > 0 {
err = createLogs(receipt.Logs, receiptID, tx)
if err != nil {
tx.Rollback()
return err
}
}
}
tx.Commit()
return nil
}
func createReceipt(receipt core.Receipt, blockID int64, tx *sqlx.Tx) (int64, error) {
var receiptID int64
err := tx.QueryRow(
`INSERT INTO full_sync_receipts
(contract_address, tx_hash, cumulative_gas_used, gas_used, state_root, status, block_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id`,
receipt.ContractAddress, receipt.TxHash, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.StateRoot, receipt.Status, blockID,
).Scan(&receiptID)
if err != nil {
logrus.Error("createReceipt: Error inserting: ", err)
}
return receiptID, err
}
func createLogs(logs []core.FullSyncLog, receiptID int64, tx *sqlx.Tx) error {
for _, log := range logs {
_, err := tx.Exec(
`INSERT INTO full_sync_logs (block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data, receipt_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`,
log.BlockNumber, log.Address, log.TxHash, log.Index, log.Topics[0], log.Topics[1], log.Topics[2], log.Topics[3], log.Data, receiptID,
)
if err != nil {
return err
}
}
return nil
}
func (FullSyncReceiptRepository) CreateFullSyncReceiptInTx(blockID int64, receipt core.Receipt, tx *sqlx.Tx) (int64, error) {
var receiptID int64
addressID, getAddressErr := repository.GetOrCreateAddressInTransaction(tx, receipt.ContractAddress)
if getAddressErr != nil {
logrus.Error("createReceipt: Error getting address id: ", getAddressErr)
return receiptID, getAddressErr
}
err := tx.QueryRow(
`INSERT INTO full_sync_receipts
(contract_address_id, tx_hash, cumulative_gas_used, gas_used, state_root, status, block_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id`,
addressID, receipt.TxHash, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.StateRoot, receipt.Status, blockID).Scan(&receiptID)
if err != nil {
tx.Rollback()
logrus.Warning("CreateReceipt: error inserting receipt: ", err)
return receiptID, err
}
return receiptID, nil
}
func (receiptRepository FullSyncReceiptRepository) GetFullSyncReceipt(txHash string) (core.Receipt, error) {
row := receiptRepository.DB.QueryRow(
`SELECT contract_address_id,
tx_hash,
cumulative_gas_used,
gas_used,
state_root,
status
FROM full_sync_receipts
WHERE tx_hash = $1`, txHash)
receipt, err := loadReceipt(row)
if err != nil {
switch err {
case sql.ErrNoRows:
return core.Receipt{}, datastore.ErrReceiptDoesNotExist(txHash)
default:
return core.Receipt{}, err
}
}
return receipt, nil
}
func loadReceipt(receiptsRow *sql.Row) (core.Receipt, error) {
var contractAddress string
var txHash string
var cumulativeGasUsed uint64
var gasUsed uint64
var stateRoot string
var status int
err := receiptsRow.Scan(&contractAddress, &txHash, &cumulativeGasUsed, &gasUsed, &stateRoot, &status)
return core.Receipt{
TxHash: txHash,
ContractAddress: contractAddress,
CumulativeGasUsed: cumulativeGasUsed,
GasUsed: gasUsed,
StateRoot: stateRoot,
Status: status,
}, err
}
@@ -0,0 +1,161 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Receipt Repository", func() {
var blockRepository datastore.BlockRepository
var logRepository datastore.FullSyncLogRepository
var receiptRepository datastore.FullSyncReceiptRepository
var db *postgres.DB
var node core.Node
BeforeEach(func() {
node = core.Node{
GenesisBlock: "GENESIS",
NetworkID: 1,
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
}
db = test_config.NewTestDB(node)
test_config.CleanTestDB(db)
blockRepository = repositories.NewBlockRepository(db)
logRepository = repositories.FullSyncLogRepository{DB: db}
receiptRepository = repositories.FullSyncReceiptRepository{DB: db}
})
Describe("Saving multiple receipts", func() {
It("persists each receipt and its logs", func() {
blockNumber := int64(1234567)
blockId, err := blockRepository.CreateOrUpdateBlock(core.Block{Number: blockNumber})
Expect(err).NotTo(HaveOccurred())
txHashOne := "0xTxHashOne"
txHashTwo := "0xTxHashTwo"
addressOne := "0xAddressOne"
addressTwo := "0xAddressTwo"
logsOne := []core.FullSyncLog{{
Address: addressOne,
BlockNumber: blockNumber,
TxHash: txHashOne,
}, {
Address: addressOne,
BlockNumber: blockNumber,
TxHash: txHashOne,
}}
logsTwo := []core.FullSyncLog{{
BlockNumber: blockNumber,
TxHash: txHashTwo,
Address: addressTwo,
}}
receiptOne := core.Receipt{
Logs: logsOne,
TxHash: txHashOne,
}
receiptTwo := core.Receipt{
Logs: logsTwo,
TxHash: txHashTwo,
}
receipts := []core.Receipt{receiptOne, receiptTwo}
err = receiptRepository.CreateReceiptsAndLogs(blockId, receipts)
Expect(err).NotTo(HaveOccurred())
persistedReceiptOne, err := receiptRepository.GetFullSyncReceipt(txHashOne)
Expect(err).NotTo(HaveOccurred())
Expect(persistedReceiptOne).NotTo(BeNil())
Expect(persistedReceiptOne.TxHash).To(Equal(txHashOne))
persistedReceiptTwo, err := receiptRepository.GetFullSyncReceipt(txHashTwo)
Expect(err).NotTo(HaveOccurred())
Expect(persistedReceiptTwo).NotTo(BeNil())
Expect(persistedReceiptTwo.TxHash).To(Equal(txHashTwo))
persistedAddressOneLogs, err := logRepository.GetLogs(addressOne, blockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(persistedAddressOneLogs).NotTo(BeNil())
Expect(len(persistedAddressOneLogs)).To(Equal(2))
persistedAddressTwoLogs, err := logRepository.GetLogs(addressTwo, blockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(persistedAddressTwoLogs).NotTo(BeNil())
Expect(len(persistedAddressTwoLogs)).To(Equal(1))
})
})
Describe("Saving receipts on a block's transactions", func() {
It("returns the receipt when it exists", func() {
expected := core.Receipt{
ContractAddress: "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
CumulativeGasUsed: 7996119,
GasUsed: 21000,
Logs: []core.FullSyncLog{},
StateRoot: "0x88abf7e73128227370aa7baa3dd4e18d0af70e92ef1f9ef426942fbe2dddb733",
Status: 1,
TxHash: "0xe340558980f89d5f86045ac11e5cc34e4bcec20f9f1e2a427aa39d87114e8223",
}
transaction := fakes.GetFakeTransaction(expected.TxHash, expected)
block := core.Block{Transactions: []core.TransactionModel{transaction}}
_, err := blockRepository.CreateOrUpdateBlock(block)
Expect(err).NotTo(HaveOccurred())
receipt, err := receiptRepository.GetFullSyncReceipt("0xe340558980f89d5f86045ac11e5cc34e4bcec20f9f1e2a427aa39d87114e8223")
Expect(err).ToNot(HaveOccurred())
//Not currently serializing bloom logs
Expect(receipt.Bloom).To(Equal(core.Receipt{}.Bloom))
Expect(receipt.TxHash).To(Equal(expected.TxHash))
Expect(receipt.CumulativeGasUsed).To(Equal(expected.CumulativeGasUsed))
Expect(receipt.GasUsed).To(Equal(expected.GasUsed))
Expect(receipt.StateRoot).To(Equal(expected.StateRoot))
Expect(receipt.Status).To(Equal(expected.Status))
})
It("returns ErrReceiptDoesNotExist when receipt does not exist", func() {
receipt, err := receiptRepository.GetFullSyncReceipt("DOES NOT EXIST")
Expect(err).To(HaveOccurred())
Expect(receipt).To(BeZero())
})
It("still saves receipts without logs", func() {
receipt := core.Receipt{
TxHash: "0x002c4799161d809b23f67884eb6598c9df5894929fe1a9ead97ca175d360f547",
}
transaction := fakes.GetFakeTransaction(receipt.TxHash, receipt)
block := core.Block{
Transactions: []core.TransactionModel{transaction},
}
_, err := blockRepository.CreateOrUpdateBlock(block)
Expect(err).NotTo(HaveOccurred())
_, err = receiptRepository.GetFullSyncReceipt(receipt.TxHash)
Expect(err).To(Not(HaveOccurred()))
})
})
})
@@ -0,0 +1,158 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
log "github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
var ErrValidHeaderExists = errors.New("valid header already exists")
type HeaderRepository struct {
database *postgres.DB
}
func NewHeaderRepository(database *postgres.DB) HeaderRepository {
return HeaderRepository{database: database}
}
func (repository HeaderRepository) CreateOrUpdateHeader(header core.Header) (int64, error) {
hash, err := repository.getHeaderHash(header)
if err != nil {
if headerDoesNotExist(err) {
return repository.InternalInsertHeader(header)
}
log.Error("CreateOrUpdateHeader: error getting header hash: ", err)
return 0, err
}
if headerMustBeReplaced(hash, header) {
return repository.replaceHeader(header)
}
return 0, ErrValidHeaderExists
}
func (repository HeaderRepository) CreateTransactions(headerID int64, transactions []core.TransactionModel) error {
for _, transaction := range transactions {
_, err := repository.database.Exec(`INSERT INTO public.header_sync_transactions
(header_id, hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value")
VALUES ($1, $2, $3::NUMERIC, $4::NUMERIC, $5, $6::NUMERIC, $7, $8, $9::NUMERIC, $10, $11::NUMERIC)
ON CONFLICT DO NOTHING`, headerID, transaction.Hash, transaction.GasLimit, transaction.GasPrice,
transaction.Data, transaction.Nonce, transaction.Raw, transaction.From, transaction.TxIndex, transaction.To,
transaction.Value)
if err != nil {
return err
}
}
return nil
}
func (repository HeaderRepository) CreateTransactionInTx(tx *sqlx.Tx, headerID int64, transaction core.TransactionModel) (int64, error) {
var txID int64
err := tx.QueryRowx(`INSERT INTO public.header_sync_transactions
(header_id, hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value")
VALUES ($1, $2, $3::NUMERIC, $4::NUMERIC, $5, $6::NUMERIC, $7, $8, $9::NUMERIC, $10, $11::NUMERIC)
ON CONFLICT (header_id, hash) DO UPDATE
SET (gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value") = ($3::NUMERIC, $4::NUMERIC, $5, $6::NUMERIC, $7, $8, $9::NUMERIC, $10, $11::NUMERIC)
RETURNING id`,
headerID, transaction.Hash, transaction.GasLimit, transaction.GasPrice,
transaction.Data, transaction.Nonce, transaction.Raw, transaction.From,
transaction.TxIndex, transaction.To, transaction.Value).Scan(&txID)
if err != nil {
log.Error("header_repository: error inserting transaction: ", err)
return txID, err
}
return txID, err
}
func (repository HeaderRepository) GetHeader(blockNumber int64) (core.Header, error) {
var header core.Header
err := repository.database.Get(&header, `SELECT id, block_number, hash, raw, block_timestamp FROM headers WHERE block_number = $1 AND eth_node_fingerprint = $2`,
blockNumber, repository.database.Node.ID)
if err != nil {
log.Error("GetHeader: error getting headers: ", err)
}
return header, err
}
func (repository HeaderRepository) MissingBlockNumbers(startingBlockNumber, endingBlockNumber int64, nodeID string) ([]int64, error) {
numbers := make([]int64, 0)
err := repository.database.Select(&numbers,
`SELECT series.block_number
FROM (SELECT generate_series($1::INT, $2::INT) AS block_number) AS series
LEFT OUTER JOIN (SELECT block_number FROM headers
WHERE eth_node_fingerprint = $3) AS synced
USING (block_number)
WHERE synced.block_number IS NULL`,
startingBlockNumber, endingBlockNumber, nodeID)
if err != nil {
log.Errorf("MissingBlockNumbers failed to get blocks between %v - %v for node %v",
startingBlockNumber, endingBlockNumber, nodeID)
return []int64{}, err
}
return numbers, nil
}
func headerMustBeReplaced(hash string, header core.Header) bool {
return hash != header.Hash
}
func headerDoesNotExist(err error) bool {
return err == sql.ErrNoRows
}
func (repository HeaderRepository) getHeaderHash(header core.Header) (string, error) {
var hash string
err := repository.database.Get(&hash, `SELECT hash FROM headers WHERE block_number = $1 AND eth_node_fingerprint = $2`,
header.BlockNumber, repository.database.Node.ID)
return hash, err
}
// Function is public so we can test insert being called for the same header
// Can happen when concurrent processes are inserting headers
// Otherwise should not occur since only called in CreateOrUpdateHeader
func (repository HeaderRepository) InternalInsertHeader(header core.Header) (int64, error) {
var headerID int64
row := repository.database.QueryRowx(
`INSERT INTO public.headers (block_number, hash, block_timestamp, raw, eth_node_id, eth_node_fingerprint)
VALUES ($1, $2, $3::NUMERIC, $4, $5, $6) ON CONFLICT DO NOTHING RETURNING id`,
header.BlockNumber, header.Hash, header.Timestamp, header.Raw, repository.database.NodeID, repository.database.Node.ID)
err := row.Scan(&headerID)
if err != nil {
if err == sql.ErrNoRows {
return 0, ErrValidHeaderExists
}
log.Error("InternalInsertHeader: error inserting header: ", err)
}
return headerID, err
}
func (repository HeaderRepository) replaceHeader(header core.Header) (int64, error) {
_, err := repository.database.Exec(`DELETE FROM headers WHERE block_number = $1 AND eth_node_fingerprint = $2`,
header.BlockNumber, repository.database.Node.ID)
if err != nil {
log.Error("replaceHeader: error deleting headers: ", err)
return 0, err
}
return repository.InternalInsertHeader(header)
}
@@ -0,0 +1,511 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"database/sql"
"encoding/json"
"math/big"
"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/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Block header repository", func() {
var (
rawHeader []byte
err error
timestamp string
db *postgres.DB
repo repositories.HeaderRepository
header core.Header
)
BeforeEach(func() {
rawHeader, err = json.Marshal(types.Header{})
Expect(err).NotTo(HaveOccurred())
timestamp = big.NewInt(123456789).String()
db = test_config.NewTestDB(test_config.NewTestNode())
test_config.CleanTestDB(db)
repo = repositories.NewHeaderRepository(db)
header = core.Header{
BlockNumber: 100,
Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex(),
Raw: rawHeader,
Timestamp: timestamp,
}
})
Describe("creating or updating a header", func() {
It("adds a header", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
var dbHeader core.Header
err = db.Get(&dbHeader, `SELECT block_number, hash, raw, block_timestamp FROM public.headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(dbHeader.BlockNumber).To(Equal(header.BlockNumber))
Expect(dbHeader.Hash).To(Equal(header.Hash))
Expect(dbHeader.Raw).To(MatchJSON(header.Raw))
Expect(dbHeader.Timestamp).To(Equal(header.Timestamp))
})
It("adds node data to header", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
var ethNodeId int64
err = db.Get(&ethNodeId, `SELECT eth_node_id FROM public.headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(ethNodeId).To(Equal(db.NodeID))
var ethNodeFingerprint string
err = db.Get(&ethNodeFingerprint, `SELECT eth_node_fingerprint FROM public.headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(ethNodeFingerprint).To(Equal(db.Node.ID))
})
It("returns valid header exists error if attempting duplicate headers", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(repositories.ErrValidHeaderExists))
var dbHeaders []core.Header
err = db.Select(&dbHeaders, `SELECT block_number, hash, raw FROM public.headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(len(dbHeaders)).To(Equal(1))
})
It("does not duplicate headers in concurrent insert", func() {
_, err = repo.InternalInsertHeader(header)
Expect(err).NotTo(HaveOccurred())
_, err = repo.InternalInsertHeader(header)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(repositories.ErrValidHeaderExists))
var dbHeaders []core.Header
err = db.Select(&dbHeaders, `SELECT block_number, hash, raw FROM public.headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(len(dbHeaders)).To(Equal(1))
})
It("replaces header if hash is different", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
headerTwo := core.Header{
BlockNumber: header.BlockNumber,
Hash: common.BytesToHash([]byte{5, 4, 3, 2, 1}).Hex(),
Raw: rawHeader,
Timestamp: timestamp,
}
_, err = repo.CreateOrUpdateHeader(headerTwo)
Expect(err).NotTo(HaveOccurred())
var dbHeader core.Header
err = db.Get(&dbHeader, `SELECT block_number, hash, raw FROM headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(dbHeader.Hash).To(Equal(headerTwo.Hash))
Expect(dbHeader.Raw).To(MatchJSON(headerTwo.Raw))
})
It("does not replace header if node fingerprint is different", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
nodeTwo := core.Node{ID: "FingerprintTwo"}
dbTwo, err := postgres.NewDB(test_config.DBConfig, nodeTwo)
Expect(err).NotTo(HaveOccurred())
repoTwo := repositories.NewHeaderRepository(dbTwo)
headerTwo := core.Header{
BlockNumber: header.BlockNumber,
Hash: common.BytesToHash([]byte{5, 4, 3, 2, 1}).Hex(),
Raw: rawHeader,
Timestamp: timestamp,
}
_, err = repoTwo.CreateOrUpdateHeader(headerTwo)
Expect(err).NotTo(HaveOccurred())
var dbHeaders []core.Header
err = dbTwo.Select(&dbHeaders, `SELECT block_number, hash, raw FROM headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(len(dbHeaders)).To(Equal(2))
})
It("only replaces header with matching node fingerprint", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
nodeTwo := core.Node{ID: "FingerprintTwo"}
dbTwo, err := postgres.NewDB(test_config.DBConfig, nodeTwo)
Expect(err).NotTo(HaveOccurred())
repoTwo := repositories.NewHeaderRepository(dbTwo)
headerTwo := core.Header{
BlockNumber: header.BlockNumber,
Hash: common.BytesToHash([]byte{5, 4, 3, 2, 1}).Hex(),
Raw: rawHeader,
Timestamp: timestamp,
}
_, err = repoTwo.CreateOrUpdateHeader(headerTwo)
headerThree := core.Header{
BlockNumber: header.BlockNumber,
Hash: common.BytesToHash([]byte{1, 1, 1, 1, 1}).Hex(),
Raw: rawHeader,
Timestamp: timestamp,
}
_, err = repoTwo.CreateOrUpdateHeader(headerThree)
Expect(err).NotTo(HaveOccurred())
var dbHeaders []core.Header
err = dbTwo.Select(&dbHeaders, `SELECT block_number, hash, raw FROM headers WHERE block_number = $1`, header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(len(dbHeaders)).To(Equal(2))
Expect(dbHeaders[0].Hash).To(Or(Equal(header.Hash), Equal(headerThree.Hash)))
Expect(dbHeaders[1].Hash).To(Or(Equal(header.Hash), Equal(headerThree.Hash)))
Expect(dbHeaders[0].Raw).To(Or(MatchJSON(header.Raw), MatchJSON(headerThree.Raw)))
Expect(dbHeaders[1].Raw).To(Or(MatchJSON(header.Raw), MatchJSON(headerThree.Raw)))
})
})
Describe("creating a receipt", func() {
It("adds a receipt in a tx", func() {
headerID, err := repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
fromAddress := common.HexToAddress("0x1234")
toAddress := common.HexToAddress("0x5678")
txHash := common.HexToHash("0x9876")
txIndex := big.NewInt(123)
transaction := core.TransactionModel{
Data: []byte{},
From: fromAddress.Hex(),
GasLimit: 0,
GasPrice: 0,
Hash: txHash.Hex(),
Nonce: 0,
Raw: []byte{},
To: toAddress.Hex(),
TxIndex: txIndex.Int64(),
Value: "0",
}
tx, err := db.Beginx()
Expect(err).ToNot(HaveOccurred())
txId, txErr := repo.CreateTransactionInTx(tx, headerID, transaction)
Expect(txErr).ToNot(HaveOccurred())
contractAddr := common.HexToAddress("0x1234")
stateRoot := common.HexToHash("0x5678")
receipt := core.Receipt{
ContractAddress: contractAddr.Hex(),
TxHash: txHash.Hex(),
GasUsed: 10,
CumulativeGasUsed: 100,
StateRoot: stateRoot.Hex(),
Rlp: []byte{1, 2, 3},
}
receiptRepo := repositories.HeaderSyncReceiptRepository{}
_, receiptErr := receiptRepo.CreateHeaderSyncReceiptInTx(headerID, txId, receipt, tx)
Expect(receiptErr).ToNot(HaveOccurred())
commitErr := tx.Commit()
Expect(commitErr).ToNot(HaveOccurred())
type idModel struct {
TransactionId int64 `db:"transaction_id"`
ContractAddressId int64 `db:"contract_address_id"`
CumulativeGasUsed uint64 `db:"cumulative_gas_used"`
GasUsed uint64 `db:"gas_used"`
StateRoot string `db:"state_root"`
Status int
TxHash string `db:"tx_hash"`
Rlp []byte `db:"rlp"`
}
var addressId int64
getAddressErr := db.Get(&addressId, `SELECT id FROM addresses WHERE address = $1`, contractAddr.Hex())
Expect(getAddressErr).NotTo(HaveOccurred())
var dbReceipt idModel
getReceiptErr := db.Get(&dbReceipt,
`SELECT transaction_id, contract_address_id, cumulative_gas_used, gas_used, state_root, status, tx_hash, rlp
FROM public.header_sync_receipts WHERE header_id = $1`, headerID)
Expect(getReceiptErr).NotTo(HaveOccurred())
Expect(dbReceipt.TransactionId).To(Equal(txId))
Expect(dbReceipt.TxHash).To(Equal(txHash.Hex()))
Expect(dbReceipt.ContractAddressId).To(Equal(addressId))
Expect(dbReceipt.CumulativeGasUsed).To(Equal(uint64(100)))
Expect(dbReceipt.GasUsed).To(Equal(uint64(10)))
Expect(dbReceipt.StateRoot).To(Equal(stateRoot.Hex()))
Expect(dbReceipt.Status).To(Equal(0))
Expect(dbReceipt.Rlp).To(Equal([]byte{1, 2, 3}))
})
})
Describe("creating a transaction", func() {
var (
headerID int64
transactions []core.TransactionModel
)
BeforeEach(func() {
var err error
headerID, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
fromAddress := common.HexToAddress("0x1234")
toAddress := common.HexToAddress("0x5678")
txHash := common.HexToHash("0x9876")
txHashTwo := common.HexToHash("0x5432")
txIndex := big.NewInt(123)
transactions = []core.TransactionModel{{
Data: []byte{},
From: fromAddress.Hex(),
GasLimit: 0,
GasPrice: 0,
Hash: txHash.Hex(),
Nonce: 0,
Raw: []byte{},
To: toAddress.Hex(),
TxIndex: txIndex.Int64(),
Value: "0",
Receipt: core.Receipt{},
}, {
Data: []byte{},
From: fromAddress.Hex(),
GasLimit: 1,
GasPrice: 1,
Hash: txHashTwo.Hex(),
Nonce: 1,
Raw: []byte{},
To: toAddress.Hex(),
TxIndex: 1,
Value: "1",
}}
insertErr := repo.CreateTransactions(headerID, transactions)
Expect(insertErr).NotTo(HaveOccurred())
})
It("adds transactions", func() {
var dbTransactions []core.TransactionModel
err = db.Select(&dbTransactions,
`SELECT hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
FROM public.header_sync_transactions WHERE header_id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
Expect(dbTransactions).To(ConsistOf(transactions))
})
It("silently ignores duplicate inserts", func() {
insertTwoErr := repo.CreateTransactions(headerID, transactions)
Expect(insertTwoErr).NotTo(HaveOccurred())
var dbTransactions []core.TransactionModel
err = db.Select(&dbTransactions,
`SELECT hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
FROM public.header_sync_transactions WHERE header_id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
Expect(len(dbTransactions)).To(Equal(2))
})
})
Describe("creating a transaction in a sqlx tx", func() {
It("adds a transaction", func() {
headerID, err := repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
fromAddress := common.HexToAddress("0x1234")
toAddress := common.HexToAddress("0x5678")
txHash := common.HexToHash("0x9876")
txIndex := big.NewInt(123)
transaction := core.TransactionModel{
Data: []byte{},
From: fromAddress.Hex(),
GasLimit: 0,
GasPrice: 0,
Hash: txHash.Hex(),
Nonce: 0,
Raw: []byte{1, 2, 3},
To: toAddress.Hex(),
TxIndex: txIndex.Int64(),
Value: "0",
}
tx, err := db.Beginx()
Expect(err).ToNot(HaveOccurred())
_, insertErr := repo.CreateTransactionInTx(tx, headerID, transaction)
commitErr := tx.Commit()
Expect(commitErr).ToNot(HaveOccurred())
Expect(insertErr).NotTo(HaveOccurred())
var dbTransaction core.TransactionModel
err = db.Get(&dbTransaction,
`SELECT hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
FROM public.header_sync_transactions WHERE header_id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
Expect(dbTransaction).To(Equal(transaction))
})
It("silently upserts", func() {
headerID, err := repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
fromAddress := common.HexToAddress("0x1234")
toAddress := common.HexToAddress("0x5678")
txHash := common.HexToHash("0x9876")
txIndex := big.NewInt(123)
transaction := core.TransactionModel{
Data: []byte{},
From: fromAddress.Hex(),
GasLimit: 0,
GasPrice: 0,
Hash: txHash.Hex(),
Nonce: 0,
Raw: []byte{},
Receipt: core.Receipt{},
To: toAddress.Hex(),
TxIndex: txIndex.Int64(),
Value: "0",
}
tx1, err := db.Beginx()
Expect(err).ToNot(HaveOccurred())
txId1, insertErr := repo.CreateTransactionInTx(tx1, headerID, transaction)
commit1Err := tx1.Commit()
Expect(commit1Err).ToNot(HaveOccurred())
Expect(insertErr).NotTo(HaveOccurred())
tx2, err := db.Beginx()
Expect(err).ToNot(HaveOccurred())
txId2, insertErr := repo.CreateTransactionInTx(tx2, headerID, transaction)
commit2Err := tx2.Commit()
Expect(commit2Err).ToNot(HaveOccurred())
Expect(insertErr).NotTo(HaveOccurred())
Expect(txId1).To(Equal(txId2))
var dbTransactions []core.TransactionModel
err = db.Select(&dbTransactions,
`SELECT hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
FROM public.header_sync_transactions WHERE header_id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
Expect(len(dbTransactions)).To(Equal(1))
})
})
Describe("Getting a header", func() {
It("returns header if it exists", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
dbHeader, err := repo.GetHeader(header.BlockNumber)
Expect(err).NotTo(HaveOccurred())
Expect(dbHeader.ID).NotTo(BeZero())
Expect(dbHeader.BlockNumber).To(Equal(header.BlockNumber))
Expect(dbHeader.Hash).To(Equal(header.Hash))
Expect(dbHeader.Raw).To(MatchJSON(header.Raw))
Expect(dbHeader.Timestamp).To(Equal(header.Timestamp))
})
It("does not return header for a different node fingerprint", func() {
_, err = repo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
nodeTwo := core.Node{ID: "FingerprintTwo"}
dbTwo, err := postgres.NewDB(test_config.DBConfig, nodeTwo)
Expect(err).NotTo(HaveOccurred())
repoTwo := repositories.NewHeaderRepository(dbTwo)
_, err = repoTwo.GetHeader(header.BlockNumber)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(sql.ErrNoRows))
})
})
Describe("Getting missing headers", func() {
It("returns block numbers for headers not in the database", func() {
_, err = repo.CreateOrUpdateHeader(core.Header{
BlockNumber: 1,
Raw: rawHeader,
Timestamp: timestamp,
})
Expect(err).NotTo(HaveOccurred())
_, err = repo.CreateOrUpdateHeader(core.Header{
BlockNumber: 3,
Raw: rawHeader,
Timestamp: timestamp,
})
Expect(err).NotTo(HaveOccurred())
_, err = repo.CreateOrUpdateHeader(core.Header{
BlockNumber: 5,
Raw: rawHeader,
Timestamp: timestamp,
})
Expect(err).NotTo(HaveOccurred())
missingBlockNumbers, err := repo.MissingBlockNumbers(1, 5, db.Node.ID)
Expect(err).NotTo(HaveOccurred())
Expect(missingBlockNumbers).To(ConsistOf([]int64{2, 4}))
})
It("does not count headers created by a different node fingerprint", func() {
_, err = repo.CreateOrUpdateHeader(core.Header{
BlockNumber: 1,
Raw: rawHeader,
Timestamp: timestamp,
})
Expect(err).NotTo(HaveOccurred())
_, err = repo.CreateOrUpdateHeader(core.Header{
BlockNumber: 3,
Raw: rawHeader,
Timestamp: timestamp,
})
Expect(err).NotTo(HaveOccurred())
_, err = repo.CreateOrUpdateHeader(core.Header{
BlockNumber: 5,
Raw: rawHeader,
Timestamp: timestamp,
})
Expect(err).NotTo(HaveOccurred())
nodeTwo := core.Node{ID: "FingerprintTwo"}
dbTwo, err := postgres.NewDB(test_config.DBConfig, nodeTwo)
Expect(err).NotTo(HaveOccurred())
repoTwo := repositories.NewHeaderRepository(dbTwo)
missingBlockNumbers, err := repoTwo.MissingBlockNumbers(1, 5, nodeTwo.ID)
Expect(err).NotTo(HaveOccurred())
Expect(missingBlockNumbers).To(ConsistOf([]int64{1, 2, 3, 4, 5}))
})
})
})
@@ -0,0 +1,145 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/libraries/shared/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
const insertHeaderSyncLogQuery = `INSERT INTO header_sync_logs
(header_id, address, topics, data, block_number, block_hash, tx_index, tx_hash, log_index, raw)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT DO NOTHING`
type HeaderSyncLogRepository struct {
db *postgres.DB
}
func NewHeaderSyncLogRepository(db *postgres.DB) HeaderSyncLogRepository {
return HeaderSyncLogRepository{
db: db,
}
}
type headerSyncLog struct {
ID int64
HeaderID int64 `db:"header_id"`
Address int64
Topics pq.ByteaArray
Data []byte
BlockNumber uint64 `db:"block_number"`
BlockHash string `db:"block_hash"`
TxHash string `db:"tx_hash"`
TxIndex uint `db:"tx_index"`
LogIndex uint `db:"log_index"`
Transformed bool
Raw []byte
}
func (repo HeaderSyncLogRepository) GetUntransformedHeaderSyncLogs() ([]core.HeaderSyncLog, error) {
rows, queryErr := repo.db.Queryx(`SELECT * FROM public.header_sync_logs WHERE transformed = false`)
if queryErr != nil {
return nil, queryErr
}
var results []core.HeaderSyncLog
for rows.Next() {
var rawLog headerSyncLog
scanErr := rows.StructScan(&rawLog)
if scanErr != nil {
return nil, scanErr
}
var logTopics []common.Hash
for _, topic := range rawLog.Topics {
logTopics = append(logTopics, common.BytesToHash(topic))
}
address, addrErr := repository.GetAddressByID(repo.db, rawLog.Address)
if addrErr != nil {
return nil, addrErr
}
reconstructedLog := types.Log{
Address: common.HexToAddress(address),
Topics: logTopics,
Data: rawLog.Data,
BlockNumber: rawLog.BlockNumber,
TxHash: common.HexToHash(rawLog.TxHash),
TxIndex: rawLog.TxIndex,
BlockHash: common.HexToHash(rawLog.BlockHash),
Index: rawLog.LogIndex,
// TODO: revisit if not cascade deleting logs when header removed
// currently, fetched logs are cascade deleted if removed
Removed: false,
}
result := core.HeaderSyncLog{
ID: rawLog.ID,
HeaderID: rawLog.HeaderID,
Log: reconstructedLog,
Transformed: rawLog.Transformed,
}
// TODO: Consider returning each result async to avoid keeping large result sets in memory
results = append(results, result)
}
return results, nil
}
func (repo HeaderSyncLogRepository) CreateHeaderSyncLogs(headerID int64, logs []types.Log) error {
tx, txErr := repo.db.Beginx()
if txErr != nil {
return txErr
}
for _, log := range logs {
err := repo.insertLog(headerID, log, tx)
if err != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Errorf("failed to rollback header sync log insert: %s", rollbackErr.Error())
}
return err
}
}
return tx.Commit()
}
func (repo HeaderSyncLogRepository) insertLog(headerID int64, log types.Log, tx *sqlx.Tx) error {
topics := buildTopics(log)
raw, jsonErr := log.MarshalJSON()
if jsonErr != nil {
return jsonErr
}
addressID, addrErr := repository.GetOrCreateAddressInTransaction(tx, log.Address.Hex())
if addrErr != nil {
return addrErr
}
_, insertErr := tx.Exec(insertHeaderSyncLogQuery, headerID, addressID, topics, log.Data, log.BlockNumber,
log.BlockHash.Hex(), log.TxIndex, log.TxHash.Hex(), log.Index, raw)
return insertErr
}
func buildTopics(log types.Log) pq.ByteaArray {
var topics pq.ByteaArray
for _, topic := range log.Topics {
topics = append(topics, topic.Bytes())
}
return topics
}
@@ -0,0 +1,213 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/lib/pq"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
repository2 "github.com/vulcanize/vulcanizedb/libraries/shared/repository"
"github.com/vulcanize/vulcanizedb/libraries/shared/test_data"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Header sync log repository", func() {
var (
db *postgres.DB
headerID int64
repository datastore.HeaderSyncLogRepository
)
BeforeEach(func() {
db = test_config.NewTestDB(test_config.NewTestNode())
test_config.CleanTestDB(db)
headerRepository := repositories.NewHeaderRepository(db)
var headerErr error
headerID, headerErr = headerRepository.CreateOrUpdateHeader(fakes.FakeHeader)
Expect(headerErr).NotTo(HaveOccurred())
repository = repositories.NewHeaderSyncLogRepository(db)
})
AfterEach(func() {
closeErr := db.Close()
Expect(closeErr).NotTo(HaveOccurred())
})
Describe("CreateHeaderSyncLogs", func() {
type headerSyncLog struct {
ID int64
HeaderID int64 `db:"header_id"`
Address int64
Topics pq.ByteaArray
Data []byte
BlockNumber uint64 `db:"block_number"`
BlockHash string `db:"block_hash"`
TxHash string `db:"tx_hash"`
TxIndex uint `db:"tx_index"`
LogIndex uint `db:"log_index"`
Transformed bool
Raw []byte
}
It("writes a log to the db", func() {
log := test_data.GenericTestLog()
err := repository.CreateHeaderSyncLogs(headerID, []types.Log{log})
Expect(err).NotTo(HaveOccurred())
var dbLog headerSyncLog
lookupErr := db.Get(&dbLog, `SELECT * FROM header_sync_logs`)
Expect(lookupErr).NotTo(HaveOccurred())
Expect(dbLog.ID).NotTo(BeZero())
Expect(dbLog.HeaderID).To(Equal(headerID))
actualAddress, addressErr := repository2.GetAddressByID(db, dbLog.Address)
Expect(addressErr).NotTo(HaveOccurred())
Expect(actualAddress).To(Equal(log.Address.Hex()))
Expect(dbLog.Topics[0]).To(Equal(log.Topics[0].Bytes()))
Expect(dbLog.Topics[1]).To(Equal(log.Topics[1].Bytes()))
Expect(dbLog.Data).To(Equal(log.Data))
Expect(dbLog.BlockNumber).To(Equal(log.BlockNumber))
Expect(dbLog.BlockHash).To(Equal(log.BlockHash.Hex()))
Expect(dbLog.TxIndex).To(Equal(log.TxIndex))
Expect(dbLog.TxHash).To(Equal(log.TxHash.Hex()))
Expect(dbLog.LogIndex).To(Equal(log.Index))
expectedRaw, jsonErr := log.MarshalJSON()
Expect(jsonErr).NotTo(HaveOccurred())
Expect(dbLog.Raw).To(MatchJSON(expectedRaw))
Expect(dbLog.Transformed).To(BeFalse())
})
It("writes several logs to the db", func() {
log1 := test_data.GenericTestLog()
log2 := test_data.GenericTestLog()
logs := []types.Log{log1, log2}
err := repository.CreateHeaderSyncLogs(headerID, logs)
Expect(err).NotTo(HaveOccurred())
var count int
lookupErr := db.Get(&count, `SELECT COUNT(*) FROM header_sync_logs`)
Expect(lookupErr).NotTo(HaveOccurred())
Expect(count).To(Equal(len(logs)))
})
It("persists record that can be unpacked into types.Log", func() {
// important if we want to decouple log persistence from transforming and still make use of
// tools on types.Log like abi.Unpack
log := test_data.GenericTestLog()
err := repository.CreateHeaderSyncLogs(headerID, []types.Log{log})
Expect(err).NotTo(HaveOccurred())
var dbLog headerSyncLog
lookupErr := db.Get(&dbLog, `SELECT * FROM header_sync_logs`)
Expect(lookupErr).NotTo(HaveOccurred())
var logTopics []common.Hash
for _, topic := range dbLog.Topics {
logTopics = append(logTopics, common.BytesToHash(topic))
}
actualAddress, addressErr := repository2.GetAddressByID(db, dbLog.Address)
Expect(addressErr).NotTo(HaveOccurred())
reconstructedLog := types.Log{
Address: common.HexToAddress(actualAddress),
Topics: logTopics,
Data: dbLog.Data,
BlockNumber: dbLog.BlockNumber,
TxHash: common.HexToHash(dbLog.TxHash),
TxIndex: dbLog.TxIndex,
BlockHash: common.HexToHash(dbLog.BlockHash),
Index: dbLog.LogIndex,
Removed: false,
}
Expect(reconstructedLog).To(Equal(log))
})
It("does not duplicate logs", func() {
log := test_data.GenericTestLog()
err := repository.CreateHeaderSyncLogs(headerID, []types.Log{log, log})
Expect(err).NotTo(HaveOccurred())
var count int
lookupErr := db.Get(&count, `SELECT COUNT(*) FROM header_sync_logs`)
Expect(lookupErr).NotTo(HaveOccurred())
Expect(count).To(Equal(1))
})
})
Describe("GetUntransformedHeaderSyncLogs", func() {
Describe("when there are no logs", func() {
It("returns empty collection", func() {
result, err := repository.GetUntransformedHeaderSyncLogs()
Expect(err).NotTo(HaveOccurred())
Expect(len(result)).To(BeZero())
})
})
Describe("when there are logs", func() {
var log1, log2 types.Log
BeforeEach(func() {
log1 = test_data.GenericTestLog()
log2 = test_data.GenericTestLog()
logs := []types.Log{log1, log2}
logsErr := repository.CreateHeaderSyncLogs(headerID, logs)
Expect(logsErr).NotTo(HaveOccurred())
})
It("returns persisted logs", func() {
result, err := repository.GetUntransformedHeaderSyncLogs()
Expect(err).NotTo(HaveOccurred())
Expect(len(result)).To(Equal(2))
Expect(result[0].Log).To(Or(Equal(log1), Equal(log2)))
Expect(result[1].Log).To(Or(Equal(log1), Equal(log2)))
Expect(result[0].Log).NotTo(Equal(result[1].Log))
})
It("excludes logs that have been transformed", func() {
_, insertErr := db.Exec(`UPDATE public.header_sync_logs SET transformed = true WHERE tx_hash = $1`, log1.TxHash.Hex())
Expect(insertErr).NotTo(HaveOccurred())
result, err := repository.GetUntransformedHeaderSyncLogs()
Expect(err).NotTo(HaveOccurred())
Expect(len(result)).To(Equal(1))
Expect(result[0].Log).To(Equal(log2))
})
It("returns empty collection if all logs transformed", func() {
_, insertErr := db.Exec(`UPDATE public.header_sync_logs SET transformed = true WHERE header_id = $1`, headerID)
Expect(insertErr).NotTo(HaveOccurred())
result, err := repository.GetUntransformedHeaderSyncLogs()
Expect(err).NotTo(HaveOccurred())
Expect(len(result)).To(BeZero())
})
})
})
})
@@ -0,0 +1,47 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"github.com/ethereum/go-ethereum/log"
"github.com/jmoiron/sqlx"
"github.com/vulcanize/vulcanizedb/libraries/shared/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
type HeaderSyncReceiptRepository struct{}
func (HeaderSyncReceiptRepository) CreateHeaderSyncReceiptInTx(headerID, transactionID int64, receipt core.Receipt, tx *sqlx.Tx) (int64, error) {
var receiptID int64
addressID, getAddressErr := repository.GetOrCreateAddressInTransaction(tx, receipt.ContractAddress)
if getAddressErr != nil {
log.Error("createReceipt: Error getting address id: ", getAddressErr)
return receiptID, getAddressErr
}
err := tx.QueryRowx(`INSERT INTO public.header_sync_receipts
(header_id, transaction_id, contract_address_id, cumulative_gas_used, gas_used, state_root, status, tx_hash, rlp)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (header_id, transaction_id) DO UPDATE
SET (contract_address_id, cumulative_gas_used, gas_used, state_root, status, tx_hash, rlp) = ($3, $4::NUMERIC, $5::NUMERIC, $6, $7, $8, $9)
RETURNING id`,
headerID, transactionID, addressID, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.StateRoot, receipt.Status, receipt.TxHash, receipt.Rlp).Scan(&receiptID)
if err != nil {
log.Error("header_repository: error inserting receipt: ", err)
return receiptID, err
}
return receiptID, err
}
@@ -0,0 +1,133 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"encoding/json"
"math/big"
"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/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Header Sync Receipt Repo", func() {
var (
rawHeader []byte
err error
timestamp string
db *postgres.DB
receiptRepo repositories.HeaderSyncReceiptRepository
headerRepo repositories.HeaderRepository
header core.Header
)
BeforeEach(func() {
rawHeader, err = json.Marshal(types.Header{})
Expect(err).NotTo(HaveOccurred())
timestamp = big.NewInt(123456789).String()
db = test_config.NewTestDB(test_config.NewTestNode())
test_config.CleanTestDB(db)
receiptRepo = repositories.HeaderSyncReceiptRepository{}
headerRepo = repositories.NewHeaderRepository(db)
header = core.Header{
BlockNumber: 100,
Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex(),
Raw: rawHeader,
Timestamp: timestamp,
}
})
Describe("creating a receipt", func() {
It("adds a receipt in a tx", func() {
headerID, err := headerRepo.CreateOrUpdateHeader(header)
Expect(err).NotTo(HaveOccurred())
fromAddress := common.HexToAddress("0x1234")
toAddress := common.HexToAddress("0x5678")
txHash := common.HexToHash("0x9876")
txIndex := big.NewInt(123)
transaction := core.TransactionModel{
Data: []byte{},
From: fromAddress.Hex(),
GasLimit: 0,
GasPrice: 0,
Hash: txHash.Hex(),
Nonce: 0,
Raw: []byte{},
To: toAddress.Hex(),
TxIndex: txIndex.Int64(),
Value: "0",
}
tx, err := db.Beginx()
Expect(err).ToNot(HaveOccurred())
txId, txErr := headerRepo.CreateTransactionInTx(tx, headerID, transaction)
Expect(txErr).ToNot(HaveOccurred())
contractAddr := common.HexToAddress("0x1234")
stateRoot := common.HexToHash("0x5678")
receipt := core.Receipt{
ContractAddress: contractAddr.Hex(),
TxHash: txHash.Hex(),
GasUsed: 10,
CumulativeGasUsed: 100,
StateRoot: stateRoot.Hex(),
Rlp: []byte{1, 2, 3},
}
_, receiptErr := receiptRepo.CreateHeaderSyncReceiptInTx(headerID, txId, receipt, tx)
Expect(receiptErr).ToNot(HaveOccurred())
commitErr := tx.Commit()
Expect(commitErr).ToNot(HaveOccurred())
type idModel struct {
TransactionId int64 `db:"transaction_id"`
ContractAddressId int64 `db:"contract_address_id"`
CumulativeGasUsed uint64 `db:"cumulative_gas_used"`
GasUsed uint64 `db:"gas_used"`
StateRoot string `db:"state_root"`
Status int
TxHash string `db:"tx_hash"`
Rlp []byte `db:"rlp"`
}
var addressId int64
getAddressErr := db.Get(&addressId, `SELECT id FROM addresses WHERE address = $1`, contractAddr.Hex())
Expect(getAddressErr).NotTo(HaveOccurred())
var dbReceipt idModel
getReceiptErr := db.Get(&dbReceipt,
`SELECT transaction_id, contract_address_id, cumulative_gas_used, gas_used, state_root, status, tx_hash, rlp
FROM public.header_sync_receipts WHERE header_id = $1`, headerID)
Expect(getReceiptErr).NotTo(HaveOccurred())
Expect(dbReceipt.TransactionId).To(Equal(txId))
Expect(dbReceipt.TxHash).To(Equal(txHash.Hex()))
Expect(dbReceipt.ContractAddressId).To(Equal(addressId))
Expect(dbReceipt.CumulativeGasUsed).To(Equal(uint64(100)))
Expect(dbReceipt.GasUsed).To(Equal(uint64(10)))
Expect(dbReceipt.StateRoot).To(Equal(stateRoot.Hex()))
Expect(dbReceipt.Status).To(Equal(0))
Expect(dbReceipt.Rlp).To(Equal([]byte{1, 2, 3}))
})
})
})
@@ -0,0 +1,92 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"database/sql"
"encoding/json"
"errors"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/filters"
)
type FilterRepository struct {
*postgres.DB
}
func (filterRepository FilterRepository) CreateFilter(query filters.LogFilter) error {
_, err := filterRepository.DB.Exec(
`INSERT INTO log_filters
(name, from_block, to_block, address, topic0, topic1, topic2, topic3)
VALUES ($1, NULLIF($2, -1), NULLIF($3, -1), $4, NULLIF($5, ''), NULLIF($6, ''), NULLIF($7, ''), NULLIF($8, ''))`,
query.Name, query.FromBlock, query.ToBlock, query.Address, query.Topics[0], query.Topics[1], query.Topics[2], query.Topics[3])
if err != nil {
return err
}
return nil
}
func (filterRepository FilterRepository) GetFilter(name string) (filters.LogFilter, error) {
lf := DBLogFilter{}
err := filterRepository.DB.Get(&lf,
`SELECT
id,
name,
from_block,
to_block,
address,
json_build_array(topic0, topic1, topic2, topic3) AS topics
FROM log_filters
WHERE name = $1`, name)
if err != nil {
switch err {
case sql.ErrNoRows:
return filters.LogFilter{}, datastore.ErrFilterDoesNotExist(name)
default:
return filters.LogFilter{}, err
}
}
dbLogFilterToCoreLogFilter(lf)
return *lf.LogFilter, nil
}
type DBTopics []*string
func (t *DBTopics) Scan(src interface{}) error {
asBytes, ok := src.([]byte)
if !ok {
return error(errors.New("scan source was not []byte"))
}
return json.Unmarshal(asBytes, &t)
}
type DBLogFilter struct {
ID int
*filters.LogFilter
Topics DBTopics
}
func dbLogFilterToCoreLogFilter(lf DBLogFilter) {
for i, v := range lf.Topics {
if v != nil {
lf.LogFilter.Topics[i] = *v
}
}
}
@@ -0,0 +1,127 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/eth/filters"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Log Filters Repository", func() {
var db *postgres.DB
var filterRepository datastore.FilterRepository
var node core.Node
BeforeEach(func() {
node = core.Node{
GenesisBlock: "GENESIS",
NetworkID: 1,
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
}
db = test_config.NewTestDB(node)
test_config.CleanTestDB(db)
filterRepository = repositories.FilterRepository{DB: db}
})
Describe("LogFilter", func() {
It("inserts filter into watched events", func() {
logFilter := filters.LogFilter{
Name: "TestFilter",
FromBlock: 1,
ToBlock: 2,
Address: "0x8888f1f195afa192cfee860698584c030f4c9db1",
Topics: core.Topics{
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
},
}
err := filterRepository.CreateFilter(logFilter)
Expect(err).ToNot(HaveOccurred())
})
It("returns error if name is not provided", func() {
logFilter := filters.LogFilter{
FromBlock: 1,
ToBlock: 2,
Address: "0x8888f1f195afa192cfee860698584c030f4c9db1",
Topics: core.Topics{
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
},
}
err := filterRepository.CreateFilter(logFilter)
Expect(err).To(HaveOccurred())
})
It("gets a log filter", func() {
expectedLogFilter1 := filters.LogFilter{
Name: "TestFilter1",
FromBlock: 1,
ToBlock: 2,
Address: "0x8888f1f195afa192cfee860698584c030f4c9db1",
Topics: core.Topics{
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
},
}
err := filterRepository.CreateFilter(expectedLogFilter1)
Expect(err).ToNot(HaveOccurred())
expectedLogFilter2 := filters.LogFilter{
Name: "TestFilter2",
FromBlock: 10,
ToBlock: 20,
Address: "0x8888f1f195afa192cfee860698584c030f4c9db1",
Topics: core.Topics{
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"",
},
}
err = filterRepository.CreateFilter(expectedLogFilter2)
Expect(err).ToNot(HaveOccurred())
logFilter1, err := filterRepository.GetFilter("TestFilter1")
Expect(err).ToNot(HaveOccurred())
Expect(logFilter1).To(Equal(expectedLogFilter1))
logFilter2, err := filterRepository.GetFilter("TestFilter2")
Expect(err).ToNot(HaveOccurred())
Expect(logFilter2).To(Equal(expectedLogFilter2))
})
It("returns ErrFilterDoesNotExist error when log does not exist", func() {
_, err := filterRepository.GetFilter("TestFilter1")
Expect(err).To(Equal(datastore.ErrFilterDoesNotExist("TestFilter1")))
})
})
})
@@ -0,0 +1,36 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"io/ioutil"
"testing"
"github.com/sirupsen/logrus"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestRepositories(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Repositories Suite")
}
var _ = BeforeSuite(func() {
logrus.SetOutput(ioutil.Discard)
})
@@ -0,0 +1,47 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"database/sql"
"github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
var ErrDuplicateDiff = sql.ErrNoRows
type StorageDiffRepository struct {
db *postgres.DB
}
func NewStorageDiffRepository(db *postgres.DB) StorageDiffRepository {
return StorageDiffRepository{db: db}
}
func (repository StorageDiffRepository) CreateStorageDiff(input utils.StorageDiffInput) (int64, error) {
var storageDiffID int64
row := repository.db.QueryRowx(`INSERT INTO public.storage_diff
(hashed_address, block_height, block_hash, storage_key, storage_value) VALUES ($1, $2, $3, $4, $5)
ON CONFLICT DO NOTHING RETURNING id`, input.HashedAddress.Bytes(), input.BlockHeight, input.BlockHash.Bytes(),
input.StorageKey.Bytes(), input.StorageValue.Bytes())
err := row.Scan(&storageDiffID)
if err != nil && err == sql.ErrNoRows {
return 0, ErrDuplicateDiff
}
return storageDiffID, err
}
@@ -0,0 +1,83 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
"database/sql"
"math/rand"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils"
"github.com/vulcanize/vulcanizedb/libraries/shared/test_data"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Storage diffs repository", func() {
var (
db *postgres.DB
repo repositories.StorageDiffRepository
fakeStorageDiff utils.StorageDiffInput
)
BeforeEach(func() {
db = test_config.NewTestDB(test_config.NewTestNode())
test_config.CleanTestDB(db)
repo = repositories.NewStorageDiffRepository(db)
fakeStorageDiff = utils.StorageDiffInput{
HashedAddress: test_data.FakeHash(),
BlockHash: test_data.FakeHash(),
BlockHeight: rand.Int(),
StorageKey: test_data.FakeHash(),
StorageValue: test_data.FakeHash(),
}
})
Describe("CreateStorageDiff", func() {
It("adds a storage diff to the db, returning id", func() {
id, createErr := repo.CreateStorageDiff(fakeStorageDiff)
Expect(createErr).NotTo(HaveOccurred())
Expect(id).NotTo(BeZero())
var persisted utils.PersistedStorageDiff
getErr := db.Get(&persisted, `SELECT * FROM public.storage_diff`)
Expect(getErr).NotTo(HaveOccurred())
Expect(persisted.ID).To(Equal(id))
Expect(persisted.HashedAddress).To(Equal(fakeStorageDiff.HashedAddress))
Expect(persisted.BlockHash).To(Equal(fakeStorageDiff.BlockHash))
Expect(persisted.BlockHeight).To(Equal(fakeStorageDiff.BlockHeight))
Expect(persisted.StorageKey).To(Equal(fakeStorageDiff.StorageKey))
Expect(persisted.StorageValue).To(Equal(fakeStorageDiff.StorageValue))
})
It("does not duplicate storage diffs", func() {
_, createErr := repo.CreateStorageDiff(fakeStorageDiff)
Expect(createErr).NotTo(HaveOccurred())
_, createTwoErr := repo.CreateStorageDiff(fakeStorageDiff)
Expect(createTwoErr).To(HaveOccurred())
Expect(createTwoErr).To(MatchError(sql.ErrNoRows))
var count int
getErr := db.Get(&count, `SELECT count(*) FROM public.storage_diff`)
Expect(getErr).NotTo(HaveOccurred())
Expect(count).To(Equal(1))
})
})
})
@@ -0,0 +1,52 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories
import (
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
)
type WatchedEventRepository struct {
*postgres.DB
}
func (watchedEventRepository WatchedEventRepository) GetWatchedEvents(name string) ([]*core.WatchedEvent, error) {
rows, err := watchedEventRepository.DB.Queryx(`SELECT id, name, block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data FROM watched_event_logs where name=$1`, name)
if err != nil {
logrus.Error("GetWatchedEvents: error getting watched events: ", err)
return nil, err
}
defer rows.Close()
lgs := make([]*core.WatchedEvent, 0)
for rows.Next() {
lg := new(core.WatchedEvent)
err = rows.StructScan(lg)
if err != nil {
logrus.Warn("GetWatchedEvents: error scanning log: ", err)
return nil, err
}
lgs = append(lgs, lg)
}
if err = rows.Err(); err != nil {
logrus.Warn("GetWatchedEvents: error scanning logs: ", err)
return nil, err
}
return lgs, nil
}
@@ -0,0 +1,161 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repositories_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/eth/filters"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("Watched Events Repository", func() {
var db *postgres.DB
var blocksRepository datastore.BlockRepository
var filterRepository datastore.FilterRepository
var logRepository datastore.FullSyncLogRepository
var receiptRepository datastore.FullSyncReceiptRepository
var watchedEventRepository datastore.WatchedEventRepository
BeforeEach(func() {
db = test_config.NewTestDB(core.Node{})
test_config.CleanTestDB(db)
blocksRepository = repositories.NewBlockRepository(db)
filterRepository = repositories.FilterRepository{DB: db}
logRepository = repositories.FullSyncLogRepository{DB: db}
receiptRepository = repositories.FullSyncReceiptRepository{DB: db}
watchedEventRepository = repositories.WatchedEventRepository{DB: db}
})
It("retrieves watched event logs that match the event filter", func() {
filter := filters.LogFilter{
Name: "Filter1",
FromBlock: 0,
ToBlock: 10,
Address: "0x123",
Topics: core.Topics{0: "event1=10", 2: "event3=hello"},
}
logs := []core.FullSyncLog{
{
BlockNumber: 0,
TxHash: "0x1",
Address: "0x123",
Topics: core.Topics{0: "event1=10", 2: "event3=hello"},
Index: 0,
Data: "",
},
}
expectedWatchedEventLog := []*core.WatchedEvent{
{
Name: "Filter1",
BlockNumber: 0,
TxHash: "0x1",
Address: "0x123",
Topic0: "event1=10",
Topic2: "event3=hello",
Index: 0,
Data: "",
},
}
err := filterRepository.CreateFilter(filter)
Expect(err).ToNot(HaveOccurred())
blockId, err := blocksRepository.CreateOrUpdateBlock(core.Block{})
Expect(err).NotTo(HaveOccurred())
tx, txBeginErr := db.Beginx()
Expect(txBeginErr).NotTo(HaveOccurred())
receiptId, err := receiptRepository.CreateFullSyncReceiptInTx(blockId, core.Receipt{}, tx)
tx.Commit()
Expect(err).NotTo(HaveOccurred())
err = logRepository.CreateLogs(logs, receiptId)
Expect(err).ToNot(HaveOccurred())
matchingLogs, err := watchedEventRepository.GetWatchedEvents("Filter1")
Expect(err).ToNot(HaveOccurred())
Expect(len(matchingLogs)).To(Equal(1))
Expect(matchingLogs[0].Name).To(Equal(expectedWatchedEventLog[0].Name))
Expect(matchingLogs[0].BlockNumber).To(Equal(expectedWatchedEventLog[0].BlockNumber))
Expect(matchingLogs[0].TxHash).To(Equal(expectedWatchedEventLog[0].TxHash))
Expect(matchingLogs[0].Address).To(Equal(expectedWatchedEventLog[0].Address))
Expect(matchingLogs[0].Topic0).To(Equal(expectedWatchedEventLog[0].Topic0))
Expect(matchingLogs[0].Topic1).To(Equal(expectedWatchedEventLog[0].Topic1))
Expect(matchingLogs[0].Topic2).To(Equal(expectedWatchedEventLog[0].Topic2))
Expect(matchingLogs[0].Data).To(Equal(expectedWatchedEventLog[0].Data))
})
It("retrieves a watched event log by name", func() {
filter := filters.LogFilter{
Name: "Filter1",
FromBlock: 0,
ToBlock: 10,
Address: "0x123",
Topics: core.Topics{0: "event1=10", 2: "event3=hello"},
}
logs := []core.FullSyncLog{
{
BlockNumber: 0,
TxHash: "0x1",
Address: "0x123",
Topics: core.Topics{0: "event1=10", 2: "event3=hello"},
Index: 0,
Data: "",
},
{
BlockNumber: 100,
TxHash: "",
Address: "",
Topics: core.Topics{},
Index: 0,
Data: "",
},
}
expectedWatchedEventLog := []*core.WatchedEvent{{
Name: "Filter1",
BlockNumber: 0,
TxHash: "0x1",
Address: "0x123",
Topic0: "event1=10",
Topic2: "event3=hello",
Index: 0,
Data: "",
}}
err := filterRepository.CreateFilter(filter)
Expect(err).ToNot(HaveOccurred())
blockId, err := blocksRepository.CreateOrUpdateBlock(core.Block{Hash: "Ox123"})
Expect(err).NotTo(HaveOccurred())
tx, _ := db.Beginx()
receiptId, err := receiptRepository.CreateFullSyncReceiptInTx(blockId, core.Receipt{}, tx)
tx.Commit()
Expect(err).NotTo(HaveOccurred())
err = logRepository.CreateLogs(logs, receiptId)
Expect(err).ToNot(HaveOccurred())
matchingLogs, err := watchedEventRepository.GetWatchedEvents("Filter1")
Expect(err).ToNot(HaveOccurred())
Expect(len(matchingLogs)).To(Equal(1))
Expect(matchingLogs[0].Name).To(Equal(expectedWatchedEventLog[0].Name))
Expect(matchingLogs[0].BlockNumber).To(Equal(expectedWatchedEventLog[0].BlockNumber))
Expect(matchingLogs[0].TxHash).To(Equal(expectedWatchedEventLog[0].TxHash))
Expect(matchingLogs[0].Address).To(Equal(expectedWatchedEventLog[0].Address))
Expect(matchingLogs[0].Topic0).To(Equal(expectedWatchedEventLog[0].Topic0))
Expect(matchingLogs[0].Topic1).To(Equal(expectedWatchedEventLog[0].Topic1))
Expect(matchingLogs[0].Topic2).To(Equal(expectedWatchedEventLog[0].Topic2))
Expect(matchingLogs[0].Data).To(Equal(expectedWatchedEventLog[0].Data))
})
})
+93
View File
@@ -0,0 +1,93 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package datastore
import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/jmoiron/sqlx"
"github.com/vulcanize/vulcanizedb/libraries/shared/storage/utils"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/filters"
)
type AddressRepository interface {
GetOrCreateAddress(address string) (int, error)
}
type BlockRepository interface {
CreateOrUpdateBlock(block core.Block) (int64, error)
GetBlock(blockNumber int64) (core.Block, error)
MissingBlockNumbers(startingBlockNumber, endingBlockNumber int64, nodeID string) []int64
SetBlocksStatus(chainHead int64) error
}
type CheckedHeadersRepository interface {
MarkHeaderChecked(headerID int64) error
MarkHeadersUnchecked(startingBlockNumber int64) error
UncheckedHeaders(startingBlockNumber, endingBlockNumber, checkCount int64) ([]core.Header, error)
}
type CheckedLogsRepository interface {
AlreadyWatchingLog(addresses []string, topic0 string) (bool, error)
MarkLogWatched(addresses []string, topic0 string) error
}
type ContractRepository interface {
CreateContract(contract core.Contract) error
GetContract(contractHash string) (core.Contract, error)
ContractExists(contractHash string) (bool, error)
}
type FilterRepository interface {
CreateFilter(filter filters.LogFilter) error
GetFilter(name string) (filters.LogFilter, error)
}
type FullSyncLogRepository interface {
CreateLogs(logs []core.FullSyncLog, receiptID int64) error
GetLogs(address string, blockNumber int64) ([]core.FullSyncLog, error)
}
type HeaderRepository interface {
CreateOrUpdateHeader(header core.Header) (int64, error)
CreateTransactions(headerID int64, transactions []core.TransactionModel) error
GetHeader(blockNumber int64) (core.Header, error)
MissingBlockNumbers(startingBlockNumber, endingBlockNumber int64, nodeID string) ([]int64, error)
}
type HeaderSyncLogRepository interface {
GetUntransformedHeaderSyncLogs() ([]core.HeaderSyncLog, error)
CreateHeaderSyncLogs(headerID int64, logs []types.Log) error
}
type FullSyncReceiptRepository interface {
CreateReceiptsAndLogs(blockID int64, receipts []core.Receipt) error
CreateFullSyncReceiptInTx(blockID int64, receipt core.Receipt, tx *sqlx.Tx) (int64, error)
GetFullSyncReceipt(txHash string) (core.Receipt, error)
}
type HeaderSyncReceiptRepository interface {
CreateFullSyncReceiptInTx(blockID int64, receipt core.Receipt, tx *sqlx.Tx) (int64, error)
}
type StorageDiffRepository interface {
CreateStorageDiff(input utils.StorageDiffInput) (int64, error)
}
type WatchedEventRepository interface {
GetWatchedEvents(name string) ([]*core.WatchedEvent, error)
}