begin work on: Add checked_headers column for methods that are polled so taht we don’t duplicate; Add batching of method polling so that we arent generating a rediculously large account address list before using it to poll methods (or persist the list in pg?); User passed ABI and other ways to get ABI; Add ability to collect []byte and hashes from events and use them in method polling same manner as addresses; Event filter addrs => only those event’s addresses/hashes are used for polling; Option to persist seen address/hash/bytes lists into pg; Only generate lists of addresses, []byte, or hashes if a method will use them later

This commit is contained in:
Ian Norden 2018-12-07 09:38:46 -06:00
parent 8c5b1b4dbe
commit 0a59f06cac
28 changed files with 676 additions and 420 deletions

View File

@ -67,9 +67,10 @@ func lightOmniWatcher() {
for _, addr := range contractAddresses { for _, addr := range contractAddresses {
t.SetEvents(addr, contractEvents) t.SetEvents(addr, contractEvents)
t.SetMethods(addr, contractMethods) t.SetMethods(addr, contractMethods)
t.SetEventAddrs(addr, eventAddrs) t.SetEventArgs(addr, eventArgs)
t.SetMethodAddrs(addr, methodAddrs) t.SetMethodArgs(addr, methodArgs)
t.SetRange(addr, [2]int64{startingBlockNumber, endingBlockNumber}) t.SetRange(addr, [2]int64{startingBlockNumber, endingBlockNumber})
t.SetCreateAddrList(addr, createAddrList)
} }
err := t.Init() err := t.Init()
@ -90,11 +91,12 @@ func init() {
lightOmniWatcherCmd.Flags().StringVarP(&contractAddress, "contract-address", "a", "", "Single address to generate watchers for") lightOmniWatcherCmd.Flags().StringVarP(&contractAddress, "contract-address", "a", "", "Single address to generate watchers for")
lightOmniWatcherCmd.Flags().StringArrayVarP(&contractAddresses, "contract-addresses", "l", []string{}, "list of addresses to use; warning: watcher targets the same events and methods for each address") lightOmniWatcherCmd.Flags().StringArrayVarP(&contractAddresses, "contract-addresses", "l", []string{}, "list of addresses to use; warning: watcher targets the same events and methods for each address")
lightOmniWatcherCmd.Flags().StringArrayVarP(&contractEvents, "contract-events", "e", []string{}, "Subset of events to watch; by default all events are watched") lightOmniWatcherCmd.Flags().StringArrayVarP(&contractEvents, "events", "e", []string{}, "Subset of events to watch; by default all events are watched")
lightOmniWatcherCmd.Flags().StringArrayVarP(&contractMethods, "contract-methods", "m", nil, "Subset of methods to poll; by default no methods are polled") lightOmniWatcherCmd.Flags().StringArrayVarP(&contractMethods, "methods", "m", nil, "Subset of methods to poll; by default no methods are polled")
lightOmniWatcherCmd.Flags().StringArrayVarP(&eventAddrs, "event-filter-addresses", "f", []string{}, "Account addresses to persist event data for; default is to persist for all found token holder addresses") lightOmniWatcherCmd.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")
lightOmniWatcherCmd.Flags().StringArrayVarP(&methodAddrs, "method-filter-addresses", "g", []string{}, "Account addresses to poll methods with; default is to poll with all found token holder addresses") lightOmniWatcherCmd.Flags().StringArrayVarP(&methodArgs, "method-args", "g", []string{}, "Argument values to limit methods to; will only call methods with emitted values that were specified here")
lightOmniWatcherCmd.Flags().StringVarP(&network, "network", "n", "", `Network the contract is deployed on; options: "ropsten", "kovan", and "rinkeby"; default is mainnet"`) lightOmniWatcherCmd.Flags().StringVarP(&network, "network", "n", "", `Network the contract is deployed on; options: "ropsten", "kovan", and "rinkeby"; default is mainnet"`)
lightOmniWatcherCmd.Flags().Int64VarP(&startingBlockNumber, "starting-block-number", "s", 0, "Block to begin watching- default is first block the contract exists") lightOmniWatcherCmd.Flags().Int64VarP(&startingBlockNumber, "starting-block-number", "s", 0, "Block to begin watching- default is first block the contract exists")
lightOmniWatcherCmd.Flags().Int64VarP(&endingBlockNumber, "ending-block-number", "d", -1, "Block to end watching- default is most recent block") lightOmniWatcherCmd.Flags().Int64VarP(&endingBlockNumber, "ending-block-number", "d", -1, "Block to end watching- default is most recent block")
lightOmniWatcherCmd.Flags().BoolVarP(&createAddrList, "create-address-list", "c", false, "Set to true to persist address seen in emitted events into the database")
} }

View File

@ -67,8 +67,8 @@ func omniWatcher() {
for _, addr := range contractAddresses { for _, addr := range contractAddresses {
t.SetEvents(addr, contractEvents) t.SetEvents(addr, contractEvents)
t.SetMethods(addr, contractMethods) t.SetMethods(addr, contractMethods)
t.SetEventAddrs(addr, eventAddrs) t.SetEventArgs(addr, eventArgs)
t.SetMethodAddrs(addr, methodAddrs) t.SetMethodArgs(addr, methodArgs)
t.SetRange(addr, [2]int64{startingBlockNumber, endingBlockNumber}) t.SetRange(addr, [2]int64{startingBlockNumber, endingBlockNumber})
} }
@ -90,11 +90,12 @@ func init() {
omniWatcherCmd.Flags().StringVarP(&contractAddress, "contract-address", "a", "", "Single address to generate watchers for") 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(&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, "contract-events", "e", []string{}, "Subset of events to watch; by default all events are watched") omniWatcherCmd.Flags().StringArrayVarP(&contractEvents, "events", "e", []string{}, "Subset of events to watch; by default all events are watched")
omniWatcherCmd.Flags().StringArrayVarP(&contractMethods, "contract-methods", "m", nil, "Subset of methods to poll; by default no methods are polled") omniWatcherCmd.Flags().StringArrayVarP(&contractMethods, "methods", "m", nil, "Subset of methods to poll; by default no methods are polled")
omniWatcherCmd.Flags().StringArrayVarP(&eventAddrs, "event-filter-addresses", "f", []string{}, "Account addresses to persist event data for; default is to persist for all found token holder addresses") 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(&methodAddrs, "method-filter-addresses", "g", []string{}, "Account addresses to poll methods with; default is to poll with all found token holder addresses") 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().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().Int64VarP(&startingBlockNumber, "starting-block-number", "s", 0, "Block to begin watching- default is first block the contract exists")
omniWatcherCmd.Flags().Int64VarP(&endingBlockNumber, "ending-block-number", "d", -1, "Block to end watching- default is most recent block") omniWatcherCmd.Flags().Int64VarP(&endingBlockNumber, "ending-block-number", "d", -1, "Block to end watching- default is most recent block")
omniWatcherCmd.Flags().BoolVarP(&createAddrList, "create-address-list", "c", false, "Set to true to persist address seen in emitted events into the database")
} }

View File

@ -47,8 +47,9 @@ var (
contractAddresses []string contractAddresses []string
contractEvents []string contractEvents []string
contractMethods []string contractMethods []string
eventAddrs []string eventArgs []string
methodAddrs []string methodArgs []string
createAddrList bool
) )
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{

View File

@ -1,41 +1,46 @@
Run lightSync, starting at the blockheight the ENS registry was published at.
`./vulcanize lightSync --config=./environments/<config.toml> --starting-block-number=3327417`
1. What is the label hash of this domain? 1. What is the label hash of this domain?
Q. Does this mean for a given namehash of "a.b.c" find keccak256(a), keccak256(b), and keccak256(c)? Do we know the parent domain and/or owner address? Q. Does this mean for a given namehash of "a.b.c" where we don't know what "c" is find keccak256(c)? Do we know the parent domain and/or owner address?
1. Watch NewOwner(bytes32 indexed node, bytes32 indexed label, address owner) events of the ENS Registry contract and filter for the root domain and/or owner address to narrow search 1. Watch NewOwner(bytes32 indexed node, bytes32 indexed label, address owner) events of the ENS Registry contract and filter for the parent node and/or owner address to narrow search
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3327417 --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner`
2. For each node + label pair we find emitted, calculate the keccak256(abi.encodePacked(node, label)) and see if it matches our namehash 2. For each node + label pair we find emitted, calculate the keccak256(abi.encodePacked(node, label)) and see if it matches our domain's namehash
3. If it does, hash(label) is our answer 3. If it does, keccak256(label) is our label hash
2. What is the parent domain of this domain? 2. What is the parent domain of this domain?
1. Watch NewOwner(bytes32 indexed node, bytes32 indexed label, address owner) events of the ENS Registry contract 1. Watch NewOwner(bytes32 indexed node, bytes32 indexed label, address owner) events of the ENS Registry contract
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3327417 --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner`
2. Filter for our label (domain) and collect the node (parent domain namehash) that was emitted with it 2. If we know our label, filter for it and collect the node (parent domain namehash) that was emitted with it
3. Call the Registry's resolver(bytes32 node) method for the parent node to find the parent domain's Resolver 3. If we don't know our label I believe we need to take a similar aqpproach as in section 1 where we try every keccak256(abi.encodePacked(node, label)) until we find the namehash for our domain
4. Call its Resolver's name(bytes32 node) method for the parent node to find the parent domain's name 4. Call the Registry's resolver(bytes32 node) method for the parent node to find the parent domain's Resolver
5. Call its Resolver's name(bytes32 node) method for the parent node to find the parent domain's name
3. What are the subdomains of this domain? 3. What are the subdomains of this domain?
1. Watch NewOwner events of the ENS Registry contract 1. Watch NewOwner(bytes32 indexed node, bytes32 indexed label, address owner) events of the ENS Registry contract
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3327417 --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner`
2. Filter for our node (domain) and collect all the labels emitted with it 2. Filter for our node (domain) and collect all the labels emitted with it
3. Calculate subdomain hashes: subnode = keccak256(abi.encodePacked(node, label)); 3. Calculate subdomain hashes: subnode = keccak256(abi.encodePacked(node, label))
4. Call the Registry's resolver(bytes32 node) method for a subnode to find the subdomain's Resolver 4. Call the Registry's resolver(bytes32 node) method for a subnode to find the subdomain's Resolver
5. Call its Resolver's name(bytes32 node) method for a subnode to find the subdomain's name 5. Call its Resolver's name(bytes32 node) method for a subnode to find the subdomain's name
4. What domains does this address own? 4. What domains does this address own?
1. Watch NewOwner(bytes32 indexed node, bytes32 indexed label, address owner) and Transfer(bytes32 indexed node, address owner) events of the ENS Registry contract and filter for the address 1. Watch NewOwner(bytes32 indexed node, bytes32 indexed label, address owner) and Transfer(bytes32 indexed node, address owner) events of the ENS Registry contract and filter for the address
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-eevents=NewOwner --contract-events=Transfer --event-filter-addresses=<address>` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3327417 --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-eevents=NewOwner --contract-events=Transfer --event-filter-addresses=<address>`
2. Generate list of all nodes this address has ever owned 2. Collect node and label values; calculate subnodes = keccak256(abi.encodePacked(node, label))
3. Check which of these they still own at a given blockheight by iterating over the list and calling the owner(bytes32 node) method 3. Aggregate nodes and subnodes into a list and check which of these they still own at a given blockheight by iterating over the list and calling the Registry's owner(bytes32 node) method
4. Call the Registry's resolver(bytes32 node) method for a node to find the domain's Resolver 4. Call the Registry's resolver(bytes32 node) method for a node to find the domain's Resolver
5. Call its Resolver's name(bytes32 node) method for the node to find the domain's name 5. Call its Resolver's name(bytes32 node) method for the node to find the domain's name
5. What names point to this address? 5. What names point to this address?
Q. Is this in terms of which ENS nodes point to a given Resolver address? E.g. All nodes where the ENS records[node].resolver == address? Or is this in terms of Resolver records? E.g. All the records[node].names where the Resolver records[node].addr == address Q. Is this in terms of which ENS nodes point to a given Resolver address? E.g. All nodes where the ENS records[node].resolver == address? Or is this in terms of Resolver records? E.g. All the records[node].names where the Resolver records[node].addr == address
1. In the former case, watch NewResolver(bytes32 indexed node, address resolver) events of the Registry and filter for the account address 1. In the former case, watch NewResolver(bytes32 indexed node, address resolver) events of the Registry and filter for the account address
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x1da022710dF5002339274AaDEe8D58218e9D6AB5 --contract-events=NewResolver --event-filter-addresses=<address>` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3327417 --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewResolver --event-filter-addresses=<address>`
2. Generate a list of nodes that have pointed to this resolver address 2. Generate a list of nodes that have pointed to this resolver address
3. Check which of these names still point at the address by iterating over the list and calling the resolver(bytes32 node) method 3. Check which of these still point at the address by iterating over the list and calling the Registry's resolver(bytes32 node) method
1. In the latter case, watch AddrChanged(bytes32 indexed node, address a) events of the Resolver and filter for the account address 1. In the latter case, watch AddrChanged(bytes32 indexed node, address a) events of the Resolver and filter for the account address
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x1da022710dF5002339274AaDEe8D58218e9D6AB5 --contract-events=AddrChanged --event-filter-addresses=<address>` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3648359 --contract-address=0x1da022710dF5002339274AaDEe8D58218e9D6AB5 --contract-events=AddrChanged --event-filter-addresses=<address>`
2. Generate our list of nodes that have pointed towards our address 2. Generate our list of nodes that have pointed towards our address
3. Check which of these they still own at a given blockheight by iterating over the list and calling the Resolver's addr(bytes32 node) method 3. Check which of these they still own at a given blockheight by iterating over the list and calling the Resolver's addr(bytes32 node) method
4. We can then fetch the string names of these nodes using the Resolver's name(bytes32 node) method. 4. We can then fetch the string names of these nodes using the Resolver's name(bytes32 node) method.
@ -48,9 +53,9 @@ we could also perform []byte filtering on the events and automate polling of eve
currently working on adding this in, and once it is in you would be able to automate more of the steps in these processes. currently working on adding this in, and once it is in you would be able to automate more of the steps in these processes.
E.g. you will be able to run E.g. you will be able to run
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner --contract-events=Transfer --event-args=<address> --contract-methods=owner` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3327417 --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner --contract-events=Transfer --event-args=<address> --contract-methods=owner`
To automate the process in question 4 through step 3 (it will collect node []byte values emitted from the events it watches and then use those to call the owner method, persisting the results) To automate the process in question 4 through step 3 (it will collect node []byte values emitted from the events it watches and then use those to call the owner method, persisting the results)
Or Or
`./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=## --ending-block-numer=### --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner --event-args=<bytes-to-filter-for>` `./vulcanize lightOmniWacther --config=./environments/<config.toml> --starting-block-number=3327417 --contract-address=0x314159265dD8dbb310642f98f50C066173C1259b --contract-events=NewOwner --event-args=<bytes-to-filter-for>`
To provide automated filtering for node []byte values in question 3. To provide automated filtering for node []byte values in question 3.

View File

@ -69,7 +69,9 @@ func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (
} }
strValues := make(map[string]string, len(values)) strValues := make(map[string]string, len(values))
seenBytes := make([]interface{}, 0, len(values))
seenAddrs := make([]interface{}, 0, len(values))
seenHashes := make([]interface{}, 0, len(values))
for fieldName, input := range values { for fieldName, input := range values {
// Postgres cannot handle custom types, resolve to strings // Postgres cannot handle custom types, resolve to strings
switch input.(type) { switch input.(type) {
@ -79,10 +81,11 @@ func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (
case common.Address: case common.Address:
a := input.(common.Address) a := input.(common.Address)
strValues[fieldName] = a.String() strValues[fieldName] = a.String()
c.ContractInfo.AddTokenHolderAddress(a.String()) // cache address in a list of contract's token holder addresses seenAddrs = append(seenAddrs, a)
case common.Hash: case common.Hash:
h := input.(common.Hash) h := input.(common.Hash)
strValues[fieldName] = h.String() strValues[fieldName] = h.String()
seenHashes = append(seenHashes, h)
case string: case string:
strValues[fieldName] = input.(string) strValues[fieldName] = input.(string)
case bool: case bool:
@ -90,6 +93,7 @@ func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (
case []byte: case []byte:
b := input.([]byte) b := input.([]byte)
strValues[fieldName] = string(b) strValues[fieldName] = string(b)
seenBytes = append(seenBytes, b)
case byte: case byte:
b := input.(byte) b := input.(byte)
strValues[fieldName] = string(b) strValues[fieldName] = string(b)
@ -107,6 +111,17 @@ func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (
Tx: watchedEvent.TxHash, Tx: watchedEvent.TxHash,
} }
// Cache emitted values if their caching is turned on
if c.ContractInfo.EmittedAddrs != nil {
c.ContractInfo.AddEmittedAddr(seenAddrs...)
}
if c.ContractInfo.EmittedHashes != nil {
c.ContractInfo.AddEmittedHash(seenHashes...)
}
if c.ContractInfo.EmittedBytes != nil {
c.ContractInfo.AddEmittedBytes(seenBytes...)
}
return eventLog, nil return eventLog, nil
} }

View File

@ -81,21 +81,21 @@ var _ = Describe("Converter", func() {
_, err := c.Convert(mocks.MockTranferEvent, event) _, err := c.Convert(mocks.MockTranferEvent, event)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
b, ok := con.TknHolderAddrs["0x000000000000000000000000000000000000Af21"] b, ok := con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
b, ok = con.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"] b, ok = con.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
_, ok = con.TknHolderAddrs["0x"] _, ok = con.EmittedAddrs[common.HexToAddress("0x")]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
_, ok = con.TknHolderAddrs[""] _, ok = con.EmittedAddrs[""]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
_, ok = con.TknHolderAddrs["0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP"] _, ok = con.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
}) })

View File

@ -18,7 +18,7 @@ package transformer
import ( import (
"errors" "errors"
"fmt"
"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"
@ -63,8 +63,11 @@ type transformer struct {
// Lists of addresses to filter event or method data // Lists of addresses to filter event or method data
// before persisting; if empty no filter is applied // before persisting; if empty no filter is applied
EventAddrs map[string][]string EventArgs map[string][]string
MethodAddrs map[string][]string MethodArgs map[string][]string
// Whether or not to create a list of token holder addresses for the contract in postgres
CreateAddrList map[string]bool
} }
// Transformer takes in config for blockchain, database, and network id // Transformer takes in config for blockchain, database, and network id
@ -81,8 +84,8 @@ func NewTransformer(network string, BC core.BlockChain, DB *postgres.DB) *transf
WatchedEvents: map[string][]string{}, WatchedEvents: map[string][]string{},
WantedMethods: map[string][]string{}, WantedMethods: map[string][]string{},
ContractRanges: map[string][2]int64{}, ContractRanges: map[string][2]int64{},
EventAddrs: map[string][]string{}, EventArgs: map[string][]string{},
MethodAddrs: map[string][]string{}, MethodArgs: map[string][]string{},
} }
} }
@ -116,25 +119,22 @@ func (t *transformer) Init() error {
lastBlock = t.ContractRanges[contractAddr][1] lastBlock = t.ContractRanges[contractAddr][1]
} }
// Get contract name // Get contract name if it has one
var name = new(string) var name = new(string)
err = t.FetchContractData(t.Abi(), contractAddr, "name", nil, &name, lastBlock) t.FetchContractData(t.Abi(), contractAddr, "name", nil, &name, lastBlock)
if err != nil {
return errors.New(fmt.Sprintf("unable to fetch contract name: %v\r\n", err))
}
// Remove any accidental duplicate inputs in filter addresses // Remove any potential accidental duplicate inputs in arg filter values
EventAddrs := map[string]bool{} eventArgs := map[string]bool{}
for _, addr := range t.EventAddrs[contractAddr] { for _, arg := range t.EventArgs[contractAddr] {
EventAddrs[addr] = true eventArgs[arg] = true
} }
MethodAddrs := map[string]bool{} methodArgs := map[string]bool{}
for _, addr := range t.MethodAddrs[contractAddr] { for _, arg := range t.MethodArgs[contractAddr] {
MethodAddrs[addr] = 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: t.Network, Network: t.Network,
Address: contractAddr, Address: contractAddr,
@ -143,11 +143,11 @@ func (t *transformer) Init() error {
StartingBlock: firstBlock, StartingBlock: firstBlock,
LastBlock: lastBlock, LastBlock: lastBlock,
Events: t.GetEvents(subset), Events: t.GetEvents(subset),
Methods: t.GetAddrMethods(t.WantedMethods[contractAddr]), Methods: t.GetSelectMethods(t.WantedMethods[contractAddr]),
EventAddrs: EventAddrs, FilterArgs: eventArgs,
MethodAddrs: MethodAddrs, MethodArgs: methodArgs,
TknHolderAddrs: map[string]bool{}, CreateAddrList: t.CreateAddrList[contractAddr],
} }.Init()
// Use info to create filters // Use info to create filters
err = info.GenerateFilters() err = info.GenerateFilters()
@ -222,26 +222,31 @@ func (tr transformer) Execute() error {
} }
// Used to set which contract addresses and which of their events to watch // Used to set which contract addresses and which of their events to watch
func (t *transformer) SetEvents(contractAddr string, filterSet []string) { func (tr *transformer) SetEvents(contractAddr string, filterSet []string) {
t.WatchedEvents[contractAddr] = filterSet tr.WatchedEvents[contractAddr] = filterSet
} }
// Used to set subset of account addresses to watch events for // Used to set subset of account addresses to watch events for
func (t *transformer) SetEventAddrs(contractAddr string, filterSet []string) { func (tr *transformer) SetEventArgs(contractAddr string, filterSet []string) {
t.EventAddrs[contractAddr] = filterSet tr.EventArgs[contractAddr] = filterSet
} }
// Used to set which contract addresses and which of their methods to call // Used to set which contract addresses and which of their methods to call
func (t *transformer) SetMethods(contractAddr string, filterSet []string) { func (tr *transformer) SetMethods(contractAddr string, filterSet []string) {
t.WantedMethods[contractAddr] = filterSet tr.WantedMethods[contractAddr] = filterSet
} }
// Used to set subset of account addresses to poll methods on // Used to set subset of account addresses to poll methods on
func (t *transformer) SetMethodAddrs(contractAddr string, filterSet []string) { func (tr *transformer) SetMethodArgs(contractAddr string, filterSet []string) {
t.MethodAddrs[contractAddr] = filterSet tr.MethodArgs[contractAddr] = filterSet
} }
// Used to set the block range to watch for a given address // Used to set the block range to watch for a given address
func (t *transformer) SetRange(contractAddr string, rng [2]int64) { func (tr *transformer) SetRange(contractAddr string, rng [2]int64) {
t.ContractRanges[contractAddr] = rng tr.ContractRanges[contractAddr] = rng
}
// Used to set the block range to watch for a given address
func (tr *transformer) SetCreateAddrList(contractAddr string, on bool) {
tr.CreateAddrList[contractAddr] = on
} }

View File

@ -21,6 +21,7 @@ import (
"math/rand" "math/rand"
"time" "time"
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -62,8 +63,8 @@ var _ = Describe("Transformer", func() {
It("Sets which account addresses to watch events for", func() { It("Sets which account addresses to watch events for", func() {
eventAddrs := []string{"test1", "test2"} eventAddrs := []string{"test1", "test2"}
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer("", blockChain, db)
t.SetEventAddrs(constants.TusdContractAddress, eventAddrs) t.SetEventArgs(constants.TusdContractAddress, eventAddrs)
Expect(t.EventAddrs[constants.TusdContractAddress]).To(Equal(eventAddrs)) Expect(t.EventArgs[constants.TusdContractAddress]).To(Equal(eventAddrs))
}) })
}) })
@ -80,8 +81,8 @@ var _ = Describe("Transformer", func() {
It("Sets which account addresses to poll methods against", func() { It("Sets which account addresses to poll methods against", func() {
methodAddrs := []string{"test1", "test2"} methodAddrs := []string{"test1", "test2"}
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer("", blockChain, db)
t.SetMethodAddrs(constants.TusdContractAddress, methodAddrs) t.SetMethodArgs(constants.TusdContractAddress, methodAddrs)
Expect(t.MethodAddrs[constants.TusdContractAddress]).To(Equal(methodAddrs)) Expect(t.MethodArgs[constants.TusdContractAddress]).To(Equal(methodAddrs))
}) })
}) })
@ -159,10 +160,10 @@ var _ = Describe("Transformer", func() {
Expect(log.Value).To(Equal("1097077688018008265106216665536940668749033598146")) Expect(log.Value).To(Equal("1097077688018008265106216665536940668749033598146"))
}) })
It("Keeps track of contract-related addresses while transforming event data", 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) t := transformer.NewTransformer("", blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
t.SetMethods(constants.TusdContractAddress, nil) t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"})
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -172,18 +173,24 @@ var _ = Describe("Transformer", func() {
err = t.Execute() err = t.Execute()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
b, ok := c.TknHolderAddrs["0x000000000000000000000000000000000000Af21"] b, ok := c.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
b, ok = c.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"] b, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
_, ok = c.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843b1234567890"] _, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843b1234567890")]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
_, ok = c.TknHolderAddrs["0x"] _, 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)) Expect(ok).To(Equal(false))
}) })
@ -199,7 +206,12 @@ var _ = Describe("Transformer", func() {
res := test_helpers.BalanceOf{} res := test_helpers.BalanceOf{}
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x000000000000000000000000000000000000Af21' AND block = '6194634'", constants.TusdContractAddress)).StructScan(&res) err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843bCE061BA391' AND block = '6194634'", constants.TusdContractAddress)).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'", constants.TusdContractAddress)).StructScan(&res)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(res.Balance).To(Equal("0")) Expect(res.Balance).To(Equal("0"))
Expect(res.TokenName).To(Equal("TrueUSD")) Expect(res.TokenName).To(Equal("TrueUSD"))

View File

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethTypes "github.com/ethereum/go-ethereum/core/types" gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
@ -67,6 +68,9 @@ func (c *converter) Convert(logs []gethTypes.Log, event types.Event, headerID in
} }
strValues := make(map[string]string, len(values)) strValues := make(map[string]string, len(values))
seenBytes := make([]interface{}, 0, len(values))
seenAddrs := make([]interface{}, 0, len(values))
seenHashes := make([]interface{}, 0, len(values))
for fieldName, input := range values { for fieldName, input := range values {
// Postgres cannot handle custom types, resolve everything to strings // Postgres cannot handle custom types, resolve everything to strings
switch input.(type) { switch input.(type) {
@ -76,17 +80,19 @@ func (c *converter) Convert(logs []gethTypes.Log, event types.Event, headerID in
case common.Address: case common.Address:
a := input.(common.Address) a := input.(common.Address)
strValues[fieldName] = a.String() strValues[fieldName] = a.String()
c.ContractInfo.AddTokenHolderAddress(a.String()) // cache address in a list of contract's token holder addresses seenAddrs = append(seenAddrs, a)
case common.Hash: case common.Hash:
h := input.(common.Hash) h := input.(common.Hash)
strValues[fieldName] = h.String() strValues[fieldName] = h.String()
seenHashes = append(seenHashes, h)
case string: case string:
strValues[fieldName] = input.(string) strValues[fieldName] = input.(string)
case bool: case bool:
strValues[fieldName] = strconv.FormatBool(input.(bool)) strValues[fieldName] = strconv.FormatBool(input.(bool))
case []byte: case []byte:
b := input.([]byte) b := input.([]byte)
strValues[fieldName] = string(b) strValues[fieldName] = hexutil.Encode(b)
seenBytes = append(seenBytes, b)
case byte: case byte:
b := input.(byte) b := input.(byte)
strValues[fieldName] = string(b) strValues[fieldName] = string(b)
@ -109,6 +115,17 @@ func (c *converter) Convert(logs []gethTypes.Log, event types.Event, headerID in
TransactionIndex: log.TxIndex, TransactionIndex: log.TxIndex,
Id: headerID, Id: headerID,
}) })
// Cache emitted values if their caching is turned on
if c.ContractInfo.EmittedAddrs != nil {
c.ContractInfo.AddEmittedAddr(seenAddrs...)
}
if c.ContractInfo.EmittedHashes != nil {
c.ContractInfo.AddEmittedHash(seenHashes...)
}
if c.ContractInfo.EmittedBytes != nil {
c.ContractInfo.AddEmittedBytes(seenBytes...)
}
} }
} }

View File

@ -84,21 +84,21 @@ var _ = Describe("Converter", func() {
_, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232) _, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
b, ok := con.TknHolderAddrs["0x000000000000000000000000000000000000Af21"] b, ok := con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
b, ok = con.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"] b, ok = con.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
_, ok = con.TknHolderAddrs["0x"] _, ok = con.EmittedAddrs[common.HexToAddress("0x")]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
_, ok = con.TknHolderAddrs[""] _, ok = con.EmittedAddrs[""]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
_, ok = con.TknHolderAddrs["0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP"] _, ok = con.EmittedAddrs[common.HexToAddress("0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP")]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
}) })

View File

@ -18,6 +18,7 @@ package repository
import ( import (
"database/sql" "database/sql"
"github.com/hashicorp/golang-lru" "github.com/hashicorp/golang-lru"
"github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/core"
@ -85,7 +86,8 @@ func (r *headerRepository) MissingHeaders(startingBlockNumber int64, endingBlock
LEFT JOIN checked_headers on headers.id = header_id LEFT JOIN checked_headers on headers.id = header_id
WHERE (header_id ISNULL OR ` + eventID + ` IS FALSE) WHERE (header_id ISNULL OR ` + eventID + ` IS FALSE)
AND headers.block_number >= $1 AND headers.block_number >= $1
AND headers.eth_node_fingerprint = $2` AND headers.eth_node_fingerprint = $2
ORDER BY headers.block_number`
err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID) err = r.db.Select(&result, query, startingBlockNumber, r.db.Node.ID)
} else { } else {
query = `SELECT headers.id, headers.block_number, headers.hash FROM headers query = `SELECT headers.id, headers.block_number, headers.hash FROM headers
@ -93,7 +95,8 @@ func (r *headerRepository) MissingHeaders(startingBlockNumber int64, endingBlock
WHERE (header_id ISNULL OR ` + eventID + ` IS FALSE) WHERE (header_id ISNULL OR ` + eventID + ` IS FALSE)
AND headers.block_number >= $1 AND headers.block_number >= $1
AND headers.block_number <= $2 AND headers.block_number <= $2
AND headers.eth_node_fingerprint = $3` AND headers.eth_node_fingerprint = $3
ORDER BY headers.block_number`
err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID) err = r.db.Select(&result, query, startingBlockNumber, endingBlockNumber, r.db.Node.ID)
} }

View File

@ -42,7 +42,7 @@ func (r *blockRetriever) RetrieveFirstBlock() (int64, error) {
var firstBlock int var firstBlock int
err := r.db.Get( err := r.db.Get(
&firstBlock, &firstBlock,
"SELECT block_number FROM headers ORDER BY block_number ASC LIMIT 1", "SELECT block_number FROM headers ORDER BY block_number LIMIT 1",
) )
return int64(firstBlock), err return int64(firstBlock), err

View File

@ -18,7 +18,6 @@ package transformer
import ( import (
"errors" "errors"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers"
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -30,6 +29,7 @@ import (
"github.com/vulcanize/vulcanizedb/pkg/omni/light/repository" "github.com/vulcanize/vulcanizedb/pkg/omni/light/repository"
"github.com/vulcanize/vulcanizedb/pkg/omni/light/retriever" "github.com/vulcanize/vulcanizedb/pkg/omni/light/retriever"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller"
srep "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository" srep "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository"
@ -67,8 +67,11 @@ type transformer struct {
// Lists of addresses to filter event or method data // Lists of addresses to filter event or method data
// before persisting; if empty no filter is applied // before persisting; if empty no filter is applied
EventAddrs map[string][]string EventArgs map[string][]string
MethodAddrs map[string][]string MethodArgs map[string][]string
// Whether or not to create a list of token holder addresses for the contract in postgres
CreateAddrList map[string]bool
} }
// Transformer takes in config for blockchain, database, and network id // Transformer takes in config for blockchain, database, and network id
@ -86,8 +89,9 @@ func NewTransformer(network string, bc core.BlockChain, db *postgres.DB) *transf
WatchedEvents: map[string][]string{}, WatchedEvents: map[string][]string{},
WantedMethods: map[string][]string{}, WantedMethods: map[string][]string{},
ContractRanges: map[string][2]int64{}, ContractRanges: map[string][2]int64{},
EventAddrs: map[string][]string{}, EventArgs: map[string][]string{},
MethodAddrs: map[string][]string{}, MethodArgs: map[string][]string{},
CreateAddrList: map[string]bool{},
} }
} }
@ -126,18 +130,18 @@ func (tr *transformer) Init() error {
var name = new(string) var name = new(string)
tr.FetchContractData(tr.Abi(), contractAddr, "name", nil, &name, lastBlock) tr.FetchContractData(tr.Abi(), contractAddr, "name", nil, &name, lastBlock)
// Remove any potential accidental duplicate inputs in filter addresses // Remove any potential accidental duplicate inputs in arg filter values
EventAddrs := map[string]bool{} eventArgs := map[string]bool{}
for _, addr := range tr.EventAddrs[contractAddr] { for _, arg := range tr.EventArgs[contractAddr] {
EventAddrs[addr] = true eventArgs[arg] = true
} }
MethodAddrs := map[string]bool{} methodArgs := map[string]bool{}
for _, addr := range tr.MethodAddrs[contractAddr] { for _, arg := range tr.MethodArgs[contractAddr] {
MethodAddrs[addr] = true methodArgs[arg] = true
} }
// Aggregate info into contract object // Aggregate info into contract object and store for execution
info := &contract.Contract{ tr.Contracts[contractAddr] = contract.Contract{
Name: *name, Name: *name,
Network: tr.Network, Network: tr.Network,
Address: contractAddr, Address: contractAddr,
@ -146,14 +150,11 @@ func (tr *transformer) Init() error {
StartingBlock: firstBlock, StartingBlock: firstBlock,
LastBlock: lastBlock, LastBlock: lastBlock,
Events: tr.GetEvents(subset), Events: tr.GetEvents(subset),
Methods: tr.GetAddrMethods(tr.WantedMethods[contractAddr]), Methods: tr.GetSelectMethods(tr.WantedMethods[contractAddr]),
EventAddrs: EventAddrs, FilterArgs: eventArgs,
MethodAddrs: MethodAddrs, MethodArgs: methodArgs,
TknHolderAddrs: map[string]bool{}, CreateAddrList: tr.CreateAddrList[contractAddr],
} }.Init()
// Store contract info for execution
tr.Contracts[contractAddr] = info
} }
return nil return nil
@ -216,14 +217,14 @@ func (tr *transformer) Execute() error {
if err != nil { if err != nil {
return err return err
} }
// Poll contract methods at this header's block height
// with arguments collected from event logs up to this point
if err := tr.PollContractAt(*con, header.BlockNumber); err != nil {
return err
}
} }
} }
// After persisting all watched event logs
// poller polls select contract methods
// and persists the results into custom pg tables
if err := tr.PollContract(*con); err != nil {
return err
}
} }
return nil return nil
@ -235,8 +236,8 @@ func (tr *transformer) SetEvents(contractAddr string, filterSet []string) {
} }
// Used to set subset of account addresses to watch events for // Used to set subset of account addresses to watch events for
func (tr *transformer) SetEventAddrs(contractAddr string, filterSet []string) { func (tr *transformer) SetEventArgs(contractAddr string, filterSet []string) {
tr.EventAddrs[contractAddr] = filterSet tr.EventArgs[contractAddr] = filterSet
} }
// Used to set which contract addresses and which of their methods to call // Used to set which contract addresses and which of their methods to call
@ -245,11 +246,16 @@ func (tr *transformer) SetMethods(contractAddr string, filterSet []string) {
} }
// Used to set subset of account addresses to poll methods on // Used to set subset of account addresses to poll methods on
func (tr *transformer) SetMethodAddrs(contractAddr string, filterSet []string) { func (tr *transformer) SetMethodArgs(contractAddr string, filterSet []string) {
tr.MethodAddrs[contractAddr] = filterSet tr.MethodArgs[contractAddr] = filterSet
} }
// Used to set the block range to watch for a given address // Used to set the block range to watch for a given address
func (tr *transformer) SetRange(contractAddr string, rng [2]int64) { func (tr *transformer) SetRange(contractAddr string, rng [2]int64) {
tr.ContractRanges[contractAddr] = rng tr.ContractRanges[contractAddr] = rng
} }
// Used to set the block range to watch for a given address
func (tr *transformer) SetCreateAddrList(contractAddr string, on bool) {
tr.CreateAddrList[contractAddr] = on
}

View File

@ -18,6 +18,8 @@ package transformer_test
import ( import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -59,8 +61,8 @@ var _ = Describe("Transformer", func() {
It("Sets which account addresses to watch events for", func() { It("Sets which account addresses to watch events for", func() {
eventAddrs := []string{"test1", "test2"} eventAddrs := []string{"test1", "test2"}
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer("", blockChain, db)
t.SetEventAddrs(constants.TusdContractAddress, eventAddrs) t.SetEventArgs(constants.TusdContractAddress, eventAddrs)
Expect(t.EventAddrs[constants.TusdContractAddress]).To(Equal(eventAddrs)) Expect(t.EventArgs[constants.TusdContractAddress]).To(Equal(eventAddrs))
}) })
}) })
@ -77,8 +79,8 @@ var _ = Describe("Transformer", func() {
It("Sets which account addresses to poll methods against", func() { It("Sets which account addresses to poll methods against", func() {
methodAddrs := []string{"test1", "test2"} methodAddrs := []string{"test1", "test2"}
t := transformer.NewTransformer("", blockChain, db) t := transformer.NewTransformer("", blockChain, db)
t.SetMethodAddrs(constants.TusdContractAddress, methodAddrs) t.SetMethodArgs(constants.TusdContractAddress, methodAddrs)
Expect(t.MethodAddrs[constants.TusdContractAddress]).To(Equal(methodAddrs)) Expect(t.MethodArgs[constants.TusdContractAddress]).To(Equal(methodAddrs))
}) })
}) })
@ -164,10 +166,10 @@ var _ = Describe("Transformer", func() {
Expect(log.Value).To(Equal("9998940000000000000000")) Expect(log.Value).To(Equal("9998940000000000000000"))
}) })
It("Keeps track of contract-related addresses while transforming event data", 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) t := transformer.NewTransformer("", blockChain, db)
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"}) t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
t.SetMethods(constants.TusdContractAddress, nil) t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"})
err = t.Init() err = t.Init()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -177,18 +179,24 @@ var _ = Describe("Transformer", func() {
err = t.Execute() err = t.Execute()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
b, ok := c.TknHolderAddrs["0x1062a747393198f70F71ec65A582423Dba7E5Ab3"] b, ok := c.EmittedAddrs[common.HexToAddress("0x1062a747393198f70F71ec65A582423Dba7E5Ab3")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
b, ok = c.TknHolderAddrs["0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0"] b, ok = c.EmittedAddrs[common.HexToAddress("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
_, ok = c.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843b1234567890"] _, ok = c.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843b1234567890")]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
_, ok = c.TknHolderAddrs["0x"] _, 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)) Expect(ok).To(Equal(false))
}) })
@ -204,6 +212,17 @@ var _ = Describe("Transformer", func() {
res := test_helpers.BalanceOf{} res := test_helpers.BalanceOf{}
c, ok := t.Contracts[constants.TusdContractAddress]
Expect(ok).To(Equal(true))
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))
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x1062a747393198f70F71ec65A582423Dba7E5Ab3' AND block = '6791669'", constants.TusdContractAddress)).StructScan(&res) err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x1062a747393198f70F71ec65A582423Dba7E5Ab3' AND block = '6791669'", constants.TusdContractAddress)).StructScan(&res)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(res.Balance).To(Equal("55849938025000000000000")) Expect(res.Balance).To(Equal("55849938025000000000000"))

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,8 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/filters" "github.com/vulcanize/vulcanizedb/pkg/filters"
@ -38,13 +40,42 @@ type Contract struct {
ParsedAbi abi.ABI // Parsed abi ParsedAbi abi.ABI // Parsed abi
Events map[string]types.Event // Map of events to their names Events map[string]types.Event // Map of events to their names
Methods map[string]types.Method // Map of methods to their names Methods map[string]types.Method // Map of methods to their names
Filters map[string]filters.LogFilter // Map of event filters to their names Filters map[string]filters.LogFilter // Map of event filters to their names; used only for full sync watcher
EventAddrs map[string]bool // User-input list of account addresses to watch events for FilterArgs map[string]bool // User-input list of values to filter event logs for
MethodAddrs map[string]bool // User-input list of account addresses to poll methods for MethodArgs map[string]bool // User-input list of values to limit method polling to
TknHolderAddrs map[string]bool // List of all contract-associated addresses, populated as events are transformed EmittedAddrs map[interface{}]bool // List of all unique addresses collected from converted event logs
EmittedBytes map[interface{}]bool // List of all unique bytes collected from converted event logs
EmittedHashes map[interface{}]bool // List of all unique hashes collected from converted event logs
CreateAddrList bool // Whether or not to persist address list to postgres
} }
// Use contract info to generate event filters // If we will be calling methods that use addr, hash, or byte arrays
// as arguments then we initialize map to hold these types of values
func (c Contract) Init() *Contract {
for _, method := range c.Methods {
for _, arg := range method.Args {
switch arg.Type.T {
case abi.AddressTy:
c.EmittedAddrs = map[interface{}]bool{}
case abi.HashTy:
c.EmittedHashes = map[interface{}]bool{}
case abi.BytesTy, abi.FixedBytesTy:
c.EmittedBytes = map[interface{}]bool{}
default:
}
}
}
// 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
}
// Use contract info to generate event filters - full sync omni watcher only
func (c *Contract) GenerateFilters() error { func (c *Contract) GenerateFilters() error {
c.Filters = map[string]filters.LogFilter{} c.Filters = map[string]filters.LogFilter{}
@ -65,39 +96,45 @@ func (c *Contract) GenerateFilters() error {
return nil return nil
} }
// Returns true if address is in list of addresses to // Returns true if address is in list of arguments to
// filter events for or if no filtering is specified // filter events for or if no filtering is specified
func (c *Contract) IsEventAddr(addr string) bool { func (c *Contract) WantedEventArg(arg string) bool {
if c.EventAddrs == nil { if c.FilterArgs == nil {
return false return false
} else if len(c.EventAddrs) == 0 { } else if len(c.FilterArgs) == 0 {
return true return true
} else if a, ok := c.EventAddrs[addr]; ok { } else if a, ok := c.FilterArgs[arg]; ok {
return a return a
} }
return false return false
} }
// Returns true if address is in list of addresses to // Returns true if address is in list of arguments to
// poll methods for or if no filtering is specified // poll methods with or if no filtering is specified
func (c *Contract) IsMethodAddr(addr string) bool { func (c *Contract) WantedMethodArg(arg interface{}) bool {
if c.MethodAddrs == nil { if c.MethodArgs == nil {
return false return false
} else if len(c.MethodAddrs) == 0 { } else if len(c.MethodArgs) == 0 {
return true return true
} else if a, ok := c.MethodAddrs[addr]; ok { }
// resolve interface to one of the three types we handle as arguments
str := StringifyArg(arg)
// See if it's hex string has been filtered for
if a, ok := c.MethodArgs[str]; ok {
return a return a
} }
return false return false
} }
// Returns true if mapping value matches filtered for address or if not filter exists // Returns true if any mapping value matches filtered for address or if no filter exists
// Used to check if an event log name-value mapping should be filtered or not // Used to check if an event log name-value mapping should be filtered or not
func (c *Contract) PassesEventFilter(args map[string]string) bool { func (c *Contract) PassesEventFilter(args map[string]string) bool {
for _, arg := range args { for _, arg := range args {
if c.IsEventAddr(arg) { if c.WantedEventArg(arg) {
return true return true
} }
} }
@ -105,10 +142,47 @@ func (c *Contract) PassesEventFilter(args map[string]string) bool {
return false return false
} }
// Used to add an address to the token holder address list // Add event emitted address to our list if it passes filter and method polling is on
// if it is on the method polling list or the filter is open func (c *Contract) AddEmittedAddr(addresses ...interface{}) {
func (c *Contract) AddTokenHolderAddress(addr string) { for _, addr := range addresses {
if c.TknHolderAddrs != nil && c.IsMethodAddr(addr) { if c.WantedMethodArg(addr) && c.Methods != nil {
c.TknHolderAddrs[addr] = true c.EmittedAddrs[addr] = true
}
} }
} }
// Add event emitted hash to our list if it passes filter and method polling is on
func (c *Contract) AddEmittedHash(hashes ...interface{}) {
for _, hash := range hashes {
if c.WantedMethodArg(hash) && c.Methods != nil {
c.EmittedHashes[hash] = true
}
}
}
// Add event emitted bytes to our list if it passes filter and method polling is on
func (c *Contract) AddEmittedBytes(byteArrays ...interface{}) {
for _, bytes := range byteArrays {
if c.WantedMethodArg(bytes) && c.Methods != nil {
c.EmittedBytes[bytes] = true
}
}
}
func StringifyArg(arg interface{}) (str string) {
switch arg.(type) {
case string:
str = arg.(string)
case common.Address:
a := arg.(common.Address)
str = a.String()
case common.Hash:
a := arg.(common.Hash)
str = a.String()
case []byte:
a := arg.([]byte)
str = hexutil.Encode(a)
}
return
}

View File

@ -23,6 +23,7 @@ import (
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
) )
var _ = Describe("Contract", func() { var _ = Describe("Contract", func() {
@ -61,45 +62,45 @@ var _ = Describe("Contract", func() {
BeforeEach(func() { BeforeEach(func() {
info = &contract.Contract{} info = &contract.Contract{}
info.MethodAddrs = map[string]bool{} info.MethodArgs = map[string]bool{}
info.EventAddrs = map[string]bool{} info.FilterArgs = map[string]bool{}
}) })
It("Returns true if address is in event address filter list", func() { It("Returns true if address is in event address filter list", func() {
info.EventAddrs["testAddress1"] = true info.FilterArgs["testAddress1"] = true
info.EventAddrs["testAddress2"] = true info.FilterArgs["testAddress2"] = true
is := info.IsEventAddr("testAddress1") is := info.WantedEventArg("testAddress1")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
is = info.IsEventAddr("testAddress2") is = info.WantedEventArg("testAddress2")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
info.MethodAddrs["testAddress3"] = true info.MethodArgs["testAddress3"] = true
is = info.IsEventAddr("testAddress3") is = info.WantedEventArg("testAddress3")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
}) })
It("Returns true if event address filter is empty (no filter)", func() { It("Returns true if event address filter is empty (no filter)", func() {
is := info.IsEventAddr("testAddress1") is := info.WantedEventArg("testAddress1")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
is = info.IsEventAddr("testAddress2") is = info.WantedEventArg("testAddress2")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
}) })
It("Returns false if address is not in event address filter list", func() { It("Returns false if address is not in event address filter list", func() {
info.EventAddrs["testAddress1"] = true info.FilterArgs["testAddress1"] = true
info.EventAddrs["testAddress2"] = true info.FilterArgs["testAddress2"] = true
is := info.IsEventAddr("testAddress3") is := info.WantedEventArg("testAddress3")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
}) })
It("Returns false if event address filter is nil (block all)", func() { It("Returns false if event address filter is nil (block all)", func() {
info.EventAddrs = nil info.FilterArgs = nil
is := info.IsEventAddr("testAddress1") is := info.WantedEventArg("testAddress1")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
is = info.IsEventAddr("testAddress2") is = info.WantedEventArg("testAddress2")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
}) })
}) })
@ -107,45 +108,45 @@ var _ = Describe("Contract", func() {
Describe("IsMethodAddr", func() { Describe("IsMethodAddr", func() {
BeforeEach(func() { BeforeEach(func() {
info = &contract.Contract{} info = &contract.Contract{}
info.MethodAddrs = map[string]bool{} info.MethodArgs = map[string]bool{}
info.EventAddrs = map[string]bool{} info.FilterArgs = map[string]bool{}
}) })
It("Returns true if address is in method address filter list", func() { It("Returns true if address is in method address filter list", func() {
info.MethodAddrs["testAddress1"] = true info.MethodArgs["testAddress1"] = true
info.MethodAddrs["testAddress2"] = true info.MethodArgs["testAddress2"] = true
is := info.IsMethodAddr("testAddress1") is := info.WantedMethodArg("testAddress1")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
is = info.IsMethodAddr("testAddress2") is = info.WantedMethodArg("testAddress2")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
info.EventAddrs["testAddress3"] = true info.FilterArgs["testAddress3"] = true
is = info.IsMethodAddr("testAddress3") is = info.WantedMethodArg("testAddress3")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
}) })
It("Returns true if method address filter list is empty (no filter)", func() { It("Returns true if method address filter list is empty (no filter)", func() {
is := info.IsMethodAddr("testAddress1") is := info.WantedMethodArg("testAddress1")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
is = info.IsMethodAddr("testAddress2") is = info.WantedMethodArg("testAddress2")
Expect(is).To(Equal(true)) Expect(is).To(Equal(true))
}) })
It("Returns false if address is not in method address filter list", func() { It("Returns false if address is not in method address filter list", func() {
info.MethodAddrs["testAddress1"] = true info.MethodArgs["testAddress1"] = true
info.MethodAddrs["testAddress2"] = true info.MethodArgs["testAddress2"] = true
is := info.IsMethodAddr("testAddress3") is := info.WantedMethodArg("testAddress3")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
}) })
It("Returns false if method address filter list is nil (block all)", func() { It("Returns false if method address filter list is nil (block all)", func() {
info.MethodAddrs = nil info.MethodArgs = nil
is := info.IsMethodAddr("testAddress1") is := info.WantedMethodArg("testAddress1")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
is = info.IsMethodAddr("testAddress2") is = info.WantedMethodArg("testAddress2")
Expect(is).To(Equal(false)) Expect(is).To(Equal(false))
}) })
}) })
@ -154,14 +155,14 @@ var _ = Describe("Contract", func() {
var mapping map[string]string var mapping map[string]string
BeforeEach(func() { BeforeEach(func() {
info = &contract.Contract{} info = &contract.Contract{}
info.EventAddrs = map[string]bool{} info.FilterArgs = map[string]bool{}
mapping = map[string]string{} mapping = map[string]string{}
}) })
It("Return true if event log name-value mapping has filtered for address as a value", func() { It("Return true if event log name-value mapping has filtered for address as a value", func() {
info.EventAddrs["testAddress1"] = true info.FilterArgs["testAddress1"] = true
info.EventAddrs["testAddress2"] = true info.FilterArgs["testAddress2"] = true
mapping["testInputName1"] = "testAddress1" mapping["testInputName1"] = "testAddress1"
mapping["testInputName2"] = "testAddress2" mapping["testInputName2"] = "testAddress2"
@ -181,8 +182,8 @@ var _ = Describe("Contract", func() {
}) })
It("Return false if event log name-value mapping does not have filtered for address as a value", func() { It("Return false if event log name-value mapping does not have filtered for address as a value", func() {
info.EventAddrs["testAddress1"] = true info.FilterArgs["testAddress1"] = true
info.EventAddrs["testAddress2"] = true info.FilterArgs["testAddress2"] = true
mapping["testInputName3"] = "testAddress3" mapping["testInputName3"] = "testAddress3"
@ -191,7 +192,7 @@ var _ = Describe("Contract", func() {
}) })
It("Return false if event address filter list is nil (block all)", func() { It("Return false if event address filter list is nil (block all)", func() {
info.EventAddrs = nil info.FilterArgs = nil
mapping["testInputName1"] = "testAddress1" mapping["testInputName1"] = "testAddress1"
mapping["testInputName2"] = "testAddress2" mapping["testInputName2"] = "testAddress2"
@ -202,32 +203,33 @@ var _ = Describe("Contract", func() {
}) })
}) })
Describe("AddTokenHolderAddress", func() { Describe("AddEmittedAddr", func() {
BeforeEach(func() { BeforeEach(func() {
info = &contract.Contract{} info = &contract.Contract{}
info.EventAddrs = map[string]bool{} info.FilterArgs = map[string]bool{}
info.MethodAddrs = map[string]bool{} info.MethodArgs = map[string]bool{}
info.TknHolderAddrs = map[string]bool{} info.Methods = map[string]types.Method{}
info.EmittedAddrs = map[interface{}]bool{}
}) })
It("Adds address to list if it is on the method filter address list", func() { It("Adds address to list if it is on the method filter address list", func() {
info.MethodAddrs["testAddress2"] = true info.MethodArgs["testAddress2"] = true
info.AddTokenHolderAddress("testAddress2") info.AddEmittedAddr("testAddress2")
b := info.TknHolderAddrs["testAddress2"] b := info.EmittedAddrs["testAddress2"]
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
}) })
It("Adds address to list if method filter is empty", func() { It("Adds address to list if method filter is empty", func() {
info.AddTokenHolderAddress("testAddress2") info.AddEmittedAddr("testAddress2")
b := info.TknHolderAddrs["testAddress2"] b := info.EmittedAddrs["testAddress2"]
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
}) })
It("Does not add address to list if both filters are closed (nil)", func() { It("Does not add address to list if both filters are closed (nil)", func() {
info.EventAddrs = nil // close both info.FilterArgs = nil // close both
info.MethodAddrs = nil info.MethodArgs = nil
info.AddTokenHolderAddress("testAddress1") info.AddEmittedAddr("testAddress1")
b := info.TknHolderAddrs["testAddress1"] b := info.EmittedAddrs["testAddress1"]
Expect(b).To(Equal(false)) Expect(b).To(Equal(false))
}) })
}) })

View File

@ -136,17 +136,19 @@ func SetupTusdContract(wantedEvents, wantedMethods []string) *contract.Contract
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
return &contract.Contract{ return &contract.Contract{
Name: "TrueUSD", Name: "TrueUSD",
Address: constants.TusdContractAddress, Address: constants.TusdContractAddress,
Abi: p.Abi(), Abi: p.Abi(),
ParsedAbi: p.ParsedAbi(), ParsedAbi: p.ParsedAbi(),
StartingBlock: 6194634, StartingBlock: 6194634,
LastBlock: 6507323, LastBlock: 6507323,
Events: p.GetEvents(wantedEvents), Events: p.GetEvents(wantedEvents),
Methods: p.GetMethods(wantedMethods), Methods: p.GetMethods(wantedMethods),
EventAddrs: map[string]bool{}, MethodArgs: map[string]bool{},
MethodAddrs: map[string]bool{}, FilterArgs: map[string]bool{},
TknHolderAddrs: map[string]bool{}, EmittedAddrs: map[interface{}]bool{},
EmittedBytes: map[interface{}]bool{},
EmittedHashes: map[interface{}]bool{},
} }
} }
@ -178,10 +180,10 @@ func TearDown(db *postgres.DB) {
_, err = tx.Exec(`ALTER TABLE public.checked_headers DROP COLUMN IF EXISTS transfer_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e`) _, err = tx.Exec(`ALTER TABLE public.checked_headers DROP COLUMN IF EXISTS transfer_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e`)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DROP SCHEMA IF EXISTS full_0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E CASCADE`) _, err = tx.Exec(`DROP SCHEMA IF EXISTS full_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e CASCADE`)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DROP SCHEMA IF EXISTS light_0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E CASCADE`) _, err = tx.Exec(`DROP SCHEMA IF EXISTS light_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e CASCADE`)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = tx.Commit() err = tx.Commit()

View File

@ -17,9 +17,13 @@
package parser package parser
import ( import (
"errors"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/vulcanize/vulcanizedb/pkg/geth" "github.com/vulcanize/vulcanizedb/pkg/geth"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
) )
@ -30,7 +34,7 @@ type Parser interface {
Abi() string Abi() string
ParsedAbi() abi.ABI ParsedAbi() abi.ABI
GetMethods(wanted []string) map[string]types.Method GetMethods(wanted []string) map[string]types.Method
GetAddrMethods(wanted []string) map[string]types.Method GetSelectMethods(wanted []string) map[string]types.Method
GetEvents(wanted []string) map[string]types.Event GetEvents(wanted []string) map[string]types.Event
} }
@ -59,41 +63,47 @@ func (p *parser) ParsedAbi() abi.ABI {
// Retrieves and parses the abi string // Retrieves and parses the abi string
// for the given contract address // for the given contract address
func (p *parser) Parse(contractAddr string) error { func (p *parser) Parse(contractAddr string) error {
// If the abi is one our locally stored abis, fetch
// TODO: Allow users to pass abis through config
knownAbi, err := p.lookUp(contractAddr)
if err == nil {
p.abi = knownAbi
p.parsedAbi, err = geth.ParseAbi(knownAbi)
return err
}
// Try getting abi from etherscan
abiStr, err := p.client.GetAbi(contractAddr) abiStr, err := p.client.GetAbi(contractAddr)
if err != nil { if err != nil {
return err return err
} }
//TODO: Implement other ways to fetch abi
p.abi = abiStr p.abi = abiStr
p.parsedAbi, err = geth.ParseAbi(abiStr) p.parsedAbi, err = geth.ParseAbi(abiStr)
return err return err
} }
func (p *parser) lookUp(contractAddr string) (string, error) {
if v, ok := constants.Abis[common.HexToAddress(contractAddr)]; ok {
return v, nil
}
return "", errors.New("ABI not present in lookup tabe")
}
// Returns wanted methods, if they meet the criteria, as map of types.Methods // Returns wanted methods, if they meet the criteria, as map of types.Methods
// Empty wanted array => all methods that fit are returned // Empty wanted array => all methods that fit are returned
// Nil wanted array => no events are returned // Nil wanted array => no events are returned
func (p *parser) GetAddrMethods(wanted []string) map[string]types.Method { func (p *parser) GetSelectMethods(wanted []string) map[string]types.Method {
addrMethods := map[string]types.Method{} addrMethods := map[string]types.Method{}
if wanted == nil { if wanted == nil {
return addrMethods return nil
} }
for _, m := range p.parsedAbi.Methods { for _, m := range p.parsedAbi.Methods {
// Only return methods that have less than 3 inputs, 1 output, and wanted if okInputTypes(m, wanted) {
if len(m.Inputs) < 3 && len(m.Outputs) == 1 && (len(wanted) == 0 || stringInSlice(wanted, m.Name)) { wantedMethod := types.NewMethod(m)
addrsOnly := true addrMethods[wantedMethod.Name] = wantedMethod
for _, input := range m.Inputs {
if input.Type.T != abi.AddressTy {
addrsOnly = false
}
}
// Only return methods if inputs are all of type address and output is of the accepted types
if addrsOnly && wantType(m.Outputs[0]) {
method := types.NewMethod(m)
addrMethods[method.Name] = method
}
} }
} }
@ -138,9 +148,20 @@ func (p *parser) GetEvents(wanted []string) map[string]types.Event {
return events return events
} }
func wantType(arg abi.Argument) bool { func okReturnType(arg abi.Argument) bool {
wanted := []byte{abi.UintTy, abi.IntTy, abi.BoolTy, abi.StringTy, abi.AddressTy, abi.HashTy} wantedTypes := []byte{
for _, ty := range wanted { abi.UintTy,
abi.IntTy,
abi.BoolTy,
abi.StringTy,
abi.AddressTy,
abi.HashTy,
abi.BytesTy,
abi.FixedBytesTy,
abi.FixedPointTy,
}
for _, ty := range wantedTypes {
if arg.Type.T == ty { if arg.Type.T == ty {
return true return true
} }
@ -149,6 +170,27 @@ func wantType(arg abi.Argument) bool {
return false return false
} }
func okInputTypes(m abi.Method, wanted []string) bool {
// Only return method if it has less than 3 arguments, a single output value, and it is a method we want or we want all methods (empty 'wanted' slice)
if len(m.Inputs) < 3 && len(m.Outputs) == 1 && (len(wanted) == 0 || stringInSlice(wanted, m.Name)) {
// Only return methods if inputs are all of accepted types and output is of the accepted types
if !okReturnType(m.Outputs[0]) {
return false
}
for _, input := range m.Inputs {
switch input.Type.T {
case abi.AddressTy, abi.HashTy, abi.BytesTy, abi.FixedBytesTy:
default:
return false
}
}
return true
}
return false
}
func stringInSlice(list []string, s string) bool { func stringInSlice(list []string, s string) bool {
for _, b := range list { for _, b := range list {
if b == s { if b == s {

View File

@ -174,14 +174,14 @@ var _ = Describe("Parser", func() {
}) })
Describe("GetAddrMethods", func() { Describe("GetAddrMethods", func() {
It("Parses and returns only methods whose inputs, if any, are all addresses", func() { It("Parses and returns only methods whose inputs, if any, are all of type address, hash or []byte", func() {
contractAddr := "0xDdE2D979e8d39BB8416eAfcFC1758f3CaB2C9C72" contractAddr := "0xDdE2D979e8d39BB8416eAfcFC1758f3CaB2C9C72"
err = p.Parse(contractAddr) err = p.Parse(contractAddr)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
wanted := []string{"isApprovedForAll", "supportsInterface", "getApproved", "totalSupply", "balanceOf"} wanted := []string{"isApprovedForAll", "supportsInterface", "getApproved", "totalSupply", "balanceOf"}
methods := p.GetMethods(wanted) methods := p.GetMethods(wanted)
selectMethods := p.GetAddrMethods(wanted) selectMethods := p.GetSelectMethods(wanted)
_, ok := selectMethods["totalSupply"] _, ok := selectMethods["totalSupply"]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
@ -199,7 +199,7 @@ var _ = Describe("Parser", func() {
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
_, ok = selectMethods["supportsInterface"] _, ok = selectMethods["supportsInterface"]
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(true))
_, ok = methods["supportsInterface"] _, ok = methods["supportsInterface"]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))

View File

@ -22,7 +22,9 @@ import (
"math/big" "math/big"
"strconv" "strconv"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/core"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
@ -33,6 +35,7 @@ import (
type Poller interface { type Poller interface {
PollContract(con contract.Contract) error PollContract(con contract.Contract) error
PollContractAt(con contract.Contract, blockNumber int64) error
FetchContractData(contractAbi, contractAddress, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error FetchContractData(contractAbi, contractAddress, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error
} }
@ -50,22 +53,28 @@ func NewPoller(blockChain core.BlockChain, db *postgres.DB, mode types.Mode) *po
} }
} }
// Used to call contract's methods found in abi using list of contract-related addresses
func (p *poller) PollContract(con contract.Contract) error { func (p *poller) PollContract(con contract.Contract) error {
for i := con.StartingBlock; i <= con.LastBlock; i++ {
p.PollContractAt(con, i)
}
return nil
}
func (p *poller) PollContractAt(con contract.Contract, blockNumber int64) error {
p.contract = con p.contract = con
// Iterate over each of the contracts methods
for _, m := range con.Methods { for _, m := range con.Methods {
switch len(m.Args) { switch len(m.Args) {
case 0: case 0:
if err := p.pollNoArg(m); err != nil { if err := p.pollNoArgAt(m, blockNumber); err != nil {
return err return err
} }
case 1: case 1:
if err := p.pollSingleArg(m); err != nil { if err := p.pollSingleArgAt(m, blockNumber); err != nil {
return err return err
} }
case 2: case 2:
if err := p.pollDoubleArg(m); err != nil { if err := p.pollDoubleArgAt(m, blockNumber); err != nil {
return err return err
} }
default: default:
@ -77,61 +86,137 @@ func (p *poller) PollContract(con contract.Contract) error {
return nil return nil
} }
// Poll methods that take no arguments func (p *poller) pollNoArgAt(m types.Method, bn int64) error {
func (p *poller) pollNoArg(m types.Method) error {
result := types.Result{ result := types.Result{
Block: bn,
Method: m, Method: m,
Inputs: nil, Inputs: nil,
PgType: m.Return[0].PgType, PgType: m.Return[0].PgType,
} }
for i := p.contract.StartingBlock; i <= p.contract.LastBlock; i++ { var out interface{}
var out interface{} err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, nil, &out, bn)
err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, nil, &out, i) if err != nil {
if err != nil { return errors.New(fmt.Sprintf("poller error calling 0 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err))
return errors.New(fmt.Sprintf("poller error calling 0 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", i, m.Name, p.contract.Address, err)) }
}
strOut, err := stringify(out) strOut, err := stringify(out)
if err != nil { if err != nil {
return err return err
} }
result.Output = strOut result.Output = strOut
result.Block = i
// Persist result immediately // Persist result immediately
err = p.PersistResult(result, p.contract.Address, p.contract.Name) err = p.PersistResults([]types.Result{result}, m, p.contract.Address, p.contract.Name)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("poller error persisting 0 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", i, m.Name, p.contract.Address, err)) return errors.New(fmt.Sprintf("poller error persisting 0 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err))
}
} }
return nil return nil
} }
// Use token holder address to poll methods that take 1 address argument (e.g. balanceOf) // Use token holder address to poll methods that take 1 address argument (e.g. balanceOf)
func (p *poller) pollSingleArg(m types.Method) error { func (p *poller) pollSingleArgAt(m types.Method, bn int64) error {
result := types.Result{ result := types.Result{
Block: bn,
Method: m, Method: m,
Inputs: make([]interface{}, 1), Inputs: make([]interface{}, 1),
PgType: m.Return[0].PgType, PgType: m.Return[0].PgType,
} }
for addr := range p.contract.TknHolderAddrs { // Depending on the type of the arg choose
for i := p.contract.StartingBlock; i <= p.contract.LastBlock; i++ { // the correct argument set to iterate over
hashArgs := []common.Address{common.HexToAddress(addr)} var args map[interface{}]bool
in := make([]interface{}, len(hashArgs)) switch m.Args[0].Type.T {
strIn := make([]interface{}, len(hashArgs)) case abi.FixedBytesTy, abi.BytesTy:
for i, s := range hashArgs { args = p.contract.EmittedBytes
in[i] = s case abi.HashTy:
strIn[i] = s.String() args = p.contract.EmittedHashes
} case abi.AddressTy:
args = p.contract.EmittedAddrs
}
if len(args) == 0 { // If we haven't collected any args by now we can't call the method
return nil
}
results := make([]types.Result, 0, len(args))
for arg := range args {
in := []interface{}{arg}
strIn := []interface{}{contract.StringifyArg(arg)}
var out interface{}
err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, bn)
if err != nil {
return errors.New(fmt.Sprintf("poller error calling 1 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err))
}
strOut, err := stringify(out)
if err != nil {
return err
}
result.Inputs = strIn
result.Output = strOut
results = append(results, result)
}
// Persist results as batch
err := p.PersistResults(results, m, p.contract.Address, p.contract.Name)
if err != nil {
return errors.New(fmt.Sprintf("poller error persisting 1 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err))
}
return nil
}
// Use token holder address to poll methods that take 2 address arguments (e.g. allowance)
func (p *poller) pollDoubleArgAt(m types.Method, bn int64) error {
result := types.Result{
Block: bn,
Method: m,
Inputs: make([]interface{}, 2),
PgType: m.Return[0].PgType,
}
// Depending on the type of the args choose
// the correct argument sets to iterate over
var firstArgs map[interface{}]bool
switch m.Args[0].Type.T {
case abi.FixedBytesTy, abi.BytesTy:
firstArgs = p.contract.EmittedBytes
case abi.HashTy:
firstArgs = p.contract.EmittedHashes
case abi.AddressTy:
firstArgs = p.contract.EmittedAddrs
}
if len(firstArgs) == 0 {
return nil
}
var secondArgs map[interface{}]bool
switch m.Args[1].Type.T {
case abi.FixedBytesTy, abi.BytesTy:
secondArgs = p.contract.EmittedBytes
case abi.HashTy:
secondArgs = p.contract.EmittedHashes
case abi.AddressTy:
secondArgs = p.contract.EmittedAddrs
}
if len(secondArgs) == 0 {
return nil
}
results := make([]types.Result, 0, len(firstArgs)*len(secondArgs))
for arg1 := range firstArgs {
for arg2 := range secondArgs {
in := []interface{}{arg1, arg2}
strIn := []interface{}{contract.StringifyArg(arg1), contract.StringifyArg(arg2)}
var out interface{} var out interface{}
err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, i) err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, bn)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("poller error calling 1 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", i, m.Name, p.contract.Address, err)) return errors.New(fmt.Sprintf("poller error calling 2 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err))
} }
strOut, err := stringify(out) strOut, err := stringify(out)
@ -140,61 +225,15 @@ func (p *poller) pollSingleArg(m types.Method) error {
} }
result.Output = strOut result.Output = strOut
result.Block = i
result.Inputs = strIn result.Inputs = strIn
results = append(results, result)
err = p.PersistResult(result, p.contract.Address, p.contract.Name)
if err != nil {
return errors.New(fmt.Sprintf("poller error persisting 1 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", i, m.Name, p.contract.Address, err))
}
} }
} }
return nil err := p.PersistResults(results, m, p.contract.Address, p.contract.Name)
} if err != nil {
return errors.New(fmt.Sprintf("poller error persisting 2 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", bn, m.Name, p.contract.Address, err))
// Use token holder address to poll methods that take 2 address arguments (e.g. allowance)
func (p *poller) pollDoubleArg(m types.Method) error {
// For a large block range and address list this will take a really, really long time- maybe we should only do 1 arg methods
result := types.Result{
Method: m,
Inputs: make([]interface{}, 2),
PgType: m.Return[0].PgType,
}
for addr1 := range p.contract.TknHolderAddrs {
for addr2 := range p.contract.TknHolderAddrs {
for i := p.contract.StartingBlock; i <= p.contract.LastBlock; i++ {
hashArgs := []common.Address{common.HexToAddress(addr1), common.HexToAddress(addr2)}
in := make([]interface{}, len(hashArgs))
strIn := make([]interface{}, len(hashArgs))
for i, s := range hashArgs {
in[i] = s
strIn[i] = s.String()
}
var out interface{}
err := p.bc.FetchContractData(p.contract.Abi, p.contract.Address, m.Name, in, &out, i)
if err != nil {
return errors.New(fmt.Sprintf("poller error calling 2 argument method\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", i, m.Name, p.contract.Address, err))
}
strOut, err := stringify(out)
if err != nil {
return err
}
result.Output = strOut
result.Block = i
result.Inputs = strIn
err = p.PersistResult(result, p.contract.Address, p.contract.Name)
if err != nil {
return errors.New(fmt.Sprintf("poller error persisting 2 argument method result\r\nblock: %d, method: %s, contract: %s\r\nerr: %v", i, m.Name, p.contract.Address, err))
}
}
}
} }
return nil return nil
@ -208,19 +247,22 @@ func (p *poller) FetchContractData(contractAbi, contractAddress, method string,
func stringify(input interface{}) (string, error) { func stringify(input interface{}) (string, error) {
switch input.(type) { switch input.(type) {
case *big.Int: case *big.Int:
var b *big.Int b := input.(*big.Int)
b = input.(*big.Int)
return b.String(), nil return b.String(), nil
case common.Address: case common.Address:
var a common.Address a := input.(common.Address)
a = input.(common.Address)
return a.String(), nil return a.String(), nil
case common.Hash: case common.Hash:
var h common.Hash h := input.(common.Hash)
h = input.(common.Hash)
return h.String(), nil return h.String(), nil
case string: case string:
return input.(string), nil return input.(string), nil
case []byte:
b := hexutil.Encode(input.([]byte))
return b, nil
case byte:
b := input.(byte)
return string(b), nil
case bool: case bool:
return strconv.FormatBool(input.(bool)), nil return strconv.FormatBool(input.(bool)), nil
default: default:

View File

@ -19,6 +19,7 @@ package poller_test
import ( import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -54,10 +55,7 @@ var _ = Describe("Poller", func() {
Expect(con.Abi).To(Equal(constants.TusdAbiString)) Expect(con.Abi).To(Equal(constants.TusdAbiString))
con.StartingBlock = 6707322 con.StartingBlock = 6707322
con.LastBlock = 6707323 con.LastBlock = 6707323
con.TknHolderAddrs = map[string]bool{ con.AddEmittedAddr(common.HexToAddress("0xfE9e8709d3215310075d67E3ed32A380CCf451C8"), common.HexToAddress("0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE"))
"0xfE9e8709d3215310075d67E3ed32A380CCf451C8": true,
"0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE": true,
}
err := p.PollContract(*con) err := p.PollContract(*con)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -90,10 +88,7 @@ var _ = Describe("Poller", func() {
Expect(con.Abi).To(Equal(constants.TusdAbiString)) Expect(con.Abi).To(Equal(constants.TusdAbiString))
con.StartingBlock = 6707322 con.StartingBlock = 6707322
con.LastBlock = 6707323 con.LastBlock = 6707323
con.TknHolderAddrs = map[string]bool{ con.AddEmittedAddr(common.HexToAddress("0xfE9e8709d3215310075d67E3ed32A380CCf451C8"), common.HexToAddress("0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE"))
"0xfE9e8709d3215310075d67E3ed32A380CCf451C8": true,
"0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE": true,
}
err := p.PollContract(*con) err := p.PollContract(*con)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())

View File

@ -19,12 +19,12 @@ package repository
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/vulcanize/vulcanizedb/pkg/omni/light/repository"
"strings" "strings"
"github.com/hashicorp/golang-lru" "github.com/hashicorp/golang-lru"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
"github.com/vulcanize/vulcanizedb/pkg/omni/light/repository"
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types" "github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
) )
@ -65,12 +65,8 @@ func NewEventRepository(db *postgres.DB, mode types.Mode) *eventRepository {
// Creates table for the watched contract event if needed // Creates table for the watched contract event if needed
// Persists converted event log data into this custom table // Persists converted event log data into this custom table
func (r *eventRepository) PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error { func (r *eventRepository) PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
if logs == nil {
return errors.New("event repository error: passed a nil log slice")
}
if len(logs) == 0 { if len(logs) == 0 {
return errors.New("event repository error: passed an empty log slice") return errors.New("event repository error: passed empty logs slice")
} }
_, err := r.CreateContractSchema(contractAddr) _, err := r.CreateContractSchema(contractAddr)
if err != nil { if err != nil {
@ -112,7 +108,7 @@ func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types
pgStr = pgStr + "(header_id, token_name, raw_log, log_idx, tx_idx" pgStr = pgStr + "(header_id, token_name, raw_log, log_idx, tx_idx"
el := len(event.Values) el := len(event.Values)
// Pack the corresponding variables in a slice // Preallocate slice of needed capacity and proceed to pack variables into it in same order they appear in string
data := make([]interface{}, 0, 5+el) data := make([]interface{}, 0, 5+el)
data = append(data, data = append(data,
event.Id, event.Id,
@ -134,6 +130,7 @@ func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types
} }
pgStr = pgStr + ")" pgStr = pgStr + ")"
// Add this query to the transaction
_, err = tx.Exec(pgStr, data...) _, err = tx.Exec(pgStr, data...)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@ -141,8 +138,9 @@ func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types
} }
} }
// Mark header as checked for this eventId
eventId := strings.ToLower(eventInfo.Name + "_" + contractAddr) eventId := strings.ToLower(eventInfo.Name + "_" + contractAddr)
err = repository.MarkHeaderCheckedInTransaction(logs[0].Id, tx, eventId) err = repository.MarkHeaderCheckedInTransaction(logs[0].Id, tx, eventId) // This assumes all logs are from same block
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return err return err

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/ethereum/go-ethereum/common"
geth "github.com/ethereum/go-ethereum/core/types" geth "github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -137,15 +138,15 @@ var _ = Describe("Repository", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
}) })
It("Persists contract event log values into custom tables, adding any addresses to a growing list of contract associated addresses", func() { It("Persists contract event log values into custom tables", func() {
err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name) err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
b, ok := con.TknHolderAddrs["0x000000000000000000000000000000000000Af21"] b, ok := con.EmittedAddrs[common.HexToAddress("0x000000000000000000000000000000000000Af21")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))
b, ok = con.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"] b, ok = con.EmittedAddrs[common.HexToAddress("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391")]
Expect(ok).To(Equal(true)) Expect(ok).To(Equal(true))
Expect(b).To(Equal(true)) Expect(b).To(Equal(true))

View File

@ -30,8 +30,8 @@ import (
const methodCacheSize = 1000 const methodCacheSize = 1000
type MethodRepository interface { type MethodRepository interface {
PersistResult(method types.Result, contractAddr, contractName string) error PersistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error
CreateMethodTable(contractAddr string, method types.Result) (bool, error) CreateMethodTable(contractAddr string, method types.Method) (bool, error)
CreateContractSchema(contractAddr string) (bool, error) CreateContractSchema(contractAddr string) (bool, error)
CheckSchemaCache(key string) (interface{}, bool) CheckSchemaCache(key string) (interface{}, bool)
CheckTableCache(key string) (interface{}, bool) CheckTableCache(key string) (interface{}, bool)
@ -55,65 +55,74 @@ func NewMethodRepository(db *postgres.DB, mode types.Mode) *methodRepository {
} }
} }
func (r *methodRepository) PersistResult(method types.Result, contractAddr, contractName string) error { // Creates a schema for the contract if needed
if len(method.Args) != len(method.Inputs) { // Creates table for the contract method if needed
return errors.New("error: given number of inputs does not match number of method arguments") // Persists method polling data into this custom table
func (r *methodRepository) PersistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error {
if len(results) == 0 {
return errors.New("method repository error: passed empty results slice")
} }
if len(method.Return) != 1 {
return errors.New("error: given number of outputs does not match number of method return values")
}
_, err := r.CreateContractSchema(contractAddr) _, err := r.CreateContractSchema(contractAddr)
if err != nil { if err != nil {
return err return err
} }
_, err = r.CreateMethodTable(contractAddr, method) _, err = r.CreateMethodTable(contractAddr, methodInfo)
if err != nil { if err != nil {
return err return err
} }
return r.persistResult(method, contractAddr, contractName) return r.persistResults(results, methodInfo, contractAddr, contractName)
} }
// 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) persistResult(method types.Result, contractAddr, contractName string) error { func (r *methodRepository) persistResults(results []types.Result, methodInfo types.Method, contractAddr, contractName string) error {
// Begin postgres string tx, err := r.DB.Begin()
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_method ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(method.Name))
pgStr = pgStr + "(token_name, block"
ml := len(method.Args)
// Preallocate slice of needed size and proceed to pack variables into it in same order they appear in string
data := make([]interface{}, 0, 3+ml)
data = append(data,
contractName,
method.Block)
// Iterate over method args and return value, adding names
// to the string and pushing values to the slice
for i, arg := range method.Args {
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(arg.Name)) // Add underscore after to avoid any collisions with reserved pg words
data = append(data, method.Inputs[i])
}
pgStr = pgStr + ", returned) VALUES ($1, $2"
data = append(data, method.Output)
// For each input entry we created we add its postgres command variable to the string
for i := 0; i <= ml; i++ {
pgStr = pgStr + fmt.Sprintf(", $%d", i+3)
}
pgStr = pgStr + ")"
_, err := r.DB.Exec(pgStr, data...)
if err != nil { if err != nil {
return err return err
} }
return nil for _, result := range results {
// Begin postgres string
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_method ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(result.Name))
pgStr = pgStr + "(token_name, block"
ml := len(result.Args)
// Preallocate slice of needed capacity and proceed to pack variables into it in same order they appear in string
data := make([]interface{}, 0, 3+ml)
data = append(data,
contractName,
result.Block)
// Iterate over method args and return value, adding names
// to the string and pushing values to the slice
for i, arg := range result.Args {
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(arg.Name)) // Add underscore after to avoid any collisions with reserved pg words
data = append(data, result.Inputs[i])
}
pgStr = pgStr + ", returned) VALUES ($1, $2"
data = append(data, result.Output)
// For each input entry we created we add its postgres command variable to the string
for i := 0; i <= ml; i++ {
pgStr = pgStr + fmt.Sprintf(", $%d", i+3)
}
pgStr = pgStr + ")"
// Add this query to the transaction
_, err = tx.Exec(pgStr, data...)
if err != nil {
println("howdy")
tx.Rollback()
return err
}
}
return tx.Commit()
} }
// Checks for event table and creates it if it does not already exist // Checks for event table and creates it if it does not already exist
func (r *methodRepository) CreateMethodTable(contractAddr string, method types.Result) (bool, error) { func (r *methodRepository) CreateMethodTable(contractAddr string, method types.Method) (bool, error) {
tableID := fmt.Sprintf("%s_%s.%s_method", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(method.Name)) tableID := fmt.Sprintf("%s_%s.%s_method", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(method.Name))
// Check cache before querying pq to see if table exists // Check cache before querying pq to see if table exists
@ -139,7 +148,7 @@ func (r *methodRepository) CreateMethodTable(contractAddr string, method types.R
} }
// Creates a table for the given contract and event // Creates a table for the given contract and event
func (r *methodRepository) newMethodTable(tableID string, method types.Result) error { func (r *methodRepository) newMethodTable(tableID string, method types.Method) error {
// Begin pg string // Begin pg string
pgStr := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ", tableID) pgStr := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ", tableID)
pgStr = pgStr + "(id SERIAL, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL," pgStr = pgStr + "(id SERIAL, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL,"

View File

@ -95,11 +95,11 @@ var _ = Describe("Repository", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true)) Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult) created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true)) Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult) created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(false)) Expect(created).To(Equal(false))
}) })
@ -113,7 +113,7 @@ var _ = Describe("Repository", func() {
_, ok := dataStore.CheckTableCache(tableID) _, ok := dataStore.CheckTableCache(tableID)
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
created, err = dataStore.CreateMethodTable(con.Address, mockResult) created, err = dataStore.CreateMethodTable(con.Address, method)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true)) Expect(created).To(Equal(true))
@ -125,7 +125,7 @@ var _ = Describe("Repository", func() {
Describe("PersistResult", func() { Describe("PersistResult", func() {
It("Persists result from method polling in custom pg table", func() { It("Persists result from method polling in custom pg table", func() {
err = dataStore.PersistResult(mockResult, con.Address, con.Name) err = dataStore.PersistResults([]types.Result{mockResult}, method, con.Address, con.Name)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
scanStruct := test_helpers.BalanceOf{} scanStruct := test_helpers.BalanceOf{}
@ -142,7 +142,7 @@ var _ = Describe("Repository", func() {
}) })
It("Fails with empty result", func() { It("Fails with empty result", func() {
err = dataStore.PersistResult(types.Result{}, con.Address, con.Name) err = dataStore.PersistResults([]types.Result{}, method, con.Address, con.Name)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
}) })
}) })
@ -184,11 +184,11 @@ var _ = Describe("Repository", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true)) Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult) created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true)) Expect(created).To(Equal(true))
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult) created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, method)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(false)) Expect(created).To(Equal(false))
}) })
@ -202,7 +202,7 @@ var _ = Describe("Repository", func() {
_, ok := dataStore.CheckTableCache(tableID) _, ok := dataStore.CheckTableCache(tableID)
Expect(ok).To(Equal(false)) Expect(ok).To(Equal(false))
created, err = dataStore.CreateMethodTable(con.Address, mockResult) created, err = dataStore.CreateMethodTable(con.Address, method)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(created).To(Equal(true)) Expect(created).To(Equal(true))
@ -214,7 +214,7 @@ var _ = Describe("Repository", func() {
Describe("PersistResult", func() { Describe("PersistResult", func() {
It("Persists result from method polling in custom pg table for light sync mode vDB", func() { It("Persists result from method polling in custom pg table for light sync mode vDB", func() {
err = dataStore.PersistResult(mockResult, con.Address, con.Name) err = dataStore.PersistResults([]types.Result{mockResult}, method, con.Address, con.Name)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
scanStruct := test_helpers.BalanceOf{} scanStruct := test_helpers.BalanceOf{}
@ -231,7 +231,7 @@ var _ = Describe("Repository", func() {
}) })
It("Fails with empty result", func() { It("Fails with empty result", func() {
err = dataStore.PersistResult(types.Result{}, con.Address, con.Name) err = dataStore.PersistResults([]types.Result{}, method, con.Address, con.Name)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
}) })
}) })

View File

@ -59,7 +59,7 @@ func NewEvent(e abi.Event) Event {
fields[i].Indexed = input.Indexed fields[i].Indexed = input.Indexed
// Fill in pg type based on abi type // Fill in pg type based on abi type
switch fields[i].Type.T { switch fields[i].Type.T {
case abi.StringTy, abi.HashTy, abi.AddressTy: case abi.HashTy, abi.AddressTy:
fields[i].PgType = "CHARACTER VARYING(66)" fields[i].PgType = "CHARACTER VARYING(66)"
case abi.IntTy, abi.UintTy: case abi.IntTy, abi.UintTy:
fields[i].PgType = "DECIMAL" fields[i].PgType = "DECIMAL"
@ -71,8 +71,6 @@ func NewEvent(e abi.Event) Event {
fields[i].PgType = "TEXT[]" fields[i].PgType = "TEXT[]"
case abi.FixedPointTy: case abi.FixedPointTy:
fields[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres? fields[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres?
case abi.FunctionTy:
fields[i].PgType = "TEXT"
default: default:
fields[i].PgType = "TEXT" fields[i].PgType = "TEXT"
} }

View File

@ -48,7 +48,7 @@ func NewMethod(m abi.Method) Method {
inputs[i].Type = input.Type inputs[i].Type = input.Type
inputs[i].Indexed = input.Indexed inputs[i].Indexed = input.Indexed
switch inputs[i].Type.T { switch inputs[i].Type.T {
case abi.StringTy, abi.HashTy, abi.AddressTy: case abi.HashTy, abi.AddressTy:
inputs[i].PgType = "CHARACTER VARYING(66)" inputs[i].PgType = "CHARACTER VARYING(66)"
case abi.IntTy, abi.UintTy: case abi.IntTy, abi.UintTy:
inputs[i].PgType = "DECIMAL" inputs[i].PgType = "DECIMAL"
@ -60,8 +60,6 @@ func NewMethod(m abi.Method) Method {
inputs[i].PgType = "TEXT[]" inputs[i].PgType = "TEXT[]"
case abi.FixedPointTy: case abi.FixedPointTy:
inputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres? inputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres?
case abi.FunctionTy:
inputs[i].PgType = "TEXT"
default: default:
inputs[i].PgType = "TEXT" inputs[i].PgType = "TEXT"
} }
@ -74,7 +72,7 @@ func NewMethod(m abi.Method) Method {
outputs[i].Type = output.Type outputs[i].Type = output.Type
outputs[i].Indexed = output.Indexed outputs[i].Indexed = output.Indexed
switch outputs[i].Type.T { switch outputs[i].Type.T {
case abi.StringTy, abi.HashTy, abi.AddressTy: case abi.HashTy, abi.AddressTy:
outputs[i].PgType = "CHARACTER VARYING(66)" outputs[i].PgType = "CHARACTER VARYING(66)"
case abi.IntTy, abi.UintTy: case abi.IntTy, abi.UintTy:
outputs[i].PgType = "DECIMAL" outputs[i].PgType = "DECIMAL"
@ -86,8 +84,6 @@ func NewMethod(m abi.Method) Method {
outputs[i].PgType = "TEXT[]" outputs[i].PgType = "TEXT[]"
case abi.FixedPointTy: case abi.FixedPointTy:
outputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres? outputs[i].PgType = "MONEY" // use shopspring/decimal for fixed point numbers in go and money type in postgres?
case abi.FunctionTy:
outputs[i].PgType = "TEXT"
default: default:
outputs[i].PgType = "TEXT" outputs[i].PgType = "TEXT"
} }