trimming down to ipfs watchers

This commit is contained in:
Ian Norden
2020-05-29 22:02:47 -05:00
parent 78e9fbd248
commit fe083c12f9
422 changed files with 1310 additions and 28341 deletions
@@ -26,9 +26,9 @@ import (
"github.com/lib/pq"
log "github.com/sirupsen/logrus"
utils "github.com/vulcanize/vulcanizedb/libraries/shared/utilities"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
"github.com/vulcanize/ipfs-chain-watcher/utils"
)
// CIDRetriever satisfies the CIDRetriever interface for bitcoin
@@ -22,8 +22,8 @@ import (
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// Cleaner satisfies the shared.Cleaner interface fo bitcoin
@@ -23,21 +23,19 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
var (
// Block 0
// header variables
blockHash1 = crypto.Keccak256Hash([]byte{00, 02})
blocKNumber1 = big.NewInt(0)
headerCid1 = "mockHeader1CID"
parentHash = crypto.Keccak256Hash([]byte{00, 01})
totalDifficulty = "50000000000000000000"
reward = "5000000000000000000"
headerModel1 = btc.HeaderModel{
blockHash1 = crypto.Keccak256Hash([]byte{00, 02})
blocKNumber1 = big.NewInt(0)
headerCid1 = "mockHeader1CID"
parentHash = crypto.Keccak256Hash([]byte{00, 01})
headerModel1 = btc.HeaderModel{
BlockHash: blockHash1.String(),
BlockNumber: blocKNumber1.String(),
ParentHash: parentHash.String(),
@@ -23,7 +23,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// PayloadConverter satisfies the PayloadConverter interface for bitcoin
@@ -21,8 +21,8 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc/mocks"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc/mocks"
)
var _ = Describe("Converter", func() {
@@ -23,9 +23,9 @@ import (
"github.com/multiformats/go-multihash"
"github.com/vulcanize/vulcanizedb/pkg/ipfs"
"github.com/vulcanize/vulcanizedb/pkg/ipfs/ipld"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs/ipld"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// ResponseFilterer satisfies the ResponseFilterer interface for bitcoin
@@ -23,7 +23,7 @@ import (
"github.com/btcsuite/btcd/rpcclient"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// HTTPPayloadStreamer satisfies the PayloadStreamer interface for bitcoin over http endpoints (since bitcoin core doesn't support websockets)
@@ -24,8 +24,8 @@ import (
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
type CIDIndexer struct {
@@ -20,10 +20,10 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc/mocks"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc/mocks"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
var _ = Describe("Indexer", func() {
@@ -26,8 +26,8 @@ import (
"github.com/ipfs/go-cid"
log "github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/ipfs"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
var (
@@ -22,9 +22,9 @@ import (
"github.com/jmoiron/sqlx"
log "github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/ipfs"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// IPLDPGFetcher satisfies the IPLDFetcher interface for ethereum
@@ -19,8 +19,8 @@ package mocks
import (
"fmt"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// PayloadConverter is the underlying struct for the Converter interface
@@ -19,11 +19,8 @@ package mocks
import (
"fmt"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// CIDIndexer is the underlying struct for the Indexer interface
@@ -36,7 +33,7 @@ type CIDIndexer struct {
func (repo *CIDIndexer) Index(cids shared.CIDsForIndexing) error {
cidPayload, ok := cids.(*btc.CIDPayload)
if !ok {
return fmt.Errorf("index expected cids type %T got %T", &eth.CIDPayload{}, cids)
return fmt.Errorf("index expected cids type %T got %T", &btc.CIDPayload{}, cids)
}
repo.PassedCIDPayload = append(repo.PassedCIDPayload, cidPayload)
return repo.ReturnErr
@@ -19,9 +19,9 @@ package mocks
import (
"fmt"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// IPLDPublisher is the underlying struct for the Publisher interface
@@ -25,7 +25,8 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
)
var (
@@ -23,7 +23,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// PayloadFetcher satisfies the PayloadFetcher interface for bitcoin
@@ -20,9 +20,9 @@ import (
"fmt"
"strconv"
"github.com/vulcanize/vulcanizedb/pkg/ipfs/ipld"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs/ipld"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// IPLDPublisherAndIndexer satisfies the IPLDPublisher interface for bitcoin
@@ -25,12 +25,12 @@ import (
"github.com/multiformats/go-multihash"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/ipfs/ipld"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs/ipld"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc/mocks"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc/mocks"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
var _ = Describe("PublishAndIndexer", func() {
@@ -20,10 +20,10 @@ import (
"fmt"
"strconv"
"github.com/vulcanize/vulcanizedb/pkg/ipfs"
"github.com/vulcanize/vulcanizedb/pkg/ipfs/dag_putters"
"github.com/vulcanize/vulcanizedb/pkg/ipfs/ipld"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs/dag_putters"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs/ipld"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// IPLDPublisher satisfies the IPLDPublisher for ethereum
@@ -23,9 +23,9 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
mocks2 "github.com/vulcanize/vulcanizedb/pkg/ipfs/mocks"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc"
"github.com/vulcanize/vulcanizedb/pkg/super_node/btc/mocks"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc"
"github.com/vulcanize/ipfs-chain-watcher/pkg/btc/mocks"
mocks2 "github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs/mocks"
)
var (
@@ -22,7 +22,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
const (
@@ -22,7 +22,7 @@ import (
"github.com/spf13/viper"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// SubscriptionSettings config is used by a subscriber to specify what bitcoin data to stream from the super node
@@ -19,7 +19,7 @@ package btc
import (
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
)
// TearDownDB is used to tear down the super node dbs after tests
@@ -19,7 +19,7 @@ package btc
import (
"math/big"
"github.com/vulcanize/vulcanizedb/pkg/ipfs"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
+1 -1
View File
@@ -38,7 +38,7 @@ var _ = Describe("Loading the config", func() {
It("reads the private config using the environment", func() {
viper.SetConfigName("config")
viper.AddConfigPath("$GOPATH/src/github.com/vulcanize/vulcanizedb/environments/")
viper.AddConfigPath("$GOPATH/src/github.com/vulcanize/ipfs-chain-watcher/environments/")
Expect(viper.Get("client.ipcpath")).To(BeNil())
testConfig := viper.New()
-218
View File
@@ -1,218 +0,0 @@
// 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 config
import (
"strings"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/vulcanize/vulcanizedb/pkg/eth"
)
// Config struct for generic contract transformer
type ContractConfig struct {
// Name for the transformer
Name string
// Ethereum network name; default "" is mainnet
Network string
// List of contract addresses (map to ensure no duplicates)
Addresses map[string]bool
// Map of contract address to abi
// If an address has no associated abi the parser will attempt to fetch one from etherscan
Abis map[string]string
// Map of contract address to slice of events
// Used to set which addresses to watch
// If any events are listed in the slice only those will be watched
// Otherwise all events in the contract ABI are watched
Events map[string][]string
// Map of contract address to slice of methods
// If any methods are listed in the slice only those will be polled
// Otherwise no methods will be polled
Methods map[string][]string
// Map of contract address to slice of event arguments to filter for
// If arguments are provided then only events which emit those arguments are watched
// Otherwise arguments are not filtered on events
EventArgs map[string][]string
// Map of contract address to slice of method arguments to limit polling to
// If arguments are provided then only those arguments are allowed as arguments in method polling
// Otherwise any argument of the right type seen emitted from events at that contract will be used in method polling
MethodArgs map[string][]string
// Map of contract address to their starting block
StartingBlocks map[string]int64
// Map of contract address to whether or not to pipe method polling results forward into subsequent method calls
Piping map[string]bool
}
func (contractConfig *ContractConfig) PrepConfig() {
addrs := viper.GetStringSlice("contract.addresses")
contractConfig.Network = viper.GetString("contract.network")
contractConfig.Addresses = make(map[string]bool, len(addrs))
contractConfig.Abis = make(map[string]string, len(addrs))
contractConfig.Methods = make(map[string][]string, len(addrs))
contractConfig.Events = make(map[string][]string, len(addrs))
contractConfig.MethodArgs = make(map[string][]string, len(addrs))
contractConfig.EventArgs = make(map[string][]string, len(addrs))
contractConfig.StartingBlocks = make(map[string]int64, len(addrs))
contractConfig.Piping = make(map[string]bool, len(addrs))
// De-dupe addresses
for _, addr := range addrs {
contractConfig.Addresses[strings.ToLower(addr)] = true
}
// Iterate over addresses to pull out config info for each contract
for _, addr := range addrs {
transformer := viper.GetStringMap("contract." + addr)
// Get and check abi
var abi string
abiInterface, abiOK := transformer["abi"]
if !abiOK {
log.Warnf("contract %s not configured with an ABI, will attempt to fetch it from Etherscan\r\n", addr)
} else {
abi, abiOK = abiInterface.(string)
if !abiOK {
log.Fatal(addr, "transformer `abi` not of type []string")
}
}
if abi != "" {
if _, abiErr := eth.ParseAbi(abi); abiErr != nil {
log.Fatal(addr, "transformer `abi` not valid JSON")
}
}
contractConfig.Abis[strings.ToLower(addr)] = abi
// Get and check events
events := make([]string, 0)
eventsInterface, eventsOK := transformer["events"]
if !eventsOK {
log.Warnf("contract %s not configured with a list of events to watch, will watch all events\r\n", addr)
events = []string{}
} else {
eventsI, eventsOK := eventsInterface.([]interface{})
if !eventsOK {
log.Fatal(addr, "transformer `events` not of type []string\r\n")
}
for _, strI := range eventsI {
str, strOK := strI.(string)
if !strOK {
log.Fatal(addr, "transformer `events` not of type []string\r\n")
}
events = append(events, str)
}
}
contractConfig.Events[strings.ToLower(addr)] = events
// Get and check methods
methods := make([]string, 0)
methodsInterface, methodsOK := transformer["methods"]
if !methodsOK {
log.Warnf("contract %s not configured with a list of methods to poll, will not poll any methods\r\n", addr)
methods = []string{}
} else {
methodsI, methodsOK := methodsInterface.([]interface{})
if !methodsOK {
log.Fatal(addr, "transformer `methods` not of type []string\r\n")
}
for _, strI := range methodsI {
str, strOK := strI.(string)
if !strOK {
log.Fatal(addr, "transformer `methods` not of type []string\r\n")
}
methods = append(methods, str)
}
}
contractConfig.Methods[strings.ToLower(addr)] = methods
// Get and check eventArgs
eventArgs := make([]string, 0)
eventArgsInterface, eventArgsOK := transformer["eventArgs"]
if !eventArgsOK {
log.Warnf("contract %s not configured with a list of event arguments to filter for, will not filter events for specific emitted values\r\n", addr)
eventArgs = []string{}
} else {
eventArgsI, eventArgsOK := eventArgsInterface.([]interface{})
if !eventArgsOK {
log.Fatal(addr, "transformer `eventArgs` not of type []string\r\n")
}
for _, strI := range eventArgsI {
str, strOK := strI.(string)
if !strOK {
log.Fatal(addr, "transformer `eventArgs` not of type []string\r\n")
}
eventArgs = append(eventArgs, str)
}
}
contractConfig.EventArgs[strings.ToLower(addr)] = eventArgs
// Get and check methodArgs
methodArgs := make([]string, 0)
methodArgsInterface, methodArgsOK := transformer["methodArgs"]
if !methodArgsOK {
log.Warnf("contract %s not configured with a list of method argument values to poll with, will poll methods with all available arguments\r\n", addr)
methodArgs = []string{}
} else {
methodArgsI, methodArgsOK := methodArgsInterface.([]interface{})
if !methodArgsOK {
log.Fatal(addr, "transformer `methodArgs` not of type []string\r\n")
}
for _, strI := range methodArgsI {
str, strOK := strI.(string)
if !strOK {
log.Fatal(addr, "transformer `methodArgs` not of type []string\r\n")
}
methodArgs = append(methodArgs, str)
}
}
contractConfig.MethodArgs[strings.ToLower(addr)] = methodArgs
// Get and check startingBlock
startInterface, startOK := transformer["startingblock"]
if !startOK {
log.Fatal(addr, "transformer config is missing `startingBlock` value\r\n")
}
start, startOK := startInterface.(int64)
if !startOK {
log.Fatal(addr, "transformer `startingBlock` not of type int\r\n")
}
contractConfig.StartingBlocks[strings.ToLower(addr)] = start
// Get pipping
var piping bool
_, pipeOK := transformer["piping"]
if !pipeOK {
log.Warnf("contract %s does not have its `piping` set, by default piping is turned off\r\n", addr)
piping = false
} else {
pipingInterface := transformer["piping"]
piping, pipeOK = pipingInterface.(bool)
if !pipeOK {
log.Fatal(addr, "transformer `piping` not of type bool\r\n")
}
}
contractConfig.Piping[strings.ToLower(addr)] = piping
}
}
-167
View File
@@ -1,167 +0,0 @@
// 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 config
import (
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/vulcanize/vulcanizedb/pkg/plugin/helpers"
)
type Plugin struct {
Transformers map[string]Transformer
FilePath string
FileName string
Save bool
Home string
}
type Transformer struct {
Path string
Type TransformerType
MigrationPath string
MigrationRank uint64
RepositoryPath string
}
func (pluginConfig *Plugin) GetPluginPaths() (string, string, error) {
path, err := helpers.CleanPath(pluginConfig.FilePath)
if err != nil {
return "", "", err
}
name := strings.Split(pluginConfig.FileName, ".")[0]
goFile := filepath.Join(path, name+".go")
soFile := filepath.Join(path, name+".so")
return goFile, soFile, nil
}
// Removes duplicate migration paths and returns them in ranked order
func (pluginConfig *Plugin) GetMigrationsPaths() ([]string, error) {
paths := make(map[uint64]string)
highestRank := -1
for name, transformer := range pluginConfig.Transformers {
repo := transformer.RepositoryPath
mig := transformer.MigrationPath
path := filepath.Join("$GOPATH/src", pluginConfig.Home, "vendor", repo, mig)
cleanPath, err := helpers.CleanPath(path)
if err != nil {
return nil, err
}
// If there is a different path with the same rank then we have a conflict
_, ok := paths[transformer.MigrationRank]
if ok {
conflictingPath := paths[transformer.MigrationRank]
if conflictingPath != cleanPath {
return nil, fmt.Errorf("transformer %s has the same migration rank (%d) as another transformer", name, transformer.MigrationRank)
}
}
paths[transformer.MigrationRank] = cleanPath
if int(transformer.MigrationRank) >= highestRank {
highestRank = int(transformer.MigrationRank)
}
}
// Check for gaps and duplicates
if len(paths) != (highestRank + 1) {
return []string{}, errors.New("number of distinct ranks does not match number of distinct migration paths")
}
if anyDupes(paths) {
return []string{}, errors.New("duplicate paths with different ranks present")
}
sortedPaths := make([]string, len(paths))
for rank, path := range paths {
sortedPaths[rank] = path
}
return sortedPaths, nil
}
// Removes duplicate repo paths before returning them
func (pluginConfig *Plugin) GetRepoPaths() map[string]bool {
paths := make(map[string]bool)
for _, transformer := range pluginConfig.Transformers {
paths[transformer.RepositoryPath] = true
}
return paths
}
type TransformerType int
const (
UnknownTransformerType TransformerType = iota
EthEvent
EthStorage
EthContract
)
func (transformerType TransformerType) String() string {
names := [...]string{
"Unknown",
"eth_event",
"eth_storage",
"eth_contract",
}
if transformerType > EthContract || transformerType < EthEvent {
return "Unknown"
}
return names[transformerType]
}
func GetTransformerType(str string) TransformerType {
types := [...]TransformerType{
EthEvent,
EthStorage,
EthContract,
}
for _, ty := range types {
if ty.String() == str {
return ty
}
}
return UnknownTransformerType
}
func anyDupes(list map[uint64]string) bool {
seen := make([]string, 0, len(list))
for _, str := range list {
dupe := inList(str, seen)
if dupe {
return true
}
seen = append(seen, str)
}
return false
}
func inList(str string, list []string) bool {
for _, element := range list {
if str == element {
return true
}
}
return false
}
-242
View File
@@ -1,242 +0,0 @@
// 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 config_test
import (
"os"
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/config"
)
var allDifferentPathsConfig = config.Plugin{
Transformers: map[string]config.Transformer{
"transformer1": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer2": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path2",
MigrationRank: 2,
RepositoryPath: "test/repo/path",
},
"transformer3": {
Path: "test/init/path2",
Type: config.EthEvent,
MigrationPath: "test/migration/path3",
MigrationRank: 1,
RepositoryPath: "test/repo/path",
},
},
}
var overlappingPathsConfig = config.Plugin{
Transformers: map[string]config.Transformer{
"transformer1": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer2": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer3": {
Path: "test/init/path2",
Type: config.EthEvent,
MigrationPath: "test/migration/path3",
MigrationRank: 1,
RepositoryPath: "test/repo/path",
},
},
}
var conflictErrorConfig = config.Plugin{
Transformers: map[string]config.Transformer{
"transformer1": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer2": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path2",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer3": {
Path: "test/init/path2",
Type: config.EthEvent,
MigrationPath: "test/migration/path3",
MigrationRank: 1,
RepositoryPath: "test/repo/path",
},
},
}
var gapErrorConfig = config.Plugin{
Transformers: map[string]config.Transformer{
"transformer1": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer2": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path2",
MigrationRank: 3,
RepositoryPath: "test/repo/path",
},
"transformer3": {
Path: "test/init/path2",
Type: config.EthEvent,
MigrationPath: "test/migration/path3",
MigrationRank: 1,
RepositoryPath: "test/repo/path",
},
},
}
var missingRankErrorConfig = config.Plugin{
Transformers: map[string]config.Transformer{
"transformer1": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer2": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path2",
RepositoryPath: "test/repo/path",
},
"transformer3": {
Path: "test/init/path2",
Type: config.EthEvent,
MigrationPath: "test/migration/path3",
MigrationRank: 1,
RepositoryPath: "test/repo/path",
},
},
}
var duplicateErrorConfig = config.Plugin{
Transformers: map[string]config.Transformer{
"transformer1": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
MigrationRank: 0,
RepositoryPath: "test/repo/path",
},
"transformer2": {
Path: "test/init/path",
Type: config.EthEvent,
MigrationPath: "test/migration/path1",
RepositoryPath: "test/repo/path",
MigrationRank: 2,
},
"transformer3": {
Path: "test/init/path2",
Type: config.EthEvent,
MigrationPath: "test/migration/path3",
MigrationRank: 1,
RepositoryPath: "test/repo/path",
},
},
}
var _ = Describe("GetMigrationsPaths", func() {
It("Sorts migration paths by rank", func() {
plugin := allDifferentPathsConfig
migrationPaths, err := plugin.GetMigrationsPaths()
Expect(err).ToNot(HaveOccurred())
Expect(len(migrationPaths)).To(Equal(3))
env := os.Getenv("GOPATH")
path1 := filepath.Join(env, "src/vendor/test/repo/path/test/migration/path1")
path2 := filepath.Join(env, "src/vendor/test/repo/path/test/migration/path3")
path3 := filepath.Join(env, "src/vendor/test/repo/path/test/migration/path2")
expectedMigrationPaths := []string{path1, path2, path3}
Expect(migrationPaths).To(Equal(expectedMigrationPaths))
})
It("Expects identical migration paths to have the same rank", func() {
plugin := overlappingPathsConfig
migrationPaths, err := plugin.GetMigrationsPaths()
Expect(err).ToNot(HaveOccurred())
Expect(len(migrationPaths)).To(Equal(2))
env := os.Getenv("GOPATH")
path1 := filepath.Join(env, "src/vendor/test/repo/path/test/migration/path1")
path2 := filepath.Join(env, "src/vendor/test/repo/path/test/migration/path3")
expectedMigrationPaths := []string{path1, path2}
Expect(migrationPaths).To(Equal(expectedMigrationPaths))
})
It("Fails if two different migration paths have the same rank", func() {
plugin := conflictErrorConfig
migrationPaths, err := plugin.GetMigrationsPaths()
Expect(err).To(HaveOccurred())
Expect(len(migrationPaths)).To(Equal(0))
Expect(err.Error()).To(ContainSubstring("has the same migration rank"))
})
It("Fails if there is a gap in the ranks of the migration paths", func() {
plugin := gapErrorConfig
migrationPaths, err := plugin.GetMigrationsPaths()
Expect(err).To(HaveOccurred())
Expect(len(migrationPaths)).To(Equal(0))
Expect(err.Error()).To(ContainSubstring("number of distinct ranks does not match number of distinct migration paths"))
})
It("Fails if a transformer is missing its rank", func() {
plugin := missingRankErrorConfig
migrationPaths, err := plugin.GetMigrationsPaths()
Expect(err).To(HaveOccurred())
Expect(len(migrationPaths)).To(Equal(0))
Expect(err.Error()).To(ContainSubstring("has the same migration rank"))
})
It("Fails if the same migration path has more than one rank", func() {
plugin := duplicateErrorConfig
migrationPaths, err := plugin.GetMigrationsPaths()
Expect(err).To(HaveOccurred())
Expect(len(migrationPaths)).To(Equal(0))
Expect(err.Error()).To(ContainSubstring("duplicate paths with different ranks present"))
})
})
@@ -21,7 +21,7 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/vulcanize/vulcanizedb/pkg/eth/client"
"github.com/vulcanize/ipfs-chain-watcher/pkg/client"
)
type RPCClient interface {
-105
View File
@@ -1,105 +0,0 @@
// 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 eth
import (
"errors"
"strings"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/vulcanize/vulcanizedb/pkg/fs"
)
var (
ErrInvalidAbiFile = errors.New("invalid abi")
ErrMissingAbiFile = errors.New("missing abi")
ErrAPIRequestFailed = errors.New("etherscan api request failed")
)
type Response struct {
Status string
Message string
Result string
}
type EtherScanAPI struct {
client *http.Client
url string
}
func NewEtherScanClient(url string) *EtherScanAPI {
return &EtherScanAPI{
client: &http.Client{Timeout: 10 * time.Second},
url: url,
}
}
func GenURL(network string) string {
switch network {
case "ropsten":
return "https://ropsten.etherscan.io"
case "kovan":
return "https://kovan.etherscan.io"
case "rinkeby":
return "https://rinkeby.etherscan.io"
default:
return "https://api.etherscan.io"
}
}
//https://api.etherscan.io/api?module=contract&action=getabi&address=%s
func (e *EtherScanAPI) GetAbi(contractHash string) (string, error) {
target := new(Response)
request := fmt.Sprintf("%s/api?module=contract&action=getabi&address=%s", e.url, contractHash)
r, err := e.client.Get(request)
if err != nil {
return "", ErrAPIRequestFailed
}
defer r.Body.Close()
err = json.NewDecoder(r.Body).Decode(&target)
return target.Result, err
}
func ParseAbiFile(abiFilePath string) (abi.ABI, error) {
abiString, err := ReadAbiFile(abiFilePath)
if err != nil {
return abi.ABI{}, ErrMissingAbiFile
}
return ParseAbi(abiString)
}
func ParseAbi(abiString string) (abi.ABI, error) {
parsedAbi, err := abi.JSON(strings.NewReader(abiString))
if err != nil {
return abi.ABI{}, ErrInvalidAbiFile
}
return parsedAbi, nil
}
func ReadAbiFile(abiFilePath string) (string, error) {
reader := fs.FsReader{}
filesBytes, err := reader.Read(abiFilePath)
if err != nil {
return "", ErrMissingAbiFile
}
return string(filesBytes), nil
}
-135
View File
@@ -1,135 +0,0 @@
// 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 eth_test
import (
"net/http"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/ghttp"
"github.com/vulcanize/vulcanizedb/pkg/eth"
"github.com/vulcanize/vulcanizedb/test_config"
)
var _ = Describe("ABI files", func() {
Describe("Reading ABI files", func() {
It("loads a valid ABI file", func() {
path := test_config.ABIFilePath + "valid_abi.json"
contractAbi, err := eth.ParseAbiFile(path)
Expect(contractAbi).NotTo(BeNil())
Expect(err).To(BeNil())
})
It("reads the contents of a valid ABI file", func() {
path := test_config.ABIFilePath + "valid_abi.json"
contractAbi, err := eth.ReadAbiFile(path)
Expect(contractAbi).To(Equal("[{\"foo\": \"bar\"}]"))
Expect(err).To(BeNil())
})
It("returns an error when the file does not exist", func() {
path := test_config.ABIFilePath + "missing_abi.json"
contractAbi, err := eth.ParseAbiFile(path)
Expect(contractAbi).To(Equal(abi.ABI{}))
Expect(err).To(Equal(eth.ErrMissingAbiFile))
})
It("returns an error when the file has invalid contents", func() {
path := test_config.ABIFilePath + "invalid_abi.json"
contractAbi, err := eth.ParseAbiFile(path)
Expect(contractAbi).To(Equal(abi.ABI{}))
Expect(err).To(Equal(eth.ErrInvalidAbiFile))
})
Describe("Request ABI from endpoint", func() {
var (
server *ghttp.Server
client *eth.EtherScanAPI
abiString string
err error
)
BeforeEach(func() {
server = ghttp.NewServer()
client = eth.NewEtherScanClient(server.URL())
path := test_config.ABIFilePath + "sample_abi.json"
abiString, err = eth.ReadAbiFile(path)
Expect(err).NotTo(HaveOccurred())
_, err = eth.ParseAbi(abiString)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
server.Close()
})
Describe("Fetching ABI from api (etherscan)", func() {
BeforeEach(func() {
response := fmt.Sprintf(`{"status":"1","message":"OK","result":%q}`, abiString)
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/api", "module=contract&action=getabi&address=0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"),
ghttp.RespondWith(http.StatusOK, response),
),
)
})
It("should make a GET request with supplied contract hash", func() {
abi, err := client.GetAbi("0xd26114cd6EE289AccF82350c8d8487fedB8A0C07")
Expect(server.ReceivedRequests()).Should(HaveLen(1))
Expect(err).ShouldNot(HaveOccurred())
Expect(abi).Should(Equal(abiString))
})
})
})
Describe("Generating etherscan endpoints based on network", func() {
It("should return the main endpoint as the default", func() {
url := eth.GenURL("")
Expect(url).To(Equal("https://api.etherscan.io"))
})
It("generates various test network endpoint if test network is supplied", func() {
ropstenUrl := eth.GenURL("ropsten")
rinkebyUrl := eth.GenURL("rinkeby")
kovanUrl := eth.GenURL("kovan")
Expect(ropstenUrl).To(Equal("https://ropsten.etherscan.io"))
Expect(kovanUrl).To(Equal("https://kovan.etherscan.io"))
Expect(rinkebyUrl).To(Equal("https://rinkeby.etherscan.io"))
})
})
})
})
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"context"
"math/big"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
@@ -30,10 +30,10 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth/mocks"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/eth"
"github.com/vulcanize/ipfs-chain-watcher/pkg/eth/mocks"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
var (
@@ -22,15 +22,15 @@ import (
"fmt"
"math/big"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/vulcanize/vulcanizedb/pkg/ipfs"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/ipfs"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
)
var (
-279
View File
@@ -1,279 +0,0 @@
// 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 eth
import (
"errors"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
"github.com/vulcanize/vulcanizedb/pkg/eth/client"
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
var ErrEmptyHeader = errors.New("empty header returned over RPC")
const MAX_BATCH_SIZE = 100
type BlockChain struct {
blockConverter vulcCommon.BlockConverter
ethClient core.EthClient
headerConverter vulcCommon.HeaderConverter
node core.Node
rpcClient core.RPCClient
transactionConverter vulcCommon.TransactionConverter
}
func NewBlockChain(ethClient core.EthClient, rpcClient core.RPCClient, node core.Node, converter vulcCommon.TransactionConverter) *BlockChain {
return &BlockChain{
blockConverter: vulcCommon.NewBlockConverter(converter),
ethClient: ethClient,
headerConverter: vulcCommon.HeaderConverter{},
node: node,
rpcClient: rpcClient,
transactionConverter: converter,
}
}
func (blockChain *BlockChain) GetBlockByNumber(blockNumber int64) (block core.Block, err error) {
gethBlock, err := blockChain.ethClient.BlockByNumber(context.Background(), big.NewInt(blockNumber))
logrus.Debugf("GetBlockByNumber called with block %d", blockNumber)
if err != nil {
return block, err
}
return blockChain.blockConverter.ToCoreBlock(gethBlock)
}
func (blockChain *BlockChain) GetEthLogsWithCustomQuery(query ethereum.FilterQuery) ([]types.Log, error) {
gethLogs, err := blockChain.ethClient.FilterLogs(context.Background(), query)
logrus.Debug("GetEthLogsWithCustomQuery called")
if err != nil {
return []types.Log{}, err
}
return gethLogs, nil
}
func (blockChain *BlockChain) GetHeaderByNumber(blockNumber int64) (header core.Header, err error) {
logrus.Debugf("GetHeaderByNumber called with block %d", blockNumber)
if blockChain.node.NetworkID == string(core.KOVAN_NETWORK_ID) {
return blockChain.getPOAHeader(blockNumber)
}
return blockChain.getPOWHeader(blockNumber)
}
func (blockChain *BlockChain) GetHeadersByNumbers(blockNumbers []int64) (header []core.Header, err error) {
logrus.Debug("GetHeadersByNumbers called")
if blockChain.node.NetworkID == string(core.KOVAN_NETWORK_ID) {
return blockChain.getPOAHeaders(blockNumbers)
}
return blockChain.getPOWHeaders(blockNumbers)
}
func (blockChain *BlockChain) GetFullSyncLogs(contract core.Contract, startingBlockNumber, endingBlockNumber *big.Int) ([]core.FullSyncLog, error) {
if endingBlockNumber == nil {
endingBlockNumber = startingBlockNumber
}
contractAddress := common.HexToAddress(contract.Hash)
fc := ethereum.FilterQuery{
FromBlock: startingBlockNumber,
ToBlock: endingBlockNumber,
Addresses: []common.Address{contractAddress},
Topics: nil,
}
gethLogs, err := blockChain.GetEthLogsWithCustomQuery(fc)
if err != nil {
return []core.FullSyncLog{}, err
}
logs := vulcCommon.ToFullSyncLogs(gethLogs)
return logs, nil
}
func (blockChain *BlockChain) GetTransactions(transactionHashes []common.Hash) ([]core.TransactionModel, error) {
numTransactions := len(transactionHashes)
var batch []client.BatchElem
transactions := make([]core.RPCTransaction, numTransactions)
for index, transactionHash := range transactionHashes {
batchElem := client.BatchElem{
Method: "eth_getTransactionByHash",
Result: &transactions[index],
Args: []interface{}{transactionHash},
}
batch = append(batch, batchElem)
}
rpcErr := blockChain.rpcClient.BatchCall(batch)
if rpcErr != nil {
return []core.TransactionModel{}, rpcErr
}
return blockChain.transactionConverter.ConvertRPCTransactionsToModels(transactions)
}
func (blockChain *BlockChain) LastBlock() (*big.Int, error) {
block, err := blockChain.ethClient.HeaderByNumber(context.Background(), nil)
if err != nil {
return big.NewInt(0), err
}
return block.Number, err
}
func (blockChain *BlockChain) Node() core.Node {
return blockChain.node
}
func (blockChain *BlockChain) getPOAHeader(blockNumber int64) (header core.Header, err error) {
var POAHeader core.POAHeader
blockNumberArg := hexutil.EncodeBig(big.NewInt(blockNumber))
includeTransactions := false
err = blockChain.rpcClient.CallContext(context.Background(), &POAHeader, "eth_getBlockByNumber", blockNumberArg, includeTransactions)
if err != nil {
return header, err
}
if POAHeader.Number == nil {
return header, ErrEmptyHeader
}
return blockChain.headerConverter.Convert(&types.Header{
ParentHash: POAHeader.ParentHash,
UncleHash: POAHeader.UncleHash,
Coinbase: POAHeader.Coinbase,
Root: POAHeader.Root,
TxHash: POAHeader.TxHash,
ReceiptHash: POAHeader.ReceiptHash,
Bloom: POAHeader.Bloom,
Difficulty: POAHeader.Difficulty.ToInt(),
Number: POAHeader.Number.ToInt(),
GasLimit: uint64(POAHeader.GasLimit),
GasUsed: uint64(POAHeader.GasUsed),
Time: uint64(POAHeader.Time),
Extra: POAHeader.Extra,
}, POAHeader.Hash.String()), nil
}
func (blockChain *BlockChain) getPOAHeaders(blockNumbers []int64) (headers []core.Header, err error) {
var batch []client.BatchElem
var POAHeaders [MAX_BATCH_SIZE]core.POAHeader
includeTransactions := false
for index, blockNumber := range blockNumbers {
if index >= MAX_BATCH_SIZE {
break
}
blockNumberArg := hexutil.EncodeBig(big.NewInt(blockNumber))
batchElem := client.BatchElem{
Method: "eth_getBlockByNumber",
Result: &POAHeaders[index],
Args: []interface{}{blockNumberArg, includeTransactions},
}
batch = append(batch, batchElem)
}
err = blockChain.rpcClient.BatchCall(batch)
if err != nil {
return headers, err
}
for _, POAHeader := range POAHeaders {
var header core.Header
//Header.Number of the newest block will return nil.
if _, err := strconv.ParseUint(POAHeader.Number.ToInt().String(), 16, 64); err == nil {
header = blockChain.headerConverter.Convert(&types.Header{
ParentHash: POAHeader.ParentHash,
UncleHash: POAHeader.UncleHash,
Coinbase: POAHeader.Coinbase,
Root: POAHeader.Root,
TxHash: POAHeader.TxHash,
ReceiptHash: POAHeader.ReceiptHash,
Bloom: POAHeader.Bloom,
Difficulty: POAHeader.Difficulty.ToInt(),
Number: POAHeader.Number.ToInt(),
GasLimit: uint64(POAHeader.GasLimit),
GasUsed: uint64(POAHeader.GasUsed),
Time: uint64(POAHeader.Time),
Extra: POAHeader.Extra,
}, POAHeader.Hash.String())
headers = append(headers, header)
}
}
return headers, err
}
func (blockChain *BlockChain) getPOWHeader(blockNumber int64) (header core.Header, err error) {
gethHeader, err := blockChain.ethClient.HeaderByNumber(context.Background(), big.NewInt(blockNumber))
if err != nil {
return header, err
}
return blockChain.headerConverter.Convert(gethHeader, gethHeader.Hash().String()), nil
}
func (blockChain *BlockChain) getPOWHeaders(blockNumbers []int64) (headers []core.Header, err error) {
var batch []client.BatchElem
var POWHeaders [MAX_BATCH_SIZE]types.Header
includeTransactions := false
for index, blockNumber := range blockNumbers {
if index >= MAX_BATCH_SIZE {
break
}
blockNumberArg := hexutil.EncodeBig(big.NewInt(blockNumber))
batchElem := client.BatchElem{
Method: "eth_getBlockByNumber",
Result: &POWHeaders[index],
Args: []interface{}{blockNumberArg, includeTransactions},
}
batch = append(batch, batchElem)
}
err = blockChain.rpcClient.BatchCall(batch)
if err != nil {
return headers, err
}
for _, POWHeader := range POWHeaders {
if POWHeader.Number != nil {
header := blockChain.headerConverter.Convert(&POWHeader, POWHeader.Hash().String())
headers = append(headers, header)
}
}
return headers, err
}
func (blockChain *BlockChain) GetAccountBalance(address common.Address, blockNumber *big.Int) (*big.Int, error) {
logrus.Debug("GetAccountBalance called")
return blockChain.ethClient.BalanceAt(context.Background(), address, blockNumber)
}
-272
View File
@@ -1,272 +0,0 @@
// 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 eth_test
import (
"context"
"errors"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth"
vulcCore "github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
)
var _ = Describe("Geth blockchain", func() {
var (
mockClient *fakes.MockEthClient
blockChain *eth.BlockChain
mockRpcClient *fakes.MockRPCClient
mockTransactionConverter *fakes.MockTransactionConverter
node vulcCore.Node
)
BeforeEach(func() {
mockClient = fakes.NewMockEthClient()
mockRpcClient = fakes.NewMockRPCClient()
mockTransactionConverter = fakes.NewMockTransactionConverter()
node = vulcCore.Node{}
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, mockTransactionConverter)
})
Describe("getting a block", func() {
It("fetches block from ethClient", func() {
mockClient.SetBlockByNumberReturnBlock(types.NewBlockWithHeader(&types.Header{}))
blockNumber := int64(100)
_, err := blockChain.GetBlockByNumber(blockNumber)
Expect(err).NotTo(HaveOccurred())
mockClient.AssertBlockByNumberCalledWith(context.Background(), big.NewInt(blockNumber))
})
It("returns err if ethClient returns err", func() {
mockClient.SetBlockByNumberErr(fakes.FakeError)
_, err := blockChain.GetBlockByNumber(100)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
})
Describe("getting a header", func() {
Describe("default/mainnet", func() {
It("fetches header from ethClient", func() {
blockNumber := int64(100)
mockClient.SetHeaderByNumberReturnHeader(&types.Header{Number: big.NewInt(blockNumber)})
_, err := blockChain.GetHeaderByNumber(blockNumber)
Expect(err).NotTo(HaveOccurred())
mockClient.AssertHeaderByNumberCalledWith(context.Background(), big.NewInt(blockNumber))
})
It("returns err if ethClient returns err", func() {
mockClient.SetHeaderByNumberErr(fakes.FakeError)
_, err := blockChain.GetHeaderByNumber(100)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
It("fetches headers with multiple blocks", func() {
_, err := blockChain.GetHeadersByNumbers([]int64{100, 99})
Expect(err).NotTo(HaveOccurred())
mockRpcClient.AssertBatchCalledWith("eth_getBlockByNumber", 2)
})
})
Describe("POA/Kovan", func() {
It("fetches header from rpcClient", func() {
node.NetworkID = string(vulcCore.KOVAN_NETWORK_ID)
blockNumber := hexutil.Big(*big.NewInt(100))
mockRpcClient.SetReturnPOAHeader(vulcCore.POAHeader{Number: &blockNumber})
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, fakes.NewMockTransactionConverter())
_, err := blockChain.GetHeaderByNumber(100)
Expect(err).NotTo(HaveOccurred())
mockRpcClient.AssertCallContextCalledWith(context.Background(), &vulcCore.POAHeader{}, "eth_getBlockByNumber")
})
It("returns err if rpcClient returns err", func() {
node.NetworkID = string(vulcCore.KOVAN_NETWORK_ID)
mockRpcClient.SetCallContextErr(fakes.FakeError)
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, fakes.NewMockTransactionConverter())
_, err := blockChain.GetHeaderByNumber(100)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
It("returns error if returned header is empty", func() {
node.NetworkID = string(vulcCore.KOVAN_NETWORK_ID)
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, fakes.NewMockTransactionConverter())
_, err := blockChain.GetHeaderByNumber(100)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(eth.ErrEmptyHeader))
})
It("returns multiple headers with multiple blocknumbers", func() {
node.NetworkID = string(vulcCore.KOVAN_NETWORK_ID)
blockNumber := hexutil.Big(*big.NewInt(100))
mockRpcClient.SetReturnPOAHeaders([]vulcCore.POAHeader{{Number: &blockNumber}})
_, err := blockChain.GetHeadersByNumbers([]int64{100, 99})
Expect(err).NotTo(HaveOccurred())
mockRpcClient.AssertBatchCalledWith("eth_getBlockByNumber", 2)
})
})
})
Describe("getting logs with default FilterQuery", func() {
It("fetches logs from ethClient", func() {
mockClient.SetFilterLogsReturnLogs([]types.Log{{}})
contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()}
startingBlockNumber := big.NewInt(1)
endingBlockNumber := big.NewInt(2)
_, err := blockChain.GetFullSyncLogs(contract, startingBlockNumber, endingBlockNumber)
Expect(err).NotTo(HaveOccurred())
expectedQuery := ethereum.FilterQuery{
FromBlock: startingBlockNumber,
ToBlock: endingBlockNumber,
Addresses: []common.Address{common.HexToAddress(contract.Hash)},
}
mockClient.AssertFilterLogsCalledWith(context.Background(), expectedQuery)
})
It("returns err if ethClient returns err", func() {
mockClient.SetFilterLogsErr(fakes.FakeError)
contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()}
startingBlockNumber := big.NewInt(1)
endingBlockNumber := big.NewInt(2)
_, err := blockChain.GetFullSyncLogs(contract, startingBlockNumber, endingBlockNumber)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
})
Describe("getting logs with a custom FilterQuery", func() {
It("fetches logs from ethClient", func() {
mockClient.SetFilterLogsReturnLogs([]types.Log{{}})
address := common.HexToAddress("0x")
startingBlockNumber := big.NewInt(1)
endingBlockNumber := big.NewInt(2)
topic := common.HexToHash("0x")
query := ethereum.FilterQuery{
FromBlock: startingBlockNumber,
ToBlock: endingBlockNumber,
Addresses: []common.Address{address},
Topics: [][]common.Hash{{topic}},
}
_, err := blockChain.GetEthLogsWithCustomQuery(query)
Expect(err).NotTo(HaveOccurred())
mockClient.AssertFilterLogsCalledWith(context.Background(), query)
})
It("returns err if ethClient returns err", func() {
mockClient.SetFilterLogsErr(fakes.FakeError)
contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()}
startingBlockNumber := big.NewInt(1)
endingBlockNumber := big.NewInt(2)
query := ethereum.FilterQuery{
FromBlock: startingBlockNumber,
ToBlock: endingBlockNumber,
Addresses: []common.Address{common.HexToAddress(contract.Hash)},
Topics: nil,
}
_, err := blockChain.GetEthLogsWithCustomQuery(query)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
})
Describe("getting transactions", func() {
It("fetches transaction for each hash", func() {
_, err := blockChain.GetTransactions([]common.Hash{{}, {}})
Expect(err).NotTo(HaveOccurred())
mockRpcClient.AssertBatchCalledWith("eth_getTransactionByHash", 2)
})
It("converts transaction indexes from hex to int", func() {
_, err := blockChain.GetTransactions([]common.Hash{{}, {}})
Expect(err).NotTo(HaveOccurred())
Expect(mockTransactionConverter.ConvertHeaderTransactionIndexToIntCalled).To(BeTrue())
})
})
Describe("getting the most recent block number", func() {
It("fetches latest header from ethClient", func() {
blockNumber := int64(100)
mockClient.SetHeaderByNumberReturnHeader(&types.Header{Number: big.NewInt(blockNumber)})
result, err := blockChain.LastBlock()
Expect(err).NotTo(HaveOccurred())
mockClient.AssertHeaderByNumberCalledWith(context.Background(), nil)
Expect(result).To(Equal(big.NewInt(blockNumber)))
})
})
Describe("getting an account balance", func() {
It("fetches the balance for a given account address at a given block height", func() {
balance := big.NewInt(100000)
mockClient.SetBalanceAt(balance)
result, err := blockChain.GetAccountBalance(common.HexToAddress("0x40"), big.NewInt(100))
Expect(err).NotTo(HaveOccurred())
mockClient.AssertBalanceAtCalled(context.Background(), common.HexToAddress("0x40"), big.NewInt(100))
Expect(result).To(Equal(balance))
})
It("fails if the client returns an error", func() {
balance := big.NewInt(100000)
mockClient.SetBalanceAt(balance)
setErr := errors.New("testError")
mockClient.SetBalanceAtErr(setErr)
_, err := blockChain.GetAccountBalance(common.HexToAddress("0x40"), big.NewInt(100))
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(setErr))
})
})
})
@@ -27,9 +27,9 @@ import (
"github.com/lib/pq"
log "github.com/sirupsen/logrus"
utils "github.com/vulcanize/vulcanizedb/libraries/shared/utilities"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
"github.com/vulcanize/ipfs-chain-watcher/utils"
)
// CIDRetriever satisfies the CIDRetriever interface for ethereum
@@ -24,11 +24,11 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth"
eth2 "github.com/vulcanize/vulcanizedb/pkg/super_node/eth"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth/mocks"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/eth"
eth2 "github.com/vulcanize/ipfs-chain-watcher/pkg/eth"
"github.com/vulcanize/ipfs-chain-watcher/pkg/eth/mocks"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
var (
@@ -22,8 +22,8 @@ import (
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// Cleaner satisfies the shared.Cleaner interface fo ethereum
@@ -24,10 +24,9 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth"
eth2 "github.com/vulcanize/vulcanizedb/pkg/super_node/eth"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/eth"
"github.com/vulcanize/ipfs-chain-watcher/pkg/postgres"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
var (
@@ -39,7 +38,7 @@ var (
parentHash = crypto.Keccak256Hash([]byte{00, 01})
totalDifficulty = "50000000000000000000"
reward = "5000000000000000000"
headerModel = eth2.HeaderModel{
headerModel = eth.HeaderModel{
BlockHash: blockHash1.String(),
BlockNumber: blocKNumber1.String(),
CID: headerCID1,
@@ -55,7 +54,7 @@ var (
tx2Hash = crypto.Keccak256Hash([]byte{01, 02})
txSrc = common.HexToAddress("0x010a")
txDst = common.HexToAddress("0x020a")
txModels1 = []eth2.TxModel{
txModels1 = []eth.TxModel{
{
CID: tx1CID,
TxHash: tx1Hash.String(),
@@ -73,7 +72,7 @@ var (
uncleHash = crypto.Keccak256Hash([]byte{02, 02})
uncleParentHash = crypto.Keccak256Hash([]byte{02, 01})
uncleReward = "1000000000000000000"
uncleModels1 = []eth2.UncleModel{
uncleModels1 = []eth.UncleModel{
{
CID: uncleCID,
Reward: uncleReward,
@@ -87,7 +86,7 @@ var (
rct2CID = "mockRct2CID"
rct1Contract = common.Address{}
rct2Contract = common.HexToAddress("0x010c")
receiptModels1 = map[common.Hash]eth2.ReceiptModel{
receiptModels1 = map[common.Hash]eth.ReceiptModel{
tx1Hash: {
CID: rct1CID,
ContractHash: crypto.Keccak256Hash(rct1Contract.Bytes()).String(),
@@ -105,7 +104,7 @@ var (
state2CID1 = "mockState2CID1"
state2Path = []byte{'\x02'}
state2Key = crypto.Keccak256Hash(txDst.Bytes())
stateModels1 = []eth2.StateNodeModel{
stateModels1 = []eth.StateNodeModel{
{
CID: state1CID1,
Path: state1Path,
@@ -124,7 +123,7 @@ var (
storageCID = "mockStorageCID1"
storagePath = []byte{'\x01'}
storageKey = crypto.Keccak256Hash(common.Hex2Bytes("0x0000000000000000000000000000000000000000000000000000000000000000"))
storageModels1 = map[string][]eth2.StorageNodeModel{
storageModels1 = map[string][]eth.StorageNodeModel{
common.Bytes2Hex(state1Path): {
{
CID: storageCID,
@@ -148,7 +147,7 @@ var (
blockHash2 = crypto.Keccak256Hash([]byte{00, 03})
blocKNumber2 = big.NewInt(1)
headerCID2 = "mockHeaderCID2"
headerModel2 = eth2.HeaderModel{
headerModel2 = eth.HeaderModel{
BlockHash: blockHash2.String(),
BlockNumber: blocKNumber2.String(),
CID: headerCID2,
@@ -159,7 +158,7 @@ var (
// tx variables
tx3CID = "mockTx3CID"
tx3Hash = crypto.Keccak256Hash([]byte{01, 03})
txModels2 = []eth2.TxModel{
txModels2 = []eth.TxModel{
{
CID: tx3CID,
TxHash: tx3Hash.String(),
@@ -168,7 +167,7 @@ var (
}
// receipt variables
rct3CID = "mockRct3CID"
receiptModels2 = map[common.Hash]eth2.ReceiptModel{
receiptModels2 = map[common.Hash]eth.ReceiptModel{
tx3Hash: {
CID: rct3CID,
ContractHash: crypto.Keccak256Hash(rct1Contract.Bytes()).String(),
@@ -177,7 +176,7 @@ var (
// state variables
state1CID2 = "mockState1CID2"
stateModels2 = []eth2.StateNodeModel{
stateModels2 = []eth.StateNodeModel{
{
CID: state1CID2,
Path: state1Path,
@@ -213,15 +212,15 @@ var (
var _ = Describe("Cleaner", func() {
var (
db *postgres.DB
repo *eth2.CIDIndexer
cleaner *eth2.Cleaner
repo *eth.CIDIndexer
cleaner *eth.Cleaner
)
BeforeEach(func() {
var err error
db, err = shared.SetupDB()
Expect(err).ToNot(HaveOccurred())
repo = eth2.NewCIDIndexer(db)
cleaner = eth2.NewCleaner(db)
repo = eth.NewCIDIndexer(db)
cleaner = eth.NewCleaner(db)
})
Describe("Clean", func() {
BeforeEach(func() {
-61
View File
@@ -1,61 +0,0 @@
// 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 eth
import (
"context"
"errors"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
)
var (
ErrInvalidStateAttribute = errors.New("invalid state attribute")
)
func (blockChain *BlockChain) FetchContractData(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error {
parsed, err := ParseAbi(abiJSON)
if err != nil {
return err
}
var input []byte
if methodArgs != nil {
input, err = parsed.Pack(method, methodArgs...)
} else {
input, err = parsed.Pack(method)
}
if err != nil {
return err
}
var bn *big.Int
if blockNumber > 0 {
bn = big.NewInt(blockNumber)
}
output, err := blockChain.callContract(address, input, bn)
if err != nil {
return err
}
return parsed.Unpack(result, method, output)
}
func (blockChain *BlockChain) callContract(contractHash string, input []byte, blockNumber *big.Int) ([]byte, error) {
to := common.HexToAddress(contractHash)
msg := ethereum.CallMsg{To: &to, Data: input}
return blockChain.ethClient.CallContract(context.Background(), msg, blockNumber)
}
@@ -1,227 +0,0 @@
// 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 converter
import (
"encoding/json"
"fmt"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
)
// ConverterInterface is the interface for converting geth logs to our custom log type
type ConverterInterface interface {
Convert(logs []gethTypes.Log, event types.Event, headerID int64) ([]types.Log, error)
ConvertBatch(logs []gethTypes.Log, events map[string]types.Event, headerID int64) (map[string][]types.Log, error)
Update(info *contract.Contract)
}
// Converter is the underlying struct for the ConverterInterface
type Converter struct {
ContractInfo *contract.Contract
}
// Update is used to configure the converter with a specific contract
func (c *Converter) Update(info *contract.Contract) {
c.ContractInfo = info
}
// Convert the given watched event log into a types.Log for the given event
func (c *Converter) Convert(logs []gethTypes.Log, event types.Event, headerID int64) ([]types.Log, error) {
boundContract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil)
returnLogs := make([]types.Log, 0, len(logs))
for _, log := range logs {
values := make(map[string]interface{})
for _, field := range event.Fields {
var i interface{}
values[field.Name] = i
}
err := boundContract.UnpackLogIntoMap(values, event.Name, log)
if err != nil {
return nil, err
}
strValues := make(map[string]string, len(values))
seenAddrs := make([]interface{}, 0, len(values))
seenHashes := make([]interface{}, 0, len(values))
for fieldName, input := range values {
// Postgres cannot handle custom types, resolve everything to strings
switch input.(type) {
case *big.Int:
b := input.(*big.Int)
strValues[fieldName] = b.String()
case common.Address:
a := input.(common.Address)
strValues[fieldName] = a.String()
seenAddrs = append(seenAddrs, a)
case common.Hash:
h := input.(common.Hash)
strValues[fieldName] = h.String()
seenHashes = append(seenHashes, h)
case string:
strValues[fieldName] = input.(string)
case bool:
strValues[fieldName] = strconv.FormatBool(input.(bool))
case []byte:
b := input.([]byte)
strValues[fieldName] = hexutil.Encode(b)
if len(b) == 32 {
seenHashes = append(seenHashes, common.HexToHash(strValues[fieldName]))
}
case uint8:
u := input.(uint8)
strValues[fieldName] = strconv.Itoa(int(u))
case [32]uint8:
raw := input.([32]uint8)
converted := convertUintSliceToHash(raw)
strValues[fieldName] = converted.String()
seenHashes = append(seenHashes, converted)
default:
return nil, fmt.Errorf("error: unhandled abi type %T", input)
}
}
// Only hold onto logs that pass our address filter, if any
if c.ContractInfo.PassesEventFilter(strValues) {
raw, err := json.Marshal(log)
if err != nil {
return nil, err
}
returnLogs = append(returnLogs, types.Log{
LogIndex: log.Index,
Values: strValues,
Raw: raw,
TransactionIndex: log.TxIndex,
ID: headerID,
})
// Cache emitted values if their caching is turned on
if c.ContractInfo.EmittedAddrs != nil {
c.ContractInfo.AddEmittedAddr(seenAddrs...)
}
if c.ContractInfo.EmittedHashes != nil {
c.ContractInfo.AddEmittedHash(seenHashes...)
}
}
}
return returnLogs, nil
}
// ConvertBatch converts the given watched event logs into types.Logs; returns a map of event names to a slice of their converted logs
func (c *Converter) ConvertBatch(logs []gethTypes.Log, events map[string]types.Event, headerID int64) (map[string][]types.Log, error) {
boundContract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil)
eventsToLogs := make(map[string][]types.Log)
for _, event := range events {
eventsToLogs[event.Name] = make([]types.Log, 0, len(logs))
// Iterate through all event logs
for _, log := range logs {
// If the log is of this event type, process it as such
if event.Sig() == log.Topics[0] {
values := make(map[string]interface{})
err := boundContract.UnpackLogIntoMap(values, event.Name, log)
if err != nil {
return nil, err
}
// Postgres cannot handle custom types, so we will resolve everything to strings
strValues := make(map[string]string, len(values))
// Keep track of addresses and hashes emitted from events
seenAddrs := make([]interface{}, 0, len(values))
seenHashes := make([]interface{}, 0, len(values))
for fieldName, input := range values {
switch input.(type) {
case *big.Int:
b := input.(*big.Int)
strValues[fieldName] = b.String()
case common.Address:
a := input.(common.Address)
strValues[fieldName] = a.String()
seenAddrs = append(seenAddrs, a)
case common.Hash:
h := input.(common.Hash)
strValues[fieldName] = h.String()
seenHashes = append(seenHashes, h)
case string:
strValues[fieldName] = input.(string)
case bool:
strValues[fieldName] = strconv.FormatBool(input.(bool))
case []byte:
b := input.([]byte)
strValues[fieldName] = hexutil.Encode(b)
if len(b) == 32 { // collect byte arrays of size 32 as hashes
seenHashes = append(seenHashes, common.BytesToHash(b))
}
case uint8:
u := input.(uint8)
strValues[fieldName] = strconv.Itoa(int(u))
case [32]uint8:
raw := input.([32]uint8)
converted := convertUintSliceToHash(raw)
strValues[fieldName] = converted.String()
seenHashes = append(seenHashes, converted)
default:
return nil, fmt.Errorf("error: unhandled abi type %T", input)
}
}
// Only hold onto logs that pass our argument filter, if any
if c.ContractInfo.PassesEventFilter(strValues) {
raw, err := json.Marshal(log)
if err != nil {
return nil, err
}
eventsToLogs[event.Name] = append(eventsToLogs[event.Name], types.Log{
LogIndex: log.Index,
Values: strValues,
Raw: raw,
TransactionIndex: log.TxIndex,
ID: headerID,
})
// Cache emitted values that pass the argument filter if their caching is turned on
if c.ContractInfo.EmittedAddrs != nil {
c.ContractInfo.AddEmittedAddr(seenAddrs...)
}
if c.ContractInfo.EmittedHashes != nil {
c.ContractInfo.AddEmittedHash(seenHashes...)
}
}
}
}
}
return eventsToLogs, nil
}
func convertUintSliceToHash(raw [32]uint8) common.Hash {
var asBytes []byte
for _, u := range raw {
asBytes = append(asBytes, u)
}
return common.BytesToHash(asBytes)
}
@@ -1,35 +0,0 @@
// 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 converter_test
import (
"io/ioutil"
"log"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestConverter(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Header Sync Converter Suite Test")
}
var _ = BeforeSuite(func() {
log.SetOutput(ioutil.Discard)
})
@@ -1,186 +0,0 @@
// 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 converter_test
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/converter"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers/mocks"
)
var _ = Describe("Converter", func() {
var con *contract.Contract
var tusdWantedEvents = []string{"Transfer", "Mint"}
var ensWantedEvents = []string{"NewOwner"}
var marketPlaceWantedEvents = []string{"OrderCreated"}
var molochWantedEvents = []string{"SubmitVote"}
var err error
Describe("Update", func() {
It("Updates contract info held by the converter", func() {
con = test_helpers.SetupTusdContract(tusdWantedEvents, []string{})
c := converter.Converter{}
c.Update(con)
Expect(c.ContractInfo).To(Equal(con))
info := test_helpers.SetupTusdContract([]string{}, []string{})
c.Update(info)
Expect(c.ContractInfo).To(Equal(info))
})
})
Describe("Convert", func() {
It("Converts a watched event log to mapping of event input names to values", func() {
con = test_helpers.SetupTusdContract(tusdWantedEvents, []string{})
_, ok := con.Events["Approval"]
Expect(ok).To(Equal(false))
event, ok := con.Events["Transfer"]
Expect(ok).To(Equal(true))
c := converter.Converter{}
c.Update(con)
logs, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
sender1 := common.HexToAddress("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")
sender2 := common.HexToAddress("0x000000000000000000000000000000000000000000000000000000000000af21")
value := helpers.BigFromString("1097077688018008265106216665536940668749033598146")
Expect(logs[0].Values["to"]).To(Equal(sender1.String()))
Expect(logs[0].Values["from"]).To(Equal(sender2.String()))
Expect(logs[0].Values["value"]).To(Equal(value.String()))
Expect(logs[0].ID).To(Equal(int64(232)))
Expect(logs[1].Values["to"]).To(Equal(sender2.String()))
Expect(logs[1].Values["from"]).To(Equal(sender1.String()))
Expect(logs[1].Values["value"]).To(Equal(value.String()))
Expect(logs[1].ID).To(Equal(int64(232)))
})
It("Keeps track of addresses it sees if they will be used for method polling", func() {
con = test_helpers.SetupTusdContract(tusdWantedEvents, []string{"balanceOf"})
event, ok := con.Events["Transfer"]
Expect(ok).To(Equal(true))
c := converter.Converter{}
c.Update(con)
_, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232)
Expect(err).ToNot(HaveOccurred())
b, ok := con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = con.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
_, ok = con.EmittedAddrs[common.HexToAddress("0x")]
Expect(ok).To(Equal(false))
_, ok = con.EmittedAddrs[""]
Expect(ok).To(Equal(false))
_, ok = con.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")]
Expect(ok).To(Equal(false))
_, ok = con.EmittedHashes[common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553")]
Expect(ok).To(Equal(false))
})
It("Keeps track of hashes it sees if they will be used for method polling", func() {
con = test_helpers.SetupENSContract(ensWantedEvents, []string{"owner"})
event, ok := con.Events["NewOwner"]
Expect(ok).To(Equal(true))
c := converter.Converter{}
c.Update(con)
_, err := c.Convert([]types.Log{mocks.MockNewOwnerLog1, mocks.MockNewOwnerLog2}, event, 232)
Expect(err).ToNot(HaveOccurred())
Expect(len(con.EmittedHashes)).To(Equal(3))
b, ok := con.EmittedHashes[common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = con.EmittedHashes[common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = con.EmittedHashes[common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba400")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
_, ok = con.EmittedHashes[common.HexToHash("0x9dd48thiscc444isc242510c0made03upa5975cac061dhashb843bce061ba400")]
Expect(ok).To(Equal(false))
_, ok = con.EmittedHashes[common.HexToAddress("0x")]
Expect(ok).To(Equal(false))
_, ok = con.EmittedHashes[""]
Expect(ok).To(Equal(false))
// Does not keep track of emitted addresses if the methods provided will not use them
_, ok = con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(false))
})
It("correctly parses bytes32", func() {
con = test_helpers.SetupMarketPlaceContract(marketPlaceWantedEvents, []string{})
event, ok := con.Events["OrderCreated"]
Expect(ok).To(BeTrue())
c := converter.Converter{}
c.Update(con)
result, err := c.Convert([]types.Log{mocks.MockOrderCreatedLog}, event, 232)
Expect(err).NotTo(HaveOccurred())
Expect(len(result)).To(Equal(1))
Expect(result[0].Values["id"]).To(Equal("0x633f94affdcabe07c000231f85c752c97b9cc43966b432ec4d18641e6d178233"))
})
It("correctly parses uint8", func() {
con = test_helpers.SetupMolochContract(molochWantedEvents, []string{})
event, ok := con.Events["SubmitVote"]
Expect(ok).To(BeTrue())
c := converter.Converter{}
c.Update(con)
result, err := c.Convert([]types.Log{mocks.MockSubmitVoteLog}, event, 232)
Expect(err).NotTo(HaveOccurred())
Expect(len(result)).To(Equal(1))
Expect(result[0].Values["uintVote"]).To(Equal("1"))
})
It("Fails with an empty contract", func() {
event := con.Events["Transfer"]
c := converter.Converter{}
c.Update(&contract.Contract{})
_, err = c.Convert([]types.Log{mocks.MockTransferLog1}, event, 232)
Expect(err).To(HaveOccurred())
})
})
})
@@ -1,71 +0,0 @@
// 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 fetcher
import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
// Fetcher is the fetching interface
type Fetcher interface {
FetchLogs(contractAddresses []string, topics []common.Hash, missingHeader core.Header) ([]types.Log, error)
}
type fetcher struct {
blockChain core.BlockChain
}
// NewFetcher returns a new Fetcher
func NewFetcher(blockchain core.BlockChain) Fetcher {
return &fetcher{
blockChain: blockchain,
}
}
// FetchLogs checks all topic0s, on all addresses, fetching matching logs for the given header
func (fetcher *fetcher) FetchLogs(contractAddresses []string, topic0s []common.Hash, header core.Header) ([]types.Log, error) {
addresses := hexStringsToAddresses(contractAddresses)
blockHash := common.HexToHash(header.Hash)
query := ethereum.FilterQuery{
BlockHash: &blockHash,
Addresses: addresses,
// Search for _any_ of the topics in topic0 position; see docs on `FilterQuery`
Topics: [][]common.Hash{topic0s},
}
logs, err := fetcher.blockChain.GetEthLogsWithCustomQuery(query)
if err != nil {
// TODO review aggregate fetching error handling
return []types.Log{}, err
}
return logs, nil
}
func hexStringsToAddresses(hexStrings []string) []common.Address {
var addresses []common.Address
for _, hexString := range hexStrings {
address := common.HexToAddress(hexString)
addresses = append(addresses, address)
}
return addresses
}
@@ -1,35 +0,0 @@
// 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 fetcher_test
import (
"io/ioutil"
"log"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestFetcher(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Fetcher Suite Test")
}
var _ = BeforeSuite(func() {
log.SetOutput(ioutil.Discard)
})
@@ -1,66 +0,0 @@
// 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 fetcher_test
import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/fetcher"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
)
var _ = Describe("Fetcher", func() {
Describe("FetchLogs", func() {
It("fetches logs based on the given query", func() {
blockChain := fakes.NewMockBlockChain()
fetcher := fetcher.NewFetcher(blockChain)
header := fakes.FakeHeader
addresses := []string{"0xfakeAddress", "0xanotherFakeAddress"}
topicZeros := [][]common.Hash{{common.BytesToHash([]byte{1, 2, 3, 4, 5})}}
_, err := fetcher.FetchLogs(addresses, []common.Hash{common.BytesToHash([]byte{1, 2, 3, 4, 5})}, header)
address1 := common.HexToAddress("0xfakeAddress")
address2 := common.HexToAddress("0xanotherFakeAddress")
Expect(err).NotTo(HaveOccurred())
blockHash := common.HexToHash(header.Hash)
expectedQuery := ethereum.FilterQuery{
BlockHash: &blockHash,
Addresses: []common.Address{address1, address2},
Topics: topicZeros,
}
blockChain.AssertGetEthLogsWithCustomQueryCalledWith(expectedQuery)
})
It("returns an error if fetching the logs fails", func() {
blockChain := fakes.NewMockBlockChain()
blockChain.SetGetEthLogsWithCustomQueryErr(fakes.FakeError)
fetcher := fetcher.NewFetcher(blockChain)
_, err := fetcher.FetchLogs([]string{}, []common.Hash{}, core.Header{})
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
})
})
})
@@ -1,271 +0,0 @@
// 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 repository
import (
"fmt"
"github.com/hashicorp/golang-lru"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
const columnCacheSize = 1000
// HeaderRepository interfaces with the header and checked_headers tables
type HeaderRepository interface {
AddCheckColumn(id string) error
AddCheckColumns(ids []string) error
MarkHeaderChecked(headerID int64, eventID string) error
MarkHeaderCheckedForAll(headerID int64, ids []string) error
MarkHeadersCheckedForAll(headers []core.Header, ids []string) error
MissingHeaders(startingBlockNumber int64, endingBlockNumber int64, eventID string) ([]core.Header, error)
MissingMethodsCheckedEventsIntersection(startingBlockNumber, endingBlockNumber int64, methodIds, eventIds []string) ([]core.Header, error)
MissingHeadersForAll(startingBlockNumber, endingBlockNumber int64, ids []string) ([]core.Header, error)
CheckCache(key string) (interface{}, bool)
}
type headerRepository struct {
db *postgres.DB
columns *lru.Cache // Cache created columns to minimize db connections
}
// NewHeaderRepository returns a new HeaderRepository
func NewHeaderRepository(db *postgres.DB) HeaderRepository {
ccs, _ := lru.New(columnCacheSize)
return &headerRepository{
db: db,
columns: ccs,
}
}
// AddCheckColumn adds a checked_header column for the provided column id
func (r *headerRepository) AddCheckColumn(id string) error {
// Check cache to see if column already exists before querying pg
_, ok := r.columns.Get(id)
if ok {
return nil
}
pgStr := "ALTER TABLE public.checked_headers ADD COLUMN IF NOT EXISTS "
pgStr = pgStr + id + " INTEGER NOT NULL DEFAULT 0"
_, err := r.db.Exec(pgStr)
if err != nil {
return err
}
// Add column name to cache
r.columns.Add(id, true)
return nil
}
// AddCheckColumns adds a checked_header column for all of the provided column ids
func (r *headerRepository) AddCheckColumns(ids []string) error {
var err error
baseQuery := "ALTER TABLE public.checked_headers"
input := make([]string, 0, len(ids))
for _, id := range ids {
_, ok := r.columns.Get(id)
if !ok {
baseQuery += " ADD COLUMN IF NOT EXISTS " + id + " INTEGER NOT NULL DEFAULT 0,"
input = append(input, id)
}
}
if len(input) > 0 {
_, err = r.db.Exec(baseQuery[:len(baseQuery)-1])
if err == nil {
for _, id := range input {
r.columns.Add(id, true)
}
}
}
return err
}
// MarkHeaderChecked marks the header checked for the provided column id
func (r *headerRepository) MarkHeaderChecked(headerID int64, id string) error {
_, err := r.db.Exec(`INSERT INTO public.checked_headers (header_id, `+id+`)
VALUES ($1, $2)
ON CONFLICT (header_id) DO
UPDATE SET `+id+` = checked_headers.`+id+` + 1`, headerID, 1)
return err
}
// MarkHeaderCheckedForAll marks the header checked for all of the provided column ids
func (r *headerRepository) MarkHeaderCheckedForAll(headerID int64, ids []string) error {
pgStr := "INSERT INTO public.checked_headers (header_id, "
for _, id := range ids {
pgStr += id + ", "
}
pgStr = pgStr[:len(pgStr)-2] + ") VALUES ($1, "
for i := 0; i < len(ids); i++ {
pgStr += "1, "
}
pgStr = pgStr[:len(pgStr)-2] + ") ON CONFLICT (header_id) DO UPDATE SET "
for _, id := range ids {
pgStr += id + `= checked_headers.` + id + ` + 1, `
}
pgStr = pgStr[:len(pgStr)-2]
_, err := r.db.Exec(pgStr, headerID)
return err
}
// MarkHeadersCheckedForAll marks all of the provided headers checked for each of the provided column ids
func (r *headerRepository) MarkHeadersCheckedForAll(headers []core.Header, ids []string) error {
tx, err := r.db.Beginx()
if err != nil {
return err
}
for _, header := range headers {
pgStr := "INSERT INTO public.checked_headers (header_id, "
for _, id := range ids {
pgStr += id + ", "
}
pgStr = pgStr[:len(pgStr)-2] + ") VALUES ($1, "
for i := 0; i < len(ids); i++ {
pgStr += "1, "
}
pgStr = pgStr[:len(pgStr)-2] + ") ON CONFLICT (header_id) DO UPDATE SET "
for _, id := range ids {
pgStr += fmt.Sprintf("%s = checked_headers.%s + 1, ", id, id)
}
pgStr = pgStr[:len(pgStr)-2]
_, err = tx.Exec(pgStr, header.ID)
if err != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Warnf("error rolling back transaction: %s", rollbackErr.Error())
}
return err
}
}
err = tx.Commit()
return err
}
// MissingHeaders returns missing headers for the provided checked_headers column id
func (r *headerRepository) MissingHeaders(startingBlockNumber, endingBlockNumber int64, id string) ([]core.Header, error) {
var result []core.Header
var query string
var err error
if endingBlockNumber == -1 {
query = `SELECT headers.id, headers.block_number, headers.hash FROM headers
LEFT JOIN checked_headers on headers.id = header_id
WHERE (header_id ISNULL OR checked_headers.` + id + `=0)
AND headers.block_number >= $1
AND headers.eth_node_fingerprint = $2
ORDER BY headers.block_number`
err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID)
} else {
query = `SELECT headers.id, headers.block_number, headers.hash FROM headers
LEFT JOIN checked_headers on headers.id = header_id
WHERE (header_id ISNULL OR checked_headers.` + id + `=0)
AND headers.block_number >= $1
AND headers.block_number <= $2
AND headers.eth_node_fingerprint = $3
ORDER BY headers.block_number`
err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID)
}
return continuousHeaders(result), err
}
// MissingHeadersForAll returns missing headers for all of the provided checked_headers column ids
func (r *headerRepository) MissingHeadersForAll(startingBlockNumber, endingBlockNumber int64, ids []string) ([]core.Header, error) {
var result []core.Header
var query string
var err error
baseQuery := `SELECT headers.id, headers.block_number, headers.hash FROM headers
LEFT JOIN checked_headers on headers.id = header_id
WHERE (header_id ISNULL`
for _, id := range ids {
baseQuery += ` OR checked_headers.` + id + `= 0`
}
if endingBlockNumber == -1 {
endStr := `) AND headers.block_number >= $1
AND headers.eth_node_fingerprint = $2
ORDER BY headers.block_number`
query = baseQuery + endStr
err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID)
} else {
endStr := `) AND headers.block_number >= $1
AND headers.block_number <= $2
AND headers.eth_node_fingerprint = $3
ORDER BY headers.block_number`
query = baseQuery + endStr
err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID)
}
return continuousHeaders(result), err
}
// MissingMethodsCheckedEventsIntersection returns headers that have been checked for all of the provided event ids but not for the provided method ids
func (r *headerRepository) MissingMethodsCheckedEventsIntersection(startingBlockNumber, endingBlockNumber int64, methodIds, eventIds []string) ([]core.Header, error) {
var result []core.Header
var query string
var err error
baseQuery := `SELECT headers.id, headers.block_number, headers.hash FROM headers
LEFT JOIN checked_headers on headers.id = header_id
WHERE (header_id IS NOT NULL`
for _, id := range eventIds {
baseQuery += ` AND ` + id + `!=0`
}
baseQuery += `) AND (`
for _, id := range methodIds {
baseQuery += id + ` =0 AND `
}
baseQuery = baseQuery[:len(baseQuery)-5] + `) `
if endingBlockNumber == -1 {
endStr := `AND headers.block_number >= $1
AND headers.eth_node_fingerprint = $2
ORDER BY headers.block_number`
query = baseQuery + endStr
err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID)
} else {
endStr := `AND headers.block_number >= $1
AND headers.block_number <= $2
AND headers.eth_node_fingerprint = $3
ORDER BY headers.block_number`
query = baseQuery + endStr
err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID)
}
return continuousHeaders(result), err
}
// Returns a continuous set of headers
func continuousHeaders(headers []core.Header) []core.Header {
if len(headers) < 1 {
logrus.Trace("no headers to arrange continuously")
return headers
}
previousHeader := headers[0].BlockNumber
for i := 1; i < len(headers); i++ {
previousHeader++
if headers[i].BlockNumber != previousHeader {
return headers[:i]
}
}
return headers
}
// CheckCache checks the repositories column id cache for a value
func (r *headerRepository) CheckCache(key string) (interface{}, bool) {
return r.columns.Get(key)
}
@@ -1,371 +0,0 @@
// 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 repository_test
import (
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers/mocks"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
var _ = Describe("Repository", func() {
var db *postgres.DB
var contractHeaderRepo repository.HeaderRepository // contract_watcher headerSync header repository
var coreHeaderRepo repositories.HeaderRepository // pkg/datastore header repository
var eventIDs = []string{
"eventName_contractAddr",
"eventName_contractAddr2",
"eventName_contractAddr3",
}
var methodIDs = []string{
"methodName_contractAddr",
"methodName_contractAddr2",
"methodName_contractAddr3",
}
BeforeEach(func() {
db, _ = test_helpers.SetupDBandBC()
contractHeaderRepo = repository.NewHeaderRepository(db)
coreHeaderRepo = repositories.NewHeaderRepository(db)
})
AfterEach(func() {
test_helpers.TearDown(db)
})
Describe("AddCheckColumn", func() {
It("Creates a column for the given eventID to mark if the header has been checked for that event", func() {
query := fmt.Sprintf("SELECT %s FROM checked_headers", eventIDs[0])
_, err := db.Exec(query)
Expect(err).To(HaveOccurred())
err = contractHeaderRepo.AddCheckColumn(eventIDs[0])
Expect(err).ToNot(HaveOccurred())
_, err = db.Exec(query)
Expect(err).ToNot(HaveOccurred())
})
It("Caches column it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
_, ok := contractHeaderRepo.CheckCache(eventIDs[0])
Expect(ok).To(Equal(false))
err := contractHeaderRepo.AddCheckColumn(eventIDs[0])
Expect(err).ToNot(HaveOccurred())
v, ok := contractHeaderRepo.CheckCache(eventIDs[0])
Expect(ok).To(Equal(true))
Expect(v).To(Equal(true))
})
})
Describe("AddCheckColumns", func() {
It("Creates a column for the given eventIDs to mark if the header has been checked for those events", func() {
for _, id := range eventIDs {
_, err := db.Exec(fmt.Sprintf("SELECT %s FROM checked_headers", id))
Expect(err).To(HaveOccurred())
}
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).ToNot(HaveOccurred())
for _, id := range eventIDs {
_, err := db.Exec(fmt.Sprintf("SELECT %s FROM checked_headers", id))
Expect(err).ToNot(HaveOccurred())
}
})
It("Caches columns it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
for _, id := range eventIDs {
_, ok := contractHeaderRepo.CheckCache(id)
Expect(ok).To(Equal(false))
}
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).ToNot(HaveOccurred())
for _, id := range eventIDs {
v, ok := contractHeaderRepo.CheckCache(id)
Expect(ok).To(Equal(true))
Expect(v).To(Equal(true))
}
})
})
Describe("MissingHeaders", func() {
It("Returns all unchecked headers for the given eventID", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumn(eventIDs[0])
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
})
It("Returns unchecked headers in ascending order", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumn(eventIDs[0])
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
h1 := missingHeaders[0]
h2 := missingHeaders[1]
h3 := missingHeaders[2]
Expect(h1.BlockNumber).To(Equal(mocks.MockHeader1.BlockNumber))
Expect(h2.BlockNumber).To(Equal(mocks.MockHeader2.BlockNumber))
Expect(h3.BlockNumber).To(Equal(mocks.MockHeader3.BlockNumber))
})
It("Returns only contiguous chunks of headers", func() {
addDiscontinuousHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(2))
Expect(missingHeaders[0].BlockNumber).To(Equal(mocks.MockHeader1.BlockNumber))
Expect(missingHeaders[1].BlockNumber).To(Equal(mocks.MockHeader2.BlockNumber))
})
It("Fails if eventID does not yet exist in check_headers table", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumn(eventIDs[0])
Expect(err).ToNot(HaveOccurred())
_, err = contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, "notEventId")
Expect(err).To(HaveOccurred())
})
})
Describe("MissingHeadersForAll", func() { // HERE
It("Returns all headers that have not been checked for all of the ids provided", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeadersForAll(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
err = contractHeaderRepo.MarkHeaderChecked(missingHeaders[0].ID, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
missingHeaders, err = contractHeaderRepo.MissingHeadersForAll(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
err = contractHeaderRepo.MarkHeaderChecked(missingHeaders[0].ID, eventIDs[1])
Expect(err).ToNot(HaveOccurred())
err = contractHeaderRepo.MarkHeaderChecked(missingHeaders[0].ID, eventIDs[2])
Expect(err).ToNot(HaveOccurred())
missingHeaders, err = contractHeaderRepo.MissingHeadersForAll(mocks.MockHeader2.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(2))
})
It("Returns only contiguous chunks of headers", func() {
addDiscontinuousHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeadersForAll(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(2))
Expect(missingHeaders[0].BlockNumber).To(Equal(mocks.MockHeader1.BlockNumber))
Expect(missingHeaders[1].BlockNumber).To(Equal(mocks.MockHeader2.BlockNumber))
})
It("returns headers after starting header if starting header not missing", func() {
addLaterHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).NotTo(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeadersForAll(mocks.MockHeader1.BlockNumber, -1, eventIDs)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(2))
Expect(missingHeaders[0].BlockNumber).To(Equal(mocks.MockHeader3.BlockNumber))
Expect(missingHeaders[1].BlockNumber).To(Equal(mocks.MockHeader4.BlockNumber))
})
It("Fails if one of the eventIDs does not yet exist in check_headers table", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).ToNot(HaveOccurred())
badEventIDs := append(eventIDs, "notEventId")
_, err = contractHeaderRepo.MissingHeadersForAll(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, badEventIDs)
Expect(err).To(HaveOccurred())
})
})
Describe("MarkHeaderChecked", func() {
It("Marks the header checked for the given eventID", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumn(eventIDs[0])
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
headerID := missingHeaders[0].ID
err = contractHeaderRepo.MarkHeaderChecked(headerID, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
missingHeaders, err = contractHeaderRepo.MissingHeaders(mocks.MockHeader2.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(2))
})
It("Fails if eventID does not yet exist in check_headers table", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumn(eventIDs[0])
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
headerID := missingHeaders[0].ID
err = contractHeaderRepo.MarkHeaderChecked(headerID, "notEventId")
Expect(err).To(HaveOccurred())
missingHeaders, err = contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
})
})
Describe("MarkHeaderCheckedForAll", func() {
It("Marks the header checked for all provided column ids", func() {
addHeaders(coreHeaderRepo)
err := contractHeaderRepo.AddCheckColumns(eventIDs)
Expect(err).ToNot(HaveOccurred())
missingHeaders, err := contractHeaderRepo.MissingHeadersForAll(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
headerID := missingHeaders[0].ID
err = contractHeaderRepo.MarkHeaderCheckedForAll(headerID, eventIDs)
Expect(err).ToNot(HaveOccurred())
missingHeaders, err = contractHeaderRepo.MissingHeaders(mocks.MockHeader2.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(2))
})
})
Describe("MarkHeadersCheckedForAll", func() {
It("Marks the headers checked for all provided column ids", func() {
addHeaders(coreHeaderRepo)
methodIDs := []string{
"methodName_contractAddr",
"methodName_contractAddr2",
"methodName_contractAddr3",
}
var missingHeaders []core.Header
for _, id := range methodIDs {
err := contractHeaderRepo.AddCheckColumn(id)
Expect(err).ToNot(HaveOccurred())
missingHeaders, err = contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, id)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
}
err := contractHeaderRepo.MarkHeadersCheckedForAll(missingHeaders, methodIDs)
Expect(err).ToNot(HaveOccurred())
for _, id := range methodIDs {
missingHeaders, err = contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, id)
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(0))
}
})
})
Describe("MissingMethodsCheckedEventsIntersection", func() {
It("Returns headers that have been checked for all the provided events but have not been checked for all the provided methods", func() {
addHeaders(coreHeaderRepo)
for i, id := range eventIDs {
err := contractHeaderRepo.AddCheckColumn(id)
Expect(err).ToNot(HaveOccurred())
err = contractHeaderRepo.AddCheckColumn(methodIDs[i])
Expect(err).ToNot(HaveOccurred())
}
missingHeaders, err := contractHeaderRepo.MissingHeaders(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, eventIDs[0])
Expect(err).ToNot(HaveOccurred())
Expect(len(missingHeaders)).To(Equal(3))
headerID := missingHeaders[0].ID
headerID2 := missingHeaders[1].ID
for i, id := range eventIDs {
err = contractHeaderRepo.MarkHeaderChecked(headerID, id)
Expect(err).ToNot(HaveOccurred())
err = contractHeaderRepo.MarkHeaderChecked(headerID2, id)
Expect(err).ToNot(HaveOccurred())
err = contractHeaderRepo.MarkHeaderChecked(headerID, methodIDs[i])
Expect(err).ToNot(HaveOccurred())
}
intersectionHeaders, err := contractHeaderRepo.MissingMethodsCheckedEventsIntersection(mocks.MockHeader1.BlockNumber, mocks.MockHeader4.BlockNumber, methodIDs, eventIDs)
Expect(err).ToNot(HaveOccurred())
Expect(len(intersectionHeaders)).To(Equal(1))
Expect(intersectionHeaders[0].ID).To(Equal(headerID2))
})
})
})
func addHeaders(coreHeaderRepo repositories.HeaderRepository) {
_, err := coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader1)
Expect(err).NotTo(HaveOccurred())
_, err = coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader2)
Expect(err).NotTo(HaveOccurred())
_, err = coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader3)
Expect(err).NotTo(HaveOccurred())
}
func addDiscontinuousHeaders(coreHeaderRepo repositories.HeaderRepository) {
_, err := coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader1)
Expect(err).NotTo(HaveOccurred())
_, err = coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader2)
Expect(err).NotTo(HaveOccurred())
_, err = coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader4)
Expect(err).NotTo(HaveOccurred())
}
func addLaterHeaders(coreHeaderRepo repositories.HeaderRepository) {
_, err := coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader3)
Expect(err).NotTo(HaveOccurred())
_, err = coreHeaderRepo.CreateOrUpdateHeader(mocks.MockHeader4)
Expect(err).NotTo(HaveOccurred())
}
@@ -1,35 +0,0 @@
// 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 repository_test
import (
"io/ioutil"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)
func TestRepository(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Header Sync Repository Suite Test")
}
var _ = BeforeSuite(func() {
logrus.SetOutput(ioutil.Discard)
})
@@ -1,61 +0,0 @@
// 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 retriever
import (
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
// BlockRetriever is used to retrieve the first block for a given contract and the most recent block
// It requires a vDB synced database with blocks, transactions, receipts, and logs
type BlockRetriever interface {
RetrieveFirstBlock() (int64, error)
RetrieveMostRecentBlock() (int64, error)
}
type blockRetriever struct {
db *postgres.DB
}
// NewBlockRetriever returns a new BlockRetriever
func NewBlockRetriever(db *postgres.DB) BlockRetriever {
return &blockRetriever{
db: db,
}
}
// RetrieveFirstBlock retrieves block number of earliest header in repo
func (r *blockRetriever) RetrieveFirstBlock() (int64, error) {
var firstBlock int
err := r.db.Get(
&firstBlock,
"SELECT block_number FROM headers ORDER BY block_number LIMIT 1",
)
return int64(firstBlock), err
}
// RetrieveMostRecentBlock retrieves block number of latest header in repo
func (r *blockRetriever) RetrieveMostRecentBlock() (int64, error) {
var lastBlock int
err := r.db.Get(
&lastBlock,
"SELECT block_number FROM headers ORDER BY block_number DESC LIMIT 1",
)
return int64(lastBlock), err
}
@@ -1,85 +0,0 @@
// 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 retriever_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/retriever"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers/mocks"
"github.com/vulcanize/vulcanizedb/pkg/eth/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
var _ = Describe("Block Retriever", func() {
var db *postgres.DB
var r retriever.BlockRetriever
var headerRepository repositories.HeaderRepository
BeforeEach(func() {
db, _ = test_helpers.SetupDBandBC()
headerRepository = repositories.NewHeaderRepository(db)
r = retriever.NewBlockRetriever(db)
})
AfterEach(func() {
test_helpers.TearDown(db)
})
Describe("RetrieveFirstBlock", func() {
It("Retrieves block number of earliest header in the database", func() {
_, err := headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
Expect(err).ToNot(HaveOccurred())
_, err = headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
Expect(err).ToNot(HaveOccurred())
_, err = headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
Expect(err).ToNot(HaveOccurred())
i, err := r.RetrieveFirstBlock()
Expect(err).NotTo(HaveOccurred())
Expect(i).To(Equal(int64(6194632)))
})
It("Fails if no headers can be found in the database", func() {
_, err := r.RetrieveFirstBlock()
Expect(err).To(HaveOccurred())
})
})
Describe("RetrieveMostRecentBlock", func() {
It("Retrieves the latest header's block number", func() {
_, err := headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
Expect(err).ToNot(HaveOccurred())
_, err = headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
Expect(err).ToNot(HaveOccurred())
_, err = headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
Expect(err).ToNot(HaveOccurred())
i, err := r.RetrieveMostRecentBlock()
Expect(err).ToNot(HaveOccurred())
Expect(i).To(Equal(int64(6194634)))
})
It("Fails if no headers can be found in the database", func() {
i, err := r.RetrieveMostRecentBlock()
Expect(err).To(HaveOccurred())
Expect(i).To(Equal(int64(0)))
})
})
})
@@ -1,35 +0,0 @@
// 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 retriever_test
import (
"io/ioutil"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)
func TestRetriever(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Header Sync Block Number Retriever Suite Test")
}
var _ = BeforeSuite(func() {
logrus.SetOutput(ioutil.Discard)
})
@@ -1,337 +0,0 @@
// 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 transformer
import (
"database/sql"
"errors"
"fmt"
"math"
"strings"
"github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/config"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/converter"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/fetcher"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/retriever"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/parser"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/poller"
srep "github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
// Transformer is the top level struct for transforming watched contract data
// Requires a header synced vDB (headers) and a running eth node (or infura)
type Transformer struct {
// Database interfaces
EventRepository srep.EventRepository // Holds transformed watched event log data
HeaderRepository repository.HeaderRepository // Interface for interaction with header repositories
// Pre-processing interfaces
Parser parser.Parser // Parses events and methods out of contract abi fetched using contract address
Retriever retriever.BlockRetriever // Retrieves first block for contract
// Processing interfaces
Fetcher fetcher.Fetcher // Fetches event logs, using header hashes
Converter converter.ConverterInterface // Converts watched event logs into custom log
Poller poller.Poller // Polls methods using arguments collected from events and persists them using a method datastore
// Store contract configuration information
Config config.ContractConfig
// Store contract info as mapping to contract address
Contracts map[string]*contract.Contract
// Internally configured transformer variables
contractAddresses []string // Holds all contract addresses, for batch fetching of logs
sortedEventIds map[string][]string // Map to sort event column ids by contract, for post fetch processing and persisting of logs
sortedMethodIds map[string][]string // Map to sort method column ids by contract, for post fetch method polling
eventIds []string // Holds event column ids across all contract, for batch fetching of headers
eventFilters []common.Hash // Holds topic0 hashes across all contracts, for batch fetching of logs
Start int64 // Hold the lowest starting block and the highest ending block
}
// Order-of-operations:
// 1. Create new transformer
// 2. Load contract addresses and their parameters
// 3. Init
// 4. Execute
// NewTransformer takes in a contract config, blockchain, and database, and returns a new Transformer
func NewTransformer(con config.ContractConfig, bc core.BlockChain, db *postgres.DB) *Transformer {
return &Transformer{
Poller: poller.NewPoller(bc, db, types.HeaderSync),
Fetcher: fetcher.NewFetcher(bc),
Parser: parser.NewParser(con.Network),
HeaderRepository: repository.NewHeaderRepository(db),
Retriever: retriever.NewBlockRetriever(db),
Converter: &converter.Converter{},
Contracts: map[string]*contract.Contract{},
EventRepository: srep.NewEventRepository(db, types.HeaderSync),
Config: con,
}
}
// Init initialized the Transformer
// Use after creating and setting transformer
// Loops over all of the addr => filter sets
// Uses parser to pull event info from abi
// Use this info to generate event filters
func (tr *Transformer) Init() error {
// Initialize internally configured transformer settings
tr.contractAddresses = make([]string, 0) // Holds all contract addresses, for batch fetching of logs
tr.sortedEventIds = make(map[string][]string) // Map to sort event column ids by contract, for post fetch processing and persisting of logs
tr.sortedMethodIds = make(map[string][]string) // Map to sort method column ids by contract, for post fetch method polling
tr.eventIds = make([]string, 0) // Holds event column ids across all contract, for batch fetching of headers
tr.eventFilters = make([]common.Hash, 0) // Holds topic0 hashes across all contracts, for batch fetching of logs
tr.Start = math.MaxInt64
// Iterate through all internal contract addresses
for contractAddr := range tr.Config.Addresses {
// Configure Abi
if tr.Config.Abis[contractAddr] == "" {
// If no abi is given in the config, this method will try fetching from internal look-up table and etherscan
parseErr := tr.Parser.Parse(contractAddr)
if parseErr != nil {
return fmt.Errorf("error parsing contract by address: %s", parseErr.Error())
}
} else {
// If we have an abi from the config, load that into the parser
parseErr := tr.Parser.ParseAbiStr(tr.Config.Abis[contractAddr])
if parseErr != nil {
return fmt.Errorf("error parsing contract abi: %s", parseErr.Error())
}
}
// Get first block and most recent block number in the header repo
firstBlock, retrieveErr := tr.Retriever.RetrieveFirstBlock()
if retrieveErr != nil {
if retrieveErr == sql.ErrNoRows {
logrus.Error(fmt.Errorf("error retrieving first block: %s", retrieveErr.Error()))
firstBlock = 0
} else {
return fmt.Errorf("error retrieving first block: %s", retrieveErr.Error())
}
}
// Set to specified range if it falls within the bounds
if firstBlock < tr.Config.StartingBlocks[contractAddr] {
firstBlock = tr.Config.StartingBlocks[contractAddr]
}
// Get contract name if it has one
var name = new(string)
pollingErr := tr.Poller.FetchContractData(tr.Parser.Abi(), contractAddr, "name", nil, name, -1)
if pollingErr != nil {
// can't return this error because "name" might not exist on the contract
logrus.Warnf("error fetching contract data: %s", pollingErr.Error())
}
// Remove any potential accidental duplicate inputs
eventArgs := map[string]bool{}
for _, arg := range tr.Config.EventArgs[contractAddr] {
eventArgs[arg] = true
}
methodArgs := map[string]bool{}
for _, arg := range tr.Config.MethodArgs[contractAddr] {
methodArgs[arg] = true
}
// Aggregate info into contract object and store for execution
con := contract.Contract{
Name: *name,
Network: tr.Config.Network,
Address: contractAddr,
Abi: tr.Parser.Abi(),
ParsedAbi: tr.Parser.ParsedAbi(),
StartingBlock: firstBlock,
Events: tr.Parser.GetEvents(tr.Config.Events[contractAddr]),
Methods: tr.Parser.GetSelectMethods(tr.Config.Methods[contractAddr]),
FilterArgs: eventArgs,
MethodArgs: methodArgs,
Piping: tr.Config.Piping[contractAddr],
}.Init()
tr.Contracts[contractAddr] = con
tr.contractAddresses = append(tr.contractAddresses, con.Address)
// Create checked_headers columns for each event id and append to list of all event ids
tr.sortedEventIds[con.Address] = make([]string, 0, len(con.Events))
for _, event := range con.Events {
eventID := strings.ToLower(event.Name + "_" + con.Address)
addColumnErr := tr.HeaderRepository.AddCheckColumn(eventID)
if addColumnErr != nil {
return fmt.Errorf("error adding check column: %s", addColumnErr.Error())
}
// Keep track of this event id; sorted and unsorted
tr.sortedEventIds[con.Address] = append(tr.sortedEventIds[con.Address], eventID)
tr.eventIds = append(tr.eventIds, eventID)
// Append this event sig to the filters
tr.eventFilters = append(tr.eventFilters, event.Sig())
}
// Create checked_headers columns for each method id and append list of all method ids
tr.sortedMethodIds[con.Address] = make([]string, 0, len(con.Methods))
for _, m := range con.Methods {
methodID := strings.ToLower(m.Name + "_" + con.Address)
addColumnErr := tr.HeaderRepository.AddCheckColumn(methodID)
if addColumnErr != nil {
return fmt.Errorf("error adding check column: %s", addColumnErr.Error())
}
tr.sortedMethodIds[con.Address] = append(tr.sortedMethodIds[con.Address], methodID)
}
// Update start to the lowest block
if con.StartingBlock < tr.Start {
tr.Start = con.StartingBlock
}
}
return nil
}
// Execute runs the transformation processes
func (tr *Transformer) Execute() error {
if len(tr.Contracts) == 0 {
return errors.New("error: transformer has no initialized contracts")
}
// Find unchecked headers for all events across all contracts; these are returned in asc order
missingHeaders, missingHeadersErr := tr.HeaderRepository.MissingHeadersForAll(tr.Start, -1, tr.eventIds)
if missingHeadersErr != nil {
return fmt.Errorf("error getting missing headers: %s", missingHeadersErr.Error())
}
// Iterate over headers
for _, header := range missingHeaders {
// Set `start` to this header
// This way if we throw an error but don't bring the execution cycle down (how it is currently handled)
// we restart the cycle at this header
tr.Start = header.BlockNumber
// Map to sort batch fetched logs by which contract they belong to, for post fetch processing
sortedLogs := make(map[string][]gethTypes.Log)
// And fetch all event logs across contracts at this header
allLogs, fetchErr := tr.Fetcher.FetchLogs(tr.contractAddresses, tr.eventFilters, header)
if fetchErr != nil {
return fmt.Errorf("error fetching logs: %s", fetchErr.Error())
}
// If no logs are found mark the header checked for all of these eventIDs
// and continue to method polling and onto the next iteration
if len(allLogs) < 1 {
markCheckedErr := tr.HeaderRepository.MarkHeaderCheckedForAll(header.ID, tr.eventIds)
if markCheckedErr != nil {
return fmt.Errorf("error marking header checked: %s", markCheckedErr.Error())
}
pollingErr := tr.methodPolling(header, tr.sortedMethodIds)
if pollingErr != nil {
return fmt.Errorf("error polling methods: %s", pollingErr.Error())
}
tr.Start = header.BlockNumber + 1 // Empty header; setup to start at the next header
logrus.Tracef("no logs found for block %d, continuing", header.BlockNumber)
continue
}
for _, log := range allLogs {
addr := strings.ToLower(log.Address.Hex())
sortedLogs[addr] = append(sortedLogs[addr], log)
}
// Process logs for each contract
for conAddr, logs := range sortedLogs {
if logs == nil {
logrus.Tracef("no logs found for contract %s at block %d, continuing", conAddr, header.BlockNumber)
continue
}
// Configure converter with this contract
con := tr.Contracts[conAddr]
tr.Converter.Update(con)
// Convert logs into batches of log mappings (eventName => []types.Logs
convertedLogs, convertErr := tr.Converter.ConvertBatch(logs, con.Events, header.ID)
if convertErr != nil {
return fmt.Errorf("error converting logs: %s", convertErr.Error())
}
// Cycle through each type of event log and persist them
for eventName, logs := range convertedLogs {
// If logs for this event are empty, mark them checked at this header and continue
if len(logs) < 1 {
logrus.Tracef("no logs found for event %s on contract %s at block %d, continuing", eventName, conAddr, header.BlockNumber)
continue
}
// If logs aren't empty, persist them
persistErr := tr.EventRepository.PersistLogs(logs, con.Events[eventName], con.Address, con.Name)
if persistErr != nil {
return fmt.Errorf("error persisting logs: %s", persistErr.Error())
}
}
}
markCheckedErr := tr.HeaderRepository.MarkHeaderCheckedForAll(header.ID, tr.eventIds)
if markCheckedErr != nil {
return fmt.Errorf("error marking header checked: %s", markCheckedErr.Error())
}
// Poll contracts at this block height
pollingErr := tr.methodPolling(header, tr.sortedMethodIds)
if pollingErr != nil {
return fmt.Errorf("error polling methods: %s", pollingErr.Error())
}
// Success; setup to start at the next header
tr.Start = header.BlockNumber + 1
}
return nil
}
// Used to poll contract methods at a given header
func (tr *Transformer) methodPolling(header core.Header, sortedMethodIds map[string][]string) error {
for _, con := range tr.Contracts {
// Skip method polling processes if no methods are specified
// Also don't try to poll methods below this contract's specified starting block
if len(con.Methods) == 0 || header.BlockNumber < con.StartingBlock {
logrus.Tracef("not polling contract: %s", con.Address)
continue
}
// Poll all methods for this contract at this header
pollingErr := tr.Poller.PollContractAt(*con, header.BlockNumber)
if pollingErr != nil {
return fmt.Errorf("error polling contract %s: %s", con.Address, pollingErr.Error())
}
// Mark this header checked for the methods
markCheckedErr := tr.HeaderRepository.MarkHeaderCheckedForAll(header.ID, sortedMethodIds[con.Address])
if markCheckedErr != nil {
return fmt.Errorf("error marking header checked: %s", markCheckedErr.Error())
}
}
return nil
}
// GetConfig returns the transformers config; satisfies the transformer interface
func (tr *Transformer) GetConfig() config.ContractConfig {
return tr.Config
}
@@ -1,35 +0,0 @@
// 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 transformer_test
import (
"io/ioutil"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)
func TestTransformer(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Header Sync Transformer Suite Test")
}
var _ = BeforeSuite(func() {
logrus.SetOutput(ioutil.Discard)
})
@@ -1,138 +0,0 @@
// 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 transformer_test
import (
"database/sql"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/retriever"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/header/transformer"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers/mocks"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/parser"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/poller"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
)
var _ = Describe("Transformer", func() {
var fakeAddress = "0x1234567890abcdef"
Describe("Init", func() {
It("Initializes transformer's contract objects", func() {
blockRetriever := &fakes.MockHeaderSyncBlockRetriever{}
firstBlock := int64(1)
blockRetriever.FirstBlock = firstBlock
parsr := &fakes.MockParser{}
fakeAbi := "fake_abi"
parsr.AbiToReturn = fakeAbi
pollr := &fakes.MockPoller{}
fakeContractName := "fake_contract_name"
pollr.ContractName = fakeContractName
t := getFakeTransformer(blockRetriever, parsr, pollr)
err := t.Init()
Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[fakeAddress]
Expect(ok).To(Equal(true))
Expect(c.StartingBlock).To(Equal(firstBlock))
Expect(c.Abi).To(Equal(fakeAbi))
Expect(c.Name).To(Equal(fakeContractName))
Expect(c.Address).To(Equal(fakeAddress))
})
It("Fails to initialize if first block cannot be fetched from vDB headers table", func() {
blockRetriever := &fakes.MockHeaderSyncBlockRetriever{}
blockRetriever.FirstBlockErr = fakes.FakeError
t := getFakeTransformer(blockRetriever, &fakes.MockParser{}, &fakes.MockPoller{})
err := t.Init()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fakes.FakeError.Error()))
})
})
Describe("Execute", func() {
It("Executes contract transformations", func() {
blockRetriever := &fakes.MockHeaderSyncBlockRetriever{}
firstBlock := int64(1)
blockRetriever.FirstBlock = firstBlock
parsr := &fakes.MockParser{}
fakeAbi := "fake_abi"
parsr.AbiToReturn = fakeAbi
pollr := &fakes.MockPoller{}
fakeContractName := "fake_contract_name"
pollr.ContractName = fakeContractName
t := getFakeTransformer(blockRetriever, parsr, pollr)
err := t.Init()
Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[fakeAddress]
Expect(ok).To(Equal(true))
Expect(c.StartingBlock).To(Equal(firstBlock))
Expect(c.Abi).To(Equal(fakeAbi))
Expect(c.Name).To(Equal(fakeContractName))
Expect(c.Address).To(Equal(fakeAddress))
})
It("uses first block from config if vDB headers table has no rows", func() {
blockRetriever := &fakes.MockHeaderSyncBlockRetriever{}
blockRetriever.FirstBlockErr = sql.ErrNoRows
t := getFakeTransformer(blockRetriever, &fakes.MockParser{}, &fakes.MockPoller{})
err := t.Init()
Expect(err).ToNot(HaveOccurred())
})
It("returns error if fetching first block fails for other reason", func() {
blockRetriever := &fakes.MockHeaderSyncBlockRetriever{}
blockRetriever.FirstBlockErr = fakes.FakeError
t := getFakeTransformer(blockRetriever, &fakes.MockParser{}, &fakes.MockPoller{})
err := t.Init()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fakes.FakeError.Error()))
})
})
})
func getFakeTransformer(blockRetriever retriever.BlockRetriever, parsr parser.Parser, pollr poller.Poller) transformer.Transformer {
return transformer.Transformer{
Parser: parsr,
Retriever: blockRetriever,
Poller: pollr,
HeaderRepository: &fakes.MockHeaderSyncHeaderRepository{},
Contracts: map[string]*contract.Contract{},
Config: mocks.MockConfig,
}
}
File diff suppressed because one or more lines are too long
@@ -1,129 +0,0 @@
// 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 constants
import (
"github.com/ethereum/go-ethereum/common/hexutil"
)
// SupportsInterfaceABI is the basic abi needed to check which interfaces are adhered to
var SupportsInterfaceABI = `[{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]`
// Individual event interfaces for constructing ABI from
var AddrChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"a","type":"address"}],"name":"AddrChanged","type":"event"}`
var ContentChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes32"}],"name":"ContentChanged","type":"event"}`
var NameChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"name","type":"string"}],"name":"NameChanged","type":"event"}`
var AbiChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"}`
var PubkeyChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"x","type":"bytes32"},{"indexed":false,"name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"}`
var TextChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"indexedKey","type":"string"},{"indexed":false,"name":"key","type":"string"}],"name":"TextChanged","type":"event"}`
var MultihashChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes"}],"name":"MultihashChanged","type":"event"}`
var ContenthashChangeInterface = `{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"}`
// Resolver interface signatures
type Interface int
// Interface enums
const (
MetaSig Interface = iota
AddrChangeSig
ContentChangeSig
NameChangeSig
AbiChangeSig
PubkeyChangeSig
TextChangeSig
MultihashChangeSig
ContentHashChangeSig
)
// Hex returns the hex signature for an interface
func (e Interface) Hex() string {
strings := [...]string{
"0x01ffc9a7",
"0x3b3b57de",
"0xd8389dc5",
"0x691f3431",
"0x2203ab56",
"0xc8690233",
"0x59d1d43c",
"0xe89401a1",
"0xbc1c58d1",
}
if e < MetaSig || e > ContentHashChangeSig {
return "Unknown"
}
return strings[e]
}
// Bytes returns the bytes signature for an interface
func (e Interface) Bytes() [4]uint8 {
if e < MetaSig || e > ContentHashChangeSig {
return [4]byte{}
}
str := e.Hex()
by, _ := hexutil.Decode(str)
var byArray [4]uint8
for i := 0; i < 4; i++ {
byArray[i] = by[i]
}
return byArray
}
// EventSig returns the event signature for an interface
func (e Interface) EventSig() string {
strings := [...]string{
"",
"AddrChanged(bytes32,address)",
"ContentChanged(bytes32,bytes32)",
"NameChanged(bytes32,string)",
"ABIChanged(bytes32,uint256)",
"PubkeyChanged(bytes32,bytes32,bytes32)",
"TextChanged(bytes32,string,string)",
"MultihashChanged(bytes32,bytes)",
"ContenthashChanged(bytes32,bytes)",
}
if e < MetaSig || e > ContentHashChangeSig {
return "Unknown"
}
return strings[e]
}
// MethodSig returns the method signature for an interface
func (e Interface) MethodSig() string {
strings := [...]string{
"supportsInterface(bytes4)",
"addr(bytes32)",
"content(bytes32)",
"name(bytes32)",
"ABI(bytes32,uint256)",
"pubkey(bytes32)",
"text(bytes32,string)",
"multihash(bytes32)",
"setContenthash(bytes32,bytes)",
}
if e < MetaSig || e > ContentHashChangeSig {
return "Unknown"
}
return strings[e]
}
@@ -1,172 +0,0 @@
// 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 contract
import (
"errors"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/filters"
)
// Contract object to hold our contract data
type Contract struct {
Name string // Name of the contract
Address string // Address of the contract
Network string // Network on which the contract is deployed; default empty "" is Ethereum mainnet
StartingBlock int64 // Starting block of the contract
Abi string // Abi string
ParsedAbi abi.ABI // Parsed abi
Events map[string]types.Event // List of events to watch
Methods []types.Method // List of methods to poll
Filters map[string]filters.LogFilter // Map of event filters to their event names; used only for full sync watcher
FilterArgs map[string]bool // User-input list of values to filter event logs for
MethodArgs map[string]bool // User-input list of values to limit method polling to
EmittedAddrs map[interface{}]bool // List of all unique addresses collected from converted event logs
EmittedHashes map[interface{}]bool // List of all unique hashes collected from converted event logs
CreateAddrList bool // Whether or not to persist address list to postgres
CreateHashList bool // Whether or not to persist hash list to postgres
Piping bool // Whether or not to pipe method results forward as arguments to subsequent methods
}
// Init initializes a contract object
// If we will be calling methods that use addr, hash, or byte arrays
// as arguments then we initialize maps to hold these types of values
func (c Contract) Init() *Contract {
for _, method := range c.Methods {
for _, arg := range method.Args {
switch arg.Type.T {
case abi.AddressTy:
c.EmittedAddrs = map[interface{}]bool{}
case abi.HashTy, abi.BytesTy, abi.FixedBytesTy:
c.EmittedHashes = map[interface{}]bool{}
default:
}
}
}
return &c
}
// GenerateFilters uses contract info to generate event filters - full sync contract watcher only
func (c *Contract) GenerateFilters() error {
c.Filters = map[string]filters.LogFilter{}
for name, event := range c.Events {
c.Filters[name] = filters.LogFilter{
Name: c.Address + "_" + event.Name,
FromBlock: c.StartingBlock,
ToBlock: -1,
Address: common.HexToAddress(c.Address).Hex(),
Topics: core.Topics{event.Sig().Hex()},
}
}
// If no filters were generated, throw an error (no point in continuing with this contract)
if len(c.Filters) == 0 {
return errors.New("error: no filters created")
}
return nil
}
// WantedEventArg returns true if address is in list of arguments to
// filter events for or if no filtering is specified
func (c *Contract) WantedEventArg(arg string) bool {
if c.FilterArgs == nil {
return false
} else if len(c.FilterArgs) == 0 {
return true
} else if a, ok := c.FilterArgs[arg]; ok {
return a
}
return false
}
// WantedMethodArg returns true if address is in list of arguments to
// poll methods with or if no filtering is specified
func (c *Contract) WantedMethodArg(arg interface{}) bool {
if c.MethodArgs == nil {
return false
} else if len(c.MethodArgs) == 0 {
return true
}
// resolve interface to one of the three types we handle as arguments
str := StringifyArg(arg)
// See if it's hex string has been filtered for
if a, ok := c.MethodArgs[str]; ok {
return a
}
return false
}
// PassesEventFilter returns true if any mapping value matches filtered for address or if no filter exists
// Used to check if an event log name-value mapping should be filtered or not
func (c *Contract) PassesEventFilter(args map[string]string) bool {
for _, arg := range args {
if c.WantedEventArg(arg) {
return true
}
}
return false
}
// AddEmittedAddr adds event emitted addresses to our list if it passes filter and method polling is on
func (c *Contract) AddEmittedAddr(addresses ...interface{}) {
for _, addr := range addresses {
if c.WantedMethodArg(addr) && c.Methods != nil {
c.EmittedAddrs[addr] = true
}
}
}
// AddEmittedHash adds event emitted hashes to our list if it passes filter and method polling is on
func (c *Contract) AddEmittedHash(hashes ...interface{}) {
for _, hash := range hashes {
if c.WantedMethodArg(hash) && c.Methods != nil {
c.EmittedHashes[hash] = true
}
}
}
// StringifyArg resolves a method argument type to string type
func StringifyArg(arg interface{}) (str string) {
switch arg.(type) {
case string:
str = arg.(string)
case common.Address:
a := arg.(common.Address)
str = a.String()
case common.Hash:
a := arg.(common.Hash)
str = a.String()
case []byte:
a := arg.([]byte)
str = hexutil.Encode(a)
}
return
}
@@ -1,35 +0,0 @@
// 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 contract_test
import (
"io/ioutil"
"log"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestContract(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Contract Suite Test")
}
var _ = BeforeSuite(func() {
log.SetOutput(ioutil.Discard)
})
@@ -1,236 +0,0 @@
// 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 contract_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers/mocks"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
)
var _ = Describe("Contract", func() {
var err error
var info *contract.Contract
var wantedEvents = []string{"Transfer", "Approval"}
Describe("GenerateFilters", func() {
It("Generates filters from contract data", func() {
info = test_helpers.SetupTusdContract(wantedEvents, nil)
err = info.GenerateFilters()
Expect(err).ToNot(HaveOccurred())
val, ok := info.Filters["Transfer"]
Expect(ok).To(Equal(true))
Expect(val).To(Equal(mocks.ExpectedTransferFilter))
val, ok = info.Filters["Approval"]
Expect(ok).To(Equal(true))
Expect(val).To(Equal(mocks.ExpectedApprovalFilter))
val, ok = info.Filters["Mint"]
Expect(ok).To(Equal(false))
})
It("Fails with an empty contract", func() {
info = &contract.Contract{}
err = info.GenerateFilters()
Expect(err).To(HaveOccurred())
})
})
Describe("IsEventAddr", func() {
BeforeEach(func() {
info = &contract.Contract{}
info.MethodArgs = map[string]bool{}
info.FilterArgs = map[string]bool{}
})
It("Returns true if address is in event address filter list", func() {
info.FilterArgs["testAddress1"] = true
info.FilterArgs["testAddress2"] = true
is := info.WantedEventArg("testAddress1")
Expect(is).To(Equal(true))
is = info.WantedEventArg("testAddress2")
Expect(is).To(Equal(true))
info.MethodArgs["testAddress3"] = true
is = info.WantedEventArg("testAddress3")
Expect(is).To(Equal(false))
})
It("Returns true if event address filter is empty (no filter)", func() {
is := info.WantedEventArg("testAddress1")
Expect(is).To(Equal(true))
is = info.WantedEventArg("testAddress2")
Expect(is).To(Equal(true))
})
It("Returns false if address is not in event address filter list", func() {
info.FilterArgs["testAddress1"] = true
info.FilterArgs["testAddress2"] = true
is := info.WantedEventArg("testAddress3")
Expect(is).To(Equal(false))
})
It("Returns false if event address filter is nil (block all)", func() {
info.FilterArgs = nil
is := info.WantedEventArg("testAddress1")
Expect(is).To(Equal(false))
is = info.WantedEventArg("testAddress2")
Expect(is).To(Equal(false))
})
})
Describe("IsMethodAddr", func() {
BeforeEach(func() {
info = &contract.Contract{}
info.MethodArgs = map[string]bool{}
info.FilterArgs = map[string]bool{}
})
It("Returns true if address is in method address filter list", func() {
info.MethodArgs["testAddress1"] = true
info.MethodArgs["testAddress2"] = true
is := info.WantedMethodArg("testAddress1")
Expect(is).To(Equal(true))
is = info.WantedMethodArg("testAddress2")
Expect(is).To(Equal(true))
info.FilterArgs["testAddress3"] = true
is = info.WantedMethodArg("testAddress3")
Expect(is).To(Equal(false))
})
It("Returns true if method address filter list is empty (no filter)", func() {
is := info.WantedMethodArg("testAddress1")
Expect(is).To(Equal(true))
is = info.WantedMethodArg("testAddress2")
Expect(is).To(Equal(true))
})
It("Returns false if address is not in method address filter list", func() {
info.MethodArgs["testAddress1"] = true
info.MethodArgs["testAddress2"] = true
is := info.WantedMethodArg("testAddress3")
Expect(is).To(Equal(false))
})
It("Returns false if method address filter list is nil (block all)", func() {
info.MethodArgs = nil
is := info.WantedMethodArg("testAddress1")
Expect(is).To(Equal(false))
is = info.WantedMethodArg("testAddress2")
Expect(is).To(Equal(false))
})
})
Describe("PassesEventFilter", func() {
var mapping map[string]string
BeforeEach(func() {
info = &contract.Contract{}
info.FilterArgs = map[string]bool{}
mapping = map[string]string{}
})
It("Return true if event log name-value mapping has filtered for address as a value", func() {
info.FilterArgs["testAddress1"] = true
info.FilterArgs["testAddress2"] = true
mapping["testInputName1"] = "testAddress1"
mapping["testInputName2"] = "testAddress2"
mapping["testInputName3"] = "testAddress3"
pass := info.PassesEventFilter(mapping)
Expect(pass).To(Equal(true))
})
It("Return true if event address filter list is empty (no filter)", func() {
mapping["testInputName1"] = "testAddress1"
mapping["testInputName2"] = "testAddress2"
mapping["testInputName3"] = "testAddress3"
pass := info.PassesEventFilter(mapping)
Expect(pass).To(Equal(true))
})
It("Return false if event log name-value mapping does not have filtered for address as a value", func() {
info.FilterArgs["testAddress1"] = true
info.FilterArgs["testAddress2"] = true
mapping["testInputName3"] = "testAddress3"
pass := info.PassesEventFilter(mapping)
Expect(pass).To(Equal(false))
})
It("Return false if event address filter list is nil (block all)", func() {
info.FilterArgs = nil
mapping["testInputName1"] = "testAddress1"
mapping["testInputName2"] = "testAddress2"
mapping["testInputName3"] = "testAddress3"
pass := info.PassesEventFilter(mapping)
Expect(pass).To(Equal(false))
})
})
Describe("AddEmittedAddr", func() {
BeforeEach(func() {
info = &contract.Contract{}
info.FilterArgs = map[string]bool{}
info.MethodArgs = map[string]bool{}
info.Methods = []types.Method{}
info.EmittedAddrs = map[interface{}]bool{}
})
It("Adds address to list if it is on the method filter address list", func() {
info.MethodArgs["testAddress2"] = true
info.AddEmittedAddr("testAddress2")
b := info.EmittedAddrs["testAddress2"]
Expect(b).To(Equal(true))
})
It("Adds address to list if method filter is empty", func() {
info.AddEmittedAddr("testAddress2")
b := info.EmittedAddrs["testAddress2"]
Expect(b).To(Equal(true))
})
It("Does not add address to list if both filters are closed (nil)", func() {
info.FilterArgs = nil // close both
info.MethodArgs = nil
info.AddEmittedAddr("testAddress1")
b := info.EmittedAddrs["testAddress1"]
Expect(b).To(Equal(false))
})
})
})
@@ -1,124 +0,0 @@
// 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 fetcher
import (
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
// Fetcher serves as the lower level data fetcher that calls the underlying
// blockchain's FetchConctractData method for a given return type
// FetcherInterface is the interface definition for a fetcher
type FetcherInterface interface {
FetchBigInt(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (big.Int, error)
FetchBool(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (bool, error)
FetchAddress(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Address, error)
FetchString(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (string, error)
FetchHash(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Hash, error)
}
// Used to create a new Fetcher error for a given error and fetch method
func newFetcherError(err error, fetchMethod string) *fetcherError {
e := fetcherError{err.Error(), fetchMethod}
log.Println(e.Error())
return &e
}
// Fetcher struct
type Fetcher struct {
BlockChain core.BlockChain // Underlying Blockchain
}
// Fetcher error
type fetcherError struct {
err string
fetchMethod string
}
// Error method
func (fe *fetcherError) Error() string {
return fmt.Sprintf("Error fetching %s: %s", fe.fetchMethod, fe.err)
}
// Generic Fetcher methods used by Getters to call contract methods
// FetchBigInt is the method used to fetch big.Int value from contract
func (f Fetcher) FetchBigInt(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (big.Int, error) {
var result = new(big.Int)
err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber)
if err != nil {
return *result, newFetcherError(err, method)
}
return *result, nil
}
// FetchBool is the method used to fetch bool value from contract
func (f Fetcher) FetchBool(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (bool, error) {
var result = new(bool)
err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber)
if err != nil {
return *result, newFetcherError(err, method)
}
return *result, nil
}
// FetchAddress is the method used to fetch address value from contract
func (f Fetcher) FetchAddress(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Address, error) {
var result = new(common.Address)
err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber)
if err != nil {
return *result, newFetcherError(err, method)
}
return *result, nil
}
// FetchString is the method used to fetch string value from contract
func (f Fetcher) FetchString(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (string, error) {
var result = new(string)
err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber)
if err != nil {
return *result, newFetcherError(err, method)
}
return *result, nil
}
// FetchHash is the method used to fetch hash value from contract
func (f Fetcher) FetchHash(method, contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (common.Hash, error) {
var result = new(common.Hash)
err := f.BlockChain.FetchContractData(contractAbi, contractAddress, method, methodArgs, &result, blockNumber)
if err != nil {
return *result, newFetcherError(err, method)
}
return *result, nil
}
@@ -1,113 +0,0 @@
// 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 getter
import (
"fmt"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/fetcher"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
// InterfaceGetter is used to derive the interface of a contract
type InterfaceGetter interface {
GetABI(resolverAddr string, blockNumber int64) (string, error)
GetBlockChain() core.BlockChain
}
type interfaceGetter struct {
fetcher.Fetcher
}
// NewInterfaceGetter returns a new InterfaceGetter
func NewInterfaceGetter(blockChain core.BlockChain) InterfaceGetter {
return &interfaceGetter{
Fetcher: fetcher.Fetcher{
BlockChain: blockChain,
},
}
}
// GetABI is used to construct a custom ABI based on the results from calling supportsInterface
func (g *interfaceGetter) GetABI(resolverAddr string, blockNumber int64) (string, error) {
a := constants.SupportsInterfaceABI
args := make([]interface{}, 1)
args[0] = constants.MetaSig.Bytes()
supports, err := g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err != nil {
return "", fmt.Errorf("call to getSupportsInterface failed: %v", err)
}
if !supports {
return "", fmt.Errorf("contract does not support interface")
}
abiStr := `[`
args[0] = constants.AddrChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.AddrChangeInterface + ","
}
args[0] = constants.NameChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.NameChangeInterface + ","
}
args[0] = constants.ContentChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.ContentChangeInterface + ","
}
args[0] = constants.AbiChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.AbiChangeInterface + ","
}
args[0] = constants.PubkeyChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.PubkeyChangeInterface + ","
}
args[0] = constants.ContentHashChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.ContenthashChangeInterface + ","
}
args[0] = constants.MultihashChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.MultihashChangeInterface + ","
}
args[0] = constants.TextChangeSig.Bytes()
supports, err = g.getSupportsInterface(a, resolverAddr, blockNumber, args)
if err == nil && supports {
abiStr += constants.TextChangeInterface + ","
}
abiStr = abiStr[:len(abiStr)-1] + `]`
return abiStr, nil
}
// Use this method to check whether or not a contract supports a given method/event interface
func (g *interfaceGetter) getSupportsInterface(contractAbi, contractAddress string, blockNumber int64, methodArgs []interface{}) (bool, error) {
return g.Fetcher.FetchBool("supportsInterface", contractAbi, contractAddress, blockNumber, methodArgs)
}
// GetBlockChain is a method to retrieve the Getter's blockchain
func (g *interfaceGetter) GetBlockChain() core.BlockChain {
return g.Fetcher.BlockChain
}
@@ -1,72 +0,0 @@
// 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 helpers
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
// ConvertToLog converts a watched event to a log
func ConvertToLog(watchedEvent core.WatchedEvent) types.Log {
allTopics := []string{watchedEvent.Topic0, watchedEvent.Topic1, watchedEvent.Topic2, watchedEvent.Topic3}
var nonNilTopics []string
for _, topic := range allTopics {
if topic != "" {
nonNilTopics = append(nonNilTopics, topic)
}
}
return types.Log{
Address: common.HexToAddress(watchedEvent.Address),
Topics: createTopics(nonNilTopics...),
Data: hexutil.MustDecode(watchedEvent.Data),
BlockNumber: uint64(watchedEvent.BlockNumber),
TxHash: common.HexToHash(watchedEvent.TxHash),
TxIndex: 0,
BlockHash: common.HexToHash("0x0"),
Index: uint(watchedEvent.Index),
Removed: false,
}
}
func createTopics(topics ...string) []common.Hash {
var topicsArray []common.Hash
for _, topic := range topics {
topicsArray = append(topicsArray, common.HexToHash(topic))
}
return topicsArray
}
// BigFromString creates a big.Int from a string
func BigFromString(n string) *big.Int {
b := new(big.Int)
b.SetString(n, 10)
return b
}
// GenerateSignature returns the keccak256 hash hex of a string
func GenerateSignature(s string) string {
eventSignature := []byte(s)
hash := crypto.Keccak256Hash(eventSignature)
return hash.Hex()
}
@@ -1,243 +0,0 @@
// 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 test_helpers
import (
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/config"
"github.com/vulcanize/vulcanizedb/pkg/eth"
"github.com/vulcanize/vulcanizedb/pkg/eth/client"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers/mocks"
rpc2 "github.com/vulcanize/vulcanizedb/pkg/eth/converters/rpc"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/node"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
"github.com/vulcanize/vulcanizedb/test_config"
)
type TransferLog struct {
ID int64 `db:"id"`
VulcanizeLogID int64 `db:"vulcanize_log_id"`
TokenName string `db:"token_name"`
Block int64 `db:"block"`
Tx string `db:"tx"`
From string `db:"from_"`
To string `db:"to_"`
Value string `db:"value_"`
}
type NewOwnerLog struct {
ID int64 `db:"id"`
VulcanizeLogID int64 `db:"vulcanize_log_id"`
TokenName string `db:"token_name"`
Block int64 `db:"block"`
Tx string `db:"tx"`
Node string `db:"node_"`
Label string `db:"label_"`
Owner string `db:"owner_"`
}
type HeaderSyncTransferLog struct {
ID int64 `db:"id"`
HeaderID int64 `db:"header_id"`
TokenName string `db:"token_name"`
LogIndex int64 `db:"log_idx"`
TxIndex int64 `db:"tx_idx"`
From string `db:"from_"`
To string `db:"to_"`
Value string `db:"value_"`
RawLog []byte `db:"raw_log"`
}
type HeaderSyncNewOwnerLog struct {
ID int64 `db:"id"`
HeaderID int64 `db:"header_id"`
TokenName string `db:"token_name"`
LogIndex int64 `db:"log_idx"`
TxIndex int64 `db:"tx_idx"`
Node string `db:"node_"`
Label string `db:"label_"`
Owner string `db:"owner_"`
RawLog []byte `db:"raw_log"`
}
type BalanceOf struct {
ID int64 `db:"id"`
TokenName string `db:"token_name"`
Block int64 `db:"block"`
Address string `db:"who_"`
Balance string `db:"returned"`
}
type Resolver struct {
ID int64 `db:"id"`
TokenName string `db:"token_name"`
Block int64 `db:"block"`
Node string `db:"node_"`
Address string `db:"returned"`
}
type Owner struct {
ID int64 `db:"id"`
TokenName string `db:"token_name"`
Block int64 `db:"block"`
Node string `db:"node_"`
Address string `db:"returned"`
}
func SetupDBandBC() (*postgres.DB, core.BlockChain) {
con := test_config.TestClient
testIPC := con.IPCPath
rawRPCClient, err := rpc.Dial(testIPC)
Expect(err).NotTo(HaveOccurred())
rpcClient := client.NewRPCClient(rawRPCClient, testIPC)
ethClient := ethclient.NewClient(rawRPCClient)
blockChainClient := client.NewEthClient(ethClient)
madeNode := node.MakeNode(rpcClient)
transactionConverter := rpc2.NewRPCTransactionConverter(ethClient)
blockChain := eth.NewBlockChain(blockChainClient, rpcClient, madeNode, transactionConverter)
db, err := postgres.NewDB(config.Database{
Hostname: "localhost",
Name: "vulcanize_testing",
Port: 5432,
}, blockChain.Node())
Expect(err).NotTo(HaveOccurred())
return db, blockChain
}
func SetupTusdContract(wantedEvents, wantedMethods []string) *contract.Contract {
p := mocks.NewParser(constants.TusdAbiString)
err := p.Parse(constants.TusdContractAddress)
Expect(err).ToNot(HaveOccurred())
return contract.Contract{
Name: "TrueUSD",
Address: constants.TusdContractAddress,
Abi: p.Abi(),
ParsedAbi: p.ParsedAbi(),
StartingBlock: 6194634,
Events: p.GetEvents(wantedEvents),
Methods: p.GetSelectMethods(wantedMethods),
MethodArgs: map[string]bool{},
FilterArgs: map[string]bool{},
}.Init()
}
func SetupENSContract(wantedEvents, wantedMethods []string) *contract.Contract {
p := mocks.NewParser(constants.ENSAbiString)
err := p.Parse(constants.EnsContractAddress)
Expect(err).ToNot(HaveOccurred())
return contract.Contract{
Name: "ENS-Registry",
Address: constants.EnsContractAddress,
Abi: p.Abi(),
ParsedAbi: p.ParsedAbi(),
StartingBlock: 6194634,
Events: p.GetEvents(wantedEvents),
Methods: p.GetSelectMethods(wantedMethods),
MethodArgs: map[string]bool{},
FilterArgs: map[string]bool{},
}.Init()
}
func SetupMarketPlaceContract(wantedEvents, wantedMethods []string) *contract.Contract {
p := mocks.NewParser(constants.MarketPlaceAbiString)
err := p.Parse(constants.MarketPlaceContractAddress)
Expect(err).NotTo(HaveOccurred())
return contract.Contract{
Name: "Marketplace",
Address: constants.MarketPlaceContractAddress,
StartingBlock: 6496012,
Abi: p.Abi(),
ParsedAbi: p.ParsedAbi(),
Events: p.GetEvents(wantedEvents),
Methods: p.GetSelectMethods(wantedMethods),
FilterArgs: map[string]bool{},
MethodArgs: map[string]bool{},
}.Init()
}
func SetupMolochContract(wantedEvents, wantedMethods []string) *contract.Contract {
p := mocks.NewParser(constants.MolochAbiString)
err := p.Parse(constants.MolochContractAddress)
Expect(err).NotTo(HaveOccurred())
return contract.Contract{
Name: "Moloch",
Address: constants.MolochContractAddress,
StartingBlock: 7218566,
Abi: p.Abi(),
ParsedAbi: p.ParsedAbi(),
Events: p.GetEvents(wantedEvents),
Methods: p.GetSelectMethods(wantedMethods),
FilterArgs: map[string]bool{},
MethodArgs: map[string]bool{},
}.Init()
}
// TODO: tear down/setup DB from migrations so this doesn't alter the schema between tests
func TearDown(db *postgres.DB) {
tx, err := db.Beginx()
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM addresses`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM headers`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec("DELETE FROM header_sync_transactions")
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM header_sync_receipts`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DROP TABLE checked_headers`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`CREATE TABLE checked_headers (
id SERIAL PRIMARY KEY,
header_id INTEGER UNIQUE NOT NULL REFERENCES headers (id) ON DELETE CASCADE);`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DROP SCHEMA IF EXISTS full_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e CASCADE`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DROP SCHEMA IF EXISTS header_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e CASCADE`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DROP SCHEMA IF EXISTS full_0x314159265dd8dbb310642f98f50c066173c1259b CASCADE`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DROP SCHEMA IF EXISTS header_0x314159265dd8dbb310642f98f50c066173c1259b CASCADE`)
Expect(err).NotTo(HaveOccurred())
err = tx.Commit()
Expect(err).NotTo(HaveOccurred())
_, err = db.Exec(`VACUUM checked_headers`)
Expect(err).NotTo(HaveOccurred())
}
@@ -1,360 +0,0 @@
// 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 mocks
import (
"encoding/json"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/config"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/eth/filters"
)
var TransferBlock1 = core.Block{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert",
Number: 6194633,
Transactions: []core.TransactionModel{{
GasLimit: 0,
GasPrice: 0,
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654aaa",
Nonce: 0,
Receipt: core.Receipt{
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654aaa",
ContractAddress: "",
Logs: []core.FullSyncLog{{
BlockNumber: 6194633,
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654aaa",
Address: constants.TusdContractAddress,
Topics: core.Topics{
constants.TransferEvent.Signature(),
"0x000000000000000000000000000000000000000000000000000000000000af21",
"0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391",
"",
},
Index: 1,
Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe",
}},
},
TxIndex: 0,
Value: "0",
}},
}
var TransferBlock2 = core.Block{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ooo",
Number: 6194634,
Transactions: []core.TransactionModel{{
GasLimit: 0,
GasPrice: 0,
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee",
Nonce: 0,
Receipt: core.Receipt{
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee",
ContractAddress: "",
Logs: []core.FullSyncLog{{
BlockNumber: 6194634,
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee",
Address: constants.TusdContractAddress,
Topics: core.Topics{
constants.TransferEvent.Signature(),
"0x000000000000000000000000000000000000000000000000000000000000af21",
"0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391",
"",
},
Index: 1,
Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe",
}},
},
TxIndex: 0,
Value: "0",
}},
}
var NewOwnerBlock1 = core.Block{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ppp",
Number: 6194635,
Transactions: []core.TransactionModel{{
GasLimit: 0,
GasPrice: 0,
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb",
Nonce: 0,
Receipt: core.Receipt{
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb",
ContractAddress: "",
Logs: []core.FullSyncLog{{
BlockNumber: 6194635,
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb",
Address: constants.EnsContractAddress,
Topics: core.Topics{
constants.NewOwnerEvent.Signature(),
"0x0000000000000000000000000000000000000000000000000000c02aaa39b223",
"0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391",
"",
},
Index: 1,
Data: "0x000000000000000000000000000000000000000000000000000000000000af21",
}},
},
TxIndex: 0,
Value: "0",
}},
}
var NewOwnerBlock2 = core.Block{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ggg",
Number: 6194636,
Transactions: []core.TransactionModel{{
GasLimit: 0,
GasPrice: 0,
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654lll",
Nonce: 0,
Receipt: core.Receipt{
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654lll",
ContractAddress: "",
Logs: []core.FullSyncLog{{
BlockNumber: 6194636,
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654lll",
Address: constants.EnsContractAddress,
Topics: core.Topics{
constants.NewOwnerEvent.Signature(),
"0x0000000000000000000000000000000000000000000000000000c02aaa39b223",
"0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba400",
"",
},
Index: 1,
Data: "0x000000000000000000000000000000000000000000000000000000000000af21",
}},
},
TxIndex: 0,
Value: "0",
}},
}
var ExpectedTransferFilter = filters.LogFilter{
Name: constants.TusdContractAddress + "_" + "Transfer",
Address: constants.TusdContractAddress,
ToBlock: -1,
FromBlock: 6194634,
Topics: core.Topics{constants.TransferEvent.Signature()},
}
var ExpectedApprovalFilter = filters.LogFilter{
Name: constants.TusdContractAddress + "_" + "Approval",
Address: constants.TusdContractAddress,
ToBlock: -1,
FromBlock: 6194634,
Topics: core.Topics{constants.ApprovalEvent.Signature()},
}
var MockTranferEvent = core.WatchedEvent{
LogID: 1,
Name: constants.TransferEvent.String(),
BlockNumber: 5488076,
Address: constants.TusdContractAddress,
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae",
Index: 110,
Topic0: constants.TransferEvent.Signature(),
Topic1: "0x000000000000000000000000000000000000000000000000000000000000af21",
Topic2: "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391",
Topic3: "",
Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe",
}
var rawFakeHeader, _ = json.Marshal(core.Header{})
var MockHeader1 = core.Header{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad123ert",
BlockNumber: 6194632,
Raw: rawFakeHeader,
Timestamp: "50000000",
}
var MockHeader2 = core.Header{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad456yui",
BlockNumber: 6194633,
Raw: rawFakeHeader,
Timestamp: "50000015",
}
var MockHeader3 = core.Header{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs",
BlockNumber: 6194634,
Raw: rawFakeHeader,
Timestamp: "50000030",
}
var MockHeader4 = core.Header{
Hash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad234hfs",
BlockNumber: 6194635,
Raw: rawFakeHeader,
Timestamp: "50000030",
}
var MockTransferLog1 = types.Log{
Index: 1,
Address: common.HexToAddress(constants.TusdContractAddress),
BlockNumber: 5488076,
TxIndex: 110,
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae"),
Topics: []common.Hash{
common.HexToHash(constants.TransferEvent.Signature()),
common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"),
common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"),
},
Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"),
}
var MockTransferLog2 = types.Log{
Index: 3,
Address: common.HexToAddress(constants.TusdContractAddress),
BlockNumber: 5488077,
TxIndex: 2,
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546df"),
Topics: []common.Hash{
common.HexToHash(constants.TransferEvent.Signature()),
common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"),
common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"),
},
Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"),
}
var MockMintLog = types.Log{
Index: 10,
Address: common.HexToAddress(constants.TusdContractAddress),
BlockNumber: 5488080,
TxIndex: 50,
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6minty"),
Topics: []common.Hash{
common.HexToHash(constants.MintEvent.Signature()),
common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"),
},
Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"),
}
var MockNewOwnerLog1 = types.Log{
Index: 1,
Address: common.HexToAddress(constants.EnsContractAddress),
BlockNumber: 5488076,
TxIndex: 110,
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae"),
Topics: []common.Hash{
common.HexToHash(constants.NewOwnerEvent.Signature()),
common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553"),
common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"),
},
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000af21"),
}
var MockNewOwnerLog2 = types.Log{
Index: 3,
Address: common.HexToAddress(constants.EnsContractAddress),
BlockNumber: 5488077,
TxIndex: 2,
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546df"),
Topics: []common.Hash{
common.HexToHash(constants.NewOwnerEvent.Signature()),
common.HexToHash("0x000000000000000000000000c02aaa39b223helloa0e5c4f27ead9083c752553"),
common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba400"),
},
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000af21"),
}
var MockOrderCreatedLog = types.Log{
Address: common.HexToAddress(constants.MarketPlaceContractAddress),
Topics: []common.Hash{
common.HexToHash("0x84c66c3f7ba4b390e20e8e8233e2a516f3ce34a72749e4f12bd010dfba238039"),
common.HexToHash("0xffffffffffffffffffffffffffffff72ffffffffffffffffffffffffffffffd0"),
common.HexToHash("0x00000000000000000000000083b7b6f360a9895d163ea797d9b939b9173b292a"),
},
Data: hexutil.MustDecode("0x633f94affdcabe07c000231f85c752c97b9cc43966b432ec4d18641e6d178233000000000000000000000000f87e31492faf9a91b02ee0deaad50d51d56d5d4d0000000000000000000000000000000000000000000003da9fbcf4446d6000000000000000000000000000000000000000000000000000000000016db2524880"),
BlockNumber: 8587618,
TxHash: common.HexToHash("0x7ad9e2f88416738f3c7ad0a6d260f71794532206a0e838299f5014b4fe81e66e"),
TxIndex: 93,
BlockHash: common.HexToHash("0x06a1762b7f2e070793fc24cd785de0fa485e728832c4f3469790153ae51a56a2"),
Index: 59,
Removed: false,
}
var MockSubmitVoteLog = types.Log{
Address: common.HexToAddress(constants.MolochContractAddress),
Topics: []common.Hash{
common.HexToHash("0x29bf0061f2faa9daa482f061b116195432d435536d8af4ae6b3c5dd78223679b"),
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000061"),
common.HexToHash("0x0000000000000000000000006ddf1b8e6d71b5b33912607098be123ffe62ae53"),
common.HexToHash("0x00000000000000000000000037385081870ef47e055410fefd582e2a95d2960b"),
},
Data: hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000001"),
BlockNumber: 8517621,
TxHash: common.HexToHash("0xcc7390a2099812d0dfc9baef201afbc7a44bfae145050c9dc700b77cbd3cd752"),
TxIndex: 103,
BlockHash: common.HexToHash("0x3e82681d8036b1225fcaa8bcd4cdbe757b39f13468286b303cde22146385525e"),
Index: 132,
Removed: false,
}
var ens = strings.ToLower(constants.EnsContractAddress)
var tusd = strings.ToLower(constants.TusdContractAddress)
var MockConfig = config.ContractConfig{
Network: "",
Addresses: map[string]bool{
"0x1234567890abcdef": true,
},
Abis: map[string]string{
"0x1234567890abcdef": "fake_abi",
},
Events: map[string][]string{
"0x1234567890abcdef": {"Transfer"},
},
Methods: map[string][]string{
"0x1234567890abcdef": nil,
},
MethodArgs: map[string][]string{
"0x1234567890abcdef": nil,
},
EventArgs: map[string][]string{
"0x1234567890abcdef": nil,
},
}
var MockEmptyConfig = config.ContractConfig{
Network: "",
Addresses: map[string]bool{
"0x1234567890abcdef": true,
},
Abis: map[string]string{
"0x1234567890abcdef": "fake_abi",
},
Events: map[string][]string{
"0x1234567890abcdef": nil,
},
Methods: map[string][]string{
"0x1234567890abcdef": nil,
},
MethodArgs: map[string][]string{
"0x1234567890abcdef": nil,
},
EventArgs: map[string][]string{
"0x1234567890abcdef": nil,
},
}
@@ -1,165 +0,0 @@
// 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 mocks
import (
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/vulcanize/vulcanizedb/pkg/eth"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/parser"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
)
// Mock parser
// Is given ABI string instead of address
// Performs all other functions of the real parser
type mockParser struct {
abi string
parsedAbi abi.ABI
}
func NewParser(abi string) parser.Parser {
return &mockParser{
abi: abi,
}
}
func (p *mockParser) Abi() string {
return p.abi
}
func (p *mockParser) ParsedAbi() abi.ABI {
return p.parsedAbi
}
func (p *mockParser) ParseAbiStr(abiStr string) error {
panic("implement me")
}
// Retrieves and parses the abi string
// for the given contract address
func (p *mockParser) Parse(contractAddr string) error {
var err error
p.parsedAbi, err = eth.ParseAbi(p.abi)
return err
}
// Returns only specified methods, if they meet the criteria
// Returns as array with methods in same order they were specified
// Nil wanted array => no events are returned
func (p *mockParser) GetSelectMethods(wanted []string) []types.Method {
wLen := len(wanted)
if wLen == 0 {
return nil
}
methods := make([]types.Method, wLen)
for _, m := range p.parsedAbi.Methods {
for i, name := range wanted {
if name == m.Name && okTypes(m, wanted) {
methods[i] = types.NewMethod(m)
}
}
}
return methods
}
// Returns wanted methods
// Empty wanted array => all methods are returned
// Nil wanted array => no methods are returned
func (p *mockParser) GetMethods(wanted []string) []types.Method {
if wanted == nil {
return nil
}
methods := make([]types.Method, 0)
length := len(wanted)
for _, m := range p.parsedAbi.Methods {
if length == 0 || stringInSlice(wanted, m.Name) {
methods = append(methods, types.NewMethod(m))
}
}
return methods
}
// Returns wanted events as map of types.Events
// If no events are specified, all events are returned
func (p *mockParser) GetEvents(wanted []string) map[string]types.Event {
events := map[string]types.Event{}
for _, e := range p.parsedAbi.Events {
if len(wanted) == 0 || stringInSlice(wanted, e.Name) {
event := types.NewEvent(e)
events[e.Name] = event
}
}
return events
}
func stringInSlice(list []string, s string) bool {
for _, b := range list {
if b == s {
return true
}
}
return false
}
func okTypes(m abi.Method, wanted []string) bool {
// Only return method if it has less than 3 arguments, a single output value, and it is a method we want or we want all methods (empty 'wanted' slice)
if len(m.Inputs) < 3 && len(m.Outputs) == 1 && (len(wanted) == 0 || stringInSlice(wanted, m.Name)) {
// Only return methods if inputs are all of accepted types and output is of the accepted types
if !okReturnType(m.Outputs[0]) {
return false
}
for _, input := range m.Inputs {
switch input.Type.T {
case abi.AddressTy, abi.HashTy, abi.BytesTy, abi.FixedBytesTy:
default:
return false
}
}
return true
}
return false
}
func okReturnType(arg abi.Argument) bool {
wantedTypes := []byte{
abi.UintTy,
abi.IntTy,
abi.BoolTy,
abi.StringTy,
abi.AddressTy,
abi.HashTy,
abi.BytesTy,
abi.FixedBytesTy,
abi.FixedPointTy,
}
for _, ty := range wantedTypes {
if arg.Type.T == ty {
return true
}
}
return false
}
@@ -1,109 +0,0 @@
// 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 test_helpers
import (
"strings"
"github.com/vulcanize/vulcanizedb/pkg/config"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/constants"
)
var ens = strings.ToLower(constants.EnsContractAddress)
var tusd = strings.ToLower(constants.TusdContractAddress)
var TusdConfig = config.ContractConfig{
Network: "",
Addresses: map[string]bool{
tusd: true,
},
Abis: map[string]string{
tusd: "",
},
Events: map[string][]string{
tusd: {"Transfer"},
},
Methods: map[string][]string{
tusd: nil,
},
MethodArgs: map[string][]string{
tusd: nil,
},
EventArgs: map[string][]string{
tusd: nil,
},
StartingBlocks: map[string]int64{
tusd: 5197514,
},
}
var ENSConfig = config.ContractConfig{
Network: "",
Addresses: map[string]bool{
ens: true,
},
Abis: map[string]string{
ens: "",
},
Events: map[string][]string{
ens: {"NewOwner"},
},
Methods: map[string][]string{
ens: nil,
},
MethodArgs: map[string][]string{
ens: nil,
},
EventArgs: map[string][]string{
ens: nil,
},
StartingBlocks: map[string]int64{
ens: 3327417,
},
}
var ENSandTusdConfig = config.ContractConfig{
Network: "",
Addresses: map[string]bool{
ens: true,
tusd: true,
},
Abis: map[string]string{
ens: "",
tusd: "",
},
Events: map[string][]string{
ens: {"NewOwner"},
tusd: {"Transfer"},
},
Methods: map[string][]string{
ens: nil,
tusd: nil,
},
MethodArgs: map[string][]string{
ens: nil,
tusd: nil,
},
EventArgs: map[string][]string{
ens: nil,
tusd: nil,
},
StartingBlocks: map[string]int64{
ens: 3327417,
tusd: 5197514,
},
}
@@ -1,221 +0,0 @@
// 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 parser
import (
"errors"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/vulcanize/vulcanizedb/pkg/eth"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
)
// Parser is used to fetch and parse contract ABIs
// It is dependent on etherscan's api
type Parser interface {
Parse(contractAddr string) error
ParseAbiStr(abiStr string) error
Abi() string
ParsedAbi() abi.ABI
GetMethods(wanted []string) []types.Method
GetSelectMethods(wanted []string) []types.Method
GetEvents(wanted []string) map[string]types.Event
}
type parser struct {
client *eth.EtherScanAPI
abi string
parsedAbi abi.ABI
}
// NewParser returns a new Parser
func NewParser(network string) Parser {
url := eth.GenURL(network)
return &parser{
client: eth.NewEtherScanClient(url),
}
}
// Abi returns the parser's configured abi string
func (p *parser) Abi() string {
return p.abi
}
// ParsedAbi returns the parser's parsed abi
func (p *parser) ParsedAbi() abi.ABI {
return p.parsedAbi
}
// Parse retrieves and parses the abi string
// for the given contract address
func (p *parser) Parse(contractAddr string) error {
// If the abi is one our locally stored abis, fetch
// TODO: Allow users to pass abis through config
knownAbi, err := p.lookUp(contractAddr)
if err == nil {
p.abi = knownAbi
p.parsedAbi, err = eth.ParseAbi(knownAbi)
return err
}
// Try getting abi from etherscan
abiStr, err := p.client.GetAbi(contractAddr)
if err != nil {
return err
}
//TODO: Implement other ways to fetch abi
p.abi = abiStr
p.parsedAbi, err = eth.ParseAbi(abiStr)
return err
}
// ParseAbiStr loads and parses an abi from a given abi string
func (p *parser) ParseAbiStr(abiStr string) error {
var err error
p.abi = abiStr
p.parsedAbi, err = eth.ParseAbi(abiStr)
return err
}
func (p *parser) lookUp(contractAddr string) (string, error) {
if v, ok := constants.ABIs[common.HexToAddress(contractAddr)]; ok {
return v, nil
}
return "", errors.New("ABI not present in lookup table")
}
// GetSelectMethods returns only specified methods, if they meet the criteria
// Returns as array with methods in same order they were specified
// Nil or empty wanted array => no events are returned
func (p *parser) GetSelectMethods(wanted []string) []types.Method {
wLen := len(wanted)
if wLen == 0 {
return nil
}
methods := make([]types.Method, wLen)
for _, m := range p.parsedAbi.Methods {
for i, name := range wanted {
if name == m.Name && okTypes(m, wanted) {
methods[i] = types.NewMethod(m)
}
}
}
return methods
}
// GetMethods returns wanted methods
// Empty wanted array => all methods are returned
// Nil wanted array => no methods are returned
func (p *parser) GetMethods(wanted []string) []types.Method {
if wanted == nil {
return nil
}
methods := make([]types.Method, 0)
length := len(wanted)
for _, m := range p.parsedAbi.Methods {
if length == 0 || stringInSlice(wanted, m.Name) {
methods = append(methods, types.NewMethod(m))
}
}
return methods
}
// GetEvents returns wanted events as map of types.Events
// Empty wanted array => all events are returned
// Nil wanted array => no events are returned
func (p *parser) GetEvents(wanted []string) map[string]types.Event {
events := map[string]types.Event{}
if wanted == nil {
return events
}
length := len(wanted)
for _, e := range p.parsedAbi.Events {
if length == 0 || stringInSlice(wanted, e.Name) {
events[e.Name] = types.NewEvent(e)
}
}
return events
}
func okReturnType(arg abi.Argument) bool {
wantedTypes := []byte{
abi.UintTy,
abi.IntTy,
abi.BoolTy,
abi.StringTy,
abi.AddressTy,
abi.HashTy,
abi.BytesTy,
abi.FixedBytesTy,
abi.FixedPointTy,
}
for _, ty := range wantedTypes {
if arg.Type.T == ty {
return true
}
}
return false
}
func okTypes(m abi.Method, wanted []string) bool {
// Only return method if it has less than 3 arguments, a single output value, and it is a method we want or we want all methods (empty 'wanted' slice)
if len(m.Inputs) < 3 && len(m.Outputs) == 1 && (len(wanted) == 0 || stringInSlice(wanted, m.Name)) {
// Only return methods if inputs are all of accepted types and output is of the accepted types
if !okReturnType(m.Outputs[0]) {
return false
}
for _, input := range m.Inputs {
switch input.Type.T {
// Addresses are properly labeled and caught
// But hashes tend to not be explicitly labeled and caught
// Instead bytes32 are assumed to be hashes
case abi.AddressTy, abi.HashTy:
case abi.FixedBytesTy:
if input.Type.Size != 32 {
return false
}
default:
return false
}
}
return true
}
return false
}
func stringInSlice(list []string, s string) bool {
for _, b := range list {
if b == s {
return true
}
}
return false
}
@@ -1,35 +0,0 @@
// 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 parser_test
import (
"io/ioutil"
"log"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestParser(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Parser Suite Test")
}
var _ = BeforeSuite(func() {
log.SetOutput(ioutil.Discard)
})
@@ -1,226 +0,0 @@
// 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 parser_test
import (
"github.com/ethereum/go-ethereum/accounts/abi"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers/mocks"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/parser"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
)
var _ = Describe("Parser", func() {
var p parser.Parser
var err error
BeforeEach(func() {
p = parser.NewParser("")
})
Describe("Mock Parse", func() {
It("Uses parses given abi string", func() {
mp := mocks.NewParser(constants.DaiAbiString)
err = mp.Parse(constants.DaiContractAddress)
Expect(err).ToNot(HaveOccurred())
parsedAbi := mp.ParsedAbi()
expectedAbi, err := eth.ParseAbi(constants.DaiAbiString)
Expect(err).ToNot(HaveOccurred())
Expect(parsedAbi).To(Equal(expectedAbi))
methods := mp.GetSelectMethods([]string{"balanceOf"})
Expect(len(methods)).To(Equal(1))
balOf := methods[0]
Expect(balOf.Name).To(Equal("balanceOf"))
Expect(len(balOf.Args)).To(Equal(1))
Expect(len(balOf.Return)).To(Equal(1))
events := mp.GetEvents([]string{"Transfer"})
_, ok := events["Mint"]
Expect(ok).To(Equal(false))
e, ok := events["Transfer"]
Expect(ok).To(Equal(true))
Expect(len(e.Fields)).To(Equal(3))
})
})
Describe("Parse", func() {
It("Fetches and parses abi from etherscan using contract address", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359" // dai contract address
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
expectedAbi := constants.DaiAbiString
Expect(p.Abi()).To(Equal(expectedAbi))
expectedParsedAbi, err := eth.ParseAbi(expectedAbi)
Expect(err).ToNot(HaveOccurred())
Expect(p.ParsedAbi()).To(Equal(expectedParsedAbi))
})
It("Fails with a normal, non-contract, account address", func() {
addr := "0xAb2A8F7cB56D9EC65573BA1bE0f92Fa2Ff7dd165"
err = p.Parse(addr)
Expect(err).To(HaveOccurred())
})
})
Describe("GetEvents", func() {
It("Returns parsed events", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
events := p.GetEvents([]string{"Transfer"})
e, ok := events["Transfer"]
Expect(ok).To(Equal(true))
abiTy := e.Fields[0].Type.T
Expect(abiTy).To(Equal(abi.AddressTy))
pgTy := e.Fields[0].PgType
Expect(pgTy).To(Equal("CHARACTER VARYING(66)"))
abiTy = e.Fields[1].Type.T
Expect(abiTy).To(Equal(abi.AddressTy))
pgTy = e.Fields[1].PgType
Expect(pgTy).To(Equal("CHARACTER VARYING(66)"))
abiTy = e.Fields[2].Type.T
Expect(abiTy).To(Equal(abi.UintTy))
pgTy = e.Fields[2].PgType
Expect(pgTy).To(Equal("NUMERIC"))
_, ok = events["Approval"]
Expect(ok).To(Equal(false))
})
})
Describe("GetSelectMethods", func() {
It("Parses and returns only methods specified in passed array", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
methods := p.GetSelectMethods([]string{"balanceOf"})
Expect(len(methods)).To(Equal(1))
balOf := methods[0]
Expect(balOf.Name).To(Equal("balanceOf"))
Expect(len(balOf.Args)).To(Equal(1))
Expect(len(balOf.Return)).To(Equal(1))
abiTy := balOf.Args[0].Type.T
Expect(abiTy).To(Equal(abi.AddressTy))
pgTy := balOf.Args[0].PgType
Expect(pgTy).To(Equal("CHARACTER VARYING(66)"))
abiTy = balOf.Return[0].Type.T
Expect(abiTy).To(Equal(abi.UintTy))
pgTy = balOf.Return[0].PgType
Expect(pgTy).To(Equal("NUMERIC"))
})
It("Parses and returns methods in the order they were specified", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
selectMethods := p.GetSelectMethods([]string{"balanceOf", "allowance"})
Expect(len(selectMethods)).To(Equal(2))
balOf := selectMethods[0]
allow := selectMethods[1]
Expect(balOf.Name).To(Equal("balanceOf"))
Expect(allow.Name).To(Equal("allowance"))
})
It("Returns nil if given a nil or empty array", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
var nilArr []types.Method
selectMethods := p.GetSelectMethods([]string{})
Expect(selectMethods).To(Equal(nilArr))
selectMethods = p.GetMethods(nil)
Expect(selectMethods).To(Equal(nilArr))
})
})
Describe("GetMethods", func() {
It("Parses and returns only methods specified in passed array", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
methods := p.GetMethods([]string{"balanceOf"})
Expect(len(methods)).To(Equal(1))
balOf := methods[0]
Expect(balOf.Name).To(Equal("balanceOf"))
Expect(len(balOf.Args)).To(Equal(1))
Expect(len(balOf.Return)).To(Equal(1))
abiTy := balOf.Args[0].Type.T
Expect(abiTy).To(Equal(abi.AddressTy))
pgTy := balOf.Args[0].PgType
Expect(pgTy).To(Equal("CHARACTER VARYING(66)"))
abiTy = balOf.Return[0].Type.T
Expect(abiTy).To(Equal(abi.UintTy))
pgTy = balOf.Return[0].PgType
Expect(pgTy).To(Equal("NUMERIC"))
})
It("Returns nil if given a nil array", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
var nilArr []types.Method
selectMethods := p.GetMethods(nil)
Expect(selectMethods).To(Equal(nilArr))
})
It("Returns every method if given an empty array", func() {
contractAddr := "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred())
selectMethods := p.GetMethods([]string{})
Expect(len(selectMethods)).To(Equal(25))
})
})
})
@@ -1,291 +0,0 @@
// 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 poller
import (
"errors"
"fmt"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
// Poller is the interface for polling public contract methods
type Poller interface {
PollContract(con contract.Contract, lastBlock int64) error
PollContractAt(con contract.Contract, blockNumber int64) error
FetchContractData(contractAbi, contractAddress, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error
}
type poller struct {
repository.MethodRepository
bc core.BlockChain
contract contract.Contract
}
// NewPoller returns a new Poller
func NewPoller(blockChain core.BlockChain, db *postgres.DB, mode types.Mode) Poller {
return &poller{
MethodRepository: repository.NewMethodRepository(db, mode),
bc: blockChain,
}
}
// PollContract polls a contract's public methods from the contracts starting block to specified last block
func (p *poller) PollContract(con contract.Contract, lastBlock int64) error {
for i := con.StartingBlock; i <= lastBlock; i++ {
if err := p.PollContractAt(con, i); err != nil {
return err
}
}
return nil
}
// PollContractAt polls a contract's public getter methods at the specified block height
func (p *poller) PollContractAt(con contract.Contract, blockNumber int64) error {
p.contract = con
for _, m := range con.Methods {
switch len(m.Args) {
case 0:
if err := p.pollNoArgAt(m, blockNumber); err != nil {
return err
}
case 1:
if err := p.pollSingleArgAt(m, blockNumber); err != nil {
return err
}
case 2:
if err := p.pollDoubleArgAt(m, blockNumber); err != nil {
return err
}
default:
return errors.New("poller error: too many arguments to handle")
}
}
return nil
}
func (p *poller) pollNoArgAt(m types.Method, bn int64) error {
result := types.Result{
Block: bn,
Method: m,
Inputs: nil,
PgType: m.Return[0].PgType,
}
var out interface{}
err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, nil, &out, bn)
if err != nil {
return fmt.Errorf("poller error calling 0 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)
}
strOut, err := stringify(out)
if err != nil {
return err
}
// Cache returned value if piping is turned on
p.cache(out)
result.Output = strOut
// Persist result immediately
err = p.PersistResults([]types.Result{result}, m, p.contract.Address, p.contract.Name)
if err != nil {
return fmt.Errorf("poller error persisting 0 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)
}
return nil
}
// Use token holder address to poll methods that take 1 address argument (e.g. balanceOf)
func (p *poller) pollSingleArgAt(m types.Method, bn int64) error {
result := types.Result{
Block: bn,
Method: m,
Inputs: make([]interface{}, 1),
PgType: m.Return[0].PgType,
}
// Depending on the type of the arg choose
// the correct argument set to iterate over
var args map[interface{}]bool
switch m.Args[0].Type.T {
case abi.HashTy, abi.FixedBytesTy:
args = p.contract.EmittedHashes
case abi.AddressTy:
args = p.contract.EmittedAddrs
}
if len(args) == 0 { // If we haven't collected any args by now we can't call the method
return nil
}
results := make([]types.Result, 0, len(args))
for arg := range args {
in := []interface{}{arg}
strIn := []interface{}{contract.StringifyArg(arg)}
var out interface{}
err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, bn)
if err != nil {
return fmt.Errorf("poller error calling 1 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)
}
strOut, err := stringify(out)
if err != nil {
return err
}
p.cache(out)
// Write inputs and outputs to result and append result to growing set
result.Inputs = strIn
result.Output = strOut
results = append(results, result)
}
// Persist result set as batch
err := p.PersistResults(results, m, p.contract.Address, p.contract.Name)
if err != nil {
return fmt.Errorf("poller error persisting 1 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)
}
return nil
}
// Use token holder address to poll methods that take 2 address arguments (e.g. allowance)
func (p *poller) pollDoubleArgAt(m types.Method, bn int64) error {
result := types.Result{
Block: bn,
Method: m,
Inputs: make([]interface{}, 2),
PgType: m.Return[0].PgType,
}
// Depending on the type of the args choose
// the correct argument sets to iterate over
var firstArgs map[interface{}]bool
switch m.Args[0].Type.T {
case abi.HashTy, abi.FixedBytesTy:
firstArgs = p.contract.EmittedHashes
case abi.AddressTy:
firstArgs = p.contract.EmittedAddrs
}
if len(firstArgs) == 0 {
return nil
}
var secondArgs map[interface{}]bool
switch m.Args[1].Type.T {
case abi.HashTy, abi.FixedBytesTy:
secondArgs = p.contract.EmittedHashes
case abi.AddressTy:
secondArgs = p.contract.EmittedAddrs
}
if len(secondArgs) == 0 {
return nil
}
results := make([]types.Result, 0, len(firstArgs)*len(secondArgs))
for arg1 := range firstArgs {
for arg2 := range secondArgs {
in := []interface{}{arg1, arg2}
strIn := []interface{}{contract.StringifyArg(arg1), contract.StringifyArg(arg2)}
var out interface{}
err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, bn)
if err != nil {
return fmt.Errorf("poller error calling 2 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)
}
strOut, err := stringify(out)
if err != nil {
return err
}
p.cache(out)
result.Output = strOut
result.Inputs = strIn
results = append(results, result)
}
}
err := p.PersistResults(results, m, p.contract.Address, p.contract.Name)
if err != nil {
return fmt.Errorf("poller error persisting 2 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err)
}
return nil
}
// FetchContractData is just a wrapper around the poller blockchain's FetchContractData method
func (p *poller) FetchContractData(contractAbi, contractAddress, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error {
return p.bc.FetchContractData(contractAbi, contractAddress, method, methodArgs, result, blockNumber)
}
// This is used to cache a method return value if method piping is turned on
func (p *poller) cache(out interface{}) {
if p.contract.Piping {
switch out.(type) {
case common.Hash:
if p.contract.EmittedHashes != nil {
p.contract.AddEmittedHash(out.(common.Hash))
}
case []byte:
if p.contract.EmittedHashes != nil && len(out.([]byte)) == 32 {
p.contract.AddEmittedHash(common.BytesToHash(out.([]byte)))
}
case common.Address:
if p.contract.EmittedAddrs != nil {
p.contract.AddEmittedAddr(out.(common.Address))
}
default:
}
}
}
func stringify(input interface{}) (string, error) {
switch input.(type) {
case *big.Int:
b := input.(*big.Int)
return b.String(), nil
case common.Address:
a := input.(common.Address)
return a.String(), nil
case common.Hash:
h := input.(common.Hash)
return h.String(), nil
case string:
return input.(string), nil
case []byte:
b := hexutil.Encode(input.([]byte))
return b, nil
case byte:
b := input.(byte)
return string(b), nil
case bool:
return strconv.FormatBool(input.(bool)), nil
default:
return "", errors.New("error: unhandled return type")
}
}
@@ -1,316 +0,0 @@
// 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 repository
import (
"errors"
"fmt"
"strings"
"github.com/hashicorp/golang-lru"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
const (
// Number of contract address and method ids to keep in cache
contractCacheSize = 100
eventCacheSize = 1000
)
// EventRepository is used to persist event data into custom tables
type EventRepository interface {
PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error
CreateEventTable(contractAddr string, event types.Event) (bool, error)
CreateContractSchema(contractName string) (bool, error)
CheckSchemaCache(key string) (interface{}, bool)
CheckTableCache(key string) (interface{}, bool)
}
type eventRepository struct {
db *postgres.DB
mode types.Mode
schemas *lru.Cache // Cache names of recently used schemas to minimize db connections
tables *lru.Cache // Cache names of recently used tables to minimize db connections
}
// NewEventRepository returns a new EventRepository
func NewEventRepository(db *postgres.DB, mode types.Mode) EventRepository {
ccs, _ := lru.New(contractCacheSize)
ecs, _ := lru.New(eventCacheSize)
return &eventRepository{
db: db,
mode: mode,
schemas: ccs,
tables: ecs,
}
}
// PersistLogs creates a schema for the contract if needed
// Creates table for the watched contract event if needed
// Persists converted event log data into this custom table
func (r *eventRepository) PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
if len(logs) == 0 {
return errors.New("event repository error: passed empty logs slice")
}
_, schemaErr := r.CreateContractSchema(contractAddr)
if schemaErr != nil {
return fmt.Errorf("error creating schema for contract %s: %s", contractAddr, schemaErr.Error())
}
_, tableErr := r.CreateEventTable(contractAddr, eventInfo)
if tableErr != nil {
return fmt.Errorf("error creating table for event %s on contract %s: %s", eventInfo.Name, contractAddr, tableErr.Error())
}
return r.persistLogs(logs, eventInfo, contractAddr, contractName)
}
func (r *eventRepository) persistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
var err error
switch r.mode {
case types.HeaderSync:
err = r.persistHeaderSyncLogs(logs, eventInfo, contractAddr, contractName)
case types.FullSync:
err = r.persistFullSyncLogs(logs, eventInfo, contractAddr, contractName)
default:
return errors.New("event repository error: unhandled mode")
}
return err
}
// Creates a custom postgres command to persist logs for the given event (compatible with header synced vDB)
func (r *eventRepository) persistHeaderSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
tx, txErr := r.db.Beginx()
if txErr != nil {
return fmt.Errorf("error beginning db transaction: %s", txErr.Error())
}
for _, event := range logs {
// Begin pg query string
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_event ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventInfo.Name))
pgStr = pgStr + "(header_id, token_name, raw_log, log_idx, tx_idx"
el := len(event.Values)
// Preallocate slice of needed capacity and proceed to pack variables into it in same order they appear in string
data := make([]interface{}, 0, 5+el)
data = append(data,
event.ID,
contractName,
event.Raw,
event.LogIndex,
event.TransactionIndex)
// Iterate over inputs and append name to query string and value to input data
for inputName, input := range event.Values {
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName)) // Add underscore after to avoid any collisions with reserved pg words
data = append(data, input)
}
// For each input entry we created we add its postgres command variable to the string
pgStr = pgStr + ") VALUES ($1, $2, $3, $4, $5"
for i := 0; i < el; i++ {
pgStr = pgStr + fmt.Sprintf(", $%d", i+6)
}
pgStr = pgStr + ") ON CONFLICT DO NOTHING"
logrus.Tracef("query for inserting log: %s", pgStr)
// Add this query to the transaction
_, execErr := tx.Exec(pgStr, data...)
if execErr != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Warnf("error rolling back transactions while persisting logs: %s", rollbackErr.Error())
}
return fmt.Errorf("error executing query: %s", execErr.Error())
}
}
return tx.Commit()
}
// Creates a custom postgres command to persist logs for the given event (compatible with fully synced vDB)
func (r *eventRepository) persistFullSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
tx, txErr := r.db.Beginx()
if txErr != nil {
return fmt.Errorf("error beginning db transaction: %s", txErr.Error())
}
for _, event := range logs {
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_event ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventInfo.Name))
pgStr = pgStr + "(vulcanize_log_id, token_name, block, tx"
el := len(event.Values)
data := make([]interface{}, 0, 4+el)
data = append(data,
event.ID,
contractName,
event.Block,
event.Tx)
for inputName, input := range event.Values {
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName))
data = append(data, input)
}
pgStr = pgStr + ") VALUES ($1, $2, $3, $4"
for i := 0; i < el; i++ {
pgStr = pgStr + fmt.Sprintf(", $%d", i+5)
}
pgStr = pgStr + ") ON CONFLICT (vulcanize_log_id) DO NOTHING"
logrus.Tracef("query for inserting log: %s", pgStr)
_, execErr := tx.Exec(pgStr, data...)
if execErr != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Warnf("error rolling back transactions while persisting logs: %s", rollbackErr.Error())
}
return fmt.Errorf("error executing query: %s", execErr.Error())
}
}
return tx.Commit()
}
// CreateEventTable checks for event table and creates it if it does not already exist
// Returns true if it created a new table; returns false if table already existed
func (r *eventRepository) CreateEventTable(contractAddr string, event types.Event) (bool, error) {
tableID := fmt.Sprintf("%s_%s.%s_event", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(event.Name))
// Check cache before querying pq to see if table exists
_, ok := r.tables.Get(tableID)
if ok {
return false, nil
}
tableExists, checkTableErr := r.checkForTable(contractAddr, event.Name)
if checkTableErr != nil {
return false, fmt.Errorf("error checking for table: %s", checkTableErr)
}
if !tableExists {
createTableErr := r.newEventTable(tableID, event)
if createTableErr != nil {
return false, fmt.Errorf("error creating table: %s", createTableErr.Error())
}
}
// Add table id to cache
r.tables.Add(tableID, true)
return !tableExists, nil
}
// Creates a table for the given contract and event
func (r *eventRepository) newEventTable(tableID string, event types.Event) error {
// Begin pg string
var pgStr = fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ", tableID)
var err error
// Handle different modes
switch r.mode {
case types.FullSync:
pgStr = pgStr + "(id SERIAL, vulcanize_log_id INTEGER NOT NULL UNIQUE, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL, tx CHARACTER VARYING(66) NOT NULL,"
// Iterate over event fields, using their name and pgType to grow the string
for _, field := range event.Fields {
pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType)
}
pgStr = pgStr + " CONSTRAINT log_index_fk FOREIGN KEY (vulcanize_log_id) REFERENCES full_sync_logs (id) ON DELETE CASCADE)"
case types.HeaderSync:
pgStr = pgStr + "(id SERIAL, header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, token_name CHARACTER VARYING(66) NOT NULL, raw_log JSONB, log_idx INTEGER NOT NULL, tx_idx INTEGER NOT NULL,"
for _, field := range event.Fields {
pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType)
}
pgStr = pgStr + " UNIQUE (header_id, tx_idx, log_idx))"
default:
return errors.New("unhandled repository mode")
}
_, err = r.db.Exec(pgStr)
return err
}
// Checks if a table already exists for the given contract and event
func (r *eventRepository) checkForTable(contractAddr string, eventName string) (bool, error) {
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '%s_%s' AND table_name = '%s_event')", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventName))
var exists bool
err := r.db.Get(&exists, pgStr)
return exists, err
}
// CreateContractSchema checks for contract schema and creates it if it does not already exist
// Returns true if it created a new schema; returns false if schema already existed
func (r *eventRepository) CreateContractSchema(contractAddr string) (bool, error) {
if contractAddr == "" {
return false, errors.New("error: no contract address specified")
}
// Check cache before querying pq to see if schema exists
_, ok := r.schemas.Get(contractAddr)
if ok {
return false, nil
}
schemaExists, checkSchemaErr := r.checkForSchema(contractAddr)
if checkSchemaErr != nil {
return false, fmt.Errorf("error checking for schema: %s", checkSchemaErr.Error())
}
if !schemaExists {
createSchemaErr := r.newContractSchema(contractAddr)
if createSchemaErr != nil {
return false, fmt.Errorf("error creating schema: %s", createSchemaErr.Error())
}
}
// Add schema name to cache
r.schemas.Add(contractAddr, true)
return !schemaExists, nil
}
// Creates a schema for the given contract
func (r *eventRepository) newContractSchema(contractAddr string) error {
_, err := r.db.Exec("CREATE SCHEMA IF NOT EXISTS " + r.mode.String() + "_" + strings.ToLower(contractAddr))
return err
}
// Checks if a schema already exists for the given contract
func (r *eventRepository) checkForSchema(contractAddr string) (bool, error) {
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = '%s_%s')", r.mode.String(), strings.ToLower(contractAddr))
var exists bool
err := r.db.QueryRow(pgStr).Scan(&exists)
return exists, err
}
// CheckSchemaCache is used to query the schema name cache
func (r *eventRepository) CheckSchemaCache(key string) (interface{}, bool) {
return r.schemas.Get(key)
}
// CheckTableCache is used to query the table name cache
func (r *eventRepository) CheckTableCache(key string) (interface{}, bool) {
return r.tables.Get(key)
}
@@ -1,235 +0,0 @@
// 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 repository
import (
"errors"
"fmt"
"strings"
"github.com/hashicorp/golang-lru"
"github.com/sirupsen/logrus"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
const methodCacheSize = 1000
// MethodRepository is used to persist public getter method data
type MethodRepository interface {
PersistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error
CreateMethodTable(contractAddr string, method types.Method) (bool, error)
CreateContractSchema(contractAddr string) (bool, error)
CheckSchemaCache(key string) (interface{}, bool)
CheckTableCache(key string) (interface{}, bool)
}
type methodRepository struct {
*postgres.DB
mode types.Mode
schemas *lru.Cache // Cache names of recently used schemas to minimize db connections
tables *lru.Cache // Cache names of recently used tables to minimize db connections
}
// NewMethodRepository returns a new MethodRepository
func NewMethodRepository(db *postgres.DB, mode types.Mode) MethodRepository {
ccs, _ := lru.New(contractCacheSize)
mcs, _ := lru.New(methodCacheSize)
return &methodRepository{
DB: db,
mode: mode,
schemas: ccs,
tables: mcs,
}
}
// PersistResults creates a schema for the contract if needed
// Creates table for the contract method if needed
// Persists method polling data into this custom table
func (r *methodRepository) PersistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error {
if len(results) == 0 {
return errors.New("method repository error: passed empty results slice")
}
_, err := r.CreateContractSchema(contractAddr)
if err != nil {
return err
}
_, err = r.CreateMethodTable(contractAddr, methodInfo)
if err != nil {
return err
}
return r.persistResults(results, methodInfo, contractAddr, contractName)
}
// Creates a custom postgres command to persist logs for the given event
func (r *methodRepository) persistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error {
tx, err := r.DB.Beginx()
if err != nil {
return err
}
for _, result := range results {
// Begin postgres string
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_method ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(result.Name))
pgStr = pgStr + "(token_name, block"
ml := len(result.Args)
// Preallocate slice of needed capacity and proceed to pack variables into it in same order they appear in string
data := make([]interface{}, 0, 3+ml)
data = append(data,
contractName,
result.Block)
// Iterate over method args and return value, adding names
// to the string and pushing values to the slice
for i, arg := range result.Args {
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(arg.Name)) // Add underscore after to avoid any collisions with reserved pg words
data = append(data, result.Inputs[i])
}
pgStr = pgStr + ", returned) VALUES ($1, $2"
data = append(data, result.Output)
// For each input entry we created we add its postgres command variable to the string
for i := 0; i <= ml; i++ {
pgStr = pgStr + fmt.Sprintf(", $%d", i+3)
}
pgStr = pgStr + ")"
// Add this query to the transaction
_, err = tx.Exec(pgStr, data...)
if err != nil {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
logrus.Warnf("error rolling back transaction: %s", rollbackErr.Error())
}
return err
}
}
return tx.Commit()
}
// CreateMethodTable checks for event table and creates it if it does not already exist
func (r *methodRepository) CreateMethodTable(contractAddr string, method types.Method) (bool, error) {
tableID := fmt.Sprintf("%s_%s.%s_method", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(method.Name))
// Check cache before querying pq to see if table exists
_, ok := r.tables.Get(tableID)
if ok {
return false, nil
}
tableExists, err := r.checkForTable(contractAddr, method.Name)
if err != nil {
return false, err
}
if !tableExists {
err = r.newMethodTable(tableID, method)
if err != nil {
return false, err
}
}
// Add schema name to cache
r.tables.Add(tableID, true)
return !tableExists, nil
}
// Creates a table for the given contract and event
func (r *methodRepository) newMethodTable(tableID string, method types.Method) error {
// Begin pg string
pgStr := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ", tableID)
pgStr = pgStr + "(id SERIAL, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL,"
// Iterate over method inputs and outputs, using their name and pgType to grow the string
for _, arg := range method.Args {
pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(arg.Name), arg.PgType)
}
pgStr = pgStr + fmt.Sprintf(" returned %s NOT NULL)", method.Return[0].PgType)
_, err := r.DB.Exec(pgStr)
return err
}
// Checks if a table already exists for the given contract and event
func (r *methodRepository) checkForTable(contractAddr string, methodName string) (bool, error) {
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '%s_%s' AND table_name = '%s_method')", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(methodName))
var exists bool
err := r.DB.Get(&exists, pgStr)
return exists, err
}
// CreateContractSchema checks for contract schema and creates it if it does not already exist
func (r *methodRepository) CreateContractSchema(contractAddr string) (bool, error) {
if contractAddr == "" {
return false, errors.New("error: no contract address specified")
}
// Check cache before querying pq to see if schema exists
_, ok := r.schemas.Get(contractAddr)
if ok {
return false, nil
}
schemaExists, err := r.checkForSchema(contractAddr)
if err != nil {
return false, err
}
if !schemaExists {
err = r.newContractSchema(contractAddr)
if err != nil {
return false, err
}
}
// Add schema name to cache
r.schemas.Add(contractAddr, true)
return !schemaExists, nil
}
// Creates a schema for the given contract
func (r *methodRepository) newContractSchema(contractAddr string) error {
_, err := r.DB.Exec("CREATE SCHEMA IF NOT EXISTS " + r.mode.String() + "_" + strings.ToLower(contractAddr))
return err
}
// Checks if a schema already exists for the given contract
func (r *methodRepository) checkForSchema(contractAddr string) (bool, error) {
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = '%s_%s')", r.mode.String(), strings.ToLower(contractAddr))
var exists bool
err := r.DB.Get(&exists, pgStr)
return exists, err
}
// CheckSchemaCache is used to query the schema name cache
func (r *methodRepository) CheckSchemaCache(key string) (interface{}, bool) {
return r.schemas.Get(key)
}
// CheckTableCache is used to query the table name cache
func (r *methodRepository) CheckTableCache(key string) (interface{}, bool) {
return r.tables.Get(key)
}
@@ -1,240 +0,0 @@
// 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 repository_test
import (
"fmt"
"strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/repository"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
var _ = Describe("Repository", func() {
var db *postgres.DB
var dataStore repository.MethodRepository
var con *contract.Contract
var err error
var mockResult types.Result
var method types.Method
BeforeEach(func() {
con = test_helpers.SetupTusdContract([]string{}, []string{"balanceOf"})
Expect(len(con.Methods)).To(Equal(1))
method = con.Methods[0]
mockResult = types.Result{
Method: method,
PgType: method.Return[0].PgType,
Inputs: make([]interface{}, 1),
Output: new(interface{}),
Block: 6707323,
}
mockResult.Inputs[0] = "0xfE9e8709d3215310075d67E3ed32A380CCf451C8"
mockResult.Output = "66386309548896882859581786"
db, _ = test_helpers.SetupDBandBC()
dataStore = repository.NewMethodRepository(db, types.FullSync)
})
AfterEach(func() {
test_helpers.TearDown(db)
})
Describe("Full Sync Mode", func() {
BeforeEach(func() {
dataStore = repository.NewMethodRepository(db, types.FullSync)
})
Describe("CreateContractSchema", func() {
It("Creates schema if it doesn't exist", func() {
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
created, err = dataStore.CreateContractSchema(constants.TusdContractAddress)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(false))
})
It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
_, ok := dataStore.CheckSchemaCache(con.Address)
Expect(ok).To(Equal(false))
created, err := dataStore.CreateContractSchema(con.Address)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
v, ok := dataStore.CheckSchemaCache(con.Address)
Expect(ok).To(Equal(true))
Expect(v).To(Equal(true))
})
})
Describe("CreateMethodTable", func() {
It("Creates table if it doesn't exist", func() {
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(false))
})
It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
created, err := dataStore.CreateContractSchema(con.Address)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
tableID := fmt.Sprintf("%s_%s.%s_method", types.FullSync, strings.ToLower(con.Address), strings.ToLower(method.Name))
_, ok := dataStore.CheckTableCache(tableID)
Expect(ok).To(Equal(false))
created, err = dataStore.CreateMethodTable(con.Address, method)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
v, ok := dataStore.CheckTableCache(tableID)
Expect(ok).To(Equal(true))
Expect(v).To(Equal(true))
})
})
Describe("PersistResult", func() {
It("Persists result from method polling in custom pg table", func() {
err = dataStore.PersistResults([]types.Result{mockResult}, method, con.Address, con.Name)
Expect(err).ToNot(HaveOccurred())
scanStruct := test_helpers.BalanceOf{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method", constants.TusdContractAddress)).StructScan(&scanStruct)
expectedLog := test_helpers.BalanceOf{
ID: 1,
TokenName: "TrueUSD",
Block: 6707323,
Address: "0xfE9e8709d3215310075d67E3ed32A380CCf451C8",
Balance: "66386309548896882859581786",
}
Expect(scanStruct).To(Equal(expectedLog))
})
It("Fails with empty result", func() {
err = dataStore.PersistResults([]types.Result{}, method, con.Address, con.Name)
Expect(err).To(HaveOccurred())
})
})
})
Describe("Header Sync Mode", func() {
BeforeEach(func() {
dataStore = repository.NewMethodRepository(db, types.HeaderSync)
})
Describe("CreateContractSchema", func() {
It("Creates schema if it doesn't exist", func() {
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
created, err = dataStore.CreateContractSchema(constants.TusdContractAddress)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(false))
})
It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
_, ok := dataStore.CheckSchemaCache(con.Address)
Expect(ok).To(Equal(false))
created, err := dataStore.CreateContractSchema(con.Address)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
v, ok := dataStore.CheckSchemaCache(con.Address)
Expect(ok).To(Equal(true))
Expect(v).To(Equal(true))
})
})
Describe("CreateMethodTable", func() {
It("Creates table if it doesn't exist", func() {
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(false))
})
It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
created, err := dataStore.CreateContractSchema(con.Address)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
tableID := fmt.Sprintf("%s_%s.%s_method", types.HeaderSync, strings.ToLower(con.Address), strings.ToLower(method.Name))
_, ok := dataStore.CheckTableCache(tableID)
Expect(ok).To(Equal(false))
created, err = dataStore.CreateMethodTable(con.Address, method)
Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true))
v, ok := dataStore.CheckTableCache(tableID)
Expect(ok).To(Equal(true))
Expect(v).To(Equal(true))
})
})
Describe("PersistResult", func() {
It("Persists result from method polling in custom pg table for header sync mode vDB", func() {
err = dataStore.PersistResults([]types.Result{mockResult}, method, con.Address, con.Name)
Expect(err).ToNot(HaveOccurred())
scanStruct := test_helpers.BalanceOf{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM header_%s.balanceof_method", constants.TusdContractAddress)).StructScan(&scanStruct)
expectedLog := test_helpers.BalanceOf{
ID: 1,
TokenName: "TrueUSD",
Block: 6707323,
Address: "0xfE9e8709d3215310075d67E3ed32A380CCf451C8",
Balance: "66386309548896882859581786",
}
Expect(scanStruct).To(Equal(expectedLog))
})
It("Fails with empty result", func() {
err = dataStore.PersistResults([]types.Result{}, method, con.Address, con.Name)
Expect(err).To(HaveOccurred())
})
})
})
})
@@ -1,35 +0,0 @@
// 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 repository_test
import (
"io/ioutil"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)
func TestRepository(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Shared Repository Suite Test")
}
var _ = BeforeSuite(func() {
logrus.SetOutput(ioutil.Discard)
})
@@ -1,121 +0,0 @@
// 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 retriever
import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/eth/contract_watcher/shared/types"
"github.com/vulcanize/vulcanizedb/pkg/postgres"
)
// AddressRetriever is used to retrieve the addresses associated with a contract
type AddressRetriever interface {
RetrieveTokenHolderAddresses(info contract.Contract) (map[common.Address]bool, error)
}
type addressRetriever struct {
db *postgres.DB
mode types.Mode
}
// NewAddressRetriever returns a new AddressRetriever
func NewAddressRetriever(db *postgres.DB, mode types.Mode) AddressRetriever {
return &addressRetriever{
db: db,
mode: mode,
}
}
// RetrieveTokenHolderAddresses is used to retrieve list of token-holding/contract-related addresses by iterating over available events
// This generic method should work whether or not the argument/input names of the events meet the expected standard
// This could be generalized to iterate over ALL events and pull out any address arguments
func (r *addressRetriever) RetrieveTokenHolderAddresses(info contract.Contract) (map[common.Address]bool, error) {
addrList := make([]string, 0)
_, ok := info.Filters["Transfer"]
if ok {
addrs, err := r.retrieveTransferAddresses(info)
if err != nil {
return nil, err
}
addrList = append(addrList, addrs...)
}
_, ok = info.Filters["Mint"]
if ok {
addrs, err := r.retrieveTokenMintees(info)
if err != nil {
return nil, err
}
addrList = append(addrList, addrs...)
}
contractAddresses := make(map[common.Address]bool)
for _, addr := range addrList {
contractAddresses[common.HexToAddress(addr)] = true
}
return contractAddresses, nil
}
func (r *addressRetriever) retrieveTransferAddresses(con contract.Contract) ([]string, error) {
transferAddrs := make([]string, 0)
event := con.Events["Transfer"]
for _, field := range event.Fields { // Iterate over event fields, finding the ones with address type
if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses
addrs := make([]string, 0)
pgStr := fmt.Sprintf("SELECT %s_ FROM %s_%s.%s_event", strings.ToLower(field.Name), r.mode.String(), strings.ToLower(con.Address), strings.ToLower(event.Name))
err := r.db.Select(&addrs, pgStr)
if err != nil {
return []string{}, err
}
transferAddrs = append(transferAddrs, addrs...) // And append them to the growing list
}
}
return transferAddrs, nil
}
func (r *addressRetriever) retrieveTokenMintees(con contract.Contract) ([]string, error) {
mintAddrs := make([]string, 0)
event := con.Events["Mint"]
for _, field := range event.Fields { // Iterate over event fields, finding the ones with address type
if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses
addrs := make([]string, 0)
pgStr := fmt.Sprintf("SELECT %s_ FROM %s_%s.%s_event", strings.ToLower(field.Name), r.mode.String(), strings.ToLower(con.Address), strings.ToLower(event.Name))
err := r.db.Select(&addrs, pgStr)
if err != nil {
return []string{}, err
}
mintAddrs = append(mintAddrs, addrs...) // And append them to the growing list
}
}
return mintAddrs, nil
}
@@ -1,99 +0,0 @@
// 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 types
import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// Event is our custom event type
type Event struct {
Name string
Anonymous bool
Fields []Field
}
// Field is our custom event field type which associates a postgres type with the field
type Field struct {
abi.Argument // Name, Type, Indexed
PgType string // Holds type used when committing data held in this field to postgres
}
// Log is used to hold instance of an event log data
type Log struct {
ID int64 // VulcanizeIdLog for full sync and header ID for header sync contract watcher
Values map[string]string // Map of event input names to their values
// Used for full sync only
Block int64
Tx string
// Used for headerSync only
LogIndex uint
TransactionIndex uint
Raw []byte // json.Unmarshalled byte array of geth/core/types.Log{}
}
// NewEvent unpacks abi.Event into our custom Event struct
func NewEvent(e abi.Event) Event {
fields := make([]Field, len(e.Inputs))
for i, input := range e.Inputs {
fields[i] = Field{}
fields[i].Name = input.Name
fields[i].Type = input.Type
fields[i].Indexed = input.Indexed
// Fill in pg type based on abi type
switch fields[i].Type.T {
case abi.HashTy, abi.AddressTy:
fields[i].PgType = "CHARACTER VARYING(66)"
case abi.IntTy, abi.UintTy:
fields[i].PgType = "NUMERIC"
case abi.BoolTy:
fields[i].PgType = "BOOLEAN"
case abi.BytesTy, abi.FixedBytesTy:
fields[i].PgType = "BYTEA"
case abi.ArrayTy:
fields[i].PgType = "TEXT[]"
case abi.FixedPointTy:
fields[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres?
default:
fields[i].PgType = "TEXT"
}
}
return Event{
Name: e.Name,
Anonymous: e.Anonymous,
Fields: fields,
}
}
// Sig returns the hash signature for an event
func (e Event) Sig() common.Hash {
types := make([]string, len(e.Fields))
for i, input := range e.Fields {
types[i] = input.Type.String()
}
return crypto.Keccak256Hash([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))
}
@@ -1,113 +0,0 @@
// 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 types
import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// Method is our custom method struct
type Method struct {
Name string
Const bool
Args []Field
Return []Field
}
// Result is used to hold instance of result from method call with given inputs and block
type Result struct {
Method
Inputs []interface{} // Will only use addresses
Output interface{}
PgType string // Holds output pg type
Block int64
}
// NewMethod unpacks abi.Method into our custom Method struct
func NewMethod(m abi.Method) Method {
inputs := make([]Field, len(m.Inputs))
for i, input := range m.Inputs {
inputs[i] = Field{}
inputs[i].Name = input.Name
inputs[i].Type = input.Type
inputs[i].Indexed = input.Indexed
switch inputs[i].Type.T {
case abi.HashTy, abi.AddressTy:
inputs[i].PgType = "CHARACTER VARYING(66)"
case abi.IntTy, abi.UintTy:
inputs[i].PgType = "NUMERIC"
case abi.BoolTy:
inputs[i].PgType = "BOOLEAN"
case abi.BytesTy, abi.FixedBytesTy:
inputs[i].PgType = "BYTEA"
case abi.ArrayTy:
inputs[i].PgType = "TEXT[]"
case abi.FixedPointTy:
inputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres?
default:
inputs[i].PgType = "TEXT"
}
}
outputs := make([]Field, len(m.Outputs))
for i, output := range m.Outputs {
outputs[i] = Field{}
outputs[i].Name = output.Name
outputs[i].Type = output.Type
outputs[i].Indexed = output.Indexed
switch outputs[i].Type.T {
case abi.HashTy, abi.AddressTy:
outputs[i].PgType = "CHARACTER VARYING(66)"
case abi.IntTy, abi.UintTy:
outputs[i].PgType = "NUMERIC"
case abi.BoolTy:
outputs[i].PgType = "BOOLEAN"
case abi.BytesTy, abi.FixedBytesTy:
outputs[i].PgType = "BYTEA"
case abi.ArrayTy:
outputs[i].PgType = "TEXT[]"
case abi.FixedPointTy:
outputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres?
default:
outputs[i].PgType = "TEXT"
}
}
return Method{
Name: m.Name,
Const: m.Const,
Args: inputs,
Return: outputs,
}
}
// Sig returns the hash signature for the method
func (m Method) Sig() common.Hash {
types := make([]string, len(m.Args))
i := 0
for _, arg := range m.Args {
types[i] = arg.Type.String()
i++
}
return crypto.Keccak256Hash([]byte(fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ","))))
}
@@ -1,43 +0,0 @@
// 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 types
// Mode is used to explicitly represent the operating mode of the transformer
type Mode int
// Mode enums
const (
HeaderSync Mode = iota
FullSync
)
// IsValid returns true is the Mode is valid
func (mode Mode) IsValid() bool {
return mode >= HeaderSync && mode <= FullSync
}
// String returns the string representation of the mode
func (mode Mode) String() string {
switch mode {
case HeaderSync:
return "header"
case FullSync:
return "full"
default:
return "unknown"
}
}
@@ -26,7 +26,7 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/statediff"
"github.com/vulcanize/vulcanizedb/pkg/super_node/shared"
"github.com/vulcanize/ipfs-chain-watcher/pkg/shared"
)
// PayloadConverter satisfies the PayloadConverter interface for ethereum
@@ -22,8 +22,8 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth"
"github.com/vulcanize/vulcanizedb/pkg/super_node/eth/mocks"
"github.com/vulcanize/ipfs-chain-watcher/pkg/eth"
"github.com/vulcanize/ipfs-chain-watcher/pkg/eth/mocks"
)
var _ = Describe("Converter", func() {
@@ -1,88 +0,0 @@
// 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 cold_db
import (
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
"golang.org/x/sync/errgroup"
)
type ColdDbTransactionConverter struct{}
func NewColdDbTransactionConverter() *ColdDbTransactionConverter {
return &ColdDbTransactionConverter{}
}
func (cdtc *ColdDbTransactionConverter) ConvertBlockTransactionsToCore(gethBlock *types.Block) ([]core.TransactionModel, error) {
var g errgroup.Group
coreTransactions := make([]core.TransactionModel, len(gethBlock.Transactions()))
for gethTransactionIndex, gethTransaction := range gethBlock.Transactions() {
transaction := gethTransaction
transactionIndex := uint(gethTransactionIndex)
g.Go(func() error {
signer := getSigner(transaction)
sender, err := signer.Sender(transaction)
if err != nil {
return err
}
coreTransaction := transToCoreTrans(transaction, &sender)
coreTransactions[transactionIndex] = coreTransaction
return nil
})
}
if err := g.Wait(); err != nil {
return coreTransactions, err
}
return coreTransactions, nil
}
func (cdtc *ColdDbTransactionConverter) ConvertRPCTransactionsToModels(transactions []core.RPCTransaction) ([]core.TransactionModel, error) {
panic("converting transaction indexes to integer not supported for cold import")
}
func getSigner(tx *types.Transaction) types.Signer {
v, _, _ := tx.RawSignatureValues()
if v.Sign() != 0 && tx.Protected() {
return types.NewEIP155Signer(tx.ChainId())
}
return types.HomesteadSigner{}
}
func transToCoreTrans(transaction *types.Transaction, from *common.Address) core.TransactionModel {
return core.TransactionModel{
Hash: transaction.Hash().Hex(),
Nonce: transaction.Nonce(),
To: strings.ToLower(addressToHex(transaction.To())),
From: strings.ToLower(addressToHex(from)),
GasLimit: transaction.Gas(),
GasPrice: transaction.GasPrice().Int64(),
Value: transaction.Value().String(),
Data: transaction.Data(),
}
}
func addressToHex(to *common.Address) string {
if to == nil {
return ""
}
return to.Hex()
}
@@ -1,91 +0,0 @@
// 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 common
import (
"encoding/json"
"math/big"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
type BlockConverter struct {
transactionConverter TransactionConverter
}
func NewBlockConverter(transactionConverter TransactionConverter) BlockConverter {
return BlockConverter{transactionConverter: transactionConverter}
}
func (bc BlockConverter) ToCoreBlock(gethBlock *types.Block) (core.Block, error) {
transactions, err := bc.transactionConverter.ConvertBlockTransactionsToCore(gethBlock)
if err != nil {
return core.Block{}, err
}
coreBlock := core.Block{
Difficulty: gethBlock.Difficulty().Int64(),
ExtraData: hexutil.Encode(gethBlock.Extra()),
GasLimit: gethBlock.GasLimit(),
GasUsed: gethBlock.GasUsed(),
Hash: gethBlock.Hash().Hex(),
Miner: strings.ToLower(gethBlock.Coinbase().Hex()),
Nonce: hexutil.Encode(gethBlock.Header().Nonce[:]),
Number: gethBlock.Number().Int64(),
ParentHash: gethBlock.ParentHash().Hex(),
Size: gethBlock.Size().String(),
Time: gethBlock.Time(),
Transactions: transactions,
UncleHash: gethBlock.UncleHash().Hex(),
}
coreBlock.Reward = CalcBlockReward(coreBlock, gethBlock.Uncles()).String()
totalUncleReward, uncles := bc.ToCoreUncle(coreBlock, gethBlock.Uncles())
coreBlock.UnclesReward = totalUncleReward.String()
coreBlock.Uncles = uncles
return coreBlock, nil
}
// Rewards for the miners of uncles is calculated as (U_n + 8 - B_n) * R / 8
// Where U_n is the uncle block number, B_n is the parent block number and R is the static block reward at B_n
// https://github.com/ethereum/go-ethereum/issues/1591
// https://ethereum.stackexchange.com/questions/27172/different-uncles-reward
// https://github.com/ethereum/homestead-guide/issues/399
// Returns the total uncle reward and the individual processed uncles
func (bc BlockConverter) ToCoreUncle(block core.Block, uncles []*types.Header) (*big.Int, []core.Uncle) {
totalUncleRewards := new(big.Int)
coreUncles := make([]core.Uncle, 0, len(uncles))
for _, uncle := range uncles {
thisUncleReward := calcUncleMinerReward(block.Number, uncle.Number.Int64())
raw, _ := json.Marshal(uncle)
coreUncle := core.Uncle{
Miner: uncle.Coinbase.Hex(),
Hash: uncle.Hash().Hex(),
Raw: raw,
Reward: thisUncleReward.String(),
Timestamp: strconv.FormatUint(uncle.Time, 10),
}
coreUncles = append(coreUncles, coreUncle)
totalUncleRewards.Add(totalUncleRewards, thisUncleReward)
}
return totalUncleRewards, coreUncles
}
@@ -1,393 +0,0 @@
// 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 common_test
import (
"bytes"
"io/ioutil"
"log"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
"github.com/vulcanize/vulcanizedb/pkg/eth/converters/rpc"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
)
var _ = Describe("Conversion of GethBlock to core.Block", func() {
It("converts basic Block metadata", func() {
difficulty := big.NewInt(1)
gasLimit := uint64(100000)
gasUsed := uint64(100000)
miner := common.HexToAddress("0x0000000000000000000000000000000000000123")
extraData, _ := hexutil.Decode("0xe4b883e5bda9e7a59ee4bb99e9b1bc")
nonce := types.BlockNonce{10}
number := int64(1)
time := uint64(140000000)
header := types.Header{
Difficulty: difficulty,
GasLimit: uint64(gasLimit),
GasUsed: uint64(gasUsed),
Extra: extraData,
Coinbase: miner,
Nonce: nonce,
Number: big.NewInt(number),
ParentHash: common.Hash{64},
Time: time,
UncleHash: common.Hash{128},
}
block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})
client := fakes.NewMockEthClient()
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred())
Expect(coreBlock.Difficulty).To(Equal(difficulty.Int64()))
Expect(coreBlock.GasLimit).To(Equal(gasLimit))
Expect(coreBlock.Miner).To(Equal(miner.Hex()))
Expect(coreBlock.GasUsed).To(Equal(gasUsed))
Expect(coreBlock.Hash).To(Equal(block.Hash().Hex()))
Expect(coreBlock.Nonce).To(Equal(hexutil.Encode(header.Nonce[:])))
Expect(coreBlock.Number).To(Equal(number))
Expect(coreBlock.ParentHash).To(Equal(block.ParentHash().Hex()))
Expect(coreBlock.ExtraData).To(Equal(hexutil.Encode(block.Extra())))
Expect(coreBlock.Size).To(Equal(block.Size().String()))
Expect(coreBlock.Time).To(Equal(time))
Expect(coreBlock.UncleHash).To(Equal(block.UncleHash().Hex()))
Expect(coreBlock.IsFinal).To(BeFalse())
})
Describe("The block and uncle rewards calculations", func() {
It("calculates block rewards for a block", func() {
transaction := types.NewTransaction(
uint64(226823),
common.HexToAddress("0x108fedb097c1dcfed441480170144d8e19bb217f"),
big.NewInt(1080900090000000000),
uint64(90000),
big.NewInt(50000000000),
[]byte{},
)
transactions := []*types.Transaction{transaction}
txHash := transaction.Hash()
receipt := types.Receipt{
TxHash: txHash,
GasUsed: uint64(21000),
CumulativeGasUsed: uint64(21000),
}
receipts := []*types.Receipt{&receipt}
client := fakes.NewMockEthClient()
client.SetTransactionReceipts(receipts)
number := int64(1071819)
header := types.Header{
Number: big.NewInt(number),
}
uncles := []*types.Header{{Number: big.NewInt(1071817)}, {Number: big.NewInt(1071818)}}
block := types.NewBlock(&header, transactions, uncles, []*types.Receipt{&receipt})
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred())
expectedBlockReward := new(big.Int)
expectedBlockReward.SetString("5313550000000000000", 10)
blockReward := vulcCommon.CalcBlockReward(coreBlock, block.Uncles())
Expect(blockReward.String()).To(Equal(expectedBlockReward.String()))
})
It("calculates the uncles reward for a block", func() {
transaction := types.NewTransaction(
uint64(226823),
common.HexToAddress("0x108fedb097c1dcfed441480170144d8e19bb217f"),
big.NewInt(1080900090000000000),
uint64(90000),
big.NewInt(50000000000),
[]byte{})
transactions := []*types.Transaction{transaction}
receipt := types.Receipt{
TxHash: transaction.Hash(),
GasUsed: uint64(21000),
CumulativeGasUsed: uint64(21000),
}
receipts := []*types.Receipt{&receipt}
header := types.Header{
Number: big.NewInt(int64(1071819)),
}
uncles := []*types.Header{
{Number: big.NewInt(1071816)},
{Number: big.NewInt(1071817)},
}
block := types.NewBlock(&header, transactions, uncles, receipts)
client := fakes.NewMockEthClient()
client.SetTransactionReceipts(receipts)
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred())
expectedTotalReward := new(big.Int)
expectedTotalReward.SetString("6875000000000000000", 10)
totalReward, coreUncles := blockConverter.ToCoreUncle(coreBlock, block.Uncles())
Expect(totalReward.String()).To(Equal(expectedTotalReward.String()))
Expect(len(coreUncles)).To(Equal(2))
Expect(coreUncles[0].Reward).To(Equal("3125000000000000000"))
Expect(coreUncles[0].Miner).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(coreUncles[0].Hash).To(Equal("0xb629de4014b6e30cf9555ee833f1806fa0d8b8516fde194405f9c98c2deb8772"))
Expect(coreUncles[1].Reward).To(Equal("3750000000000000000"))
Expect(coreUncles[1].Miner).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(coreUncles[1].Hash).To(Equal("0x673f5231e4888a951e0bc8a25b5774b982e6e9e258362c21affaff6e02dd5a2b"))
})
It("decreases the static block reward from 5 to 3 for blocks after block 4,269,999", func() {
transactionOne := types.NewTransaction(
uint64(8072),
common.HexToAddress("0xebd17720aeb7ac5186c5dfa7bafeb0bb14c02551 "),
big.NewInt(0),
uint64(500000),
big.NewInt(42000000000),
[]byte{},
)
transactionTwo := types.NewTransaction(uint64(8071),
common.HexToAddress("0x3cdab63d764c8c5048ed5e8f0a4e95534ba7e1ea"),
big.NewInt(0),
uint64(500000),
big.NewInt(42000000000),
[]byte{})
transactions := []*types.Transaction{transactionOne, transactionTwo}
receiptOne := types.Receipt{
TxHash: transactionOne.Hash(),
GasUsed: uint64(297508),
CumulativeGasUsed: uint64(0),
}
receiptTwo := types.Receipt{
TxHash: transactionTwo.Hash(),
GasUsed: uint64(297508),
CumulativeGasUsed: uint64(0),
}
receipts := []*types.Receipt{&receiptOne, &receiptTwo}
number := int64(4370055)
header := types.Header{
Number: big.NewInt(number),
}
var uncles []*types.Header
block := types.NewBlock(&header, transactions, uncles, receipts)
client := fakes.NewMockEthClient()
client.SetTransactionReceipts(receipts)
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred())
expectedRewards := new(big.Int)
expectedRewards.SetString("3024990672000000000", 10)
rewards := vulcCommon.CalcBlockReward(coreBlock, block.Uncles())
Expect(rewards.String()).To(Equal(expectedRewards.String()))
})
})
Describe("the converted transactions", func() {
It("is empty", func() {
header := types.Header{}
block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})
client := fakes.NewMockEthClient()
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred())
Expect(len(coreBlock.Transactions)).To(Equal(0))
})
It("converts a single transaction", func() {
gethTransaction := types.NewTransaction(
uint64(10000), common.Address{1},
big.NewInt(10),
uint64(5000),
big.NewInt(3),
hexutil.MustDecode("0xf7d8c8830000000000000000000000000000000000000000000000000000000000037788000000000000000000000000000000000000000000000000000000000003bd14"),
)
var rawTransaction bytes.Buffer
encodeErr := gethTransaction.EncodeRLP(&rawTransaction)
Expect(encodeErr).NotTo(HaveOccurred())
gethReceipt := &types.Receipt{
Bloom: types.BytesToBloom(hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
ContractAddress: common.HexToAddress("x123"),
CumulativeGasUsed: uint64(7996119),
GasUsed: uint64(21000),
Logs: []*types.Log{},
Status: uint64(1),
TxHash: gethTransaction.Hash(),
}
client := fakes.NewMockEthClient()
client.SetTransactionReceipts([]*types.Receipt{gethReceipt})
header := types.Header{}
block := types.NewBlock(
&header,
[]*types.Transaction{gethTransaction},
[]*types.Header{},
[]*types.Receipt{gethReceipt},
)
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred())
Expect(len(coreBlock.Transactions)).To(Equal(1))
coreTransaction := coreBlock.Transactions[0]
expectedData := common.FromHex("0xf7d8c8830000000000000000000000000000000000000000000000000000000000037788000000000000000000000000000000000000000000000000000000000003bd14")
Expect(coreTransaction.Data).To(Equal(expectedData))
Expect(coreTransaction.To).To(Equal(gethTransaction.To().Hex()))
Expect(coreTransaction.From).To(Equal("0x0000000000000000000000000000000000000123"))
Expect(coreTransaction.GasLimit).To(Equal(gethTransaction.Gas()))
Expect(coreTransaction.GasPrice).To(Equal(gethTransaction.GasPrice().Int64()))
Expect(coreTransaction.Raw).To(Equal(rawTransaction.Bytes()))
Expect(coreTransaction.TxIndex).To(Equal(int64(0)))
Expect(coreTransaction.Value).To(Equal(gethTransaction.Value().String()))
Expect(coreTransaction.Nonce).To(Equal(gethTransaction.Nonce()))
coreReceipt := coreTransaction.Receipt
expectedReceipt, err := vulcCommon.ToCoreReceipt(gethReceipt)
Expect(err).ToNot(HaveOccurred())
Expect(coreReceipt).To(Equal(expectedReceipt))
})
It("has an empty 'To' field when transaction creates a new contract", func() {
gethTransaction := types.NewContractCreation(
uint64(10000),
big.NewInt(10),
uint64(5000),
big.NewInt(3),
[]byte("1234"),
)
gethReceipt := &types.Receipt{
CumulativeGasUsed: uint64(1),
GasUsed: uint64(1),
TxHash: gethTransaction.Hash(),
ContractAddress: common.HexToAddress("0x1023342345"),
}
client := fakes.NewMockEthClient()
client.SetTransactionReceipts([]*types.Receipt{gethReceipt})
block := types.NewBlock(
&types.Header{},
[]*types.Transaction{gethTransaction},
[]*types.Header{},
[]*types.Receipt{gethReceipt},
)
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred())
coreTransaction := coreBlock.Transactions[0]
Expect(coreTransaction.To).To(Equal(""))
coreReceipt := coreTransaction.Receipt
expectedReceipt, err := vulcCommon.ToCoreReceipt(gethReceipt)
Expect(err).ToNot(HaveOccurred())
Expect(coreReceipt).To(Equal(expectedReceipt))
})
})
Describe("transaction error handling", func() {
var gethTransaction *types.Transaction
var gethReceipt *types.Receipt
var header *types.Header
var block *types.Block
BeforeEach(func() {
log.SetOutput(ioutil.Discard)
gethTransaction = types.NewTransaction(
uint64(0),
common.Address{},
big.NewInt(0),
uint64(0),
big.NewInt(0),
[]byte{},
)
gethReceipt = &types.Receipt{}
header = &types.Header{}
block = types.NewBlock(
header,
[]*types.Transaction{gethTransaction},
[]*types.Header{},
[]*types.Receipt{gethReceipt},
)
})
AfterEach(func() {
defer log.SetOutput(os.Stdout)
})
It("returns an error when transaction sender call fails", func() {
client := fakes.NewMockEthClient()
client.SetTransactionSenderErr(fakes.FakeError)
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
_, err := blockConverter.ToCoreBlock(block)
Expect(err).To(MatchError(fakes.FakeError))
})
It("returns an error when transaction receipt call fails", func() {
client := fakes.NewMockEthClient()
client.SetTransactionReceiptErr(fakes.FakeError)
transactionConverter := rpc.NewRPCTransactionConverter(client)
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
_, err := blockConverter.ToCoreBlock(block)
Expect(err).To(MatchError(fakes.FakeError))
})
})
})
@@ -1,29 +0,0 @@
// 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 common_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestCommon(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Common Suite")
}
@@ -1,57 +0,0 @@
// 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 common
import (
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
func ToFullSyncLogs(gethLogs []types.Log) []core.FullSyncLog {
var logs []core.FullSyncLog
for _, log := range gethLogs {
log := ToCoreLog(log)
logs = append(logs, log)
}
return logs
}
func makeTopics(topics []common.Hash) core.Topics {
var hexTopics core.Topics
for i, topic := range topics {
hexTopics[i] = topic.Hex()
}
return hexTopics
}
func ToCoreLog(gethLog types.Log) core.FullSyncLog {
topics := gethLog.Topics
hexTopics := makeTopics(topics)
return core.FullSyncLog{
Address: strings.ToLower(gethLog.Address.Hex()),
BlockNumber: int64(gethLog.BlockNumber),
Topics: hexTopics,
TxHash: gethLog.TxHash.Hex(),
Index: int64(gethLog.Index),
Data: hexutil.Encode(gethLog.Data),
}
}
@@ -1,110 +0,0 @@
// 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 common_test
import (
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
var _ = Describe("Conversion of GethLog to core.FullSyncLog", func() {
It("converts geth log to internal log format", func() {
gethLog := types.Log{
Address: common.HexToAddress("0x448a5065aeBB8E423F0896E6c5D525C040f59af3"),
BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
BlockNumber: 2019236,
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000001a055690d9db80000"),
Index: 2,
TxIndex: 3,
TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
Topics: []common.Hash{
common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
},
}
expected := core.FullSyncLog{
Address: strings.ToLower(gethLog.Address.Hex()),
BlockNumber: int64(gethLog.BlockNumber),
Data: hexutil.Encode(gethLog.Data),
TxHash: gethLog.TxHash.Hex(),
Index: 2,
Topics: core.Topics{
gethLog.Topics[0].Hex(),
gethLog.Topics[1].Hex(),
},
}
coreLog := vulcCommon.ToCoreLog(gethLog)
Expect(coreLog.Address).To(Equal(expected.Address))
Expect(coreLog.BlockNumber).To(Equal(expected.BlockNumber))
Expect(coreLog.Data).To(Equal(expected.Data))
Expect(coreLog.Index).To(Equal(expected.Index))
Expect(coreLog.Topics[0]).To(Equal(expected.Topics[0]))
Expect(coreLog.Topics[1]).To(Equal(expected.Topics[1]))
Expect(coreLog.TxHash).To(Equal(expected.TxHash))
})
It("converts geth log array to array of internal logs", func() {
gethLogOne := types.Log{
Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
BlockNumber: 2019236,
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000001a055690d9db80000"),
Index: 2,
TxIndex: 3,
TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
Topics: []common.Hash{
common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
},
}
gethLogTwo := types.Log{
Address: common.HexToAddress("0x123"),
BlockHash: common.HexToHash("0x576"),
BlockNumber: 2019236,
Data: hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000001"),
Index: 3,
TxIndex: 4,
TxHash: common.HexToHash("0x134"),
Topics: []common.Hash{
common.HexToHash("0xaaa"),
common.HexToHash("0xbbb"),
},
}
expectedOne := vulcCommon.ToCoreLog(gethLogOne)
expectedTwo := vulcCommon.ToCoreLog(gethLogTwo)
coreLogs := vulcCommon.ToFullSyncLogs([]types.Log{gethLogOne, gethLogTwo})
Expect(len(coreLogs)).To(Equal(2))
Expect(coreLogs[0]).To(Equal(expectedOne))
Expect(coreLogs[1]).To(Equal(expectedTwo))
})
})
@@ -1,42 +0,0 @@
// 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 common
import (
"encoding/json"
"strconv"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
type HeaderConverter struct{}
func (converter HeaderConverter) Convert(gethHeader *types.Header, blockHash string) core.Header {
rawHeader, err := json.Marshal(gethHeader)
if err != nil {
panic(err)
}
coreHeader := core.Header{
Hash: blockHash,
BlockNumber: gethHeader.Number.Int64(),
Raw: rawHeader,
Timestamp: strconv.FormatUint(gethHeader.Time, 10),
}
return coreHeader
}
@@ -1,65 +0,0 @@
// 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 common_test
import (
"encoding/json"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
common2 "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
"github.com/vulcanize/vulcanizedb/pkg/eth/fakes"
)
var _ = Describe("Block header converter", func() {
It("converts geth header to core header", func() {
gethHeader := &types.Header{
Difficulty: big.NewInt(1),
Number: big.NewInt(2),
ParentHash: common.HexToHash("0xParent"),
ReceiptHash: common.HexToHash("0xReceipt"),
Root: common.HexToHash("0xRoot"),
Time: uint64(123456789),
TxHash: common.HexToHash("0xTransaction"),
UncleHash: common.HexToHash("0xUncle"),
}
converter := common2.HeaderConverter{}
hash := fakes.FakeHash.String()
coreHeader := converter.Convert(gethHeader, hash)
Expect(coreHeader.BlockNumber).To(Equal(gethHeader.Number.Int64()))
Expect(coreHeader.Hash).To(Equal(hash))
Expect(coreHeader.Timestamp).To(Equal(strconv.FormatUint(gethHeader.Time, 10)))
})
It("includes raw bytes for header as JSON", func() {
gethHeader := types.Header{Number: big.NewInt(123)}
converter := common2.HeaderConverter{}
coreHeader := converter.Convert(&gethHeader, fakes.FakeHash.String())
expectedJSON, err := json.Marshal(gethHeader)
Expect(err).NotTo(HaveOccurred())
Expect(coreHeader.Raw).To(Equal(expectedJSON))
})
})
@@ -1,89 +0,0 @@
// 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 common
import (
"bytes"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
func ToCoreReceipts(gethReceipts types.Receipts) ([]core.Receipt, error) {
var coreReceipts []core.Receipt
for _, receipt := range gethReceipts {
coreReceipt, err := ToCoreReceipt(receipt)
if err != nil {
return nil, err
}
coreReceipts = append(coreReceipts, coreReceipt)
}
return coreReceipts, nil
}
func ToCoreReceipt(gethReceipt *types.Receipt) (core.Receipt, error) {
bloom := hexutil.Encode(gethReceipt.Bloom.Bytes())
var postState string
var status int
postState, status = postStateOrStatus(gethReceipt)
logs := dereferenceLogs(gethReceipt)
contractAddress := setContractAddress(gethReceipt)
rlpBuff := new(bytes.Buffer)
receiptForStorage := types.ReceiptForStorage(*gethReceipt)
err := receiptForStorage.EncodeRLP(rlpBuff)
if err != nil {
return core.Receipt{}, err
}
return core.Receipt{
Bloom: bloom,
ContractAddress: contractAddress,
CumulativeGasUsed: gethReceipt.CumulativeGasUsed,
GasUsed: gethReceipt.GasUsed,
Logs: logs,
StateRoot: postState,
TxHash: gethReceipt.TxHash.Hex(),
Status: status,
Rlp: rlpBuff.Bytes(),
}, nil
}
func setContractAddress(gethReceipt *types.Receipt) string {
emptyAddress := common.Address{}.Bytes()
if bytes.Equal(gethReceipt.ContractAddress.Bytes(), emptyAddress) {
return ""
}
return gethReceipt.ContractAddress.Hex()
}
func dereferenceLogs(gethReceipt *types.Receipt) []core.FullSyncLog {
logs := []core.FullSyncLog{}
for _, log := range gethReceipt.Logs {
logs = append(logs, ToCoreLog(*log))
}
return logs
}
func postStateOrStatus(gethReceipts *types.Receipt) (string, int) {
if len(gethReceipts.PostState) != 0 {
return hexutil.Encode(gethReceipts.PostState), -99
}
return "", int(gethReceipts.Status)
}
@@ -1,115 +0,0 @@
// 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 common_test
import (
"bytes"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
var _ = Describe("Conversion of GethReceipt to core.Receipt", func() {
It(`converts geth receipt to internal receipt format (pre Byzantium has post-transaction stateroot)`, func() {
receipt := types.Receipt{
Bloom: types.BytesToBloom(hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
ContractAddress: common.Address{},
CumulativeGasUsed: uint64(25000),
GasUsed: uint64(21000),
Logs: []*types.Log{},
PostState: hexutil.MustDecode("0x88abf7e73128227370aa7baa3dd4e18d0af70e92ef1f9ef426942fbe2dddb733"),
TxHash: common.HexToHash("0x97d99bc7729211111a21b12c933c949d4f31684f1d6954ff477d0477538ff017"),
}
rlpBuff := new(bytes.Buffer)
receiptForStorage := types.ReceiptForStorage(receipt)
err := receiptForStorage.EncodeRLP(rlpBuff)
Expect(err).ToNot(HaveOccurred())
expected := core.Receipt{
Bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
ContractAddress: "",
CumulativeGasUsed: 25000,
GasUsed: 21000,
Logs: []core.FullSyncLog{},
StateRoot: "0x88abf7e73128227370aa7baa3dd4e18d0af70e92ef1f9ef426942fbe2dddb733",
Status: -99,
TxHash: receipt.TxHash.Hex(),
Rlp: rlpBuff.Bytes(),
}
coreReceipt, err := vulcCommon.ToCoreReceipt(&receipt)
Expect(err).ToNot(HaveOccurred())
Expect(coreReceipt.Bloom).To(Equal(expected.Bloom))
Expect(coreReceipt.ContractAddress).To(Equal(expected.ContractAddress))
Expect(coreReceipt.CumulativeGasUsed).To(Equal(expected.CumulativeGasUsed))
Expect(coreReceipt.GasUsed).To(Equal(expected.GasUsed))
Expect(coreReceipt.Logs).To(Equal(expected.Logs))
Expect(coreReceipt.StateRoot).To(Equal(expected.StateRoot))
Expect(coreReceipt.Status).To(Equal(expected.Status))
Expect(coreReceipt.TxHash).To(Equal(expected.TxHash))
Expect(bytes.Compare(coreReceipt.Rlp, expected.Rlp)).To(Equal(0))
})
It("converts geth receipt to internal receipt format (post Byzantium has status", func() {
receipt := types.Receipt{
Bloom: types.BytesToBloom(hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
ContractAddress: common.HexToAddress("x0123"),
CumulativeGasUsed: uint64(7996119),
GasUsed: uint64(21000),
Logs: []*types.Log{},
Status: uint64(1),
TxHash: common.HexToHash("0xe340558980f89d5f86045ac11e5cc34e4bcec20f9f1e2a427aa39d87114e8223"),
}
rlpBuff := new(bytes.Buffer)
receiptForStorage := types.ReceiptForStorage(receipt)
err := receiptForStorage.EncodeRLP(rlpBuff)
Expect(err).ToNot(HaveOccurred())
expected := core.Receipt{
Bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
ContractAddress: receipt.ContractAddress.Hex(),
CumulativeGasUsed: 7996119,
GasUsed: 21000,
Logs: []core.FullSyncLog{},
StateRoot: "",
Status: 1,
TxHash: receipt.TxHash.Hex(),
Rlp: rlpBuff.Bytes(),
}
coreReceipt, err := vulcCommon.ToCoreReceipt(&receipt)
Expect(err).ToNot(HaveOccurred())
Expect(coreReceipt.Bloom).To(Equal(expected.Bloom))
Expect(coreReceipt.ContractAddress).To(Equal(""))
Expect(coreReceipt.CumulativeGasUsed).To(Equal(expected.CumulativeGasUsed))
Expect(coreReceipt.GasUsed).To(Equal(expected.GasUsed))
Expect(coreReceipt.Logs).To(Equal(expected.Logs))
Expect(coreReceipt.StateRoot).To(Equal(expected.StateRoot))
Expect(coreReceipt.Status).To(Equal(expected.Status))
Expect(coreReceipt.TxHash).To(Equal(expected.TxHash))
Expect(bytes.Compare(coreReceipt.Rlp, expected.Rlp)).To(Equal(0))
})
})
@@ -1,27 +0,0 @@
// 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 common
import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/eth/core"
)
type TransactionConverter interface {
ConvertBlockTransactionsToCore(gethBlock *types.Block) ([]core.TransactionModel, error)
ConvertRPCTransactionsToModels(transactions []core.RPCTransaction) ([]core.TransactionModel, error)
}
-13
View File
@@ -1,13 +0,0 @@
package rpc_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestRpc(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Rpc Suite")
}

Some files were not shown because too many files have changed in this diff Show More