refactor omni code; rename omniWatcher => contractWatcher; use config instead of excessive number of CLI flags

This commit is contained in:
Ian Norden 2019-03-11 11:19:18 -05:00
parent 84b65caedd
commit 8174ce4aee
20 changed files with 1278 additions and 706 deletions

123
cmd/contractWatcher.go Normal file
View File

@ -0,0 +1,123 @@
// VulcanizeDB
// Copyright © 2018 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 cmd
import (
"fmt"
"github.com/vulcanize/vulcanizedb/pkg/config"
"time"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
st "github.com/vulcanize/vulcanizedb/libraries/shared/transformer"
ft "github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer"
lt "github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer"
"github.com/vulcanize/vulcanizedb/utils"
)
// contractWatcherCmd represents the contractWatcher command
var contractWatcherCmd = &cobra.Command{
Use: "contractWatcher",
Short: "Watches events at the provided contract address using fully synced vDB",
Long: `Uses input contract address and event filters to watch events
Expects an ethereum node to be running
Expects an archival node synced into vulcanizeDB
Requires a .toml config file:
[database]
name = "vulcanize_public"
hostname = "localhost"
port = 5432
[client]
ipcPath = "https://mainnet.infura.io/J5Vd2fRtGsw0zZ0Ov3BL"
[contract]
network = ""
addresses = [
"contractAddress1",
"contractAddress2"
]
[contract.contractAddress1]
abi = 'ABI for contract 1'
startingBlock = 982463
[contract.contractAddress2]
abi = 'ABI for contract 2'
events = [
"event1",
"event2"
]
eventArgs = [
"arg1",
"arg2"
]
methods = [
"method1",
"method2"
]
methodArgs = [
"arg1",
"arg2"
]
startingBlock = 4448566
piping = true
`,
Run: func(cmd *cobra.Command, args []string) {
contractWatcher()
},
}
var (
mode string
)
func contractWatcher() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
blockChain := getBlockChain()
db := utils.LoadPostgres(databaseConfig, blockChain.Node())
var t st.GenericTransformer
con := config.ContractConfig{}
con.PrepConfig()
switch mode {
case "light":
t = lt.NewTransformer(con, blockChain, &db)
case "full":
t = ft.NewTransformer(con, blockChain, &db)
default:
log.Fatal("Invalid mode")
}
err := t.Init()
if err != nil {
log.Fatal(fmt.Sprintf("Failed to initialized transformer\r\nerr: %v\r\n", err))
}
for range ticker.C {
t.Execute()
}
}
func init() {
rootCmd.AddCommand(contractWatcherCmd)
contractWatcherCmd.Flags().StringVarP(&mode, "mode", "o", "light", "'light' or 'full' mode to work with either light synced or fully synced vDB (default is light)")
}

View File

@ -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 cmd
import (
"fmt"
"log"
"time"
"github.com/spf13/cobra"
ft "github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer"
lt "github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer"
st "github.com/vulcanize/vulcanizedb/pkg/omni/shared/transformer"
"github.com/vulcanize/vulcanizedb/utils"
)
// omniWatcherCmd represents the omniWatcher command
var omniWatcherCmd = &cobra.Command{
Use: "omniWatcher",
Short: "Watches events at the provided contract address using fully synced vDB",
Long: `Uses input contract address and event filters to watch events
Expects an ethereum node to be running
Expects an archival node synced into vulcanizeDB
Requires a .toml config file:
[database]
name = "vulcanize_public"
hostname = "localhost"
port = 5432
[client]
ipcPath = "/Users/user/Library/Ethereum/geth.ipc"
`,
Run: func(cmd *cobra.Command, args []string) {
omniWatcher()
},
}
var (
network string
contractAddress string
contractAddresses []string
contractEvents []string
contractMethods []string
eventArgs []string
methodArgs []string
methodPiping bool
mode string
)
func omniWatcher() {
if contractAddress == "" && len(contractAddresses) == 0 {
log.Fatal("Contract address required")
}
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
blockChain := getBlockChain()
db := utils.LoadPostgres(databaseConfig, blockChain.Node())
var t st.Transformer
switch mode {
case "light":
t = lt.NewTransformer(network, blockChain, &db)
case "full":
t = ft.NewTransformer(network, blockChain, &db)
default:
log.Fatal("Invalid mode")
}
contractAddresses = append(contractAddresses, contractAddress)
for _, addr := range contractAddresses {
t.SetEvents(addr, contractEvents)
t.SetMethods(addr, contractMethods)
t.SetEventArgs(addr, eventArgs)
t.SetMethodArgs(addr, methodArgs)
t.SetPiping(addr, methodPiping)
t.SetStartingBlock(addr, startingBlockNumber)
}
err := t.Init()
if err != nil {
log.Fatal(fmt.Sprintf("Failed to initialized transformer\r\nerr: %v\r\n", err))
}
for range ticker.C {
t.Execute()
}
}
func init() {
rootCmd.AddCommand(omniWatcherCmd)
omniWatcherCmd.Flags().StringVarP(&mode, "mode", "o", "light", "'light' or 'full' mode to work with either light synced or fully synced vDB (default is light)")
omniWatcherCmd.Flags().StringVarP(&contractAddress, "contract-address", "a", "", "Single address to generate watchers for")
omniWatcherCmd.Flags().StringArrayVarP(&contractAddresses, "contract-addresses", "l", []string{}, "list of addresses to use; warning: watcher targets the same events and methods for each address")
omniWatcherCmd.Flags().StringArrayVarP(&contractEvents, "events", "e", []string{}, "Subset of events to watch; by default all events are watched")
omniWatcherCmd.Flags().StringArrayVarP(&contractMethods, "methods", "m", nil, "Subset of methods to poll; by default no methods are polled")
omniWatcherCmd.Flags().StringArrayVarP(&eventArgs, "event-args", "f", []string{}, "Argument values to filter event logs for; will only persist event logs that emit at least one of the value specified")
omniWatcherCmd.Flags().StringArrayVarP(&methodArgs, "method-args", "g", []string{}, "Argument values to limit methods to; will only call methods with emitted values that were specified here")
omniWatcherCmd.Flags().StringVarP(&network, "network", "n", "", `Network the contract is deployed on; options: "ropsten", "kovan", and "rinkeby"; default is mainnet"`)
omniWatcherCmd.Flags().Int64VarP(&startingBlockNumber, "starting-block-number", "s", 0, "Block to begin watching- default is first block the contract exists")
omniWatcherCmd.Flags().BoolVarP(&methodPiping, "piping", "p", false, "Turn on method output piping: methods listed first will be polled first and their output used as input to subsequent methods")
}

View File

@ -2,6 +2,7 @@ package integration
import ( import (
"fmt" "fmt"
"github.com/vulcanize/vulcanizedb/pkg/config"
"math/rand" "math/rand"
"strings" "strings"
"time" "time"
@ -41,8 +42,7 @@ var _ = Describe("Omni full transformer", func() {
It("Initializes transformer's contract objects", func() { It("Initializes transformer's contract objects", func() {
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1) blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1)
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2) blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2)
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -64,9 +64,7 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("Transforms watched contract data into custom repositories", func() { It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
t.SetMethods(constants.TusdContractAddress, nil)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -85,9 +83,12 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() { It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) testConf = mocks.TusdConfig
t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -119,9 +120,12 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("Polls given methods using generated token holder address", func() { It("Polls given methods using generated token holder address", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) testConf = mocks.TusdConfig
t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -145,9 +149,7 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("Fails if initialization has not been done", func() { It("Fails if initialization has not been done", func() {
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
t.SetMethods(constants.TusdContractAddress, nil)
err = t.Execute() err = t.Execute()
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
@ -161,9 +163,7 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("Transforms watched contract data into custom repositories", func() { It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.ENSConfig, blockChain, db)
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"})
t.SetMethods(constants.EnsContractAddress, nil)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -183,9 +183,12 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() { It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -210,9 +213,12 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("Polls given methods using generated token holder address", func() { It("Polls given methods using generated token holder address", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -235,10 +241,12 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("It does not perist events if they do not pass the emitted arg filter", func() { It("It does not perist events if they do not pass the emitted arg filter", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, nil) testConf.EventArgs = map[string][]string{
t.SetEventArgs(constants.EnsContractAddress, []string{"fake_filter_value"}) ensAddr: {"fake_filter_value"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -252,10 +260,15 @@ var _ = Describe("Omni full transformer", func() {
}) })
It("If a method arg filter is applied, only those arguments are used in polling", func() { It("If a method arg filter is applied, only those arguments are used in polling", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.MethodArgs = map[string][]string{
t.SetMethodArgs(constants.EnsContractAddress, []string{"0x0000000000000000000000000000000000000000000000000000c02aaa39b223"}) ensAddr: {"0x0000000000000000000000000000000000000000000000000000c02aaa39b223"},
}
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())

View File

@ -2,6 +2,7 @@ package integration
import ( import (
"fmt" "fmt"
"github.com/vulcanize/vulcanizedb/pkg/config"
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -39,8 +40,7 @@ var _ = Describe("Omnit light transformer", func() {
It("Initializes transformer's contract objects", func() { It("Initializes transformer's contract objects", func() {
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1) headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3) headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -70,9 +70,7 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Transforms watched contract data into custom repositories", func() { It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
t.SetMethods(constants.TusdContractAddress, nil)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()
@ -89,9 +87,12 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() { It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) testConf = mocks.TusdConfig
t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[tusdAddr] c, ok := t.Contracts[tusdAddr]
@ -131,9 +132,12 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Polls given methods using generated token holder address", func() { It("Polls given methods using generated token holder address", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) testConf = mocks.TusdConfig
t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()
@ -150,9 +154,7 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Fails if initialization has not been done", func() { It("Fails if initialization has not been done", func() {
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
t.SetMethods(constants.TusdContractAddress, nil)
err = t.Execute() err = t.Execute()
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
}) })
@ -173,9 +175,7 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Transforms watched contract data into custom repositories", func() { It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.ENSConfig, blockChain, db)
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"})
t.SetMethods(constants.EnsContractAddress, nil)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()
@ -192,9 +192,12 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() { It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[ensAddr] c, ok := t.Contracts[ensAddr]
@ -218,9 +221,12 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Polls given method using list of collected hashes", func() { It("Polls given method using list of collected hashes", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()
@ -242,10 +248,12 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("It does not persist events if they do not pass the emitted arg filter", func() { It("It does not persist events if they do not pass the emitted arg filter", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, nil) testConf.EventArgs = map[string][]string{
t.SetEventArgs(constants.EnsContractAddress, []string{"fake_filter_value"}) ensAddr: {"fake_filter_value"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()
@ -257,10 +265,15 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("If a method arg filter is applied, only those arguments are used in polling", func() { It("If a method arg filter is applied, only those arguments are used in polling", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.MethodArgs = map[string][]string{
t.SetMethodArgs(constants.EnsContractAddress, []string{"0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"}) ensAddr: {"0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"},
}
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()
@ -302,11 +315,7 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Transforms watched contract data into custom repositories", func() { It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer(mocks.ENSandTusdConfig, blockChain, db)
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"})
t.SetMethods(constants.EnsContractAddress, nil)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
t.SetMethods(constants.TusdContractAddress, nil)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()
@ -332,11 +341,13 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Keeps track of contract-related hashes and addresses while transforming event data if they need to be used for later method polling", func() { It("Keeps track of contract-related hashes and addresses while transforming event data if they need to be used for later method polling", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSandTusdConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.Methods = map[string][]string{
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) ensAddr: {"owner"},
t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
ens, ok := t.Contracts[ensAddr] ens, ok := t.Contracts[ensAddr]
@ -376,11 +387,13 @@ var _ = Describe("Omnit light transformer", func() {
}) })
It("Polls given methods for each contract, using list of collected values", func() { It("Polls given methods for each contract, using list of collected values", func() {
t := transformer.NewTransformer("", blockChain, db) var testConf config.ContractConfig
t.SetEvents(constants.EnsContractAddress, []string{"NewOwner"}) testConf = mocks.ENSandTusdConfig
t.SetMethods(constants.EnsContractAddress, []string{"owner"}) testConf.Methods = map[string][]string{
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) ensAddr: {"owner"},
t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"}) tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = t.Execute() err = t.Execute()

177
pkg/config/contract.go Normal file
View File

@ -0,0 +1,177 @@
// VulcanizeDB
// Copyright © 2018 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 (
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"strings"
)
// 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 (oc *ContractConfig) PrepConfig() {
addrs := viper.GetStringSlice("contract.addresses")
oc.Network = viper.GetString("contract.network")
oc.Addresses = make(map[string]bool, len(addrs))
oc.Abis = make(map[string]string, len(addrs))
oc.Methods = make(map[string][]string, len(addrs))
oc.EventArgs = make(map[string][]string, len(addrs))
oc.MethodArgs = make(map[string][]string, len(addrs))
oc.EventArgs = make(map[string][]string, len(addrs))
oc.StartingBlocks = make(map[string]int64, len(addrs))
oc.Piping = make(map[string]bool, len(addrs))
// De-dupe addresses
for _, addr := range addrs {
oc.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
abi, abiOK := transformer["abi"]
if !abiOK || abi == nil {
log.Fatal(addr, "transformer config is missing `abi` value")
}
abiRef, abiOK := abi.(string)
if !abiOK {
log.Fatal(addr, "transformer `events` not of type []string")
}
oc.Abis[strings.ToLower(addr)] = abiRef
// Get and check events
events, eventsOK := transformer["events"]
if !eventsOK || events == nil {
log.Fatal(addr, "transformer config is missing `events` value")
}
eventsRef, eventsOK := events.([]string)
if !eventsOK {
log.Fatal(addr, "transformer `events` not of type []string")
}
if eventsRef == nil {
eventsRef = []string{}
}
oc.Events[strings.ToLower(addr)] = eventsRef
// Get and check methods
methods, methodsOK := transformer["methods"]
if !methodsOK || methods == nil {
log.Fatal(addr, "transformer config is missing `methods` value")
}
methodsRef, methodsOK := methods.([]string)
if !methodsOK {
log.Fatal(addr, "transformer `methods` not of type []string")
}
if methodsRef == nil {
methodsRef = []string{}
}
oc.Methods[strings.ToLower(addr)] = methodsRef
// Get and check eventArgs
eventArgs, eventArgsOK := transformer["eventArgs"]
if !eventArgsOK || eventArgs == nil {
log.Fatal(addr, "transformer config is missing `eventArgs` value")
}
eventArgsRef, eventArgsOK := eventArgs.([]string)
if !eventArgsOK {
log.Fatal(addr, "transformer `eventArgs` not of type []string")
}
if eventArgsRef == nil {
eventArgsRef = []string{}
}
oc.EventArgs[strings.ToLower(addr)] = eventArgsRef
// Get and check methodArgs
methodArgs, methodArgsOK := transformer["methodArgs"]
if !methodArgsOK || methodArgs == nil {
log.Fatal(addr, "transformer config is missing `methodArgs` value")
}
methodArgsRef, methodArgsOK := methodArgs.([]string)
if !methodArgsOK {
log.Fatal(addr, "transformer `methodArgs` not of type []string")
}
if methodArgsRef == nil {
methodArgsRef = []string{}
}
oc.MethodArgs[strings.ToLower(addr)] = methodArgsRef
// Get and check startingBlock
start, startOK := transformer["startingBlock"]
if !startOK || start == nil {
log.Fatal(addr, "transformer config is missing `startingBlock` value")
}
startRef, startOK := start.(int64)
if !startOK {
log.Fatal(addr, "transformer `startingBlock` not of type int")
}
oc.StartingBlocks[strings.ToLower(addr)] = startRef
// Get pipping
pipe, pipeOK := transformer["pipping"]
if !pipeOK || pipe == nil {
log.Fatal(addr, "transformer config is missing `pipping` value")
}
pipeRef, pipeOK := pipe.(bool)
if !pipeOK {
log.Fatal(addr, "transformer `piping` not of type bool")
}
oc.Piping[strings.ToLower(addr)] = pipeRef
}
}

View File

@ -55,10 +55,10 @@ func (c *converter) Update(info *contract.Contract) {
// Convert the given watched event log into a types.Log for the given event // Convert the given watched event log into a types.Log for the given event
func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (*types.Log, error) { func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (*types.Log, error) {
boundContract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil) contract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil)
values := make(map[string]interface{}) values := make(map[string]interface{})
log := helpers.ConvertToLog(watchedEvent) log := helpers.ConvertToLog(watchedEvent)
err := boundContract.UnpackLogIntoMap(values, event.Name, log) err := contract.UnpackLogIntoMap(values, event.Name, log)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -18,8 +18,8 @@ package transformer
import ( import (
"errors" "errors"
"strings"
"github.com/vulcanize/vulcanizedb/pkg/config"
"github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/datastore" "github.com/vulcanize/vulcanizedb/pkg/datastore"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
@ -34,7 +34,7 @@ import (
) )
// Requires a fully synced vDB and a running eth node (or infura) // Requires a fully synced vDB and a running eth node (or infura)
type Transformer struct { type transformer struct {
// Database interfaces // Database interfaces
datastore.FilterRepository // Log filters repo; accepts filters generated by Contract.GenerateFilters() datastore.FilterRepository // Log filters repo; accepts filters generated by Contract.GenerateFilters()
datastore.WatchedEventRepository // Watched event log views, created by the log filters datastore.WatchedEventRepository // Watched event log views, created by the log filters
@ -48,52 +48,25 @@ type Transformer struct {
converter.Converter // Converts watched event logs into custom log converter.Converter // Converts watched event logs into custom log
poller.Poller // Polls methods using contract's token holder addresses and persists them using method datastore poller.Poller // Polls methods using contract's token holder addresses and persists them using method datastore
// Ethereum network name; default "" is mainnet // Store contract configuration information
Network string Config config.ContractConfig
// Store contract info as mapping to contract address // Store contract info as mapping to contract address
Contracts map[string]*contract.Contract Contracts map[string]*contract.Contract
// Targeted subset of events/methods
// Stored as map sof contract address to events/method names of interest
WatchedEvents map[string][]string // Default/empty event list means all are watched
WantedMethods map[string][]string // Default/empty method list means none are polled
// Starting block for contracts
ContractStart map[string]int64
// Lists of addresses to filter event or method data
// before persisting; if empty no filter is applied
EventArgs map[string][]string
MethodArgs map[string][]string
// Whether or not to create a list of emitted address or hashes for the contract in postgres
CreateAddrList map[string]bool
CreateHashList map[string]bool
// Method piping on/off for a contract
Piping map[string]bool
} }
// Transformer takes in config for blockchain, database, and network id // Transformer takes in config for blockchain, database, and network id
func NewTransformer(network string, BC core.BlockChain, DB *postgres.DB) *Transformer { func NewTransformer(con config.ContractConfig, BC core.BlockChain, DB *postgres.DB) *transformer {
return &Transformer{ return &transformer{
Poller: poller.NewPoller(BC, DB, types.FullSync), Poller: poller.NewPoller(BC, DB, types.FullSync),
Parser: parser.NewParser(network), Parser: parser.NewParser(con.Network),
BlockRetriever: retriever.NewBlockRetriever(DB), BlockRetriever: retriever.NewBlockRetriever(DB),
Converter: converter.NewConverter(&contract.Contract{}), Converter: converter.NewConverter(&contract.Contract{}),
Contracts: map[string]*contract.Contract{}, Contracts: map[string]*contract.Contract{},
WatchedEventRepository: repositories.WatchedEventRepository{DB: DB}, WatchedEventRepository: repositories.WatchedEventRepository{DB: DB},
FilterRepository: repositories.FilterRepository{DB: DB}, FilterRepository: repositories.FilterRepository{DB: DB},
EventRepository: repository.NewEventRepository(DB, types.FullSync), EventRepository: repository.NewEventRepository(DB, types.FullSync),
WatchedEvents: map[string][]string{}, Config: con,
WantedMethods: map[string][]string{},
ContractStart: map[string]int64{},
EventArgs: map[string][]string{},
MethodArgs: map[string][]string{},
CreateAddrList: map[string]bool{},
CreateHashList: map[string]bool{},
Piping: map[string]bool{},
} }
} }
@ -101,59 +74,66 @@ func NewTransformer(network string, BC core.BlockChain, DB *postgres.DB) *Transf
// Loops over all of the addr => filter sets // Loops over all of the addr => filter sets
// Uses parser to pull event info from abi // Uses parser to pull event info from abi
// Use this info to generate event filters // Use this info to generate event filters
func (transformer *Transformer) Init() error { func (tr *transformer) Init() error {
for contractAddr, subset := range transformer.WatchedEvents { for contractAddr := range tr.Config.Addresses {
// Get Abi // Configure Abi
err := transformer.Parser.Parse(contractAddr) 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
err := tr.Parser.Parse(contractAddr)
if err != nil { if err != nil {
return err return err
} }
} else {
// If we have an abi from the config, load that into the parser
err := tr.Parser.ParseAbiStr(tr.Config.Abis[contractAddr])
if err != nil {
return err
}
}
// Get first block and most recent block number in the header repo // Get first block and most recent block number in the header repo
firstBlock, err := transformer.BlockRetriever.RetrieveFirstBlock(contractAddr) firstBlock, err := tr.BlockRetriever.RetrieveFirstBlock(contractAddr)
if err != nil { if err != nil {
return err return err
} }
lastBlock, err := transformer.BlockRetriever.RetrieveMostRecentBlock() lastBlock, err := tr.BlockRetriever.RetrieveMostRecentBlock()
if err != nil { if err != nil {
return err return err
} }
// Set to specified range if it falls within the bounds // Set to specified range if it falls within the bounds
if firstBlock < transformer.ContractStart[contractAddr] { if firstBlock < tr.Config.StartingBlocks[contractAddr] {
firstBlock = transformer.ContractStart[contractAddr] firstBlock = tr.Config.StartingBlocks[contractAddr]
} }
// Get contract name if it has one // Get contract name if it has one
var name = new(string) var name = new(string)
transformer.Poller.FetchContractData(transformer.Abi(), contractAddr, "name", nil, name, lastBlock) tr.FetchContractData(tr.Abi(), contractAddr, "name", nil, &name, lastBlock)
// Remove any potential accidental duplicate inputs in arg filter values // Remove any potential accidental duplicate inputs in arg filter values
eventArgs := map[string]bool{} eventArgs := map[string]bool{}
for _, arg := range transformer.EventArgs[contractAddr] { for _, arg := range tr.Config.EventArgs[contractAddr] {
eventArgs[arg] = true eventArgs[arg] = true
} }
methodArgs := map[string]bool{} methodArgs := map[string]bool{}
for _, arg := range transformer.MethodArgs[contractAddr] { for _, arg := range tr.Config.MethodArgs[contractAddr] {
methodArgs[arg] = true methodArgs[arg] = true
} }
// Aggregate info into contract object // Aggregate info into contract object
info := contract.Contract{ info := contract.Contract{
Name: *name, Name: *name,
Network: transformer.Network, Network: tr.Config.Network,
Address: contractAddr, Address: contractAddr,
Abi: transformer.Parser.Abi(), Abi: tr.Parser.Abi(),
ParsedAbi: transformer.Parser.ParsedAbi(), ParsedAbi: tr.Parser.ParsedAbi(),
StartingBlock: firstBlock, StartingBlock: firstBlock,
LastBlock: lastBlock, LastBlock: lastBlock,
Events: transformer.Parser.GetEvents(subset), Events: tr.Parser.GetEvents(tr.Config.Events[contractAddr]),
Methods: transformer.Parser.GetSelectMethods(transformer.WantedMethods[contractAddr]), Methods: tr.Parser.GetSelectMethods(tr.Config.Methods[contractAddr]),
FilterArgs: eventArgs, FilterArgs: eventArgs,
MethodArgs: methodArgs, MethodArgs: methodArgs,
CreateAddrList: transformer.CreateAddrList[contractAddr], Piping: tr.Config.Piping[contractAddr],
CreateHashList: transformer.CreateHashList[contractAddr],
Piping: transformer.Piping[contractAddr],
}.Init() }.Init()
// Use info to create filters // Use info to create filters
@ -164,14 +144,14 @@ func (transformer *Transformer) Init() error {
// Iterate over filters and push them to the repo using filter repository interface // Iterate over filters and push them to the repo using filter repository interface
for _, filter := range info.Filters { for _, filter := range info.Filters {
err = transformer.FilterRepository.CreateFilter(filter) err = tr.CreateFilter(filter)
if err != nil { if err != nil {
return err return err
} }
} }
// Store contract info for further processing // Store contract info for further processing
transformer.Contracts[contractAddr] = info tr.Contracts[contractAddr] = info
} }
return nil return nil
@ -182,18 +162,18 @@ func (transformer *Transformer) Init() error {
// Uses converter to convert logs into custom log type // Uses converter to convert logs into custom log type
// Persists converted logs into custuom postgres tables // Persists converted logs into custuom postgres tables
// Calls selected methods, using token holder address generated during event log conversion // Calls selected methods, using token holder address generated during event log conversion
func (transformer Transformer) Execute() error { func (tr *transformer) Execute() error {
if len(transformer.Contracts) == 0 { if len(tr.Contracts) == 0 {
return errors.New("error: transformer has no initialized contracts to work with") return errors.New("error: transformer has no initialized contracts to work with")
} }
// Iterate through all internal contracts // Iterate through all internal contracts
for _, con := range transformer.Contracts { for _, con := range tr.Contracts {
// Update converter with current contract // Update converter with current contract
transformer.Update(con) tr.Update(con)
// Iterate through contract filters and get watched event logs // Iterate through contract filters and get watched event logs
for eventSig, filter := range con.Filters { for eventSig, filter := range con.Filters {
watchedEvents, err := transformer.GetWatchedEvents(filter.Name) watchedEvents, err := tr.GetWatchedEvents(filter.Name)
if err != nil { if err != nil {
return err return err
} }
@ -201,7 +181,7 @@ func (transformer Transformer) Execute() error {
// Iterate over watched event logs // Iterate over watched event logs
for _, we := range watchedEvents { for _, we := range watchedEvents {
// Convert them to our custom log type // Convert them to our custom log type
cstm, err := transformer.Converter.Convert(*we, con.Events[eventSig]) cstm, err := tr.Converter.Convert(*we, con.Events[eventSig])
if err != nil { if err != nil {
return err return err
} }
@ -211,7 +191,7 @@ func (transformer Transformer) Execute() error {
// If log is not empty, immediately persist in repo // If log is not empty, immediately persist in repo
// Run this in seperate goroutine? // Run this in seperate goroutine?
err = transformer.PersistLogs([]types.Log{*cstm}, con.Events[eventSig], con.Address, con.Name) err = tr.PersistLogs([]types.Log{*cstm}, con.Events[eventSig], con.Address, con.Name)
if err != nil { if err != nil {
return err return err
} }
@ -222,7 +202,7 @@ func (transformer Transformer) Execute() error {
// poller polls select contract methods // poller polls select contract methods
// and persists the results into custom pg tables // and persists the results into custom pg tables
// Run this in seperate goroutine? // Run this in seperate goroutine?
if err := transformer.PollContract(*con); err != nil { if err := tr.PollContract(*con); err != nil {
return err return err
} }
} }
@ -230,42 +210,6 @@ func (transformer Transformer) Execute() error {
return nil return nil
} }
// Used to set which contract addresses and which of their events to watch func (tr *transformer) GetConfig() config.ContractConfig {
func (transformer *Transformer) SetEvents(contractAddr string, filterSet []string) { return tr.Config
transformer.WatchedEvents[strings.ToLower(contractAddr)] = filterSet
}
// Used to set subset of account addresses to watch events for
func (transformer *Transformer) SetEventArgs(contractAddr string, filterSet []string) {
transformer.EventArgs[strings.ToLower(contractAddr)] = filterSet
}
// Used to set which contract addresses and which of their methods to call
func (transformer *Transformer) SetMethods(contractAddr string, filterSet []string) {
transformer.WantedMethods[strings.ToLower(contractAddr)] = filterSet
}
// Used to set subset of account addresses to poll methods on
func (transformer *Transformer) SetMethodArgs(contractAddr string, filterSet []string) {
transformer.MethodArgs[strings.ToLower(contractAddr)] = filterSet
}
// Used to set the block range to watch for a given address
func (transformer *Transformer) SetStartingBlock(contractAddr string, start int64) {
transformer.ContractStart[strings.ToLower(contractAddr)] = start
}
// Used to set whether or not to persist an account address list
func (transformer *Transformer) SetCreateAddrList(contractAddr string, on bool) {
transformer.CreateAddrList[strings.ToLower(contractAddr)] = on
}
// Used to set whether or not to persist an hash list
func (transformer *Transformer) SetCreateHashList(contractAddr string, on bool) {
transformer.CreateHashList[strings.ToLower(contractAddr)] = on
}
// Used to turn method piping on for a contract
func (transformer *Transformer) SetPiping(contractAddr string, on bool) {
transformer.Piping[strings.ToLower(contractAddr)] = on
} }

View File

@ -17,160 +17,307 @@
package transformer_test package transformer_test
import ( import (
"fmt"
"github.com/vulcanize/vulcanizedb/pkg/config"
"math/rand" "math/rand"
"strings"
"time" "time"
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/fakes" "github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/omni/full/retriever" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer" "github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
) )
var _ = Describe("Transformer", func() { var _ = Describe("Transformer", func() {
var fakeAddress = "0x1234567890abcdef" var db *postgres.DB
var err error
var blockChain core.BlockChain
var blockRepository repositories.BlockRepository
var ensAddr = strings.ToLower(constants.EnsContractAddress)
var tusdAddr = strings.ToLower(constants.TusdContractAddress)
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
Describe("SetEvents", func() { BeforeEach(func() {
It("Sets which events to watch from the given contract address", func() { db, blockChain = test_helpers.SetupDBandBC()
watchedEvents := []string{"Transfer", "Mint"} blockRepository = *repositories.NewBlockRepository(db)
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetEvents(fakeAddress, watchedEvents)
Expect(t.WatchedEvents[fakeAddress]).To(Equal(watchedEvents))
})
}) })
Describe("SetEventAddrs", func() { AfterEach(func() {
It("Sets which account addresses to watch events for", func() { test_helpers.TearDown(db)
eventAddrs := []string{"test1", "test2"}
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetEventArgs(fakeAddress, eventAddrs)
Expect(t.EventArgs[fakeAddress]).To(Equal(eventAddrs))
})
})
Describe("SetMethods", func() {
It("Sets which methods to poll at the given contract address", func() {
watchedMethods := []string{"balanceOf", "totalSupply"}
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetMethods(fakeAddress, watchedMethods)
Expect(t.WantedMethods[fakeAddress]).To(Equal(watchedMethods))
})
})
Describe("SetMethodAddrs", func() {
It("Sets which account addresses to poll methods against", func() {
methodAddrs := []string{"test1", "test2"}
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetMethodArgs(fakeAddress, methodAddrs)
Expect(t.MethodArgs[fakeAddress]).To(Equal(methodAddrs))
})
})
Describe("SetStartingBlock", func() {
It("Sets the block range that the contract should be watched within", func() {
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetStartingBlock(fakeAddress, 11)
Expect(t.ContractStart[fakeAddress]).To(Equal(int64(11)))
})
})
Describe("SetCreateAddrList", func() {
It("Sets the block range that the contract should be watched within", func() {
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetCreateAddrList(fakeAddress, true)
Expect(t.CreateAddrList[fakeAddress]).To(Equal(true))
})
})
Describe("SetCreateHashList", func() {
It("Sets the block range that the contract should be watched within", func() {
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetCreateHashList(fakeAddress, true)
Expect(t.CreateHashList[fakeAddress]).To(Equal(true))
})
}) })
Describe("Init", func() { Describe("Init", func() {
It("Initializes transformer's contract objects", func() { It("Initializes transformer's contract objects", func() {
blockRetriever := &fakes.MockFullBlockRetriever{} blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1)
firstBlock := int64(1) blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2)
mostRecentBlock := int64(2) t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
blockRetriever.FirstBlock = firstBlock err = t.Init()
blockRetriever.MostRecentBlock = mostRecentBlock
parsr := &fakes.MockParser{}
fakeAbi := "fake_abi"
eventName := "Transfer"
event := types.Event{}
parsr.AbiToReturn = fakeAbi
parsr.EventName = eventName
parsr.Event = event
pollr := &fakes.MockPoller{}
fakeContractName := "fake_contract_name"
pollr.ContractName = fakeContractName
t := getTransformer(blockRetriever, parsr, pollr)
t.SetEvents(fakeAddress, []string{"Transfer"})
err := t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[fakeAddress] c, ok := t.Contracts[tusdAddr]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(c.StartingBlock).To(Equal(firstBlock)) Expect(c.StartingBlock).To(Equal(int64(6194633)))
Expect(c.LastBlock).To(Equal(mostRecentBlock)) Expect(c.LastBlock).To(Equal(int64(6194634)))
Expect(c.Abi).To(Equal(fakeAbi)) Expect(c.Abi).To(Equal(constants.TusdAbiString))
Expect(c.Name).To(Equal(fakeContractName)) Expect(c.Name).To(Equal("TrueUSD"))
Expect(c.Address).To(Equal(fakeAddress)) Expect(c.Address).To(Equal(tusdAddr))
}) })
It("Fails to initialize if first and most recent blocks cannot be fetched from vDB", func() { It("Fails to initialize if first and most recent blocks cannot be fetched from vDB", func() {
blockRetriever := &fakes.MockFullBlockRetriever{} t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
blockRetriever.FirstBlockErr = fakes.FakeError err = t.Init()
t := getTransformer(blockRetriever, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetEvents(fakeAddress, []string{"Transfer"})
err := t.Init()
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
}) })
It("Does nothing if watched events are unset", func() { It("Does nothing if watched events are unset", func() {
t := getTransformer(&fakes.MockFullBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{}) blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1)
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2)
var testConf config.ContractConfig
testConf = mocks.TusdConfig
testConf.Events = nil
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).To(HaveOccurred())
err := t.Init() _, ok := t.Contracts[tusdAddr]
Expect(err).ToNot(HaveOccurred())
_, ok := t.Contracts[fakeAddress]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
}) })
}) })
Describe("Execute", func() {
BeforeEach(func() {
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1)
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2)
}) })
func getTransformer(blockRetriever retriever.BlockRetriever, parsr parser.Parser, pollr poller.Poller) transformer.Transformer { It("Transforms watched contract data into custom repositories", func() {
return transformer.Transformer{ t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
FilterRepository: &fakes.MockFilterRepository{}, err = t.Init()
Parser: parsr, Expect(err).ToNot(HaveOccurred())
BlockRetriever: blockRetriever,
Poller: pollr, err = t.Execute()
Contracts: map[string]*contract.Contract{}, Expect(err).ToNot(HaveOccurred())
WatchedEvents: map[string][]string{},
WantedMethods: map[string][]string{}, log := test_helpers.TransferLog{}
ContractStart: map[string]int64{}, err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.transfer_event WHERE block = 6194634", tusdAddr)).StructScan(&log)
EventArgs: map[string][]string{},
MethodArgs: map[string][]string{}, // We don't know vulcID, so compare individual fields instead of complete structures
CreateAddrList: map[string]bool{}, Expect(log.Tx).To(Equal("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee"))
CreateHashList: map[string]bool{}, Expect(log.Block).To(Equal(int64(6194634)))
Expect(log.From).To(Equal("0x000000000000000000000000000000000000Af21"))
Expect(log.To).To(Equal("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"))
Expect(log.Value).To(Equal("1097077688018008265106216665536940668749033598146"))
})
It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() {
var testConf config.ContractConfig
testConf = mocks.TusdConfig
testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
} }
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[tusdAddr]
Expect(ok).To(Equal(true))
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
b, ok := c.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
_, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843b1234567890")]
Expect(ok).To(Equal(false))
_, ok = c.EmittedAddrs[common.HexToAddress("0x")]
Expect(ok).To(Equal(false))
_, ok = c.EmittedAddrs[""]
Expect(ok).To(Equal(false))
_, ok = c.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")]
Expect(ok).To(Equal(false))
})
It("Polls given methods using generated token holder address", func() {
var testConf config.ContractConfig
testConf = mocks.TusdConfig
testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
} }
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
res := test_helpers.BalanceOf{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x000000000000000000000000000000000000Af21' AND block = '6194634'", tusdAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Balance).To(Equal("0"))
Expect(res.TokenName).To(Equal("TrueUSD"))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843bCE061BA391' AND block = '6194634'", tusdAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Balance).To(Equal("0"))
Expect(res.TokenName).To(Equal("TrueUSD"))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6194634'", tusdAddr)).StructScan(&res)
Expect(err).To(HaveOccurred())
})
It("Fails if initialization has not been done", func() {
t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
err = t.Execute()
Expect(err).To(HaveOccurred())
})
})
Describe("Execute- against ENS registry contract", func() {
BeforeEach(func() {
blockRepository.CreateOrUpdateBlock(mocks.NewOwnerBlock1)
blockRepository.CreateOrUpdateBlock(mocks.NewOwnerBlock2)
})
It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer(mocks.ENSConfig, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
log := test_helpers.NewOwnerLog{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.newowner_event", ensAddr)).StructScan(&log)
// We don't know vulcID, so compare individual fields instead of complete structures
Expect(log.Tx).To(Equal("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654bbb"))
Expect(log.Block).To(Equal(int64(6194635)))
Expect(log.Node).To(Equal("0x0000000000000000000000000000000000000000000000000000c02aaa39b223"))
Expect(log.Label).To(Equal("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"))
Expect(log.Owner).To(Equal("0x000000000000000000000000000000000000Af21"))
})
It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[ensAddr]
Expect(ok).To(Equal(true))
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
Expect(len(c.EmittedHashes)).To(Equal(3))
b, ok := c.EmittedHashes[common.HexToHash("0x0000000000000000000000000000000000000000000000000000c02aaa39b223")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = c.EmittedHashes[common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
// Doesn't keep track of address since it wouldn't be used in calling the 'owner' method
_, ok = c.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(false))
})
It("Polls given methods using generated token holder address", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
res := test_helpers.Owner{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x0000000000000000000000000000000000000000000000000000c02aaa39b223' AND block = '6194636'", ensAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(res.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391' AND block = '6194636'", ensAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(res.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x9THIS110dcc444fIS242510c09bbAbe21aFAKEcacNODE82f7b843HASH61ba391' AND block = '6194636'", ensAddr)).StructScan(&res)
Expect(err).To(HaveOccurred())
})
It("It does not perist events if they do not pass the emitted arg filter", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.EventArgs = map[string][]string{
ensAddr: {"fake_filter_value"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
log := test_helpers.LightNewOwnerLog{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.newowner_event", ensAddr)).StructScan(&log)
Expect(err).To(HaveOccurred())
})
It("If a method arg filter is applied, only those arguments are used in polling", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
testConf.MethodArgs = map[string][]string{
ensAddr: {"0x0000000000000000000000000000000000000000000000000000c02aaa39b223"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
res := test_helpers.Owner{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x0000000000000000000000000000000000000000000000000000c02aaa39b223' AND block = '6194636'", ensAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(res.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.owner_method WHERE node_ = '0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391' AND block = '6194636'", ensAddr)).StructScan(&res)
Expect(err).To(HaveOccurred())
})
})
})

View File

@ -54,7 +54,7 @@ func (c *converter) Update(info *contract.Contract) {
// Convert the given watched event log into a types.Log for the given event // 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) { 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) contract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil)
returnLogs := make([]types.Log, 0, len(logs)) returnLogs := make([]types.Log, 0, len(logs))
for _, log := range logs { for _, log := range logs {
values := make(map[string]interface{}) values := make(map[string]interface{})
@ -63,7 +63,7 @@ func (c *converter) Convert(logs []gethTypes.Log, event types.Event, headerID in
values[field.Name] = i values[field.Name] = i
} }
err := boundContract.UnpackLogIntoMap(values, event.Name, log) err := contract.UnpackLogIntoMap(values, event.Name, log)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -133,7 +133,7 @@ func (c *converter) Convert(logs []gethTypes.Log, event types.Event, headerID in
// Convert the given watched event logs into types.Logs; returns a map of event names to a slice of their converted logs // Convert 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) { 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) contract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil)
eventsToLogs := make(map[string][]types.Log) eventsToLogs := make(map[string][]types.Log)
for _, event := range events { for _, event := range events {
eventsToLogs[event.Name] = make([]types.Log, 0, len(logs)) eventsToLogs[event.Name] = make([]types.Log, 0, len(logs))
@ -142,7 +142,7 @@ func (c *converter) ConvertBatch(logs []gethTypes.Log, events map[string]types.E
// If the log is of this event type, process it as such // If the log is of this event type, process it as such
if event.Sig() == log.Topics[0] { if event.Sig() == log.Topics[0] {
values := make(map[string]interface{}) values := make(map[string]interface{})
err := boundContract.UnpackLogIntoMap(values, event.Name, log) err := contract.UnpackLogIntoMap(values, event.Name, log)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -17,8 +17,8 @@
package repository package repository
import ( import (
"database/sql"
"fmt" "fmt"
"github.com/jmoiron/sqlx"
"github.com/hashicorp/golang-lru" "github.com/hashicorp/golang-lru"
@ -125,7 +125,7 @@ func (r *headerRepository) MarkHeaderCheckedForAll(headerID int64, ids []string)
} }
func (r *headerRepository) MarkHeadersCheckedForAll(headers []core.Header, ids []string) error { func (r *headerRepository) MarkHeadersCheckedForAll(headers []core.Header, ids []string) error {
tx, err := r.db.Beginx() tx, err := r.db.Begin()
if err != nil { if err != nil {
return err return err
} }
@ -250,7 +250,7 @@ func (r *headerRepository) CheckCache(key string) (interface{}, bool) {
return r.columns.Get(key) return r.columns.Get(key)
} }
func MarkHeaderCheckedInTransaction(headerID int64, tx *sqlx.Tx, eventID string) error { func MarkHeaderCheckedInTransaction(headerID int64, tx *sql.Tx, eventID string) error {
_, err := tx.Exec(`INSERT INTO public.checked_headers (header_id, `+eventID+`) _, err := tx.Exec(`INSERT INTO public.checked_headers (header_id, `+eventID+`)
VALUES ($1, $2) VALUES ($1, $2)
ON CONFLICT (header_id) DO ON CONFLICT (header_id) DO

View File

@ -27,7 +27,7 @@ import (
func TestRetriever(t *testing.T) { func TestRetriever(t *testing.T) {
RegisterFailHandler(Fail) RegisterFailHandler(Fail)
RunSpecs(t, "Light Block Number Retriever Suite Test") RunSpecs(t, "Light BLock Number Retriever Suite Test")
} }
var _ = BeforeSuite(func() { var _ = BeforeSuite(func() {

View File

@ -18,6 +18,7 @@ package transformer
import ( import (
"errors" "errors"
"github.com/vulcanize/vulcanizedb/pkg/config"
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -37,7 +38,7 @@ import (
) )
// Requires a light synced vDB (headers) and a running eth node (or infura) // Requires a light synced vDB (headers) and a running eth node (or infura)
type Transformer struct { type transformer struct {
// Database interfaces // Database interfaces
srep.EventRepository // Holds transformed watched event log data srep.EventRepository // Holds transformed watched event log data
repository.HeaderRepository // Interface for interaction with header repositories repository.HeaderRepository // Interface for interaction with header repositories
@ -51,32 +52,12 @@ type Transformer struct {
converter.Converter // Converts watched event logs into custom log converter.Converter // Converts watched event logs into custom log
poller.Poller // Polls methods using arguments collected from events and persists them using a method datastore poller.Poller // Polls methods using arguments collected from events and persists them using a method datastore
// Ethereum network name; default "" is mainnet // Store contract configuration information
Network string Config config.ContractConfig
// Store contract info as mapping to contract address // Store contract info as mapping to contract address
Contracts map[string]*contract.Contract Contracts map[string]*contract.Contract
// Targeted subset of events/methods
// Stored as maps of contract address to events/method names of interest
WatchedEvents map[string][]string // Default/empty event list means all are watched
WantedMethods map[string][]string // Default/empty method list means none are polled
// Starting block number for each contract
ContractStart map[string]int64
// Lists of argument values to filter event or
// method data with; if empty no filter is applied
EventArgs map[string][]string
MethodArgs map[string][]string
// Whether or not to create a list of emitted address or hashes for the contract in postgres
CreateAddrList map[string]bool
CreateHashList map[string]bool
// Method piping on/off for a contract
Piping map[string]bool
// Internally configured transformer variables // Internally configured transformer variables
contractAddresses []string // Holds all contract addresses, for batch fetching of logs 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 sortedEventIds map[string][]string // Map to sort event column ids by contract, for post fetch processing and persisting of logs
@ -93,26 +74,18 @@ type Transformer struct {
// 4. Execute // 4. Execute
// Transformer takes in config for blockchain, database, and network id // Transformer takes in config for blockchain, database, and network id
func NewTransformer(network string, bc core.BlockChain, db *postgres.DB) *Transformer { func NewTransformer(con config.ContractConfig, bc core.BlockChain, db *postgres.DB) *transformer {
return &Transformer{ return &transformer{
Poller: poller.NewPoller(bc, db, types.LightSync), Poller: poller.NewPoller(bc, db, types.LightSync),
Fetcher: fetcher.NewFetcher(bc), Fetcher: fetcher.NewFetcher(bc),
Parser: parser.NewParser(network), Parser: parser.NewParser(con.Network),
HeaderRepository: repository.NewHeaderRepository(db), HeaderRepository: repository.NewHeaderRepository(db),
BlockRetriever: retriever.NewBlockRetriever(db), BlockRetriever: retriever.NewBlockRetriever(db),
Converter: converter.NewConverter(&contract.Contract{}), Converter: converter.NewConverter(&contract.Contract{}),
Contracts: map[string]*contract.Contract{}, Contracts: map[string]*contract.Contract{},
EventRepository: srep.NewEventRepository(db, types.LightSync), EventRepository: srep.NewEventRepository(db, types.LightSync),
WatchedEvents: map[string][]string{}, Config: con,
WantedMethods: map[string][]string{},
ContractStart: map[string]int64{},
EventArgs: map[string][]string{},
MethodArgs: map[string][]string{},
CreateAddrList: map[string]bool{},
CreateHashList: map[string]bool{},
Piping: map[string]bool{},
Network: network,
} }
} }
@ -120,7 +93,7 @@ func NewTransformer(network string, bc core.BlockChain, db *postgres.DB) *Transf
// Loops over all of the addr => filter sets // Loops over all of the addr => filter sets
// Uses parser to pull event info from abi // Uses parser to pull event info from abi
// Use this info to generate event filters // Use this info to generate event filters
func (tr *Transformer) Init() error { func (tr *transformer) Init() error {
// Initialize internally configured transformer settings // Initialize internally configured transformer settings
tr.contractAddresses = make([]string, 0) // Holds all contract addresses, for batch fetching of logs 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.sortedEventIds = make(map[string][]string) // Map to sort event column ids by contract, for post fetch processing and persisting of logs
@ -130,12 +103,21 @@ func (tr *Transformer) Init() error {
tr.start = 100000000000 // Hold the lowest starting block and the highest ending block tr.start = 100000000000 // Hold the lowest starting block and the highest ending block
// Iterate through all internal contract addresses // Iterate through all internal contract addresses
for contractAddr, subset := range tr.WatchedEvents { for contractAddr := range tr.Config.Addresses {
// Get Abi // 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
err := tr.Parser.Parse(contractAddr) err := tr.Parser.Parse(contractAddr)
if err != nil { if err != nil {
return err return err
} }
} else {
// If we have an abi from the config, load that into the parser
err := tr.Parser.ParseAbiStr(tr.Config.Abis[contractAddr])
if err != nil {
return err
}
}
// Get first block and most recent block number in the header repo // Get first block and most recent block number in the header repo
firstBlock, err := tr.BlockRetriever.RetrieveFirstBlock() firstBlock, err := tr.BlockRetriever.RetrieveFirstBlock()
@ -148,40 +130,38 @@ func (tr *Transformer) Init() error {
} }
// Set to specified range if it falls within the bounds // Set to specified range if it falls within the bounds
if firstBlock < tr.ContractStart[contractAddr] { if firstBlock < tr.Config.StartingBlocks[contractAddr] {
firstBlock = tr.ContractStart[contractAddr] firstBlock = tr.Config.StartingBlocks[contractAddr]
} }
// Get contract name if it has one // Get contract name if it has one
var name = new(string) var name = new(string)
tr.Poller.FetchContractData(tr.Abi(), contractAddr, "name", nil, name, lastBlock) tr.FetchContractData(tr.Abi(), contractAddr, "name", nil, &name, lastBlock)
// Remove any potential accidental duplicate inputs in arg filter values // Remove any potential accidental duplicate inputs
eventArgs := map[string]bool{} eventArgs := map[string]bool{}
for _, arg := range tr.EventArgs[contractAddr] { for _, arg := range tr.Config.EventArgs[contractAddr] {
eventArgs[arg] = true eventArgs[arg] = true
} }
methodArgs := map[string]bool{} methodArgs := map[string]bool{}
for _, arg := range tr.MethodArgs[contractAddr] { for _, arg := range tr.Config.MethodArgs[contractAddr] {
methodArgs[arg] = true methodArgs[arg] = true
} }
// Aggregate info into contract object and store for execution // Aggregate info into contract object and store for execution
con := contract.Contract{ con := contract.Contract{
Name: *name, Name: *name,
Network: tr.Network, Network: tr.Config.Network,
Address: contractAddr, Address: contractAddr,
Abi: tr.Parser.Abi(), Abi: tr.Parser.Abi(),
ParsedAbi: tr.Parser.ParsedAbi(), ParsedAbi: tr.Parser.ParsedAbi(),
StartingBlock: firstBlock, StartingBlock: firstBlock,
LastBlock: -1, LastBlock: -1,
Events: tr.Parser.GetEvents(subset), Events: tr.Parser.GetEvents(tr.Config.Events[contractAddr]),
Methods: tr.Parser.GetSelectMethods(tr.WantedMethods[contractAddr]), Methods: tr.Parser.GetSelectMethods(tr.Config.Methods[contractAddr]),
FilterArgs: eventArgs, FilterArgs: eventArgs,
MethodArgs: methodArgs, MethodArgs: methodArgs,
CreateAddrList: tr.CreateAddrList[contractAddr], Piping: tr.Config.Piping[contractAddr],
CreateHashList: tr.CreateHashList[contractAddr],
Piping: tr.Piping[contractAddr],
}.Init() }.Init()
tr.Contracts[contractAddr] = con tr.Contracts[contractAddr] = con
tr.contractAddresses = append(tr.contractAddresses, con.Address) tr.contractAddresses = append(tr.contractAddresses, con.Address)
@ -221,7 +201,7 @@ func (tr *Transformer) Init() error {
return nil return nil
} }
func (tr *Transformer) Execute() error { func (tr *transformer) Execute() error {
if len(tr.Contracts) == 0 { if len(tr.Contracts) == 0 {
return errors.New("error: transformer has no initialized contracts") return errors.New("error: transformer has no initialized contracts")
} }
@ -311,7 +291,7 @@ func (tr *Transformer) Execute() error {
} }
// Used to poll contract methods at a given header // Used to poll contract methods at a given header
func (tr *Transformer) methodPolling(header core.Header, sortedMethodIds map[string][]string) error { func (tr *transformer) methodPolling(header core.Header, sortedMethodIds map[string][]string) error {
for _, con := range tr.Contracts { for _, con := range tr.Contracts {
// Skip method polling processes if no methods are specified // Skip method polling processes if no methods are specified
// Also don't try to poll methods below this contract's specified starting block // Also don't try to poll methods below this contract's specified starting block
@ -335,42 +315,6 @@ func (tr *Transformer) methodPolling(header core.Header, sortedMethodIds map[str
return nil return nil
} }
// Used to set which contract addresses and which of their events to watch func (tr *transformer) GetConfig() config.ContractConfig {
func (tr *Transformer) SetEvents(contractAddr string, filterSet []string) { return tr.Config
tr.WatchedEvents[strings.ToLower(contractAddr)] = filterSet
}
// Used to set subset of account addresses to watch events for
func (tr *Transformer) SetEventArgs(contractAddr string, filterSet []string) {
tr.EventArgs[strings.ToLower(contractAddr)] = filterSet
}
// Used to set which contract addresses and which of their methods to call
func (tr *Transformer) SetMethods(contractAddr string, filterSet []string) {
tr.WantedMethods[strings.ToLower(contractAddr)] = filterSet
}
// Used to set subset of account addresses to poll methods on
func (tr *Transformer) SetMethodArgs(contractAddr string, filterSet []string) {
tr.MethodArgs[strings.ToLower(contractAddr)] = filterSet
}
// Used to set the block range to watch for a given address
func (tr *Transformer) SetStartingBlock(contractAddr string, start int64) {
tr.ContractStart[strings.ToLower(contractAddr)] = start
}
// Used to set whether or not to persist an account address list
func (tr *Transformer) SetCreateAddrList(contractAddr string, on bool) {
tr.CreateAddrList[strings.ToLower(contractAddr)] = on
}
// Used to set whether or not to persist an hash list
func (tr *Transformer) SetCreateHashList(contractAddr string, on bool) {
tr.CreateHashList[strings.ToLower(contractAddr)] = on
}
// Used to turn method piping on for a contract
func (tr *Transformer) SetPiping(contractAddr string, on bool) {
tr.Piping[strings.ToLower(contractAddr)] = on
} }

View File

@ -17,150 +17,446 @@
package transformer_test package transformer_test
import ( import (
"fmt"
"github.com/vulcanize/vulcanizedb/pkg/config"
"strings"
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/vulcanize/vulcanizedb/pkg/fakes" "github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/omni/light/retriever" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
"github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer" "github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks"
) )
var _ = Describe("Transformer", func() { var _ = Describe("Transformer", func() {
var fakeAddress = "0x1234567890abcdef" var db *postgres.DB
var err error
var blockChain core.BlockChain
var headerRepository repositories.HeaderRepository
var headerID, headerID2 int64
var ensAddr = strings.ToLower(constants.EnsContractAddress)
var tusdAddr = strings.ToLower(constants.TusdContractAddress)
Describe("SetEvents", func() { BeforeEach(func() {
It("Sets which events to watch from the given contract address", func() { db, blockChain = test_helpers.SetupDBandBC()
watchedEvents := []string{"Transfer", "Mint"} headerRepository = repositories.NewHeaderRepository(db)
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetEvents(fakeAddress, watchedEvents)
Expect(t.WatchedEvents[fakeAddress]).To(Equal(watchedEvents))
})
}) })
Describe("SetEventAddrs", func() { AfterEach(func() {
It("Sets which account addresses to watch events for", func() { test_helpers.TearDown(db)
eventAddrs := []string{"test1", "test2"}
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetEventArgs(fakeAddress, eventAddrs)
Expect(t.EventArgs[fakeAddress]).To(Equal(eventAddrs))
})
})
Describe("SetMethods", func() {
It("Sets which methods to poll at the given contract address", func() {
watchedMethods := []string{"balanceOf", "totalSupply"}
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetMethods(fakeAddress, watchedMethods)
Expect(t.WantedMethods[fakeAddress]).To(Equal(watchedMethods))
})
})
Describe("SetMethodAddrs", func() {
It("Sets which account addresses to poll methods against", func() {
methodAddrs := []string{"test1", "test2"}
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetMethodArgs(fakeAddress, methodAddrs)
Expect(t.MethodArgs[fakeAddress]).To(Equal(methodAddrs))
})
})
Describe("SetStartingBlock", func() {
It("Sets the block range that the contract should be watched within", func() {
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetStartingBlock(fakeAddress, 11)
Expect(t.ContractStart[fakeAddress]).To(Equal(int64(11)))
})
})
Describe("SetCreateAddrList", func() {
It("Sets the block range that the contract should be watched within", func() {
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetCreateAddrList(fakeAddress, true)
Expect(t.CreateAddrList[fakeAddress]).To(Equal(true))
})
})
Describe("SetCreateHashList", func() {
It("Sets the block range that the contract should be watched within", func() {
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetCreateHashList(fakeAddress, true)
Expect(t.CreateHashList[fakeAddress]).To(Equal(true))
})
}) })
Describe("Init", func() { Describe("Init", func() {
It("Initializes transformer's contract objects", func() { It("Initializes transformer's contract objects", func() {
blockRetriever := &fakes.MockLightBlockRetriever{} headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
firstBlock := int64(1) headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
blockRetriever.FirstBlock = firstBlock t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
err = t.Init()
parsr := &fakes.MockParser{}
fakeAbi := "fake_abi"
parsr.AbiToReturn = fakeAbi
pollr := &fakes.MockPoller{}
fakeContractName := "fake_contract_name"
pollr.ContractName = fakeContractName
t := getFakeTransformer(blockRetriever, parsr, pollr)
t.SetEvents(fakeAddress, []string{"Transfer"})
err := t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[fakeAddress] c, ok := t.Contracts[tusdAddr]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(c.StartingBlock).To(Equal(firstBlock)) Expect(c.StartingBlock).To(Equal(int64(6194632)))
Expect(c.LastBlock).To(Equal(int64(-1))) Expect(c.LastBlock).To(Equal(int64(-1)))
Expect(c.Abi).To(Equal(fakeAbi)) Expect(c.Abi).To(Equal(constants.TusdAbiString))
Expect(c.Name).To(Equal(fakeContractName)) Expect(c.Name).To(Equal("TrueUSD"))
Expect(c.Address).To(Equal(fakeAddress)) Expect(c.Address).To(Equal(tusdAddr))
}) })
It("Fails to initialize if first and most recent block numbers cannot be fetched from vDB headers table", func() { It("Fails to initialize if first and most recent block numbers cannot be fetched from vDB headers table", func() {
blockRetriever := &fakes.MockLightBlockRetriever{} t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
blockRetriever.FirstBlockErr = fakes.FakeError err = t.Init()
t := getFakeTransformer(blockRetriever, &fakes.MockParser{}, &fakes.MockPoller{})
t.SetEvents(fakeAddress, []string{"Transfer"})
err := t.Init()
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(fakes.FakeError))
}) })
It("Does nothing if watched events are unset", func() { It("Does nothing if nothing if no addresses are configured", func() {
t := getFakeTransformer(&fakes.MockLightBlockRetriever{}, &fakes.MockParser{}, &fakes.MockPoller{}) headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
err := t.Init() var testConf config.ContractConfig
testConf = mocks.TusdConfig
testConf.Addresses = nil
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
_, ok := t.Contracts[fakeAddress] _, ok := t.Contracts[tusdAddr]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
}) })
}) })
Describe("Execute- against TrueUSD contract", func() {
BeforeEach(func() {
header1, err := blockChain.GetHeaderByNumber(6791668)
Expect(err).ToNot(HaveOccurred())
header2, err := blockChain.GetHeaderByNumber(6791669)
Expect(err).ToNot(HaveOccurred())
header3, err := blockChain.GetHeaderByNumber(6791670)
Expect(err).ToNot(HaveOccurred())
headerRepository.CreateOrUpdateHeader(header1)
headerID, err = headerRepository.CreateOrUpdateHeader(header2)
Expect(err).ToNot(HaveOccurred())
headerRepository.CreateOrUpdateHeader(header3)
}) })
func getFakeTransformer(blockRetriever retriever.BlockRetriever, parsr parser.Parser, pollr poller.Poller) transformer.Transformer { It("Transforms watched contract data into custom repositories", func() {
return transformer.Transformer{ t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
Parser: parsr, err = t.Init()
BlockRetriever: blockRetriever, Expect(err).ToNot(HaveOccurred())
Poller: pollr, err = t.Execute()
HeaderRepository: &fakes.MockLightHeaderRepository{}, Expect(err).ToNot(HaveOccurred())
Contracts: map[string]*contract.Contract{},
WatchedEvents: map[string][]string{}, log := test_helpers.LightTransferLog{}
WantedMethods: map[string][]string{}, err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.transfer_event", tusdAddr)).StructScan(&log)
ContractStart: map[string]int64{}, Expect(err).ToNot(HaveOccurred())
EventArgs: map[string][]string{}, // We don't know vulcID, so compare individual fields instead of complete structures
MethodArgs: map[string][]string{}, Expect(log.HeaderID).To(Equal(headerID))
CreateAddrList: map[string]bool{}, Expect(log.From).To(Equal("0x1062a747393198f70F71ec65A582423Dba7E5Ab3"))
CreateHashList: map[string]bool{}, Expect(log.To).To(Equal("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0"))
Expect(log.Value).To(Equal("9998940000000000000000"))
})
It("Keeps track of contract-related addresses while transforming event data if they need to be used for later method polling", func() {
var testConf config.ContractConfig
testConf = mocks.TusdConfig
testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
} }
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[tusdAddr]
Expect(ok).To(Equal(true))
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
Expect(len(c.EmittedAddrs)).To(Equal(4))
Expect(len(c.EmittedHashes)).To(Equal(0))
b, ok := c.EmittedAddrs[common.HexToAddress("0x1062a747393198f70F71ec65A582423Dba7E5Ab3")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = c.EmittedAddrs[common.HexToAddress("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = c.EmittedAddrs[common.HexToAddress("0x571A326f5B15E16917dC17761c340c1ec5d06f6d")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = c.EmittedAddrs[common.HexToAddress("0xFBb1b73C4f0BDa4f67dcA266ce6Ef42f520fBB98")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
_, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843b1234567890")]
Expect(ok).To(Equal(false))
_, ok = c.EmittedAddrs[common.HexToAddress("0x")]
Expect(ok).To(Equal(false))
_, ok = c.EmittedAddrs[""]
Expect(ok).To(Equal(false))
_, ok = c.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")]
Expect(ok).To(Equal(false))
})
It("Polls given methods using generated token holder address", func() {
var testConf config.ContractConfig
testConf = mocks.TusdConfig
testConf.Methods = map[string][]string{
tusdAddr: {"balanceOf"},
} }
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
res := test_helpers.BalanceOf{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x1062a747393198f70F71ec65A582423Dba7E5Ab3' AND block = '6791669'", tusdAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Balance).To(Equal("55849938025000000000000"))
Expect(res.TokenName).To(Equal("TrueUSD"))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843b1234567890' AND block = '6791669'", tusdAddr)).StructScan(&res)
Expect(err).To(HaveOccurred())
})
It("Fails if initialization has not been done", func() {
t := transformer.NewTransformer(mocks.TusdConfig, blockChain, db)
err = t.Execute()
Expect(err).To(HaveOccurred())
})
})
Describe("Execute- against ENS registry contract", func() {
BeforeEach(func() {
header1, err := blockChain.GetHeaderByNumber(6885695)
Expect(err).ToNot(HaveOccurred())
header2, err := blockChain.GetHeaderByNumber(6885696)
Expect(err).ToNot(HaveOccurred())
header3, err := blockChain.GetHeaderByNumber(6885697)
Expect(err).ToNot(HaveOccurred())
headerRepository.CreateOrUpdateHeader(header1)
headerID, err = headerRepository.CreateOrUpdateHeader(header2)
Expect(err).ToNot(HaveOccurred())
headerRepository.CreateOrUpdateHeader(header3)
})
It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer(mocks.ENSConfig, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
log := test_helpers.LightNewOwnerLog{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.newowner_event", ensAddr)).StructScan(&log)
Expect(err).ToNot(HaveOccurred())
// We don't know vulcID, so compare individual fields instead of complete structures
Expect(log.HeaderID).To(Equal(headerID))
Expect(log.Node).To(Equal("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"))
Expect(log.Label).To(Equal("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047"))
Expect(log.Owner).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef"))
})
It("Keeps track of contract-related hashes while transforming event data if they need to be used for later method polling", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
c, ok := t.Contracts[ensAddr]
Expect(ok).To(Equal(true))
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
Expect(len(c.EmittedHashes)).To(Equal(2))
Expect(len(c.EmittedAddrs)).To(Equal(0))
b, ok := c.EmittedHashes[common.HexToHash("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = c.EmittedHashes[common.HexToHash("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
// Doesn't keep track of address since it wouldn't be used in calling the 'owner' method
_, ok = c.EmittedAddrs[common.HexToAddress("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef")]
Expect(ok).To(Equal(false))
})
It("Polls given method using list of collected hashes", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
res := test_helpers.Owner{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885696'", ensAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef"))
Expect(res.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047' AND block = '6885696'", ensAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Address).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(res.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x9THIS110dcc444fIS242510c09bbAbe21aFAKEcacNODE82f7b843HASH61ba391' AND block = '6885696'", ensAddr)).StructScan(&res)
Expect(err).To(HaveOccurred())
})
It("It does not persist events if they do not pass the emitted arg filter", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.EventArgs = map[string][]string{
ensAddr: {"fake_filter_value"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
log := test_helpers.LightNewOwnerLog{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.newowner_event", ensAddr)).StructScan(&log)
Expect(err).To(HaveOccurred())
})
It("If a method arg filter is applied, only those arguments are used in polling", func() {
var testConf config.ContractConfig
testConf = mocks.ENSConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
}
testConf.MethodArgs = map[string][]string{
ensAddr: {"0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
res := test_helpers.Owner{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885696'", ensAddr)).StructScan(&res)
Expect(err).ToNot(HaveOccurred())
Expect(res.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef"))
Expect(res.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047' AND block = '6885696'", ensAddr)).StructScan(&res)
Expect(err).To(HaveOccurred())
})
})
Describe("Execute- against both ENS and TrueUSD", func() {
BeforeEach(func() {
header1, err := blockChain.GetHeaderByNumber(6791668)
Expect(err).ToNot(HaveOccurred())
header2, err := blockChain.GetHeaderByNumber(6791669)
Expect(err).ToNot(HaveOccurred())
header3, err := blockChain.GetHeaderByNumber(6791670)
Expect(err).ToNot(HaveOccurred())
header4, err := blockChain.GetHeaderByNumber(6885695)
Expect(err).ToNot(HaveOccurred())
header5, err := blockChain.GetHeaderByNumber(6885696)
Expect(err).ToNot(HaveOccurred())
header6, err := blockChain.GetHeaderByNumber(6885697)
Expect(err).ToNot(HaveOccurred())
headerRepository.CreateOrUpdateHeader(header1)
headerID, err = headerRepository.CreateOrUpdateHeader(header2)
Expect(err).ToNot(HaveOccurred())
headerRepository.CreateOrUpdateHeader(header3)
headerRepository.CreateOrUpdateHeader(header4)
headerID2, err = headerRepository.CreateOrUpdateHeader(header5)
Expect(err).ToNot(HaveOccurred())
headerRepository.CreateOrUpdateHeader(header6)
})
It("Transforms watched contract data into custom repositories", func() {
t := transformer.NewTransformer(mocks.ENSandTusdConfig, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
newOwnerLog := test_helpers.LightNewOwnerLog{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.newowner_event", ensAddr)).StructScan(&newOwnerLog)
Expect(err).ToNot(HaveOccurred())
// We don't know vulcID, so compare individual fields instead of complete structures
Expect(newOwnerLog.HeaderID).To(Equal(headerID2))
Expect(newOwnerLog.Node).To(Equal("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"))
Expect(newOwnerLog.Label).To(Equal("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047"))
Expect(newOwnerLog.Owner).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef"))
transferLog := test_helpers.LightTransferLog{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.transfer_event", tusdAddr)).StructScan(&transferLog)
Expect(err).ToNot(HaveOccurred())
// We don't know vulcID, so compare individual fields instead of complete structures
Expect(transferLog.HeaderID).To(Equal(headerID))
Expect(transferLog.From).To(Equal("0x1062a747393198f70F71ec65A582423Dba7E5Ab3"))
Expect(transferLog.To).To(Equal("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0"))
Expect(transferLog.Value).To(Equal("9998940000000000000000"))
})
It("Keeps track of contract-related hashes and addresses while transforming event data if they need to be used for later method polling", func() {
var testConf config.ContractConfig
testConf = mocks.ENSandTusdConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
ens, ok := t.Contracts[ensAddr]
Expect(ok).To(Equal(true))
tusd, ok := t.Contracts[tusdAddr]
Expect(ok).To(Equal(true))
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
Expect(len(ens.EmittedHashes)).To(Equal(2))
Expect(len(ens.EmittedAddrs)).To(Equal(0))
Expect(len(tusd.EmittedAddrs)).To(Equal(4))
Expect(len(tusd.EmittedHashes)).To(Equal(0))
b, ok := ens.EmittedHashes[common.HexToHash("0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = ens.EmittedHashes[common.HexToHash("0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = tusd.EmittedAddrs[common.HexToAddress("0x1062a747393198f70F71ec65A582423Dba7E5Ab3")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = tusd.EmittedAddrs[common.HexToAddress("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = tusd.EmittedAddrs[common.HexToAddress("0x571A326f5B15E16917dC17761c340c1ec5d06f6d")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
b, ok = tusd.EmittedAddrs[common.HexToAddress("0xFBb1b73C4f0BDa4f67dcA266ce6Ef42f520fBB98")]
Expect(ok).To(Equal(true))
Expect(b).To(Equal(true))
})
It("Polls given methods for each contract, using list of collected values", func() {
var testConf config.ContractConfig
testConf = mocks.ENSandTusdConfig
testConf.Methods = map[string][]string{
ensAddr: {"owner"},
tusdAddr: {"balanceOf"},
}
t := transformer.NewTransformer(testConf, blockChain, db)
err = t.Init()
Expect(err).ToNot(HaveOccurred())
err = t.Execute()
Expect(err).ToNot(HaveOccurred())
owner := test_helpers.Owner{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' AND block = '6885696'", ensAddr)).StructScan(&owner)
Expect(err).ToNot(HaveOccurred())
Expect(owner.Address).To(Equal("0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef"))
Expect(owner.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ce695797aaf402b1c186bad9eca28842625b5047' AND block = '6885696'", ensAddr)).StructScan(&owner)
Expect(err).ToNot(HaveOccurred())
Expect(owner.Address).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(owner.TokenName).To(Equal(""))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.owner_method WHERE node_ = '0x95832c7a47ff8a7840e28b78ceMADEUPaaf4HASHc186badTHItransformers.8IS625bFAKE' AND block = '6885696'", ensAddr)).StructScan(&owner)
Expect(err).To(HaveOccurred())
bal := test_helpers.BalanceOf{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x1062a747393198f70F71ec65A582423Dba7E5Ab3' AND block = '6791669'", tusdAddr)).StructScan(&bal)
Expect(err).ToNot(HaveOccurred())
Expect(bal.Balance).To(Equal("55849938025000000000000"))
Expect(bal.TokenName).To(Equal("TrueUSD"))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843b1234567890' AND block = '6791669'", tusdAddr)).StructScan(&bal)
Expect(err).To(HaveOccurred())
})
})
})

View File

@ -72,7 +72,6 @@ var TusdContractAddress = "0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E"
var EnsContractAddress = "0x314159265dD8dbb310642f98f50C066173C1259b" var EnsContractAddress = "0x314159265dD8dbb310642f98f50C066173C1259b"
var PublicResolverAddress = "0x1da022710dF5002339274AaDEe8D58218e9D6AB5" var PublicResolverAddress = "0x1da022710dF5002339274AaDEe8D58218e9D6AB5"
// TODO: Consider whether these should be moved to plugins
// Contract Owner // Contract Owner
var DaiContractOwner = "0x0000000000000000000000000000000000000000" var DaiContractOwner = "0x0000000000000000000000000000000000000000"
var TusdContractOwner = "0x9978d2d229a69b3aef93420d132ab22b44e3578f" var TusdContractOwner = "0x9978d2d229a69b3aef93420d132ab22b44e3578f"

View File

@ -64,12 +64,6 @@ func (c Contract) Init() *Contract {
} }
} }
// If we are creating an address list in postgres
// we initialize the map despite what method call, if any
if c.CreateAddrList {
c.EmittedAddrs = map[interface{}]bool{}
}
return &c return &c
} }

View File

@ -106,7 +106,6 @@ type Owner struct {
Address string `db:"returned"` Address string `db:"returned"`
} }
// TODO: consider whether this should be moved to libraries/shared
func SetupBC() core.BlockChain { func SetupBC() core.BlockChain {
infuraIPC := "https://mainnet.infura.io/v3/b09888c1113640cc9ab42750ce750c05" infuraIPC := "https://mainnet.infura.io/v3/b09888c1113640cc9ab42750ce750c05"
rawRpcClient, err := rpc.Dial(infuraIPC) rawRpcClient, err := rpc.Dial(infuraIPC)
@ -114,9 +113,9 @@ func SetupBC() core.BlockChain {
rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC) rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC)
ethClient := ethclient.NewClient(rawRpcClient) ethClient := ethclient.NewClient(rawRpcClient)
blockChainClient := client.NewEthClient(ethClient) blockChainClient := client.NewEthClient(ethClient)
blockChainNode := node.MakeNode(rpcClient) node := node.MakeNode(rpcClient)
transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) transactionConverter := rpc2.NewRpcTransactionConverter(ethClient)
blockChain := geth.NewBlockChain(blockChainClient, rpcClient, blockChainNode, transactionConverter) blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter)
return blockChain return blockChain
} }
@ -128,9 +127,9 @@ func SetupDBandBC() (*postgres.DB, core.BlockChain) {
rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC) rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC)
ethClient := ethclient.NewClient(rawRpcClient) ethClient := ethclient.NewClient(rawRpcClient)
blockChainClient := client.NewEthClient(ethClient) blockChainClient := client.NewEthClient(ethClient)
blockChainNode := node.MakeNode(rpcClient) node := node.MakeNode(rpcClient)
transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) transactionConverter := rpc2.NewRpcTransactionConverter(ethClient)
blockChain := geth.NewBlockChain(blockChainClient, rpcClient, blockChainNode, transactionConverter) blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter)
db, err := postgres.NewDB(config.Database{ db, err := postgres.NewDB(config.Database{
Hostname: "localhost", Hostname: "localhost",
@ -189,7 +188,6 @@ func SetupTusdContract(wantedEvents, wantedMethods []string) *contract.Contract
}.Init() }.Init()
} }
// TODO: consider whether this can be moved to plugin or libraries/shared
func SetupENSRepo(vulcanizeLogId *int64, wantedEvents, wantedMethods []string) (*postgres.DB, *contract.Contract) { func SetupENSRepo(vulcanizeLogId *int64, wantedEvents, wantedMethods []string) (*postgres.DB, *contract.Contract) {
db, err := postgres.NewDB(config.Database{ db, err := postgres.NewDB(config.Database{
Hostname: "localhost", Hostname: "localhost",
@ -238,7 +236,7 @@ func SetupENSContract(wantedEvents, wantedMethods []string) *contract.Contract {
} }
func TearDown(db *postgres.DB) { func TearDown(db *postgres.DB) {
tx, err := db.Beginx() tx, err := db.Begin()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM blocks`) _, err = tx.Exec(`DELETE FROM blocks`)

View File

@ -18,6 +18,8 @@ package mocks
import ( import (
"encoding/json" "encoding/json"
"github.com/vulcanize/vulcanizedb/pkg/config"
"strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -249,3 +251,78 @@ var MockNewOwnerLog2 = types.Log{
}, },
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000af21"), Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000af21"),
} }
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: []string{"Transfer"},
},
Methods: map[string][]string{
tusd: nil,
},
MethodArgs: map[string][]string{
tusd: nil,
},
EventArgs: map[string][]string{
tusd: nil,
},
}
var ENSConfig = config.ContractConfig{
Network: "",
Addresses: map[string]bool{
ens: true,
},
Abis: map[string]string{
ens: "",
},
Events: map[string][]string{
ens: []string{"NewOwner"},
},
Methods: map[string][]string{
ens: nil,
},
MethodArgs: map[string][]string{
ens: nil,
},
EventArgs: map[string][]string{
ens: nil,
},
}
var ENSandTusdConfig = config.ContractConfig{
Network: "",
Addresses: map[string]bool{
ens: true,
tusd: true,
},
Abis: map[string]string{
ens: "",
tusd: "",
},
Events: map[string][]string{
ens: []string{"NewOwner"},
tusd: []string{"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,
},
}

View File

@ -97,7 +97,7 @@ func (r *eventRepository) persistLogs(logs []types.Log, eventInfo types.Event, c
// Creates a custom postgres command to persist logs for the given event (compatible with light synced vDB) // Creates a custom postgres command to persist logs for the given event (compatible with light synced vDB)
func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error { func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
tx, err := r.db.Beginx() tx, err := r.db.Begin()
if err != nil { if err != nil {
return err return err
} }
@ -151,7 +151,7 @@ func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types
// Creates a custom postgres command to persist logs for the given event (compatible with fully synced vDB) // 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 { func (r *eventRepository) persistFullSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
tx, err := r.db.Beginx() tx, err := r.db.Begin()
if err != nil { if err != nil {
return err return err
} }

View File

@ -77,7 +77,7 @@ func (r *methodRepository) PersistResults(results []types.Result, methodInfo typ
// Creates a custom postgres command to persist logs for the given event // 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 { func (r *methodRepository) persistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error {
tx, err := r.DB.Beginx() tx, err := r.DB.Begin()
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,32 +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
// Used to extract any/all events and a subset of method (state variable)
// data for any contract and persists it to custom postgres tables in vDB
type Transformer interface {
SetEvents(contractAddr string, filterSet []string)
SetEventArgs(contractAddr string, filterSet []string)
SetMethods(contractAddr string, filterSet []string)
SetMethodArgs(contractAddr string, filterSet []string)
SetStartingBlock(contractAddr string, start int64)
SetCreateAddrList(contractAddr string, on bool)
SetCreateHashList(contractAddr string, on bool)
SetPiping(contractAddr string, on bool)
Init() error
Execute() error
}